Merge mozilla-inbound to mozilla-central. a=merge
authorDorel Luca <dluca@mozilla.com>
Fri, 18 May 2018 12:56:07 +0300
changeset 418803 11ee70f24ea5
parent 418779 ac730cb87faa (current diff)
parent 418802 e5e68461d391 (diff)
child 418813 42657f96c6a4
child 418849 54db0dcb08f8
push id34013
push userdluca@mozilla.com
push date2018-05-18 09:56 +0000
treeherdermozilla-central@11ee70f24ea5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone62.0a1
first release with
nightly linux32
11ee70f24ea5 / 62.0a1 / 20180518100150 / files
nightly linux64
11ee70f24ea5 / 62.0a1 / 20180518100150 / files
nightly mac
11ee70f24ea5 / 62.0a1 / 20180518100150 / files
nightly win32
11ee70f24ea5 / 62.0a1 / 20180518100150 / files
nightly win64
11ee70f24ea5 / 62.0a1 / 20180518100150 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-inbound to mozilla-central. a=merge
testing/web-platform/meta/WebIDL/ecmascript-binding/default-iterator-object.html.ini
testing/web-platform/meta/WebIDL/ecmascript-binding/iterator-prototype-object.html.ini
--- a/devtools/client/debugger/new/README.mozilla
+++ b/devtools/client/debugger/new/README.mozilla
@@ -1,13 +1,13 @@
 This is the debugger.html project output.
 See https://github.com/devtools-html/debugger.html
 
-Version 54
+Version 55
 
-Comparison: https://github.com/devtools-html/debugger.html/compare/release-53...release-54
+Comparison: https://github.com/devtools-html/debugger.html/compare/release-54...release-55
 
 Packages:
 - babel-plugin-transform-es2015-modules-commonjs @6.26.2
 - babel-preset-react @6.24.1
 - react @16.2.0
 - react-dom @16.2.0
-- webpack @3.11.0
+- webpack @3.12.0
--- a/devtools/client/debugger/new/dist/debugger.css
+++ b/devtools/client/debugger/new/dist/debugger.css
@@ -958,16 +958,99 @@ img.close::before {
   width: 100%;
   height: 100%;
   padding: 6px;
 }
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
 
+.command-bar-button {
+  appearance: none;
+  background: transparent;
+  border: none;
+  display: inline-block;
+  text-align: center;
+  position: relative;
+  padding: 0px 5px;
+  fill: currentColor;
+}
+
+.command-bar-button:focus {
+  outline: none;
+}
+
+.command-bar-button:disabled {
+  opacity: 0.8;
+  cursor: default;
+}
+
+.command-bar-button:not(.disabled):hover {
+  background: var(--theme-toolbar-background-hover);
+}
+
+:root.theme-dark .command-bar-button {
+  color: var(--theme-body-color);
+}
+
+.command-bar-button > * {
+  width: 16px;
+  height: 16px;
+  display: inline-block;
+  vertical-align: middle;
+}
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
+
+.toggle-button {
+  transform: translate(0, 0px);
+  transition: transform 0.25s ease-in-out;
+  padding: 5px;
+}
+
+.toggle-button .togglePanes {
+  vertical-align: -2px;
+}
+
+.toggle-button svg {
+  fill: var(--theme-comment);
+  vertical-align: 0;
+}
+
+:root.theme-dark .toggle-button svg {
+  fill: var(--theme-comment-alt);
+}
+
+.toggle-button.end {
+  margin-inline-end: 0px;
+  margin-inline-start: auto;
+}
+
+.toggle-button.start {
+  margin-inline-start: 0px;
+}
+
+html:not([dir="rtl"]) .toggle-button.end svg,
+html[dir="rtl"] .toggle-button.start svg {
+  transform: rotate(180deg);
+}
+
+html .toggle-button.end.vertical svg {
+  transform: rotate(-90deg);
+}
+
+.toggle-button.start.collapsed,
+.toggle-button.end.collapsed {
+  transform: rotate(180deg);
+}
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
+
 .search-shadow {
   margin: 1px;
 }
 
 .search-shadow.focused {
   box-shadow: var(--theme-focus-box-shadow-textbox);
 }
 
@@ -1665,99 +1748,16 @@ menuseparator {
 
 .function-signature .comma {
   color: var(--object-color);
 }
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
 
-.command-bar-button {
-  appearance: none;
-  background: transparent;
-  border: none;
-  display: inline-block;
-  text-align: center;
-  position: relative;
-  padding: 0px 5px;
-  fill: currentColor;
-}
-
-.command-bar-button:focus {
-  outline: none;
-}
-
-.command-bar-button:disabled {
-  opacity: 0.8;
-  cursor: default;
-}
-
-.command-bar-button:not(.disabled):hover {
-  background: var(--theme-toolbar-background-hover);
-}
-
-:root.theme-dark .command-bar-button {
-  color: var(--theme-body-color);
-}
-
-.command-bar-button > * {
-  width: 16px;
-  height: 16px;
-  display: inline-block;
-  vertical-align: middle;
-}
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
-
-.toggle-button {
-  transform: translate(0, 0px);
-  transition: transform 0.25s ease-in-out;
-  padding: 5px;
-}
-
-.toggle-button .togglePanes {
-  vertical-align: -2px;
-}
-
-.toggle-button svg {
-  fill: var(--theme-comment);
-  vertical-align: 0;
-}
-
-:root.theme-dark .toggle-button svg {
-  fill: var(--theme-comment-alt);
-}
-
-.toggle-button.end {
-  margin-inline-end: 0px;
-  margin-inline-start: auto;
-}
-
-.toggle-button.start {
-  margin-inline-start: 0px;
-}
-
-html:not([dir="rtl"]) .toggle-button.end svg,
-html[dir="rtl"] .toggle-button.start svg {
-  transform: rotate(180deg);
-}
-
-html .toggle-button.end.vertical svg {
-  transform: rotate(-90deg);
-}
-
-.toggle-button.start.collapsed,
-.toggle-button.end.collapsed {
-  transform: rotate(180deg);
-}
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
-
 .source-footer {
   background: var(--theme-body-background);
   border-top: 1px solid var(--theme-splitter-color);
   position: absolute;
   display: flex;
   bottom: 0;
   left: 0;
   right: 0;
@@ -3911,16 +3911,27 @@ img.skipPausing {
   font-family: Courier;
 }
 
 .shortcutKey,
 .shortcutLabel {
   flex: 1;
   overflow: hidden;
   text-overflow: ellipsis;
+  cursor: pointer;
+}
+
+.welcomebox__searchSources,
+.welcomebox__searchProject {
+  transition: color 0.15s linear;
+}
+
+.welcomebox__searchSources:hover,
+.welcomebox__searchProject:hover {
+  color: var(--theme-body-color);
 }
 
 .shortcutKey {
   text-align: right;
   padding-right: 10px;
   font-family: Courier;
 }
 
--- a/devtools/client/debugger/new/src/actions/breakpoints.js
+++ b/devtools/client/debugger/new/src/actions/breakpoints.js
@@ -174,21 +174,26 @@ function enableBreakpoint(location) {
     getState,
     client,
     sourceMaps
   }) => {
     const breakpoint = (0, _selectors.getBreakpoint)(getState(), location);
 
     if (!breakpoint || breakpoint.loading) {
       return;
-    }
+    } // To instantly reflect in the UI, we optimistically enable the breakpoint
+
+
+    const enabledBreakpoint = _objectSpread({}, breakpoint, {
+      disabled: false
+    });
 
     return dispatch({
       type: "ENABLE_BREAKPOINT",
-      breakpoint,
+      breakpoint: enabledBreakpoint,
       [_promise.PROMISE]: (0, _addBreakpoint2.default)(getState, client, sourceMaps, breakpoint)
     });
   };
 }
 /**
  * Disable a single breakpoint
  *
  * @memberof actions/breakpoints
--- a/devtools/client/debugger/new/src/actions/pause/paused.js
+++ b/devtools/client/debugger/new/src/actions/pause/paused.js
@@ -51,35 +51,35 @@ function paused(pauseInfo) {
     client,
     sourceMaps
   }) {
     const {
       frames,
       why,
       loadedObjects
     } = pauseInfo;
-    const rootFrame = frames.length > 0 ? frames[0] : null;
+    const topFrame = frames.length > 0 ? frames[0] : null;
 
-    if (rootFrame) {
-      const mappedFrame = await (0, _mapFrames.updateFrameLocation)(rootFrame, sourceMaps);
+    if (topFrame && why.type == "resumeLimit") {
+      const mappedFrame = await (0, _mapFrames.updateFrameLocation)(topFrame, sourceMaps);
       const source = await getOriginalSourceForFrame(getState(), mappedFrame); // Ensure that the original file has loaded if there is one.
 
       await dispatch((0, _loadSourceText.loadSourceText)(source));
 
       if ((0, _pause.shouldStep)(mappedFrame, getState(), sourceMaps)) {
         dispatch((0, _commands.command)("stepOver"));
         return;
       }
     }
 
     dispatch({
       type: "PAUSED",
       why,
       frames,
-      selectedFrameId: rootFrame ? rootFrame.id : undefined,
+      selectedFrameId: topFrame ? topFrame.id : undefined,
       loadedObjects: loadedObjects || []
     });
     const hiddenBreakpointLocation = (0, _selectors.getHiddenBreakpointLocation)(getState());
 
     if (hiddenBreakpointLocation) {
       dispatch((0, _breakpoints.removeBreakpoint)(hiddenBreakpointLocation));
     }
 
--- a/devtools/client/debugger/new/src/actions/project-text-search.js
+++ b/devtools/client/debugger/new/src/actions/project-text-search.js
@@ -1,15 +1,16 @@
 "use strict";
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
 exports.addSearchQuery = addSearchQuery;
 exports.clearSearchQuery = clearSearchQuery;
+exports.addSearchResult = addSearchResult;
 exports.clearSearchResults = clearSearchResults;
 exports.clearSearch = clearSearch;
 exports.updateSearchStatus = updateSearchStatus;
 exports.closeProjectSearch = closeProjectSearch;
 exports.searchSources = searchSources;
 exports.searchSource = searchSource;
 
 var _search = require("../workers/search/index");
@@ -38,16 +39,27 @@ function addSearchQuery(query) {
 }
 
 function clearSearchQuery() {
   return {
     type: "CLEAR_QUERY"
   };
 }
 
+function addSearchResult(sourceId, filepath, matches) {
+  return {
+    type: "ADD_SEARCH_RESULT",
+    result: {
+      sourceId,
+      filepath,
+      matches
+    }
+  };
+}
+
 function clearSearchResults() {
   return {
     type: "CLEAR_SEARCH_RESULTS"
   };
 }
 
 function clearSearch() {
   return {
@@ -100,18 +112,11 @@ function searchSource(sourceId, query) {
     }
 
     const matches = await (0, _search.findSourceMatches)(sourceRecord.toJS(), query);
 
     if (!matches.length) {
       return;
     }
 
-    dispatch({
-      type: "ADD_SEARCH_RESULT",
-      result: {
-        sourceId: sourceRecord.id,
-        filepath: sourceRecord.url,
-        matches
-      }
-    });
+    dispatch(addSearchResult(sourceRecord.id, sourceRecord.url, matches));
   };
 }
\ No newline at end of file
--- a/devtools/client/debugger/new/src/actions/sources/index.js
+++ b/devtools/client/debugger/new/src/actions/sources/index.js
@@ -1,58 +1,58 @@
 "use strict";
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
 
+var _blackbox = require("./blackbox");
+
+Object.keys(_blackbox).forEach(function (key) {
+  if (key === "default" || key === "__esModule") return;
+  Object.defineProperty(exports, key, {
+    enumerable: true,
+    get: function () {
+      return _blackbox[key];
+    }
+  });
+});
+
 var _loadSourceText = require("./loadSourceText");
 
 Object.keys(_loadSourceText).forEach(function (key) {
   if (key === "default" || key === "__esModule") return;
   Object.defineProperty(exports, key, {
     enumerable: true,
     get: function () {
       return _loadSourceText[key];
     }
   });
 });
 
-var _prettyPrint = require("./prettyPrint");
-
-Object.keys(_prettyPrint).forEach(function (key) {
-  if (key === "default" || key === "__esModule") return;
-  Object.defineProperty(exports, key, {
-    enumerable: true,
-    get: function () {
-      return _prettyPrint[key];
-    }
-  });
-});
-
 var _newSources = require("./newSources");
 
 Object.keys(_newSources).forEach(function (key) {
   if (key === "default" || key === "__esModule") return;
   Object.defineProperty(exports, key, {
     enumerable: true,
     get: function () {
       return _newSources[key];
     }
   });
 });
 
-var _blackbox = require("./blackbox");
+var _prettyPrint = require("./prettyPrint");
 
-Object.keys(_blackbox).forEach(function (key) {
+Object.keys(_prettyPrint).forEach(function (key) {
   if (key === "default" || key === "__esModule") return;
   Object.defineProperty(exports, key, {
     enumerable: true,
     get: function () {
-      return _blackbox[key];
+      return _prettyPrint[key];
     }
   });
 });
 
 var _select = require("./select");
 
 Object.keys(_select).forEach(function (key) {
   if (key === "default" || key === "__esModule") return;
--- a/devtools/client/debugger/new/src/actions/sources/loadSourceText.js
+++ b/devtools/client/debugger/new/src/actions/sources/loadSourceText.js
@@ -91,17 +91,17 @@ function loadSourceText(source) {
       deferred.resolve();
       requests.delete(id);
       return;
     }
 
     const newSource = (0, _selectors.getSource)(getState(), source.get("id")).toJS();
 
     if ((0, _devtoolsSourceMap.isOriginalId)(newSource.id) && !newSource.isWasm) {
-      const generatedSource = (0, _selectors.getGeneratedSource)(getState(), source.toJS());
+      const generatedSource = (0, _selectors.getGeneratedSource)(getState(), source);
       await dispatch(loadSourceText(generatedSource));
     }
 
     if (!newSource.isWasm) {
       await parser.setSource(newSource);
     } // signal that the action is finished
 
 
--- a/devtools/client/debugger/new/src/actions/sources/select.js
+++ b/devtools/client/debugger/new/src/actions/sources/select.js
@@ -1,13 +1,14 @@
 "use strict";
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
+exports.clearSelectedLocation = exports.setPendingSelectedLocation = exports.setSelectedLocation = undefined;
 exports.selectSourceURL = selectSourceURL;
 exports.selectSource = selectSource;
 exports.selectLocation = selectLocation;
 exports.selectSpecificLocation = selectSpecificLocation;
 exports.selectSpecificSource = selectSpecificSource;
 exports.jumpToMappedLocation = jumpToMappedLocation;
 exports.jumpToMappedSelectedLocation = jumpToMappedSelectedLocation;
 
@@ -32,44 +33,57 @@ var _location = require("../../utils/loc
 var _sourceMaps = require("../../utils/source-maps");
 
 var _selectors = require("../../selectors/index");
 
 function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; }
 
 function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
 
+const setSelectedLocation = exports.setSelectedLocation = (source, location) => ({
+  type: "SET_SELECTED_LOCATION",
+  source,
+  location
+});
+
+const setPendingSelectedLocation = exports.setPendingSelectedLocation = (url, options) => ({
+  type: "SET_PENDING_SELECTED_LOCATION",
+  url: url,
+  line: options.location ? options.location.line : null
+});
+
+const clearSelectedLocation = exports.clearSelectedLocation = () => ({
+  type: "CLEAR_SELECTED_LOCATION"
+});
 /**
  * Deterministically select a source that has a given URL. This will
  * work regardless of the connection status or if the source exists
  * yet. This exists mostly for external things to interact with the
  * debugger.
  *
  * @memberof actions/sources
  * @static
  */
+
+
 function selectSourceURL(url, options = {}) {
   return async ({
     dispatch,
     getState
   }) => {
     const source = (0, _selectors.getSourceByURL)(getState(), url);
 
     if (source) {
       const sourceId = source.id;
       const location = (0, _location.createLocation)(_objectSpread({}, options.location, {
         sourceId
       }));
       await dispatch(selectLocation(location));
     } else {
-      dispatch({
-        type: "SELECT_SOURCE_URL",
-        url: url,
-        line: options.location ? options.location.line : null
-      });
+      dispatch(setPendingSelectedLocation(url, options));
     }
   };
 }
 /**
  * @memberof actions/sources
  * @static
  */
 
@@ -101,34 +115,28 @@ function selectLocation(location) {
       // shut down too fast and it tries to display a default source.
       return;
     }
 
     const sourceRecord = (0, _selectors.getSource)(getState(), location.sourceId);
 
     if (!sourceRecord) {
       // If there is no source we deselect the current selected source
-      return dispatch({
-        type: "CLEAR_SELECTED_SOURCE"
-      });
+      return dispatch(clearSelectedLocation());
     }
 
     const activeSearch = (0, _selectors.getActiveSearch)(getState());
 
     if (activeSearch !== "file") {
       dispatch((0, _ui.closeActiveSearch)());
     }
 
     const source = sourceRecord.toJS();
     dispatch((0, _tabs.addTab)(source.url, 0));
-    dispatch({
-      type: "SELECT_SOURCE",
-      source,
-      location
-    });
+    dispatch(setSelectedLocation(source, location));
     await dispatch((0, _loadSourceText.loadSourceText)(sourceRecord));
     const selectedSource = (0, _selectors.getSelectedSource)(getState());
 
     if (!selectedSource) {
       return;
     }
 
     const sourceId = selectedSource.id;
@@ -159,34 +167,28 @@ function selectSpecificLocation(location
       // shut down too fast and it tries to display a default source.
       return;
     }
 
     const sourceRecord = (0, _selectors.getSource)(getState(), location.sourceId);
 
     if (!sourceRecord) {
       // If there is no source we deselect the current selected source
-      return dispatch({
-        type: "CLEAR_SELECTED_SOURCE"
-      });
+      return dispatch(clearSelectedLocation());
     }
 
     const activeSearch = (0, _selectors.getActiveSearch)(getState());
 
     if (activeSearch !== "file") {
       dispatch((0, _ui.closeActiveSearch)());
     }
 
     const source = sourceRecord.toJS();
     dispatch((0, _tabs.addTab)(source, 0));
-    dispatch({
-      type: "SELECT_SOURCE",
-      source,
-      location
-    });
+    dispatch(setSelectedLocation(source, location));
     await dispatch((0, _loadSourceText.loadSourceText)(sourceRecord));
     const selectedSource = (0, _selectors.getSelectedSource)(getState());
 
     if (!selectedSource) {
       return;
     }
 
     const sourceId = selectedSource.id;
--- a/devtools/client/debugger/new/src/components/Editor/Footer.js
+++ b/devtools/client/debugger/new/src/components/Editor/Footer.js
@@ -23,19 +23,17 @@ var _classnames2 = _interopRequireDefaul
 var _prefs = require("../../utils/prefs");
 
 var _source = require("../../utils/source");
 
 var _sources = require("../../reducers/sources");
 
 var _editor = require("../../utils/editor/index");
 
-var _PaneToggle = require("../shared/Button/PaneToggle");
-
-var _PaneToggle2 = _interopRequireDefault(_PaneToggle);
+var _Button = require("../shared/Button/index");
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
 class SourceFooter extends _react.PureComponent {
   prettyPrintButton() {
@@ -124,17 +122,17 @@ class SourceFooter extends _react.PureCo
     }, "C");
   }
 
   renderToggleButton() {
     if (this.props.horizontal) {
       return;
     }
 
-    return _react2.default.createElement(_PaneToggle2.default, {
+    return _react2.default.createElement(_Button.PaneToggleButton, {
       position: "end",
       collapsed: !this.props.endPanelCollapsed,
       horizontal: this.props.horizontal,
       handleClick: this.props.togglePaneCollapse
     });
   }
 
   renderCommands() {
@@ -184,19 +182,18 @@ class SourceFooter extends _react.PureCo
       className: "source-footer"
     }, this.renderCommands(), this.renderSourceSummary(), this.renderToggleButton());
   }
 
 }
 
 const mapStateToProps = state => {
   const selectedSource = (0, _selectors.getSelectedSource)(state);
-  const selectedId = selectedSource.get("id");
-  const source = selectedSource.toJS();
+  const selectedId = selectedSource.id;
   return {
     selectedSource,
-    mappedSource: (0, _sources.getGeneratedSource)(state, source),
+    mappedSource: (0, _sources.getGeneratedSource)(state, selectedSource),
     prettySource: (0, _selectors.getPrettySource)(state, selectedId),
     endPanelCollapsed: (0, _selectors.getPaneCollapse)(state, "end")
   };
 };
 
 exports.default = (0, _reactRedux.connect)(mapStateToProps, _actions2.default)(SourceFooter);
\ No newline at end of file
--- a/devtools/client/debugger/new/src/components/Editor/Preview/Popup.js
+++ b/devtools/client/debugger/new/src/components/Editor/Preview/Popup.js
@@ -63,17 +63,17 @@ const {
 
 class Popup extends _react.Component {
   constructor(...args) {
     var _temp;
 
     return _temp = super(...args), this.onMouseLeave = e => {
       const relatedTarget = e.relatedTarget;
 
-      if (relatedTarget && (relatedTarget.classList.contains("popover") || relatedTarget.classList.contains("debug-expression") || relatedTarget.classList.contains("editor-mount"))) {
+      if (relatedTarget && relatedTarget.classList && (relatedTarget.classList.contains("popover") || relatedTarget.classList.contains("debug-expression") || relatedTarget.classList.contains("editor-mount"))) {
         return;
       }
 
       this.props.onClose();
     }, _temp;
   }
 
   async componentWillMount() {
--- a/devtools/client/debugger/new/src/components/Editor/Tab.js
+++ b/devtools/client/debugger/new/src/components/Editor/Tab.js
@@ -7,19 +7,17 @@ Object.defineProperty(exports, "__esModu
 var _react = require("devtools/client/shared/vendor/react");
 
 var _react2 = _interopRequireDefault(_react);
 
 var _reactRedux = require("devtools/client/shared/vendor/react-redux");
 
 var _devtoolsContextmenu = require("devtools/client/debugger/new/dist/vendors").vendored["devtools-contextmenu"];
 
-var _Close = require("../shared/Button/Close");
-
-var _Close2 = _interopRequireDefault(_Close);
+var _Button = require("../shared/Button/index");
 
 var _actions = require("../../actions/index");
 
 var _actions2 = _interopRequireDefault(_actions);
 
 var _source = require("../../utils/source");
 
 var _clipboard = require("../../utils/clipboard");
@@ -166,17 +164,17 @@ class Tab extends _react.PureComponent {
     return _react2.default.createElement("div", {
       className: className,
       key: sourceId,
       onMouseUp: handleTabClick,
       onContextMenu: e => this.onTabContextMenu(e, sourceId),
       title: (0, _source.getFileURL)(src)
     }, sourceAnnotation, _react2.default.createElement("div", {
       className: "filename"
-    }, filename), _react2.default.createElement(_Close2.default, {
+    }, filename), _react2.default.createElement(_Button.CloseButton, {
       handleClick: onClickClose,
       tooltip: L10N.getStr("sourceTabs.closeTabButtonTooltip")
     }));
   }
 
 }
 
 const mapStateToProps = (state, {
--- a/devtools/client/debugger/new/src/components/Editor/Tabs.js
+++ b/devtools/client/debugger/new/src/components/Editor/Tabs.js
@@ -27,19 +27,17 @@ var _actions = require("../../actions/in
 var _actions2 = _interopRequireDefault(_actions);
 
 var _lodash = require("devtools/client/shared/vendor/lodash");
 
 var _Tab = require("./Tab");
 
 var _Tab2 = _interopRequireDefault(_Tab);
 
-var _PaneToggle = require("../shared/Button/PaneToggle");
-
-var _PaneToggle2 = _interopRequireDefault(_PaneToggle);
+var _Button = require("../shared/Button/index");
 
 var _Dropdown = require("../shared/Dropdown");
 
 var _Dropdown2 = _interopRequireDefault(_Dropdown);
 
 function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
@@ -169,17 +167,17 @@ class Tabs extends _react.PureComponent 
 
     return _react2.default.createElement(_Dropdown2.default, {
       panel: Panel,
       icon: icon
     });
   }
 
   renderStartPanelToggleButton() {
-    return _react2.default.createElement(_PaneToggle2.default, {
+    return _react2.default.createElement(_Button.PaneToggleButton, {
       position: "start",
       collapsed: !this.props.startPanelCollapsed,
       handleClick: this.props.togglePaneCollapse
     });
   }
 
   renderEndPanelToggleButton() {
     const {
@@ -187,17 +185,17 @@ class Tabs extends _react.PureComponent 
       endPanelCollapsed,
       togglePaneCollapse
     } = this.props;
 
     if (!horizontal) {
       return;
     }
 
-    return _react2.default.createElement(_PaneToggle2.default, {
+    return _react2.default.createElement(_Button.PaneToggleButton, {
       position: "end",
       collapsed: !endPanelCollapsed,
       handleClick: togglePaneCollapse,
       horizontal: horizontal
     });
   }
 
   render() {
--- a/devtools/client/debugger/new/src/components/QuickOpenModal.js
+++ b/devtools/client/debugger/new/src/components/QuickOpenModal.js
@@ -428,25 +428,26 @@ class QuickOpenModal extends _react.Comp
 
     if (!enabled) {
       return null;
     }
 
     const newResults = results && results.slice(0, 100);
     const items = this.highlightMatching(query, newResults || []);
     const expanded = !!items && items.length > 0;
+    const summaryMsg = this.isGotoQuery() ? L10N.getStr("shortcuts.gotoLine") : "";
     return _react2.default.createElement(_Modal2.default, {
       "in": enabled,
       handleClose: this.closeModal
     }, _react2.default.createElement(_SearchInput2.default, _extends({
       query: query,
       hasPrefix: true,
       count: this.getResultCount(),
       placeholder: L10N.getStr("sourceSearch.search"),
-      summaryMsg: "",
+      summaryMsg: summaryMsg,
       showErrorEmoji: this.shouldShowErrorEmoji(),
       onChange: this.onChange,
       onKeyDown: this.onKeyDown,
       handleClose: this.closeModal,
       expanded: expanded,
       showClose: false,
       selectedItemId: expanded && items[selectedIndex] ? items[selectedIndex].id : ""
     }, this.isSourceSearch() ? {
--- a/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoint.js
+++ b/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoint.js
@@ -13,19 +13,17 @@ var _reactDom = require("devtools/client
 var _reactDom2 = _interopRequireDefault(_reactDom);
 
 var _classnames = require("devtools/client/debugger/new/dist/vendors").vendored["classnames"];
 
 var _classnames2 = _interopRequireDefault(_classnames);
 
 var _devtoolsSourceMap = require("devtools/client/shared/source-map/index.js");
 
-var _Close = require("../shared/Button/Close");
-
-var _Close2 = _interopRequireDefault(_Close);
+var _Button = require("../shared/Button/index");
 
 var _breakpoint = require("../../utils/breakpoint/index");
 
 var _prefs = require("../../utils/prefs");
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
@@ -161,17 +159,17 @@ class Breakpoint extends _react.Componen
       line = breakpoint.generatedLocation.line;
       column = breakpoint.generatedLocation.column;
     }
 
     return _react2.default.createElement("div", {
       className: "breakpoint-line-close"
     }, _react2.default.createElement("div", {
       className: "breakpoint-line"
-    }, getBreakpointLocation(breakpoint.source, line, column)), _react2.default.createElement(_Close2.default, {
+    }, getBreakpointLocation(breakpoint.source, line, column)), _react2.default.createElement(_Button.CloseButton, {
       handleClick: onCloseClick,
       tooltip: L10N.getStr("breakpoints.removeBreakpointTooltip")
     }));
   }
 
   render() {
     const {
       breakpoint,
--- a/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoints.js
+++ b/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoints.js
@@ -151,29 +151,31 @@ class Breakpoints extends _react.Compone
     } = this.props;
 
     if (breakpoints.size == 0) {
       return;
     }
 
     const groupedBreakpoints = (0, _lodash.groupBy)((0, _lodash.sortBy)([...breakpoints.valueSeq()], bp => bp.location.line), bp => (0, _source.getRawSourceURL)(bp.source.url));
     return [...Object.keys(groupedBreakpoints).sort(sortFilenames).map(url => {
-      const file = (0, _source.getFilenameFromURL)(url);
       const groupBreakpoints = groupedBreakpoints[url].filter(bp => !bp.hidden && (bp.text || bp.originalText));
 
       if (!groupBreakpoints.length) {
         return null;
       }
 
+      const {
+        source
+      } = groupBreakpoints[0];
       return [_react2.default.createElement("div", {
         className: "breakpoint-heading",
         title: url,
         key: url,
-        onClick: () => this.props.selectSource(groupBreakpoints[0].source.id)
-      }, file), ...groupBreakpoints.map(bp => this.renderBreakpoint(bp))];
+        onClick: () => this.props.selectSource(source.id)
+      }, (0, _source.getFilename)(source)), ...groupBreakpoints.map(bp => this.renderBreakpoint(bp))];
     })];
   }
 
   render() {
     return _react2.default.createElement("div", {
       className: "pane breakpoints-list"
     }, this.renderExceptionsOptions(), this.renderBreakpoints());
   }
--- a/devtools/client/debugger/new/src/components/SecondaryPanes/EventListeners.js
+++ b/devtools/client/debugger/new/src/components/SecondaryPanes/EventListeners.js
@@ -11,19 +11,17 @@ var _react2 = _interopRequireDefault(_re
 var _reactRedux = require("devtools/client/shared/vendor/react-redux");
 
 var _actions = require("../../actions/index");
 
 var _actions2 = _interopRequireDefault(_actions);
 
 var _selectors = require("../../selectors/index");
 
-var _Close = require("../shared/Button/Close");
-
-var _Close2 = _interopRequireDefault(_Close);
+var _Button = require("../shared/Button/index");
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; }
 
 function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
 
 class EventListeners extends _react.Component {
@@ -53,17 +51,17 @@ class EventListeners extends _react.Comp
         type: "checkbox",
         className: "listener-checkbox",
         checked: checked,
         onChange: () => this.handleCheckbox(breakpoint, location)
       }), _react2.default.createElement("span", {
         className: "type"
       }, type), _react2.default.createElement("span", {
         className: "selector"
-      }, selector), breakpoint ? _react2.default.createElement(_Close2.default, {
+      }, selector), breakpoint ? _react2.default.createElement(_Button.CloseButton, {
         handleClick: ev => this.removeBreakpoint(ev, breakpoint)
       }) : "");
     };
   }
 
   handleCheckbox(breakpoint, location) {
     if (!breakpoint) {
       return this.props.addBreakpoint(location);
--- a/devtools/client/debugger/new/src/components/SecondaryPanes/Expressions.js
+++ b/devtools/client/debugger/new/src/components/SecondaryPanes/Expressions.js
@@ -21,19 +21,17 @@ var _actions = require("../../actions/in
 var _actions2 = _interopRequireDefault(_actions);
 
 var _selectors = require("../../selectors/index");
 
 var _expressions = require("../../utils/expressions");
 
 var _firefox = require("../../client/firefox");
 
-var _Close = require("../shared/Button/Close");
-
-var _Close2 = _interopRequireDefault(_Close);
+var _Button = require("../shared/Button/index");
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
 class Expressions extends _react.Component {
   constructor(props) {
@@ -144,17 +142,17 @@ class Expressions extends _react.Compone
         roots: [root],
         autoExpandDepth: 0,
         disableWrap: true,
         focusable: false,
         openLink: openLink,
         createObjectClient: grip => (0, _firefox.createObjectClient)(grip)
       }), _react2.default.createElement("div", {
         className: "expression-container__close-btn"
-      }, _react2.default.createElement(_Close2.default, {
+      }, _react2.default.createElement(_Button.CloseButton, {
         handleClick: e => this.deleteExpression(e, expression)
       }))));
     };
 
     this.state = {
       editing: false,
       editIndex: -1,
       inputValue: "",
--- a/devtools/client/debugger/new/src/components/WelcomeBox.js
+++ b/devtools/client/debugger/new/src/components/WelcomeBox.js
@@ -14,19 +14,17 @@ var _reactRedux = require("devtools/clie
 var _actions = require("../actions/index");
 
 var _actions2 = _interopRequireDefault(_actions);
 
 var _selectors = require("../selectors/index");
 
 var _text = require("../utils/text");
 
-var _PaneToggle = require("./shared/Button/PaneToggle");
-
-var _PaneToggle2 = _interopRequireDefault(_PaneToggle);
+var _Button = require("./shared/Button/index");
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
 class WelcomeBox extends _react.Component {
   renderToggleButton() {
@@ -35,17 +33,17 @@ class WelcomeBox extends _react.Componen
       endPanelCollapsed,
       togglePaneCollapse
     } = this.props;
 
     if (horizontal) {
       return;
     }
 
-    return _react2.default.createElement(_PaneToggle2.default, {
+    return _react2.default.createElement(_Button.PaneToggleButton, {
       position: "end",
       collapsed: !endPanelCollapsed,
       horizontal: horizontal,
       handleClick: togglePaneCollapse
     });
   }
 
   render() {
@@ -60,23 +58,27 @@ class WelcomeBox extends _react.Componen
     return _react2.default.createElement("div", {
       className: "welcomebox"
     }, _react2.default.createElement("div", {
       className: "alignlabel"
     }, _react2.default.createElement("div", {
       className: "shortcutFunction"
     }, _react2.default.createElement("p", {
       className: "welcomebox__searchSources",
+      role: "button",
+      tabIndex: "0",
       onClick: () => openQuickOpen()
     }, _react2.default.createElement("span", {
       className: "shortcutKey"
     }, searchSourcesShortcut), _react2.default.createElement("span", {
       className: "shortcutLabel"
     }, searchSourcesLabel)), _react2.default.createElement("p", {
       className: "welcomebox__searchProject",
+      role: "button",
+      tabIndex: "0",
       onClick: setActiveSearch.bind(null, "project")
     }, _react2.default.createElement("span", {
       className: "shortcutKey"
     }, searchProjectShortcut), _react2.default.createElement("span", {
       className: "shortcutLabel"
     }, searchProjectLabel))), this.renderToggleButton()));
   }
 
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/new/src/components/shared/Button/CloseButton.js
@@ -0,0 +1,30 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _react = require("devtools/client/shared/vendor/react");
+
+var _react2 = _interopRequireDefault(_react);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
+function CloseButton({
+  handleClick,
+  buttonClass,
+  tooltip
+}) {
+  return _react2.default.createElement("button", {
+    className: buttonClass ? `close-btn ${buttonClass}` : "close-btn",
+    onClick: handleClick,
+    title: tooltip
+  }, _react2.default.createElement("img", {
+    className: "close"
+  }));
+}
+
+exports.default = CloseButton;
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/new/src/components/shared/Button/PaneToggleButton.js
@@ -0,0 +1,52 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _react = require("devtools/client/shared/vendor/react");
+
+var _react2 = _interopRequireDefault(_react);
+
+var _classnames = require("devtools/client/debugger/new/dist/vendors").vendored["classnames"];
+
+var _classnames2 = _interopRequireDefault(_classnames);
+
+var _Svg = require("devtools/client/debugger/new/dist/vendors").vendored["Svg"];
+
+var _Svg2 = _interopRequireDefault(_Svg);
+
+var _ = require(".//index");
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
+class PaneToggleButton extends _react.PureComponent {
+  render() {
+    const {
+      position,
+      collapsed,
+      horizontal,
+      handleClick
+    } = this.props;
+    const title = !collapsed ? L10N.getStr("expandPanes") : L10N.getStr("collapsePanes");
+    return _react2.default.createElement(_.CommandBarButton, {
+      className: (0, _classnames2.default)("toggle-button", position, {
+        collapsed,
+        vertical: !horizontal
+      }),
+      onClick: () => handleClick(position, collapsed),
+      title: title
+    }, _react2.default.createElement(_Svg2.default, {
+      name: "togglePanes"
+    }));
+  }
+
+}
+
+PaneToggleButton.defaultProps = {
+  horizontal: false
+};
+exports.default = PaneToggleButton;
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/new/src/components/shared/Button/index.js
@@ -0,0 +1,28 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.PaneToggleButton = exports.debugBtn = exports.CommandBarButton = exports.CloseButton = undefined;
+
+var _CloseButton = require("./CloseButton");
+
+var _CloseButton2 = _interopRequireDefault(_CloseButton);
+
+var _CommandBarButton = require("./CommandBarButton");
+
+var _CommandBarButton2 = _interopRequireDefault(_CommandBarButton);
+
+var _PaneToggleButton = require("./PaneToggleButton");
+
+var _PaneToggleButton2 = _interopRequireDefault(_PaneToggleButton);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
+exports.CloseButton = _CloseButton2.default;
+exports.CommandBarButton = _CommandBarButton2.default;
+exports.debugBtn = _CommandBarButton.debugBtn;
+exports.PaneToggleButton = _PaneToggleButton2.default;
\ No newline at end of file
--- a/devtools/client/debugger/new/src/components/shared/Button/moz.build
+++ b/devtools/client/debugger/new/src/components/shared/Button/moz.build
@@ -3,12 +3,13 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DIRS += [
 
 ]
 
 DevToolsModules(
-    'Close.js',
+    'CloseButton.js',
     'CommandBarButton.js',
-    'PaneToggle.js',
+    'index.js',
+    'PaneToggleButton.js',
 )
--- a/devtools/client/debugger/new/src/components/shared/SearchInput.js
+++ b/devtools/client/debugger/new/src/components/shared/SearchInput.js
@@ -3,19 +3,17 @@
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
 
 var _react = require("devtools/client/shared/vendor/react");
 
 var _react2 = _interopRequireDefault(_react);
 
-var _Close = require("./Button/Close");
-
-var _Close2 = _interopRequireDefault(_Close);
+var _Button = require("./Button/index");
 
 var _Svg = require("devtools/client/debugger/new/dist/vendors").vendored["Svg"];
 
 var _Svg2 = _interopRequireDefault(_Svg);
 
 var _classnames = require("devtools/client/debugger/new/dist/vendors").vendored["classnames"];
 
 var _classnames2 = _interopRequireDefault(_classnames);
@@ -168,17 +166,17 @@ class SearchInput extends _react.Compone
     }, _react2.default.createElement("div", {
       className: (0, _classnames2.default)("search-field", size),
       role: "combobox",
       "aria-haspopup": "listbox",
       "aria-owns": "result-list",
       "aria-expanded": expanded
     }, this.renderSvg(), _react2.default.createElement("input", inputProps), summaryMsg && _react2.default.createElement("div", {
       className: "summary"
-    }, summaryMsg), this.renderNav(), showClose && _react2.default.createElement(_Close2.default, {
+    }, summaryMsg), this.renderNav(), showClose && _react2.default.createElement(_Button.CloseButton, {
       handleClick: handleClose,
       buttonClass: size
     })));
   }
 
 }
 
 SearchInput.defaultProps = {
--- a/devtools/client/debugger/new/src/reducers/pause.js
+++ b/devtools/client/debugger/new/src/reducers/pause.js
@@ -214,17 +214,17 @@ function update(state = createPauseState
         shouldPauseOnExceptions,
         shouldPauseOnCaughtExceptions
       });
 
     case "COMMAND":
       {
         return action.status === "start" ? _objectSpread({}, state, emptyPauseState, {
           command: action.command,
-          previousLocation: buildPreviousLocation(state, action)
+          previousLocation: getPauseLocation(state, action)
         }) : _objectSpread({}, state, {
           command: null
         });
       }
 
     case "RESUME":
       return _objectSpread({}, state, emptyPauseState);
 
@@ -248,27 +248,28 @@ function update(state = createPauseState
           skipPausing
         });
       }
   }
 
   return state;
 }
 
-function buildPreviousLocation(state, action) {
+function getPauseLocation(state, action) {
   const {
     frames,
     previousLocation
-  } = state;
+  } = state; // NOTE: We store the previous location so that we ensure that we
+  // do not stop at the same location twice when we step over.
 
   if (action.command !== "stepOver") {
     return null;
   }
 
-  const frame = frames && frames.length > 0 ? frames[0] : null;
+  const frame = frames && frames[0];
 
   if (!frame) {
     return previousLocation;
   }
 
   return {
     location: frame.location,
     generatedLocation: frame.generatedLocation
--- a/devtools/client/debugger/new/src/reducers/sources.js
+++ b/devtools/client/debugger/new/src/reducers/sources.js
@@ -84,35 +84,35 @@ function update(state = initialSourcesSt
         return updateSource(state, source);
       }
 
     case "ADD_SOURCES":
       {
         return action.sources.reduce((newState, source) => updateSource(newState, source), state);
       }
 
-    case "SELECT_SOURCE":
+    case "SET_SELECTED_LOCATION":
       location = _objectSpread({}, action.location, {
         url: action.source.url
       });
       _prefs.prefs.pendingSelectedLocation = location;
       return state.set("selectedLocation", _objectSpread({
         sourceId: action.source.id
       }, action.location)).set("pendingSelectedLocation", location);
 
-    case "CLEAR_SELECTED_SOURCE":
+    case "CLEAR_SELECTED_LOCATION":
       location = {
         url: ""
       };
       _prefs.prefs.pendingSelectedLocation = location;
       return state.set("selectedLocation", {
         sourceId: ""
       }).set("pendingSelectedLocation", location);
 
-    case "SELECT_SOURCE_URL":
+    case "SET_PENDING_SELECTED_LOCATION":
       location = {
         url: action.url,
         line: action.line
       };
       _prefs.prefs.pendingSelectedLocation = location;
       return state.set("pendingSelectedLocation", location);
 
     case "ADD_TAB":
@@ -348,22 +348,22 @@ const getSourcesState = state => state.s
 function getSource(state, id) {
   return getSourceInSources(getSources(state), id);
 }
 
 function getSourceByURL(state, url) {
   return getSourceByUrlInSources(state.sources.sources, url);
 }
 
-function getGeneratedSource(state, source) {
-  if (!source || !(0, _devtoolsSourceMap.isOriginalId)(source.id)) {
+function getGeneratedSource(state, sourceRecord) {
+  if (!sourceRecord || !(0, _devtoolsSourceMap.isOriginalId)(sourceRecord.id)) {
     return null;
   }
 
-  return getSource(state, (0, _devtoolsSourceMap.originalToGeneratedId)(source.id));
+  return getSource(state, (0, _devtoolsSourceMap.originalToGeneratedId)(sourceRecord.id));
 }
 
 function getPendingSelectedLocation(state) {
   return state.sources.pendingSelectedLocation;
 }
 
 function getPrettySource(state, id) {
   const source = getSource(state, id);
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemapped-scopes.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemapped-scopes.js
@@ -352,26 +352,21 @@ add_task(async function() {
       ["__WEBPACK_IMPORTED_MODULE_0__src_mod1__", "{\u2026}"],
       ["__webpack_require__", "(optimized away)"],
       ["arguments", "(unavailable)"],
       ["module", "(optimized away)"],
       "root()"
     ]
   );
 
-  await breakpointScopes(
-    dbg,
-    "webpack-functions",
-    { line: 4, column: 0 },
-    [
-      "Block",
-      ["<this>", "{\u2026}"],
-      ["arguments", "Arguments"],
-      ["x", "4"],
-      "webpackFunctions",
-      ["__webpack_exports__", "(optimized away)"],
-      ["__webpack_require__", "(optimized away)"],
-      ["arguments", "(unavailable)"],
-      ["module", "{\u2026}"],
-      ["root", "(optimized away)"]
-    ]
-  );
+  await breakpointScopes(dbg, "webpack-functions", { line: 4, column: 0 }, [
+    "Block",
+    ["<this>", "{\u2026}"],
+    ["arguments", "Arguments"],
+    ["x", "4"],
+    "webpackFunctions",
+    ["__webpack_exports__", "(optimized away)"],
+    ["__webpack_require__", "(optimized away)"],
+    ["arguments", "(unavailable)"],
+    ["module", "{\u2026}"],
+    ["root", "(optimized away)"]
+  ]);
 });
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-sources.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-sources.js
@@ -3,17 +3,17 @@
 
 // Tests that the source tree works.
 
 async function waitForSourceCount(dbg, i) {
   // We are forced to wait until the DOM nodes appear because the
   // source tree batches its rendering.
   await waitUntil(() => {
     return findAllElements(dbg, "sourceNodes").length === i;
-  });
+  }, `waiting for ${i} sources`);
 }
 
 async function assertSourceCount(dbg, count) {
   await waitForSourceCount(dbg, count);
   is(findAllElements(dbg, "sourceNodes").length, count, `${count} sources`);
 }
 
 function getLabel(dbg, index) {
@@ -31,17 +31,17 @@ add_task(async function() {
   // Expand nodes and make sure more sources appear.
   await assertSourceCount(dbg, 2);
   await clickElement(dbg, "sourceDirectoryLabel", 2);
 
   await assertSourceCount(dbg, 7);
   await clickElement(dbg, "sourceDirectoryLabel", 3);
   await assertSourceCount(dbg, 8);
 
-  const selected = waitForDispatch(dbg, "SELECT_SOURCE");
+  const selected = waitForDispatch(dbg, "SET_SELECTED_LOCATION");
   await clickElement(dbg, "sourceNode", 4);
   await selected;
   await waitForSelectedSource(dbg);
 
   // Ensure the source file clicked is now focused
   await waitForElementWithSelector(dbg, ".sources-list .focused");
 
   const focusedNode = findElementWithSelector(dbg, ".sources-list .focused");
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -261,18 +261,19 @@ Inspector.prototype = {
       await onAllPanelsUpdated;
       await this.markup.expandNode(this.selection.nodeFront);
     }
 
     // Setup the toolbar only now because it may depend on the document.
     await this.setupToolbar();
 
     // Show the 3 pane onboarding tooltip only if the inspector is visisble since the
-    // Accessibility panel initializes the Inspector.
-    if (this.show3PaneTooltip && this.toolbox.currentToolId === "inspector") {
+    // Accessibility panel initializes the Inspector and if it is not the browser toolbox.
+    if (this.show3PaneTooltip && !this.target.chrome &&
+        this.toolbox.currentToolId === "inspector") {
       this.threePaneTooltip = new ThreePaneOnboardingTooltip(this.toolbox, this.panelDoc);
     }
 
     // Log the 3 pane inspector setting on inspector open. The question we want to answer
     // is:
     // "What proportion of users use the 3 pane vs 2 pane inspector on inspector open?"
     this.telemetry.keyedScalarAdd(THREE_PANE_ENABLED_SCALAR, this.is3PaneModeEnabled, 1);
 
@@ -481,16 +482,20 @@ Inspector.prototype = {
   },
 
   /**
    * Check if the inspector should use the landscape mode.
    *
    * @return {Boolean} true if the inspector should be in landscape mode.
    */
   useLandscapeMode: function() {
+    if (!this.panelDoc) {
+      return true;
+    }
+
     let { clientWidth } = this.panelDoc.getElementById("inspector-splitter-box");
     return this.is3PaneModeEnabled && this.toolbox.hostType == Toolbox.HostType.SIDE ?
       clientWidth > SIDE_PORTAIT_MODE_WIDTH_THRESHOLD :
       clientWidth > PORTRAIT_MODE_WIDTH_THRESHOLD;
   },
 
   /**
    * Build Splitter located between the main and side area of
@@ -547,18 +552,22 @@ Inspector.prototype = {
     this.sidebar.off("destroy", this.onSidebarHidden);
   },
 
   /**
    * If Toolbox width is less than 600 px, the splitter changes its mode
    * to `horizontal` to support portrait view.
    */
   onPanelWindowResize: function() {
-    this.splitBox.setState({
-      vert: this.useLandscapeMode(),
+    window.cancelIdleCallback(this._resizeTimerId);
+    this._resizeTimerId = window.requestIdleCallback(() => {
+      this.splitBox.setState({
+        vert: this.useLandscapeMode(),
+      });
+      this.emit("inspector-resize");
     });
   },
 
   getSidebarSize: function() {
     let width;
     let height;
     let splitSidebarWidth;
 
--- a/devtools/client/inspector/test/browser_inspector_breadcrumbs_visibility.js
+++ b/devtools/client/inspector/test/browser_inspector_breadcrumbs_visibility.js
@@ -42,17 +42,19 @@ add_task(async function() {
   // No way to wait for scrolling to end (Bug 1172171)
   // Rather than wait a max time; limit test to instant scroll behavior
   inspector.breadcrumbs.arrowScrollBox.scrollBehavior = "instant";
 
   await toolbox.switchHost(Toolbox.HostType.WINDOW);
   let hostWindow = toolbox.win.parent;
   let originalWidth = hostWindow.outerWidth;
   let originalHeight = hostWindow.outerHeight;
+  let inspectorResized = inspector.once("inspector-resize");
   hostWindow.resizeTo(640, 300);
+  await inspectorResized;
 
   info("Testing transitions ltr");
   await pushPref("intl.uidirection", 0);
   await testBreadcrumbTransitions(hostWindow, inspector);
 
   info("Testing transitions rtl");
   await pushPref("intl.uidirection", 1);
   await testBreadcrumbTransitions(hostWindow, inspector);
--- a/devtools/client/shared/components/reps/reps.js
+++ b/devtools/client/shared/components/reps/reps.js
@@ -663,17 +663,17 @@ const ObjectWithURL = __webpack_require_
 const GripArray = __webpack_require__(3661);
 const GripMap = __webpack_require__(3663);
 const GripMapEntry = __webpack_require__(3664);
 const Grip = __webpack_require__(3656);
 
 // List of all registered template.
 // XXX there should be a way for extensions to register a new
 // or modify an existing rep.
-const reps = [RegExp, StyleSheet, Event, DateTime, CommentNode, ElementNode, TextNode, Attribute, Func, PromiseRep, ArrayRep, Document, DocumentType, Window, ObjectWithText, ObjectWithURL, ErrorRep, GripArray, GripMap, GripMapEntry, Grip, Undefined, Null, StringRep, Number, SymbolRep, InfinityRep, NaNRep, Accessor];
+const reps = [RegExp, StyleSheet, Event, DateTime, CommentNode, ElementNode, TextNode, Attribute, Func, PromiseRep, ArrayRep, Document, DocumentType, Window, ObjectWithText, ObjectWithURL, ErrorRep, GripArray, GripMap, GripMapEntry, Grip, Undefined, Null, StringRep, Number, SymbolRep, InfinityRep, NaNRep, Accessor, Obj];
 
 /**
  * Generic rep that is used for rendering native JS types or an object.
  * The right template used for rendering is picked automatically according
  * to the current value type. The value must be passed in as the 'object'
  * property.
  */
 const Rep = function (props) {
@@ -693,17 +693,17 @@ const Rep = function (props) {
  * debuggee object).
  *
  * @param defaultRep {React.Component} The default template
  * that should be used to render given object if none is found.
  *
  * @param noGrip {Boolean} If true, will only check reps not made for remote
  *                         objects.
  */
-function getRep(object, defaultRep = Obj, noGrip = false) {
+function getRep(object, defaultRep = Grip, noGrip = false) {
   for (let i = 0; i < reps.length; i++) {
     const rep = reps[i];
     try {
       // supportsObject could return weight (not only true/false
       // but a number), which would allow to priorities templates and
       // support better extensibility.
       if (rep.supportsObject(object, noGrip)) {
         return rep.rep;
@@ -1148,18 +1148,18 @@ function ItemRep(props) {
     mode: mode
   })), delim);
 }
 
 function getLength(object) {
   return object.length;
 }
 
-function supportsObject(object) {
-  return Array.isArray(object) || Object.prototype.toString.call(object) === "[object Arguments]";
+function supportsObject(object, noGrip = false) {
+  return noGrip && (Array.isArray(object) || Object.prototype.toString.call(object) === "[object Arguments]");
 }
 
 const maxLengthMap = new Map();
 maxLengthMap.set(MODE.SHORT, 3);
 maxLengthMap.set(MODE.LONG, 10);
 
 // Exports from this module
 module.exports = {
@@ -4760,18 +4760,18 @@ function propIterator(props, object, max
   return elements;
 }
 
 function isInterestingProp(value) {
   const type = typeof value;
   return type == "boolean" || type == "number" || type == "string" && value;
 }
 
-function supportsObject(object) {
-  return true;
+function supportsObject(object, noGrip = false) {
+  return noGrip;
 }
 
 // Exports from this module
 module.exports = {
   rep: wrapRender(ObjectRep),
   supportsObject
 };
 
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -877,16 +877,17 @@ CreateInterfaceObject(JSContext* cx, JS:
 
 static JSObject*
 CreateInterfacePrototypeObject(JSContext* cx, JS::Handle<JSObject*> global,
                                JS::Handle<JSObject*> parentProto,
                                const js::Class* protoClass,
                                const NativeProperties* properties,
                                const NativeProperties* chromeOnlyProperties,
                                const char* const* unscopableNames,
+                               const char* toStringTag,
                                bool isGlobal)
 {
   JS::Rooted<JSObject*> ourProto(cx,
     JS_NewObjectWithUniqueType(cx, Jsvalify(protoClass), parentProto));
   if (!ourProto ||
       // We don't try to define properties on the global's prototype; those
       // properties go on the global itself.
       (!isGlobal &&
@@ -912,16 +913,31 @@ CreateInterfacePrototypeObject(JSContext
       SYMBOL_TO_JSID(JS::GetWellKnownSymbol(cx, JS::SymbolCode::unscopables)));
     // Readonly and non-enumerable to match Array.prototype.
     if (!JS_DefinePropertyById(cx, ourProto, unscopableId, unscopableObj,
                                JSPROP_READONLY)) {
       return nullptr;
     }
   }
 
+  if (toStringTag) {
+    JS::Rooted<JSString*> toStringTagStr(cx,
+                                         JS_NewStringCopyZ(cx, toStringTag));
+    if (!toStringTagStr) {
+      return nullptr;
+    }
+
+    JS::Rooted<jsid> toStringTagId(cx,
+      SYMBOL_TO_JSID(JS::GetWellKnownSymbol(cx, JS::SymbolCode::toStringTag)));
+    if (!JS_DefinePropertyById(cx, ourProto, toStringTagId, toStringTagStr,
+                               JSPROP_READONLY)) {
+      return nullptr;
+    }
+  }
+
   return ourProto;
 }
 
 bool
 DefineProperties(JSContext* cx, JS::Handle<JSObject*> obj,
                  const NativeProperties* properties,
                  const NativeProperties* chromeOnlyProperties)
 {
@@ -961,16 +977,17 @@ DefineProperties(JSContext* cx, JS::Hand
 
   return true;
 }
 
 void
 CreateInterfaceObjects(JSContext* cx, JS::Handle<JSObject*> global,
                        JS::Handle<JSObject*> protoProto,
                        const js::Class* protoClass, JS::Heap<JSObject*>* protoCache,
+                       const char* toStringTag,
                        JS::Handle<JSObject*> constructorProto,
                        const js::Class* constructorClass,
                        unsigned ctorNargs, const NamedConstructor* namedConstructors,
                        JS::Heap<JSObject*>* constructorCache,
                        const NativeProperties* properties,
                        const NativeProperties* chromeOnlyProperties,
                        const char* name, bool defineOnGlobal,
                        const char* const* unscopableNames,
@@ -998,26 +1015,28 @@ CreateInterfaceObjects(JSContext* cx, JS
              "If, and only if, there is an interface prototype object we need "
              "to cache it");
   MOZ_ASSERT(bool(constructorClass) == bool(constructorCache),
              "If, and only if, there is an interface object we need to cache "
              "it");
   MOZ_ASSERT(constructorProto || !constructorClass,
              "Must have a constructor proto if we plan to create a constructor "
              "object");
+  MOZ_ASSERT(protoClass || !toStringTag,
+             "Must have a prototype object if we have a @@toStringTag");
 
   bool isChrome = nsContentUtils::ThreadsafeIsSystemCaller(cx);
 
   JS::Rooted<JSObject*> proto(cx);
   if (protoClass) {
     proto =
       CreateInterfacePrototypeObject(cx, global, protoProto, protoClass,
                                      properties,
                                      isChrome ? chromeOnlyProperties : nullptr,
-                                     unscopableNames, isGlobal);
+                                     unscopableNames, toStringTag, isGlobal);
     if (!proto) {
       return;
     }
 
     *protoCache = proto;
   }
   else {
     MOZ_ASSERT(!proto);
@@ -4091,10 +4110,45 @@ GetPerInterfaceObjectHandle(JSContext* a
    */
 
   const JS::Heap<JSObject*>& entrySlot =
     protoAndIfaceCache.EntrySlotMustExist(aSlotId);
   MOZ_ASSERT(JS::ObjectIsNotGray(entrySlot));
   return JS::Handle<JSObject*>::fromMarkedLocation(entrySlot.address());
 }
 
+namespace binding_detail {
+bool
+IsGetterEnabled(JSContext* aCx, JS::Handle<JSObject*> aObj,
+                JSJitGetterOp aGetter,
+                const Prefable<const JSPropertySpec>* aAttributes)
+{
+  MOZ_ASSERT(aAttributes);
+  MOZ_ASSERT(aAttributes->specs);
+  do {
+    if (aAttributes->isEnabled(aCx, aObj)) {
+      const JSPropertySpec* specs = aAttributes->specs;
+      do {
+        MOZ_ASSERT(specs->isAccessor());
+        if (specs->isSelfHosted()) {
+          // It won't have a JSJitGetterOp.
+          continue;
+        }
+        const JSJitInfo* info = specs->accessors.getter.native.info;
+        if (!info) {
+          continue;
+        }
+        MOZ_ASSERT(info->type() == JSJitInfo::OpType::Getter);
+        if (info->getter == aGetter) {
+          return true;
+        }
+      } while ((++specs)->name);
+    }
+  } while ((++aAttributes)->specs);
+
+  // Didn't find it.
+  return false;
+}
+
+} // namespace binding_detail
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -698,16 +698,18 @@ struct NamedConstructor
  *                null if both constructorClass and constructor are null (as in,
  *                if we're not creating an interface object at all).
  * protoClass is the JSClass to use for the interface prototype object.
  *            This is null if we should not create an interface prototype
  *            object.
  * protoCache a pointer to a JSObject pointer where we should cache the
  *            interface prototype object. This must be null if protoClass is and
  *            vice versa.
+ * toStringTag if not null, a string to define as @@toStringTag on the prototype.
+ *             Must be null if protoClass is.
  * constructorClass is the JSClass to use for the interface object.
  *                  This is null if we should not create an interface object or
  *                  if it should be a function object.
  * constructor holds the JSNative to back the interface object which should be a
  *             Function, unless constructorClass is non-null in which case it is
  *             ignored. If this is null and constructorClass is also null then
  *             we should not create an interface object at all.
  * ctorNargs is the length of the constructor function; 0 if no constructor
@@ -736,16 +738,17 @@ struct NamedConstructor
  * non-null. If constructorClass or constructor are non-null, the resulting
  * interface object will be defined on the given global with property name
  * |name|, which must also be non-null.
  */
 void
 CreateInterfaceObjects(JSContext* cx, JS::Handle<JSObject*> global,
                        JS::Handle<JSObject*> protoProto,
                        const js::Class* protoClass, JS::Heap<JSObject*>* protoCache,
+                       const char* toStringTag,
                        JS::Handle<JSObject*> interfaceProto,
                        const js::Class* constructorClass,
                        unsigned ctorNargs, const NamedConstructor* namedConstructors,
                        JS::Heap<JSObject*>* constructorCache,
                        const NativeProperties* regularProperties,
                        const NativeProperties* chromeOnlyProperties,
                        const char* name, bool defineOnGlobal,
                        const char* const* unscopableNames,
@@ -3485,14 +3488,22 @@ namespace binding_detail {
 JSObject* UnprivilegedJunkScopeOrWorkerGlobal();
 
 // Implementation of the [HTMLConstructor] extended attribute.
 bool
 HTMLConstructor(JSContext* aCx, unsigned aArgc, JS::Value* aVp,
                 constructors::id::ID aConstructorId,
                 prototypes::id::ID aProtoId,
                 CreateInterfaceObjectsMethod aCreator);
+
+// A method to test whether an attribute with the given JSJitGetterOp getter is
+// enabled in the given set of prefable proeprty specs.  For use for toJSON
+// conversions.  aObj is the object that would be used as the "this" value.
+bool
+IsGetterEnabled(JSContext* aCx, JS::Handle<JSObject*> aObj,
+                JSJitGetterOp aGetter,
+                const Prefable<const JSPropertySpec>* aAttributes);
 } // namespace binding_detail
 
 } // namespace dom
 } // namespace mozilla
 
 #endif /* mozilla_dom_BindingUtils_h__ */
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -512,17 +512,17 @@ class CGDOMJSClass(CGThing):
               },
               $*{descriptor}
             };
             static_assert(${instanceReservedSlots} == DOM_INSTANCE_RESERVED_SLOTS,
                           "Must have the right minimal number of reserved slots.");
             static_assert(${reservedSlots} >= ${slotCount},
                           "Must have enough reserved slots.");
             """,
-            name=self.descriptor.interface.identifier.name,
+            name=self.descriptor.interface.getClassName(),
             flags=classFlags,
             addProperty=ADDPROPERTY_HOOK_NAME if wantsAddProperty(self.descriptor) else 'nullptr',
             newEnumerate=newEnumerateHook,
             resolve=resolveHook,
             mayResolve=mayResolveHook,
             finalize=FINALIZE_HOOK_NAME,
             call=callHook,
             trace=traceHook,
@@ -673,17 +673,17 @@ class CGPrototypeJSClass(CGThing):
               false,
               ${prototypeID},
               ${depth},
               ${hooks},
               "[object ${name}Prototype]",
               ${protoGetter}
             };
             """,
-            name=self.descriptor.interface.identifier.name,
+            name=self.descriptor.interface.getClassName(),
             slotCount=slotCount,
             type=type,
             hooks=NativePropertyHooks(self.descriptor),
             prototypeID=prototypeID,
             depth=depth,
             protoGetter=protoGetter)
 
 
@@ -1070,25 +1070,25 @@ class CGHeaders(CGWrapper):
                 if iface.hasInterfaceObject():
                     parent = iface.parent
                     while parent and not parent.hasInterfaceObject():
                         parent = parent.parent
                     if parent:
                         ancestors.append(parent)
         interfaceDeps.extend(ancestors)
 
-        # Include parent interface headers needed for jsonifier code.
+        # Include parent interface headers needed for default toJSON code.
         jsonInterfaceParents = []
         for desc in descriptors:
-            if not desc.operations['Jsonifier']:
+            if not desc.hasDefaultToJSON:
                 continue
             parent = desc.interface.parent
             while parent:
                 parentDesc = desc.getDescriptor(parent.identifier.name)
-                if parentDesc.operations['Jsonifier']:
+                if parentDesc.hasDefaultToJSON:
                     jsonInterfaceParents.append(parentDesc.interface)
                 parent = parent.parent
         interfaceDeps.extend(jsonInterfaceParents)
 
         bindingIncludes = set(self.getDeclarationFilename(d) for d in interfaceDeps)
 
         # Grab all the implementation declaration files we need.
         implementationIncludes = set(d.headerFile for d in descriptors if d.needsHeaderInclude())
@@ -2406,30 +2406,16 @@ class MethodDefiner(PropertyDefiner):
                     "length": 0,
                     "flags": "JSPROP_ENUMERATE",
                     "condition": PropertyDefiner.getControllingCondition(stringifier, descriptor)
                 }
                 if isChromeOnly(stringifier):
                     self.chrome.append(toStringDesc)
                 else:
                     self.regular.append(toStringDesc)
-            jsonifier = descriptor.operations['Jsonifier']
-            if (jsonifier and
-                unforgeable == MemberIsUnforgeable(jsonifier, descriptor)):
-                toJSONDesc = {
-                    "name": "toJSON",
-                    "nativeName": jsonifier.identifier.name,
-                    "length": 0,
-                    "flags": "JSPROP_ENUMERATE",
-                    "condition": PropertyDefiner.getControllingCondition(jsonifier, descriptor)
-                }
-                if isChromeOnly(jsonifier):
-                    self.chrome.append(toJSONDesc)
-                else:
-                    self.regular.append(toJSONDesc)
             if (unforgeable and
                 descriptor.interface.getExtendedAttribute("Unforgeable")):
                 # Synthesize our valueOf method
                 self.regular.append({
                     "name": 'valueOf',
                     "selfHostedName": "Object_valueOf",
                     "methodInfo": False,
                     "length": 0,
@@ -2873,46 +2859,72 @@ class CGNativeProperties(CGList):
 
     def declare(self):
         return ""
 
     def define(self):
         return CGList.define(self)
 
 
-class CGJsonifyAttributesMethod(CGAbstractMethod):
-    """
-    Generate the JsonifyAttributes method for an interface descriptor
-    """
-    def __init__(self, descriptor):
+class CGCollectJSONAttributesMethod(CGAbstractMethod):
+    """
+    Generate the CollectJSONAttributes method for an interface descriptor
+    """
+    def __init__(self, descriptor, toJSONMethod):
         args = [Argument('JSContext*', 'aCx'),
                 Argument('JS::Handle<JSObject*>', 'obj'),
                 Argument('%s*' % descriptor.nativeType, 'self'),
                 Argument('JS::Rooted<JSObject*>&', 'aResult')]
-        CGAbstractMethod.__init__(self, descriptor, 'JsonifyAttributes',
+        CGAbstractMethod.__init__(self, descriptor, 'CollectJSONAttributes',
                                   'bool', args, canRunScript=True)
+        self.toJSONMethod = toJSONMethod
 
     def definition_body(self):
         ret = ''
         interface = self.descriptor.interface
+        toJSONCondition = PropertyDefiner.getControllingCondition(self.toJSONMethod,
+                                                                  self.descriptor)
         for m in interface.members:
-            if m.isAttr() and not m.isStatic() and m.type.isSerializable():
-                ret += fill(
+            if m.isAttr() and not m.isStatic() and m.type.isJSONType():
+                getAndDefine = fill(
                     """
-                    { // scope for "temp"
-                      JS::Rooted<JS::Value> temp(aCx);
-                      if (!get_${name}(aCx, obj, self, JSJitGetterCallArgs(&temp))) {
-                        return false;
-                      }
-                      if (!JS_DefineProperty(aCx, aResult, "${name}", temp, JSPROP_ENUMERATE)) {
-                        return false;
-                      }
+                    JS::Rooted<JS::Value> temp(aCx);
+                    if (!get_${name}(aCx, obj, self, JSJitGetterCallArgs(&temp))) {
+                      return false;
+                    }
+                    if (!JS_DefineProperty(aCx, aResult, "${name}", temp, JSPROP_ENUMERATE)) {
+                      return false;
                     }
                     """,
                     name=IDLToCIdentifier(m.identifier.name))
+                # Make sure we don't include things which are supposed to be
+                # disabled.  Things that either don't have disablers or whose
+                # disablers match the disablers for our toJSON method can't
+                # possibly be disabled, but other things might be.
+                condition = PropertyDefiner.getControllingCondition(m, self.descriptor)
+                if condition.hasDisablers() and condition != toJSONCondition:
+                    ret += fill(
+                        """
+                        // This is unfortunately a linear scan through sAttributes, but we
+                        // only do it for things which _might_ be disabled, which should
+                        // help keep the performance problems down.
+                        if (IsGetterEnabled(aCx, obj, (JSJitGetterOp)get_${name}, sAttributes)) {
+                          $*{getAndDefine}
+                        }
+                        """,
+                        name=IDLToCIdentifier(m.identifier.name),
+                        getAndDefine=getAndDefine)
+                else:
+                    ret += fill(
+                        """
+                        { // scope for "temp"
+                          $*{getAndDefine}
+                        }
+                        """,
+                        getAndDefine=getAndDefine)
         ret += 'return true;\n'
         return ret
 
 
 class CGCreateInterfaceObjectsMethod(CGAbstractMethod):
     """
     Generate the CreateInterfaceObjects method for an interface descriptor.
 
@@ -3046,33 +3058,41 @@ class CGCreateInterfaceObjectsMethod(CGA
             properties = "sNativeProperties.Upcast()"
         else:
             properties = "nullptr"
         if self.properties.hasChromeOnly():
             chromeProperties = "sChromeOnlyNativeProperties.Upcast()"
         else:
             chromeProperties = "nullptr"
 
+        toStringTag = self.descriptor.interface.toStringTag
+        if toStringTag:
+            toStringTag = '"%s"' % toStringTag
+        else:
+            toStringTag = "nullptr"
+
         call = fill(
             """
             JS::Heap<JSObject*>* protoCache = ${protoCache};
             JS::Heap<JSObject*>* interfaceCache = ${interfaceCache};
             dom::CreateInterfaceObjects(aCx, aGlobal, ${parentProto},
                                         ${protoClass}, protoCache,
+                                        ${toStringTag},
                                         ${constructorProto}, ${interfaceClass}, ${constructArgs}, ${namedConstructors},
                                         interfaceCache,
                                         ${properties},
                                         ${chromeProperties},
                                         ${name}, aDefineOnGlobal,
                                         ${unscopableNames},
                                         ${isGlobal});
             """,
             protoClass=protoClass,
             parentProto=parentProto,
             protoCache=protoCache,
+            toStringTag=toStringTag,
             constructorProto=constructorProto,
             interfaceClass=interfaceClass,
             constructArgs=constructArgs,
             namedConstructors=namedConstructors,
             interfaceCache=interfaceCache,
             properties=properties,
             chromeProperties=chromeProperties,
             name='"' + self.descriptor.interface.identifier.name + '"' if needInterfaceObject else "nullptr",
@@ -8610,42 +8630,42 @@ class CGMethodPromiseWrapper(CGAbstractS
             methodName=self.method.name,
             args=", ".join(arg.name for arg in self.args))
 
     @staticmethod
     def makeName(methodName):
         return methodName + "_promiseWrapper"
 
 
-class CGJsonifierMethod(CGSpecializedMethod):
+class CGDefaultToJSONMethod(CGSpecializedMethod):
     def __init__(self, descriptor, method):
-        assert method.isJsonifier()
+        assert method.isDefaultToJSON()
         CGSpecializedMethod.__init__(self, descriptor, method)
 
     def definition_body(self):
         ret = dedent("""
             JS::Rooted<JSObject*> result(cx, JS_NewPlainObject(cx));
             if (!result) {
               return false;
             }
             """)
 
         jsonDescriptors = [self.descriptor]
         interface = self.descriptor.interface.parent
         while interface:
             descriptor = self.descriptor.getDescriptor(interface.identifier.name)
-            if descriptor.operations['Jsonifier']:
+            if descriptor.hasDefaultToJSON:
                 jsonDescriptors.append(descriptor)
             interface = interface.parent
 
         # Iterate the array in reverse: oldest ancestor first
         for descriptor in jsonDescriptors[::-1]:
             ret += fill(
                 """
-                if (!${parentclass}::JsonifyAttributes(cx, obj, self, result)) {
+                if (!${parentclass}::CollectJSONAttributes(cx, obj, self, result)) {
                   return false;
                 }
                 """,
                 parentclass=toBindingNamespace(descriptor.name)
                 )
         ret += ('args.rval().setObject(*result);\n'
                 'return true;\n')
         return ret
@@ -12233,25 +12253,22 @@ def stripTrailingWhitespace(text):
     return '\n'.join(line.rstrip() for line in lines) + tail
 
 
 class MemberProperties:
     def __init__(self):
         self.isCrossOriginMethod = False
         self.isCrossOriginGetter = False
         self.isCrossOriginSetter = False
-        self.isJsonifier = False
 
 
 def memberProperties(m, descriptor):
     props = MemberProperties()
     if m.isMethod():
-        if m == descriptor.operations['Jsonifier']:
-            props.isJsonifier = True
-        elif (not m.isIdentifierLess() or m == descriptor.operations['Stringifier']):
+        if (not m.isIdentifierLess() or m == descriptor.operations['Stringifier']):
             if not m.isStatic() and descriptor.interface.hasInterfacePrototypeObject():
                 if m.getExtendedAttribute("CrossOriginCallable"):
                     props.isCrossOriginMethod = True
     elif m.isAttr():
         if not m.isStatic() and descriptor.interface.hasInterfacePrototypeObject():
             if m.getExtendedAttribute("CrossOriginReadable"):
                 props.isCrossOriginGetter = True
         if not m.readonly:
@@ -12281,34 +12298,34 @@ class CGDescriptor(CGThing):
         cgThings.append(CGGeneric(declare="typedef %s NativeType;\n" %
                                   descriptor.nativeType))
         parent = descriptor.interface.parent
         if parent:
             cgThings.append(CGGeneric("static_assert(IsRefcounted<NativeType>::value == IsRefcounted<%s::NativeType>::value,\n"
                                       "              \"Can't inherit from an interface with a different ownership model.\");\n" %
                                       toBindingNamespace(descriptor.parentPrototypeName)))
 
-        jsonifierMethod = None
+        defaultToJSONMethod = None
         crossOriginMethods, crossOriginGetters, crossOriginSetters = set(), set(), set()
         unscopableNames = list()
         for n in descriptor.interface.namedConstructors:
             cgThings.append(CGClassConstructor(descriptor, n,
                                                NamedConstructorName(n)))
         for m in descriptor.interface.members:
             if m.isMethod() and m.identifier.name == 'QueryInterface':
                 continue
 
             props = memberProperties(m, descriptor)
 
             if m.isMethod():
                 if m.getExtendedAttribute("Unscopable"):
                     assert not m.isStatic()
                     unscopableNames.append(m.identifier.name)
-                if props.isJsonifier:
-                    jsonifierMethod = m
+                if m.isDefaultToJSON():
+                    defaultToJSONMethod = m
                 elif not m.isIdentifierLess() or m == descriptor.operations['Stringifier']:
                     if m.isStatic():
                         assert descriptor.interface.hasInterfaceObject()
                         cgThings.append(CGStaticMethod(descriptor, m))
                         if m.returnsPromise():
                             cgThings.append(CGStaticMethodJitinfo(m))
                     elif descriptor.interface.hasInterfacePrototypeObject():
                         specializedMethod = CGSpecializedMethod(descriptor, m)
@@ -12359,20 +12376,19 @@ class CGDescriptor(CGThing):
                 elif m.getExtendedAttribute("LenientSetter"):
                     cgThings.append(CGSpecializedLenientSetter(descriptor, m))
                 if (not m.isStatic() and
                     descriptor.interface.hasInterfacePrototypeObject()):
                     cgThings.append(CGMemberJITInfo(descriptor, m))
             if m.isConst() and m.type.isPrimitive():
                 cgThings.append(CGConstDefinition(m))
 
-        if jsonifierMethod:
-            cgThings.append(CGJsonifyAttributesMethod(descriptor))
-            cgThings.append(CGJsonifierMethod(descriptor, jsonifierMethod))
-            cgThings.append(CGMemberJITInfo(descriptor, jsonifierMethod))
+        if defaultToJSONMethod:
+            cgThings.append(CGDefaultToJSONMethod(descriptor, defaultToJSONMethod))
+            cgThings.append(CGMemberJITInfo(descriptor, defaultToJSONMethod))
         if descriptor.interface.isNavigatorProperty():
             cgThings.append(CGConstructNavigatorObject(descriptor))
 
         if descriptor.concrete and not descriptor.proxy:
             if wantsAddProperty(descriptor):
                 cgThings.append(CGAddPropertyHook(descriptor))
 
             # Always have a finalize hook, regardless of whether the class
@@ -12386,16 +12402,21 @@ class CGDescriptor(CGThing):
         if descriptor.interface.isJSImplemented():
             for m in clearableCachedAttrs(descriptor):
                 cgThings.append(CGJSImplClearCachedValueMethod(descriptor, m))
 
         properties = PropertyArrays(descriptor)
         cgThings.append(CGGeneric(define=str(properties)))
         cgThings.append(CGNativeProperties(descriptor, properties))
 
+        if defaultToJSONMethod:
+            # Now that we know about our property arrays, we can
+            # output our "collect attribute values" method, which uses those.
+            cgThings.append(CGCollectJSONAttributesMethod(descriptor, defaultToJSONMethod))
+
         if descriptor.interface.hasInterfaceObject():
             cgThings.append(CGClassConstructor(descriptor,
                                                descriptor.interface.ctor()))
             cgThings.append(CGInterfaceObjectJSClass(descriptor, properties))
             cgThings.append(CGNamedConstructors(descriptor))
 
         cgThings.append(CGLegacyCallHook(descriptor))
         if descriptor.interface.getExtendedAttribute("NeedResolve"):
@@ -14687,19 +14708,16 @@ class CGBindingImplClass(CGClass):
                     return
             if name == "LegacyCaller":
                 if op.isIdentifierLess():
                     # XXXbz I wish we were consistent about our renaming here.
                     name = "LegacyCall"
                 else:
                     # We already added this method
                     return
-            if name == "Jsonifier":
-                # We already added this method
-                return
             self.methodDecls.append(
                 CGNativeMember(descriptor, op,
                                name,
                                (returnType, args),
                                descriptor.getExtendedAttributes(op)))
         # Sort things by name so we get stable ordering in the output.
         ops = descriptor.operations.items()
         ops.sort(key=lambda x: x[0])
--- a/dom/bindings/Configuration.py
+++ b/dom/bindings/Configuration.py
@@ -412,37 +412,38 @@ class Descriptor(DescriptorProvider):
             'IndexedGetter': None,
             'IndexedSetter': None,
             'IndexedDeleter': None,
             'NamedGetter': None,
             'NamedSetter': None,
             'NamedDeleter': None,
             'Stringifier': None,
             'LegacyCaller': None,
-            'Jsonifier': None
             }
 
-        # Stringifiers and jsonifiers need to be set up whether an interface is
+        self.hasDefaultToJSON = False
+
+        # Stringifiers need to be set up whether an interface is
         # concrete or not, because they're actually prototype methods and hence
         # can apply to instances of descendant interfaces.  Legacy callers and
         # named/indexed operations only need to be set up on concrete
         # interfaces, since they affect the JSClass we end up using, not the
         # prototype object.
         def addOperation(operation, m):
             if not self.operations[operation]:
                 self.operations[operation] = m
 
         # Since stringifiers go on the prototype, we only need to worry
         # about our own stringifier, not those of our ancestor interfaces.
         if not self.interface.isExternal():
             for m in self.interface.members:
                 if m.isMethod() and m.isStringifier():
                     addOperation('Stringifier', m)
-                if m.isMethod() and m.isJsonifier():
-                    addOperation('Jsonifier', m)
+                if m.isMethod() and m.isDefaultToJSON():
+                    self.hasDefaultToJSON = True
 
         if self.concrete:
             self.proxy = False
             iface = self.interface
             for m in iface.members:
                 # Don't worry about inheriting legacycallers either: in
                 # practice these are on most-derived prototypes.
                 if m.isMethod() and m.isLegacycaller():
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -679,17 +679,17 @@ def convertExposedAttrToGlobalNameSet(ex
 
 def globalNameSetToExposureSet(globalScope, nameSet, exposureSet):
     for name in nameSet:
         exposureSet.update(globalScope.globalNameMapping[name])
 
 
 class IDLInterfaceOrNamespace(IDLObjectWithScope, IDLExposureMixins):
     def __init__(self, location, parentScope, name, parent, members,
-                 isKnownNonPartial):
+                 isKnownNonPartial, toStringTag):
         assert isinstance(parentScope, IDLScope)
         assert isinstance(name, IDLUnresolvedIdentifier)
         assert isKnownNonPartial or not parent
         assert isKnownNonPartial or len(members) == 0
 
         self.parent = None
         self._callback = False
         self._finished = False
@@ -717,16 +717,18 @@ class IDLInterfaceOrNamespace(IDLObjectW
         # members and those of ancestor interfaces.
         self.totalMembersInSlots = 0
         # Tracking of the number of own own members we have in slots
         self._ownMembersInSlots = 0
         # If this is an iterator interface, we need to know what iterable
         # interface we're iterating for in order to get its nativeType.
         self.iterableInterface = None
 
+        self.toStringTag = toStringTag
+
         IDLObjectWithScope.__init__(self, location, parentScope, name)
         IDLExposureMixins.__init__(self, location)
 
         if isKnownNonPartial:
             self.setNonPartial(location, parent, members)
 
     def ctor(self):
         identifier = IDLUnresolvedIdentifier(self.location, "constructor",
@@ -1012,20 +1014,19 @@ class IDLInterfaceOrNamespace(IDLObjectW
             # spec would otherwise require us to synthesize and is
             # missing the ones we plan to synthesize.
             if not any(m.isMethod() and m.isStringifier() for m in self.members):
                 raise WebIDLError("Unforgeable interface %s does not have a "
                                   "stringifier" % self.identifier.name,
                                   [self.location])
 
             for m in self.members:
-                if ((m.isMethod() and m.isJsonifier()) or
-                    m.identifier.name == "toJSON"):
+                if m.identifier.name == "toJSON":
                     raise WebIDLError("Unforgeable interface %s has a "
-                                      "jsonifier so we won't be able to add "
+                                      "toJSON so we won't be able to add "
                                       "one ourselves" % self.identifier.name,
                                       [self.location, m.location])
 
                 if m.identifier.name == "valueOf" and not m.isStatic():
                     raise WebIDLError("Unforgeable interface %s has a valueOf "
                                       "member so we won't be able to add one "
                                       "ourselves" % self.identifier.name,
                                       [self.location, m.location])
@@ -1110,25 +1111,22 @@ class IDLInterfaceOrNamespace(IDLObjectW
             if member.isGetter():
                 memberType = "getters"
             elif member.isSetter():
                 memberType = "setters"
             elif member.isDeleter():
                 memberType = "deleters"
             elif member.isStringifier():
                 memberType = "stringifiers"
-            elif member.isJsonifier():
-                memberType = "jsonifiers"
             elif member.isLegacycaller():
                 memberType = "legacycallers"
             else:
                 continue
 
-            if (memberType != "stringifiers" and memberType != "legacycallers" and
-                memberType != "jsonifiers"):
+            if (memberType != "stringifiers" and memberType != "legacycallers"):
                 if member.isNamed():
                     memberType = "named " + memberType
                 else:
                     assert member.isIndexed()
                     memberType = "indexed " + memberType
 
             if memberType in specialMembersSeen:
                 raise WebIDLError("Multiple " + memberType + " on %s" % (self),
@@ -1574,26 +1572,34 @@ class IDLInterfaceOrNamespace(IDLObjectW
 
     conditionExtendedAttributes = [ "Pref", "ChromeOnly", "Func",
                                     "SecureContext" ]
     def isExposedConditionally(self, exclusions=[]):
         return any(((not a in exclusions) and self.getExtendedAttribute(a)) for a in self.conditionExtendedAttributes)
 
 class IDLInterface(IDLInterfaceOrNamespace):
     def __init__(self, location, parentScope, name, parent, members,
-                 isKnownNonPartial):
+                 isKnownNonPartial, classNameOverride=None,
+                 toStringTag=None):
         IDLInterfaceOrNamespace.__init__(self, location, parentScope, name,
-                                         parent, members, isKnownNonPartial)
+                                         parent, members, isKnownNonPartial,
+                                         toStringTag)
+        self.classNameOverride = classNameOverride
 
     def __str__(self):
         return "Interface '%s'" % self.identifier.name
 
     def isInterface(self):
         return True
 
+    def getClassName(self):
+        if self.classNameOverride:
+            return self.classNameOverride
+        return self.identifier.name
+
     def addExtendedAttributes(self, attrs):
         for attr in attrs:
             identifier = attr.identifier()
 
             # Special cased attrs
             if identifier == "TreatNonCallableAsNull":
                 raise WebIDLError("TreatNonCallableAsNull cannot be specified on interfaces",
                                   [attr.location, self.location])
@@ -1761,17 +1767,18 @@ class IDLInterface(IDLInterfaceOrNamespa
 
             attrlist = attr.listValue()
             self._extendedAttrDict[identifier] = attrlist if len(attrlist) else True
 
 
 class IDLNamespace(IDLInterfaceOrNamespace):
     def __init__(self, location, parentScope, name, members, isKnownNonPartial):
         IDLInterfaceOrNamespace.__init__(self, location, parentScope, name,
-                                         None, members, isKnownNonPartial)
+                                         None, members, isKnownNonPartial,
+                                         toStringTag=None)
 
     def __str__(self):
         return "Namespace '%s'" % self.identifier.name
 
     def isNamespace(self):
         return True
 
     def addExtendedAttributes(self, attrs):
@@ -2144,17 +2151,17 @@ class IDLType(IDLObject):
 
     def isFloat(self):
         return False
 
     def isUnrestricted(self):
         # Should only call this on float types
         assert self.isFloat()
 
-    def isSerializable(self):
+    def isJSONType(self):
         return False
 
     def tag(self):
         assert False  # Override me!
 
     def treatNonCallableAsNull(self):
         assert self.tag() == IDLType.Tags.callback
         return self.nullable() and self.inner.callback._treatNonCallableAsNull
@@ -2342,18 +2349,18 @@ class IDLNullableType(IDLParametrizedTyp
         return self.inner.isNonCallbackInterface()
 
     def isEnum(self):
         return self.inner.isEnum()
 
     def isUnion(self):
         return self.inner.isUnion()
 
-    def isSerializable(self):
-        return self.inner.isSerializable()
+    def isJSONType(self):
+        return self.inner.isJSONType()
 
     def tag(self):
         return self.inner.tag()
 
     def complete(self, scope):
         self.inner = self.inner.complete(scope)
         if self.inner.nullable():
             raise WebIDLError("The inner type of a nullable type must not be "
@@ -2422,18 +2429,18 @@ class IDLSequenceType(IDLParametrizedTyp
         return False
 
     def isInterface(self):
         return False
 
     def isEnum(self):
         return False
 
-    def isSerializable(self):
-        return self.inner.isSerializable()
+    def isJSONType(self):
+        return self.inner.isJSONType()
 
     def tag(self):
         return IDLType.Tags.sequence
 
     def complete(self, scope):
         self.inner = self.inner.complete(scope)
         self.name = self.inner.name + "Sequence"
         return self
@@ -2468,16 +2475,19 @@ class IDLRecordType(IDLParametrizedType)
         return isinstance(other, IDLRecordType) and self.inner == other.inner
 
     def __str__(self):
         return self.keyType.__str__() + self.inner.__str__() + "Record"
 
     def isRecord(self):
         return True
 
+    def isJSONType(self):
+        return self.inner.isJSONType()
+
     def tag(self):
         return IDLType.Tags.record
 
     def complete(self, scope):
         self.inner = self.inner.complete(scope)
         self.name = self.keyType.name + self.inner.name + "Record"
         return self
 
@@ -2517,18 +2527,18 @@ class IDLUnionType(IDLType):
         return self.name.__hash__()
 
     def isVoid(self):
         return False
 
     def isUnion(self):
         return True
 
-    def isSerializable(self):
-        return all(m.isSerializable() for m in self.memberTypes)
+    def isJSONType(self):
+        return all(m.isJSONType() for m in self.memberTypes)
 
     def includesRestrictedFloat(self):
         return any(t.includesRestrictedFloat() for t in self.memberTypes)
 
     def tag(self):
         return IDLType.Tags.union
 
     def resolveType(self, parentScope):
@@ -2660,16 +2670,19 @@ class IDLTypedefType(IDLType):
         return self.inner.isDOMString()
 
     def isUSVString(self):
         return self.inner.isUSVString()
 
     def isVoid(self):
         return self.inner.isVoid()
 
+    def isJSONType(self):
+        return self.inner.isJSONType()
+
     def isSequence(self):
         return self.inner.isSequence()
 
     def isRecord(self):
         return self.inner.isRecord()
 
     def isReadableStream(self):
         return self.inner.isReadableStream()
@@ -2801,25 +2814,30 @@ class IDLWrapperType(IDLType):
         return self.isInterface() and self.inner.isCallback()
 
     def isNonCallbackInterface(self):
         return self.isInterface() and not self.inner.isCallback()
 
     def isEnum(self):
         return isinstance(self.inner, IDLEnum)
 
-    def isSerializable(self):
+    def isJSONType(self):
         if self.isInterface():
             if self.inner.isExternal():
                 return False
-            return any(m.isMethod() and m.isJsonifier() for m in self.inner.members)
+            iface = self.inner
+            while iface:
+                if any(m.isMethod() and m.isToJSON() for m in self.inner.members):
+                    return True
+                iface = iface.parent
+            return False
         elif self.isEnum():
             return True
         elif self.isDictionary():
-            return all(m.type.isSerializable() for m in self.inner.members)
+            return all(m.type.isJSONType() for m in self.inner.members)
         else:
             raise WebIDLError("IDLWrapperType wraps type %s that we don't know if "
                               "is serializable" % type(self.inner), [self.location])
 
     def resolveType(self, parentScope):
         assert isinstance(parentScope, IDLScope)
         self.inner.resolve(parentScope)
 
@@ -3095,18 +3113,18 @@ class IDLBuiltinType(IDLType):
                 self._typeTag == IDLBuiltinType.Types.unrestricted_float or
                 self._typeTag == IDLBuiltinType.Types.unrestricted_double)
 
     def isUnrestricted(self):
         assert self.isFloat()
         return (self._typeTag == IDLBuiltinType.Types.unrestricted_float or
                 self._typeTag == IDLBuiltinType.Types.unrestricted_double)
 
-    def isSerializable(self):
-        return self.isPrimitive() or self.isString() or self.isDate()
+    def isJSONType(self):
+        return self.isPrimitive() or self.isString() or self.isObject()
 
     def includesRestrictedFloat(self):
         return self.isFloat() and not self.isUnrestricted()
 
     def tag(self):
         return IDLBuiltinType.TagLookup[self._typeTag]
 
     def isDistinguishableFrom(self, other):
@@ -4655,17 +4673,17 @@ class IDLMethod(IDLInterfaceMember, IDLS
         'Neither',
         'Named',
         'Indexed'
     )
 
     def __init__(self, location, identifier, returnType, arguments,
                  static=False, getter=False, setter=False,
                  deleter=False, specialType=NamedOrIndexed.Neither,
-                 legacycaller=False, stringifier=False, jsonifier=False,
+                 legacycaller=False, stringifier=False,
                  maplikeOrSetlikeOrIterable=None, htmlConstructor=False):
         # REVIEW: specialType is NamedOrIndexed -- wow, this is messed up.
         IDLInterfaceMember.__init__(self, location, identifier,
                                     IDLInterfaceMember.Tags.Method)
 
         self._hasOverloads = False
 
         assert isinstance(returnType, IDLType)
@@ -4680,18 +4698,16 @@ class IDLMethod(IDLInterfaceMember, IDLS
         assert isinstance(setter, bool)
         self._setter = setter
         assert isinstance(deleter, bool)
         self._deleter = deleter
         assert isinstance(legacycaller, bool)
         self._legacycaller = legacycaller
         assert isinstance(stringifier, bool)
         self._stringifier = stringifier
-        assert isinstance(jsonifier, bool)
-        self._jsonifier = jsonifier
         assert maplikeOrSetlikeOrIterable is None or isinstance(maplikeOrSetlikeOrIterable, IDLMaplikeOrSetlikeOrIterableBase)
         self.maplikeOrSetlikeOrIterable = maplikeOrSetlikeOrIterable
         assert isinstance(htmlConstructor, bool)
         # The identifier of a HTMLConstructor must be 'constructor'.
         assert not htmlConstructor or identifier.name == "constructor"
         self._htmlConstructor = htmlConstructor
         self._specialType = specialType
         self._unforgeable = False
@@ -4729,22 +4745,16 @@ class IDLMethod(IDLInterfaceMember, IDLS
             assert not arguments[1].optional and not arguments[1].variadic
 
         if self._stringifier:
             assert len(self._overloads) == 1
             overload = self._overloads[0]
             assert len(overload.arguments) == 0
             assert overload.returnType == BuiltinTypes[IDLBuiltinType.Types.domstring]
 
-        if self._jsonifier:
-            assert len(self._overloads) == 1
-            overload = self._overloads[0]
-            assert len(overload.arguments) == 0
-            assert overload.returnType == BuiltinTypes[IDLBuiltinType.Types.object]
-
     def isStatic(self):
         return self._static
 
     def forceStatic(self):
         self._static = True
 
     def isGetter(self):
         return self._getter
@@ -4766,33 +4776,35 @@ class IDLMethod(IDLInterfaceMember, IDLS
         return self._specialType == IDLMethod.NamedOrIndexed.Indexed
 
     def isLegacycaller(self):
         return self._legacycaller
 
     def isStringifier(self):
         return self._stringifier
 
-    def isJsonifier(self):
-        return self._jsonifier
+    def isToJSON(self):
+        return self.identifier.name == "toJSON"
+
+    def isDefaultToJSON(self):
+        return self.isToJSON() and self.getExtendedAttribute("Default")
 
     def isMaplikeOrSetlikeOrIterableMethod(self):
         """
         True if this method was generated as part of a
         maplike/setlike/etc interface (e.g. has/get methods)
         """
         return self.maplikeOrSetlikeOrIterable is not None
 
     def isSpecial(self):
         return (self.isGetter() or
                 self.isSetter() or
                 self.isDeleter() or
                 self.isLegacycaller() or
-                self.isStringifier() or
-                self.isJsonifier())
+                self.isStringifier())
 
     def isHTMLConstructor(self):
         return self._htmlConstructor
 
     def hasOverloads(self):
         return self._hasOverloads
 
     def isIdentifierLess(self):
@@ -4838,18 +4850,16 @@ class IDLMethod(IDLInterfaceMember, IDLS
         assert not self.isGetter()
         assert not method.isGetter()
         assert not self.isSetter()
         assert not method.isSetter()
         assert not self.isDeleter()
         assert not method.isDeleter()
         assert not self.isStringifier()
         assert not method.isStringifier()
-        assert not self.isJsonifier()
-        assert not method.isJsonifier()
         assert not self.isHTMLConstructor()
         assert not method.isHTMLConstructor()
 
         return self
 
     def signatures(self):
         return [(overload.returnType, overload.arguments) for overload in
                 self._overloads]
@@ -4962,16 +4972,29 @@ class IDLMethod(IDLInterfaceMember, IDLS
                               [overloadWithPromiseReturnType.location])
 
         if self.getExtendedAttribute("StaticClassOverride") and not \
            (self.identifier.scope.isJSImplemented() and self.isStatic()):
             raise WebIDLError("StaticClassOverride can be applied to static"
                               " methods on JS-implemented classes only.",
                               [self.location])
 
+        # Ensure that toJSON methods satisfy the spec constraints on them.
+        if self.identifier.name == "toJSON":
+            if len(self.signatures()) != 1:
+                raise WebIDLError("toJSON method has multiple overloads",
+                                  [self._overloads[0].location,
+                                   self._overloads[1].location])
+            if len(self.signatures()[0][1]) != 0:
+                raise WebIDLError("toJSON method has arguments",
+                                  [self.location])
+            if not self.signatures()[0][0].isJSONType():
+                raise WebIDLError("toJSON method has non-JSON return type",
+                                  [self.location])
+
     def overloadsForArgCount(self, argc):
         return [overload for overload in self._overloads if
                 len(overload.arguments) == argc or
                 (len(overload.arguments) > argc and
                  all(arg.optional for arg in overload.arguments[argc:])) or
                 (len(overload.arguments) < argc and
                  len(overload.arguments) > 0 and
                  overload.arguments[-1].variadic)]
@@ -5095,16 +5118,24 @@ class IDLMethod(IDLInterfaceMember, IDLS
             if not attr.noArguments():
                 raise WebIDLError("[CEReactions] must take no arguments",
                                   [attr.location])
 
             if self.isSpecial() and not self.isSetter() and not self.isDeleter():
                 raise WebIDLError("[CEReactions] is only allowed on operation, "
                                   "attribute, setter, and deleter",
                                   [attr.location, self.location])
+        elif identifier == "Default":
+            if not attr.noArguments():
+                raise WebIDLError("[Default] must take no arguments",
+                                  [attr.location])
+
+            if not self.isToJSON():
+                raise WebIDLError("[Default] is only allowed on toJSON operations",
+                                  [attr.location, self.location])
         elif (identifier == "Throws" or
               identifier == "CanOOM" or
               identifier == "NewObject" or
               identifier == "ChromeOnly" or
               identifier == "Pref" or
               identifier == "Deprecated" or
               identifier == "Func" or
               identifier == "SecureContext" or
@@ -5282,17 +5313,16 @@ class Tokenizer(object):
         "typedef": "TYPEDEF",
         "implements": "IMPLEMENTS",
         "const": "CONST",
         "null": "NULL",
         "true": "TRUE",
         "false": "FALSE",
         "serializer": "SERIALIZER",
         "stringifier": "STRINGIFIER",
-        "jsonifier": "JSONIFIER",
         "unrestricted": "UNRESTRICTED",
         "attribute": "ATTRIBUTE",
         "readonly": "READONLY",
         "inherit": "INHERIT",
         "static": "STATIC",
         "getter": "GETTER",
         "setter": "SETTER",
         "deleter": "DELETER",
@@ -6113,29 +6143,16 @@ class Parser(Tokenizer):
                                              allowDoubleUnderscore=True)
         method = IDLMethod(self.getLocation(p, 1),
                            identifier,
                            returnType=BuiltinTypes[IDLBuiltinType.Types.domstring],
                            arguments=[],
                            stringifier=True)
         p[0] = method
 
-    def p_Jsonifier(self, p):
-        """
-            Operation : JSONIFIER SEMICOLON
-        """
-        identifier = IDLUnresolvedIdentifier(BuiltinLocation("<auto-generated-identifier>"),
-                                             "__jsonifier", allowDoubleUnderscore=True)
-        method = IDLMethod(self.getLocation(p, 1),
-                           identifier,
-                           returnType=BuiltinTypes[IDLBuiltinType.Types.object],
-                           arguments=[],
-                           jsonifier=True)
-        p[0] = method
-
     def p_QualifierStatic(self, p):
         """
             Qualifier : STATIC
         """
         p[0] = [IDLInterfaceMember.Special.Static]
 
     def p_QualifierStringifier(self, p):
         """
@@ -6279,17 +6296,16 @@ class Parser(Tokenizer):
                          | MAPLIKE
                          | PARTIAL
                          | REQUIRED
                          | SERIALIZER
                          | SETLIKE
                          | SETTER
                          | STATIC
                          | STRINGIFIER
-                         | JSONIFIER
                          | TYPEDEF
                          | UNRESTRICTED
                          | NAMESPACE
         """
         p[0] = p[1]
 
     def p_AttributeName(self, p):
         """
@@ -6431,17 +6447,16 @@ class Parser(Tokenizer):
                   | OCTET
                   | OPTIONAL
                   | SEQUENCE
                   | RECORD
                   | SETTER
                   | SHORT
                   | STATIC
                   | STRINGIFIER
-                  | JSONIFIER
                   | TRUE
                   | TYPEDEF
                   | UNSIGNED
                   | VOID
         """
         pass
 
     def p_OtherOrComma(self, p):
@@ -6947,19 +6962,22 @@ class Parser(Tokenizer):
                     return IDLExtendedAttribute(iface.location, (str, ))
                 nextMethod = IDLMethod(
                     iface.location,
                     IDLUnresolvedIdentifier(iface.location, "next"),
                     BuiltinTypes[IDLBuiltinType.Types.object], [])
                 nextMethod.addExtendedAttributes([simpleExtendedAttr("Throws")])
                 itr_ident = IDLUnresolvedIdentifier(iface.location,
                                                     iface.identifier.name + "Iterator")
+                toStringTag = iface.identifier.name + " Iterator"
                 itr_iface = IDLInterface(iface.location, self.globalScope(),
                                          itr_ident, None, [nextMethod],
-                                         isKnownNonPartial=True)
+                                         isKnownNonPartial=True,
+                                         classNameOverride=toStringTag,
+                                         toStringTag=toStringTag)
                 itr_iface.addExtendedAttributes([simpleExtendedAttr("NoInterfaceObject")])
                 # Make sure the exposure set for the iterator interface is the
                 # same as the exposure set for the iterable interface, because
                 # we're going to generate methods on the iterable that return
                 # instances of the iterator.
                 itr_iface._exposureGlobalNames = set(iface._exposureGlobalNames)
                 # Always append generated iterable interfaces after the
                 # interface they're a member of, otherwise nativeType generation
--- a/dom/bindings/parser/tests/test_cereactions.py
+++ b/dom/bindings/parser/tests/test_cereactions.py
@@ -126,22 +126,8 @@ def WebIDLTest(parser, harness):
         """)
         results = parser.finish()
     except:
         threw = True
 
     harness.ok(threw,
                "Should have thrown for [CEReactions] used on a stringifier")
 
-    parser = parser.reset()
-    threw = False
-    try:
-        parser.parse("""
-            interface Foo {
-              [CEReactions] jsonifier;
-            };
-        """)
-
-        results = parser.finish()
-    except:
-        threw = True
-
-    harness.ok(threw, "Should have thrown for [CEReactions] used on a jsonifier")
new file mode 100644
--- /dev/null
+++ b/dom/bindings/parser/tests/test_toJSON.py
@@ -0,0 +1,59 @@
+def WebIDLTest(parser, harness):
+    threw = False
+    try:
+        parser.parse(
+            """
+            interface Test {
+              object toJSON();
+            };
+            """)
+        results = parser.finish()
+    except:
+        threw = True
+    harness.ok(not threw, "Should allow a toJSON method.")
+
+    parser = parser.reset()
+    threw = False
+    try:
+        parser.parse(
+            """
+            interface Test {
+              object toJSON(object arg);
+              object toJSON(long arg);
+            };
+            """)
+        results = parser.finish()
+    except:
+        threw = True
+    harness.ok(threw, "Should not allow overloads of a toJSON method.")
+
+    parser = parser.reset()
+    threw = False
+    try:
+        parser.parse(
+            """
+            interface Test {
+              object toJSON(object arg);
+            };
+            """)
+        results = parser.finish()
+    except:
+        threw = True
+    harness.ok(threw, "Should not allow a toJSON method with arguments.")
+
+    parser = parser.reset()
+    threw = False
+    try:
+        parser.parse(
+            """
+            interface Test {
+              any toJSON();
+            };
+            """)
+        results = parser.finish()
+    except:
+        threw = True
+    harness.ok(threw, "Should not allow a toJSON method with a non-JSON return type.")
+
+    # We should probably write some tests here about what types are
+    # considered JSON types.  Bug 1462537.
--- a/dom/bindings/test/TestBindingHeader.h
+++ b/dom/bindings/test/TestBindingHeader.h
@@ -940,22 +940,22 @@ public:
   uint32_t UnforgeableAttr2();
   uint32_t UnforgeableMethod();
   uint32_t UnforgeableMethod2();
   void Stringify(nsString&);
   void PassRenamedInterface(nsRenamedInterface&);
   TestInterface* PutForwardsAttr();
   TestInterface* PutForwardsAttr2();
   TestInterface* PutForwardsAttr3();
-  void GetJsonifierShouldSkipThis(JSContext*, JS::MutableHandle<JS::Value>);
-  void SetJsonifierShouldSkipThis(JSContext*, JS::Rooted<JS::Value>&);
-  TestParentInterface* JsonifierShouldSkipThis2();
-  void SetJsonifierShouldSkipThis2(TestParentInterface&);
-  TestCallbackInterface* JsonifierShouldSkipThis3();
-  void SetJsonifierShouldSkipThis3(TestCallbackInterface&);
+  void GetToJSONShouldSkipThis(JSContext*, JS::MutableHandle<JS::Value>);
+  void SetToJSONShouldSkipThis(JSContext*, JS::Rooted<JS::Value>&);
+  TestParentInterface* ToJSONShouldSkipThis2();
+  void SetToJSONShouldSkipThis2(TestParentInterface&);
+  TestCallbackInterface* ToJSONShouldSkipThis3();
+  void SetToJSONShouldSkipThis3(TestCallbackInterface&);
   void ThrowingMethod(ErrorResult& aRv);
   bool GetThrowingAttr(ErrorResult& aRv) const;
   void SetThrowingAttr(bool arg, ErrorResult& aRv);
   bool GetThrowingGetterAttr(ErrorResult& aRv) const;
   void SetThrowingGetterAttr(bool arg);
   bool ThrowingSetterAttr() const;
   void SetThrowingSetterAttr(bool arg, ErrorResult& aRv);
   void CanOOMMethod(OOMReporter& aRv);
--- a/dom/bindings/test/TestCodeGen.webidl
+++ b/dom/bindings/test/TestCodeGen.webidl
@@ -958,20 +958,20 @@ interface TestInterface {
   [CEReactions] void ceReactionsMethodOverload(DOMString bar);
   [CEReactions] attribute boolean ceReactionsAttr;
   legacycaller short(unsigned long arg1, TestInterface arg2);
   void passArgsWithDefaults(optional long arg1,
                             optional TestInterface? arg2 = null,
                             optional Dict arg3, optional double arg4 = 5.0,
                             optional float arg5);
 
-  attribute any jsonifierShouldSkipThis;
-  attribute TestParentInterface jsonifierShouldSkipThis2;
-  attribute TestCallbackInterface jsonifierShouldSkipThis3;
-  jsonifier;
+  attribute any toJSONShouldSkipThis;
+  attribute TestParentInterface toJSONShouldSkipThis2;
+  attribute TestCallbackInterface toJSONShouldSkipThis3;
+  [Default] object toJSON();
 
   attribute byte dashed-attribute;
   void dashed-method();
 
   // [NonEnumerable] tests
   [NonEnumerable]
   attribute boolean nonEnumerableAttr;
   [NonEnumerable]
--- a/dom/bindings/test/TestExampleGen.webidl
+++ b/dom/bindings/test/TestExampleGen.webidl
@@ -791,20 +791,20 @@ interface TestExampleInterface {
   [CEReactions] void ceReactionsMethodOverload();
   [CEReactions] void ceReactionsMethodOverload(DOMString bar);
   [CEReactions] attribute boolean ceReactionsAttr;
   legacycaller short(unsigned long arg1, TestInterface arg2);
   void passArgsWithDefaults(optional long arg1,
                             optional TestInterface? arg2 = null,
                             optional Dict arg3, optional double arg4 = 5.0,
                             optional float arg5);
-  attribute any jsonifierShouldSkipThis;
-  attribute TestParentInterface jsonifierShouldSkipThis2;
-  attribute TestCallbackInterface jsonifierShouldSkipThis3;
-  jsonifier;
+  attribute any toJSONShouldSkipThis;
+  attribute TestParentInterface toJSONShouldSkipThis2;
+  attribute TestCallbackInterface toJSONShouldSkipThis3;
+  [Default] object toJSON();
 
   attribute byte dashed-attribute;
   void dashed-method();
 
   // [NonEnumerable] tests
   [NonEnumerable]
   attribute boolean nonEnumerableAttr;
   [NonEnumerable]
--- a/dom/bindings/test/TestJSImplGen.webidl
+++ b/dom/bindings/test/TestJSImplGen.webidl
@@ -809,20 +809,20 @@ interface TestJSImplInterface {
   // now, because we always pass in the caller principal anyway.
   //  [NeedsSubjectPrincipal] void needsSubjectPrincipalMethod();
   //  [NeedsSubjectPrincipal] attribute boolean needsSubjectPrincipalAttr;
   // legacycaller short(unsigned long arg1, TestInterface arg2);
   void passArgsWithDefaults(optional long arg1,
                             optional TestInterface? arg2 = null,
                             optional Dict arg3, optional double arg4 = 5.0,
                             optional float arg5);
-  attribute any jsonifierShouldSkipThis;
-  attribute TestParentInterface jsonifierShouldSkipThis2;
-  attribute TestCallbackInterface jsonifierShouldSkipThis3;
-  jsonifier;
+  attribute any toJSONShouldSkipThis;
+  attribute TestParentInterface toJSONShouldSkipThis2;
+  attribute TestCallbackInterface toJSONShouldSkipThis3;
+  [Default] object toJSON();
 
   attribute byte dashed-attribute;
   void dashed-method();
 
   // [NonEnumerable] tests
   [NonEnumerable]
   attribute boolean nonEnumerableAttr;
   [NonEnumerable]
--- a/dom/bindings/test/test_iterable.html
+++ b/dom/bindings/test/test_iterable.html
@@ -166,17 +166,17 @@
        var entry = entries_itr.next()
        is(key.value, undefined, "IterableDouble: Key iterator value should be undefined");
        is(key.done, true, "IterableDouble: Key iterator done should be true");
        is(value.value, undefined, "IterableDouble: Value iterator value should be undefined");
        is(value.done, true, "IterableDouble: Value iterator done should be true");
        is(entry.value, undefined, "IterableDouble: Entry iterator value should be undefined");
        is(entry.done, true, "IterableDouble: Entry iterator done should be true");
        is(Object.prototype.toString.call(Object.getPrototypeOf(key_itr)),
-          "[object TestInterfaceIterableDoubleIteratorPrototype]",
+          "[object TestInterfaceIterableDouble Iterator]",
           "iterator prototype should have the right brand");
 
        // Simple dual type iterable creation and functionality test
        info("IterableDoubleUnion: Testing simple iterable creation and functionality");
        itr = new TestInterfaceIterableDoubleUnion();
        testExistence("IterableDoubleUnion: ", itr, base_properties);
        is(itr.entries, itr[Symbol.iterator],
           "IterableDoubleUnion: Should be using @@iterator for 'entries'");
@@ -226,16 +226,16 @@
        var entry = entries_itr.next()
        is(key.value, undefined, "IterableDoubleUnion: Key iterator value should be undefined");
        is(key.done, true, "IterableDoubleUnion: Key iterator done should be true");
        is(value.value, undefined, "IterableDoubleUnion: Value iterator value should be undefined");
        is(value.done, true, "IterableDoubleUnion: Value iterator done should be true");
        is(entry.value, undefined, "IterableDoubleUnion: Entry iterator value should be undefined");
        is(entry.done, true, "IterableDoubleUnion: Entry iterator done should be true");
        is(Object.prototype.toString.call(Object.getPrototypeOf(key_itr)),
-          "[object TestInterfaceIterableDoubleUnionIteratorPrototype]",
+          "[object TestInterfaceIterableDoubleUnion Iterator]",
           "iterator prototype should have the right brand");
 
        SimpleTest.finish();
      });
     </script>
   </body>
 </html>
--- a/dom/performance/tests/mochitest.ini
+++ b/dom/performance/tests/mochitest.ini
@@ -14,11 +14,12 @@ support-files =
 [test_performance_observer.html]
 [test_performance_user_timing.html]
 [test_worker_user_timing.html]
 [test_worker_observer.html]
 [test_sharedWorker_performance_user_timing.html]
 [test_worker_performance_now.html]
 [test_timeOrigin.html]
 [test_worker_performance_entries.html]
+[test_performance_timing_json.html]
 [test_performance_server_timing.html]
 scheme = https
 [test_performance_server_timing_plain_http.html]
--- a/dom/performance/tests/test_performance_server_timing_plain_http.html
+++ b/dom/performance/tests/test_performance_server_timing_plain_http.html
@@ -28,13 +28,15 @@ promise_test(t => {
     t.add_cleanup(() => observer.disconnect());
   });
 
   makeXHR("serverTiming.sjs");
 
   return promise.then(list => {
     assert_equals(list.getEntries().length, 1);
     assert_equals(list.getEntries()[0].serverTiming, undefined);
+    assert_equals(list.getEntries()[0].toJSON().serverTiming, undefined,
+		  "toJSON should not pick up properties that aren't on the object");
   });
 }, "server-timing test");
 
 </script>
 </body>
new file mode 100644
--- /dev/null
+++ b/dom/performance/tests/test_performance_timing_json.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1375829
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1375829</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript">
+
+  /** Test for Bug 1375829 **/
+  var json = performance.timing.toJSON();
+
+  // Ensure it doesn't have any attributes that performance.timing doesn't have
+  for (let key of Object.keys(json)) {
+    ok(key in performance.timing, key + " should be a property of performance.timing");
+  }
+
+  </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1375829">Mozilla Bug 1375829</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
--- a/dom/webidl/MediaDeviceInfo.webidl
+++ b/dom/webidl/MediaDeviceInfo.webidl
@@ -15,10 +15,10 @@ enum MediaDeviceKind {
 
 [Func="Navigator::HasUserMediaSupport"]
 interface MediaDeviceInfo {
   readonly attribute DOMString       deviceId;
   readonly attribute MediaDeviceKind kind;
   readonly attribute DOMString       label;
   readonly attribute DOMString       groupId;
 
-  jsonifier;
+  [Default] object toJSON();
 };
--- a/dom/webidl/PaymentAddress.webidl
+++ b/dom/webidl/PaymentAddress.webidl
@@ -5,19 +5,17 @@
  *
  * The origin of this WebIDL file is
  *   https://www.w3.org/TR/payment-request/#paymentaddress-interface
  */
 
 [SecureContext,
  Func="mozilla::dom::PaymentRequest::PrefEnabled"]
 interface PaymentAddress {
-  // TODO: Use serializer once available. (Bug 863402)
-  // serializer = {attribute};
-  jsonifier;
+  [Default] object toJSON();
 
   readonly attribute DOMString              country;
   // TODO: Use FrozenArray once available. (Bug 1236777)
   // readonly attribute FrozenArray<DOMString> addressLine;
   [Frozen, Cached, Pure]
   readonly attribute sequence<DOMString>    addressLine;
   readonly attribute DOMString              region;
   readonly attribute DOMString              city;
--- a/dom/webidl/PaymentResponse.webidl
+++ b/dom/webidl/PaymentResponse.webidl
@@ -11,19 +11,17 @@ enum PaymentComplete {
   "success",
   "fail",
   "unknown"
 };
 
 [SecureContext,
  Func="mozilla::dom::PaymentRequest::PrefEnabled"]
 interface PaymentResponse {
-  // TODO: Use serializer once available. (Bug 863402)
-  // serializer = {attribute};
-  jsonifier;
+  [Default] object toJSON();
 
   readonly attribute DOMString       requestId;
   readonly attribute DOMString       methodName;
   readonly attribute object          details;
   readonly attribute PaymentAddress? shippingAddress;
   readonly attribute DOMString?      shippingOption;
   readonly attribute DOMString?      payerName;
   readonly attribute DOMString?      payerEmail;
--- a/dom/webidl/Performance.webidl
+++ b/dom/webidl/Performance.webidl
@@ -24,17 +24,17 @@ interface Performance : EventTarget {
 
 [Exposed=Window]
 partial interface Performance {
   [Constant]
   readonly attribute PerformanceTiming timing;
   [Constant]
   readonly attribute PerformanceNavigation navigation;
 
-  jsonifier;
+  [Default] object toJSON();
 };
 
 // http://www.w3.org/TR/performance-timeline/#sec-window.performance-attribute
 [Exposed=(Window,Worker)]
 partial interface Performance {
   PerformanceEntryList getEntries();
   PerformanceEntryList getEntriesByType(DOMString entryType);
   PerformanceEntryList getEntriesByName(DOMString name, optional DOMString
--- a/dom/webidl/PerformanceEntry.webidl
+++ b/dom/webidl/PerformanceEntry.webidl
@@ -13,10 +13,10 @@
 [Exposed=(Window,Worker)]
 interface PerformanceEntry
 {
   readonly attribute DOMString name;
   readonly attribute DOMString entryType;
   readonly attribute DOMHighResTimeStamp startTime;
   readonly attribute DOMHighResTimeStamp duration;
 
-  jsonifier;
+  [Default] object toJSON();
 };
--- a/dom/webidl/PerformanceNavigation.webidl
+++ b/dom/webidl/PerformanceNavigation.webidl
@@ -14,10 +14,10 @@ interface PerformanceNavigation {
   const unsigned short TYPE_NAVIGATE = 0;
   const unsigned short TYPE_RELOAD = 1;
   const unsigned short TYPE_BACK_FORWARD = 2;
   const unsigned short TYPE_RESERVED = 255;
 
   readonly attribute unsigned short type;
   readonly attribute unsigned short redirectCount;
 
-  jsonifier;
+  [Default] object toJSON();
 };
--- a/dom/webidl/PerformanceNavigationTiming.webidl
+++ b/dom/webidl/PerformanceNavigationTiming.webidl
@@ -24,10 +24,10 @@ interface PerformanceNavigationTiming : 
   readonly        attribute DOMHighResTimeStamp domContentLoadedEventStart;
   readonly        attribute DOMHighResTimeStamp domContentLoadedEventEnd;
   readonly        attribute DOMHighResTimeStamp domComplete;
   readonly        attribute DOMHighResTimeStamp loadEventStart;
   readonly        attribute DOMHighResTimeStamp loadEventEnd;
   readonly        attribute NavigationType      type;
   readonly        attribute unsigned short      redirectCount;
 
-  jsonifier;
+  [Default] object toJSON();
 };
--- a/dom/webidl/PerformanceResourceTiming.webidl
+++ b/dom/webidl/PerformanceResourceTiming.webidl
@@ -49,10 +49,10 @@ interface PerformanceResourceTiming : Pe
   [NeedsSubjectPrincipal]
   readonly attribute unsigned long long decodedBodySize;
 
   // TODO: Use FrozenArray once available. (Bug 1236777)
   // readonly attribute FrozenArray<PerformanceServerTiming> serverTiming;
   [SecureContext, Frozen, Cached, Pure, NeedsSubjectPrincipal]
   readonly attribute sequence<PerformanceServerTiming> serverTiming;
 
-  jsonifier;
+  [Default] object toJSON();
 };
--- a/dom/webidl/PerformanceServerTiming.webidl
+++ b/dom/webidl/PerformanceServerTiming.webidl
@@ -11,10 +11,10 @@
  */
 
 [SecureContext,Exposed=(Window,Worker)]
 interface PerformanceServerTiming {
   readonly attribute DOMString           name;
   readonly attribute DOMHighResTimeStamp duration;
   readonly attribute DOMString           description;
 
-  jsonifier;
+  [Default] object toJSON();
 };
--- a/dom/webidl/PerformanceTiming.webidl
+++ b/dom/webidl/PerformanceTiming.webidl
@@ -40,10 +40,10 @@ interface PerformanceTiming {
   readonly attribute unsigned long long timeToNonBlankPaint;
 
   // This is a Mozilla proprietary extension and not part of the
   // performance/navigation timing specification. It marks the
   // completion of the first presentation flush after DOMContentLoaded.
   [Pref="dom.performance.time_to_dom_content_flushed.enabled"]
   readonly attribute unsigned long long timeToDOMContentFlushed;
 
-  jsonifier;
+  [Default] object toJSON();
 };
--- a/dom/webidl/RTCIceCandidate.webidl
+++ b/dom/webidl/RTCIceCandidate.webidl
@@ -16,10 +16,10 @@ dictionary RTCIceCandidateInit {
 [Pref="media.peerconnection.enabled",
  JSImplementation="@mozilla.org/dom/rtcicecandidate;1",
  Constructor(RTCIceCandidateInit candidateInitDict)]
 interface RTCIceCandidate {
   attribute DOMString       candidate;
   attribute DOMString?      sdpMid;
   attribute unsigned short? sdpMLineIndex;
 
-  jsonifier;
+  [Default] object toJSON();
 };
--- a/dom/webidl/RTCSessionDescription.webidl
+++ b/dom/webidl/RTCSessionDescription.webidl
@@ -22,10 +22,10 @@ dictionary RTCSessionDescriptionInit {
 [Pref="media.peerconnection.enabled",
  JSImplementation="@mozilla.org/dom/rtcsessiondescription;1",
  Constructor(optional RTCSessionDescriptionInit descriptionInitDict)]
 interface RTCSessionDescription {
   // These should be readonly, but writing causes deprecation warnings for a bit
   attribute RTCSdpType type;
   attribute DOMString sdp;
 
-  jsonifier;
+  [Default] object toJSON();
 };
--- a/js/src/jit/BaselineCacheIRCompiler.cpp
+++ b/js/src/jit/BaselineCacheIRCompiler.cpp
@@ -262,32 +262,16 @@ BaselineCacheIRCompiler::emitGuardGroup(
         masm.branchTestObjGroupNoSpectreMitigations(Assembler::NotEqual, obj, scratch1,
                                                     failure->label());
     }
 
     return true;
 }
 
 bool
-BaselineCacheIRCompiler::emitGuardGroupHasUnanalyzedNewScript()
-{
-    Address addr(stubAddress(reader.stubOffset()));
-    AutoScratchRegister scratch1(allocator, masm);
-    AutoScratchRegister scratch2(allocator, masm);
-
-    FailurePath* failure;
-    if (!addFailurePath(&failure))
-        return false;
-
-    masm.loadPtr(addr, scratch1);
-    masm.guardGroupHasUnanalyzedNewScript(scratch1, scratch2, failure->label());
-    return true;
-}
-
-bool
 BaselineCacheIRCompiler::emitGuardProto()
 {
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     AutoScratchRegister scratch(allocator, masm);
 
     FailurePath* failure;
     if (!addFailurePath(&failure))
         return false;
--- a/js/src/jit/CacheIRCompiler.cpp
+++ b/js/src/jit/CacheIRCompiler.cpp
@@ -2987,18 +2987,21 @@ void CacheIRCompiler::emitLoadStubFieldC
     MOZ_ASSERT(mode_ == Mode::Ion);
     switch (val.getStubFieldType()) {
       case StubField::Type::Shape:
         masm.movePtr(ImmGCPtr(shapeStubField(val.getOffset())),dest);
         break;
       case StubField::Type::String:
         masm.movePtr(ImmGCPtr(stringStubField(val.getOffset())), dest);
         break;
+      case StubField::Type::ObjectGroup:
+        masm.movePtr(ImmGCPtr(groupStubField(val.getOffset())), dest);
+        break;
       default:
-            MOZ_CRASH("Unhandled stub field constant type");
+        MOZ_CRASH("Unhandled stub field constant type");
     }
 }
 
 /*
  * After this is done executing, dest contains the value; either through a constant load
  * or through the load from the stub data.
  *
  * The current policy is that Baseline will use loads from the stub data (to allow IC
@@ -3107,9 +3110,25 @@ CacheIRCompiler::emitMegamorphicLoadSlot
     masm.loadTypedOrValue(Address(masm.getStackPointer(), 0), output);
     masm.adjustStack(sizeof(Value));
 
     masm.branchIfFalseBool(scratch2, failure->label());
     if (JitOptions.spectreJitToCxxCalls)
         masm.speculationBarrier();
 
     return true;
+}
+
+bool
+CacheIRCompiler::emitGuardGroupHasUnanalyzedNewScript()
+{
+    StubFieldOffset group(reader.stubOffset(), StubField::Type::ObjectGroup);
+    AutoScratchRegister scratch1(allocator, masm);
+    AutoScratchRegister scratch2(allocator, masm);
+
+    FailurePath* failure;
+    if (!addFailurePath(&failure))
+        return false;
+
+    emitLoadStubField(group, scratch1);
+    masm.guardGroupHasUnanalyzedNewScript(scratch1, scratch2, failure->label());
+    return true;
 }
\ No newline at end of file
--- a/js/src/jit/CacheIRCompiler.h
+++ b/js/src/jit/CacheIRCompiler.h
@@ -22,16 +22,17 @@ namespace jit {
     _(GuardIsObjectOrNull)                \
     _(GuardIsString)                      \
     _(GuardIsSymbol)                      \
     _(GuardIsNumber)                      \
     _(GuardIsInt32)                       \
     _(GuardIsInt32Index)                  \
     _(GuardType)                          \
     _(GuardClass)                         \
+    _(GuardGroupHasUnanalyzedNewScript)   \
     _(GuardIsNativeFunction)              \
     _(GuardIsNativeObject)                \
     _(GuardIsProxy)                       \
     _(GuardNotDOMProxy)                   \
     _(GuardSpecificInt32Immediate)        \
     _(GuardMagicValue)                    \
     _(GuardNoUnboxedExpando)              \
     _(GuardAndLoadUnboxedExpando)         \
--- a/js/src/jit/IonCacheIRCompiler.cpp
+++ b/js/src/jit/IonCacheIRCompiler.cpp
@@ -653,32 +653,16 @@ IonCacheIRCompiler::emitGuardGroup()
         masm.branchTestObjGroupNoSpectreMitigations(Assembler::NotEqual, obj, group,
                                                     failure->label());
     }
 
     return true;
 }
 
 bool
-IonCacheIRCompiler::emitGuardGroupHasUnanalyzedNewScript()
-{
-    ObjectGroup* group = groupStubField(reader.stubOffset());
-    AutoScratchRegister scratch1(allocator, masm);
-    AutoScratchRegister scratch2(allocator, masm);
-
-    FailurePath* failure;
-    if (!addFailurePath(&failure))
-        return false;
-
-    masm.movePtr(ImmGCPtr(group), scratch1);
-    masm.guardGroupHasUnanalyzedNewScript(scratch1, scratch2, failure->label());
-    return true;
-}
-
-bool
 IonCacheIRCompiler::emitGuardProto()
 {
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     JSObject* proto = objectStubField(reader.stubOffset());
 
     AutoScratchRegister scratch(allocator, masm);
 
     FailurePath* failure;
--- a/layout/tools/reftest/remotereftest.py
+++ b/layout/tools/reftest/remotereftest.py
@@ -417,33 +417,33 @@ def run_test_harness(parser, options):
     reftest.killNamedProc('ssltunnel')
     reftest.killNamedProc('xpcshell')
 
     # Start the webserver
     retVal = reftest.startWebServer(options)
     if retVal:
         return retVal
 
-    if options.printDeviceInfo:
+    if options.printDeviceInfo and not options.verify:
         reftest.printDeviceInfo()
 
     retVal = 0
     try:
         if options.verify:
             retVal = reftest.verifyTests(options.tests, options)
         else:
             retVal = reftest.runTests(options.tests, options)
     except Exception:
         print "Automation Error: Exception caught while running tests"
         traceback.print_exc()
         retVal = 1
 
     reftest.stopWebServer(options)
 
-    if options.printDeviceInfo:
+    if options.printDeviceInfo and not options.verify:
         reftest.printDeviceInfo(printLogcat=True)
 
     return retVal
 
 
 if __name__ == "__main__":
     parser = reftestcommandline.RemoteArgumentsParser()
     options = parser.parse_args()
--- a/taskcluster/ci/spidermonkey/linux.yml
+++ b/taskcluster/ci/spidermonkey/linux.yml
@@ -130,17 +130,16 @@ sm-msan-linux64/opt:
         spidermonkey-variant: msan
 
 sm-tsan-linux64/opt:
     description: "Spidermonkey Thread Sanitizer"
     index:
         job-name: sm-tsan-linux64-opt
     treeherder:
         symbol: SM(tsan)
-        tier: 3
         platform: linux64/opt
     run:
         spidermonkey-variant: tsan
 
 sm-rootanalysis-linux64/debug:
     description: "Spidermonkey Root Analysis"
     index:
         job-name: sm-rootanalysis-linux64-debug
--- a/testing/mochitest/runtestsremote.py
+++ b/testing/mochitest/runtestsremote.py
@@ -335,17 +335,17 @@ def run_test_harness(parser, options):
     options.runByManifest = True
     # roboextender is used by mochitest-chrome tests like test_java_addons.html,
     # but not by any plain mochitests
     if options.flavor != 'chrome':
         options.extensionsToExclude.append('roboextender@mozilla.org')
 
     mochitest = MochiRemote(options)
 
-    if options.log_mach is None:
+    if options.log_mach is None and not options.verify:
         mochitest.printDeviceInfo()
 
     try:
         if options.verify:
             retVal = mochitest.verifyTests(options)
         else:
             retVal = mochitest.runTests(options)
     except Exception:
@@ -353,17 +353,17 @@ def run_test_harness(parser, options):
         traceback.print_exc()
         try:
             mochitest.cleanup(options)
         except Exception:
             # device error cleaning up... oh well!
             traceback.print_exc()
         retVal = 1
 
-    if options.log_mach is None:
+    if options.log_mach is None and not options.verify:
         mochitest.printDeviceInfo(printLogcat=True)
 
     mochitest.message_logger.finish()
 
     return retVal
 
 
 def main(args=sys.argv[1:]):
--- a/testing/mozharness/scripts/android_emulator_unittest.py
+++ b/testing/mozharness/scripts/android_emulator_unittest.py
@@ -25,16 +25,22 @@ from mozharness.base.log import FATAL
 from mozharness.base.script import BaseScript, PreScriptAction, PostScriptAction
 from mozharness.mozilla.buildbot import TBPL_RETRY, EXIT_STATUS_DICT
 from mozharness.mozilla.mozbase import MozbaseMixin
 from mozharness.mozilla.testing.testbase import TestingMixin, testing_config_options
 from mozharness.mozilla.testing.codecoverage import CodeCoverageMixin
 
 
 class AndroidEmulatorTest(TestingMixin, BaseScript, MozbaseMixin, CodeCoverageMixin):
+    """
+       A mozharness script for Android functional tests (like mochitests and reftests)
+       run on an Android emulator. This script starts and manages an Android emulator
+       for the duration of the required tests. This is like desktop_unittest.py, but
+       for Android emulator test platforms.
+    """
     config_options = [[
         ["--test-suite"],
         {"action": "store",
          "dest": "test_suite",
          "default": None
          }
     ], [
         ["--total-chunk"],
@@ -66,18 +72,16 @@ class AndroidEmulatorTest(TestingMixin, 
                          'install',
                          'run-tests',
                          ],
             require_config_file=require_config_file,
             config={
                 'virtualenv_modules': [],
                 'virtualenv_requirements': [],
                 'require_test_zip': True,
-                # IP address of the host as seen from the emulator
-                'remote_webserver': '10.0.2.2',
             }
         )
 
         # these are necessary since self.config is read only
         c = self.config
         abs_dirs = self.query_abs_dirs()
         self.adb_path = self.query_exe('adb')
         self.installer_url = c.get('installer_url')
@@ -95,24 +99,16 @@ class AndroidEmulatorTest(TestingMixin, 
             m = re.match("(.*)-(\d*)", self.test_suite)
             if m:
                 self.test_suite = m.group(1)
                 if self.this_chunk is None:
                     self.this_chunk = m.group(2)
         self.sdk_level = None
         self.xre_path = None
 
-    def _query_tests_dir(self):
-        dirs = self.query_abs_dirs()
-        try:
-            test_dir = self.config["suite_definitions"][self.test_suite]["testsdir"]
-        except Exception:
-            test_dir = self.test_suite
-        return os.path.join(dirs['abs_test_install_dir'], test_dir)
-
     def query_abs_dirs(self):
         if self.abs_dirs:
             return self.abs_dirs
         abs_dirs = super(AndroidEmulatorTest, self).query_abs_dirs()
         dirs = {}
         dirs['abs_test_install_dir'] = os.path.join(
             abs_dirs['abs_work_dir'], 'tests')
         dirs['abs_test_bin_dir'] = os.path.join(
@@ -137,16 +133,49 @@ class AndroidEmulatorTest(TestingMixin, 
         dirs['abs_avds_dir'] = self.config.get("avds_dir", "/home/cltbld/.android")
 
         for key in dirs.keys():
             if key not in abs_dirs:
                 abs_dirs[key] = dirs[key]
         self.abs_dirs = abs_dirs
         return self.abs_dirs
 
+    def _query_tests_dir(self, test_suite):
+        dirs = self.query_abs_dirs()
+        try:
+            test_dir = self.config["suite_definitions"][test_suite]["testsdir"]
+        except Exception:
+            test_dir = test_suite
+        return os.path.join(dirs['abs_test_install_dir'], test_dir)
+
+    def _query_package_name(self):
+        if self.app_name is None:
+            # For convenience, assume geckoview.test/geckoview_example when install
+            # target looks like geckoview.
+            if 'androidTest' in self.installer_path:
+                self.app_name = 'org.mozilla.geckoview.test'
+            elif 'geckoview' in self.installer_path:
+                self.app_name = 'org.mozilla.geckoview_example'
+        if self.app_name is None:
+            # Find appname from package-name.txt - assumes download-and-extract
+            # has completed successfully.
+            # The app/package name will typically be org.mozilla.fennec,
+            # but org.mozilla.firefox for release builds, and there may be
+            # other variations. 'aapt dump badging <apk>' could be used as an
+            # alternative to package-name.txt, but introduces a dependency
+            # on aapt, found currently in the Android SDK build-tools component.
+            apk_dir = self.abs_dirs['abs_work_dir']
+            self.apk_path = os.path.join(apk_dir, self.installer_path)
+            unzip = self.query_exe("unzip")
+            package_path = os.path.join(apk_dir, 'package-name.txt')
+            unzip_cmd = [unzip, '-q', '-o',  self.apk_path]
+            self.run_command(unzip_cmd, cwd=apk_dir, halt_on_failure=True)
+            self.app_name = str(self.read_from_file(package_path, verbose=True)).rstrip()
+        return self.app_name
+
     def _launch_emulator(self):
         env = self.query_env()
 
         # Write a default ddms.cfg to avoid unwanted prompts
         avd_home_dir = self.abs_dirs['abs_avds_dir']
         DDMS_FILE = os.path.join(avd_home_dir, "ddms.cfg")
         with open(DDMS_FILE, 'w') as f:
             f.write("pingOptIn=false\npingId=0\n")
@@ -187,23 +216,24 @@ class AndroidEmulatorTest(TestingMixin, 
                 self.run_command(["emulator", "-accel-check"], env=env)
             except Exception as e:
                 self.warning("Extra kvm diagnostics failed: %s" % str(e))
 
         command = ["emulator", "-avd", self.emulator["name"]]
         if "emulator_extra_args" in self.config:
             command += self.config["emulator_extra_args"].split()
 
-        tmp_file = tempfile.NamedTemporaryFile(mode='w')
-        tmp_stdout = open(tmp_file.name, 'w')
-        self.info("Trying to start the emulator with this command: %s" % ' '.join(command))
-        proc = subprocess.Popen(command, stdout=tmp_stdout, stderr=tmp_stdout, env=env)
+        dir = self.query_abs_dirs()['abs_blob_upload_dir']
+        tmp_file = tempfile.NamedTemporaryFile(mode='w', prefix='emulator-',
+                                               suffix='.log', dir=dir, delete=False)
+        self.info("Launching the emulator with: %s" % ' '.join(command))
+        self.info("Writing log to %s" % tmp_file.name)
+        proc = subprocess.Popen(command, stdout=tmp_file, stderr=tmp_file, env=env)
         return {
             "process": proc,
-            "tmp_file": tmp_file,
         }
 
     def _retry(self, max_attempts, interval, func, description, max_time=0):
         '''
         Execute func until it returns True, up to max_attempts times, waiting for
         interval seconds between each attempt. description is logged on each attempt.
         If max_time is specified, no further attempts will be made once max_time
         seconds have elapsed; this provides some protection for the case where
@@ -227,16 +257,20 @@ class AndroidEmulatorTest(TestingMixin, 
             self.info(">> %s: Attempt #%d of %d" % (description, attempts, max_attempts))
             status = func()
         return status
 
     def _run_with_timeout(self, timeout, cmd, quiet=False):
         timeout_cmd = ['timeout', '%s' % timeout] + cmd
         return self._run_proc(timeout_cmd, quiet=quiet)
 
+    def _run_adb_with_timeout(self, timeout, cmd, quiet=False):
+        cmd = [self.adb_path, '-s', self.emulator['device_id']] + cmd
+        return self._run_with_timeout(timeout, cmd, quiet)
+
     def _run_proc(self, cmd, quiet=False):
         self.info('Running %s' % subprocess.list2cmdline(cmd))
         p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
         out, err = p.communicate()
         if out and not quiet:
             self.info('%s' % str(out.strip()))
         if err and not quiet:
             self.info('stderr: %s' % str(err.strip()))
@@ -249,19 +283,18 @@ class AndroidEmulatorTest(TestingMixin, 
 
     def _verify_adb_device(self):
         out, _ = self._run_with_timeout(30, [self.adb_path, 'devices'])
         if (self.emulator['device_id'] in out) and ("device" in out):
             return True
         return False
 
     def _is_boot_completed(self):
-        boot_cmd = [self.adb_path, '-s', self.emulator['device_id'],
-                    'shell', 'getprop', 'sys.boot_completed']
-        out, _ = self._run_with_timeout(30, boot_cmd)
+        boot_cmd = ['shell', 'getprop', 'sys.boot_completed']
+        out, _ = self._run_adb_with_timeout(30, boot_cmd)
         if out.strip() == '1':
             return True
         return False
 
     def _verify_emulator(self):
         adb_ok = self._verify_adb()
         if not adb_ok:
             self.warning('Unable to communicate with adb')
@@ -277,150 +310,105 @@ class AndroidEmulatorTest(TestingMixin, 
         if not boot_ok:
             self.warning('Unable to verify Android boot completion')
             return False
         return True
 
     def _verify_emulator_and_restart_on_fail(self):
         emulator_ok = self._verify_emulator()
         if not emulator_ok:
-            self._dump_host_state()
             self._screenshot("emulator-startup-screenshot-")
             self._kill_processes(self.config["emulator_process_name"])
             self._run_proc(['ps', '-ef'])
-            self._dump_emulator_log()
             # remove emulator tmp files
             for dir in glob.glob("/tmp/android-*"):
                 self.rmtree(dir)
             self._restart_adbd()
             time.sleep(5)
             self.emulator_proc = self._launch_emulator()
         return emulator_ok
 
     def _install_target_apk(self):
         install_ok = False
         if int(self.sdk_level) >= 23:
-            cmd = [self.adb_path, '-s', self.emulator['device_id'], 'install', '-r', '-g',
-                   self.installer_path]
+            cmd = ['install', '-r', '-g', self.installer_path]
         else:
-            cmd = [self.adb_path, '-s', self.emulator['device_id'], 'install', '-r',
-                   self.installer_path]
-        out, err = self._run_with_timeout(300, cmd, True)
+            cmd = ['install', '-r', self.installer_path]
+        out, err = self._run_adb_with_timeout(300, cmd, True)
         if 'Success' in out or 'Success' in err:
             install_ok = True
         return install_ok
 
     def _install_robocop_apk(self):
         install_ok = False
         if int(self.sdk_level) >= 23:
-            cmd = [self.adb_path, '-s', self.emulator['device_id'], 'install', '-r', '-g',
-                   self.robocop_path]
+            cmd = ['install', '-r', '-g', self.robocop_path]
         else:
-            cmd = [self.adb_path, '-s', self.emulator['device_id'], 'install', '-r',
-                   self.robocop_path]
-        out, err = self._run_with_timeout(300, cmd, True)
+            cmd = ['install', '-r', self.robocop_path]
+        out, err = self._run_adb_with_timeout(300, cmd, True)
         if 'Success' in out or 'Success' in err:
             install_ok = True
         return install_ok
 
-    def _dump_host_state(self):
-        self._run_proc(['ps', '-ef'])
-        self._run_proc(['netstat', '-a', '-p', '-n', '-t', '-u'])
-
-    def _dump_emulator_log(self):
-        self.info("##### %s emulator log begins" % self.emulator["name"])
-        output = self.read_from_file(self.emulator_proc["tmp_file"].name, verbose=False)
-        if output:
-            self.info(output)
-        self.info("##### %s emulator log ends" % self.emulator["name"])
-
     def _kill_processes(self, process_name):
         p = subprocess.Popen(['ps', '-A'], stdout=subprocess.PIPE)
         out, err = p.communicate()
-        self.info("Let's kill every process called %s" % process_name)
+        self.info("Killing every process called %s" % process_name)
         for line in out.splitlines():
             if process_name in line:
                 pid = int(line.split(None, 1)[0])
                 self.info("Killing pid %d." % pid)
                 os.kill(pid, signal.SIGKILL)
 
     def _restart_adbd(self):
         self._run_with_timeout(30, [self.adb_path, 'kill-server'])
         self._run_with_timeout(30, [self.adb_path, 'root'])
 
     def _screenshot(self, prefix):
         """
-           Save a screenshot of the entire screen to the blob upload directory.
+           Save a screenshot of the entire screen to the upload directory.
         """
         dirs = self.query_abs_dirs()
         utility = os.path.join(self.xre_path, "screentopng")
         if not os.path.exists(utility):
             self.warning("Unable to take screenshot: %s does not exist" % utility)
             return
         try:
             tmpfd, filename = tempfile.mkstemp(prefix=prefix, suffix='.png',
                                                dir=dirs['abs_blob_upload_dir'])
             os.close(tmpfd)
             self.info("Taking screenshot with %s; saving to %s" % (utility, filename))
             subprocess.call([utility, filename], env=self.query_env())
         except OSError, err:
             self.warning("Failed to take screenshot: %s" % err.strerror)
 
-    def _query_package_name(self):
-        if self.app_name is None:
-            # For convenience, assume geckoview.test/geckoview_example when install
-            # target looks like geckoview.
-            if 'androidTest' in self.installer_path:
-                self.app_name = 'org.mozilla.geckoview.test'
-            elif 'geckoview' in self.installer_path:
-                self.app_name = 'org.mozilla.geckoview_example'
-        if self.app_name is None:
-            # Find appname from package-name.txt - assumes download-and-extract
-            # has completed successfully.
-            # The app/package name will typically be org.mozilla.fennec,
-            # but org.mozilla.firefox for release builds, and there may be
-            # other variations. 'aapt dump badging <apk>' could be used as an
-            # alternative to package-name.txt, but introduces a dependency
-            # on aapt, found currently in the Android SDK build-tools component.
-            apk_dir = self.abs_dirs['abs_work_dir']
-            self.apk_path = os.path.join(apk_dir, self.installer_path)
-            unzip = self.query_exe("unzip")
-            package_path = os.path.join(apk_dir, 'package-name.txt')
-            unzip_cmd = [unzip, '-q', '-o',  self.apk_path]
-            self.run_command(unzip_cmd, cwd=apk_dir, halt_on_failure=True)
-            self.app_name = str(self.read_from_file(package_path, verbose=True)).rstrip()
-        return self.app_name
-
-    def preflight_install(self):
-        # in the base class, this checks for mozinstall, but we don't use it
-        pass
-
     def _build_command(self):
         c = self.config
         dirs = self.query_abs_dirs()
 
         if self.test_suite not in self.config["suite_definitions"]:
             self.fatal("Key '%s' not defined in the config!" % self.test_suite)
 
         cmd = [
             self.query_python_path('python'),
             '-u',
             os.path.join(
-                self._query_tests_dir(),
+                self._query_tests_dir(self.test_suite),
                 self.config["suite_definitions"][self.test_suite]["run_filename"]
             ),
         ]
 
         raw_log_file = os.path.join(dirs['abs_blob_upload_dir'],
                                     '%s_raw.log' % self.test_suite)
 
         error_summary_file = os.path.join(dirs['abs_blob_upload_dir'],
                                           '%s_errorsummary.log' % self.test_suite)
         str_format_values = {
-            'remote_webserver': c['remote_webserver'],
+            # IP address of the host as seen from the emulator
+            'remote_webserver': '10.0.2.2',
             'xre_path': self.xre_path,
             'utility_path': self.xre_path,
             'certs_path': os.path.join(dirs['abs_work_dir'], 'tests/certs'),
             # TestingMixin._download_and_extract_symbols() will set
             # self.symbols_path when downloading/extracting.
             'symbols_path': self.symbols_path,
             'modules_dir': dirs['abs_modules_dir'],
             'installer_path': self.installer_path,
@@ -500,17 +488,16 @@ class AndroidEmulatorTest(TestingMixin, 
             self.fatal("Could not retrieve manifest needed to retrieve "
                        "artifacts from %s" % manifest_path)
         cache = self.config.get("tooltool_cache", None)
         if self.tooltool_fetch(manifest_path, output_dir=dir, cache=cache):
             self.warning("Unable to download from tooltool: %s" % url)
 
     def _install_emulator(self):
         dirs = self.query_abs_dirs()
-        self.mkdir_p(dirs['abs_work_dir'])
         if self.config.get('emulator_url'):
             self.download_unpack(self.config['emulator_url'], dirs['abs_work_dir'])
         elif self.config.get('emulator_manifest'):
             manifest_path = self.create_tooltool_manifest(self.config['emulator_manifest'])
             dirs = self.query_abs_dirs()
             cache = self.config.get("tooltool_cache", None)
             if self.tooltool_fetch(manifest_path,
                                    output_dir=dirs['abs_work_dir'],
@@ -537,33 +524,34 @@ class AndroidEmulatorTest(TestingMixin, 
             f.write('\n\nHost /proc/meminfo:\n')
             out, _ = self._run_proc(['cat', '/proc/meminfo'], quiet=True)
             f.write(out)
 
             f.write('\n\nHost process list:\n')
             out, _ = self._run_proc(['ps', '-ef'], quiet=True)
             f.write(out)
 
+            f.write('\n\nHost netstat:\n')
+            out, _ = self._run_proc(['netstat', '-a', '-p', '-n', '-t', '-u'], quiet=True)
+            f.write(out)
+
             f.write('\n\nEmulator /proc/cpuinfo:\n')
-            cmd = [self.adb_path, '-s', self.emulator['device_id'],
-                   'shell', 'cat', '/proc/cpuinfo']
-            out, _ = self._run_with_timeout(30, cmd, quiet=True)
+            cmd = ['shell', 'cat', '/proc/cpuinfo']
+            out, _ = self._run_adb_with_timeout(30, cmd, quiet=True)
             f.write(out)
             cpuinfo = out
 
             f.write('\n\nEmulator /proc/meminfo:\n')
-            cmd = [self.adb_path, '-s', self.emulator['device_id'],
-                   'shell', 'cat', '/proc/meminfo']
-            out, _ = self._run_with_timeout(30, cmd, quiet=True)
+            cmd = ['shell', 'cat', '/proc/meminfo']
+            out, _ = self._run_adb_with_timeout(30, cmd, quiet=True)
             f.write(out)
 
             f.write('\n\nEmulator process list:\n')
-            cmd = [self.adb_path, '-s', self.emulator['device_id'],
-                   'shell', 'ps']
-            out, _ = self._run_with_timeout(30, cmd, quiet=True)
+            cmd = ['shell', 'ps']
+            out, _ = self._run_adb_with_timeout(30, cmd, quiet=True)
             f.write(out)
 
         # Search android cpuinfo for "BogoMIPS"; if found and < 250, retry
         # this task, in hopes of getting a higher-powered environment.
         # (Carry on silently if BogoMIPS is not found -- this may vary by
         # Android implementation -- no big deal.)
         # See bug 1321605: Sometimes the emulator is really slow, and
         # low bogomips can be a good predictor of that condition.
@@ -603,16 +591,20 @@ class AndroidEmulatorTest(TestingMixin, 
             # per-test mode
             categories = ['mochitest', 'reftest', 'xpcshell']
         return categories
 
     ##########################################
     # Actions for AndroidEmulatorTest        #
     ##########################################
 
+    def preflight_install(self):
+        # in the base class, this checks for mozinstall, but we don't use it
+        pass
+
     @PreScriptAction('create-virtualenv')
     def pre_create_virtualenv(self, action):
         dirs = self.query_abs_dirs()
         requirements = None
         if self.test_suite == 'mochitest-media':
             # mochitest-media is the only thing that needs this
             requirements = os.path.join(dirs['abs_mochitest_dir'],
                                         'websocketprocessbridge',
@@ -627,16 +619,18 @@ class AndroidEmulatorTest(TestingMixin, 
     def setup_avds(self):
         '''
         If tooltool cache mechanism is enabled, the cached version is used by
         the fetch command. If the manifest includes an "unpack" field, tooltool
         will unpack all compressed archives mentioned in the manifest.
         '''
         c = self.config
         dirs = self.query_abs_dirs()
+        self.mkdir_p(dirs['abs_work_dir'])
+        self.mkdir_p(dirs['abs_blob_upload_dir'])
 
         # Always start with a clean AVD: AVD includes Android images
         # which can be stateful.
         self.rmtree(dirs['abs_avds_dir'])
         self.mkdir_p(dirs['abs_avds_dir'])
         if 'avd_url' in c:
             # Intended for experimental setups to evaluate an avd prior to
             # tooltool deployment.
@@ -674,37 +668,36 @@ class AndroidEmulatorTest(TestingMixin, 
 
         self.emulator_proc = self._launch_emulator()
 
     def verify_emulator(self):
         '''
         Check to see if the emulator can be contacted via adb.
         If any communication attempt fails, kill the emulator, re-launch, and re-check.
         '''
-        self.mkdir_p(self.query_abs_dirs()['abs_blob_upload_dir'])
         max_restarts = 5
         emulator_ok = self._retry(max_restarts, 10, self._verify_emulator_and_restart_on_fail,
                                   "Check emulator")
         if not emulator_ok:
             self.fatal('INFRA-ERROR: Unable to start emulator after %d attempts' % max_restarts,
                        EXIT_STATUS_DICT[TBPL_RETRY])
         self._dump_perf_info()
         # Start logcat for the emulator. The adb process runs until the
         # corresponding emulator is killed. Output is written directly to
-        # the blobber upload directory so that it is uploaded automatically
+        # the upload directory so that it is uploaded automatically
         # at the end of the job.
         logcat_filename = 'logcat-%s.log' % self.emulator["device_id"]
         logcat_path = os.path.join(self.abs_dirs['abs_blob_upload_dir'], logcat_filename)
         logcat_cmd = '%s -s %s logcat -v threadtime Trace:S StrictMode:S '\
             ' ExchangeService:S > %s &' % (self.adb_path, self.emulator["device_id"], logcat_path)
         self.info(logcat_cmd)
         os.system(logcat_cmd)
         # Get a post-boot emulator process list for diagnostics
-        ps_cmd = [self.adb_path, '-s', self.emulator["device_id"], 'shell', 'ps']
-        self._run_with_timeout(30, ps_cmd)
+        ps_cmd = ['shell', 'ps']
+        self._run_adb_with_timeout(30, ps_cmd)
 
     def download_and_extract(self):
         """
         Download and extract fennec APK, tests.zip, host utils, and robocop (if required).
         """
         super(AndroidEmulatorTest, self).download_and_extract(
             suite_categories=self._query_suite_categories())
         dirs = self.query_abs_dirs()
@@ -733,19 +726,18 @@ class AndroidEmulatorTest(TestingMixin, 
             self.config["suite_definitions"][self.test_suite].get("install")
         if install_needed is False:
             self.info("Skipping apk installation for %s" % self.test_suite)
             return
 
         assert self.installer_path is not None, \
             "Either add installer_path to the config or use --installer-path."
 
-        cmd = [self.adb_path, '-s', self.emulator['device_id'], 'shell',
-               'getprop', 'ro.build.version.sdk']
-        self.sdk_level, _ = self._run_with_timeout(30, cmd)
+        cmd = ['shell', 'getprop', 'ro.build.version.sdk']
+        self.sdk_level, _ = self._run_adb_with_timeout(30, cmd)
 
         # Install Fennec
         install_ok = self._retry(3, 30, self._install_target_apk, "Install app APK")
         if not install_ok:
             self.fatal('INFRA-ERROR: Failed to install %s on %s' %
                        (self.installer_path, self.emulator["name"]),
                        EXIT_STATUS_DICT[TBPL_RETRY])
 
@@ -769,20 +761,17 @@ class AndroidEmulatorTest(TestingMixin, 
         per_test_args = []
         suites = self._query_suites()
         minidump = self.query_minidump_stackwalk()
         for (per_test_suite, suite) in suites:
             self.test_suite = suite
 
             cmd = self._build_command()
 
-            try:
-                cwd = self._query_tests_dir()
-            except Exception:
-                self.fatal("Don't know how to run --test-suite '%s'!" % self.test_suite)
+            cwd = self._query_tests_dir(self.test_suite)
             env = self.query_env()
             if minidump:
                 env['MINIDUMP_STACKWALK'] = minidump
             env['MOZ_UPLOAD_DIR'] = self.query_abs_dirs()['abs_blob_upload_dir']
             env['MINIDUMP_SAVE_PATH'] = self.query_abs_dirs()['abs_blob_upload_dir']
             env['RUST_BACKTRACE'] = 'full'
 
             summary = None
@@ -800,18 +789,17 @@ class AndroidEmulatorTest(TestingMixin, 
                 final_cmd = copy.copy(cmd)
                 if len(per_test_args) > 0:
                     # in per-test mode, remove any chunk arguments from command
                     for arg in final_cmd:
                         if 'total-chunk' in arg or 'this-chunk' in arg:
                             final_cmd.remove(arg)
                 final_cmd.extend(per_test_args)
 
-                self.info("Running on %s the command %s" % (self.emulator["name"],
-                          subprocess.list2cmdline(final_cmd)))
+                self.info("Running the command %s" % subprocess.list2cmdline(final_cmd))
                 self.info("##### %s log begins" % self.test_suite)
 
                 suite_category = self.test_suite
                 parser = self.get_test_output_parser(
                     suite_category,
                     config=self.config,
                     log_obj=self.log_obj,
                     error_list=[])
deleted file mode 100644
--- a/testing/web-platform/meta/WebIDL/ecmascript-binding/default-iterator-object.html.ini
+++ /dev/null
@@ -1,7 +0,0 @@
-[default-iterator-object.html]
-  [Object.prototype.toString returns correct value]
-    expected: FAIL
-
-  [@@toStringTag has correct value from prototype]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/WebIDL/ecmascript-binding/iterator-prototype-object.html.ini
+++ /dev/null
@@ -1,7 +0,0 @@
-[iterator-prototype-object.html]
-  [Object.prototype.toString returns correct value]
-    expected: FAIL
-
-  [@@toStringTag has correct value]
-    expected: FAIL
-
--- a/toolkit/components/extensions/test/xpcshell/test_ext_i18n_css.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_i18n_css.js
@@ -15,16 +15,20 @@ const {
   promiseShutdownManager,
   promiseStartupManager,
 } = AddonTestUtils;
 
 AddonTestUtils.init(this);
 
 createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
 
+// Some multibyte characters. This sample was taken from the encoding/api-basics.html web platform test.
+const MULTIBYTE_STRING = "z\xA2\u6C34\uD834\uDD1E\uF8FF\uDBFF\uDFFD\uFFFE";
+let getCSS = (a, b) => `a { content: '${a}'; } b { content: '${b}'; }`;
+
 let extensionData = {
   background: function() {
     function backgroundFetch(url) {
       return new Promise((resolve, reject) => {
         let xhr = new XMLHttpRequest();
         xhr.overrideMimeType("text/plain");
         xhr.open("GET", url);
         xhr.onload = () => { resolve(xhr.responseText); };
@@ -37,27 +41,27 @@ let extensionData = {
       browser.test.assertEq("body { max-width: 42px; }", results[0], "CSS file localized");
       browser.test.assertEq("body { max-width: 42px; }", results[1], "CSS file localized");
 
       browser.test.assertEq("body { __MSG_foo__; }", results[2], "Text file not localized");
 
       browser.test.notifyPass("i18n-css");
     });
 
-    browser.test.sendMessage("ready", browser.runtime.getURL("foo.css"));
+    browser.test.sendMessage("ready", browser.runtime.getURL("/"));
   },
 
   manifest: {
     "applications": {
       "gecko": {
         "id": "i18n_css@mochi.test",
       },
     },
 
-    "web_accessible_resources": ["foo.css", "foo.txt", "locale.css"],
+    "web_accessible_resources": ["foo.css", "foo.txt", "locale.css", "multibyte.css"],
 
     "content_scripts": [
       {
         "matches": ["http://*/*/file_sample.html"],
         "css": ["foo.css"],
         "run_at": "document_start",
       },
       {
@@ -70,66 +74,71 @@ let extensionData = {
   },
 
   files: {
     "_locales/en/messages.json": JSON.stringify({
       "foo": {
         "message": "max-width: 42px",
         "description": "foo",
       },
+      "multibyteKey": {
+        "message": MULTIBYTE_STRING,
+      },
     }),
 
     "content.js": function() {
       let style = getComputedStyle(document.body);
       browser.test.sendMessage("content-maxWidth", style.maxWidth);
     },
 
     "foo.css": "body { __MSG_foo__; }",
     "bar.CsS": "body { __MSG_foo__; }",
     "foo.txt": "body { __MSG_foo__; }",
     "locale.css": '* { content: "__MSG_@@ui_locale__ __MSG_@@bidi_dir__ __MSG_@@bidi_reversed_dir__ __MSG_@@bidi_start_edge__ __MSG_@@bidi_end_edge__" }',
+    "multibyte.css": getCSS("__MSG_multibyteKey__", MULTIBYTE_STRING),
   },
 };
 
 async function test_i18n_css(options = {}) {
   extensionData.useAddonManager = options.useAddonManager;
   let extension = ExtensionTestUtils.loadExtension(extensionData);
 
   await extension.startup();
-  let cssURL = await extension.awaitMessage("ready");
+  let baseURL = await extension.awaitMessage("ready");
 
   let contentPage = await ExtensionTestUtils.loadContentPage(`${BASE_URL}/file_sample.html`);
 
-  let css = await contentPage.fetch(cssURL);
+  let css = await contentPage.fetch(baseURL + "foo.css");
 
   equal(css, "body { max-width: 42px; }", "CSS file localized in mochitest scope");
 
   let maxWidth = await extension.awaitMessage("content-maxWidth");
 
   equal(maxWidth, "42px", "stylesheet correctly applied");
 
-  cssURL = cssURL.replace(/foo.css$/, "locale.css");
+  css = await contentPage.fetch(baseURL + "locale.css");
+  equal(css, '* { content: "en-US ltr rtl left right" }', "CSS file localized in mochitest scope");
 
-  css = await contentPage.fetch(cssURL);
-  equal(css, '* { content: "en-US ltr rtl left right" }', "CSS file localized in mochitest scope");
+  css = await contentPage.fetch(baseURL + "multibyte.css");
+  equal(css, getCSS(MULTIBYTE_STRING, MULTIBYTE_STRING), "CSS file contains multibyte string");
 
   await contentPage.close();
 
   // We don't currently have a good way to mock this.
   if (false) {
     const DIR = "intl.uidirection";
 
     // We don't wind up actually switching the chrome registry locale, since we
     // don't have a chrome package for Hebrew. So just override it, and force
     // RTL directionality.
     const origReqLocales = Services.locale.getRequestedLocales();
     Services.locale.setRequestedLocales(["he"]);
     Preferences.set(DIR, 1);
 
-    css = await fetch(cssURL);
+    css = await fetch(baseURL + "locale.css");
     equal(css, '* { content: "he rtl ltr right left" }', "CSS file localized in mochitest scope");
 
     Services.locale.setRequestedLocales(origReqLocales);
     Preferences.reset(DIR);
   }
 
   await extension.awaitFinish("i18n-css");
   await extension.unload();
--- a/toolkit/components/extensions/test/xpcshell/test_locale_converter.js
+++ b/toolkit/components/extensions/test/xpcshell/test_locale_converter.js
@@ -124,10 +124,10 @@ add_task(async function testInvalidUUID(
   }, expectInvalidContextException);
 });
 
 
 // Test that an empty stream does not throw an NS_ERROR_ILLEGAL_VALUE.
 add_task(async function testEmptyStream() {
   let stream = StringStream("");
   let resultStream = convService.convert(stream, FROM_TYPE, TO_TYPE, URI);
-  equal(resultStream.data, "");
+  equal(resultStream.available(), 0, "Size of output stream should match size of input stream");
 });
--- a/toolkit/components/utils/simpleServices.js
+++ b/toolkit/components/utils/simpleServices.js
@@ -20,16 +20,19 @@ ChromeUtils.defineModuleGetter(this, "Ne
                                "resource://gre/modules/NetUtil.jsm");
 ChromeUtils.defineModuleGetter(this, "Services",
                                "resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "catMan", "@mozilla.org/categorymanager;1",
                                    "nsICategoryManager");
 XPCOMUtils.defineLazyServiceGetter(this, "streamConv", "@mozilla.org/streamConverters;1",
                                    "nsIStreamConverterService");
+const ArrayBufferInputStream = Components.Constructor(
+  "@mozilla.org/io/arraybuffer-input-stream;1",
+  "nsIArrayBufferInputStream", "setData");
 
 /*
  * This class provides a stream filter for locale messages in CSS files served
  * by the moz-extension: protocol handler.
  *
  * See SubstituteChannel in netwerk/protocol/res/ExtensionProtocolHandler.cpp
  * for usage.
  */
@@ -63,56 +66,56 @@ AddonLocalizationConverter.prototype = {
     let addon = WebExtensionPolicy.getByURI(uri);
     if (!addon) {
       throw new Components.Exception("Invalid context", Cr.NS_ERROR_INVALID_ARG);
     }
     return addon;
   },
 
   convertToStream(aAddon, aString) {
-    let stream = Cc["@mozilla.org/io/string-input-stream;1"]
-      .createInstance(Ci.nsIStringInputStream);
-
-    stream.data = aAddon.localize(aString);
-    return stream;
+    aString = aAddon.localize(aString);
+    let bytes = new TextEncoder().encode(aString).buffer;
+    return new ArrayBufferInputStream(bytes, 0, bytes.byteLength);
   },
 
   convert(aStream, aFromType, aToType, aContext) {
     this.checkTypes(aFromType, aToType);
     let addon = this.getAddon(aContext);
 
-    let string = (
-      aStream.available() ?
-      NetUtil.readInputStreamToString(aStream, aStream.available()) : ""
-    );
+    let count = aStream.available();
+    let string = count ?
+      new TextDecoder().decode(NetUtil.readInputStream(aStream, count)) : "";
     return this.convertToStream(addon, string);
   },
 
   asyncConvertData(aFromType, aToType, aListener, aContext) {
     this.checkTypes(aFromType, aToType);
     this.addon = this.getAddon(aContext);
     this.listener = aListener;
   },
 
   onStartRequest(aRequest, aContext) {
     this.parts = [];
+    this.decoder = new TextDecoder();
   },
 
   onDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount) {
-    this.parts.push(NetUtil.readInputStreamToString(aInputStream, aCount));
+    let bytes = NetUtil.readInputStream(aInputStream, aCount);
+    this.parts.push(this.decoder.decode(bytes, {stream: true}));
   },
 
   onStopRequest(aRequest, aContext, aStatusCode) {
     try {
       this.listener.onStartRequest(aRequest, null);
       if (Components.isSuccessCode(aStatusCode)) {
+        this.parts.push(this.decoder.decode());
         let string = this.parts.join("");
         let stream = this.convertToStream(this.addon, string);
 
-        this.listener.onDataAvailable(aRequest, null, stream, 0, stream.data.length);
+        this.listener.onDataAvailable(aRequest, null, stream, 0, stream.available());
       }
     } catch (e) {
       aStatusCode = e.result || Cr.NS_ERROR_FAILURE;
     }
     this.listener.onStopRequest(aRequest, null, aStatusCode);
   },
 };