Merge inbound to mozilla-central r=merge a=merge
authorNoemi Erli <nerli@mozilla.com>
Wed, 15 Nov 2017 11:57:12 +0200
changeset 391869 45715ece25fcb064eee4f977ebd842d44a87f22b
parent 391838 0b135d7d8cef2858767e1384de7df75e0085d7c0 (current diff)
parent 391868 da16d74f97e28c68cf18c80e2e61b2c441956cfa (diff)
child 391870 e568a5c3cadcde50510fe640065c4c7b1b6dfd6a
child 391885 32de83b016c917b2a7a3df840e6c1bde294a56d5
child 391967 76d469663b3c54093aad44d58dd932cb659465a6
push id32905
push usernerli@mozilla.com
push dateWed, 15 Nov 2017 09:57:50 +0000
treeherdermozilla-central@45715ece25fc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone59.0a1
first release with
nightly linux32
45715ece25fc / 59.0a1 / 20171115100050 / files
nightly linux64
45715ece25fc / 59.0a1 / 20171115100050 / files
nightly mac
45715ece25fc / 59.0a1 / 20171115100050 / files
nightly win32
45715ece25fc / 59.0a1 / 20171115100050 / files
nightly win64
45715ece25fc / 59.0a1 / 20171115100050 / 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 inbound to mozilla-central r=merge a=merge
build/moz.configure/toolchain.configure
js/src/gc/Verifier.cpp
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -1325,19 +1325,19 @@ js_option(env='RUSTC_OPT_LEVEL',
           nargs=1,
           help='Rust compiler optimization level (-C opt-level=%s)')
 
 # --enable-release kicks in full optimizations.
 imply_option('RUSTC_OPT_LEVEL', '2', when='--enable-release')
 
 
 @depends('RUSTC_OPT_LEVEL', debug_rust, '--enable-debug-symbols',
-         moz_optimize)
+         moz_optimize, host, target)
 def rust_compiler_flags(opt_level_option, debug_rust, debug_symbols,
-                        moz_optimize):
+                        moz_optimize, host, target):
     optimize = moz_optimize.optimize
 
     # Cargo currently supports only two interesting profiles for building:
     # development and release. Those map (roughly) to --enable-debug and
     # --disable-debug in Gecko, respectively.
     #
     # But we'd also like to support an additional axis of control for
     # optimization level. Since Cargo only supports 2 profiles, we're in
@@ -1357,17 +1357,21 @@ def rust_compiler_flags(opt_level_option
         opt_level = '1' if optimize else '0'
 
     # opt-level=0 implies -C debug-assertions, which may not be desired
     # unless Rust debugging is enabled.
     if opt_level == '0' and not debug_rust:
         debug_assertions = False
 
     if debug_symbols:
-        debug_info = '2'
+        if host.kernel == 'Linux' and target.kernel == 'Darwin':
+            # hack to work around dsymutil failing on cross-OSX builds (bug 1410148)
+            debug_info = '1'
+        else:
+            debug_info = '2'
 
     opts = []
 
     if opt_level is not None:
         opts.append('opt-level=%s' % opt_level)
     if debug_assertions is not None:
         opts.append('debug-assertions=%s' %
                     ('yes' if debug_assertions else 'no'))
--- a/devtools/client/debugger/new/README.mozilla
+++ b/devtools/client/debugger/new/README.mozilla
@@ -1,11 +1,11 @@
 This is the debugger.html project output.
 See https://github.com/devtools-html/debugger.html
 
-Taken from upstream commit: 09d6d4f93135367b2639d78ad884434f73ab449c
+Taken from upstream commit: d2e91e574acbe3d5b546508d028bd278eaabd286
 
 Packages:
 - babel-plugin-transform-es2015-modules-commonjs @6.26.0
 - babel-preset-react @6.24.1
 - react @15.6.2
 - react-dom @15.6.2
 - webpack @3.8.1
--- a/devtools/client/debugger/new/debugger.css
+++ b/devtools/client/debugger/new/debugger.css
@@ -599,16 +599,40 @@ button:focus {
 
 /* Utils */
 .absolute-center {
   position: absolute;
   top: 50%;
   left: 50%;
   transform: translate(-50%, -50%);
 }
+
+.d-flex {
+  display: flex;
+}
+
+.align-items-center {
+  align-items: center;
+}
+
+.rounded-circle {
+  border-radius: 50%;
+}
+
+.text-white {
+  color: white;
+}
+
+.text-center {
+  text-align: center;
+}
+
+.min-width-0 {
+  min-width: 0;
+}
 /* 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/>. */
 
 menupopup {
   position: fixed;
   z-index: 10000;
   background: white;
@@ -2870,16 +2894,32 @@ html .breakpoints-list .breakpoint.pause
 .frames ul .frames-group .frames-list li {
   padding-left: 30px;
 }
 
 .frames ul .frames-group .frames-list {
   border-top: 1px solid var(--theme-splitter-color);
   border-bottom: 1px solid var(--theme-splitter-color);
 }
+
+.frames ul .frames-group.expanded .badge {
+  color: var(--theme-highlight-blue);
+}
+.badge {
+  --size: 17px;
+  --radius: calc(var(--size) / 2);
+  height: var(--size);
+  min-width: var(--size);
+  line-height: var(--size);
+  background: var(--theme-toolbar-background-hover);
+  color: var(--theme-body-color);
+  border-radius: var(--radius);
+  padding: 0 4px;
+  font-size: 0.9em;
+}
 /* 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/>. */
 
 .why-paused {
   background-color: var(--theme-body-background);
   color: var(--theme-body-color);
   padding: 10px 10px 10px 20px;
@@ -2913,53 +2953,59 @@ html .breakpoints-list .breakpoint.pause
 
 .frames ul {
   list-style: none;
   margin: 0;
   padding: 0;
 }
 
 .frames ul li {
-  padding: 7px 10px 7px 21px;
+  padding: 0 10px 0 21px;
   overflow: hidden;
   display: flex;
   justify-content: space-between;
   flex-direction: row;
   align-items: center;
   margin: 0;
 }
 
 .frames ul li * {
   -moz-user-select: none;
   user-select: none;
 }
 
+.frames .badge {
+  flex-shrink: 0;
+  margin-right: 4px;
+}
+
 .frames .location {
   font-weight: lighter;
   display: flex;
   justify-content: space-between;
   flex-direction: row;
   align-items: center;
   margin: 0;
+  flex-shrink: 0;
 }
 
 .theme-light .frames .location,
 .theme-firebug .frames .location {
   color: var(--theme-comment);
 }
 
 :root.theme-dark .frames .location {
   color: var(--theme-body-color);
   opacity: 0.6;
 }
 
 .frames .title {
   text-overflow: ellipsis;
   overflow: hidden;
-  margin-right: 1em;
+  margin: 7px 0.5em 7px 0;
 }
 
 .frames ul li:hover,
 .frames ul li:focus {
   background-color: var(--theme-toolbar-background-alt);
   outline: none;
 }
 
@@ -3589,17 +3635,17 @@ html .welcomebox .toggle-button-end.coll
   border: 1px solid var(--theme-splitter-color);
   box-shadow: 0 4px 4px 0 var(--search-overlays-semitransparent);
   max-height: 300px;
   position: absolute;
   right: 0;
   top: 23px;
   width: var(--width);
   z-index: 1000;
-  overflow: scroll;
+  overflow: auto;
 }
 
 html[dir="rtl"] .dropdown {
   right: calc((var(--width) - 11px) * (-1));
 }
 
 .dropdown-block {
   padding: 0px 2px;
--- a/devtools/client/debugger/new/debugger.js
+++ b/devtools/client/debugger/new/debugger.js
@@ -26981,17 +26981,17 @@ FrameComponent.displayName = "Frame";
 "use strict";
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
 exports.default = FrameMenu;
 
-var _devtoolsLaunchpad = __webpack_require__(1362);
+var _devtoolsContextmenu = __webpack_require__(1413);
 
 var _clipboard = __webpack_require__(1388);
 
 var _lodash = __webpack_require__(2);
 
 const blackboxString = "sourceFooter.blackbox"; /* 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/>. */
@@ -27047,17 +27047,17 @@ function FrameMenu(frame, frameworkGroup
     menuOptions.push(copySourceUri2);
     menuOptions.push(blackBoxSource(source, callbacks.toggleBlackBox));
   }
 
   const copyStackTraceItem = copyStackTraceElement(callbacks.copyStackTrace);
 
   menuOptions.push(copyStackTraceItem);
 
-  (0, _devtoolsLaunchpad.showMenu)(event, menuOptions);
+  (0, _devtoolsContextmenu.showMenu)(event, menuOptions);
 }
 
 /***/ }),
 /* 1455 */,
 /* 1456 */,
 /* 1457 */,
 /* 1458 */,
 /* 1459 */
@@ -35312,17 +35312,17 @@ var _ManagedTree2 = _interopRequireDefau
 var _Svg = __webpack_require__(1359);
 
 var _Svg2 = _interopRequireDefault(_Svg);
 
 var _sourcesTree = __webpack_require__(1442);
 
 var _immutable = __webpack_require__(146);
 
-var _devtoolsLaunchpad = __webpack_require__(1362);
+var _devtoolsContextmenu = __webpack_require__(1413);
 
 var _clipboard = __webpack_require__(1388);
 
 var _utils = __webpack_require__(1366);
 
 var _prefs = __webpack_require__(226);
 
 var _ui = __webpack_require__(1385);
@@ -35490,17 +35490,17 @@ class SourcesTree extends _react.Compone
       menuOptions.push({
         id: "node-set-directory-root",
         label: setDirectoryRootLabel,
         accesskey: setDirectoryRootKey,
         disabled: false,
         click: () => (0, _ui.setProjectDirectoryRoot)(item.path)
       });
     }
-    (0, _devtoolsLaunchpad.showMenu)(event, menuOptions);
+    (0, _devtoolsContextmenu.showMenu)(event, menuOptions);
   }
 
   renderItem(item, depth, focused, _, expanded, { setExpanded }) {
     const arrow = (0, _sourcesTree.nodeHasChildren)(item) ? _react2.default.createElement("img", {
       className: (0, _classnames2.default)("arrow", {
         expanded: expanded
       }),
       onClick: e => {
@@ -40603,17 +40603,17 @@ Object.defineProperty(exports, "__esModu
 var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; /* 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.gutterMenu = gutterMenu;
 
 var _react = __webpack_require__(0);
 
-var _devtoolsLaunchpad = __webpack_require__(1362);
+var _devtoolsContextmenu = __webpack_require__(1413);
 
 var _redux = __webpack_require__(3);
 
 var _reactRedux = __webpack_require__(1189);
 
 var _editor = __webpack_require__(1358);
 
 var _selectors = __webpack_require__(1352);
@@ -40704,17 +40704,17 @@ function gutterMenu({
     const disableBreakpoint = _extends({
       accesskey: L10N.getStr("editor.disableBreakpoint.accesskey"),
       disabled: false,
       click: () => toggleDisabledBreakpoint(line)
     }, breakpoint.disabled ? gutterItems.enableBreakpoint : gutterItems.disableBreakpoint);
     items.push(disableBreakpoint);
   }
 
-  (0, _devtoolsLaunchpad.showMenu)(event, items);
+  (0, _devtoolsContextmenu.showMenu)(event, items);
 }
 
 class GutterContextMenuComponent extends _react.PureComponent {
 
   constructor() {
     super();
   }
 
@@ -40768,17 +40768,17 @@ exports.default = (0, _reactRedux.connec
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
 
 var _react = __webpack_require__(0);
 
-var _devtoolsLaunchpad = __webpack_require__(1362);
+var _devtoolsContextmenu = __webpack_require__(1413);
 
 var _devtoolsSourceMap = __webpack_require__(1360);
 
 var _clipboard = __webpack_require__(1388);
 
 var _source = __webpack_require__(1356);
 
 var _editor = __webpack_require__(1358);
@@ -40853,23 +40853,25 @@ function getMenuItems(event, {
 
   const sourceLocation = (0, _editor.getSourceLocationFromMouseEvent)(editor, selectedLocation, event);
 
   const isOriginal = (0, _devtoolsSourceMap.isOriginalId)(selectedLocation.sourceId);
   const hasSourceMap = selectedSource.get("sourceMapURL");
   const isPrettyPrinted = (0, _source.isPretty)(selectedSource.toJS());
 
   const jumpLabel = {
+    id: "node-menu-jump",
     accesskey: L10N.getStr("editor.jumpToMappedLocation1.accesskey"),
     disabled: _devtoolsSourceMap.isGeneratedId && !hasSourceMap,
     label: L10N.getFormatStr("editor.jumpToMappedLocation1", isOriginal ? L10N.getStr("generated") : L10N.getStr("original")),
     click: () => jumpToMappedLocation(sourceLocation)
   };
 
   const watchExpressionLabel = {
+    id: "node-menu-add-watch-expression",
     accesskey: L10N.getStr("expressions.accesskey"),
     label: L10N.getStr("expressions.label"),
     click: () => addExpression(editor.codeMirror.getSelection())
   };
 
   const blackBoxMenuItem = {
     id: "node-menu-blackbox",
     label: toggleBlackBoxLabel,
@@ -40930,17 +40932,17 @@ class EditorMenu extends _react.PureComp
     this.props.setContextMenu("", null);
     return this.showMenu(nextProps);
   }
 
   showMenu(nextProps) {
     const { contextMenu } = nextProps,
           options = _objectWithoutProperties(nextProps, ["contextMenu"]);
     const { event } = contextMenu;
-    (0, _devtoolsLaunchpad.showMenu)(event, getMenuItems(event, options));
+    (0, _devtoolsContextmenu.showMenu)(event, getMenuItems(event, options));
   }
 
   render() {
     return null;
   }
 }
 
 exports.default = (0, _reactRedux.connect)(state => {
@@ -41169,20 +41171,16 @@ var _react = __webpack_require__(0);
 var _react2 = _interopRequireDefault(_react);
 
 var _reactRedux = __webpack_require__(1189);
 
 var _redux = __webpack_require__(3);
 
 var _prefs = __webpack_require__(226);
 
-var _reactImmutableProptypes = __webpack_require__(150);
-
-var _reactImmutableProptypes2 = _interopRequireDefault(_reactImmutableProptypes);
-
 var _actions = __webpack_require__(1354);
 
 var _actions2 = _interopRequireDefault(_actions);
 
 var _selectors = __webpack_require__(1352);
 
 var _devtoolsConfig = __webpack_require__(1355);
 
@@ -41233,21 +41231,19 @@ var _ChromeScopes2 = _interopRequireDefa
 var _Scopes2 = __webpack_require__(1611);
 
 var _Scopes3 = _interopRequireDefault(_Scopes2);
 
 __webpack_require__(1342);
 
 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/>. */
-
-const Scopes = (0, _devtoolsConfig.isEnabled)("chromeScopes") ? _ChromeScopes2.default : _Scopes3.default;
+const Scopes = (0, _devtoolsConfig.isEnabled)("chromeScopes") ? _ChromeScopes2.default : _Scopes3.default; /* 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 debugBtn(onClick, type, className, tooltip) {
   return _react2.default.createElement(
     "button",
     {
       onClick: onClick,
       className: `${type} ${className}`,
       key: type,
@@ -41428,17 +41424,17 @@ class SecondaryPanes extends _react.Comp
     );
   }
 }
 
 SecondaryPanes.propTypes = {
   evaluateExpressions: _propTypes2.default.func.isRequired,
   pauseData: _propTypes2.default.object,
   horizontal: _propTypes2.default.bool,
-  breakpoints: _reactImmutableProptypes2.default.map.isRequired,
+  breakpoints: _propTypes2.default.object,
   breakpointsDisabled: _propTypes2.default.bool,
   breakpointsLoading: _propTypes2.default.bool,
   toggleAllBreakpoints: _propTypes2.default.func.isRequired,
   toggleShortcutsModal: _propTypes2.default.func
 };
 
 SecondaryPanes.contextTypes = {
   shortcuts: _propTypes2.default.object
@@ -41493,17 +41489,17 @@ var _actions2 = _interopRequireDefault(_
 var _selectors = __webpack_require__(1352);
 
 var _breakpoint = __webpack_require__(1364);
 
 var _utils = __webpack_require__(1366);
 
 var _source = __webpack_require__(1356);
 
-var _devtoolsLaunchpad = __webpack_require__(1362);
+var _devtoolsContextmenu = __webpack_require__(1413);
 
 var _Close = __webpack_require__(1374);
 
 var _Close2 = _interopRequireDefault(_Close);
 
 __webpack_require__(1334);
 
 var _lodash = __webpack_require__(2);
@@ -41728,17 +41724,17 @@ class Breakpoints extends _react.PureCom
     }, {
       item: editCondition,
       hidden: () => !breakpoint.condition
     }, {
       item: removeCondition,
       hidden: () => !breakpoint.condition
     }];
 
-    (0, _devtoolsLaunchpad.showMenu)(e, (0, _devtoolsLaunchpad.buildMenu)(items));
+    (0, _devtoolsContextmenu.showMenu)(e, (0, _devtoolsContextmenu.buildMenu)(items));
   }
 
   selectBreakpoint(breakpoint) {
     const sourceId = breakpoint.location.sourceId;
     const { location } = breakpoint;
     this.props.selectSource(sourceId, { location });
   }
 
@@ -42296,33 +42292,39 @@ var _FrameMenu = __webpack_require__(145
 var _FrameMenu2 = _interopRequireDefault(_FrameMenu);
 
 __webpack_require__(1336);
 
 var _Frame = __webpack_require__(1453);
 
 var _Frame2 = _interopRequireDefault(_Frame);
 
-function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+var _Badge = __webpack_require__(1704);
+
+var _Badge2 = _interopRequireDefault(_Badge);
+
+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 FrameLocation({ frame }) {
   const library = (0, _frame.getLibraryFromUrl)(frame);
   if (!library) {
     return null;
   }
 
   return _react2.default.createElement(
     "div",
     { className: "location" },
     library,
     _react2.default.createElement(_Svg2.default, { name: library.toLowerCase(), className: "annotation-logo" })
   );
-} /* 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/>. */
+}
 
 FrameLocation.displayName = "FrameLocation";
 
 class Group extends _react.Component {
 
   constructor(...args) {
     super(...args);
 
@@ -42387,18 +42389,27 @@ class Group extends _react.Component {
       {
         key: frame.id,
         className: (0, _classnames2.default)("group"),
         onClick: this.toggleFrames,
         tabIndex: 0
       },
       _react2.default.createElement(
         "div",
-        { className: "title" },
-        displayName
+        { className: "d-flex align-items-center min-width-0" },
+        _react2.default.createElement(
+          "div",
+          { className: "title" },
+          displayName
+        ),
+        _react2.default.createElement(
+          _Badge2.default,
+          null,
+          this.props.group.length
+        )
       ),
       _react2.default.createElement(FrameLocation, { frame: frame })
     );
   }
 
   render() {
     const { expanded } = this.state;
     return _react2.default.createElement(
@@ -43770,17 +43781,17 @@ var _classnames2 = _interopRequireDefaul
 var _actions = __webpack_require__(1354);
 
 var _actions2 = _interopRequireDefault(_actions);
 
 var _Close = __webpack_require__(1374);
 
 var _Close2 = _interopRequireDefault(_Close);
 
-var _devtoolsLaunchpad = __webpack_require__(1362);
+var _devtoolsContextmenu = __webpack_require__(1413);
 
 var _lodash = __webpack_require__(2);
 
 __webpack_require__(1344);
 
 var _PaneToggle = __webpack_require__(1407);
 
 var _PaneToggle2 = _interopRequireDefault(_PaneToggle);
@@ -43988,17 +43999,17 @@ class SourceTabs extends _react.PureComp
       hidden: () => tabs.some((t, i) => t === tab && tabs.size - 1 === i)
     }, { item: closeAllTabsMenuItem }, { item: { type: "separator" } }, { item: copySourceUri2 }];
 
     if (!isPrettySource) {
       items.push({ item: showSourceMenuItem });
       items.push({ item: prettyPrint });
     }
 
-    (0, _devtoolsLaunchpad.showMenu)(e, (0, _devtoolsLaunchpad.buildMenu)(items));
+    (0, _devtoolsContextmenu.showMenu)(e, (0, _devtoolsContextmenu.buildMenu)(items));
   }
 
   /*
    * Updates the hiddenSourceTabs state, by
    * finding the source tabs which are wrapped and are not on the top row.
    */
   updateHiddenSourceTabs() {
     if (!this.refs.sourceTabs) {
@@ -45933,11 +45944,84 @@ function timing(store) {
     mark(`${action.type}_start`);
     const result = next(action);
     mark(`${action.type}_end`);
     measure(`${action.type}`, `${action.type}_start`, `${action.type}_end`);
     return result;
   };
 }
 
+/***/ }),
+/* 1664 */,
+/* 1665 */,
+/* 1666 */,
+/* 1667 */,
+/* 1668 */,
+/* 1669 */,
+/* 1670 */,
+/* 1671 */,
+/* 1672 */,
+/* 1673 */,
+/* 1674 */,
+/* 1675 */,
+/* 1676 */,
+/* 1677 */,
+/* 1678 */,
+/* 1679 */,
+/* 1680 */,
+/* 1681 */,
+/* 1682 */,
+/* 1683 */,
+/* 1684 */,
+/* 1685 */,
+/* 1686 */,
+/* 1687 */,
+/* 1688 */,
+/* 1689 */,
+/* 1690 */,
+/* 1691 */,
+/* 1692 */,
+/* 1693 */,
+/* 1694 */,
+/* 1695 */,
+/* 1696 */,
+/* 1697 */,
+/* 1698 */,
+/* 1699 */,
+/* 1700 */,
+/* 1701 */,
+/* 1702 */,
+/* 1703 */,
+/* 1704 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _react = __webpack_require__(0);
+
+var _react2 = _interopRequireDefault(_react);
+
+__webpack_require__(1705);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+const Badge = ({ children }) => _react2.default.createElement(
+  "div",
+  { className: "badge text-white text-center" },
+  children
+);
+
+exports.default = Badge;
+
+/***/ }),
+/* 1705 */
+/***/ (function(module, exports) {
+
+// removed by extract-text-webpack-plugin
+
 /***/ })
 /******/ ]);
 });
\ No newline at end of file
--- a/devtools/client/framework/components/toolbox-controller.js
+++ b/devtools/client/framework/components/toolbox-controller.js
@@ -142,16 +142,20 @@ class ToolboxController extends Componen
     this.updateButtonIds();
   }
 
   setPanelDefinitions(panelDefinitions) {
     this.setState({ panelDefinitions });
     this.updateButtonIds();
   }
 
+  get panelDefinitions() {
+    return this.state.panelDefinitions;
+  }
+
   setToolboxButtons(toolboxButtons) {
     // Listen for updates of the checked attribute.
     this.state.toolboxButtons.forEach(button => {
       button.off("updatechecked", this.state.checkedButtonsUpdated);
     });
     toolboxButtons.forEach(button => {
       button.on("updatechecked", this.state.checkedButtonsUpdated);
     });
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -1920,35 +1920,35 @@ Toolbox.prototype = {
   reloadTarget: function (force) {
     this.target.activeTab.reload({ force: force });
   },
 
   /**
    * Loads the tool next to the currently selected tool.
    */
   selectNextTool: function () {
-    const index = this.panelDefinitions.findIndex(({id}) => id === this.currentToolId);
-    let definition = this.panelDefinitions[index + 1];
+    let definitions = this.component.panelDefinitions;
+    const index = definitions.findIndex(({id}) => id === this.currentToolId);
+    let definition = definitions[index + 1];
     if (!definition) {
-      definition = index === -1
-        ? this.panelDefinitions[0]
-        : this.optionsDefinition;
+      definition = index === -1 ? definitions[0] : this.optionsDefinition;
     }
     return this.selectTool(definition.id);
   },
 
   /**
    * Loads the tool just left to the currently selected tool.
    */
   selectPreviousTool: function () {
-    const index = this.panelDefinitions.findIndex(({id}) => id === this.currentToolId);
-    let definition = this.panelDefinitions[index - 1];
+    let definitions = this.component.panelDefinitions;
+    const index = definitions.findIndex(({id}) => id === this.currentToolId);
+    let definition = definitions[index - 1];
     if (!definition) {
       definition = index === -1
-        ? this.panelDefinitions[this.panelDefinitions.length - 1]
+        ? definitions[definitions.length - 1]
         : this.optionsDefinition;
     }
     return this.selectTool(definition.id);
   },
 
   /**
    * Highlights the tool's tab if it is not the currently selected tool.
    *
--- a/devtools/server/actors/highlighters/css-grid.js
+++ b/devtools/server/actors/highlighters/css-grid.js
@@ -164,17 +164,17 @@ class CssGridHighlighter extends AutoRef
     this.onPageHide = this.onPageHide.bind(this);
     this.onWillNavigate = this.onWillNavigate.bind(this);
 
     this.highlighterEnv.on("will-navigate", this.onWillNavigate);
 
     let { pageListenerTarget } = highlighterEnv;
     pageListenerTarget.addEventListener("pagehide", this.onPageHide);
 
-    // Initialize the <canvas> position to the top left corner of the page
+    // Initialize the <canvas> position to the top left corner of the page.
     this._canvasPosition = {
       x: 0,
       y: 0
     };
 
     // Calling `updateCanvasPosition` anyway since the highlighter could be initialized
     // on a page that has scrolled already.
     updateCanvasPosition(this._canvasPosition, this._scroll, this.win,
@@ -208,17 +208,17 @@ class CssGridHighlighter extends AutoRef
         "class": "canvas",
         "hidden": "true",
         "width": CANVAS_SIZE,
         "height": CANVAS_SIZE
       },
       prefix: this.ID_CLASS_PREFIX
     });
 
-    // Build the SVG element
+    // Build the SVG element.
     let svg = createSVGNode(this.win, {
       nodeType: "svg",
       parent: root,
       attributes: {
         "id": "elements",
         "width": "100%",
         "height": "100%",
         "hidden": "true"
@@ -250,17 +250,17 @@ class CssGridHighlighter extends AutoRef
       parent: regions,
       attributes: {
         "class": "cells",
         "id": "cells"
       },
       prefix: this.ID_CLASS_PREFIX
     });
 
-    // Building the grid area infobar markup
+    // Build the grid area infobar markup.
     let areaInfobarContainer = createNode(this.win, {
       parent: container,
       attributes: {
         "class": "area-infobar-container",
         "id": "area-infobar-container",
         "position": "top",
         "hidden": "true"
       },
@@ -296,17 +296,17 @@ class CssGridHighlighter extends AutoRef
       parent: areaTextbox,
       attributes: {
         "class": "area-infobar-dimensions",
         "id": "area-infobar-dimensions"
       },
       prefix: this.ID_CLASS_PREFIX
     });
 
-    // Building the grid cell infobar markup
+    // Build the grid cell infobar markup.
     let cellInfobarContainer = createNode(this.win, {
       parent: container,
       attributes: {
         "class": "cell-infobar-container",
         "id": "cell-infobar-container",
         "position": "top",
         "hidden": "true"
       },
@@ -342,17 +342,17 @@ class CssGridHighlighter extends AutoRef
       parent: cellTextbox,
       attributes: {
         "class": "cell-infobar-dimensions",
         "id": "cell-infobar-dimensions"
       },
       prefix: this.ID_CLASS_PREFIX
     });
 
-    // Building the grid line infobar markup
+    // Build the grid line infobar markup.
     let lineInfobarContainer = createNode(this.win, {
       parent: container,
       attributes: {
         "class": "line-infobar-container",
         "id": "line-infobar-container",
         "position": "top",
         "hidden": "true"
       },
@@ -391,48 +391,76 @@ class CssGridHighlighter extends AutoRef
         "id": "line-infobar-names"
       },
       prefix: this.ID_CLASS_PREFIX
     });
 
     return container;
   }
 
+  clearCache() {
+    gCachedGridPattern.clear();
+  }
+
+  /**
+   * Clear the grid area highlights.
+   */
+  clearGridAreas() {
+    let areas = this.getElement("areas");
+    areas.setAttribute("d", "");
+  }
+
+  /**
+   * Clear the grid cell highlights.
+   */
+  clearGridCell() {
+    let cells = this.getElement("cells");
+    cells.setAttribute("d", "");
+  }
+
   destroy() {
     let { highlighterEnv } = this;
     highlighterEnv.off("will-navigate", this.onWillNavigate);
 
     let { pageListenerTarget } = highlighterEnv;
     if (pageListenerTarget) {
       pageListenerTarget.removeEventListener("pagehide", this.onPageHide);
     }
 
     this.markup.destroy();
 
     // Clear the pattern cache to avoid dead object exceptions (Bug 1342051).
-    this._clearCache();
+    this.clearCache();
     AutoRefreshHighlighter.prototype.destroy.call(this);
   }
 
-  getElement(id) {
-    return this.markup.getElement(this.ID_CLASS_PREFIX + id);
-  }
-
-  get ctx() {
-    return this.canvas.getCanvasContext("2d");
-  }
-
   get canvas() {
     return this.getElement("canvas");
   }
 
   get color() {
     return this.options.color || DEFAULT_COLOR;
   }
 
+  get ctx() {
+    return this.canvas.getCanvasContext("2d");
+  }
+
+  getElement(id) {
+    return this.markup.getElement(this.ID_CLASS_PREFIX + id);
+  }
+
+  getFirstColLinePos(fragment) {
+    return fragment.cols.lines[0].start;
+  }
+
+  getFirstRowLinePos(fragment) {
+    return fragment.rows.lines[0].start;
+  }
+
   /**
    * Gets the grid gap pattern used to render the gap regions based on the device
    * pixel ratio given.
    *
    * @param  {Number} devicePixelRatio
    *         The device pixel ratio we want the pattern for.
    * @param  {Object} dimension
    *         Refers to the Map key for the grid dimension type which is either the
@@ -479,82 +507,179 @@ class CssGridHighlighter extends AutoRef
     let pattern = ctx.createPattern(canvas, "repeat");
 
     gridPatternMap.set(dimension, pattern);
     gCachedGridPattern.set(devicePixelRatio, gridPatternMap);
 
     return pattern;
   }
 
-  onPageHide({ target }) {
-    // If a page hide event is triggered for current window's highlighter, hide the
-    // highlighter.
-    if (target.defaultView === this.win) {
-      this.hide();
+  getLastColLinePos(fragment) {
+    return fragment.cols.lines[fragment.cols.lines.length - 1].start;
+  }
+
+  /**
+   * Get the GridLine index of the last edge of the explicit grid for a grid dimension.
+   *
+   * @param  {GridTracks} tracks
+   *         The grid track of a given grid dimension.
+   * @return {Number} index of the last edge of the explicit grid for a grid dimension.
+   */
+  getLastEdgeLineIndex(tracks) {
+    let trackIndex = tracks.length - 1;
+
+    // Traverse the grid track backwards until we find an explicit track.
+    while (trackIndex >= 0 && tracks[trackIndex].type != "explicit") {
+      trackIndex--;
     }
+
+    // The grid line index is the grid track index + 1.
+    return trackIndex + 1;
+  }
+
+  getLastRowLinePos(fragment) {
+    return fragment.rows.lines[fragment.rows.lines.length - 1].start;
+  }
+
+  /**
+   * The AutoRefreshHighlighter's _hasMoved method returns true only if the
+   * element's quads have changed. Override it so it also returns true if the
+   * element's grid has changed (which can happen when you change the
+   * grid-template-* CSS properties with the highlighter displayed).
+   */
+  _hasMoved() {
+    let hasMoved = AutoRefreshHighlighter.prototype._hasMoved.call(this);
+
+    let oldGridData = stringifyGridFragments(this.gridData);
+    this.gridData = this.currentNode.getGridFragments();
+    let newGridData = stringifyGridFragments(this.gridData);
+
+    return hasMoved || oldGridData !== newGridData;
   }
 
   /**
-   * Called when the page will-navigate. Used to hide the grid highlighter and clear
-   * the cached gap patterns and avoid using DeadWrapper obejcts as gap patterns the
-   * next time.
+   * Hide the highlighter, the canvas and the infobars.
    */
-  onWillNavigate({ isTopLevel }) {
-    this._clearCache();
+  _hide() {
+    setIgnoreLayoutChanges(true);
+    this._hideGrid();
+    this._hideGridElements();
+    this._hideGridAreaInfoBar();
+    this._hideGridCellInfoBar();
+    this._hideGridLineInfoBar();
+    setIgnoreLayoutChanges(false, this.highlighterEnv.document.documentElement);
+  }
+
+  _hideGrid() {
+    this.getElement("canvas").setAttribute("hidden", "true");
+  }
+
+  _hideGridAreaInfoBar() {
+    this.getElement("area-infobar-container").setAttribute("hidden", "true");
+  }
+
+  _hideGridCellInfoBar() {
+    this.getElement("cell-infobar-container").setAttribute("hidden", "true");
+  }
+
+  _hideGridElements() {
+    this.getElement("elements").setAttribute("hidden", "true");
+  }
+
+  _hideGridLineInfoBar() {
+    this.getElement("line-infobar-container").setAttribute("hidden", "true");
+  }
 
-    if (isTopLevel) {
-      this.hide();
+  /**
+   * Checks if the current node has a CSS Grid layout.
+   *
+   * @return {Boolean} true if the current node has a CSS grid layout, false otherwise.
+   */
+  isGrid() {
+    return this.currentNode.getGridFragments().length > 0;
+  }
+
+  /**
+   * Is a given grid fragment valid? i.e. does it actually have tracks? In some cases, we
+   * may have a fragment that defines column tracks but doesn't have any rows (or vice
+   * versa). In which case we do not want to draw anything for that fragment.
+   *
+   * @param  {Object} fragment
+   * @return {Boolean}
+   */
+  isValidFragment(fragment) {
+    return fragment.cols.tracks.length && fragment.rows.tracks.length;
+  }
+
+  /**
+   * The <canvas>'s position needs to be updated if the page scrolls too much, in order
+   * to give the illusion that it always covers the viewport.
+   */
+  _scrollUpdate() {
+    let hasUpdated = updateCanvasPosition(this._canvasPosition, this._scroll, this.win,
+      this._winDimensions);
+
+    if (hasUpdated) {
+      this._update();
     }
   }
 
   _show() {
     if (!this.isGrid()) {
       this.hide();
       return false;
     }
 
     // The grid pattern cache should be cleared in case the color changed.
-    this._clearCache();
+    this.clearCache();
 
     // Hide the canvas, grid element highlights and infobar.
     this._hide();
 
     return this._update();
   }
 
-  _clearCache() {
-    gCachedGridPattern.clear();
+  _showGrid() {
+    this.getElement("canvas").removeAttribute("hidden");
+  }
+
+  _showGridAreaInfoBar() {
+    this.getElement("area-infobar-container").removeAttribute("hidden");
+  }
+
+  _showGridCellInfoBar() {
+    this.getElement("cell-infobar-container").removeAttribute("hidden");
+  }
+
+  _showGridElements() {
+    this.getElement("elements").removeAttribute("hidden");
+  }
+
+  _showGridLineInfoBar() {
+    this.getElement("line-infobar-container").removeAttribute("hidden");
+  }
+
+  /**
+   * Shows all the grid area highlights for the current grid.
+   */
+  showAllGridAreas() {
+    this.renderGridArea();
   }
 
   /**
    * Shows the grid area highlight for the given area name.
    *
    * @param  {String} areaName
    *         Grid area name.
    */
   showGridArea(areaName) {
     this.renderGridArea(areaName);
   }
 
   /**
-   * Shows all the grid area highlights for the current grid.
-   */
-  showAllGridAreas() {
-    this.renderGridArea();
-  }
-
-  /**
-   * Clear the grid area highlights.
-   */
-  clearGridAreas() {
-    let areas = this.getElement("areas");
-    areas.setAttribute("d", "");
-  }
-
-  /**
    * Shows the grid cell highlight for the given grid cell options.
    *
    * @param  {Number} options.gridFragmentIndex
    *         Index of the grid fragment to render the grid cell highlight.
    * @param  {Number} options.rowNumber
    *         Row number of the grid cell to highlight.
    * @param  {Number} options.columnNumber
    *         Column number of the grid cell to highlight.
@@ -573,64 +698,756 @@ class CssGridHighlighter extends AutoRef
    * @param  {String} options.type
    *         The dimension type of the grid line.
    */
   showGridLineNames({ gridFragmentIndex, lineNumber, type }) {
     this.renderGridLineNames(gridFragmentIndex, lineNumber, type);
   }
 
   /**
-   * Clear the grid cell highlights.
+   * If a page hide event is triggered for current window's highlighter, hide the
+   * highlighter.
+   */
+  onPageHide({ target }) {
+    if (target.defaultView === this.win) {
+      this.hide();
+    }
+  }
+
+  /**
+   * Called when the page will-navigate. Used to hide the grid highlighter and clear
+   * the cached gap patterns and avoid using DeadWrapper obejcts as gap patterns the
+   * next time.
    */
-  clearGridCell() {
-    let cells = this.getElement("cells");
-    cells.setAttribute("d", "");
+  onWillNavigate({ isTopLevel }) {
+    this.clearCache();
+
+    if (isTopLevel) {
+      this.hide();
+    }
+  }
+
+  renderFragment(fragment) {
+    if (!this.isValidFragment(fragment)) {
+      return;
+    }
+
+    this.renderLines(fragment.cols, COLUMNS, this.getFirstRowLinePos(fragment),
+      this.getLastRowLinePos(fragment));
+    this.renderLines(fragment.rows, ROWS, this.getFirstColLinePos(fragment),
+      this.getLastColLinePos(fragment));
+
+    if (this.options.showGridAreasOverlay) {
+      this.renderGridAreaOverlay();
+    }
+
+    // Line numbers are rendered in a 2nd step to avoid overlapping with existing lines.
+    if (this.options.showGridLineNumbers) {
+      this.renderLineNumbers(fragment.cols, COLUMNS, this.getFirstRowLinePos(fragment));
+      this.renderLineNumbers(fragment.rows, ROWS, this.getFirstColLinePos(fragment));
+
+      if (Services.prefs.getBoolPref(NEGATIVE_LINE_NUMBERS_PREF)) {
+        this.renderNegativeLineNumbers(fragment.cols, COLUMNS,
+          this.getLastRowLinePos(fragment));
+        this.renderNegativeLineNumbers(fragment.rows, ROWS,
+          this.getLastColLinePos(fragment));
+      }
+    }
+  }
+
+  /**
+   * Render the grid area highlight for the given area name or for all the grid areas.
+   *
+   * @param  {String} areaName
+   *         Name of the grid area to be highlighted. If no area name is provided, all
+   *         the grid areas should be highlighted.
+   */
+  renderGridArea(areaName) {
+    let { devicePixelRatio } = this.win;
+    let displayPixelRatio = getDisplayPixelRatio(this.win);
+    let paths = [];
+
+    for (let i = 0; i < this.gridData.length; i++) {
+      let fragment = this.gridData[i];
+
+      for (let area of fragment.areas) {
+        if (areaName && areaName != area.name) {
+          continue;
+        }
+
+        let rowStart = fragment.rows.lines[area.rowStart - 1];
+        let rowEnd = fragment.rows.lines[area.rowEnd - 1];
+        let columnStart = fragment.cols.lines[area.columnStart - 1];
+        let columnEnd = fragment.cols.lines[area.columnEnd - 1];
+
+        let x1 = columnStart.start + columnStart.breadth;
+        let y1 = rowStart.start + rowStart.breadth;
+        let x2 = columnEnd.start;
+        let y2 = rowEnd.start;
+
+        let points = getPointsFromDiagonal(x1, y1, x2, y2, this.currentMatrix);
+
+        // Scale down by `devicePixelRatio` since SVG element already take them into
+        // account.
+        let svgPoints = points.map(point => ({
+          x: Math.round(point.x / devicePixelRatio),
+          y: Math.round(point.y / devicePixelRatio)
+        }));
+
+        // Scale down by `displayPixelRatio` since infobar's HTML elements already take it
+        // into account; and the zoom scaling is handled by `moveInfobar`.
+        let bounds = getBoundsFromPoints(points.map(point => ({
+          x: Math.round(point.x / displayPixelRatio),
+          y: Math.round(point.y / displayPixelRatio)
+        })));
+
+        paths.push(getPathDescriptionFromPoints(svgPoints));
+
+        // Update and show the info bar when only displaying a single grid area.
+        if (areaName) {
+          this._showGridAreaInfoBar();
+          this._updateGridAreaInfobar(area, bounds);
+        }
+      }
+    }
+
+    let areas = this.getElement("areas");
+    areas.setAttribute("d", paths.join(" "));
   }
 
   /**
-   * Checks if the current node has a CSS Grid layout.
+   * Render grid area name on the containing grid area cell.
    *
-   * @return {Boolean} true if the current node has a CSS grid layout, false otherwise.
+   * @param  {Object} fragment
+   *         The grid fragment of the grid container.
+   * @param  {Object} area
+   *         The area overlay to render on the CSS highlighter canvas.
+   */
+  renderGridAreaName(fragment, area) {
+    let { rowStart, rowEnd, columnStart, columnEnd } = area;
+    let { devicePixelRatio } = this.win;
+    let displayPixelRatio = getDisplayPixelRatio(this.win);
+    let offset = (displayPixelRatio / 2) % 1;
+    let fontSize = GRID_AREA_NAME_FONT_SIZE * displayPixelRatio;
+    let canvasX = Math.round(this._canvasPosition.x * devicePixelRatio);
+    let canvasY = Math.round(this._canvasPosition.y * devicePixelRatio);
+
+    this.ctx.save();
+    this.ctx.translate(offset - canvasX, offset - canvasY);
+    this.ctx.font = fontSize + "px " + GRID_FONT_FAMILY;
+    this.ctx.strokeStyle = this.color;
+    this.ctx.textAlign = "center";
+    this.ctx.textBaseline = "middle";
+
+    // Draw the text for the grid area name.
+    for (let rowNumber = rowStart; rowNumber < rowEnd; rowNumber++) {
+      for (let columnNumber = columnStart; columnNumber < columnEnd; columnNumber++) {
+        let row = fragment.rows.tracks[rowNumber - 1];
+        let column = fragment.cols.tracks[columnNumber - 1];
+
+        // Check if the font size is exceeds the bounds of the containing grid cell.
+        if (fontSize > (column.breadth * displayPixelRatio) ||
+            fontSize > (row.breadth * displayPixelRatio)) {
+          fontSize = (column.breadth + row.breadth) / 2;
+          this.ctx.font = fontSize + "px " + GRID_FONT_FAMILY;
+        }
+
+        let textWidth = this.ctx.measureText(area.name).width;
+        // The width of the character 'm' approximates the height of the text.
+        let textHeight = this.ctx.measureText("m").width;
+        // Padding in pixels for the line number text inside of the line number container.
+        let padding = 3 * displayPixelRatio;
+
+        let boxWidth = textWidth + 2 * padding;
+        let boxHeight = textHeight + 2 * padding;
+
+        let x = column.start + column.breadth / 2;
+        let y = row.start + row.breadth / 2;
+
+        [x, y] = apply(this.currentMatrix, [x, y]);
+
+        let rectXPos = x - boxWidth / 2;
+        let rectYPos = y - boxHeight / 2;
+
+        // Draw a rounded rectangle with a border width of 1 pixel,
+        // a border color matching the grid color, and a white background.
+        this.ctx.lineWidth = 1 * displayPixelRatio;
+        this.ctx.strokeStyle = this.color;
+        this.ctx.fillStyle = "white";
+        let radius = 2 * displayPixelRatio;
+        drawRoundedRect(this.ctx, rectXPos, rectYPos, boxWidth, boxHeight, radius);
+
+        this.ctx.fillStyle = this.color;
+        this.ctx.fillText(area.name, x, y + padding);
+      }
+    }
+
+    this.ctx.restore();
+  }
+
+  /**
+   * Renders the grid area overlay on the css grid highlighter canvas.
    */
-  isGrid() {
-    return this.currentNode.getGridFragments().length > 0;
+  renderGridAreaOverlay() {
+    let padding = 1;
+
+    for (let i = 0; i < this.gridData.length; i++) {
+      let fragment = this.gridData[i];
+
+      for (let area of fragment.areas) {
+        let { rowStart, rowEnd, columnStart, columnEnd, type } = area;
+
+        if (type === "implicit") {
+          continue;
+        }
+
+        // Draw the line edges for the grid area.
+        const areaColStart = fragment.cols.lines[columnStart - 1];
+        const areaColEnd = fragment.cols.lines[columnEnd - 1];
+
+        const areaRowStart = fragment.rows.lines[rowStart - 1];
+        const areaRowEnd = fragment.rows.lines[rowEnd - 1];
+
+        const areaColStartLinePos = areaColStart.start + areaColStart.breadth;
+        const areaRowStartLinePos = areaRowStart.start + areaRowStart.breadth;
+
+        this.renderLine(areaColStartLinePos + padding, areaRowStartLinePos,
+          areaRowEnd.start, COLUMNS, "areaEdge");
+        this.renderLine(areaColEnd.start - padding, areaRowStartLinePos,
+          areaRowEnd.start, COLUMNS, "areaEdge");
+
+        this.renderLine(areaRowStartLinePos + padding, areaColStartLinePos,
+          areaColEnd.start, ROWS, "areaEdge");
+        this.renderLine(areaRowEnd.start - padding, areaColStartLinePos, areaColEnd.start,
+          ROWS, "areaEdge");
+
+        this.renderGridAreaName(fragment, area);
+      }
+    }
+  }
+
+  /**
+   * Render the grid cell highlight for the given grid fragment index, row and column
+   * number.
+   *
+   * @param  {Number} gridFragmentIndex
+   *         Index of the grid fragment to render the grid cell highlight.
+   * @param  {Number} rowNumber
+   *         Row number of the grid cell to highlight.
+   * @param  {Number} columnNumber
+   *         Column number of the grid cell to highlight.
+   */
+  renderGridCell(gridFragmentIndex, rowNumber, columnNumber) {
+    let fragment = this.gridData[gridFragmentIndex];
+
+    if (!fragment) {
+      return;
+    }
+
+    let row = fragment.rows.tracks[rowNumber - 1];
+    let column = fragment.cols.tracks[columnNumber - 1];
+
+    if (!row || !column) {
+      return;
+    }
+
+    let x1 = column.start;
+    let y1 = row.start;
+    let x2 = column.start + column.breadth;
+    let y2 = row.start + row.breadth;
+
+    let { devicePixelRatio } = this.win;
+    let displayPixelRatio = getDisplayPixelRatio(this.win);
+    let points = getPointsFromDiagonal(x1, y1, x2, y2, this.currentMatrix);
+
+    // Scale down by `devicePixelRatio` since SVG element already take them into account.
+    let svgPoints = points.map(point => ({
+      x: Math.round(point.x / devicePixelRatio),
+      y: Math.round(point.y / devicePixelRatio)
+    }));
+
+    // Scale down by `displayPixelRatio` since infobar's HTML elements already take it
+    // into account, and the zoom scaling is handled by `moveInfobar`.
+    let bounds = getBoundsFromPoints(points.map(point => ({
+      x: Math.round(point.x / displayPixelRatio),
+      y: Math.round(point.y / displayPixelRatio)
+    })));
+
+    let cells = this.getElement("cells");
+    cells.setAttribute("d", getPathDescriptionFromPoints(svgPoints));
+
+    this._showGridCellInfoBar();
+    this._updateGridCellInfobar(rowNumber, columnNumber, bounds);
+  }
+
+  /**
+   * Render the grid gap area on the css grid highlighter canvas.
+   *
+   * @param  {Number} linePos
+   *         The line position along the x-axis for a column grid line and
+   *         y-axis for a row grid line.
+   * @param  {Number} startPos
+   *         The start position of the cross side of the grid line.
+   * @param  {Number} endPos
+   *         The end position of the cross side of the grid line.
+   * @param  {Number} breadth
+   *         The grid line breadth value.
+   * @param  {String} dimensionType
+   *         The grid dimension type which is either the constant COLUMNS or ROWS.
+   */
+  renderGridGap(linePos, startPos, endPos, breadth, dimensionType) {
+    let { devicePixelRatio } = this.win;
+    let displayPixelRatio = getDisplayPixelRatio(this.win);
+    let offset = (displayPixelRatio / 2) % 1;
+    let canvasX = Math.round(this._canvasPosition.x * devicePixelRatio);
+    let canvasY = Math.round(this._canvasPosition.y * devicePixelRatio);
+
+    linePos = Math.round(linePos);
+    startPos = Math.round(startPos);
+    breadth = Math.round(breadth);
+
+    this.ctx.save();
+    this.ctx.fillStyle = this.getGridGapPattern(devicePixelRatio, dimensionType);
+    this.ctx.translate(offset - canvasX, offset - canvasY);
+
+    if (dimensionType === COLUMNS) {
+      if (isFinite(endPos)) {
+        endPos = Math.round(endPos);
+      } else {
+        endPos = this._winDimensions.height;
+        startPos = -endPos;
+      }
+      drawRect(this.ctx, linePos, startPos, linePos + breadth, endPos,
+        this.currentMatrix);
+    } else {
+      if (isFinite(endPos)) {
+        endPos = Math.round(endPos);
+      } else {
+        endPos = this._winDimensions.width;
+        startPos = -endPos;
+      }
+      drawRect(this.ctx, startPos, linePos, endPos, linePos + breadth,
+        this.currentMatrix);
+    }
+
+    this.ctx.fill();
+    this.ctx.restore();
   }
 
   /**
-   * Is a given grid fragment valid? i.e. does it actually have tracks? In some cases, we
-   * may have a fragment that defines column tracks but doesn't have any rows (or vice
-   * versa). In which case we do not want to draw anything for that fragment.
+   * Render the grid line name highlight for the given grid fragment index, lineNumber,
+   * and dimensionType.
+   *
+   * @param  {Number} gridFragmentIndex
+   *         Index of the grid fragment to render the grid line highlight.
+   * @param  {Number} lineNumber
+   *         Line number of the grid line to highlight.
+   * @param  {String} dimensionType
+   *         The dimension type of the grid line.
+   */
+  renderGridLineNames(gridFragmentIndex, lineNumber, dimensionType) {
+    let fragment = this.gridData[gridFragmentIndex];
+
+    if (!fragment || !lineNumber || !dimensionType) {
+      return;
+    }
+
+    const { names } = fragment[dimensionType].lines[lineNumber - 1];
+    let linePos;
+
+    if (dimensionType === ROWS) {
+      linePos = fragment.rows.lines[lineNumber - 1];
+    } else if (dimensionType === COLUMNS) {
+      linePos = fragment.cols.lines[lineNumber - 1];
+    }
+
+    if (!linePos) {
+      return;
+    }
+
+    let currentZoom = getCurrentZoom(this.win);
+    let { bounds } = this.currentQuads.content[gridFragmentIndex];
+
+    const rowYPosition = fragment.rows.lines[0];
+    const colXPosition = fragment.rows.lines[0];
+
+    let x = dimensionType === COLUMNS
+      ? linePos.start + (bounds.left / currentZoom)
+      : colXPosition.start + (bounds.left / currentZoom);
+
+    let y = dimensionType === ROWS
+      ? linePos.start + (bounds.top / currentZoom)
+      : rowYPosition.start + (bounds.top / currentZoom);
+
+    this._showGridLineInfoBar();
+    this._updateGridLineInfobar(names.join(", "), lineNumber, x, y);
+  }
+
+  /**
+   * Render the grid line number on the css grid highlighter canvas.
    *
-   * @param {Object} fragment
-   * @return {Boolean}
+   * @param  {Number} lineNumber
+   *         The grid line number.
+   * @param  {Number} linePos
+   *         The line position along the x-axis for a column grid line and
+   *         y-axis for a row grid line.
+   * @param  {Number} startPos
+   *         The start position of the cross side of the grid line.
+   * @param  {Number} breadth
+   *         The grid line breadth value.
+   * @param  {String} dimensionType
+   *         The grid dimension type which is either the constant COLUMNS or ROWS.
+   * @param  {Number||undefined} stackedLineIndex
+   *         The line index position of the stacked line.
    */
-  isValidFragment(fragment) {
-    return fragment.cols.tracks.length && fragment.rows.tracks.length;
+  renderGridLineNumber(lineNumber, linePos, startPos, breadth, dimensionType,
+    stackedLineIndex) {
+    let displayPixelRatio = getDisplayPixelRatio(this.win);
+    let { devicePixelRatio } = this.win;
+    let offset = (displayPixelRatio / 2) % 1;
+    let fontSize = GRID_FONT_SIZE * displayPixelRatio;
+    let canvasX = Math.round(this._canvasPosition.x * devicePixelRatio);
+    let canvasY = Math.round(this._canvasPosition.y * devicePixelRatio);
+
+    linePos = Math.round(linePos);
+    startPos = Math.round(startPos);
+    breadth = Math.round(breadth);
+
+    if (linePos + breadth < 0) {
+      // Don't render the line number since the line is not visible on screen.
+      return;
+    }
+
+    this.ctx.save();
+    this.ctx.translate(offset - canvasX, offset - canvasY);
+    this.ctx.font = fontSize + "px " + GRID_FONT_FAMILY;
+
+    // For a general grid box, the height of the character "m" will be its minimum width
+    // and height. If line number's text width is greater, then use the grid box's text
+    // width instead.
+    let textHeight = this.ctx.measureText("m").width;
+    let textWidth = Math.max(textHeight, this.ctx.measureText(lineNumber).width);
+
+    // Padding in pixels for the line number text inside of the line number container.
+    let padding = 3 * displayPixelRatio;
+    let offsetFromEdge = 2 * displayPixelRatio;
+
+    let boxWidth = textWidth + 2 * padding;
+    let boxHeight = textHeight + 2 * padding;
+
+    // Calculate the x & y coordinates for the line number container, so that its arrow
+    // tip is centered on the line (or the gap if there is one), and is offset by the
+    // calculated padding value from the grid container edge.
+    let x, y;
+
+    if (dimensionType === COLUMNS) {
+      x = linePos + breadth / 2;
+      y = startPos;
+
+      if (lineNumber > 0) {
+        y -= offsetFromEdge;
+      } else {
+        y += offsetFromEdge;
+      }
+    } else if (dimensionType === ROWS) {
+      x = startPos;
+      y = linePos + breadth / 2;
+
+      if (lineNumber > 0) {
+        x -= offsetFromEdge;
+      } else {
+        x += offsetFromEdge;
+      }
+    }
+
+    [x, y] = apply(this.currentMatrix, [x, y]);
+
+    if (stackedLineIndex) {
+      // Offset the stacked line number by half of the box's width/height.
+      const xOffset = boxWidth / 4;
+      const yOffset = boxHeight / 4;
+
+      if (lineNumber > 0) {
+        x -= xOffset;
+        y -= yOffset;
+      } else {
+        x += xOffset;
+        y += yOffset;
+      }
+    }
+
+    if (!this.hasNodeTransformations) {
+      x = Math.max(x, padding);
+      y = Math.max(y, padding);
+    }
+
+    // Draw a bubble rectanglular arrow with a border width of 2 pixels, a border color
+    // matching the grid color and a white background (the line number will be written in
+    // black).
+    this.ctx.lineWidth = 2 * displayPixelRatio;
+    this.ctx.strokeStyle = this.color;
+    this.ctx.fillStyle = "white";
+
+    // See param definitions of drawBubbleRect.
+    let radius = 2 * displayPixelRatio;
+    let margin = 2 * displayPixelRatio;
+    let arrowSize = 8 * displayPixelRatio;
+
+    let minBoxSize = arrowSize * 2 + padding;
+    boxWidth = Math.max(boxWidth, minBoxSize);
+    boxHeight = Math.max(boxHeight, minBoxSize);
+
+    if (dimensionType === COLUMNS) {
+      if (lineNumber > 0) {
+        drawBubbleRect(this.ctx, x, y, boxWidth, boxHeight, radius, margin, arrowSize,
+          "top");
+        // After drawing the number box, we need to center the x/y coordinates of the
+        // number text written it.
+        y -= (boxHeight + arrowSize + radius) - boxHeight / 2;
+      } else {
+        drawBubbleRect(this.ctx, x, y, boxWidth, boxHeight, radius, margin, arrowSize,
+          "bottom");
+        y += (boxHeight + arrowSize + radius) - boxHeight / 2;
+      }
+    } else if (dimensionType === ROWS) {
+      if (lineNumber > 0) {
+        drawBubbleRect(this.ctx, x, y, boxWidth, boxHeight, radius, margin, arrowSize,
+          "left");
+        x -= (boxWidth + arrowSize + radius) - boxWidth / 2;
+      } else {
+        drawBubbleRect(this.ctx, x, y, boxWidth, boxHeight, radius, margin, arrowSize,
+          "right");
+        x += (boxWidth + arrowSize + radius) - boxWidth / 2;
+      }
+    }
+
+    // Write the line number inside of the rectangle.
+    this.ctx.textAlign = "center";
+    this.ctx.textBaseline = "middle";
+    this.ctx.fillStyle = "black";
+    const numberText = stackedLineIndex ? "" : lineNumber;
+    this.ctx.fillText(numberText, x, y);
+    this.ctx.restore();
   }
 
   /**
-   * The AutoRefreshHighlighter's _hasMoved method returns true only if the
-   * element's quads have changed. Override it so it also returns true if the
-   * element's grid has changed (which can happen when you change the
-   * grid-template-* CSS properties with the highlighter displayed).
+   * Render the grid line on the css grid highlighter canvas.
+   *
+   * @param  {Number} linePos
+   *         The line position along the x-axis for a column grid line and
+   *         y-axis for a row grid line.
+   * @param  {Number} startPos
+   *         The start position of the cross side of the grid line.
+   * @param  {Number} endPos
+   *         The end position of the cross side of the grid line.
+   * @param  {String} dimensionType
+   *         The grid dimension type which is either the constant COLUMNS or ROWS.
+   * @param  {String} lineType
+   *         The grid line type - "edge", "explicit", or "implicit".
+   */
+  renderLine(linePos, startPos, endPos, dimensionType, lineType) {
+    let { devicePixelRatio } = this.win;
+    let lineWidth = getDisplayPixelRatio(this.win);
+    let offset = (lineWidth / 2) % 1;
+    let canvasX = Math.round(this._canvasPosition.x * devicePixelRatio);
+    let canvasY = Math.round(this._canvasPosition.y * devicePixelRatio);
+
+    linePos = Math.round(linePos);
+    startPos = Math.round(startPos);
+    endPos = Math.round(endPos);
+
+    this.ctx.save();
+    this.ctx.setLineDash(GRID_LINES_PROPERTIES[lineType].lineDash);
+    this.ctx.beginPath();
+    this.ctx.translate(offset - canvasX, offset - canvasY);
+
+    let lineOptions = {
+      matrix: this.currentMatrix
+    };
+
+    if (this.options.showInfiniteLines) {
+      lineOptions.extendToBoundaries = [canvasX, canvasY, canvasX + CANVAS_SIZE,
+                                        canvasY + CANVAS_SIZE];
+    }
+
+    if (dimensionType === COLUMNS) {
+      drawLine(this.ctx, linePos, startPos, linePos, endPos, lineOptions);
+    } else {
+      drawLine(this.ctx, startPos, linePos, endPos, linePos, lineOptions);
+    }
+
+    this.ctx.strokeStyle = this.color;
+    this.ctx.globalAlpha = GRID_LINES_PROPERTIES[lineType].alpha;
+
+    if (GRID_LINES_PROPERTIES[lineType].lineWidth) {
+      this.ctx.lineWidth = GRID_LINES_PROPERTIES[lineType].lineWidth * devicePixelRatio;
+    } else {
+      this.ctx.lineWidth = lineWidth;
+    }
+
+    this.ctx.stroke();
+    this.ctx.restore();
+  }
+
+  /**
+   * Render the grid lines given the grid dimension information of the
+   * column or row lines.
+   *
+   * @param  {GridDimension} gridDimension
+   *         Column or row grid dimension object.
+   * @param  {Object} quad.bounds
+   *         The content bounds of the box model region quads.
+   * @param  {String} dimensionType
+   *         The grid dimension type which is either the constant COLUMNS or ROWS.
+   * @param  {Number} startPos
+   *         The start position of the cross side ("left" for ROWS and "top" for COLUMNS)
+   *         of the grid dimension.
+   * @param  {Number} endPos
+   *         The end position of the cross side ("left" for ROWS and "top" for COLUMNS)
+   *         of the grid dimension.
    */
-  _hasMoved() {
-    let hasMoved = AutoRefreshHighlighter.prototype._hasMoved.call(this);
+  renderLines(gridDimension, dimensionType, startPos, endPos) {
+    const { lines, tracks } = gridDimension;
+    const lastEdgeLineIndex = this.getLastEdgeLineIndex(tracks);
+
+    for (let i = 0; i < lines.length; i++) {
+      let line = lines[i];
+      let linePos = line.start;
+
+      if (i == 0 || i == lastEdgeLineIndex) {
+        this.renderLine(linePos, startPos, endPos, dimensionType, "edge");
+      } else {
+        this.renderLine(linePos, startPos, endPos, dimensionType, tracks[i - 1].type);
+      }
+
+      // Render a second line to illustrate the gutter for non-zero breadth.
+      if (line.breadth > 0) {
+        this.renderGridGap(linePos, startPos, endPos, line.breadth, dimensionType);
+        this.renderLine(linePos + line.breadth, startPos, endPos, dimensionType,
+          tracks[i].type);
+      }
+    }
+  }
+
+  /**
+   * Render the grid lines given the grid dimension information of the
+   * column or row lines.
+   *
+   * @param  {GridDimension} gridDimension
+   *         Column or row grid dimension object.
+   * @param  {String} dimensionType
+   *         The grid dimension type which is either the constant COLUMNS or ROWS.
+   * @param  {Number} startPos
+   *         The start position of the cross side ("left" for ROWS and "top" for COLUMNS)
+   *         of the grid dimension.
+   */
+  renderLineNumbers(gridDimension, dimensionType, startPos) {
+    const { lines, tracks } = gridDimension;
+    // Keep track of the number of collapsed lines per line position.
+    let stackedLines = [];
+
+    for (let i = 0, line; (line = lines[i++]);) {
+      // If you place something using negative numbers, you can trigger some implicit
+      // grid creation above and to the left of the explicit grid (assuming a
+      // horizontal-tb writing mode).
+      //
+      // The first explicit grid line gets the number of 1, and any implicit grid lines
+      // before 1 get negative numbers. Since here we're rendering only the positive line
+      // numbers, we have to skip any implicit grid lines before the first one that is
+      // explicit. The API returns a 0 as the line's number for these implicit lines that
+      // occurs before the first explicit line.
+      if (line.number === 0) {
+        continue;
+      }
+
+      // Check for overlapping lines. We render a second box beneath the last overlapping
+      // line number to indicate there are lines beneath it.
+      const gridLine = tracks[line.number - 1];
+
+      if (gridLine) {
+        const { breadth }  = gridLine;
+
+        if (breadth === 0) {
+          stackedLines.push(lines[i].number);
 
-    let oldGridData = stringifyGridFragments(this.gridData);
-    this.gridData = this.currentNode.getGridFragments();
-    let newGridData = stringifyGridFragments(this.gridData);
+          if (stackedLines.length > 0) {
+            this.renderGridLineNumber(line.number, line.start, startPos, line.breadth,
+              dimensionType, 1);
+          }
+
+          continue;
+        }
+      }
+
+      this.renderGridLineNumber(line.number, line.start, startPos, line.breadth,
+        dimensionType);
+    }
+  }
+
+  /**
+   * Render the negative grid lines given the grid dimension information of the
+   * column or row lines.
+   *
+   * @param  {GridDimension} gridDimension
+   *         Column or row grid dimension object.
+   * @param  {String} dimensionType
+   *         The grid dimension type which is either the constant COLUMNS or ROWS.
+   * @param  {Number} startPos
+   *         The start position of the cross side ("left" for ROWS and "top" for COLUMNS)
+   *         of the grid dimension.
+   */
+  renderNegativeLineNumbers(gridDimension, dimensionType, startPos) {
+    const { lines, tracks } = gridDimension;
+    // Keep track of the number of collapsed lines per line position.
+    let stackedLines = [];
+
+    for (let i = 0, line; (line = lines[i++]);) {
+      let linePos = line.start;
+      let negativeLineNumber = line.negativeNumber;
 
-    return hasMoved || oldGridData !== newGridData;
+      // Don't render any negative line number greater than -1.
+      if (negativeLineNumber == 0) {
+        break;
+      }
+
+      // Check for overlapping lines. We render a second box beneath the last overlapping
+      // line number to indicate there are lines beneath it.
+      const gridLine = tracks[line.number - 1];
+
+      if (gridLine) {
+        const { breadth }  = gridLine;
+
+        if (breadth === 0) {
+          stackedLines.push(negativeLineNumber);
+
+          if (stackedLines.length > 0) {
+            this.renderGridLineNumber(negativeLineNumber, linePos, startPos,
+              line.breadth, dimensionType, 1);
+          }
+
+          continue;
+        }
+      }
+
+      // For negative line numbers, we want to display the smallest
+      // value at the front of the stack.
+      if (stackedLines.length) {
+        negativeLineNumber = stackedLines[0];
+        stackedLines = [];
+      }
+
+      this.renderGridLineNumber(negativeLineNumber, linePos, startPos, line.breadth,
+        dimensionType);
+    }
   }
 
   /**
    * Update the highlighter on the current highlighted node (the one that was
-   * passed as an argument to show(node)).
-   * Should be called whenever node's geometry or grid changes.
+   * passed as an argument to show(node)). Should be called whenever node's geometry
+   * or grid changes.
    */
   _update() {
     setIgnoreLayoutChanges(true);
 
     let root = this.getElement("root");
     let cells = this.getElement("cells");
     let areas = this.getElement("areas");
 
@@ -648,17 +1465,17 @@ class CssGridHighlighter extends AutoRef
     // Updates the <canvas> element's position and size.
     // It also clear the <canvas>'s drawing context.
     updateCanvasElement(this.canvas, this._canvasPosition, this.win.devicePixelRatio);
 
     // Clear the grid area highlights.
     this.clearGridAreas();
     this.clearGridCell();
 
-    // Update the current matrix used in our canvas' rendering
+    // Update the current matrix used in our canvas' rendering.
     let { currentMatrix, hasNodeTransformations } = getCurrentMatrix(this.currentNode,
       this.win);
     this.currentMatrix = currentMatrix;
     this.hasNodeTransformations = hasNodeTransformations;
 
     // Start drawing the grid fragments.
     for (let i = 0; i < this.gridData.length; i++) {
       this.renderFragment(this.gridData[i]);
@@ -725,18 +1542,18 @@ class CssGridHighlighter extends AutoRef
    * @param  {Object} bounds
    *         A DOMRect-like object represent the grid cell rectangle.
    */
   _updateGridCellInfobar(rowNumber, columnNumber, bounds) {
     let { width, height } = bounds;
     let dim = parseFloat(width.toPrecision(6)) +
               " \u00D7 " +
               parseFloat(height.toPrecision(6));
-    let position = LAYOUT_L10N.getFormatStr("layout.rowColumnPositions",
-                   rowNumber, columnNumber);
+    let position = LAYOUT_L10N.getFormatStr("layout.rowColumnPositions", rowNumber,
+      columnNumber);
 
     this.getElement("cell-infobar-position").setTextContent(position);
     this.getElement("cell-infobar-dimensions").setTextContent(dim);
 
     let container = this.getElement("cell-infobar-container");
     moveInfobar(container, bounds, this.win, {
       position: "top",
       hideIfOffscreen: true
@@ -755,850 +1572,14 @@ class CssGridHighlighter extends AutoRef
    * @param  {Number} y
    *         The y-coordinate of the grid line.
    */
   _updateGridLineInfobar(gridLineNames, gridLineNumber, x, y) {
     this.getElement("line-infobar-number").setTextContent(gridLineNumber);
     this.getElement("line-infobar-names").setTextContent(gridLineNames);
 
     let container = this.getElement("line-infobar-container");
-    moveInfobar(container,
-      getBoundsFromPoints([{x, y}, {x, y}, {x, y}, {x, y}]), this.win);
-  }
-
-  /**
-   * The <canvas>'s position needs to be updated if the page scrolls too much, in order
-   * to give the illusion that it always covers the viewport.
-   */
-  _scrollUpdate() {
-    let hasUpdated = updateCanvasPosition(this._canvasPosition, this._scroll, this.win,
-      this._winDimensions);
-
-    if (hasUpdated) {
-      this._update();
-    }
-  }
-
-  getFirstRowLinePos(fragment) {
-    return fragment.rows.lines[0].start;
-  }
-
-  getLastRowLinePos(fragment) {
-    return fragment.rows.lines[fragment.rows.lines.length - 1].start;
-  }
-
-  getFirstColLinePos(fragment) {
-    return fragment.cols.lines[0].start;
-  }
-
-  getLastColLinePos(fragment) {
-    return fragment.cols.lines[fragment.cols.lines.length - 1].start;
-  }
-
-  /**
-   * Get the GridLine index of the last edge of the explicit grid for a grid dimension.
-   *
-   * @param  {GridTracks} tracks
-   *         The grid track of a given grid dimension.
-   * @return {Number} index of the last edge of the explicit grid for a grid dimension.
-   */
-  getLastEdgeLineIndex(tracks) {
-    let trackIndex = tracks.length - 1;
-
-    // Traverse the grid track backwards until we find an explicit track.
-    while (trackIndex >= 0 && tracks[trackIndex].type != "explicit") {
-      trackIndex--;
-    }
-
-    // The grid line index is the grid track index + 1.
-    return trackIndex + 1;
-  }
-
-  renderFragment(fragment) {
-    if (!this.isValidFragment(fragment)) {
-      return;
-    }
-
-    this.renderLines(fragment.cols, COLUMNS, "left", "top", "height",
-                     this.getFirstRowLinePos(fragment),
-                     this.getLastRowLinePos(fragment));
-    this.renderLines(fragment.rows, ROWS, "top", "left", "width",
-                     this.getFirstColLinePos(fragment),
-                     this.getLastColLinePos(fragment));
-
-    if (this.options.showGridAreasOverlay) {
-      this.renderGridAreaOverlay();
-    }
-
-    // Line numbers are rendered in a 2nd step to avoid overlapping with existing lines.
-    if (this.options.showGridLineNumbers) {
-      this.renderLineNumbers(fragment.cols, COLUMNS, "left", "top",
-                       this.getFirstRowLinePos(fragment));
-      this.renderLineNumbers(fragment.rows, ROWS, "top", "left",
-                       this.getFirstColLinePos(fragment));
-
-      if (Services.prefs.getBoolPref(NEGATIVE_LINE_NUMBERS_PREF)) {
-        this.renderNegativeLineNumbers(fragment.cols, COLUMNS, "left", "top",
-                          this.getLastRowLinePos(fragment));
-        this.renderNegativeLineNumbers(fragment.rows, ROWS, "top", "left",
-                          this.getLastColLinePos(fragment));
-      }
-    }
-  }
-
-  /**
-   * Render the negative grid lines given the grid dimension information of the
-   * column or row lines.
-   *
-   * See @param for renderLines.
-   */
-  renderNegativeLineNumbers(gridDimension, dimensionType, mainSide, crossSide,
-            startPos) {
-    let lineStartPos = startPos;
-
-    // Keep track of the number of collapsed lines per line position
-    let stackedLines = [];
-
-    const { lines } = gridDimension;
-
-    for (let i = 0, line; (line = lines[i++]);) {
-      let linePos = line.start;
-      let negativeLineNumber = i - lines.length - 1;
-
-      // Check for overlapping lines. We render a second box beneath the last overlapping
-      // line number to indicate there are lines beneath it.
-      const gridLine = gridDimension.tracks[line.number - 1];
-
-      if (gridLine) {
-        const { breadth }  = gridLine;
-
-        if (breadth === 0) {
-          stackedLines.push(negativeLineNumber);
-
-          if (stackedLines.length > 0) {
-            this.renderGridLineNumber(negativeLineNumber, linePos, lineStartPos,
-              line.breadth, dimensionType, 1);
-          }
-
-          continue;
-        }
-      }
-
-      // For negative line numbers, we want to display the smallest
-      // value at the front of the stack.
-      if (stackedLines.length) {
-        negativeLineNumber = stackedLines[0];
-        stackedLines = [];
-      }
-
-      this.renderGridLineNumber(negativeLineNumber, linePos, lineStartPos, line.breadth,
-        dimensionType);
-    }
-  }
-
-  /**
-   * Renders the grid area overlay on the css grid highlighter canvas.
-   */
-  renderGridAreaOverlay() {
-    let padding = 1;
-
-    for (let i = 0; i < this.gridData.length; i++) {
-      let fragment = this.gridData[i];
-
-      for (let area of fragment.areas) {
-        let { rowStart, rowEnd, columnStart, columnEnd, type } = area;
-
-        if (type === "implicit") {
-          continue;
-        }
-
-        // Draw the line edges for the grid area
-        const areaColStart = fragment.cols.lines[columnStart - 1];
-        const areaColEnd = fragment.cols.lines[columnEnd - 1];
-
-        const areaRowStart = fragment.rows.lines[rowStart - 1];
-        const areaRowEnd = fragment.rows.lines[rowEnd - 1];
-
-        const areaColStartLinePos = areaColStart.start + areaColStart.breadth;
-        const areaRowStartLinePos = areaRowStart.start + areaRowStart.breadth;
-
-        this.renderLine(areaColStartLinePos + padding,
-                        areaRowStartLinePos, areaRowEnd.start,
-                        COLUMNS, "areaEdge");
-        this.renderLine(areaColEnd.start - padding,
-                        areaRowStartLinePos, areaRowEnd.start,
-                        COLUMNS, "areaEdge");
-
-        this.renderLine(areaRowStartLinePos + padding,
-                        areaColStartLinePos, areaColEnd.start,
-                        ROWS, "areaEdge");
-        this.renderLine(areaRowEnd.start - padding,
-                        areaColStartLinePos, areaColEnd.start,
-                        ROWS, "areaEdge");
-
-        this.renderGridAreaName(fragment, area);
-      }
-    }
-
-    this.ctx.restore();
-  }
-
-  /**
-   * Render grid area name on the containing grid area cell.
-   *
-   * @param  {Object} fragment
-   *         The grid fragment of the grid container.
-   * @param  {Object} area
-   *         The area overlay to render on the CSS highlighter canvas.
-   */
-  renderGridAreaName(fragment, area) {
-    let { rowStart, rowEnd, columnStart, columnEnd } = area;
-    let { devicePixelRatio } = this.win;
-    let displayPixelRatio = getDisplayPixelRatio(this.win);
-    let offset = (displayPixelRatio / 2) % 1;
-    let fontSize = (GRID_AREA_NAME_FONT_SIZE * displayPixelRatio);
-
-    this.ctx.save();
-
-    let canvasX = Math.round(this._canvasPosition.x * devicePixelRatio);
-    let canvasY = Math.round(this._canvasPosition.y * devicePixelRatio);
-    this.ctx.translate(offset - canvasX, offset - canvasY);
-
-    this.ctx.font = fontSize + "px " + GRID_FONT_FAMILY;
-    this.ctx.strokeStyle = this.color;
-    this.ctx.textAlign = "center";
-    this.ctx.textBaseline = "middle";
-
-    // Draw the text for the grid area name.
-    for (let rowNumber = rowStart; rowNumber < rowEnd; rowNumber++) {
-      for (let columnNumber = columnStart; columnNumber < columnEnd; columnNumber++) {
-        let row = fragment.rows.tracks[rowNumber - 1];
-        let column = fragment.cols.tracks[columnNumber - 1];
-
-        // Check if the font size is exceeds the bounds of the containing grid cell.
-        if (fontSize > (column.breadth * displayPixelRatio) ||
-            fontSize > (row.breadth * displayPixelRatio)) {
-          fontSize = (column.breadth + row.breadth) / 2;
-          this.ctx.font = fontSize + "px " + GRID_FONT_FAMILY;
-        }
-
-        let textWidth = this.ctx.measureText(area.name).width;
-
-        // The width of the character 'm' approximates the height of the text.
-        let textHeight = this.ctx.measureText("m").width;
-
-        // Padding in pixels for the line number text inside of the line number container.
-        let padding = 3 * displayPixelRatio;
-
-        let boxWidth = textWidth + 2 * padding;
-        let boxHeight = textHeight + 2 * padding;
-
-        let x = column.start + column.breadth / 2;
-        let y = row.start + row.breadth / 2;
-
-        [x, y] = apply(this.currentMatrix, [x, y]);
-
-        let rectXPos = x - boxWidth / 2;
-        let rectYPos = y - boxHeight / 2;
-
-        // Draw a rounded rectangle with a border width of 1 pixel,
-        // a border color matching the grid color, and a white background
-        this.ctx.lineWidth = 1 * displayPixelRatio;
-        this.ctx.strokeStyle = this.color;
-        this.ctx.fillStyle = "white";
-        let radius = 2 * displayPixelRatio;
-        drawRoundedRect(this.ctx, rectXPos, rectYPos, boxWidth, boxHeight, radius);
-
-        this.ctx.fillStyle = this.color;
-        this.ctx.fillText(area.name, x, y + padding);
-      }
-    }
-
-    this.ctx.restore();
-  }
-
-  /**
-   * Render the grid lines given the grid dimension information of the
-   * column or row lines.
-   *
-   * @param  {GridDimension} gridDimension
-   *         Column or row grid dimension object.
-   * @param  {Object} quad.bounds
-   *         The content bounds of the box model region quads.
-   * @param  {String} dimensionType
-   *         The grid dimension type which is either the constant COLUMNS or ROWS.
-   * @param  {String} mainSide
-   *         The main side of the given grid dimension - "top" for rows and
-   *         "left" for columns.
-   * @param  {String} crossSide
-   *         The cross side of the given grid dimension - "left" for rows and
-   *         "top" for columns.
-   * @param  {String} mainSize
-   *         The main size of the given grid dimension - "width" for rows and
-   *         "height" for columns.
-   * @param  {Number} startPos
-   *         The start position of the cross side of the grid dimension.
-   * @param  {Number} endPos
-   *         The end position of the cross side of the grid dimension.
-   */
-  renderLines(gridDimension, dimensionType, mainSide, crossSide,
-              mainSize, startPos, endPos) {
-    let lineStartPos = startPos;
-    let lineEndPos = endPos;
-
-    let lastEdgeLineIndex = this.getLastEdgeLineIndex(gridDimension.tracks);
-
-    for (let i = 0; i < gridDimension.lines.length; i++) {
-      let line = gridDimension.lines[i];
-      let linePos = line.start;
-
-      if (i == 0 || i == lastEdgeLineIndex) {
-        this.renderLine(linePos, lineStartPos, lineEndPos, dimensionType, "edge");
-      } else {
-        this.renderLine(linePos, lineStartPos, lineEndPos, dimensionType,
-                        gridDimension.tracks[i - 1].type);
-      }
-
-      // Render a second line to illustrate the gutter for non-zero breadth.
-      if (line.breadth > 0) {
-        this.renderGridGap(linePos, lineStartPos, lineEndPos, line.breadth,
-                           dimensionType);
-        this.renderLine(linePos + line.breadth, lineStartPos, lineEndPos, dimensionType,
-                        gridDimension.tracks[i].type);
-      }
-    }
-  }
-
-  /**
-   * Render the grid lines given the grid dimension information of the
-   * column or row lines.
-   *
-   * see @param for renderLines.
-   */
-  renderLineNumbers(gridDimension, dimensionType, mainSide, crossSide,
-              startPos) {
-    let lineStartPos = startPos;
-
-    // Keep track of the number of collapsed lines per line position
-    let stackedLines = [];
-
-    const { lines } = gridDimension;
-
-    for (let i = 0, line; (line = lines[i++]);) {
-      let linePos = line.start;
-
-      // If you place something using negative numbers, you can trigger some implicit grid
-      // creation above and to the left of the explicit grid (assuming a horizontal-tb
-      // writing mode).
-      // The first explicit grid line gets the number of 1; any implicit grid lines
-      // before 1 get negative numbers, but do not get any positivity numbers.
-      // Since here we're rendering only the positive line numbers, we have to skip any
-      // implicit grid lines before the first tha is explicit.
-      // For such lines the API returns always 0 as line's number.
-      if (line.number === 0) {
-        continue;
-      }
-
-      // Check for overlapping lines. We render a second box beneath the last overlapping
-      // line number to indicate there are lines beneath it.
-      const gridLine = gridDimension.tracks[line.number - 1];
-
-      if (gridLine) {
-        const { breadth }  = gridLine;
-
-        if (breadth === 0) {
-          stackedLines.push(gridDimension.lines[i].number);
-
-          if (stackedLines.length > 0) {
-            this.renderGridLineNumber(line.number, linePos, lineStartPos, line.breadth,
-              dimensionType, 1);
-          }
-
-          continue;
-        }
-      }
-
-      this.renderGridLineNumber(line.number, linePos, lineStartPos, line.breadth,
-        dimensionType);
-    }
-  }
-
-  /**
-   * Render the grid line on the css grid highlighter canvas.
-   *
-   * @param  {Number} linePos
-   *         The line position along the x-axis for a column grid line and
-   *         y-axis for a row grid line.
-   * @param  {Number} startPos
-   *         The start position of the cross side of the grid line.
-   * @param  {Number} endPos
-   *         The end position of the cross side of the grid line.
-   * @param  {String} dimensionType
-   *         The grid dimension type which is either the constant COLUMNS or ROWS.
-   * @param  {String} lineType
-   *         The grid line type - "edge", "explicit", or "implicit".
-   */
-  renderLine(linePos, startPos, endPos, dimensionType, lineType) {
-    let { devicePixelRatio } = this.win;
-    let lineWidth = getDisplayPixelRatio(this.win);
-    let offset = (lineWidth / 2) % 1;
-
-    let x = Math.round(this._canvasPosition.x * devicePixelRatio);
-    let y = Math.round(this._canvasPosition.y * devicePixelRatio);
-
-    linePos = Math.round(linePos);
-    startPos = Math.round(startPos);
-    endPos = Math.round(endPos);
-
-    this.ctx.save();
-    this.ctx.setLineDash(GRID_LINES_PROPERTIES[lineType].lineDash);
-    this.ctx.beginPath();
-    this.ctx.translate(offset - x, offset - y);
-
-    let lineOptions = {
-      matrix: this.currentMatrix
-    };
-
-    if (this.options.showInfiniteLines) {
-      lineOptions.extendToBoundaries = [x, y, x + CANVAS_SIZE, y + CANVAS_SIZE];
-    }
-
-    if (dimensionType === COLUMNS) {
-      drawLine(this.ctx, linePos, startPos, linePos, endPos, lineOptions);
-    } else {
-      drawLine(this.ctx, startPos, linePos, endPos, linePos, lineOptions);
-    }
-
-    this.ctx.strokeStyle = this.color;
-    this.ctx.globalAlpha = GRID_LINES_PROPERTIES[lineType].alpha;
-
-    if (GRID_LINES_PROPERTIES[lineType].lineWidth) {
-      this.ctx.lineWidth = GRID_LINES_PROPERTIES[lineType].lineWidth * devicePixelRatio;
-    } else {
-      this.ctx.lineWidth = lineWidth;
-    }
-
-    this.ctx.stroke();
-    this.ctx.restore();
-  }
-
-  /**
-   * Render the grid line number on the css grid highlighter canvas.
-   *
-   * @param  {Number} lineNumber
-   *         The grid line number.
-   * @param  {Number} linePos
-   *         The line position along the x-axis for a column grid line and
-   *         y-axis for a row grid line.
-   * @param  {Number} startPos
-   *         The start position of the cross side of the grid line.
-   * @param  {Number} breadth
-   *         The grid line breadth value.
-   * @param  {String} dimensionType
-   *         The grid dimension type which is either the constant COLUMNS or ROWS.
-   * @param  {Number||undefined} stackedLineIndex
-   *         The line index position of the stacked line.
-   */
-  renderGridLineNumber(lineNumber, linePos, startPos, breadth, dimensionType,
-    stackedLineIndex) {
-    let displayPixelRatio = getDisplayPixelRatio(this.win);
-    let { devicePixelRatio } = this.win;
-    let offset = (displayPixelRatio / 2) % 1;
-
-    linePos = Math.round(linePos);
-    startPos = Math.round(startPos);
-    breadth = Math.round(breadth);
-
-    if (linePos + breadth < 0) {
-      // The line is not visible on screen, don't render the line number
-      return;
-    }
-
-    this.ctx.save();
-    let canvasX = Math.round(this._canvasPosition.x * devicePixelRatio);
-    let canvasY = Math.round(this._canvasPosition.y * devicePixelRatio);
-    this.ctx.translate(offset - canvasX, offset - canvasY);
-
-    let fontSize = (GRID_FONT_SIZE * displayPixelRatio);
-    this.ctx.font = fontSize + "px " + GRID_FONT_FAMILY;
-
-    // For a general grid box, the height of the character "m" will be its minimum width
-    // and height. If line number's text width is greater then grid box's text width
-    // will use that instead.
-    let textHeight = this.ctx.measureText("m").width;
-    let textWidth = Math.max(textHeight, this.ctx.measureText(lineNumber).width);
-
-    // Padding in pixels for the line number text inside of the line number container.
-    let padding = 3 * displayPixelRatio;
-    let offsetFromEdge = 2 * displayPixelRatio;
-
-    let boxWidth = textWidth + 2 * padding;
-    let boxHeight = textHeight + 2 * padding;
-
-     // Calculate the x & y coordinates for the line number container, so that its arrow
-     // tip is centered on the line (or the gap if there is one), and is offset by the
-     // calculated padding value from the grid container edge.
-    let x, y;
-
-    if (dimensionType === COLUMNS) {
-      x = linePos + breadth / 2;
-      y = startPos;
-
-      if (lineNumber > 0) {
-        y -= offsetFromEdge;
-      } else {
-        y += offsetFromEdge;
-      }
-    } else if (dimensionType === ROWS) {
-      x = startPos;
-      y = linePos + breadth / 2;
-
-      if (lineNumber > 0) {
-        x -= offsetFromEdge;
-      } else {
-        x += offsetFromEdge;
-      }
-    }
-
-    [x, y] = apply(this.currentMatrix, [x, y]);
-
-    if (stackedLineIndex) {
-      // Offset the stacked line number by half of the box's width/height
-      const xOffset = boxWidth / 4;
-      const yOffset = boxHeight / 4;
-
-      if (lineNumber > 0) {
-        x -= xOffset;
-        y -= yOffset;
-      } else {
-        x += xOffset;
-        y += yOffset;
-      }
-    }
-
-    if (!this.hasNodeTransformations) {
-      x = Math.max(x, padding);
-      y = Math.max(y, padding);
-    }
-
-    // Draw a bubble rectanglular arrow with a border width of 2 pixels, a border color
-    // matching the grid color and a white background (the line number will be written in
-    // black).
-    this.ctx.lineWidth = 2 * displayPixelRatio;
-    this.ctx.strokeStyle = this.color;
-    this.ctx.fillStyle = "white";
-
-    // See param definitions of drawBubbleRect
-    let radius = 2 * displayPixelRatio;
-    let margin = 2 * displayPixelRatio;
-    let arrowSize = 8 * displayPixelRatio;
-
-    let minBoxSize = arrowSize * 2 + padding;
-    boxWidth = Math.max(boxWidth, minBoxSize);
-    boxHeight = Math.max(boxHeight, minBoxSize);
-
-    if (dimensionType === COLUMNS) {
-      if (lineNumber > 0) {
-        drawBubbleRect(this.ctx, x, y, boxWidth, boxHeight, radius, margin, arrowSize,
-          "top");
-        // After drawing the number box, we need to center the x/y coordinates of the
-        // number text written it.
-        y -= (boxHeight + arrowSize + radius) - boxHeight / 2;
-      } else {
-        drawBubbleRect(this.ctx, x, y, boxWidth, boxHeight, radius, margin, arrowSize,
-          "bottom");
-        y += (boxHeight + arrowSize + radius) - boxHeight / 2;
-      }
-    } else if (dimensionType === ROWS) {
-      if (lineNumber > 0) {
-        drawBubbleRect(this.ctx, x, y, boxWidth, boxHeight, radius, margin, arrowSize,
-          "left");
-        x -= (boxWidth + arrowSize + radius) - boxWidth / 2;
-      } else {
-        drawBubbleRect(this.ctx, x, y, boxWidth, boxHeight, radius, margin, arrowSize,
-          "right");
-        x += (boxWidth + arrowSize + radius) - boxWidth / 2;
-      }
-    }
-
-    // Write the line number inside of the rectangle.
-    this.ctx.textAlign = "center";
-    this.ctx.textBaseline = "middle";
-    this.ctx.fillStyle = "black";
-    const numberText = stackedLineIndex ? "" : lineNumber;
-    this.ctx.fillText(numberText, x, y);
-
-    this.ctx.restore();
-  }
-
-  /**
-   * Render the grid gap area on the css grid highlighter canvas.
-   *
-   * @param  {Number} linePos
-   *         The line position along the x-axis for a column grid line and
-   *         y-axis for a row grid line.
-   * @param  {Number} startPos
-   *         The start position of the cross side of the grid line.
-   * @param  {Number} endPos
-   *         The end position of the cross side of the grid line.
-   * @param  {Number} breadth
-   *         The grid line breadth value.
-   * @param  {String} dimensionType
-   *         The grid dimension type which is either the constant COLUMNS or ROWS.
-   */
-  renderGridGap(linePos, startPos, endPos, breadth, dimensionType) {
-    let { devicePixelRatio } = this.win;
-    let displayPixelRatio = getDisplayPixelRatio(this.win);
-    let offset = (displayPixelRatio / 2) % 1;
-
-    let canvasX = Math.round(this._canvasPosition.x * devicePixelRatio);
-    let canvasY = Math.round(this._canvasPosition.y * devicePixelRatio);
-
-    linePos = Math.round(linePos);
-    startPos = Math.round(startPos);
-    breadth = Math.round(breadth);
-
-    this.ctx.save();
-    this.ctx.fillStyle = this.getGridGapPattern(devicePixelRatio, dimensionType);
-    this.ctx.translate(offset - canvasX, offset - canvasY);
-
-    if (dimensionType === COLUMNS) {
-      if (isFinite(endPos)) {
-        endPos = Math.round(endPos);
-      } else {
-        endPos = this._winDimensions.height;
-        startPos = -endPos;
-      }
-      drawRect(this.ctx, linePos, startPos, linePos + breadth, endPos,
-        this.currentMatrix);
-    } else {
-      if (isFinite(endPos)) {
-        endPos = Math.round(endPos);
-      } else {
-        endPos = this._winDimensions.width;
-        startPos = -endPos;
-      }
-      drawRect(this.ctx, startPos, linePos, endPos, linePos + breadth,
-        this.currentMatrix);
-    }
-    this.ctx.fill();
-    this.ctx.restore();
-  }
-
-  /**
-   * Render the grid area highlight for the given area name or for all the grid areas.
-   *
-   * @param  {String} areaName
-   *         Name of the grid area to be highlighted. If no area name is provided, all
-   *         the grid areas should be highlighted.
-   */
-  renderGridArea(areaName) {
-    let paths = [];
-    let { devicePixelRatio } = this.win;
-    let displayPixelRatio = getDisplayPixelRatio(this.win);
-
-    for (let i = 0; i < this.gridData.length; i++) {
-      let fragment = this.gridData[i];
-
-      for (let area of fragment.areas) {
-        if (areaName && areaName != area.name) {
-          continue;
-        }
-
-        let rowStart = fragment.rows.lines[area.rowStart - 1];
-        let rowEnd = fragment.rows.lines[area.rowEnd - 1];
-        let columnStart = fragment.cols.lines[area.columnStart - 1];
-        let columnEnd = fragment.cols.lines[area.columnEnd - 1];
-
-        let x1 = columnStart.start + columnStart.breadth;
-        let y1 = rowStart.start + rowStart.breadth;
-        let x2 = columnEnd.start;
-        let y2 = rowEnd.start;
-
-        let points = getPointsFromDiagonal(x1, y1, x2, y2, this.currentMatrix);
-
-        // Scale down by `devicePixelRatio` since SVG element already take them into
-        // account.
-        let svgPoints = points.map(point => ({
-          x: Math.round(point.x / devicePixelRatio),
-          y: Math.round(point.y / devicePixelRatio)
-        }));
-
-        // Scale down by `displayPixelRatio` since infobar's HTML elements already take it
-        // into account; and the zoom scaling is handled by `moveInfobar`.
-        let bounds = getBoundsFromPoints(points.map(point => ({
-          x: Math.round(point.x / displayPixelRatio),
-          y: Math.round(point.y / displayPixelRatio)
-        })));
-
-        paths.push(getPathDescriptionFromPoints(svgPoints));
-
-        // Update and show the info bar when only displaying a single grid area.
-        if (areaName) {
-          this._showGridAreaInfoBar();
-          this._updateGridAreaInfobar(area, bounds);
-        }
-      }
-    }
-
-    let areas = this.getElement("areas");
-    areas.setAttribute("d", paths.join(" "));
-  }
-
-  /**
-   * Render the grid cell highlight for the given grid fragment index, row and column
-   * number.
-   *
-   * @param  {Number} gridFragmentIndex
-   *         Index of the grid fragment to render the grid cell highlight.
-   * @param  {Number} rowNumber
-   *         Row number of the grid cell to highlight.
-   * @param  {Number} columnNumber
-   *         Column number of the grid cell to highlight.
-   */
-  renderGridCell(gridFragmentIndex, rowNumber, columnNumber) {
-    let fragment = this.gridData[gridFragmentIndex];
-
-    if (!fragment) {
-      return;
-    }
-
-    let row = fragment.rows.tracks[rowNumber - 1];
-    let column = fragment.cols.tracks[columnNumber - 1];
-
-    if (!row || !column) {
-      return;
-    }
-
-    let x1 = column.start;
-    let y1 = row.start;
-    let x2 = column.start + column.breadth;
-    let y2 = row.start + row.breadth;
-
-    let { devicePixelRatio } = this.win;
-    let displayPixelRatio = getDisplayPixelRatio(this.win);
-
-    let points = getPointsFromDiagonal(x1, y1, x2, y2, this.currentMatrix);
-
-    // Scale down by `devicePixelRatio` since SVG element already take them into account.
-    let svgPoints = points.map(point => ({
-      x: Math.round(point.x / devicePixelRatio),
-      y: Math.round(point.y / devicePixelRatio)
-    }));
-
-    // Scale down by `displayPixelRatio` since infobar's HTML elements already take it
-    // into account, and the zoom scaling is handled by `moveInfobar`.
-    let bounds = getBoundsFromPoints(points.map(point => ({
-      x: Math.round(point.x / displayPixelRatio),
-      y: Math.round(point.y / displayPixelRatio)
-    })));
-
-    let cells = this.getElement("cells");
-    cells.setAttribute("d", getPathDescriptionFromPoints(svgPoints));
-
-    this._showGridCellInfoBar();
-    this._updateGridCellInfobar(rowNumber, columnNumber, bounds);
-  }
-
-  /**
-   * Render the grid line name highlight for the given grid fragment index, lineNumber,
-   * and dimensionType.
-   *
-   * @param  {Number} gridFragmentIndex
-   *         Index of the grid fragment to render the grid line highlight.
-   * @param  {Number} lineNumber
-   *         Line number of the grid line to highlight.
-   * @param  {String} dimensionType
-   *         The dimension type of the grid line.
-   */
-  renderGridLineNames(gridFragmentIndex, lineNumber, dimensionType) {
-    let fragment = this.gridData[gridFragmentIndex];
-
-    if (!fragment || !lineNumber || !dimensionType) {
-      return;
-    }
-
-    const { names } = fragment[dimensionType].lines[lineNumber - 1];
-    let linePos;
-
-    if (dimensionType === ROWS) {
-      linePos = fragment.rows.lines[lineNumber - 1];
-    } else if (dimensionType === COLUMNS) {
-      linePos = fragment.cols.lines[lineNumber - 1];
-    }
-
-    if (!linePos) {
-      return;
-    }
-
-    let currentZoom = getCurrentZoom(this.win);
-    let { bounds } = this.currentQuads.content[gridFragmentIndex];
-
-    const rowYPosition = fragment.rows.lines[0];
-    const colXPosition = fragment.rows.lines[0];
-
-    let x = dimensionType === COLUMNS
-      ? linePos.start + (bounds.left / currentZoom)
-      : colXPosition.start + (bounds.left / currentZoom);
-
-    let y = dimensionType === ROWS
-      ? linePos.start + (bounds.top / currentZoom)
-      : rowYPosition.start + (bounds.top / currentZoom);
-
-    this._showGridLineInfoBar();
-    this._updateGridLineInfobar(names.join(", "), lineNumber, x, y);
-  }
-
-  /**
-   * Hide the highlighter, the canvas and the infobars.
-   */
-  _hide() {
-    setIgnoreLayoutChanges(true);
-    this._hideGrid();
-    this._hideGridElements();
-    this._hideGridAreaInfoBar();
-    this._hideGridCellInfoBar();
-    this._hideGridLineInfoBar();
-    setIgnoreLayoutChanges(false, this.highlighterEnv.document.documentElement);
-  }
-
-  _hideGrid() {
-    this.getElement("canvas").setAttribute("hidden", "true");
-  }
-
-  _showGrid() {
-    this.getElement("canvas").removeAttribute("hidden");
-  }
-
-  _hideGridElements() {
-    this.getElement("elements").setAttribute("hidden", "true");
-  }
-
-  _showGridElements() {
-    this.getElement("elements").removeAttribute("hidden");
-  }
-
-  _hideGridAreaInfoBar() {
-    this.getElement("area-infobar-container").setAttribute("hidden", "true");
-  }
-
-  _showGridAreaInfoBar() {
-    this.getElement("area-infobar-container").removeAttribute("hidden");
-  }
-
-  _hideGridCellInfoBar() {
-    this.getElement("cell-infobar-container").setAttribute("hidden", "true");
-  }
-
-  _showGridCellInfoBar() {
-    this.getElement("cell-infobar-container").removeAttribute("hidden");
-  }
-
-  _hideGridLineInfoBar() {
-    this.getElement("line-infobar-container").setAttribute("hidden", "true");
-  }
-
-  _showGridLineInfoBar() {
-    this.getElement("line-infobar-container").removeAttribute("hidden");
+    moveInfobar(container, getBoundsFromPoints([{x, y}, {x, y}, {x, y}, {x, y}]),
+      this.win);
   }
 }
 
 exports.CssGridHighlighter = CssGridHighlighter;
--- a/dom/animation/KeyframeEffectReadOnly.cpp
+++ b/dom/animation/KeyframeEffectReadOnly.cpp
@@ -1965,21 +1965,28 @@ KeyframeEffectReadOnly::UpdateEffectSet(
   EffectSet* effectSet =
     aEffectSet ? aEffectSet
                : EffectSet::GetEffectSet(mTarget->mElement,
                                          mTarget->mPseudoType);
   if (!effectSet) {
     return;
   }
 
+  nsIFrame* frame = GetAnimationFrame();
   if (HasAnimationOfProperty(eCSSProperty_opacity)) {
     effectSet->SetMayHaveOpacityAnimation();
+    if (frame) {
+      frame->SetMayHaveOpacityAnimation();
+    }
   }
   if (HasAnimationOfProperty(eCSSProperty_transform)) {
     effectSet->SetMayHaveTransformAnimation();
+    if (frame) {
+      frame->SetMayHaveTransformAnimation();
+    }
   }
 }
 
 template
 void
 KeyframeEffectReadOnly::ComposeStyle<RefPtr<AnimValuesStyleRule>&>(
   RefPtr<AnimValuesStyleRule>& aAnimationRule,
   const nsCSSPropertyIDSet& aPropertiesToSkip);
--- a/dom/clients/manager/ClientHandle.cpp
+++ b/dom/clients/manager/ClientHandle.cpp
@@ -84,16 +84,22 @@ ClientHandle::Activate(PClientManagerChi
   if (!actor) {
     Shutdown();
     return;
   }
 
   ActivateThing(static_cast<ClientHandleChild*>(actor));
 }
 
+void
+ClientHandle::ExecutionReady(const ClientInfo& aClientInfo)
+{
+  mClientInfo = aClientInfo;
+}
+
 const ClientInfo&
 ClientHandle::Info() const
 {
   return mClientInfo;
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/clients/manager/ClientHandle.h
+++ b/dom/clients/manager/ClientHandle.h
@@ -27,29 +27,34 @@ class PClientManagerChild;
 // convert it into a live actor-backed object attached to a particular
 // ClientSource somewhere in the browser.  If the ClientSource is
 // destroyed then the ClientHandle will simply begin to reject operations.
 // We do not currently provide a way to be notified when the ClientSource
 // is destroyed, but this could be added in the future.
 class ClientHandle final : public ClientThing<ClientHandleChild>
 {
   friend class ClientManager;
+  friend class ClientHandleChild;
 
   RefPtr<ClientManager> mManager;
   nsCOMPtr<nsISerialEventTarget> mSerialEventTarget;
   ClientInfo mClientInfo;
 
   ~ClientHandle();
 
   void
   Shutdown();
 
   already_AddRefed<ClientOpPromise>
   StartOp(const ClientOpConstructorArgs& aArgs);
 
+  // Private methods called by ClientHandleChild
+  void
+  ExecutionReady(const ClientInfo& aClientInfo);
+
   // Private methods called by ClientManager
   ClientHandle(ClientManager* aManager,
                nsISerialEventTarget* aSerialEventTarget,
                const ClientInfo& aClientInfo);
 
   void
   Activate(PClientManagerChild* aActor);
 
--- a/dom/clients/manager/ClientHandleChild.cpp
+++ b/dom/clients/manager/ClientHandleChild.cpp
@@ -9,16 +9,25 @@
 #include "ClientHandleOpChild.h"
 #include "mozilla/dom/ClientIPCTypes.h"
 
 namespace mozilla {
 namespace dom {
 
 using mozilla::ipc::IPCResult;
 
+IPCResult
+ClientHandleChild::RecvExecutionReady(const IPCClientInfo& aClientInfo)
+{
+  if (mHandle) {
+    mHandle->ExecutionReady(ClientInfo(aClientInfo));
+  }
+  return IPC_OK();
+}
+
 void
 ClientHandleChild::ActorDestroy(ActorDestroyReason aReason)
 {
   if (mHandle) {
     mHandle->RevokeActor(this);
 
     // Revoking the actor link should automatically cause the owner
     // to call RevokeOwner() as well.
@@ -45,25 +54,25 @@ ClientHandleChild::ClientHandleChild()
   , mTeardownStarted(false)
 {
 }
 
 void
 ClientHandleChild::SetOwner(ClientThing<ClientHandleChild>* aThing)
 {
   MOZ_DIAGNOSTIC_ASSERT(!mHandle);
-  mHandle = aThing;
+  mHandle = static_cast<ClientHandle*>(aThing);
   MOZ_DIAGNOSTIC_ASSERT(mHandle);
 }
 
 void
 ClientHandleChild::RevokeOwner(ClientThing<ClientHandleChild>* aThing)
 {
   MOZ_DIAGNOSTIC_ASSERT(mHandle);
-  MOZ_DIAGNOSTIC_ASSERT(mHandle == aThing);
+  MOZ_DIAGNOSTIC_ASSERT(mHandle == static_cast<ClientHandle*>(aThing));
   mHandle = nullptr;
 }
 
 void
 ClientHandleChild::MaybeStartTeardown()
 {
   if (mTeardownStarted) {
     return;
--- a/dom/clients/manager/ClientHandleChild.h
+++ b/dom/clients/manager/ClientHandleChild.h
@@ -14,20 +14,23 @@ namespace dom {
 
 class ClientHandle;
 class ClientInfo;
 
 template <typename ActorType> class ClientThing;
 
 class ClientHandleChild final : public PClientHandleChild
 {
-  ClientThing<ClientHandleChild>* mHandle;
+  ClientHandle* mHandle;
   bool mTeardownStarted;
 
   // PClientHandleChild interface
+  mozilla::ipc::IPCResult
+  RecvExecutionReady(const IPCClientInfo& aClientInfo) override;
+
   void
   ActorDestroy(ActorDestroyReason aReason) override;
 
   PClientHandleOpChild*
   AllocPClientHandleOpChild(const ClientOpConstructorArgs& aArgs) override;
 
   bool
   DeallocPClientHandleOpChild(PClientHandleOpChild* aActor) override;
--- a/dom/clients/manager/ClientIPCTypes.ipdlh
+++ b/dom/clients/manager/ClientIPCTypes.ipdlh
@@ -45,16 +45,22 @@ struct IPCClientWorkerState
 };
 
 union IPCClientState
 {
   IPCClientWindowState;
   IPCClientWorkerState;
 };
 
+struct ClientSourceExecutionReadyArgs
+{
+  nsCString url;
+  FrameType frameType;
+};
+
 struct ClientOpenWindowArgs
 {
 };
 
 struct ClientOpConstructorArgs
 {
 };
 
--- a/dom/clients/manager/ClientManager.cpp
+++ b/dom/clients/manager/ClientManager.cpp
@@ -4,16 +4,17 @@
  * 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/. */
 
 #include "ClientManager.h"
 
 #include "ClientHandle.h"
 #include "ClientManagerChild.h"
 #include "ClientManagerOpChild.h"
+#include "ClientPrefs.h"
 #include "ClientSource.h"
 #include "mozilla/dom/WorkerPrivate.h"
 #include "mozilla/dom/workers/bindings/WorkerHolderToken.h"
 #include "mozilla/ipc/BackgroundChild.h"
 #include "mozilla/ipc/PBackgroundChild.h"
 #include "prthread.h"
 
 namespace mozilla {
@@ -198,16 +199,18 @@ void
 ClientManager::Startup()
 {
   MOZ_ASSERT(NS_IsMainThread());
 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
   PRStatus status =
 #endif
     PR_NewThreadPrivateIndex(&sClientManagerThreadLocalIndex, nullptr);
   MOZ_DIAGNOSTIC_ASSERT(status == PR_SUCCESS);
+
+  ClientPrefsInit();
 }
 
 // static
 UniquePtr<ClientSource>
 ClientManager::CreateSource(ClientType aType, nsIPrincipal* aPrincipal)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aPrincipal);
--- a/dom/clients/manager/ClientManagerParent.cpp
+++ b/dom/clients/manager/ClientManagerParent.cpp
@@ -96,16 +96,25 @@ ClientManagerParent::AllocPClientSourceP
 
 bool
 ClientManagerParent::DeallocPClientSourceParent(PClientSourceParent* aActor)
 {
   delete aActor;
   return true;
 }
 
+IPCResult
+ClientManagerParent::RecvPClientSourceConstructor(PClientSourceParent* aActor,
+                                                  const ClientSourceConstructorArgs& aArgs)
+{
+  ClientSourceParent* actor = static_cast<ClientSourceParent*>(aActor);
+  actor->Init();
+  return IPC_OK();
+}
+
 ClientManagerParent::ClientManagerParent()
   : mService(ClientManagerService::GetOrCreateInstance())
 {
 }
 
 ClientManagerParent::~ClientManagerParent()
 {
 }
--- a/dom/clients/manager/ClientManagerParent.h
+++ b/dom/clients/manager/ClientManagerParent.h
@@ -51,16 +51,20 @@ class ClientManagerParent final : public
   DeallocPClientNavigateOpParent(PClientNavigateOpParent* aActor) override;
 
   PClientSourceParent*
   AllocPClientSourceParent(const ClientSourceConstructorArgs& aArgs) override;
 
   bool
   DeallocPClientSourceParent(PClientSourceParent* aActor) override;
 
+  mozilla::ipc::IPCResult
+  RecvPClientSourceConstructor(PClientSourceParent* aActor,
+                               const ClientSourceConstructorArgs& aArgs) override;
+
 public:
   ClientManagerParent();
   ~ClientManagerParent();
 };
 
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/clients/manager/ClientManagerService.cpp
+++ b/dom/clients/manager/ClientManagerService.cpp
@@ -78,34 +78,43 @@ ClientManagerService::GetOrCreateInstanc
   if (!sClientManagerServiceInstance) {
     sClientManagerServiceInstance = new ClientManagerService();
   }
 
   RefPtr<ClientManagerService> ref(sClientManagerServiceInstance);
   return ref.forget();
 }
 
-void
+bool
 ClientManagerService::AddSource(ClientSourceParent* aSource)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aSource);
   auto entry = mSourceTable.LookupForAdd(aSource->Info().Id());
-  MOZ_DIAGNOSTIC_ASSERT(!entry);
+  // Do not permit overwriting an existing ClientSource with the same
+  // UUID.  This would allow a spoofed ClientParentSource actor to
+  // intercept postMessage() intended for the real actor.
+  if (NS_WARN_IF(!!entry)) {
+    return false;
+  }
   entry.OrInsert([&] { return aSource; });
+  return true;
 }
 
-void
+bool
 ClientManagerService::RemoveSource(ClientSourceParent* aSource)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aSource);
   auto entry = mSourceTable.Lookup(aSource->Info().Id());
-  MOZ_DIAGNOSTIC_ASSERT(entry);
+  if (NS_WARN_IF(!entry)) {
+    return false;
+  }
   entry.Remove();
+  return true;
 }
 
 ClientSourceParent*
 ClientManagerService::FindSource(const nsID& aID, const PrincipalInfo& aPrincipalInfo)
 {
   AssertIsOnBackgroundThread();
 
   auto entry = mSourceTable.Lookup(aID);
--- a/dom/clients/manager/ClientManagerService.h
+++ b/dom/clients/manager/ClientManagerService.h
@@ -25,20 +25,20 @@ class ClientManagerService final
 
   ClientManagerService();
   ~ClientManagerService();
 
 public:
   static already_AddRefed<ClientManagerService>
   GetOrCreateInstance();
 
-  void
+  bool
   AddSource(ClientSourceParent* aSource);
 
-  void
+  bool
   RemoveSource(ClientSourceParent* aSource);
 
   ClientSourceParent*
   FindSource(const nsID& aID,
              const mozilla::ipc::PrincipalInfo& aPrincipalInfo);
 
   NS_INLINE_DECL_REFCOUNTING(mozilla::dom::ClientManagerService)
 };
new file mode 100644
--- /dev/null
+++ b/dom/clients/manager/ClientPrefs.cpp
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "ClientPrefs.h"
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+
+bool gDataURLUniqueOpaqueOrigin = false;
+
+} // anonymous namespace
+
+void
+ClientPrefsInit()
+{
+  Preferences::AddBoolVarCache(&gDataURLUniqueOpaqueOrigin,
+                               "security.data_uri.unique_opaque_origin",
+                               false);
+}
+
+bool
+ClientPrefsGetDataURLUniqueOpaqueOrigin()
+{
+  return gDataURLUniqueOpaqueOrigin;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/clients/manager/ClientPrefs.h
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+#ifndef _mozilla_dom_ClientPrefs_h
+#define _mozilla_dom_ClientPrefs_h
+
+namespace mozilla {
+namespace dom {
+
+void
+ClientPrefsInit();
+
+bool
+ClientPrefsGetAllowUniqueOpaqueOrigin();
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // _mozilla_dom_ClientPrefs_h
--- a/dom/clients/manager/ClientSource.cpp
+++ b/dom/clients/manager/ClientSource.cpp
@@ -4,54 +4,109 @@
  * 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/. */
 
 #include "ClientSource.h"
 
 #include "ClientManager.h"
 #include "ClientManagerChild.h"
 #include "ClientSourceChild.h"
+#include "ClientValidation.h"
 #include "mozilla/dom/ClientIPCTypes.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "nsIDocShell.h"
+#include "nsPIDOMWindow.h"
 
 namespace mozilla {
 namespace dom {
 
+using mozilla::dom::workers::WorkerPrivate;
 using mozilla::ipc::PrincipalInfo;
 
 void
 ClientSource::Shutdown()
 {
   NS_ASSERT_OWNINGTHREAD(ClientSource);
   if (IsShutdown()) {
     return;
   }
 
   ShutdownThing();
 
   mManager = nullptr;
 }
 
+void
+ClientSource::ExecutionReady(const ClientSourceExecutionReadyArgs& aArgs)
+{
+  // Fast fail if we don't understand this particular principal/URL combination.
+  // This can happen since we use MozURL for validation which does not handle
+  // some of the more obscure internal principal/url combinations.  Normal
+  // content pages will pass this check.
+  if (NS_WARN_IF(!ClientIsValidCreationURL(mClientInfo.PrincipalInfo(),
+                                           aArgs.url()))) {
+    Shutdown();
+    return;
+  }
+
+  mClientInfo.SetURL(aArgs.url());
+  mClientInfo.SetFrameType(aArgs.frameType());
+  MaybeExecute([aArgs](PClientSourceChild* aActor) {
+    aActor->SendExecutionReady(aArgs);
+  });
+}
+
+WorkerPrivate*
+ClientSource::GetWorkerPrivate() const
+{
+  NS_ASSERT_OWNINGTHREAD(ClientSource);
+  if (!mOwner.is<WorkerPrivate*>()) {
+    return nullptr;
+  }
+  return mOwner.as<WorkerPrivate*>();
+}
+
+nsIDocShell*
+ClientSource::GetDocShell() const
+{
+  NS_ASSERT_OWNINGTHREAD(ClientSource);
+  if (!mOwner.is<nsCOMPtr<nsIDocShell>>()) {
+    return nullptr;
+  }
+  return mOwner.as<nsCOMPtr<nsIDocShell>>();
+}
+
 ClientSource::ClientSource(ClientManager* aManager,
                            const ClientSourceConstructorArgs& aArgs)
   : mManager(aManager)
+  , mOwner(AsVariant(Nothing()))
   , mClientInfo(aArgs.id(), aArgs.type(), aArgs.principalInfo(), aArgs.creationTime())
 {
   MOZ_ASSERT(mManager);
 }
 
 void
 ClientSource::Activate(PClientManagerChild* aActor)
 {
   NS_ASSERT_OWNINGTHREAD(ClientSource);
   MOZ_ASSERT(!GetActor());
 
   if (IsShutdown()) {
     return;
   }
 
+  // Fast fail if we don't understand this particular kind of PrincipalInfo.
+  // This can happen since we use MozURL for validation which does not handle
+  // some of the more obscure internal principal/url combinations.  Normal
+  // content pages will pass this check.
+  if (NS_WARN_IF(!ClientIsValidPrincipalInfo(mClientInfo.PrincipalInfo()))) {
+    Shutdown();
+    return;
+  }
+
   ClientSourceConstructorArgs args(mClientInfo.Id(), mClientInfo.Type(),
                                    mClientInfo.PrincipalInfo(),
                                    mClientInfo.CreationTime());
   PClientSourceChild* actor = aActor->SendPClientSourceConstructor(args);
   if (!actor) {
     Shutdown();
     return;
   }
@@ -59,10 +114,123 @@ ClientSource::Activate(PClientManagerChi
   ActivateThing(static_cast<ClientSourceChild*>(actor));
 }
 
 ClientSource::~ClientSource()
 {
   Shutdown();
 }
 
+nsPIDOMWindowInner*
+ClientSource::GetInnerWindow() const
+{
+  NS_ASSERT_OWNINGTHREAD(ClientSource);
+  if (!mOwner.is<RefPtr<nsPIDOMWindowInner>>()) {
+    return nullptr;
+  }
+  return mOwner.as<RefPtr<nsPIDOMWindowInner>>();
+}
+
+void
+ClientSource::WorkerExecutionReady(WorkerPrivate* aWorkerPrivate)
+{
+  MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate);
+  aWorkerPrivate->AssertIsOnWorkerThread();
+
+  // Its safe to store the WorkerPrivate* here because the ClientSource
+  // is explicitly destroyed by WorkerPrivate before exiting its run loop.
+  MOZ_DIAGNOSTIC_ASSERT(mOwner.is<Nothing>());
+  mOwner = AsVariant(aWorkerPrivate);
+
+  ClientSourceExecutionReadyArgs args(
+    aWorkerPrivate->GetLocationInfo().mHref,
+    FrameType::None);
+
+  ExecutionReady(args);
+}
+
+nsresult
+ClientSource::WindowExecutionReady(nsPIDOMWindowInner* aInnerWindow)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_DIAGNOSTIC_ASSERT(aInnerWindow);
+  MOZ_DIAGNOSTIC_ASSERT(aInnerWindow->IsCurrentInnerWindow());
+  MOZ_DIAGNOSTIC_ASSERT(aInnerWindow->HasActiveDocument());
+
+  nsIDocument* doc = aInnerWindow->GetExtantDoc();
+  if (NS_WARN_IF(!doc)) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  // Don't use nsAutoCString here since IPC requires a full nsCString anyway.
+  nsCString spec;
+
+  nsIURI* uri = doc->GetOriginalURI();
+  if (uri) {
+    nsresult rv = uri->GetSpec(spec);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
+  nsPIDOMWindowOuter* outer = aInnerWindow->GetOuterWindow();
+  if (NS_WARN_IF(!outer)) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  FrameType frameType = FrameType::Top_level;
+  if (!outer->IsTopLevelWindow()) {
+    frameType = FrameType::Nested;
+  } else if(outer->HadOriginalOpener()) {
+    frameType = FrameType::Auxiliary;
+  }
+
+  // We should either be setting a window execution ready for the
+  // first time or setting the same window execution ready again.
+  // The secondary calls are due to initial about:blank replacement.
+  MOZ_DIAGNOSTIC_ASSERT(mOwner.is<Nothing>() ||
+                        mOwner.is<nsCOMPtr<nsIDocShell>>() ||
+                        GetInnerWindow() == aInnerWindow);
+
+  // This creates a cycle with the window.  It is broken when
+  // nsGlobalWindow::FreeInnerObjects() deletes the ClientSource.
+  mOwner = AsVariant(RefPtr<nsPIDOMWindowInner>(aInnerWindow));
+
+  ClientSourceExecutionReadyArgs args(spec, frameType);
+  ExecutionReady(args);
+
+  return NS_OK;
+}
+
+nsresult
+ClientSource::DocShellExecutionReady(nsIDocShell* aDocShell)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_DIAGNOSTIC_ASSERT(aDocShell);
+
+  nsPIDOMWindowOuter* outer = aDocShell->GetWindow();
+  if (NS_WARN_IF(!outer)) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  // TODO: dedupe this with WindowExecutionReady
+  FrameType frameType = FrameType::Top_level;
+  if (!outer->IsTopLevelWindow()) {
+    frameType = FrameType::Nested;
+  } else if(outer->HadOriginalOpener()) {
+    frameType = FrameType::Auxiliary;
+  }
+
+  MOZ_DIAGNOSTIC_ASSERT(mOwner.is<Nothing>());
+
+  // This creates a cycle with the docshell.  It is broken when
+  // nsDocShell::Destroy() deletes the ClientSource.
+  mOwner = AsVariant(nsCOMPtr<nsIDocShell>(aDocShell));
+
+  ClientSourceExecutionReadyArgs args(NS_LITERAL_CSTRING("about:blank"),
+                                      frameType);
+  ExecutionReady(args);
+
+  return NS_OK;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/clients/manager/ClientSource.h
+++ b/dom/clients/manager/ClientSource.h
@@ -4,22 +4,26 @@
  * 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/. */
 #ifndef _mozilla_dom_ClientSource_h
 #define _mozilla_dom_ClientSource_h
 
 #include "mozilla/dom/ClientInfo.h"
 #include "mozilla/dom/ClientThing.h"
 
+class nsIDocShell;
+class nsPIDOMWindowInner;
+
 namespace mozilla {
 namespace dom {
 
 class ClientManager;
 class ClientSourceChild;
 class ClientSourceConstructorArgs;
+class ClientSourceExecutionReadyArgs;
 class PClientManagerChild;
 
 namespace workers {
 class WorkerPrivate;
 } // workers namespace
 
 // ClientSource is an RAII style class that is designed to be held via
 // a UniquePtr<>.  When created ClientSource will register the existence
@@ -30,28 +34,54 @@ class WorkerPrivate;
 class ClientSource final : public ClientThing<ClientSourceChild>
 {
   friend class ClientManager;
 
   NS_DECL_OWNINGTHREAD
 
   RefPtr<ClientManager> mManager;
 
+  Variant<Nothing,
+          RefPtr<nsPIDOMWindowInner>,
+          nsCOMPtr<nsIDocShell>,
+          mozilla::dom::workers::WorkerPrivate*> mOwner;
+
   ClientInfo mClientInfo;
 
   void
   Shutdown();
 
+  void
+  ExecutionReady(const ClientSourceExecutionReadyArgs& aArgs);
+
+  mozilla::dom::workers::WorkerPrivate*
+  GetWorkerPrivate() const;
+
+  nsIDocShell*
+  GetDocShell() const;
+
   // Private methods called by ClientManager
   ClientSource(ClientManager* aManager,
                const ClientSourceConstructorArgs& aArgs);
 
   void
   Activate(PClientManagerChild* aActor);
 
 public:
   ~ClientSource();
+
+  nsPIDOMWindowInner*
+  GetInnerWindow() const;
+
+  void
+  WorkerExecutionReady(mozilla::dom::workers::WorkerPrivate* aWorkerPrivate);
+
+  nsresult
+  WindowExecutionReady(nsPIDOMWindowInner* aInnerWindow);
+
+  nsresult
+  DocShellExecutionReady(nsIDocShell* aDocShell);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // _mozilla_dom_ClientSource_h
--- a/dom/clients/manager/ClientSourceParent.cpp
+++ b/dom/clients/manager/ClientSourceParent.cpp
@@ -4,36 +4,123 @@
  * 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/. */
 
 #include "ClientSourceParent.h"
 
 #include "ClientHandleParent.h"
 #include "ClientManagerService.h"
 #include "ClientSourceOpParent.h"
+#include "ClientValidation.h"
 #include "mozilla/dom/ClientIPCTypes.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/PClientManagerParent.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/SystemGroup.h"
 #include "mozilla/Unused.h"
 
 namespace mozilla {
 namespace dom {
 
+using mozilla::ipc::BackgroundParent;
 using mozilla::ipc::IPCResult;
 using mozilla::ipc::PrincipalInfo;
 
+namespace {
+
+// It would be nice to use a lambda instead of this class, but we cannot
+// move capture in lambdas yet and ContentParent cannot be AddRef'd off
+// the main thread.
+class KillContentParentRunnable final : public Runnable
+{
+  RefPtr<ContentParent> mContentParent;
+
+public:
+  explicit KillContentParentRunnable(RefPtr<ContentParent>&& aContentParent)
+    : Runnable("KillContentParentRunnable")
+    , mContentParent(Move(aContentParent))
+  {
+    MOZ_ASSERT(mContentParent);
+  }
+
+  NS_IMETHOD
+  Run()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    mContentParent->KillHard("invalid ClientSourceParent actor");
+    mContentParent = nullptr;
+    return NS_OK;
+  }
+};
+
+} // anonymous namespace
+
+void
+ClientSourceParent::KillInvalidChild()
+{
+  // Try to get the content process before we destroy the actor below.
+  RefPtr<ContentParent> process =
+    BackgroundParent::GetContentParent(Manager()->Manager());
+
+  // First, immediately teardown the ClientSource actor.  No matter what
+  // we want to start this process as soon as possible.
+  Unused << ClientSourceParent::Send__delete__(this);
+
+  // If we are running in non-e10s, then there is nothing else to do here.
+  // There is no child process and we don't want to crash the entire browser
+  // in release builds.  In general, though, this should not happen in non-e10s
+  // so we do assert this condition.
+  if (!process) {
+    MOZ_DIAGNOSTIC_ASSERT(false, "invalid ClientSourceParent in non-e10s");
+    return;
+  }
+
+  // In e10s mode we also want to kill the child process.  Validation failures
+  // typically mean someone sent us bogus data over the IPC link.  We can't
+  // trust that process any more.  We have to do this on the main thread, so
+  // there is a small window of time before we kill the process.  This is why
+  // we start the actor destruction immediately above.
+  nsCOMPtr<nsIRunnable> r = new KillContentParentRunnable(Move(process));
+  MOZ_ALWAYS_SUCCEEDS(SystemGroup::Dispatch(TaskCategory::Other, r.forget()));
+}
+
 IPCResult
 ClientSourceParent::RecvTeardown()
 {
   Unused << Send__delete__(this);
   return IPC_OK();
 }
 
+IPCResult
+ClientSourceParent::RecvExecutionReady(const ClientSourceExecutionReadyArgs& aArgs)
+{
+  // Now that we have the creation URL for the Client we can do some validation
+  // to make sure the child actor is not giving us garbage.  Since we validate
+  // on the child side as well we treat a failure here as fatal.
+  if (!ClientIsValidCreationURL(mClientInfo.PrincipalInfo(), aArgs.url())) {
+    KillInvalidChild();
+    return IPC_OK();
+  }
+
+  mClientInfo.SetURL(aArgs.url());
+  mClientInfo.SetFrameType(aArgs.frameType());
+  mExecutionReady = true;
+
+  for (ClientHandleParent* handle : mHandleList) {
+    Unused << handle->SendExecutionReady(mClientInfo.ToIPC());
+  }
+
+  return IPC_OK();
+};
+
 void
 ClientSourceParent::ActorDestroy(ActorDestroyReason aReason)
 {
-  mService->RemoveSource(this);
+  DebugOnly<bool> removed = mService->RemoveSource(this);
+  MOZ_ASSERT(removed);
 
   nsTArray<ClientHandleParent*> handleList(mHandleList);
   for (ClientHandleParent* handle : handleList) {
     // This should trigger DetachHandle() to be called removing
     // the entry from the mHandleList.
     Unused << ClientHandleParent::Send__delete__(handle);
   }
   MOZ_DIAGNOSTIC_ASSERT(mHandleList.IsEmpty());
@@ -51,25 +138,45 @@ ClientSourceParent::DeallocPClientSource
 {
   delete aActor;
   return true;
 }
 
 ClientSourceParent::ClientSourceParent(const ClientSourceConstructorArgs& aArgs)
   : mClientInfo(aArgs.id(), aArgs.type(), aArgs.principalInfo(), aArgs.creationTime())
   , mService(ClientManagerService::GetOrCreateInstance())
+  , mExecutionReady(false)
 {
-  mService->AddSource(this);
 }
 
 ClientSourceParent::~ClientSourceParent()
 {
   MOZ_DIAGNOSTIC_ASSERT(mHandleList.IsEmpty());
 }
 
+void
+ClientSourceParent::Init()
+{
+  // Ensure the principal is reasonable before adding ourself to the service.
+  // Since we validate the principal on the child side as well, any failure
+  // here is treated as fatal.
+  if (NS_WARN_IF(!ClientIsValidPrincipalInfo(mClientInfo.PrincipalInfo()))) {
+    KillInvalidChild();
+    return;
+  }
+
+  // Its possible for AddSource() to fail if there is already an entry for
+  // our UUID.  This should not normally happen, but could if someone is
+  // spoofing IPC messages.
+  if (NS_WARN_IF(!mService->AddSource(this))) {
+    KillInvalidChild();
+    return;
+  }
+}
+
 const ClientInfo&
 ClientSourceParent::Info() const
 {
   return mClientInfo;
 }
 
 void
 ClientSourceParent::AttachHandle(ClientHandleParent* aClientHandle)
--- a/dom/clients/manager/ClientSourceParent.h
+++ b/dom/clients/manager/ClientSourceParent.h
@@ -15,34 +15,44 @@ namespace dom {
 class ClientHandleParent;
 class ClientManagerService;
 
 class ClientSourceParent final : public PClientSourceParent
 {
   ClientInfo mClientInfo;
   RefPtr<ClientManagerService> mService;
   nsTArray<ClientHandleParent*> mHandleList;
+  bool mExecutionReady;
+
+  void
+  KillInvalidChild();
 
   // PClientSourceParent
   IPCResult
   RecvTeardown() override;
 
+  IPCResult
+  RecvExecutionReady(const ClientSourceExecutionReadyArgs& aArgs) override;
+
   void
   ActorDestroy(ActorDestroyReason aReason) override;
 
   PClientSourceOpParent*
   AllocPClientSourceOpParent(const ClientOpConstructorArgs& aArgs) override;
 
   bool
   DeallocPClientSourceOpParent(PClientSourceOpParent* aActor) override;
 
 public:
   explicit ClientSourceParent(const ClientSourceConstructorArgs& aArgs);
   ~ClientSourceParent();
 
+  void
+  Init();
+
   const ClientInfo&
   Info() const;
 
   void
   AttachHandle(ClientHandleParent* aClientSource);
 
   void
   DetachHandle(ClientHandleParent* aClientSource);
new file mode 100644
--- /dev/null
+++ b/dom/clients/manager/ClientValidation.cpp
@@ -0,0 +1,184 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "ClientValidation.h"
+
+#include "ClientPrefs.h"
+#include "mozilla/net/MozURL.h"
+
+namespace mozilla {
+namespace dom {
+
+using mozilla::ipc::ContentPrincipalInfo;
+using mozilla::ipc::PrincipalInfo;
+using mozilla::net::MozURL;
+
+bool
+ClientIsValidPrincipalInfo(const PrincipalInfo& aPrincipalInfo)
+{
+  // Ideally we would verify that the source process has permission to
+  // create a window or worker with the given principal, but we don't
+  // currently have any such restriction in place.  Instead, at least
+  // verify the PrincipalInfo is an expected type and has a parsable
+  // origin/spec.
+  switch (aPrincipalInfo.type()) {
+    // Any system and null principal is acceptable.
+    case PrincipalInfo::TSystemPrincipalInfo:
+    case PrincipalInfo::TNullPrincipalInfo:
+    {
+      return true;
+    }
+
+    // Validate content principals to ensure that the origin and spec are sane.
+    case PrincipalInfo::TContentPrincipalInfo:
+    {
+      const ContentPrincipalInfo& content =
+       aPrincipalInfo.get_ContentPrincipalInfo();
+
+      // Verify the principal spec parses.
+      RefPtr<MozURL> specURL;
+      nsresult rv = MozURL::Init(getter_AddRefs(specURL), content.spec());
+      NS_ENSURE_SUCCESS(rv, false);
+
+      // Verify the principal originNoSuffix parses.
+      RefPtr<MozURL> originURL;
+      rv = MozURL::Init(getter_AddRefs(originURL),
+                        content.originNoSuffix().get_nsCString());
+      NS_ENSURE_SUCCESS(rv, false);
+
+      nsAutoCString originOrigin;
+      rv = originURL->GetOrigin(originOrigin);
+      NS_ENSURE_SUCCESS(rv, false);
+
+      nsAutoCString specOrigin;
+      rv = specURL->GetOrigin(specOrigin);
+      NS_ENSURE_SUCCESS(rv, false);
+
+      // For now require Clients to have a principal where both its
+      // originNoSuffix and spec have the same origin.  This will
+      // exclude a variety of unusual combinations within the browser
+      // but its adequate for the features need to support right now.
+      // If necessary we could expand this function to handle more
+      // cases in the future.
+      return specOrigin == originOrigin;
+    }
+    default:
+    {
+      break;
+    }
+  }
+
+  // Windows and workers should not have expanded URLs, etc.
+  return false;
+}
+
+bool
+ClientIsValidCreationURL(const PrincipalInfo& aPrincipalInfo,
+                         const nsACString& aURL)
+{
+  RefPtr<MozURL> url;
+  nsresult rv = MozURL::Init(getter_AddRefs(url), aURL);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  switch (aPrincipalInfo.type()) {
+    case PrincipalInfo::TContentPrincipalInfo:
+    {
+      // Any origin can create an about:blank or about:srcdoc Client.
+      if (aURL.LowerCaseEqualsLiteral("about:blank") ||
+          aURL.LowerCaseEqualsLiteral("about:srcdoc")) {
+        return true;
+      }
+
+      const ContentPrincipalInfo& content =
+        aPrincipalInfo.get_ContentPrincipalInfo();
+
+      // Parse the principal origin URL as well.  This ensures any MozURL
+      // parser issues effect both URLs equally.
+      RefPtr<MozURL> principalURL;
+      rv = MozURL::Init(getter_AddRefs(principalURL),
+                        content.originNoSuffix().get_nsCString());
+      NS_ENSURE_SUCCESS(rv, false);
+
+      nsAutoCString origin;
+      rv = url->GetOrigin(origin);
+      NS_ENSURE_SUCCESS(rv, false);
+
+      nsAutoCString principalOrigin;
+      rv = principalURL->GetOrigin(principalOrigin);
+      NS_ENSURE_SUCCESS(rv, false);
+
+      // The vast majority of sites should simply result in the same principal
+      // and URL origin.
+      if (principalOrigin == origin) {
+        return true;
+      }
+
+      nsAutoCString scheme;
+      rv = url->GetScheme(scheme);
+      NS_ENSURE_SUCCESS(rv, false);
+
+      // Generally any origin can also open javascript: windows and workers.
+      if (scheme.LowerCaseEqualsLiteral("javascript")) {
+        return true;
+      }
+
+      // We have some tests that use data: URL windows without an opaque
+      // origin.  This should only happen when a pref is set.
+      if (!ClientPrefsGetDataURLUniqueOpaqueOrigin() &&
+          scheme.LowerCaseEqualsLiteral("data")) {
+        return true;
+      }
+
+      nsAutoCString principalScheme;
+      rv = principalURL->GetScheme(principalScheme);
+      NS_ENSURE_SUCCESS(rv, false);
+
+      // Otherwise don't support this URL type in the clients sub-system for
+      // now.  This will exclude a variety of internal browser clients, but
+      // currently we don't need to support those.  This function can be
+      // expanded to handle more cases as necessary.
+      return false;
+    }
+    case PrincipalInfo::TSystemPrincipalInfo:
+    {
+      nsAutoCString scheme;
+      rv = url->GetScheme(scheme);
+      NS_ENSURE_SUCCESS(rv, false);
+
+      // While many types of documents can be created with a system principal,
+      // there are only a few that can reasonably become windows.  We attempt
+      // to validate the list of known cases here with a simple scheme check.
+      return scheme.LowerCaseEqualsLiteral("about") ||
+             scheme.LowerCaseEqualsLiteral("chrome") ||
+             scheme.LowerCaseEqualsLiteral("resource") ||
+             scheme.LowerCaseEqualsLiteral("blob") ||
+             scheme.LowerCaseEqualsLiteral("javascript") ||
+             scheme.LowerCaseEqualsLiteral("view-source") ||
+
+             (!ClientPrefsGetDataURLUniqueOpaqueOrigin() &&
+              scheme.LowerCaseEqualsLiteral("data"));
+    }
+    case PrincipalInfo::TNullPrincipalInfo:
+    {
+      // A wide variety of clients can have a null principal.  For example,
+      // sandboxed iframes can have a normal content URL.  For now allow
+      // any parsable URL for null principals.  This is relatively safe since
+      // null principals have unique origins and won't most ClientManagerService
+      // queries anyway.
+      return true;
+    }
+    default:
+    {
+      break;
+    }
+  }
+
+  // Clients (windows/workers) should never have an expanded principal type.
+  return false;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/clients/manager/ClientValidation.h
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+#ifndef _mozilla_dom_ClientValidation_h
+#define _mozilla_dom_ClientValidation_h
+
+namespace mozilla {
+
+namespace ipc {
+class PrincipalInfo;
+} // namespace ipc
+
+namespace dom {
+
+bool
+ClientIsValidPrincipalInfo(const mozilla::ipc::PrincipalInfo& aPrincipalInfo);
+
+bool
+ClientIsValidCreationURL(const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
+                         const nsACString& aURL);
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // _mozilla_dom_ClientValidation_h
--- a/dom/clients/manager/PClientHandle.ipdl
+++ b/dom/clients/manager/PClientHandle.ipdl
@@ -20,13 +20,15 @@ protocol PClientHandle
   manages PClientHandleOp;
 
 parent:
   async Teardown();
 
   async PClientHandleOp(ClientOpConstructorArgs aArgs);
 
 child:
+  async ExecutionReady(IPCClientInfo aClientInfo);
+
   async __delete__();
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/clients/manager/PClientSource.ipdl
+++ b/dom/clients/manager/PClientSource.ipdl
@@ -16,16 +16,17 @@ namespace dom {
 sync protocol PClientSource
 {
   manager PClientManager;
 
   manages PClientSourceOp;
 
 parent:
   async Teardown();
+  async ExecutionReady(ClientSourceExecutionReadyArgs aArgs);
 
 child:
   async PClientSourceOp(ClientOpConstructorArgs aArgs);
 
   async __delete__();
 };
 
 } // namespace dom
--- a/dom/clients/manager/moz.build
+++ b/dom/clients/manager/moz.build
@@ -31,22 +31,24 @@ UNIFIED_SOURCES += [
   'ClientManagerOpParent.cpp',
   'ClientManagerParent.cpp',
   'ClientManagerService.cpp',
   'ClientNavigateOpChild.cpp',
   'ClientNavigateOpParent.cpp',
   'ClientOpenWindowOpActors.cpp',
   'ClientOpenWindowOpChild.cpp',
   'ClientOpenWindowOpParent.cpp',
+  'ClientPrefs.cpp',
   'ClientSource.cpp',
   'ClientSourceChild.cpp',
   'ClientSourceOpChild.cpp',
   'ClientSourceOpParent.cpp',
   'ClientSourceParent.cpp',
   'ClientState.cpp',
+  'ClientValidation.cpp',
 ]
 
 IPDL_SOURCES += [
   'ClientIPCTypes.ipdlh',
   'PClientHandle.ipdl',
   'PClientHandleOp.ipdl',
   'PClientManager.ipdl',
   'PClientManagerOp.ipdl',
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -1291,17 +1291,21 @@ ContentParent::Init()
 {
   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   if (obs) {
     size_t length = ArrayLength(sObserverTopics);
     for (size_t i = 0; i < length; ++i) {
       obs->AddObserver(this, sObserverTopics[i], false);
     }
   }
+
+  // Register ContentParent as an observer for changes to any pref whose prefix
+  // matches the empty string, i.e. all of them.
   Preferences::AddStrongObserver(this, "");
+
   if (obs) {
     nsAutoString cpId;
     cpId.AppendInt(static_cast<uint64_t>(this->ChildID()));
     obs->NotifyObservers(static_cast<nsIObserver*>(this), "ipc:content-created", cpId.get());
   }
 
 #ifdef ACCESSIBILITY
   // If accessibility is running in chrome process then start it in content
@@ -2768,24 +2772,24 @@ ContentParent::Observe(nsISupports* aSub
     return NS_OK;
 
   // listening for memory pressure event
   if (!strcmp(aTopic, "memory-pressure") &&
       !StringEndsWith(nsDependentString(aData),
                       NS_LITERAL_STRING("-no-forward"))) {
       Unused << SendFlushMemory(nsDependentString(aData));
   }
-  // listening for remotePrefs...
   else if (!strcmp(aTopic, "nsPref:changed")) {
-    // Some prefs are not useful in the content process.
+    // A pref changed. If it's not on the blacklist, inform child processes.
 #define BLACKLIST_ENTRY(s) { s, (sizeof(s)/sizeof(char16_t)) - 1 }
     struct BlacklistEntry {
       const char16_t* mPrefBranch;
       size_t mLen;
     };
+    // These prefs are not useful in child processes.
     static const BlacklistEntry sContentPrefBranchBlacklist[] = {
       BLACKLIST_ENTRY(u"app.update.lastUpdateTime."),
       BLACKLIST_ENTRY(u"datareporting.policy."),
       BLACKLIST_ENTRY(u"browser.safebrowsing.provider."),
       BLACKLIST_ENTRY(u"extensions.getAddons.cache."),
       BLACKLIST_ENTRY(u"media.gmp-manager."),
       BLACKLIST_ENTRY(u"media.gmp-gmpopenh264."),
     };
--- a/editor/composer/nsEditorSpellCheck.cpp
+++ b/editor/composer/nsEditorSpellCheck.cpp
@@ -656,21 +656,27 @@ nsEditorSpellCheck::SetCurrentDictionary
         printf("***** Clearing content preferences for |%s|\n",
                NS_ConvertUTF16toUTF8(aDictionary).get());
 #endif
       }
 
       // Also store it in as a preference, so we can use it as a fallback.
       // We don't want this for mail composer because it uses
       // "spellchecker.dictionary" as a preference.
-      Preferences::SetString("spellchecker.dictionary", aDictionary);
+      //
+      // XXX: Prefs can only be set in the parent process, so this condition is
+      // necessary to stop libpref from throwing errors. But this should
+      // probably be handled in a better way.
+      if (XRE_IsParentProcess()) {
+        Preferences::SetString("spellchecker.dictionary", aDictionary);
 #ifdef DEBUG_DICT
-      printf("***** Storing spellchecker.dictionary |%s|\n",
-             NS_ConvertUTF16toUTF8(aDictionary).get());
+        printf("***** Possibly storing spellchecker.dictionary |%s|\n",
+               NS_ConvertUTF16toUTF8(aDictionary).get());
 #endif
+      }
     }
   }
   return mSpellChecker->SetCurrentDictionary(aDictionary);
 }
 
 NS_IMETHODIMP
 nsEditorSpellCheck::UninitSpellChecker()
 {
--- a/extensions/spellcheck/locales/en-US/hunspell/en-US.dic
+++ b/extensions/spellcheck/locales/en-US/hunspell/en-US.dic
@@ -1,9 +1,9 @@
-52373
+52374
 0/nm
 0th/pt
 1/n1
 1st/p
 1th/tc
 2/nm
 2nd/p
 2th/tc
@@ -27173,16 +27173,17 @@ fuck/SMGDRZ!
 fucker/M!
 fuckhead/SM!
 fuddle/DSMG
 fudge/DSMG
 fuehrer/MS
 fuel's
 fuel/ADGS
 fug
+fugacious/YP
 fugal
 fuggy
 fugitive/MS
 fugue/SM
 fuhrer/SM
 fulcrum/MS
 fulfill/LDGS
 fulfilled/U
--- a/gfx/layers/ipc/ImageBridgeParent.cpp
+++ b/gfx/layers/ipc/ImageBridgeParent.cpp
@@ -384,16 +384,19 @@ ImageBridgeParent::NotifyImageComposites
     MOZ_ASSERT(aNotifications[i].mNotification.compositable());
     ProcessId pid = aNotifications[i].mImageBridgeProcessId;
     while (end < aNotifications.Length() &&
            aNotifications[end].mImageBridgeProcessId == pid) {
       notifications.AppendElement(aNotifications[end].mNotification);
       ++end;
     }
     RefPtr<ImageBridgeParent> bridge = GetInstance(pid);
+    if (!bridge || bridge->mClosed) {
+      continue;
+    }
     bridge->SendPendingAsyncMessages();
     if (!bridge->SendDidComposite(notifications)) {
       ok = false;
     }
     i = end;
   }
   return ok;
 }
--- a/gfx/layers/wr/AsyncImagePipelineManager.cpp
+++ b/gfx/layers/wr/AsyncImagePipelineManager.cpp
@@ -132,22 +132,21 @@ AsyncImagePipelineManager::UpdateAsyncIm
   if (mDestroyed) {
     return;
   }
   AsyncImagePipeline* pipeline = mAsyncImagePipelines.Get(wr::AsUint64(aPipelineId));
   if (!pipeline) {
     return;
   }
   pipeline->mInitialised = true;
-  pipeline->mIsChanged = true;
-  pipeline->mScBounds = aScBounds;
-  pipeline->mScTransform = aScTransform;
-  pipeline->mScaleToSize = aScaleToSize;
-  pipeline->mFilter = aFilter;
-  pipeline->mMixBlendMode = aMixBlendMode;
+  pipeline->Update(aScBounds,
+                   aScTransform,
+                   aScaleToSize,
+                   aFilter,
+                   aMixBlendMode);
 }
 
 Maybe<TextureHost::ResourceUpdateOp>
 AsyncImagePipelineManager::UpdateImageKeys(wr::ResourceUpdateQueue& aResources,
                                            AsyncImagePipeline* aPipeline,
                                            nsTArray<wr::ImageKey>& aKeys)
 {
   MOZ_ASSERT(aKeys.IsEmpty());
--- a/gfx/layers/wr/AsyncImagePipelineManager.h
+++ b/gfx/layers/wr/AsyncImagePipelineManager.h
@@ -115,16 +115,33 @@ private:
   struct PipelineTexturesHolder {
     // Holds forwarding WebRenderTextureHosts.
     std::queue<ForwardingTextureHost> mTextureHosts;
     Maybe<wr::Epoch> mDestroyedEpoch;
   };
 
   struct AsyncImagePipeline {
     AsyncImagePipeline();
+    void Update(const LayoutDeviceRect& aScBounds,
+                const gfx::Matrix4x4& aScTransform,
+                const gfx::MaybeIntSize& aScaleToSize,
+                const wr::ImageRendering& aFilter,
+                const wr::MixBlendMode& aMixBlendMode)
+    {
+      mIsChanged |= !mScBounds.IsEqualEdges(aScBounds) ||
+                    mScTransform != aScTransform ||
+                    mScaleToSize != aScaleToSize ||
+                    mFilter != aFilter ||
+                    mMixBlendMode != aMixBlendMode;
+      mScBounds = aScBounds;
+      mScTransform = aScTransform;
+      mScaleToSize = aScaleToSize;
+      mFilter = aFilter;
+      mMixBlendMode = aMixBlendMode;
+    }
 
     bool mInitialised;
     bool mIsChanged;
     bool mUseExternalImage;
     LayoutDeviceRect mScBounds;
     gfx::Matrix4x4 mScTransform;
     gfx::MaybeIntSize mScaleToSize;
     wr::ImageRendering mFilter;
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -665,21 +665,33 @@ WebRenderBridgeParent::RecvEmptyTransact
     mAsyncImageManager->SetCompositionTime(TimeStamp::Now());
     ProcessWebRenderParentCommands(aCommands);
     mCompositorScheduler->ScheduleComposition();
   }
 
   mScrollData.SetFocusTarget(aFocusTarget);
   UpdateAPZ(false);
 
-  // XXX Call DidComposite at correct timing.
-  TimeStamp now = TimeStamp::Now();
-  HoldPendingTransactionId(mWrEpoch, aTransactionId, aTxnStartTime, aFwdTime);
-  mCompositorBridge->DidComposite(wr::AsUint64(mPipelineId), now, now);
-
+  if (!aCommands.IsEmpty()) {
+    uint32_t wrEpoch = GetNextWrEpoch();
+    // Send empty UpdatePipelineResources to WebRender just to notify a new epoch.
+    // The epoch is used to know a timing of calling DidComposite().
+    // This is much simpler than tracking an epoch of AsyncImagePipeline.
+    wr::ResourceUpdateQueue resourceUpdates;
+    mApi->UpdatePipelineResources(resourceUpdates, mPipelineId, wr::NewEpoch(wrEpoch));
+    HoldPendingTransactionId(wrEpoch, aTransactionId, aTxnStartTime, aFwdTime);
+  } else {
+    HoldPendingTransactionId(mWrEpoch, aTransactionId, aTxnStartTime, aFwdTime);
+    // If WebRenderBridgeParent does not have pending DidComposites,
+    // send DidComposite now.
+    if (mPendingTransactionIds.empty()) {
+      TimeStamp now = TimeStamp::Now();
+      mCompositorBridge->DidComposite(wr::AsUint64(mPipelineId), now, now);
+    }
+  }
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvSetFocusTarget(const FocusTarget& aFocusTarget)
 {
   mScrollData.SetFocusTarget(aFocusTarget);
   UpdateAPZ(false);
--- a/gfx/webrender_bindings/Moz2DImageRenderer.cpp
+++ b/gfx/webrender_bindings/Moz2DImageRenderer.cpp
@@ -3,16 +3,17 @@
 /* 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/. */
 
 #include "gfxUtils.h"
 #include "mozilla/Range.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/InlineTranslator.h"
+#include "mozilla/gfx/Logging.h"
 #include "mozilla/gfx/RecordedEvent.h"
 #include "WebRenderTypes.h"
 #include "webrender_ffi.h"
 
 #include <iostream>
 #include <unordered_map>
 
 #ifdef XP_MACOSX
@@ -119,20 +120,25 @@ GetUnscaledFont(Translator *aTranslator,
     FontType::FONTCONFIG;
 #endif
   // makes a copy of the data
   RefPtr<NativeFontResource> fontResource = Factory::CreateNativeFontResource((uint8_t*)data.mData, data.mSize,
                                                                               aTranslator->GetReferenceDrawTarget()->GetBackendType(),
                                                                               type,
                                                                               aTranslator->GetFontContext());
   RefPtr<UnscaledFont> unscaledFont;
-  if (fontResource) {
+  if (!fontResource) {
+    gfxDevCrash(LogReason::NativeFontResourceNotFound) << "Failed to creative NativeFontResource for FontKey " << key.mHandle;
+  } else {
     // Instance data is only needed for GDI fonts which webrender does not
     // support.
     unscaledFont = fontResource->CreateUnscaledFont(data.mIndex, nullptr, 0);
+    if (!unscaledFont) {
+      gfxDevCrash(LogReason::UnscaledFontNotFound) << "Failed to create UnscaledFont for FontKey " << key.mHandle;
+    }
   }
   data.mUnscaledFont = unscaledFont;
   return unscaledFont;
 }
 
 static bool Moz2DRenderCallback(const Range<const uint8_t> aBlob,
                                 gfx::IntSize aSize,
                                 gfx::SurfaceFormat aFormat,
--- a/gfx/webrender_bindings/WebRenderTypes.h
+++ b/gfx/webrender_bindings/WebRenderTypes.h
@@ -56,16 +56,17 @@ inline DebugFlags NewDebugFlags(uint32_t
   return flags;
 }
 
 inline Maybe<wr::ImageFormat>
 SurfaceFormatToImageFormat(gfx::SurfaceFormat aFormat) {
   switch (aFormat) {
     case gfx::SurfaceFormat::R8G8B8X8:
       // TODO: use RGBA + opaque flag
+    case gfx::SurfaceFormat::R8G8B8A8:
       return Some(wr::ImageFormat::BGRA8);
     case gfx::SurfaceFormat::B8G8R8X8:
       // TODO: WebRender will have a BGRA + opaque flag for this but does not
       // have it yet (cf. issue #732).
     case gfx::SurfaceFormat::B8G8R8A8:
       return Some(wr::ImageFormat::BGRA8);
     case gfx::SurfaceFormat::B8G8R8:
       return Some(wr::ImageFormat::RGB8);
--- a/ipc/mscom/Interceptor.cpp
+++ b/ipc/mscom/Interceptor.cpp
@@ -11,16 +11,17 @@
 #include "mozilla/mscom/DispatchForwarder.h"
 #include "mozilla/mscom/FastMarshaler.h"
 #include "mozilla/mscom/Interceptor.h"
 #include "mozilla/mscom/InterceptorLog.h"
 #include "mozilla/mscom/MainThreadInvoker.h"
 #include "mozilla/mscom/Objref.h"
 #include "mozilla/mscom/Registration.h"
 #include "mozilla/mscom/Utils.h"
+#include "mozilla/ThreadLocal.h"
 #include "MainThreadUtils.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/DebugOnly.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsRefPtrHashtable.h"
 #include "nsThreadUtils.h"
 #include "nsXULAppAPI.h"
@@ -126,16 +127,121 @@ public:
   LiveSetAutoLock(LiveSetAutoLock&& aOther) = delete;
   LiveSetAutoLock& operator=(const LiveSetAutoLock& aOther) = delete;
   LiveSetAutoLock& operator=(LiveSetAutoLock&& aOther) = delete;
 
 private:
   LiveSet*  mLiveSet;
 };
 
+class MOZ_RAII ReentrySentinel final
+{
+public:
+  explicit ReentrySentinel(Interceptor* aCurrent)
+    : mCurInterceptor(aCurrent)
+  {
+    static const bool kHasTls = tlsSentinelStackTop.init();
+    MOZ_RELEASE_ASSERT(kHasTls);
+
+    mPrevSentinel = tlsSentinelStackTop.get();
+    tlsSentinelStackTop.set(this);
+  }
+
+  ~ReentrySentinel()
+  {
+    tlsSentinelStackTop.set(mPrevSentinel);
+  }
+
+  bool IsOutermost() const
+  {
+    return !(mPrevSentinel && mPrevSentinel->IsMarshaling(mCurInterceptor));
+  }
+
+  ReentrySentinel(const ReentrySentinel&) = delete;
+  ReentrySentinel(ReentrySentinel&&) = delete;
+  ReentrySentinel& operator=(const ReentrySentinel&) = delete;
+  ReentrySentinel& operator=(ReentrySentinel&&) = delete;
+
+private:
+  bool IsMarshaling(Interceptor* aTopInterceptor) const
+  {
+    return aTopInterceptor == mCurInterceptor ||
+           (mPrevSentinel && mPrevSentinel->IsMarshaling(aTopInterceptor));
+  }
+
+private:
+  Interceptor*      mCurInterceptor;
+  ReentrySentinel*  mPrevSentinel;
+
+  static MOZ_THREAD_LOCAL(ReentrySentinel*) tlsSentinelStackTop;
+};
+
+MOZ_THREAD_LOCAL(ReentrySentinel*) ReentrySentinel::tlsSentinelStackTop;
+
+class MOZ_RAII LoggedQIResult final
+{
+public:
+  explicit LoggedQIResult(REFIID aIid)
+    : mIid(aIid)
+    , mHr(E_UNEXPECTED)
+    , mTarget(nullptr)
+    , mInterceptor(nullptr)
+    , mBegin(TimeStamp::Now())
+  {
+  }
+
+  ~LoggedQIResult()
+  {
+    if (!mTarget) {
+      return;
+    }
+
+    TimeStamp end(TimeStamp::Now());
+    TimeDuration total(end - mBegin);
+    TimeDuration overhead(total - mNonOverheadDuration);
+
+    InterceptorLog::QI(mHr, mTarget, mIid, mInterceptor, &overhead,
+                       &mNonOverheadDuration);
+  }
+
+  void Log(IUnknown* aTarget, IUnknown* aInterceptor)
+  {
+    mTarget = aTarget;
+    mInterceptor = aInterceptor;
+  }
+
+  void operator=(HRESULT aHr)
+  {
+    mHr = aHr;
+  }
+
+  operator HRESULT()
+  {
+    return mHr;
+  }
+
+  operator TimeDuration*()
+  {
+    return &mNonOverheadDuration;
+  }
+
+  LoggedQIResult(const LoggedQIResult&) = delete;
+  LoggedQIResult(LoggedQIResult&&) = delete;
+  LoggedQIResult& operator=(const LoggedQIResult&) = delete;
+  LoggedQIResult& operator=(LoggedQIResult&&) = delete;
+
+private:
+  REFIID        mIid;
+  HRESULT       mHr;
+  IUnknown*     mTarget;
+  IUnknown*     mInterceptor;
+  TimeDuration  mNonOverheadDuration;
+  TimeStamp     mBegin;
+};
+
 } // namespace detail
 
 static detail::LiveSet&
 GetLiveSet()
 {
   static detail::LiveSet sLiveSet;
   return sLiveSet;
 }
@@ -235,19 +341,21 @@ Interceptor::GetUnmarshalClass(REFIID ri
                                         pvDestContext, mshlflags, pCid);
 }
 
 HRESULT
 Interceptor::GetMarshalSizeMax(REFIID riid, void* pv, DWORD dwDestContext,
                                void* pvDestContext, DWORD mshlflags,
                                DWORD* pSize)
 {
+  detail::ReentrySentinel sentinel(this);
+
   HRESULT hr = mStdMarshal->GetMarshalSizeMax(MarshalAs(riid), pv, dwDestContext,
                                               pvDestContext, mshlflags, pSize);
-  if (FAILED(hr)) {
+  if (FAILED(hr) || !sentinel.IsOutermost()) {
     return hr;
   }
 
   DWORD payloadSize = 0;
   hr = mEventSink->GetHandlerPayloadSize(WrapNotNull(this),
                                          WrapNotNull(&payloadSize));
   if (hr == E_NOTIMPL) {
     return S_OK;
@@ -259,16 +367,18 @@ Interceptor::GetMarshalSizeMax(REFIID ri
   return hr;
 }
 
 HRESULT
 Interceptor::MarshalInterface(IStream* pStm, REFIID riid, void* pv,
                               DWORD dwDestContext, void* pvDestContext,
                               DWORD mshlflags)
 {
+  detail::ReentrySentinel sentinel(this);
+
   HRESULT hr;
 
 #if defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)
   // Save the current stream position
   LARGE_INTEGER seekTo;
   seekTo.QuadPart = 0;
 
   ULARGE_INTEGER objrefPos;
@@ -277,17 +387,17 @@ Interceptor::MarshalInterface(IStream* p
   if (FAILED(hr)) {
     return hr;
   }
 
 #endif // defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)
 
   hr = mStdMarshal->MarshalInterface(pStm, MarshalAs(riid), pv, dwDestContext,
                                      pvDestContext, mshlflags);
-  if (FAILED(hr)) {
+  if (FAILED(hr) || !sentinel.IsOutermost()) {
     return hr;
   }
 
 #if defined(MOZ_MSCOM_REMARSHAL_NO_HANDLER)
   if (XRE_IsContentProcess() && IsCallerExternalProcess()) {
     // The caller isn't our chrome process, so do not provide a handler.
 
     // First, save the current position that marks the current end of the
@@ -483,89 +593,28 @@ Interceptor::GetInitialInterceptorForIID
     return hr;
   }
 
   hr = GetInterceptorForIID(aTargetIid, aOutInterceptor);
   ENSURE_HR_SUCCEEDED(hr);
   return hr;
 }
 
-class MOZ_RAII LoggedQIResult final
-{
-public:
-  explicit LoggedQIResult(REFIID aIid)
-    : mIid(aIid)
-    , mHr(E_UNEXPECTED)
-    , mTarget(nullptr)
-    , mInterceptor(nullptr)
-    , mBegin(TimeStamp::Now())
-  {
-  }
-
-  ~LoggedQIResult()
-  {
-    if (!mTarget) {
-      return;
-    }
-
-    TimeStamp end(TimeStamp::Now());
-    TimeDuration total(end - mBegin);
-    TimeDuration overhead(total - mNonOverheadDuration);
-
-    InterceptorLog::QI(mHr, mTarget, mIid, mInterceptor, &overhead,
-                       &mNonOverheadDuration);
-  }
-
-  void Log(IUnknown* aTarget, IUnknown* aInterceptor)
-  {
-    mTarget = aTarget;
-    mInterceptor = aInterceptor;
-  }
-
-  void operator=(HRESULT aHr)
-  {
-    mHr = aHr;
-  }
-
-  operator HRESULT()
-  {
-    return mHr;
-  }
-
-  operator TimeDuration*()
-  {
-    return &mNonOverheadDuration;
-  }
-
-  LoggedQIResult(const LoggedQIResult&) = delete;
-  LoggedQIResult(LoggedQIResult&&) = delete;
-  LoggedQIResult& operator=(const LoggedQIResult&) = delete;
-  LoggedQIResult& operator=(LoggedQIResult&&) = delete;
-
-private:
-  REFIID        mIid;
-  HRESULT       mHr;
-  IUnknown*     mTarget;
-  IUnknown*     mInterceptor;
-  TimeDuration  mNonOverheadDuration;
-  TimeStamp     mBegin;
-};
-
 /**
  * This method contains the core guts of the handling of QueryInterface calls
  * that are delegated to us from the ICallInterceptor.
  *
  * @param aIid ID of the desired interface
  * @param aOutInterceptor The resulting emulated vtable that corresponds to
  * the interface specified by aIid.
  */
 HRESULT
 Interceptor::GetInterceptorForIID(REFIID aIid, void** aOutInterceptor)
 {
-  LoggedQIResult result(aIid);
+  detail::LoggedQIResult result(aIid);
 
   if (!aOutInterceptor) {
     return E_INVALIDARG;
   }
 
   if (aIid == IID_IUnknown) {
     // Special case: When we see IUnknown, we just provide a reference to this
     RefPtr<IInterceptor> intcpt(this);
@@ -691,29 +740,35 @@ Interceptor::QueryInterfaceTarget(REFIID
     *aOutDuration = invoker.GetDuration();
   }
   return hr;
 }
 
 HRESULT
 Interceptor::QueryInterface(REFIID riid, void** ppv)
 {
+  if (riid == IID_INoMarshal) {
+    // This entire library is designed around marshaling, so there's no point
+    // propagating this QI request all over the place!
+    return E_NOINTERFACE;
+  }
+
   return WeakReferenceSupport::QueryInterface(riid, ppv);
 }
 
 HRESULT
 Interceptor::ThreadSafeQueryInterface(REFIID aIid, IUnknown** aOutInterface)
 {
-  if (aIid == IID_INoMarshal) {
-    // This entire library is designed around marshaling, so there's no point
-    // propagating this QI request all over the place!
-    return E_NOINTERFACE;
-  }
+  if (aIid == IID_IStdMarshalInfo) {
+    detail::ReentrySentinel sentinel(this);
 
-  if (aIid == IID_IStdMarshalInfo) {
+    if (!sentinel.IsOutermost()) {
+      return E_NOINTERFACE;
+    }
+
     // Do not indicate that this interface is available unless we actually
     // support it. We'll check that by looking for a successful call to
     // IInterceptorSink::GetHandler()
     CLSID dummy;
     if (FAILED(mEventSink->GetHandler(WrapNotNull(&dummy)))) {
       return E_NOINTERFACE;
     }
 
--- a/js/src/jit/IonOptimizationLevels.cpp
+++ b/js/src/jit/IonOptimizationLevels.cpp
@@ -13,16 +13,19 @@
 using namespace js;
 using namespace js::jit;
 
 namespace js {
 namespace jit {
 
 OptimizationLevelInfo IonOptimizations;
 
+const uint32_t OptimizationInfo::CompilerWarmupThreshold = 1000;
+const uint32_t OptimizationInfo::CompilerSmallFunctionWarmupThreshold = CompilerWarmupThreshold;
+
 void
 OptimizationInfo::initNormalOptimizationInfo()
 {
     level_ = OptimizationLevel::Normal;
 
     autoTruncate_ = true;
     eaa_ = true;
     eagerSimdUnbox_ = true;
@@ -78,24 +81,22 @@ OptimizationInfo::initWasmOptimizationIn
 uint32_t
 OptimizationInfo::compilerWarmUpThreshold(JSScript* script, jsbytecode* pc) const
 {
     MOZ_ASSERT(pc == nullptr || pc == script->code() || JSOp(*pc) == JSOP_LOOPENTRY);
 
     if (pc == script->code())
         pc = nullptr;
 
-    uint32_t warmUpThreshold = compilerWarmUpThreshold_;
-    if (JitOptions.forcedDefaultIonWarmUpThreshold.isSome())
-        warmUpThreshold = JitOptions.forcedDefaultIonWarmUpThreshold.ref();
+    uint32_t warmUpThreshold = JitOptions.forcedDefaultIonWarmUpThreshold
+        .valueOr(compilerWarmUpThreshold_);
 
     if (JitOptions.isSmallFunction(script)) {
-        warmUpThreshold = compilerSmallFunctionWarmUpThreshold_;
-        if (JitOptions.forcedDefaultIonSmallFunctionWarmUpThreshold.isSome())
-            warmUpThreshold = JitOptions.forcedDefaultIonSmallFunctionWarmUpThreshold.ref();
+        warmUpThreshold = JitOptions.forcedDefaultIonSmallFunctionWarmUpThreshold
+            .valueOr(compilerSmallFunctionWarmUpThreshold_);
     }
 
     // If the script is too large to compile on the active thread, we can still
     // compile it off thread. In these cases, increase the warm-up counter
     // threshold to improve the compilation's type information and hopefully
     // avoid later recompilation.
 
     if (script->length() > MAX_ACTIVE_THREAD_SCRIPT_SIZE)
--- a/js/src/jit/IonOptimizationLevels.h
+++ b/js/src/jit/IonOptimizationLevels.h
@@ -127,24 +127,24 @@ class OptimizationInfo
     // Actually it is only needed to make sure we don't blow out the stack.
     uint32_t smallFunctionMaxInlineDepth_;
 
     // How many invocations or loop iterations are needed before functions
     // are compiled.
     uint32_t compilerWarmUpThreshold_;
 
     // Default compiler warmup threshold, unless it is overridden.
-    static const uint32_t CompilerWarmupThreshold = 1000;
+    static const uint32_t CompilerWarmupThreshold;
 
     // How many invocations or loop iterations are needed before small functions
     // are compiled.
     uint32_t compilerSmallFunctionWarmUpThreshold_;
 
     // Default small function compiler warmup threshold, unless it is overridden.
-    static const uint32_t CompilerSmallFunctionWarmupThreshold = CompilerWarmupThreshold;
+    static const uint32_t CompilerSmallFunctionWarmupThreshold;
 
     // How many invocations or loop iterations are needed before calls
     // are inlined, as a fraction of compilerWarmUpThreshold.
     double inliningWarmUpThresholdFactor_;
 
     // How many invocations or loop iterations are needed before a function
     // is hot enough to recompile the outerScript to inline that function,
     // as a multiplication of inliningWarmUpThreshold.
@@ -222,19 +222,18 @@ class OptimizationInfo
         return eliminateRedundantChecks_;
     }
 
     bool flowAliasAnalysisEnabled() const {
         return !JitOptions.disableFlowAA;
     }
 
     IonRegisterAllocator registerAllocator() const {
-        if (JitOptions.forcedRegisterAllocator.isSome())
-            return JitOptions.forcedRegisterAllocator.ref();
-        return registerAllocator_;
+        return JitOptions.forcedRegisterAllocator
+            .valueOr(registerAllocator_);
     }
 
     bool scalarReplacementEnabled() const {
         return scalarReplacement_ && !JitOptions.disableScalarReplacement;
     }
 
     uint32_t smallFunctionMaxInlineDepth() const {
         return smallFunctionMaxInlineDepth_;
@@ -260,19 +259,18 @@ class OptimizationInfo
         return inlineMaxTotalBytecodeLength_;
     }
 
     uint32_t inliningMaxCallerBytecodeLength() const {
         return inliningMaxCallerBytecodeLength_;
     }
 
     uint32_t inliningWarmUpThreshold() const {
-        uint32_t compilerWarmUpThreshold = compilerWarmUpThreshold_;
-        if (JitOptions.forcedDefaultIonWarmUpThreshold.isSome())
-            compilerWarmUpThreshold = JitOptions.forcedDefaultIonWarmUpThreshold.ref();
+        uint32_t compilerWarmUpThreshold = JitOptions.forcedDefaultIonWarmUpThreshold
+            .valueOr(compilerWarmUpThreshold_);
         return compilerWarmUpThreshold * inliningWarmUpThresholdFactor_;
     }
 
     uint32_t inliningRecompileThreshold() const {
         return inliningWarmUpThreshold() * inliningRecompileThresholdFactor_;
     }
 };
 
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -7277,19 +7277,18 @@ JS_GetGlobalJitCompilerOption(JSContext*
     MOZ_ASSERT(valueOut);
 #ifndef JS_CODEGEN_NONE
     JSRuntime* rt = cx->runtime();
     switch (opt) {
       case JSJITCOMPILER_BASELINE_WARMUP_TRIGGER:
         *valueOut = jit::JitOptions.baselineWarmUpThreshold;
         break;
       case JSJITCOMPILER_ION_WARMUP_TRIGGER:
-        *valueOut = jit::JitOptions.forcedDefaultIonWarmUpThreshold.isSome()
-                  ? jit::JitOptions.forcedDefaultIonWarmUpThreshold.ref()
-                  : jit::OptimizationInfo::CompilerWarmupThreshold;
+        *valueOut = jit::JitOptions.forcedDefaultIonWarmUpThreshold
+            .valueOr(jit::OptimizationInfo::CompilerWarmupThreshold);
         break;
       case JSJITCOMPILER_ION_FORCE_IC:
         *valueOut = jit::JitOptions.forceInlineCaches;
         break;
       case JSJITCOMPILER_ION_CHECK_RANGE_ANALYSIS:
         *valueOut = jit::JitOptions.checkRangeAnalysis;
         break;
       case JSJITCOMPILER_ION_ENABLE:
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -470,16 +470,30 @@ MayHaveAnimationOfProperty(EffectSet* ef
   if (aProperty == eCSSProperty_opacity &&
       !effects->MayHaveOpacityAnimation()) {
     return false;
   }
 
   return true;
 }
 
+static bool
+MayHaveAnimationOfProperty(const nsIFrame* aFrame, nsCSSPropertyID aProperty)
+{
+  switch (aProperty) {
+    case eCSSProperty_transform:
+      return aFrame->MayHaveTransformAnimation();
+    case eCSSProperty_opacity:
+      return aFrame->MayHaveOpacityAnimation();
+    default:
+      MOZ_ASSERT_UNREACHABLE("unexpected property");
+      return false;
+  }
+}
+
 bool
 nsLayoutUtils::HasAnimationOfProperty(EffectSet* aEffectSet,
                                       nsCSSPropertyID aProperty)
 {
   if (!aEffectSet || !MayHaveAnimationOfProperty(aEffectSet, aProperty)) {
     return false;
   }
 
@@ -491,17 +505,28 @@ nsLayoutUtils::HasAnimationOfProperty(Ef
     }
   );
 }
 
 bool
 nsLayoutUtils::HasAnimationOfProperty(const nsIFrame* aFrame,
                                       nsCSSPropertyID aProperty)
 {
-  return HasAnimationOfProperty(EffectSet::GetEffectSet(aFrame), aProperty);
+  if (!MayHaveAnimationOfProperty(aFrame, aProperty)) {
+    return false;
+  }
+
+  return HasMatchingAnimations(aFrame,
+    [&aProperty](KeyframeEffectReadOnly& aEffect)
+    {
+      return (aEffect.IsInEffect() || aEffect.IsCurrent()) &&
+             aEffect.HasAnimationOfProperty(aProperty);
+    }
+  );
+
 }
 
 bool
 nsLayoutUtils::HasEffectiveAnimation(const nsIFrame* aFrame,
                                      nsCSSPropertyID aProperty)
 {
   EffectSet* effects = EffectSet::GetEffectSet(aFrame);
   if (!effects || !MayHaveAnimationOfProperty(effects, aProperty)) {
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -664,16 +664,27 @@ nsFrame::Init(nsIContent*       aContent
                           NS_FRAME_IN_POPUP |
                           NS_FRAME_IS_NONDISPLAY));
 
     if (HasAnyStateBits(NS_FRAME_IN_POPUP) && TrackingVisibility()) {
       // Assume all frames in popups are visible.
       IncApproximateVisibleCount();
     }
   }
+  if (aPrevInFlow) {
+    mMayHaveOpacityAnimation = aPrevInFlow->MayHaveOpacityAnimation();
+    mMayHaveTransformAnimation = aPrevInFlow->MayHaveTransformAnimation();
+  } else if (mContent) {
+    EffectSet* effectSet = EffectSet::GetEffectSet(this);
+    if (effectSet) {
+      mMayHaveOpacityAnimation = effectSet->MayHaveOpacityAnimation();
+      mMayHaveTransformAnimation = effectSet->MayHaveTransformAnimation();
+    }
+  }
+
   const nsStyleDisplay *disp = StyleDisplay();
   if (disp->HasTransform(this) ||
       (IsFrameOfType(eSupportsCSSTransforms) &&
        nsLayoutUtils::HasAnimationOfProperty(this, eCSSProperty_transform))) {
     // The frame gets reconstructed if we toggle the -moz-transform
     // property, so we can set this bit here and then ignore it.
     AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED);
   }
@@ -1498,41 +1509,37 @@ nsIFrame::GetMarginRectRelativeToSelf() 
   nsMargin m = GetUsedMargin();
   m.ApplySkipSides(GetSkipSides());
   nsRect r(0, 0, mRect.width, mRect.height);
   r.Inflate(m);
   return r;
 }
 
 bool
-nsIFrame::IsTransformed(const nsStyleDisplay* aStyleDisplay,
-                        EffectSet* aEffectSet) const
-{
-  return IsCSSTransformed(aStyleDisplay, aEffectSet) ||
+nsIFrame::IsTransformed(const nsStyleDisplay* aStyleDisplay) const
+{
+  return IsCSSTransformed(aStyleDisplay) ||
          IsSVGTransformed();
 }
 
 bool
-nsIFrame::IsCSSTransformed(const nsStyleDisplay* aStyleDisplay,
-                           EffectSet* aEffectSet) const
+nsIFrame::IsCSSTransformed(const nsStyleDisplay* aStyleDisplay) const
 {
   MOZ_ASSERT(aStyleDisplay == StyleDisplay());
   return ((mState & NS_FRAME_MAY_BE_TRANSFORMED) &&
           (aStyleDisplay->HasTransform(this) ||
-           HasAnimationOfTransform(aEffectSet)));
+           HasAnimationOfTransform()));
 }
 
 bool
-nsIFrame::HasAnimationOfTransform(EffectSet* aEffectSet) const
-{
-  EffectSet* effects =
-    aEffectSet ? aEffectSet : EffectSet::GetEffectSet(this);
+nsIFrame::HasAnimationOfTransform() const
+{
 
   return IsPrimaryFrame() &&
-    nsLayoutUtils::HasAnimationOfProperty(effects, eCSSProperty_transform) &&
+    nsLayoutUtils::HasAnimationOfProperty(this, eCSSProperty_transform) &&
     IsFrameOfType(eSupportsCSSTransforms);
 }
 
 bool
 nsIFrame::HasOpacityInternal(float aThreshold,
                              EffectSet* aEffectSet) const
 {
   MOZ_ASSERT(0.0 <= aThreshold && aThreshold <= 1.0, "Invalid argument");
@@ -1580,43 +1587,42 @@ nsIFrame::Extend3DContext(const nsStyleD
 
   const nsStyleEffects* effects = StyleEffects();
   return !nsFrame::ShouldApplyOverflowClipping(this, disp) &&
          !GetClipPropClipRect(disp, effects, GetSize()) &&
          !nsSVGIntegrationUtils::UsingEffectsForFrame(this);
 }
 
 bool
-nsIFrame::Combines3DTransformWithAncestors(const nsStyleDisplay* aStyleDisplay,
-                                           EffectSet* aEffectSet) const
+nsIFrame::Combines3DTransformWithAncestors(const nsStyleDisplay* aStyleDisplay) const
 {
   MOZ_ASSERT(aStyleDisplay == StyleDisplay());
   nsIFrame* parent = GetFlattenedTreeParentPrimaryFrame();
   if (!parent || !parent->Extend3DContext()) {
     return false;
   }
-  return IsCSSTransformed(aStyleDisplay, aEffectSet) ||
+  return IsCSSTransformed(aStyleDisplay) ||
          BackfaceIsHidden(aStyleDisplay);
 }
 
 bool
-nsIFrame::In3DContextAndBackfaceIsHidden(EffectSet* aEffectSet) const
+nsIFrame::In3DContextAndBackfaceIsHidden() const
 {
   // While both tests fail most of the time, test BackfaceIsHidden()
   // first since it's likely to fail faster.
   const nsStyleDisplay* disp = StyleDisplay();
   return BackfaceIsHidden(disp) &&
-         Combines3DTransformWithAncestors(disp, aEffectSet);
+         Combines3DTransformWithAncestors(disp);
 }
 
 bool
-nsIFrame::HasPerspective(const nsStyleDisplay* aStyleDisplay, EffectSet* aEffectSet) const
+nsIFrame::HasPerspective(const nsStyleDisplay* aStyleDisplay) const
 {
   MOZ_ASSERT(aStyleDisplay == StyleDisplay());
-  if (!IsTransformed(aStyleDisplay, aEffectSet)) {
+  if (!IsTransformed(aStyleDisplay)) {
     return false;
   }
   nsIFrame* containingBlock = GetContainingBlock(SKIP_SCROLLED_FRAME, aStyleDisplay);
   if (!containingBlock) {
     return false;
   }
   return containingBlock->ChildrenHavePerspective();
 }
@@ -2746,18 +2752,18 @@ nsIFrame::BuildDisplayListForStackingCon
 
   // For preserves3d, use the dirty rect already installed on the
   // builder, since aDirtyRect maybe distorted for transforms along
   // the chain.
   nsRect visibleRect = aBuilder->GetVisibleRect();
   nsRect dirtyRect = aBuilder->GetDirtyRect();
 
   bool inTransform = aBuilder->IsInTransform();
-  bool isTransformed = IsTransformed(disp, effectSet);
-  bool hasPerspective = HasPerspective(effectSet);
+  bool isTransformed = IsTransformed(disp);
+  bool hasPerspective = HasPerspective(disp);
   // reset blend mode so we can keep track if this stacking context needs have
   // a nsDisplayBlendContainer. Set the blend mode back when the routine exits
   // so we keep track if the parent stacking context needs a container too.
   AutoSaveRestoreContainsBlendMode autoRestoreBlendMode(*aBuilder);
   aBuilder->SetContainsBlendMode(false);
 
   nsRect visibleRectOutsideTransform = visibleRect;
   bool allowAsyncAnimation = false;
@@ -9356,17 +9362,17 @@ nsIFrame::FinishAndStoreOverflow(nsOverf
                                  nsSize aNewSize, nsSize* aOldSize,
                                  const nsStyleDisplay* aStyleDisplay)
 {
   MOZ_ASSERT(FrameMaintainsOverflow(),
              "Don't call - overflow rects not maintained on these SVG frames");
 
   const nsStyleDisplay* disp = StyleDisplayWithOptionalParam(aStyleDisplay);
   EffectSet* effectSet = EffectSet::GetEffectSet(this);
-  bool hasTransform = IsTransformed(disp, effectSet);
+  bool hasTransform = IsTransformed(disp);
 
   nsRect bounds(nsPoint(0, 0), aNewSize);
   // Store the passed in overflow area if we are a preserve-3d frame or we have
   // a transform, and it's not just the frame bounds.
   if (hasTransform || Combines3DTransformWithAncestors(disp)) {
     if (!aOverflowAreas.VisualOverflow().IsEqualEdges(bounds) ||
         !aOverflowAreas.ScrollableOverflow().IsEqualEdges(bounds)) {
       nsOverflowAreas* initial =
@@ -9474,17 +9480,17 @@ nsIFrame::FinishAndStoreOverflow(nsOverf
       nsRect& o = aOverflowAreas.Overflow(otype);
       o.IntersectRect(o, *clipPropClipRect);
     }
   }
 
   /* If we're transformed, transform the overflow rect by the current transformation. */
   if (ChildrenHavePerspective(disp) && sizeChanged) {
     nsRect newBounds(nsPoint(0, 0), aNewSize);
-    RecomputePerspectiveChildrenOverflow(this, effectSet);
+    RecomputePerspectiveChildrenOverflow(this);
   }
 
   if (hasTransform) {
     SetProperty(nsIFrame::PreTransformOverflowAreasProperty(),
                 new nsOverflowAreas(aOverflowAreas));
 
     if (Combines3DTransformWithAncestors(disp)) {
       /* If we're a preserve-3d leaf frame, then our pre-transform overflow should be correct. Our
@@ -9524,28 +9530,27 @@ nsIFrame::FinishAndStoreOverflow(nsOverf
 
   if (anyOverflowChanged) {
     SVGObserverUtils::InvalidateDirectRenderingObservers(this);
   }
   return anyOverflowChanged;
 }
 
 void
-nsIFrame::RecomputePerspectiveChildrenOverflow(const nsIFrame* aStartFrame,
-                                               EffectSet* aEffectSet)
+nsIFrame::RecomputePerspectiveChildrenOverflow(const nsIFrame* aStartFrame)
 {
   nsIFrame::ChildListIterator lists(this);
   for (; !lists.IsDone(); lists.Next()) {
     nsFrameList::Enumerator childFrames(lists.CurrentList());
     for (; !childFrames.AtEnd(); childFrames.Next()) {
       nsIFrame* child = childFrames.get();
       if (!child->FrameMaintainsOverflow()) {
         continue; // frame does not maintain overflow rects
       }
-      if (child->HasPerspective(aEffectSet)) {
+      if (child->HasPerspective()) {
         nsOverflowAreas* overflow =
           child->GetProperty(nsIFrame::InitialOverflowProperty());
         nsRect bounds(nsPoint(0, 0), child->GetSize());
         if (overflow) {
           nsOverflowAreas overflowCopy = *overflow;
           child->FinishAndStoreOverflow(overflowCopy, bounds.Size());
         } else {
           nsOverflowAreas boundsOverflow;
@@ -9553,17 +9558,17 @@ nsIFrame::RecomputePerspectiveChildrenOv
           child->FinishAndStoreOverflow(boundsOverflow, bounds.Size());
         }
       } else if (child->GetContainingBlock(SKIP_SCROLLED_FRAME) == aStartFrame) {
         // If a frame is using perspective, then the size used to compute
         // perspective-origin is the size of the frame belonging to its parent
         // style context. We must find any descendant frames using our size
         // (by recursing into frames that have the same containing block)
         // to update their overflow rects too.
-        child->RecomputePerspectiveChildrenOverflow(aStartFrame, aEffectSet);
+        child->RecomputePerspectiveChildrenOverflow(aStartFrame);
       }
     }
   }
 }
 
 void
 nsIFrame::ComputePreserve3DChildrenOverflow(nsOverflowAreas& aOverflowAreas)
 {
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -628,16 +628,18 @@ public:
     , mReflowRequestedForCharDataChange(false)
     , mForceDescendIntoIfVisible(false)
     , mBuiltDisplayList(false)
     , mFrameIsModified(false)
     , mHasOverrideDirtyRegion(false)
     , mMayHaveWillChangeBudget(false)
     , mBuiltBlendContainer(false)
     , mIsPrimaryFrame(false)
+    , mMayHaveTransformAnimation(false)
+    , mMayHaveOpacityAnimation(false)
   {
     mozilla::PodZero(&mOverflow);
   }
 
   nsPresContext* PresContext() const {
     return StyleContext()->PresContext();
   }
 
@@ -1758,39 +1760,33 @@ public:
   /**
    * Returns true if this frame is transformed (e.g. has CSS or SVG transforms)
    * or if its parent is an SVG frame that has children-only transforms (e.g.
    * an SVG viewBox attribute) or if its transform-style is preserve-3d or
    * the frame has transform animations.
    *
    * @param aStyleDisplay:  If the caller has this->StyleDisplay(), providing
    *   it here will improve performance.
-   * @param aEffectSet: This function may need to look up EffectSet property.
-   *   If a caller already have one, pass it in can save property look up
-   *   time; otherwise, just left it as nullptr.
-   */
-  bool IsTransformed(const nsStyleDisplay* aStyleDisplay, mozilla::EffectSet* aEffectSet = nullptr) const;
-  bool IsTransformed(mozilla::EffectSet* aEffectSet = nullptr) const {
-    return IsTransformed(StyleDisplay(), aEffectSet);
+   */
+  bool IsTransformed(const nsStyleDisplay* aStyleDisplay) const;
+  bool IsTransformed() const {
+    return IsTransformed(StyleDisplay());
   }
 
   /**
    * Same as IsTransformed, except that it doesn't take SVG transforms
    * into account.
    */
-  bool IsCSSTransformed(const nsStyleDisplay* aStyleDisplay, mozilla::EffectSet* aEffectSet = nullptr) const;
+  bool IsCSSTransformed(const nsStyleDisplay* aStyleDisplay) const;
 
   /**
    * True if this frame has any animation of transform in effect.
    *
-   * @param aEffectSet: This function may need to look up EffectSet property.
-   *   If a caller already have one, pass it in can save property look up
-   *   time; otherwise, just left it as nullptr.
-   */
-  bool HasAnimationOfTransform(mozilla::EffectSet* aEffectSet = nullptr) const;
+   */
+  bool HasAnimationOfTransform() const;
 
   /**
    * Returns true if the frame is translucent or the frame has opacity
    * animations for the purposes of creating a stacking context.
    *
    * @param aEffectSet: This function may need to look up EffectSet property.
    *   If a caller already have one, pass it in can save property look up
    *   time; otherwise, just left it as nullptr.
@@ -1851,68 +1847,59 @@ public:
 
   /**
    * Returns whether this frame has a parent that Extend3DContext() and has
    * its own transform (or hidden backface) to be combined with the parent's
    * transform.
    *
    * @param aStyleDisplay:  If the caller has this->StyleDisplay(), providing
    *   it here will improve performance.
-   * @param aEffectSet: This function may need to look up EffectSet property.
-   *   If a caller already have one, pass it in can save property look up
-   *   time; otherwise, just left it as nullptr.
-   */
-  bool Combines3DTransformWithAncestors(const nsStyleDisplay* aStyleDisplay,
-                                        mozilla::EffectSet* aEffectSet = nullptr) const;
-  bool Combines3DTransformWithAncestors(mozilla::EffectSet* aEffectSet = nullptr) const {
-    return Combines3DTransformWithAncestors(StyleDisplay(), aEffectSet);
+   */
+  bool Combines3DTransformWithAncestors(const nsStyleDisplay* aStyleDisplay) const;
+  bool Combines3DTransformWithAncestors() const {
+    return Combines3DTransformWithAncestors(StyleDisplay());
   }
 
   /**
    * Returns whether this frame has a hidden backface and has a parent that
    * Extend3DContext(). This is useful because in some cases the hidden
    * backface can safely be ignored if it could not be visible anyway.
    *
-   * @param aEffectSet: This function may need to look up EffectSet property.
-   *   If a caller already have one, pass it in can save property look up
-   *   time; otherwise, just left it as nullptr.
-   */
-  bool In3DContextAndBackfaceIsHidden(mozilla::EffectSet* aEffectSet = nullptr) const;
+   */
+  bool In3DContextAndBackfaceIsHidden() const;
 
   bool IsPreserve3DLeaf(const nsStyleDisplay* aStyleDisplay,
                         mozilla::EffectSet* aEffectSet = nullptr) const {
     return Combines3DTransformWithAncestors(aStyleDisplay) &&
            !Extend3DContext(aStyleDisplay, aEffectSet);
   }
   bool IsPreserve3DLeaf(mozilla::EffectSet* aEffectSet = nullptr) const {
     return IsPreserve3DLeaf(StyleDisplay(), aEffectSet);
   }
 
-  bool HasPerspective(const nsStyleDisplay* aStyleDisplay,
-                      mozilla::EffectSet* aEffectSet = nullptr) const;
-  bool HasPerspective(mozilla::EffectSet* aEffectSet = nullptr) const {
-    return HasPerspective(StyleDisplay(), aEffectSet);
+  bool HasPerspective(const nsStyleDisplay* aStyleDisplay) const;
+  bool HasPerspective() const {
+    return HasPerspective(StyleDisplay());
   }
 
   bool ChildrenHavePerspective(const nsStyleDisplay* aStyleDisplay) const {
     MOZ_ASSERT(aStyleDisplay == StyleDisplay());
     return aStyleDisplay->HasPerspectiveStyle();
   }
   bool ChildrenHavePerspective() const {
     return ChildrenHavePerspective(StyleDisplay());
   }
 
   /**
    * Includes the overflow area of all descendants that participate in the current
    * 3d context into aOverflowAreas.
    */
   void ComputePreserve3DChildrenOverflow(nsOverflowAreas& aOverflowAreas);
 
-  void RecomputePerspectiveChildrenOverflow(const nsIFrame* aStartFrame,
-                                            mozilla::EffectSet* aEffectSet = nullptr);
+  void RecomputePerspectiveChildrenOverflow(const nsIFrame* aStartFrame);
 
   /**
    * Returns the number of ancestors between this and the root of our frame tree
    */
   uint32_t GetDepthInFrameTree() const;
 
   /**
    * Event handling of GUI events.
@@ -4093,16 +4080,29 @@ public:
    */
   bool IsWrapperAnonBoxNeedingRestyle() const {
     return mIsWrapperBoxNeedingRestyle;
   }
   void SetIsWrapperAnonBoxNeedingRestyle(bool aNeedsRestyle) {
     mIsWrapperBoxNeedingRestyle = aNeedsRestyle;
   }
 
+  bool MayHaveTransformAnimation() const {
+    return mMayHaveTransformAnimation;
+  }
+  void SetMayHaveTransformAnimation() {
+    mMayHaveTransformAnimation = true;
+  }
+  bool MayHaveOpacityAnimation() const {
+    return mMayHaveOpacityAnimation;
+  }
+  void SetMayHaveOpacityAnimation() {
+    mMayHaveOpacityAnimation = true;
+  }
+
   /**
    * If this returns true, the frame it's called on should get the
    * NS_FRAME_HAS_DIRTY_CHILDREN bit set on it by the caller; either directly
    * if it's already in reflow, or via calling FrameNeedsReflow() to schedule a
    * reflow.
    */
   virtual bool RenumberFrameAndDescendants(int32_t* aOrdinal,
                                            int32_t aDepth,
@@ -4340,19 +4340,22 @@ protected:
   bool mBuiltBlendContainer : 1;
 
 private:
   /**
    * True if this is the primary frame for mContent.
    */
   bool mIsPrimaryFrame : 1;
 
+  bool mMayHaveTransformAnimation : 1;
+  bool mMayHaveOpacityAnimation : 1;
+
 protected:
 
-  // There is a 3-bit gap left here.
+  // There is a 1-bit gap left here.
 
   // Helpers
   /**
    * Can we stop inside this frame when we're skipping non-rendered whitespace?
    * @param  aForward [in] Are we moving forward (or backward) in content order.
    * @param  aOffset [in/out] At what offset into the frame to start looking.
    *         on output - what offset was reached (whether or not we found a place to stop).
    * @return STOP: An appropriate offset was found within this frame,
--- a/media/audioipc/server/src/lib.rs
+++ b/media/audioipc/server/src/lib.rs
@@ -688,16 +688,17 @@ impl Server {
         Ok(())
     }
 
     pub fn poll(&mut self, poll: &mut mio::Poll) -> Result<()> {
         let mut events = mio::Events::with_capacity(16);
 
         match poll.poll(&mut events, None) {
             Ok(_) => {},
+            Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => return Ok(()),
             Err(e) => warn!("server poll error: {}", e),
         }
 
         for event in events.iter() {
             match event.token() {
                 SERVER => {
                     if let Err(e) = self.accept(poll) {
                         warn!("server accept error: {}", e);
--- a/modules/libpref/Preferences.cpp
+++ b/modules/libpref/Preferences.cpp
@@ -2,18 +2,16 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
 #include <ctype.h>
 #include <stdlib.h>
 #include <string.h>
-#include <string>
-#include <vector>
 
 #include "base/basictypes.h"
 #include "GeckoProfiler.h"
 #include "MainThreadUtils.h"
 #include "mozilla/ArenaAllocatorExtensions.h"
 #include "mozilla/ArenaAllocator.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Attributes.h"
@@ -29,16 +27,17 @@
 #include "mozilla/ScopeExit.h"
 #include "mozilla/Services.h"
 #include "mozilla/ServoStyleSet.h"
 #include "mozilla/SyncRunnable.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/UniquePtrExtensions.h"
 #include "mozilla/URLPreloader.h"
 #include "mozilla/Variant.h"
+#include "mozilla/Vector.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsAutoPtr.h"
 #include "nsCategoryManagerUtils.h"
 #include "nsClassHashtable.h"
 #include "nsCOMArray.h"
 #include "nsCOMPtr.h"
 #include "nsCRT.h"
 #include "nsDataHashtable.h"
@@ -100,40 +99,23 @@ using namespace mozilla;
         "ENSURE_MAIN_PROCESS: called %s on %s in a non-main process",          \
         func,                                                                  \
         pref);                                                                 \
       NS_ERROR(msg.get());                                                     \
       return NS_ERROR_NOT_AVAILABLE;                                           \
     }                                                                          \
   } while (0)
 
-#define ENSURE_MAIN_PROCESS_WITH_WARNING(func, pref)                           \
-  do {                                                                         \
-    if (MOZ_UNLIKELY(!XRE_IsParentProcess())) {                                \
-      nsPrintfCString msg(                                                     \
-        "ENSURE_MAIN_PROCESS: called %s on %s in a non-main process",          \
-        func,                                                                  \
-        pref);                                                                 \
-      NS_WARNING(msg.get());                                                   \
-      return NS_ERROR_NOT_AVAILABLE;                                           \
-    }                                                                          \
-  } while (0)
-
 #else // DEBUG
 
 #define ENSURE_MAIN_PROCESS(func, pref)                                        \
   if (MOZ_UNLIKELY(!XRE_IsParentProcess())) {                                  \
     return NS_ERROR_NOT_AVAILABLE;                                             \
   }
 
-#define ENSURE_MAIN_PROCESS_WITH_WARNING(func, pref)                           \
-  if (MOZ_UNLIKELY(!XRE_IsParentProcess())) {                                  \
-    return NS_ERROR_NOT_AVAILABLE;                                             \
-  }
-
 #endif // DEBUG
 
 //===========================================================================
 // The old low-level prefs API
 //===========================================================================
 
 struct PrefHashEntry;
 
@@ -753,32 +735,34 @@ static nsresult
 PREF_ClearAllUserPrefs()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!gHashTable) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
-  std::vector<std::string> prefStrings;
+  Vector<const char*> prefNames;
   for (auto iter = gHashTable->Iter(); !iter.Done(); iter.Next()) {
     auto pref = static_cast<PrefHashEntry*>(iter.Get());
 
     if (pref->mPrefFlags.HasUserValue()) {
-      prefStrings.push_back(std::string(pref->mKey));
+      if (!prefNames.append(pref->mKey)) {
+        return NS_ERROR_OUT_OF_MEMORY;
+      }
 
       pref->mPrefFlags.SetHasUserValue(false);
       if (!pref->mPrefFlags.HasDefault()) {
         iter.Remove();
       }
     }
   }
 
-  for (std::string& prefString : prefStrings) {
-    pref_DoCallback(prefString.c_str());
+  for (const char* prefName : prefNames) {
+    pref_DoCallback(prefName);
   }
 
   Preferences::HandleDirty();
   return NS_OK;
 }
 
 // Function that sets whether or not the preference is locked and therefore
 // cannot be changed.
@@ -901,24 +885,24 @@ public:
 #define WATCHING_PREF_RAII()
 
 #endif // DEBUG
 
 static PrefHashEntry*
 pref_HashTableLookup(const char* aKey)
 {
   MOZ_ASSERT(NS_IsMainThread() || mozilla::ServoStyleSet::IsInServoTraversal());
-  MOZ_ASSERT((!XRE_IsContentProcess() || gPhase != START),
+  MOZ_ASSERT((XRE_IsParentProcess() || gPhase != START),
              "pref access before commandline prefs set");
 
   // If you're hitting this assertion, you've added a pref access to start up.
   // Consider moving it later or add it to the whitelist in ContentPrefs.cpp
-  // and get review from a DOM peer
+  // and get review from a DOM peer.
 #ifdef DEBUG
-  if (XRE_IsContentProcess() && gPhase <= END_INIT_PREFS && !gWatchingPref &&
+  if (!XRE_IsParentProcess() && gPhase <= END_INIT_PREFS && !gWatchingPref &&
       !InInitArray(aKey)) {
     MOZ_CRASH_UNSAFE_PRINTF(
       "accessing non-init pref %s before the rest of the prefs are sent", aKey);
   }
 #endif
 
   return static_cast<PrefHashEntry*>(gHashTable->Search(aKey));
 }
@@ -2379,17 +2363,17 @@ nsPrefBranch::GetComplexValue(const char
                               const nsIID& aType,
                               void** aRetVal)
 {
   NS_ENSURE_ARG(aPrefName);
 
   nsresult rv;
   nsAutoCString utf8String;
 
-  // we have to do this one first because it's different than all the rest
+  // We have to do this one first because it's different to all the rest.
   if (aType.Equals(NS_GET_IID(nsIPrefLocalizedString))) {
     nsCOMPtr<nsIPrefLocalizedString> theString(
       do_CreateInstance(NS_PREFLOCALIZEDSTRING_CONTRACTID, &rv));
     if (NS_FAILED(rv)) {
       return rv;
     }
 
     const PrefName& pref = GetPrefName(aPrefName);
@@ -2428,38 +2412,32 @@ nsPrefBranch::GetComplexValue(const char
 
   // if we can't get the pref, there's no point in being here
   rv = GetCharPref(aPrefName, utf8String);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   if (aType.Equals(NS_GET_IID(nsIFile))) {
-    if (XRE_IsContentProcess()) {
-      NS_ERROR("cannot get nsIFile pref from content process");
-      return NS_ERROR_NOT_AVAILABLE;
-    }
+    ENSURE_MAIN_PROCESS("GetComplexValue(nsIFile)", aPrefName);
 
     nsCOMPtr<nsIFile> file(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
 
     if (NS_SUCCEEDED(rv)) {
       rv = file->SetPersistentDescriptor(utf8String);
       if (NS_SUCCEEDED(rv)) {
         file.forget(reinterpret_cast<nsIFile**>(aRetVal));
         return NS_OK;
       }
     }
     return rv;
   }
 
   if (aType.Equals(NS_GET_IID(nsIRelativeFilePref))) {
-    if (XRE_IsContentProcess()) {
-      NS_ERROR("cannot get nsIRelativeFilePref from content process");
-      return NS_ERROR_NOT_AVAILABLE;
-    }
+    ENSURE_MAIN_PROCESS("GetComplexValue(nsIRelativeFilePref)", aPrefName);
 
     nsACString::const_iterator keyBegin, strEnd;
     utf8String.BeginReading(keyBegin);
     utf8String.EndReading(strEnd);
 
     // The pref has the format: [fromKey]a/b/c
     if (*keyBegin++ != '[') {
       return NS_ERROR_FAILURE;
@@ -3094,19 +3072,19 @@ namespace mozilla {
 #define INITIAL_PREF_FILES 10
 
 static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID);
 
 void
 Preferences::HandleDirty()
 {
   if (!XRE_IsParentProcess()) {
-    // TODO: this should really assert because you can't set prefs in a
-    // content process. But so much code currently does this that we just
-    // ignore it for now.
+    // This path is hit a lot when setting up prefs for content processes. Just
+    // ignore it in that case, because content processes aren't responsible for
+    // saving prefs.
     return;
   }
 
   if (!gHashTable || !sPreferences) {
     return;
   }
 
   if (sPreferences->mProfileShutdown) {
@@ -3161,16 +3139,17 @@ static const char kPrefFileHeader[] =
   " * To make a manual change to preferences, you can visit the URL "
   "about:config"
   NS_LINEBREAK
   " */"
   NS_LINEBREAK
   NS_LINEBREAK;
 // clang-format on
 
+// Note: if sShutdown is true, sPreferences will be nullptr.
 StaticRefPtr<Preferences> Preferences::sPreferences;
 bool Preferences::sShutdown = false;
 
 // This globally enables or disables OMT pref writing, both sync and async.
 static int32_t sAllowOMTPrefWrite = -1;
 
 // Write the preference data to a file.
 class PreferencesWriter final
@@ -3562,17 +3541,17 @@ Preferences::GetInstanceForService()
 
   Result<Ok, const char*> res = pref_InitInitialObjects();
   if (res.isErr()) {
     sPreferences = nullptr;
     gCacheDataDesc = res.unwrapErr();
     return nullptr;
   }
 
-  if (XRE_IsContentProcess()) {
+  if (!XRE_IsParentProcess()) {
     MOZ_ASSERT(gInitPrefs);
     for (unsigned int i = 0; i < gInitPrefs->Length(); i++) {
       Preferences::SetPreference(gInitPrefs->ElementAt(i));
     }
     delete gInitPrefs;
     gInitPrefs = nullptr;
 
   } else {
@@ -3631,17 +3610,21 @@ Preferences::IsServiceAvailable()
   return !!sPreferences;
 }
 
 /* static */ bool
 Preferences::InitStaticMembers()
 {
   MOZ_ASSERT(NS_IsMainThread() || mozilla::ServoStyleSet::IsInServoTraversal());
 
-  if (!sShutdown && !sPreferences) {
+  if (MOZ_LIKELY(sPreferences)) {
+    return true;
+  }
+
+  if (!sShutdown) {
     MOZ_ASSERT(NS_IsMainThread());
     nsCOMPtr<nsIPrefService> prefService =
       do_GetService(NS_PREFSERVICE_CONTRACTID);
   }
 
   return sPreferences != nullptr;
 }
 
@@ -3781,52 +3764,43 @@ Preferences::Observe(nsISupports* aSubje
   }
 
   return rv;
 }
 
 NS_IMETHODIMP
 Preferences::ReadUserPrefsFromFile(nsIFile* aFile)
 {
-  if (MOZ_UNLIKELY(!XRE_IsParentProcess())) {
-    NS_ERROR("must load prefs from parent process");
-    return NS_ERROR_NOT_AVAILABLE;
-  }
+  ENSURE_MAIN_PROCESS("Preferences::ReadUserPrefsFromFile", "all prefs");
 
   if (!aFile) {
     NS_ERROR("ReadUserPrefsFromFile requires a parameter");
     return NS_ERROR_INVALID_ARG;
   }
 
   return openPrefFile(aFile);
 }
 
 NS_IMETHODIMP
 Preferences::ResetPrefs()
 {
-  if (MOZ_UNLIKELY(!XRE_IsParentProcess())) {
-    NS_ERROR("must reset prefs from parent process");
-    return NS_ERROR_NOT_AVAILABLE;
-  }
+  ENSURE_MAIN_PROCESS("Preferences::ResetPrefs", "all prefs");
 
   NotifyServiceObservers(NS_PREFSERVICE_RESET_TOPIC_ID);
 
   gHashTable->ClearAndPrepareForLength(PREF_HASHTABLE_INITIAL_LENGTH);
   gPrefNameArena.Clear();
 
   return pref_InitInitialObjects().isOk() ? NS_OK : NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 Preferences::ResetUserPrefs()
 {
-  if (MOZ_UNLIKELY(!XRE_IsParentProcess())) {
-    NS_ERROR("must reset user prefs from parent process");
-    return NS_ERROR_NOT_AVAILABLE;
-  }
+  ENSURE_MAIN_PROCESS("Preferences::ResetUserPrefs", "all prefs");
 
   PREF_ClearAllUserPrefs();
   return NS_OK;
 }
 
 bool
 Preferences::AllowOffMainThreadSave()
 {
@@ -4076,20 +4050,17 @@ Preferences::MakeBackupPrefFile(nsIFile*
   NS_ENSURE_SUCCESS(rv, rv);
 
   return rv;
 }
 
 nsresult
 Preferences::SavePrefFileInternal(nsIFile* aFile, SaveMethod aSaveMethod)
 {
-  if (MOZ_UNLIKELY(!XRE_IsParentProcess())) {
-    NS_ERROR("must save pref file from parent process");
-    return NS_ERROR_NOT_AVAILABLE;
-  }
+  ENSURE_MAIN_PROCESS("Preferences::SavePrefFileInternal", "all prefs");
 
   // We allow different behavior here when aFile argument is not null, but it
   // happens to be the same as the current file.  It is not clear that we
   // should, but it does give us a "force" save on the unmodified pref file
   // (see the original bug 160377 when we added this.)
 
   if (nullptr == aFile) {
     mSavePending = false;
@@ -4649,57 +4620,57 @@ Preferences::GetComplex(const char* aPre
 {
   NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
   return sPreferences->mRootBranch->GetComplexValue(aPref, aType, aResult);
 }
 
 /* static */ nsresult
 Preferences::SetCString(const char* aPref, const char* aValue)
 {
-  ENSURE_MAIN_PROCESS_WITH_WARNING("SetCString", aPref);
+  ENSURE_MAIN_PROCESS("SetCString", aPref);
   NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
   return PREF_SetCStringPref(aPref, nsDependentCString(aValue), false);
 }
 
 /* static */ nsresult
 Preferences::SetCString(const char* aPref, const nsACString& aValue)
 {
-  ENSURE_MAIN_PROCESS_WITH_WARNING("SetCString", aPref);
+  ENSURE_MAIN_PROCESS("SetCString", aPref);
   NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
   return PREF_SetCStringPref(aPref, aValue, false);
 }
 
 /* static */ nsresult
 Preferences::SetString(const char* aPref, const char16ptr_t aValue)
 {
-  ENSURE_MAIN_PROCESS_WITH_WARNING("SetString", aPref);
+  ENSURE_MAIN_PROCESS("SetString", aPref);
   NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
   return PREF_SetCStringPref(aPref, NS_ConvertUTF16toUTF8(aValue), false);
 }
 
 /* static */ nsresult
 Preferences::SetString(const char* aPref, const nsAString& aValue)
 {
-  ENSURE_MAIN_PROCESS_WITH_WARNING("SetString", aPref);
+  ENSURE_MAIN_PROCESS("SetString", aPref);
   NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
   return PREF_SetCStringPref(aPref, NS_ConvertUTF16toUTF8(aValue), false);
 }
 
 /* static */ nsresult
 Preferences::SetBool(const char* aPref, bool aValue)
 {
-  ENSURE_MAIN_PROCESS_WITH_WARNING("SetBool", aPref);
+  ENSURE_MAIN_PROCESS("SetBool", aPref);
   NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
   return PREF_SetBoolPref(aPref, aValue, false);
 }
 
 /* static */ nsresult
 Preferences::SetInt(const char* aPref, int32_t aValue)
 {
-  ENSURE_MAIN_PROCESS_WITH_WARNING("SetInt", aPref);
+  ENSURE_MAIN_PROCESS("SetInt", aPref);
   NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
   return PREF_SetIntPref(aPref, aValue, false);
 }
 
 /* static */ nsresult
 Preferences::SetFloat(const char* aPref, float aValue)
 {
   return SetCString(aPref, nsPrintfCString("%f", aValue).get());
@@ -4712,17 +4683,17 @@ Preferences::SetComplex(const char* aPre
 {
   NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
   return sPreferences->mRootBranch->SetComplexValue(aPref, aType, aValue);
 }
 
 /* static */ nsresult
 Preferences::ClearUser(const char* aPref)
 {
-  ENSURE_MAIN_PROCESS_WITH_WARNING("ClearUser", aPref);
+  ENSURE_MAIN_PROCESS("ClearUser", aPref);
   NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
   return PREF_ClearUserPref(aPref);
 }
 
 /* static */ bool
 Preferences::HasUserValue(const char* aPref)
 {
   NS_ENSURE_TRUE(InitStaticMembers(), false);
@@ -4754,17 +4725,18 @@ Preferences::AddWeakObserver(nsIObserver
   NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
   return sPreferences->mRootBranch->AddObserver(aPref, aObserver, true);
 }
 
 /* static */ nsresult
 Preferences::RemoveObserver(nsIObserver* aObserver, const char* aPref)
 {
   MOZ_ASSERT(aObserver);
-  if (!sPreferences && sShutdown) {
+  if (sShutdown) {
+    MOZ_ASSERT(!sPreferences);
     return NS_OK; // Observers have been released automatically.
   }
   NS_ENSURE_TRUE(sPreferences, NS_ERROR_NOT_AVAILABLE);
   return sPreferences->mRootBranch->RemoveObserver(aPref, aObserver);
 }
 
 /* static */ nsresult
 Preferences::AddStrongObservers(nsIObserver* aObserver, const char** aPrefs)
@@ -4787,17 +4759,18 @@ Preferences::AddWeakObservers(nsIObserve
   }
   return NS_OK;
 }
 
 /* static */ nsresult
 Preferences::RemoveObservers(nsIObserver* aObserver, const char** aPrefs)
 {
   MOZ_ASSERT(aObserver);
-  if (!sPreferences && sShutdown) {
+  if (sShutdown) {
+    MOZ_ASSERT(!sPreferences);
     return NS_OK; // Observers have been released automatically.
   }
   NS_ENSURE_TRUE(sPreferences, NS_ERROR_NOT_AVAILABLE);
 
   for (uint32_t i = 0; aPrefs[i]; i++) {
     nsresult rv = RemoveObserver(aObserver, aPrefs[i]);
     NS_ENSURE_SUCCESS(rv, rv);
   }
@@ -4850,17 +4823,18 @@ Preferences::RegisterCallbackAndCall(Pre
 
 /* static */ nsresult
 Preferences::UnregisterCallback(PrefChangedFunc aCallback,
                                 const char* aPref,
                                 void* aClosure,
                                 MatchKind aMatchKind)
 {
   MOZ_ASSERT(aCallback);
-  if (!sPreferences && sShutdown) {
+  if (sShutdown) {
+    MOZ_ASSERT(!sPreferences);
     return NS_OK; // Observers have been released automatically.
   }
   NS_ENSURE_TRUE(sPreferences, NS_ERROR_NOT_AVAILABLE);
 
   return PREF_UnregisterCallback(aPref, aCallback, aClosure, aMatchKind);
 }
 
 static void
@@ -5092,17 +5066,16 @@ Preferences::GetDefaultType(const char* 
            sPreferences->mDefaultRootBranch->GetPrefType(aPref, &result))
            ? result
            : nsIPrefBranch::PREF_INVALID;
 }
 
 } // namespace mozilla
 
 #undef ENSURE_MAIN_PROCESS
-#undef ENSURE_MAIN_PROCESS_WITH_WARNING
 
 //===========================================================================
 // Module and factory stuff
 //===========================================================================
 
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(Preferences,
                                          Preferences::GetInstanceForService)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrefLocalizedString, Init)
--- a/modules/libpref/Preferences.h
+++ b/modules/libpref/Preferences.h
@@ -284,23 +284,26 @@ public:
   template<MemoryOrdering Order>
   static nsresult AddAtomicUintVarCache(Atomic<uint32_t, Order>* aVariable,
                                         const char* aPref,
                                         uint32_t aDefault = 0);
   static nsresult AddFloatVarCache(float* aVariable,
                                    const char* aPref,
                                    float aDefault = 0.0f);
 
-  // Used to synchronise preferences between chrome and content processes.
+  // When a content process is created these methods are used to pass prefs in
+  // bulk from the parent process.
   static void GetPreferences(InfallibleTArray<PrefSetting>* aPrefs);
+  static void SetInitPreferences(nsTArray<PrefSetting>* aPrefs);
+
+  // When a pref is changed in the parent process, these methods are used to
+  // pass the update to content processes.
   static void GetPreference(PrefSetting* aPref);
   static void SetPreference(const PrefSetting& aPref);
 
-  static void SetInitPreferences(nsTArray<PrefSetting>* aPrefs);
-
 #ifdef DEBUG
   static void SetInitPhase(pref_initPhase phase);
   static pref_initPhase InitPhase();
 #endif
 
   static int64_t SizeOfIncludingThisAndOtherStuff(
     mozilla::MallocSizeOf aMallocSizeOf);
 
--- a/netwerk/srtp/src/moz.build
+++ b/netwerk/srtp/src/moz.build
@@ -47,17 +47,17 @@ for var in ('HAVE_STDLIB_H', 'HAVE_UINT8
             'HAVE_UINT32_T', 'HAVE_UINT64_T'):
     DEFINES[var] = 1
 
 # XXX while arm is not a CISC architecture, the code guarded by CPU_RISC makes
 # (at least) the AES ciphers fail their self-tests on ARM, so for now we're
 # falling back to the (presumably) slower-on-this-architecture but working
 # code path.  https://bugzilla.mozilla.org/show_bug.cgi?id=822380 has been filed
 # to make the right and more performant fix and push it back upstream.
-if CONFIG['CPU_ARCH'] in ('arm', 'x86', 'x86_64'):
+if CONFIG['CPU_ARCH'] in ('arm', 'x86', 'x86_64', 'mips', 'mips64'):
     DEFINES['CPU_CISC'] = 1
 else:
     # best guess
     DEFINES['CPU_RISC'] = 1
 
 if  CONFIG['CPU_ARCH'] in ('x86', 'x86_64'):
     DEFINES['HAVE_X86'] = True
 
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/minidump-analyzer/MinidumpAnalyzerUtils.h
@@ -0,0 +1,149 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef MinidumpAnalyzerUtils_h
+#define MinidumpAnalyzerUtils_h
+
+#ifdef XP_WIN
+#include <windows.h>
+#endif
+
+#include <vector>
+#include <map>
+#include <string>
+#include <algorithm>
+
+namespace CrashReporter {
+
+struct MinidumpAnalyzerOptions {
+  bool fullMinidump;
+  std::string forceUseModule;
+};
+
+extern MinidumpAnalyzerOptions gMinidumpAnalyzerOptions;
+
+#ifdef XP_WIN
+
+#if !defined(_MSC_VER)
+static inline std::string
+WideToMBCP(const std::wstring& wide, unsigned int cp, bool* success = nullptr)
+{
+  char* buffer = nullptr;
+  int buffer_size = WideCharToMultiByte(cp, 0, wide.c_str(),
+                                        -1, nullptr, 0, nullptr, nullptr);
+  if (buffer_size == 0) {
+    if (success) {
+      *success = false;
+    }
+
+    return "";
+  }
+
+  buffer = new char[buffer_size];
+  if (buffer == nullptr) {
+    if (success) {
+      *success = false;
+    }
+
+    return "";
+  }
+
+  WideCharToMultiByte(cp, 0, wide.c_str(),
+                      -1, buffer, buffer_size, nullptr, nullptr);
+  std::string mb = buffer;
+  delete [] buffer;
+
+  if (success) {
+    *success = true;
+  }
+
+  return mb;
+}
+#endif /* !defined(_MSC_VER) */
+
+static inline std::wstring
+UTF8ToWide(const std::string& aUtf8Str, bool *aSuccess = nullptr)
+{
+  wchar_t* buffer = nullptr;
+  int buffer_size = MultiByteToWideChar(CP_UTF8, 0, aUtf8Str.c_str(),
+                                        -1, nullptr, 0);
+  if (buffer_size == 0) {
+    if (aSuccess) {
+      *aSuccess = false;
+    }
+
+    return L"";
+  }
+
+  buffer = new wchar_t[buffer_size];
+
+  if (buffer == nullptr) {
+    if (aSuccess) {
+      *aSuccess = false;
+    }
+
+    return L"";
+  }
+
+  MultiByteToWideChar(CP_UTF8, 0, aUtf8Str.c_str(),
+                      -1, buffer, buffer_size);
+  std::wstring str = buffer;
+  delete [] buffer;
+
+  if (aSuccess) {
+    *aSuccess = true;
+  }
+
+  return str;
+}
+
+static inline std::string
+WideToMBCS(const std::wstring &inp) {
+  int buffer_size = WideCharToMultiByte(CP_ACP, 0, inp.c_str(), -1,
+                                        nullptr, 0, NULL, NULL);
+  if (buffer_size == 0) {
+    return "";
+  }
+
+  std::vector<char> buffer(buffer_size);
+  buffer[0] = 0;
+
+  WideCharToMultiByte(CP_ACP, 0, inp.c_str(), -1,
+                      buffer.data(), buffer_size, NULL, NULL);
+
+  return buffer.data();
+}
+
+static inline std::string
+UTF8toMBCS(const std::string &inp) {
+  std::wstring wide = UTF8ToWide(inp);
+  std::string ret = WideToMBCS(wide);
+  return ret;
+}
+
+#endif // XP_WIN
+
+// Check if a file exists at the specified path
+
+static inline bool
+FileExists(const std::string& aPath)
+{
+#if defined(XP_WIN)
+  DWORD attrs = GetFileAttributes(UTF8ToWide(aPath).c_str());
+  return (attrs != INVALID_FILE_ATTRIBUTES);
+#else // Non-Windows
+  struct stat sb;
+  int ret = stat(aPath.c_str(), &sb);
+  if (ret == -1 || !(sb.st_mode & S_IFREG)) {
+    return false;
+  }
+
+  return true;
+#endif // XP_WIN
+}
+
+} // namespace
+
+#endif // MinidumpAnalyzerUtils_h
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/minidump-analyzer/MozStackFrameSymbolizer.cpp
@@ -0,0 +1,120 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#if XP_WIN && HAVE_64BIT_BUILD
+
+#include "MozStackFrameSymbolizer.h"
+
+#include "MinidumpAnalyzerUtils.h"
+
+#include "processor/cfi_frame_info.h"
+
+#include <iostream>
+#include <sstream>
+#include <fstream>
+
+namespace CrashReporter {
+
+  extern MinidumpAnalyzerOptions gMinidumpAnalyzerOptions;
+
+  using google_breakpad::CFIFrameInfo;
+
+MozStackFrameSymbolizer::MozStackFrameSymbolizer() :
+  StackFrameSymbolizer(nullptr, nullptr)
+{
+}
+
+MozStackFrameSymbolizer::SymbolizerResult
+MozStackFrameSymbolizer::FillSourceLineInfo(const CodeModules* modules,
+                                            const SystemInfo* system_info,
+                                            StackFrame* stack_frame)
+{
+  SymbolizerResult ret = StackFrameSymbolizer::FillSourceLineInfo(
+    modules, system_info, stack_frame);
+
+  if (ret == kNoError && this->HasImplementation() &&
+    stack_frame->function_name.empty()) {
+    // Breakpad's Stackwalker::InstructionAddressSeemsValid only considers an
+    // address valid if it has associated symbols.
+    //
+    // This makes sense for complete & accurate symbols, but ours may be
+    // incomplete or wrong. Returning a function name tells Breakpad we
+    // recognize this address as code, so it's OK to use in stack scanning.
+    // This function is only called with addresses that land in this module.
+    //
+    // This allows us to fall back to stack scanning in the case where we were
+    // unable to provide CFI.
+    stack_frame->function_name = "<unknown code>";
+  }
+  return ret;
+}
+
+CFIFrameInfo*
+MozStackFrameSymbolizer::FindCFIFrameInfo(const StackFrame* frame)
+{
+  std::string modulePath;
+
+  // For unit testing, support loading a specified module instead of
+  // the real one.
+  bool moduleHasBeenReplaced = false;
+  if (gMinidumpAnalyzerOptions.forceUseModule.size() > 0) {
+    modulePath = gMinidumpAnalyzerOptions.forceUseModule;
+    moduleHasBeenReplaced = true;
+  } else {
+    if (!frame->module) {
+      return nullptr;
+    }
+    modulePath = frame->module->code_file();
+  }
+
+  // Get/create the unwind parser.
+  auto itMod = mModuleMap.find(modulePath);
+  std::shared_ptr<ModuleUnwindParser> unwindParser;
+  if (itMod != mModuleMap.end()) {
+    unwindParser = itMod->second;
+  } else {
+    unwindParser.reset(new ModuleUnwindParser(modulePath));
+    mModuleMap[modulePath] = unwindParser;
+  }
+
+  UnwindCFI cfi;
+  DWORD offsetAddr;
+
+  if (moduleHasBeenReplaced) {
+    // If we are replacing a module, addresses will never line up.
+    // So just act like the 1st entry is correct.
+    offsetAddr = unwindParser->GetAnyOffsetAddr();
+  } else {
+    offsetAddr = frame->instruction - frame->module->base_address();
+  }
+
+  if (!unwindParser->GetCFI(offsetAddr, cfi)) {
+    return nullptr;
+  }
+
+  std::unique_ptr<CFIFrameInfo> rules(new CFIFrameInfo());
+
+  static const size_t exprSize = 50;
+  char expr[exprSize];
+  if (cfi.stackSize == 0) {
+    snprintf(expr, exprSize, "$rsp");
+  } else {
+    snprintf(expr, exprSize, "$rsp %d +", cfi.stackSize);
+  }
+  rules->SetCFARule(expr);
+
+  if (cfi.ripOffset == 0) {
+    snprintf(expr, exprSize, ".cfa ^");
+  } else {
+    snprintf(expr, exprSize, ".cfa %d - ^", cfi.ripOffset);
+  }
+  rules->SetRARule(expr);
+
+  return rules.release();
+}
+
+} // namespace CrashReporter
+
+#endif // XP_WIN && HAVE_64BIT_BUILD
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/minidump-analyzer/MozStackFrameSymbolizer.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef MozStackFrameSymbolizer_h
+#define MozStackFrameSymbolizer_h
+
+#if XP_WIN && HAVE_64BIT_BUILD
+
+#include "Win64ModuleUnwindMetadata.h"
+
+#include "google_breakpad/processor/stack_frame_symbolizer.h"
+#include "google_breakpad/processor/stack_frame.h"
+
+#include <memory>
+
+namespace CrashReporter {
+
+using google_breakpad::CodeModule;
+using google_breakpad::CodeModules;
+using google_breakpad::SourceLineResolverInterface;
+using google_breakpad::StackFrame;
+using google_breakpad::StackFrameSymbolizer;
+using google_breakpad::SymbolSupplier;
+using google_breakpad::SystemInfo;
+
+class MozStackFrameSymbolizer : public StackFrameSymbolizer {
+  using google_breakpad::StackFrameSymbolizer::SymbolizerResult;
+
+  std::map<std::string, std::shared_ptr<ModuleUnwindParser>> mModuleMap;
+
+public:
+  MozStackFrameSymbolizer();
+
+  virtual SymbolizerResult FillSourceLineInfo(const CodeModules* modules,
+    const SystemInfo* system_info,
+    StackFrame* stack_frame);
+
+  virtual class google_breakpad::CFIFrameInfo* FindCFIFrameInfo(
+    const StackFrame* frame);
+};
+
+} // namespace CrashReporter
+
+#endif // XP_WIN && HAVE_64BIT_BUILD
+
+#endif // MozStackFrameSymbolizer_h
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.cpp
@@ -0,0 +1,265 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#if XP_WIN && HAVE_64BIT_BUILD
+
+#include "Win64ModuleUnwindMetadata.h"
+
+#include "MinidumpAnalyzerUtils.h"
+
+#include <windows.h>
+#include <winnt.h>
+#include <ImageHlp.h>
+#include <iostream>
+#include <sstream>
+#include <string>
+
+namespace CrashReporter {
+
+union UnwindCode {
+  struct {
+    uint8_t offset_in_prolog;
+    uint8_t unwind_operation_code : 4;
+    uint8_t operation_info        : 4;
+  };
+  USHORT frame_offset;
+};
+
+enum UnwindOperationCodes {
+  UWOP_PUSH_NONVOL = 0,     // info == register number
+  UWOP_ALLOC_LARGE = 1,     // no info, alloc size in next 2 slots
+  UWOP_ALLOC_SMALL = 2,     // info == size of allocation / 8 - 1
+  UWOP_SET_FPREG = 3,       // no info, FP = RSP + UNWIND_INFO.FPRegOffset*16
+  UWOP_SAVE_NONVOL = 4,     // info == register number, offset in next slot
+  UWOP_SAVE_NONVOL_FAR = 5, // info == register number, offset in next 2 slots
+  UWOP_SAVE_XMM = 6,        // Version 1; undocumented
+  UWOP_EPILOG = 6,          // Version 2; undocumented
+  UWOP_SAVE_XMM_FAR = 7,    // Version 1; undocumented
+  UWOP_SPARE = 7,           // Version 2; undocumented
+  UWOP_SAVE_XMM128 = 8,     // info == XMM reg number, offset in next slot
+  UWOP_SAVE_XMM128_FAR = 9, // info == XMM reg number, offset in next 2 slots
+  UWOP_PUSH_MACHFRAME = 10  // info == 0: no error-code, 1: error-code
+};
+
+struct UnwindInfo {
+  uint8_t version       : 3;
+  uint8_t flags         : 5;
+  uint8_t size_of_prolog;
+  uint8_t count_of_codes;
+  uint8_t frame_register : 4;
+  uint8_t frame_offset   : 4;
+  UnwindCode unwind_code[1];
+};
+
+ModuleUnwindParser::~ModuleUnwindParser()
+{
+  if (mImg) {
+    ImageUnload(mImg);
+  }
+}
+
+void*
+ModuleUnwindParser::RvaToVa(ULONG aRva)
+{
+  return ImageRvaToVa(
+    mImg->FileHeader, mImg->MappedAddress, aRva, &mImg->LastRvaSection);
+}
+
+ModuleUnwindParser::ModuleUnwindParser(const std::string& aPath)
+  : mPath(aPath)
+{
+  // Convert wchar to native charset because ImageLoad only takes
+  // a PSTR as input.
+  std::string code_file = UTF8toMBCS(aPath);
+
+  mImg = ImageLoad((PSTR)code_file.c_str(), NULL);
+  if (!mImg || !mImg->FileHeader) {
+    return;
+  }
+
+  PIMAGE_OPTIONAL_HEADER64 optional_header = &mImg->FileHeader->OptionalHeader;
+  if (optional_header->Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
+    return;
+  }
+
+  DWORD exception_rva = optional_header->
+    DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].VirtualAddress;
+
+  DWORD exception_size = optional_header->
+    DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].Size;
+
+  auto funcs = (PIMAGE_RUNTIME_FUNCTION_ENTRY)RvaToVa(exception_rva);
+  if (!funcs) {
+    return;
+  }
+
+  for (DWORD i = 0; i < exception_size / sizeof(*funcs); i++) {
+    mUnwindMap[funcs[i].BeginAddress] = &funcs[i];
+  }
+}
+
+bool
+ModuleUnwindParser::GenerateCFIForFunction(IMAGE_RUNTIME_FUNCTION_ENTRY& aFunc,
+                                           UnwindCFI& aRet)
+{
+  DWORD unwind_rva = aFunc.UnwindInfoAddress;
+  // Holds RVA to all visited IMAGE_RUNTIME_FUNCTION_ENTRY, to avoid
+  // circular references.
+  std::set<DWORD> visited;
+
+  // Follow chained function entries
+  while (unwind_rva & 0x1) {
+    unwind_rva ^= 0x1;
+
+    if (visited.end() != visited.find(unwind_rva)) {
+      return false;
+    }
+    visited.insert(unwind_rva);
+
+    auto chained_func = (PIMAGE_RUNTIME_FUNCTION_ENTRY)RvaToVa(unwind_rva);
+    if (!chained_func) {
+      return false;
+    }
+    unwind_rva = chained_func->UnwindInfoAddress;
+  }
+
+  visited.insert(unwind_rva);
+
+  auto unwind_info = (UnwindInfo*)RvaToVa(unwind_rva);
+  if (!unwind_info) {
+    return false;
+  }
+
+  DWORD stack_size = 8; // minimal stack size is 8 for RIP
+  DWORD rip_offset = 8;
+  do {
+    for (uint8_t c = 0; c < unwind_info->count_of_codes; c++) {
+      UnwindCode* unwind_code = &unwind_info->unwind_code[c];
+      switch (unwind_code->unwind_operation_code) {
+        case UWOP_PUSH_NONVOL: {
+          stack_size += 8;
+          break;
+        }
+        case UWOP_ALLOC_LARGE: {
+          if (unwind_code->operation_info == 0) {
+            c++;
+            if (c < unwind_info->count_of_codes) {
+              stack_size += (unwind_code + 1)->frame_offset * 8;
+            }
+          } else {
+            c += 2;
+            if (c < unwind_info->count_of_codes) {
+              stack_size += (unwind_code + 1)->frame_offset |
+                            ((unwind_code + 2)->frame_offset << 16);
+            }
+          }
+          break;
+        }
+        case UWOP_ALLOC_SMALL: {
+          stack_size += unwind_code->operation_info * 8 + 8;
+          break;
+        }
+        case UWOP_SET_FPREG:
+          // To correctly track RSP when it's been transferred to another
+          // register, we would need to emit CFI records for every unwind op.
+          // For simplicity, don't emit CFI records for this function as
+          // we know it will be incorrect after this point.
+          return false;
+        case UWOP_SAVE_NONVOL:
+        case UWOP_SAVE_XMM: // also v2 UWOP_EPILOG
+        case UWOP_SAVE_XMM128: {
+          c++; // skip slot with offset
+          break;
+        }
+        case UWOP_SAVE_NONVOL_FAR:
+        case UWOP_SAVE_XMM_FAR: // also v2 UWOP_SPARE
+        case UWOP_SAVE_XMM128_FAR: {
+          c += 2; // skip 2 slots with offset
+          break;
+        }
+        case UWOP_PUSH_MACHFRAME: {
+          if (unwind_code->operation_info) {
+            stack_size += 88;
+          } else {
+            stack_size += 80;
+          }
+          rip_offset += 80;
+          break;
+        }
+        default: {
+          return false;
+        }
+      }
+    }
+
+    if (unwind_info->flags & UNW_FLAG_CHAININFO) {
+      auto chained_func = (PIMAGE_RUNTIME_FUNCTION_ENTRY)(
+            (unwind_info->unwind_code +
+            ((unwind_info->count_of_codes + 1) & ~1)));
+
+      if (visited.end() != visited.find(chained_func->UnwindInfoAddress)) {
+        return false; // Circular reference
+      }
+
+      visited.insert(chained_func->UnwindInfoAddress);
+
+      unwind_info = (UnwindInfo*)RvaToVa(chained_func->UnwindInfoAddress);
+    } else {
+      unwind_info = nullptr;
+    }
+  } while (unwind_info);
+
+  aRet.beginAddress = aFunc.BeginAddress;
+  aRet.size = aFunc.EndAddress - aFunc.BeginAddress;
+  aRet.stackSize = stack_size;
+  aRet.ripOffset = rip_offset;
+  return true;
+}
+
+// For unit testing we sometimes need any address that's valid in this module.
+// Just return the first address we know of.
+DWORD
+ModuleUnwindParser::GetAnyOffsetAddr() const {
+  if (mUnwindMap.size() < 1) {
+    return 0;
+  }
+  return mUnwindMap.begin()->first;
+}
+
+bool
+ModuleUnwindParser::GetCFI(DWORD aAddress, UnwindCFI& aRet)
+{
+  // Figure out the begin address of the requested address.
+  auto itUW = mUnwindMap.lower_bound(aAddress + 1);
+  if (itUW == mUnwindMap.begin()) {
+   return false; // address before this module.
+  }
+  --itUW;
+
+  // Ensure that the function entry is big enough to contain this address.
+  IMAGE_RUNTIME_FUNCTION_ENTRY& func = *itUW->second;
+  if (aAddress > func.EndAddress) {
+    return false;
+  }
+
+  // Do we have CFI for this function already?
+  auto itCFI = mCFIMap.find(aAddress);
+  if (itCFI != mCFIMap.end()) {
+    aRet = itCFI->second;
+    return true;
+  }
+
+  // No, generate it.
+  if (!GenerateCFIForFunction(func, aRet)) {
+    return false;
+  }
+
+  mCFIMap[func.BeginAddress] = aRet;
+  return true;
+}
+
+} // namespace
+
+#endif // XP_WIN && HAVE_64BIT_BUILD
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/minidump-analyzer/Win64ModuleUnwindMetadata.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef Win64ModuleUnwindMetadata_h
+#define Win64ModuleUnwindMetadata_h
+
+#if XP_WIN && HAVE_64BIT_BUILD
+
+#include <functional>
+#include <map>
+#include <string>
+
+#include <windows.h>
+#include <winnt.h>
+#include <ImageHlp.h>
+
+namespace CrashReporter {
+
+struct UnwindCFI
+{
+  uint32_t beginAddress;
+  uint32_t size;
+  uint32_t stackSize;
+  uint32_t ripOffset;
+};
+
+// Does lazy-parsing of unwind info.
+class ModuleUnwindParser {
+  PLOADED_IMAGE mImg;
+  std::string mPath;
+
+  // Maps begin address to exception record.
+  // Populated upon construction.
+  std::map<DWORD, PIMAGE_RUNTIME_FUNCTION_ENTRY> mUnwindMap;
+
+  // Maps begin address to CFI.
+  // Populated as needed.
+  std::map<DWORD, UnwindCFI> mCFIMap;
+
+  bool GenerateCFIForFunction(IMAGE_RUNTIME_FUNCTION_ENTRY& aFunc,
+                              UnwindCFI& aRet);
+  void* RvaToVa(ULONG aRva);
+
+public:
+  explicit ModuleUnwindParser(const std::string& aPath);
+  ~ModuleUnwindParser();
+  bool GetCFI(DWORD aAddress, UnwindCFI& aRet);
+  DWORD GetAnyOffsetAddr() const;
+};
+
+} // namespace
+
+#endif // XP_WIN && HAVE_64BIT_BUILD
+
+#endif // Win64ModuleUnwindMetadata_h
--- a/toolkit/crashreporter/minidump-analyzer/minidump-analyzer.cpp
+++ b/toolkit/crashreporter/minidump-analyzer/minidump-analyzer.cpp
@@ -27,22 +27,21 @@
 #elif defined(XP_UNIX) || defined(XP_MACOSX)
 
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <unistd.h>
 
 #endif
 
-// Path of the minidump to be analyzed.
-static string gMinidumpPath;
+#include "MinidumpAnalyzerUtils.h"
 
-// When set to true print out the full minidump analysis, otherwise only
-// include the crashing thread in the output.
-static bool gFullMinidump = false;
+#if XP_WIN && HAVE_64BIT_BUILD
+#include "MozStackFrameSymbolizer.h"
+#endif
 
 namespace CrashReporter {
 
 using std::ios;
 using std::ios_base;
 using std::hex;
 using std::ofstream;
 using std::map;
@@ -57,87 +56,20 @@ using google_breakpad::CodeModule;
 using google_breakpad::CodeModules;
 using google_breakpad::Minidump;
 using google_breakpad::MinidumpProcessor;
 using google_breakpad::PathnameStripper;
 using google_breakpad::ProcessResult;
 using google_breakpad::ProcessState;
 using google_breakpad::StackFrame;
 
-#ifdef XP_WIN
-
-#if !defined(_MSC_VER)
-static string WideToMBCP(const wstring& wide,
-                         unsigned int cp,
-                         bool* success = nullptr)
-{
-  char* buffer = nullptr;
-  int buffer_size = WideCharToMultiByte(cp, 0, wide.c_str(),
-                                        -1, nullptr, 0, nullptr, nullptr);
-  if(buffer_size == 0) {
-    if (success)
-      *success = false;
-    return "";
-  }
-
-  buffer = new char[buffer_size];
-  if(buffer == nullptr) {
-    if (success)
-      *success = false;
-    return "";
-  }
-
-  WideCharToMultiByte(cp, 0, wide.c_str(),
-                      -1, buffer, buffer_size, nullptr, nullptr);
-  string mb = buffer;
-  delete [] buffer;
-
-  if (success)
-    *success = true;
-
-  return mb;
-}
-#endif /* !defined(_MSC_VER) */
+MinidumpAnalyzerOptions gMinidumpAnalyzerOptions;
 
-static wstring UTF8ToWide(const string& aUtf8Str, bool *aSuccess = nullptr)
-{
-  wchar_t* buffer = nullptr;
-  int buffer_size = MultiByteToWideChar(CP_UTF8, 0, aUtf8Str.c_str(),
-                                        -1, nullptr, 0);
-  if (buffer_size == 0) {
-    if (aSuccess) {
-      *aSuccess = false;
-    }
-
-    return L"";
-  }
-
-  buffer = new wchar_t[buffer_size];
-
-  if (buffer == nullptr) {
-    if (aSuccess) {
-      *aSuccess = false;
-    }
-
-    return L"";
-  }
-
-  MultiByteToWideChar(CP_UTF8, 0, aUtf8Str.c_str(),
-                      -1, buffer, buffer_size);
-  wstring str = buffer;
-  delete [] buffer;
-
-  if (aSuccess) {
-    *aSuccess = true;
-  }
-
-  return str;
-}
-
-#endif
+// Path of the minidump to be analyzed.
+static string gMinidumpPath;
 
 struct ModuleCompare {
   bool operator() (const CodeModule* aLhs, const CodeModule* aRhs) const {
     return aLhs->base_address() < aRhs->base_address();
   }
 };
 
 typedef map<const CodeModule*, unsigned int, ModuleCompare> OrderedModulesMap;
@@ -312,17 +244,18 @@ ConvertProcessStateToJSON(const ProcessS
   if (aProcessState.crashed()) {
     crashInfo["type"] = aProcessState.crash_reason();
     crashInfo["address"] = ToHex(aProcessState.crash_address());
 
     if (requestingThread != -1) {
       // Record the crashing thread index only if this is a full minidump
       // and all threads' stacks are present, otherwise only the crashing
       // thread stack is written out and this field is set to 0.
-      crashInfo["crashing_thread"] = gFullMinidump ? requestingThread : 0;
+      crashInfo["crashing_thread"] =
+        gMinidumpAnalyzerOptions.fullMinidump ? requestingThread : 0;
     }
   } else {
     crashInfo["type"] = Json::Value(Json::nullValue);
     // Add assertion info, if available
     string assertion = aProcessState.assertion();
 
     if (!assertion.empty()) {
       crashInfo["assertion"] = assertion;
@@ -340,17 +273,17 @@ ConvertProcessStateToJSON(const ProcessS
   }
 
   aRoot["modules"] = modules;
 
   // Threads
   Json::Value threads(Json::arrayValue);
   int threadCount = aProcessState.threads()->size();
 
-  if (!gFullMinidump && (requestingThread != -1)) {
+  if (!gMinidumpAnalyzerOptions.fullMinidump && (requestingThread != -1)) {
     // Only add the crashing thread
     Json::Value thread;
     Json::Value stack(Json::arrayValue);
     const CallStack* rawStack = aProcessState.threads()->at(requestingThread);
 
     ConvertStackToJSON(aProcessState, orderedModules, rawStack, stack);
     thread["frames"] = stack;
     threads.append(thread);
@@ -369,19 +302,24 @@ ConvertProcessStateToJSON(const ProcessS
   aRoot["threads"] = threads;
 }
 
 // Process the minidump file and append the JSON-formatted stack traces to
 // the node specified in |aRoot|
 
 static bool
 ProcessMinidump(Json::Value& aRoot, const string& aDumpFile) {
+#if XP_WIN && HAVE_64BIT_BUILD
+  MozStackFrameSymbolizer symbolizer;
+  MinidumpProcessor minidumpProcessor(&symbolizer, false);
+#else
   BasicSourceLineResolver resolver;
   // We don't have a valid symbol resolver so we pass nullptr instead.
   MinidumpProcessor minidumpProcessor(nullptr, &resolver);
+#endif
 
   // Process the minidump.
   Minidump dump(aDumpFile);
   if (!dump.Read()) {
     return false;
   }
 
   ProcessResult rv;
@@ -410,35 +348,16 @@ OpenAppend(const string& aFilename)
     new ofstream(WideToMBCP(UTF8ToWide(aFilename), CP_ACP).c_str(), mode);
 #endif  // _MSC_VER
 #else // Non-Windows
   ofstream* file = new ofstream(aFilename.c_str(), mode);
 #endif // XP_WIN
   return file;
 }
 
-// Check if a file exists at the specified path
-
-static bool
-FileExists(const string& aPath)
-{
-#if defined(XP_WIN)
-  DWORD attrs = GetFileAttributes(UTF8ToWide(aPath).c_str());
-  return (attrs != INVALID_FILE_ATTRIBUTES);
-#else // Non-Windows
-  struct stat sb;
-  int ret = stat(aPath.c_str(), &sb);
-  if (ret == -1 || !(sb.st_mode & S_IFREG)) {
-    return false;
-  }
-
-  return true;
-#endif // XP_WIN
-}
-
 // Update the extra data file by adding the StackTraces field holding the
 // JSON output of this program.
 
 static void
 UpdateExtraDataFile(const string &aDumpPath, const Json::Value& aRoot)
 {
   string extraDataPath(aDumpPath);
   int dot = extraDataPath.rfind('.');
@@ -468,17 +387,20 @@ using namespace CrashReporter;
 static void
 ParseArguments(int argc, char** argv) {
   if (argc <= 1) {
     exit(EXIT_FAILURE);
   }
 
   for (int i = 1; i < argc - 1; i++) {
     if (strcmp(argv[i], "--full") == 0) {
-      gFullMinidump = true;
+      gMinidumpAnalyzerOptions.fullMinidump = true;
+    } else if ((strcmp(argv[i], "--force-use-module") == 0) && (i < argc - 2)) {
+      gMinidumpAnalyzerOptions.forceUseModule = argv[i + 1];
+      ++i;
     } else {
       exit(EXIT_FAILURE);
     }
   }
 
   gMinidumpPath = argv[argc - 1];
 }
 
--- a/toolkit/crashreporter/minidump-analyzer/moz.build
+++ b/toolkit/crashreporter/minidump-analyzer/moz.build
@@ -22,13 +22,25 @@ if CONFIG['OS_TARGET'] != 'Android':
     LOCAL_INCLUDES += [
         '/toolkit/components/jsoncpp/include',
     ]
 
 
     if CONFIG['OS_TARGET'] == 'Darwin':
         DIST_SUBDIR = 'crashreporter.app/Contents/MacOS'
 
+if CONFIG['OS_TARGET'] == 'WINNT' and CONFIG['CPU_ARCH'] == 'x86_64':
+    UNIFIED_SOURCES += [
+        'MozStackFrameSymbolizer.cpp',
+        'Win64ModuleUnwindMetadata.cpp',
+    ]
+
+    OS_LIBS += [
+        'Dbghelp',
+        'Imagehlp'
+    ]
+
+
 # Don't use the STL wrappers in the crashreporter clients; they don't
 # link with -lmozalloc, and it really doesn't matter here anyway.
 DisableStlWrapping()
 
 include('/toolkit/crashreporter/crashreporter.mozbuild')
--- a/toolkit/crashreporter/test/CrashTestUtils.jsm
+++ b/toolkit/crashreporter/test/CrashTestUtils.jsm
@@ -3,26 +3,39 @@
 
 this.EXPORTED_SYMBOLS = ["CrashTestUtils"];
 
 this.CrashTestUtils = {
   // These will be defined using ctypes APIs below.
   crash: null,
   dumpHasStream: null,
   dumpHasInstructionPointerMemory: null,
+  dumpWin64CFITestSymbols: null,
 
   // Constants for crash()
   // Keep these in sync with nsTestCrasher.cpp!
   CRASH_INVALID_POINTER_DEREF: 0,
   CRASH_PURE_VIRTUAL_CALL:     1,
   CRASH_RUNTIMEABORT:          2,
   CRASH_OOM:                   3,
   CRASH_MOZ_CRASH:             4,
   CRASH_ABORT:                 5,
   CRASH_UNCAUGHT_EXCEPTION:    6,
+  CRASH_X64CFI_NO_MANS_LAND:   7,
+  CRASH_X64CFI_LAUNCHER:       8,
+  CRASH_X64CFI_UNKNOWN_OPCODE: 9,
+  CRASH_X64CFI_PUSH_NONVOL:    10,
+  CRASH_X64CFI_ALLOC_SMALL:    11,
+  CRASH_X64CFI_ALLOC_LARGE:    12,
+  CRASH_X64CFI_SAVE_NONVOL:    15,
+  CRASH_X64CFI_SAVE_NONVOL_FAR: 16,
+  CRASH_X64CFI_SAVE_XMM128:    17,
+  CRASH_X64CFI_SAVE_XMM128_FAR: 18,
+  CRASH_X64CFI_EPILOG:         19,
+  CRASH_X64CFI_EOF:            20,
 
   // Constants for dumpHasStream()
   // From google_breakpad/common/minidump_format.h
   MD_THREAD_LIST_STREAM:       3,
   MD_MEMORY_INFO_LIST_STREAM:  16
 };
 
 // Grab APIs from the testcrasher shared library
@@ -58,8 +71,14 @@ CrashTestUtils.dumpHasInstructionPointer
               ctypes.default_abi,
               ctypes.bool,
               ctypes.char.ptr);
 
 CrashTestUtils.dumpCheckMemory = lib.declare("DumpCheckMemory",
                                              ctypes.default_abi,
                                              ctypes.bool,
                                              ctypes.char.ptr);
+
+CrashTestUtils.getWin64CFITestFnAddrOffset =
+  lib.declare("GetWin64CFITestFnAddrOffset",
+    ctypes.default_abi,
+    ctypes.int32_t,
+    ctypes.int16_t);
--- a/toolkit/crashreporter/test/moz.build
+++ b/toolkit/crashreporter/test/moz.build
@@ -19,16 +19,21 @@ UNIFIED_SOURCES += [
     'dumputils.cpp',
     'nsTestCrasher.cpp',
 ]
 
 SOURCES += [
   'ExceptionThrower.cpp',
 ]
 
+if CONFIG['OS_TARGET'] == 'WINNT' and CONFIG['CPU_ARCH'] == 'x86_64':
+    SOURCES += [
+        'win64UnwindInfoTests.asm',
+    ]
+
 if CONFIG['CLANG_CL']:
     SOURCES['ExceptionThrower.cpp'].flags += [
         '-Xclang',
         '-fcxx-exceptions',
     ]
 elif not CONFIG['_MSC_VER']:
     SOURCES['ExceptionThrower.cpp'].flags += [
         '-fexceptions',
--- a/toolkit/crashreporter/test/nsTestCrasher.cpp
+++ b/toolkit/crashreporter/test/nsTestCrasher.cpp
@@ -2,16 +2,17 @@
 
 #include <stdio.h>
 
 #include "nscore.h"
 #include "mozilla/Unused.h"
 #include "ExceptionThrower.h"
 
 #ifdef XP_WIN
+#include <malloc.h>
 #include <windows.h>
 #endif
 
 /*
  * This pure virtual call example is from MSDN
  */
 class A;
 
@@ -38,23 +39,90 @@ void fcn( A* p )
 
 void PureVirtualCall()
 {
   // generates a pure virtual function call
   B b;
   b.use(); // make sure b's actually used
 }
 
+extern "C" {
+#if XP_WIN && HAVE_64BIT_BUILD
+  // Implementation in win64unwindInfoTests.asm
+  uint64_t x64CrashCFITest_NO_MANS_LAND(uint64_t returnpfn, void*);
+  uint64_t x64CrashCFITest_Launcher(uint64_t returnpfn, void* testProc);
+  uint64_t x64CrashCFITest_UnknownOpcode(uint64_t returnpfn, void*);
+  uint64_t x64CrashCFITest_PUSH_NONVOL(uint64_t returnpfn, void*);
+  uint64_t x64CrashCFITest_ALLOC_SMALL(uint64_t returnpfn, void*);
+  uint64_t x64CrashCFITest_ALLOC_LARGE(uint64_t returnpfn, void*);
+  uint64_t x64CrashCFITest_SAVE_NONVOL(uint64_t returnpfn, void*);
+  uint64_t x64CrashCFITest_SAVE_NONVOL_FAR(uint64_t returnpfn, void*);
+  uint64_t x64CrashCFITest_SAVE_XMM128(uint64_t returnpfn, void*);
+  uint64_t x64CrashCFITest_SAVE_XMM128_FAR(uint64_t returnpfn, void*);
+  uint64_t x64CrashCFITest_EPILOG(uint64_t returnpfn, void*);
+  uint64_t x64CrashCFITest_EOF(uint64_t returnpfn, void*);
+#endif // XP_WIN && HAVE_64BIT_BUILD
+}
+
 // Keep these in sync with CrashTestUtils.jsm!
 const int16_t CRASH_INVALID_POINTER_DEREF = 0;
 const int16_t CRASH_PURE_VIRTUAL_CALL     = 1;
 const int16_t CRASH_OOM                   = 3;
 const int16_t CRASH_MOZ_CRASH             = 4;
 const int16_t CRASH_ABORT                 = 5;
 const int16_t CRASH_UNCAUGHT_EXCEPTION    = 6;
+const int16_t CRASH_X64CFI_NO_MANS_LAND   = 7;
+const int16_t CRASH_X64CFI_LAUNCHER       = 8;
+const int16_t CRASH_X64CFI_UNKNOWN_OPCODE = 9;
+const int16_t CRASH_X64CFI_PUSH_NONVOL    = 10;
+const int16_t CRASH_X64CFI_ALLOC_SMALL    = 11;
+const int16_t CRASH_X64CFI_ALLOC_LARGE    = 12;
+const int16_t CRASH_X64CFI_SAVE_NONVOL    = 15;
+const int16_t CRASH_X64CFI_SAVE_NONVOL_FAR = 16;
+const int16_t CRASH_X64CFI_SAVE_XMM128    = 17;
+const int16_t CRASH_X64CFI_SAVE_XMM128_FAR = 18;
+const int16_t CRASH_X64CFI_EPILOG         = 19;
+const int16_t CRASH_X64CFI_EOF            = 20;
+
+#if XP_WIN && HAVE_64BIT_BUILD
+
+typedef decltype(&x64CrashCFITest_UnknownOpcode) win64CFITestFnPtr_t;
+
+static std::map<int16_t, win64CFITestFnPtr_t>
+GetWin64CFITestMap() {
+  std::map<int16_t, win64CFITestFnPtr_t> ret = {
+    { CRASH_X64CFI_NO_MANS_LAND, x64CrashCFITest_NO_MANS_LAND},
+    { CRASH_X64CFI_LAUNCHER, x64CrashCFITest_Launcher},
+    { CRASH_X64CFI_UNKNOWN_OPCODE, x64CrashCFITest_UnknownOpcode},
+    { CRASH_X64CFI_PUSH_NONVOL, x64CrashCFITest_PUSH_NONVOL},
+    { CRASH_X64CFI_ALLOC_SMALL, x64CrashCFITest_ALLOC_SMALL },
+    { CRASH_X64CFI_ALLOC_LARGE, x64CrashCFITest_ALLOC_LARGE },
+    { CRASH_X64CFI_SAVE_NONVOL, x64CrashCFITest_SAVE_NONVOL },
+    { CRASH_X64CFI_SAVE_NONVOL_FAR, x64CrashCFITest_SAVE_NONVOL_FAR },
+    { CRASH_X64CFI_SAVE_XMM128, x64CrashCFITest_SAVE_XMM128 },
+    { CRASH_X64CFI_SAVE_XMM128_FAR, x64CrashCFITest_SAVE_XMM128_FAR },
+    { CRASH_X64CFI_EPILOG, x64CrashCFITest_EPILOG },
+    { CRASH_X64CFI_EOF, x64CrashCFITest_EOF }
+  };
+  // ret values point to jump table entries, not the actual function bodies.
+  // Get the correct pointer by calling the function with returnpfn=1
+  for (auto it = ret.begin(); it != ret.end(); ++ it) {
+    it->second = (win64CFITestFnPtr_t)it->second(1, nullptr);
+  }
+  return ret;
+}
+
+void ReserveStack() {
+  // This ensures our tests have enough reserved stack space.
+  uint8_t* p = (uint8_t*)alloca(1024000);
+  // This ensures we don't optimized away this meaningless code at build time.
+  mozilla::Unused << (int)(uint64_t)p;
+}
+
+#endif // XP_WIN && HAVE_64BIT_BUILD
 
 extern "C" NS_EXPORT
 void Crash(int16_t how)
 {
   switch (how) {
   case CRASH_INVALID_POINTER_DEREF: {
     volatile int* foo = (int*)0x42;
     *foo = 0;
@@ -79,16 +147,37 @@ void Crash(int16_t how)
   case CRASH_ABORT: {
     abort();
     break;
   }
   case CRASH_UNCAUGHT_EXCEPTION: {
     ThrowException();
     break;
   }
+#if XP_WIN && HAVE_64BIT_BUILD
+  case CRASH_X64CFI_UNKNOWN_OPCODE:
+  case CRASH_X64CFI_PUSH_NONVOL:
+  case CRASH_X64CFI_ALLOC_SMALL:
+  case CRASH_X64CFI_ALLOC_LARGE:
+  case CRASH_X64CFI_SAVE_NONVOL:
+  case CRASH_X64CFI_SAVE_NONVOL_FAR:
+  case CRASH_X64CFI_SAVE_XMM128:
+  case CRASH_X64CFI_SAVE_XMM128_FAR:
+  case CRASH_X64CFI_EPILOG: {
+    ReserveStack();
+    auto m = GetWin64CFITestMap();
+    if (m.find(how) == m.end()) {
+      break;
+    }
+    auto pfnTest = m[how];
+    auto pfnLauncher = m[CRASH_X64CFI_LAUNCHER];
+    pfnLauncher(0, pfnTest);
+    break;
+  }
+#endif // XP_WIN && HAVE_64BIT_BUILD
   default:
     break;
   }
 }
 
 char testData[32];
 
 extern "C" NS_EXPORT
@@ -114,8 +203,25 @@ static LONG WINAPI HandleException(EXCEP
 }
 
 extern "C" NS_EXPORT
 void TryOverrideExceptionHandler()
 {
   SetUnhandledExceptionFilter(HandleException);
 }
 #endif
+
+extern "C" NS_EXPORT uint32_t
+GetWin64CFITestFnAddrOffset(int16_t fnid) {
+#if XP_WIN && HAVE_64BIT_BUILD
+  // fnid uses the same constants as Crash().
+  // Returns the RVA of the requested function.
+  // Returns 0 on failure.
+  auto m = GetWin64CFITestMap();
+  if (m.find(fnid) == m.end()) {
+    return 0;
+  }
+  uint64_t moduleBase = (uint64_t)GetModuleHandleW(L"testcrasher.dll");
+  return ((uint64_t)m[fnid]) - moduleBase;
+#else
+  return 0;
+#endif // XP_WIN && HAVE_64BIT_BUILD
+}
--- a/toolkit/crashreporter/test/unit/head_crashreporter.js
+++ b/toolkit/crashreporter/test/unit/head_crashreporter.js
@@ -1,13 +1,14 @@
-var {utils: Cu} = Components;
+var {utils: Cu, classes: Cc, interfaces: Ci} = Components;
 
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/Services.jsm", this);
 Cu.import("resource://testing-common/AppData.jsm", this);
+Cu.import("resource://gre/modules/AppConstants.jsm");
 
 function getEventDir() {
   return OS.Path.join(do_get_tempdir().path, "crash-events");
 }
 
 /*
  * Run an xpcshell subprocess and crash it.
  *
@@ -19,25 +20,27 @@ function getEventDir() {
  *        This code will be evaluted between crasher_subprocess_head.js
  *        and crasher_subprocess_tail.js, so it will have access
  *        to everything defined in crasher_subprocess_head.js,
  *        which includes "crashReporter", a variable holding
  *        the crash reporter service.
  *
  * @param callback
  *        A JavaScript function to be called after the subprocess
- *        crashes. It will be passed (minidump, extra), where
- *         minidump is an nsIFile of the minidump file produced,
- *         and extra is an object containing the key,value pairs from
- *         the .extra file.
+ *        crashes. It will be passed (minidump, extra, extrafile), where
+ *         - minidump is an nsIFile of the minidump file produced,
+ *         - extra is an object containing the key,value pairs from
+ *           the .extra file.
+ *         - extrafile is an nsIFile of the extra file
  *
  * @param canReturnZero
  *       If true, the subprocess may return with a zero exit code.
  *       Certain types of crashes may not cause the process to
  *       exit with an error.
+ *
  */
 function do_crash(setup, callback, canReturnZero) {
   // get current process filename (xpcshell)
   let ds = Components.classes["@mozilla.org/file/directory_service;1"]
     .getService(Components.interfaces.nsIProperties);
   let bin = ds.get("XREExeF", Components.interfaces.nsIFile);
   if (!bin.exists()) {
     // weird, can't find xpcshell binary?
@@ -95,16 +98,42 @@ function getMinidump() {
     if (f.leafName.substr(-4) == ".dmp") {
       return f;
     }
   }
 
   return null;
 }
 
+function runMinidumpAnalyzer(dumpFile, additionalArgs) {
+  if (AppConstants.platform !== "win") {
+    return;
+  }
+
+  // find minidump-analyzer executable.
+  let ds = Cc["@mozilla.org/file/directory_service;1"]
+             .getService(Ci.nsIProperties);
+  let bin = ds.get("XREExeF", Ci.nsIFile);
+  ok(bin && bin.exists());
+  bin = bin.parent;
+  ok(bin && bin.exists());
+  bin.append("minidump-analyzer.exe");
+  ok(bin.exists());
+
+  let process = Cc["@mozilla.org/process/util;1"]
+                  .createInstance(Ci.nsIProcess);
+  process.init(bin);
+  let args = [];
+  if (additionalArgs) {
+    args = args.concat(additionalArgs);
+  }
+  args.push(dumpFile.path);
+  process.run(true /* blocking */, args, args.length);
+}
+
 function handleMinidump(callback) {
   // find minidump
   let minidump = getMinidump();
 
   if (minidump == null) {
     do_throw("No minidump found!");
   }
 
@@ -126,17 +155,17 @@ function handleMinidump(callback) {
       memoryfile.remove(false);
     }
   });
 
   do_check_true(extrafile.exists());
   let extra = parseKeyValuePairsFromFile(extrafile);
 
   if (callback) {
-    callback(minidump, extra);
+    callback(minidump, extra, extrafile);
   }
 
   if (minidump.exists()) {
     minidump.remove(false);
   }
   if (extrafile.exists()) {
     extrafile.remove(false);
   }
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/head_win64cfi.js
@@ -0,0 +1,193 @@
+/* import-globals-from head_crashreporter.js */
+
+let gTestCrasherSyms = null;
+let gModules = null;
+
+// Returns the offset (int) of an IP with a given base address.
+// This is effectively (ip - base), except a bit more complication due to
+// Javascript's shaky handling of 64-bit integers.
+// base & ip are passed as hex strings.
+function getModuleOffset(base, ip) {
+  let i = 0;
+  // Find where the two addresses diverge, which enables us to perform a 32-bit
+  // subtraction.
+  // e.g.    "0x1111111111112222"
+  //       - "0x1111111111111111"
+  // becomes 2222 - 1111
+  for (; i < base.length; ++i) {
+    if (base[i] != ip[i]) {
+      break;
+    }
+  }
+  if (i == base.length) {
+    return 0;
+  }
+  let lhs2 = "0x" + base.substring(i);
+  let rhs2 = "0x" + ip.substring(i);
+  return parseInt(rhs2) - parseInt(lhs2);
+}
+
+// Uses gTestCrasherSyms to convert an address to a symbol.
+function findNearestTestCrasherSymbol(addr) {
+  addr += 1; // Breakpad sometimes offsets addresses; correct for this.
+  let closestDistance = null;
+  let closestSym = null;
+  for (let sym in gTestCrasherSyms) {
+    if (addr >= gTestCrasherSyms[sym]) {
+      let thisDistance = addr - gTestCrasherSyms[sym];
+      if (closestDistance === null || thisDistance < closestDistance) {
+        closestDistance = thisDistance;
+        closestSym = sym;
+      }
+    }
+  }
+  if (closestSym === null) {
+    return null;
+  }
+  return { symbol: closestSym, offset: closestDistance }
+}
+
+// Populate known symbols for testcrasher.dll.
+// Use the same prop names as from CrashTestUtils to avoid the need for mapping.
+function initTestCrasherSymbols() {
+  gTestCrasherSyms = { };
+  for (let k in CrashTestUtils) {
+    // Not all keys here are valid symbol names. getWin64CFITestFnAddrOffset
+    // will return 0 in those cases, no need to filter here.
+    if (Number.isInteger(CrashTestUtils[k])) {
+      let t = CrashTestUtils.getWin64CFITestFnAddrOffset(CrashTestUtils[k]);
+      if (t > 0) {
+        gTestCrasherSyms[k] = t;
+      }
+    }
+  }
+}
+
+function stackFrameToString(frameIndex, frame) {
+  // Calculate the module offset.
+  let ip = frame.ip;
+  let symbol = "";
+  let moduleOffset = "unknown_offset";
+  let filename = "unknown_module";
+
+  if (typeof frame.module_index !== "undefined" && (frame.module_index >= 0)
+    && (frame.module_index < gModules.length)) {
+
+    let base = gModules[frame.module_index].base_addr;
+    moduleOffset = getModuleOffset(base, ip);
+    filename = gModules[frame.module_index].filename;
+
+    if (filename === "testcrasher.dll") {
+      let nearestSym = findNearestTestCrasherSymbol(moduleOffset);
+      if (nearestSym !== null) {
+        symbol = nearestSym.symbol + "+" + nearestSym.offset.toString(16);
+      }
+    }
+  }
+
+  let ret = "frames[" + frameIndex + "] ip=" + ip +
+    " " + symbol +
+    ", module:" + filename +
+    ", trust:" + frame.trust +
+    ", moduleOffset:" + moduleOffset.toString(16);
+  return ret;
+}
+
+function dumpStackFrames(frames, maxFrames) {
+  for (let i = 0; i < Math.min(maxFrames, frames.length); ++i) {
+    do_print(stackFrameToString(i, frames[i]));
+  }
+}
+
+// Test that the top of the given stack (from extra data) matches the given
+// expected frames.
+//
+// expected is { symbol: "", trust: "" }
+function assertStack(stack, expected) {
+  for (let i = 0; i < stack.length; ++i) {
+    if (i >= expected.length) {
+      ok("Top stack frames were expected");
+      return;
+    }
+    let frame = stack[i];
+    let expectedFrame = expected[i];
+    let dumpThisFrame = function() {
+      do_print("  Actual frame: " + stackFrameToString(i, frame));
+      do_print("Expected { symbol: " + expectedFrame.symbol + ", trust: " + expectedFrame.trust + "}");
+    };
+
+    if (expectedFrame.trust) {
+      if (frame.trust !== expectedFrame.trust) {
+        dumpThisFrame();
+        do_print("Expected frame trust did not match.");
+        ok(false);
+      }
+    }
+
+    if (expectedFrame.symbol) {
+      if (typeof frame.module_index === "undefined") {
+        // Without a module_index, it happened in an unknown module. Currently
+        // you can't specify an expected "unknown" module.
+        do_print("Unknown symbol in unknown module.");
+        ok(false);
+      }
+      if (frame.module_index < 0 || frame.module_index >= gModules.length) {
+        dumpThisFrame();
+        do_print("Unknown module.");
+        ok(false);
+        return;
+      }
+      let base = gModules[frame.module_index].base_addr;
+      let moduleOffset = getModuleOffset(base, frame.ip);
+      let filename = gModules[frame.module_index].filename;
+      if (filename == "testcrasher.dll") {
+        let nearestSym = findNearestTestCrasherSymbol(moduleOffset);
+        if (nearestSym === null) {
+          dumpThisFrame();
+          do_print("Unknown symbol.");
+          ok(false);
+          return;
+        }
+
+        if (nearestSym.symbol !== expectedFrame.symbol) {
+          dumpThisFrame();
+          do_print("Mismatching symbol.");
+          ok(false);
+        }
+      }
+    }
+  }
+}
+
+// Performs a crash, runs minidump-analyzer, and checks expected stack analysis.
+//
+// how: The crash to perform. Constants defined in both CrashTestUtils.jsm
+//   and nsTestCrasher.cpp (i.e. CRASH_X64CFI_PUSH_NONVOL)
+// expectedStack: An array of {"symbol", "trust"} where trust is "cfi",
+//   "context", "scan", et al. May be null if you don't need to check the stack.
+// minidumpAnalyzerArgs: An array of additional arguments to pass to
+//   minidump-analyzer.exe.
+function do_x64CFITest(how, expectedStack, minidumpAnalyzerArgs) {
+
+  // Setup is run in the subprocess so we cannot use any closures.
+  let setupFn = "crashType = CrashTestUtils." + how + ";";
+
+  let callbackFn = function(minidumpFile, extra, extraFile) {
+    runMinidumpAnalyzer(minidumpFile, minidumpAnalyzerArgs);
+
+    // Refresh updated extra data
+    extra = parseKeyValuePairsFromFile(extraFile);
+
+    initTestCrasherSymbols();
+    let stackTraces = JSON.parse(extra.StackTraces);
+    let crashingThreadIndex = stackTraces.crash_info.crashing_thread;
+    gModules = stackTraces.modules;
+    let crashingFrames = stackTraces.threads[crashingThreadIndex].frames;
+
+    dumpStackFrames(crashingFrames, 10);
+
+    assertStack(crashingFrames, expectedStack);
+  };
+
+  do_crash(setupFn, callbackFn, true, true);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_alloc_large.js
@@ -0,0 +1,7 @@
+
+function run_test() {
+  do_x64CFITest("CRASH_X64CFI_ALLOC_LARGE", [
+      { symbol: "CRASH_X64CFI_ALLOC_LARGE", trust: "context" },
+      { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }
+    ]);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_alloc_small.js
@@ -0,0 +1,7 @@
+
+function run_test() {
+  do_x64CFITest("CRASH_X64CFI_ALLOC_SMALL", [
+      { symbol: "CRASH_X64CFI_ALLOC_SMALL", trust: "context" },
+      { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }
+    ]);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_epilog.js
@@ -0,0 +1,7 @@
+
+function run_test() {
+  do_x64CFITest("CRASH_X64CFI_EPILOG", [
+      { symbol: "CRASH_X64CFI_EPILOG", trust: "context" },
+      { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }
+    ]);
+}
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5283cc5df7e0a7abb20dd540cc203c4266702fff
GIT binary patch
literal 1440
zc$|e)-%C?b9RJ>J*K9U7hQ@m8E>1tF7@^aLo)*o#()__DM0UwdH*~Sxjd?8<9~#R*
zFM*Z58SGD}NRgfdjVL#Z48*LLelgI89(xFG=iGCz+pbVw_T_uNzrW{uK6jmcvtR}Q
z3uZ+DSO!Wy_&=>+u0Q^=9$p+Sx|f+i(cL>7AK}uO)KDgp<OU<6n3A}G7?&9pxwy#r
zyF*+u6^(gpHY?I<Y}GBketc_ZPUo(9$K0%X{xtUpON;lz{6kf4&l5cu9~>re`V2j7
z0MYwq_)-X7*KNPx1k{=8!NFj^iP}D6>7!>KHiJXs8ZAecd3HYz?<Jh2QLrx#>QzT@
z;rufddK&8p4pJB|J^k<XR#A_+x{r}OQfy4Z($3KN9ZdNiI&rwIClifG5j>}f*5ou<
zNk07Zq-h+|?r=eq#yEajK(+j|gdErS>>h`^&TQX%Bh}{6Nj1j4G>JI-l7#wq56!HX
zii)CKS|$%5Z^;`<V-cTnzZL0w;K2eS*&Xr?M~6J(>X5(nuNDdgwf;3ew->fA3CK;#
z-P~nMKgxAB<#$Tslvd{_FAFHm=hnhFTg%Pv<<&t1GRPzzge`qFq+ij~pE0Gg*jLqS
z+z@<B4{l!J<puN<t9*8i^ypuO9>b0Z>DV;UoxyuE<x1ZX%ng_w7Rr@A8|F34$U?c&
zS8wPQ4EaMFcR#+ht}nJvo$Gp)ZwaY*_g@r}<ARtP74O8ws4&**6@sxuEHV<?H{vHB
z0DaC%YQNkYAXp;UQgMcoBC+e3^u!5PPz$l?xYlt-?&)H59ZgJ3L<fejzht49@{DPC
zWa<62T1}+Z`D$CUiaEBbr&pZ=+gxQ^D?s_X({b!}vfzxK2B-HFI2i<Wcu2--QRlEe
zuSqjD7Hr<5U}GAfCQbY3)W;tDMsza1_olgXG~tim2DsW5>}m@%H+iCoMA=XF_{2o-
UpT}|s^B)WSf9l3PY7O=N0y)qbQvd(}
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_code_chain.js
@@ -0,0 +1,17 @@
+
+function run_test() {
+  // Test that minidump-analyzer gracefully handles chained
+  // unwind code entries that form a circular reference
+  // (infinite loop).
+  let exe = do_get_file("test_crash_win64cfi_infinite_code_chain.exe");
+  ok(exe);
+
+  // Perform a crash. We won't get unwind info, but make sure the stack scan
+  // still works.
+  do_x64CFITest("CRASH_X64CFI_ALLOC_SMALL",
+    [
+      { symbol: "CRASH_X64CFI_ALLOC_SMALL", trust: "context" },
+      { symbol: "CRASH_X64CFI_NO_MANS_LAND", trust: null }
+    ],
+    ["--force-use-module", exe.path]);
+}
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..de48576f6536cc6895f23297752690da1a694a10
GIT binary patch
literal 1440
zc$|e)-Ahwp7=O+_)MncbLt|ZajJFRWM(FaQt3{hlnjcI-s?j#x(8a!*M^h7GtpYs+
zRvKLx_zwsbCDN6k5T&cg3o*O04+eVCWf#HedCxho`w;43Kc4q}zTfx#owZ$=0zCj2
z5V8!g2&%Z?|FVRz9C>4bR|oP9i<~dt&^-_v6w;Yge<qv|`ohU%N)&pdLS`r_#FB!$
zqf1DnB2kCgY$95XjoR05A79^^W@w+`r>B(lm+42S4bIOq4;8&RL-+nz-vEtcGjw_Y
zBKP$WeDA-^Y`@?r)aoq2bLiKpwy&sN>e_{7;5AffIZV$P_u}wgLRmEm_SF7;*%IuS
zf3jqs#yx@k62ePQ|NDB&q*Gtsr&1hZbVNjL<<$9ku6Pb60=9KzB4II%bL!NZf~HHw
zg+E7HjU(C_&S<(a&N~qxHQou4@IBt_u7F)9H}Ad^YjWh65@TH$CxUfBB*C5P$<;z$
zmgVz{^Z}#|>4RLK$5rmPEPnSrm?bp3Rl3TzN`v-R>05AlZEa1d-{Z~ghO7$#(m=~y
z+c6e?6zi-@ALaUcTAiO>DL~TR+)4<uwcPCPoH9s&4l<5|7z^7K=`Ae%1y?wQzOr7U
z6~TvDaMO}knkCP1>&>pv9>Hbkthgg8jGH?3&ft??MXB!~q7K0uRFwM6h!q4es3`SW
zDth_5++CjA?(JK{Ph0ModRFJ3%=45t?sI{}XdsyyO5ThmBY}}-XTTqgN5g~BJtIDO
zFR(dF<>G~IALSC|mclcVNK!lFq$5VTLe5ef<2B<MJ*S;xdongQ7U}Irf5D(~$}ytx
z(WstZtJO$roi4XEDV#Hw^>iw8pv_*iH3JmCJH}(qRt>hu39vbjgN-9li$iiI1DKo^
zFuklwb7mu$orl28)j?HS?Zf1XasM~Mr1HJj^=(6OcWfBolE>ff@ijF%BJp_9Pw(*=
W;s)l;q25CLF{uAf9oVC$qTXLgycI0~
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_infinite_entry_chain.js
@@ -0,0 +1,17 @@
+
+function run_test() {
+  // Test that minidump-analyzer gracefully handles chained
+  // IMAGE_RUNTIME_FUNCTION_ENTRY items that form a circular reference
+  // (infinite loop).
+  let exe = do_get_file("test_crash_win64cfi_infinite_entry_chain.exe");
+  ok(exe);
+
+  // Perform a crash. We won't get unwind info, but make sure the stack scan
+  // still works.
+  do_x64CFITest("CRASH_X64CFI_ALLOC_SMALL",
+    [
+      { symbol: "CRASH_X64CFI_ALLOC_SMALL", trust: "context" },
+      { symbol: "CRASH_X64CFI_NO_MANS_LAND", trust: null }
+    ],
+    ["--force-use-module", exe.path]);
+}
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ab4ce326bdf34b9e417107d2d22adb3d01b006ee
GIT binary patch
literal 1440
zc$|e)-Ahwp7=O+_)Moo&XsoM_@%BN*2%TPZwP@y%<_DV~*&#RG(8YE(=CM?~Xe<Lg
z1Xg-yus@+9MY<9+qI4D+h#6h<#Xv8*>>^n2d*1W9?FjX-hv$91pZ9rxXB~aBU<3dY
z=3)_G87O(-|FnYHcH*TCUL46cEOY*RL(gz*giocDL+NmW9}G)UQsxJue0o&kV-oM{
z3i63$B<i+WEl8`eUAy@D@vYrC#+~zyxmor6Y3>o0CeMfYhpOC}Cwd?@I85T$4Bf2&
zk^4q)d<kA>wqI}(Y7I7UaM*94w$E64>Di0T;Lx~A%V9ESK8VA631w&$9Eihu#SvUM
z|7@9F#Dw55h49i-|6XqebsH=D=*cZd$7C$+9G&06mF~fa$8Fu|NLUWzISsTXugP-q
z;-5Q3<H+nOe5Z?=)W-?aBB~LlW#s4;GW$I4I<s@{ja-vOr_>nx(j?;TOET);tDade
z<ncN#FOvsQww29deIB2(zl-vB|APfYvfGs#j&^0l)vkQ&U)|W)Q0w;!+5M1xNkk2#
z+|506;YX>?mh!GxKc&_ADJUXJ3E8y}&en1>`#E(Gkqk142Qe4E>e8>W^k-b*9QIZ8
z8rKCMXTeP?g0g^~VpYhjkskf4(5>4MA%;x@-5I<$QZDx$#au^p6XkNB74sTqWTIT|
zv*~&Tg1+G9-H&fA>x*qu=R05Jnu96~e3!(;xF{t@r8_YxB966q#6UD24Ua?*jQGh1
zfX!J>ZC85yqzJ*5iZhguh@D~59V1vl&BVrV&2UEU>Ef7<CMG5#14GzfFi}jo$22^e
z>HW1@jilCvN?VhPIder%k2(joxk|Pc(6CCd%W%v&tHBvL15VFraB>K0@sOOw1Qw4C
zEYGV_oYf3g&oQuabx@U}eHitb4}T+!^zXf8>==#vVz&XVwFWv{{Y{PTNIYKhlRZ8$
V(EI1G+{OIIME{?<aE}^Yy}vLX7<&K!
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_invalid_exception_rva.js
@@ -0,0 +1,16 @@
+
+function run_test() {
+  // Test that minidump-analyzer gracefully handles an invalid pointer to the
+  // exception unwind information.
+  let exe = do_get_file("test_crash_win64cfi_invalid_exception_rva.exe");
+  ok(exe);
+
+  // Perform a crash. We won't get unwind info, but make sure the stack scan
+  // still works.
+  do_x64CFITest("CRASH_X64CFI_ALLOC_SMALL",
+    [
+      { symbol: "CRASH_X64CFI_ALLOC_SMALL", trust: "context" },
+      { symbol: "CRASH_X64CFI_NO_MANS_LAND", trust: null }
+    ],
+    ["--force-use-module", exe.path]);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_not_a_pe.exe
@@ -0,0 +1,1 @@
+this is not a valid PE file.
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_not_a_pe.js
@@ -0,0 +1,15 @@
+
+function run_test() {
+  // Test that minidump-analyzer gracefully handles corrupt PE files.
+  let exe = do_get_file("test_crash_win64cfi_not_a_pe.exe");
+  ok(exe);
+
+  // Perform a crash. We won't get unwind info, but make sure the stack scan
+  // still works.
+  do_x64CFITest("CRASH_X64CFI_ALLOC_SMALL",
+    [
+      { symbol: "CRASH_X64CFI_ALLOC_SMALL", trust: "context" },
+      { symbol: "CRASH_X64CFI_NO_MANS_LAND", trust: null }
+    ],
+    ["--force-use-module", exe.path]);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_push_nonvol.js
@@ -0,0 +1,7 @@
+
+function run_test() {
+  do_x64CFITest("CRASH_X64CFI_PUSH_NONVOL", [
+      { symbol: "CRASH_X64CFI_PUSH_NONVOL", trust: "context" },
+      { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }
+    ]);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_nonvol.js
@@ -0,0 +1,7 @@
+
+function run_test() {
+  do_x64CFITest("CRASH_X64CFI_SAVE_NONVOL", [
+      { symbol: "CRASH_X64CFI_SAVE_NONVOL", trust: "context" },
+      { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }
+    ]);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_nonvol_far.js
@@ -0,0 +1,7 @@
+
+function run_test() {
+  do_x64CFITest("CRASH_X64CFI_SAVE_NONVOL_FAR", [
+      { symbol: "CRASH_X64CFI_SAVE_NONVOL_FAR", trust: "context" },
+      { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }
+    ]);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_xmm128.js
@@ -0,0 +1,7 @@
+
+function run_test() {
+  do_x64CFITest("CRASH_X64CFI_SAVE_XMM128", [
+      { symbol: "CRASH_X64CFI_SAVE_XMM128", trust: "context" },
+      { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }
+    ]);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_save_xmm128_far.js
@@ -0,0 +1,7 @@
+
+function run_test() {
+  do_x64CFITest("CRASH_X64CFI_SAVE_XMM128_FAR", [
+      { symbol: "CRASH_X64CFI_SAVE_XMM128_FAR", trust: "context" },
+      { symbol: "CRASH_X64CFI_LAUNCHER", trust: "cfi" }
+    ]);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_win64cfi_unknown_op.js
@@ -0,0 +1,13 @@
+
+function run_test() {
+  // In the case of an unknown unwind code or missing CFI,
+  // make certain we can still walk the stack via stack scan. The crashing
+  // function places NO_MANS_LAND on the stack so it will get picked up via
+  // stack scan.
+  do_x64CFITest("CRASH_X64CFI_UNKNOWN_OPCODE", [
+      { symbol: "CRASH_X64CFI_UNKNOWN_OPCODE", trust: "context" },
+      // Trust may either be scan or frame_pointer; we don't really care as
+      // long as the address is expected.
+      { symbol: "CRASH_X64CFI_NO_MANS_LAND", trust: null }
+    ]);
+}
--- a/toolkit/crashreporter/test/unit/xpcshell.ini
+++ b/toolkit/crashreporter/test/unit/xpcshell.ini
@@ -32,8 +32,70 @@ skip-if = os != 'win'
 
 [test_crashreporter_appmem.js]
 # we need to skip this due to bug 838613
 skip-if = (os != 'win' && os != 'linux') || (os=='linux' && bits==32)
 
 [test_crash_AsyncShutdown.js]
 [test_event_files.js]
 [test_crash_terminator.js]
+
+[test_crash_win64cfi_unknown_op.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+
+[test_crash_win64cfi_push_nonvol.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+
+[test_crash_win64cfi_alloc_small.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+
+[test_crash_win64cfi_alloc_large.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+
+[test_crash_win64cfi_save_nonvol.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+
+[test_crash_win64cfi_save_nonvol_far.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+
+[test_crash_win64cfi_save_xmm128.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+
+[test_crash_win64cfi_save_xmm128_far.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+
+[test_crash_win64cfi_epilog.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+
+[test_crash_win64cfi_infinite_entry_chain.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+support-files = test_crash_win64cfi_infinite_entry_chain.exe
+
+[test_crash_win64cfi_infinite_code_chain.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+support-files = test_crash_win64cfi_infinite_code_chain.exe
+
+[test_crash_win64cfi_invalid_exception_rva.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+support-files = test_crash_win64cfi_invalid_exception_rva.exe
+
+[test_crash_win64cfi_not_a_pe.js]
+head = head_crashreporter.js head_win64cfi.js
+skip-if = os != 'win' || bits != 64
+support-files = test_crash_win64cfi_not_a_pe.exe
+
+
+
+
+
+
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/win64UnwindInfoTests.asm
@@ -0,0 +1,382 @@
+; 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/.
+
+; Comments indicate stack memory layout during execution.
+; For example at the top of a function, where RIP just points to the return
+; address, the stack looks like
+; rip = [ra]
+; And after pushing rax to the stack,
+; rip = [rax][ra]
+; And then, after allocating 20h bytes on the stack,
+; rip = [..20..][rax][ra]
+; And then, after pushing a function pointer,
+; rip = [pfn][..20..][rax][ra]
+
+include ksamd64.inc
+
+.code
+
+; It helps to add padding between functions so they're not right up against
+; each other. Adds clarity to debugging, and gives a bit of leeway when
+; searching for symbols (e.g. a function whose last instruction is CALL
+; would push a return address that's in the next function.)
+PaddingBetweenFunctions macro
+  repeat 10h
+     int 3
+  endm
+endm
+
+DoCrash macro
+  mov rax, 7
+  mov byte ptr [rax], 9
+endm
+
+PaddingBetweenFunctions
+
+; There is no rip addressing mode in x64. The only way to get the value
+; of rip is to call a function, and pop it from the stack.
+WhoCalledMe proc
+  pop rax ; rax is now ra
+  push rax ; Restore ra so this function can return.
+  sub rax, 5 ; Correct for the size of the call instruction
+  ret
+WhoCalledMe endp
+
+PaddingBetweenFunctions
+
+; Any function that we expect to test against on the stack, we'll need its
+; real address. If we use function pointers in C, we'll get the address to jump
+; table entries. This bit of code at the beginning of each function will
+; return the real address we'd expect to see in stack traces.
+;
+; rcx (1st arg) = mode
+; rax (return)  = address of either NO_MANS_LAND or this function.
+;
+; When mode is 0, we place the address of NO_MANS_LAND in RAX, for the function
+; to use as it wants. This is just for convenience because almost all functions
+; here need this address at some point.
+;
+; When mode is 1, the address of this function is returned.
+TestHeader macro
+  call WhoCalledMe
+  test rcx, rcx
+  je continue_test
+  ret
+continue_test:
+  inc rcx
+  call x64CrashCFITest_NO_MANS_LAND
+  xor rcx, rcx
+endm
+
+; The point of this is to add a stack frame to test against.
+; void* x64CrashCFITest_Launcher(int getAddress, void* pTestFn)
+x64CrashCFITest_Launcher proc frame
+  TestHeader
+
+  .endprolog
+  call rdx
+  ret
+x64CrashCFITest_Launcher endp
+
+PaddingBetweenFunctions
+
+; void* x64CrashCFITest_NO_MANS_LAND(uint64_t mode);
+; Not meant to be called. Only when mode = 1 in order to return its address.
+; Place this function's address on the stack so the stack scanning algorithm
+; thinks this is a return address, and places it on the stack trace.
+x64CrashCFITest_NO_MANS_LAND proc frame
+  TestHeader
+  .endprolog
+  ret
+x64CrashCFITest_NO_MANS_LAND endp
+
+PaddingBetweenFunctions
+
+; Test that we:
+; - handle unknown opcodes gracefully
+; - fall back to other stack unwind strategies if CFI doesn't work
+;
+; In order to properly unwind this frame, we'd need to fully support
+; SET_FPREG with offsets, plus restoring registers via PUSH_NONVOL.
+; To do this, sprinkle the stack with bad return addresses
+; and stack pointers.
+x64CrashCFITest_UnknownOpcode proc frame
+  TestHeader
+
+  push rax
+  .allocstack 8
+
+  push rbp
+  .pushreg rbp
+
+  push rax
+  push rsp
+  push rax
+  push rsp
+  .allocstack 20h
+  ; rsp = [rsp][pfn][rsp][pfn][rbp][pfn][ra]
+
+  lea rbp, [rsp+10h]
+  .setframe rbp, 10h
+  ; rsp = [rsp][pfn] [rsp][pfn][rbp][pfn][ra]
+  ; rbp =           ^
+
+  .endprolog
+
+  ; Now modify RSP so measuring stack size from unwind ops will not help
+  ; finding the return address.
+  push rax
+  push rsp
+  ; rsp = [rsp][pfn][rsp][pfn] [rsp][pfn][rbp][pfn][ra]
+
+  DoCrash
+
+x64CrashCFITest_UnknownOpcode endp
+
+PaddingBetweenFunctions
+
+; void* x64CrashCFITest_PUSH_NONVOL(uint64_t mode);
+;
+; Test correct handling of PUSH_NONVOL unwind code.
+;
+x64CrashCFITest_PUSH_NONVOL proc frame
+  TestHeader
+
+  push r10
+  .pushreg r10
+  push r15
+  .pushreg r15
+  push rbx
+  .pushreg rbx
+  push rsi
+  .pushreg rsi
+  push rbp
+  .pushreg rbp
+  ; rsp = [rbp][rsi][rbx][r15][r10][ra]
+
+  push rax
+  .allocstack 8
+  ; rsp = [pfn][rbp][rsi][rbx][r15][r10][ra]
+
+  .endprolog
+
+  DoCrash
+
+x64CrashCFITest_PUSH_NONVOL endp
+
+PaddingBetweenFunctions
+
+; void* x64CrashCFITest_ALLOC_SMALL(uint64_t mode);
+;
+; Small allocations are between 8bytes and 512kb-8bytes
+;
+x64CrashCFITest_ALLOC_SMALL proc frame
+  TestHeader
+
+  ; Trash rbp to force stack scan. This will force
+  ; correct behavior for test_crash_win64cfi_not_a_pe, et al.
+  xor rbp, rbp
+
+  push rax
+  push rax
+  push rax
+  push rax
+  .allocstack 20h
+  ; rsp = [pfn][pfn][pfn][pfn][ra]
+
+  .endprolog
+
+  DoCrash
+
+x64CrashCFITest_ALLOC_SMALL endp
+
+PaddingBetweenFunctions
+
+; void* x64CrashCFITest_ALLOC_LARGE(uint64_t mode);
+;
+; Allocations between 512kb and 4gb
+; Note: ReserveStackSpace() in nsTestCrasher.cpp pre-allocates stack
+; space for this.
+x64CrashCFITest_ALLOC_LARGE proc frame
+  TestHeader
+
+  sub rsp, 0a000h
+  .allocstack 0a000h
+  ; rsp = [..640kb..][ra]
+
+  mov qword ptr [rsp], rax
+  ; rsp = [pfn][..640kb-8..][ra]
+
+  .endprolog
+
+  DoCrash
+
+x64CrashCFITest_ALLOC_LARGE endp
+
+PaddingBetweenFunctions
+
+; void* x64CrashCFITest_SAVE_NONVOL(uint64_t mode);
+;
+; Test correct handling of SAVE_NONVOL unwind code.
+;
+x64CrashCFITest_SAVE_NONVOL proc frame
+  TestHeader
+
+  sub rsp, 30h
+  .allocstack 30h
+  ; rsp = [..30..][ra]
+
+  mov qword ptr [rsp+28h], r10
+  .savereg r10, 28h
+  mov qword ptr [rsp+20h], rbp
+  .savereg rbp, 20h
+  mov qword ptr [rsp+18h], rsi
+  .savereg rsi, 18h
+  mov qword ptr [rsp+10h], rbx
+  .savereg rbx, 10h
+  mov qword ptr [rsp+8], r15
+  .savereg r15, 8
+  ; rsp = [r15][rbx][rsi][rbp][r10][ra]
+
+  mov qword ptr [rsp], rax
+
+  ; rsp = [pfn][r15][rbx][rsi][rbp][r10][ra]
+
+  .endprolog
+
+  DoCrash
+
+x64CrashCFITest_SAVE_NONVOL endp
+
+PaddingBetweenFunctions
+
+; void* x64CrashCFITest_SAVE_NONVOL_FAR(uint64_t mode);
+;
+; Similar to the test above but adding 640kb to most offsets.
+; Note: ReserveStackSpace() in nsTestCrasher.cpp pre-allocates stack
+; space for this.
+x64CrashCFITest_SAVE_NONVOL_FAR proc frame
+  TestHeader
+
+  sub rsp, 0a0030h
+  .allocstack 0a0030h
+  ; rsp = [..640k..][..30..][ra]
+
+  mov qword ptr [rsp+28h+0a0000h], r10
+  .savereg r10, 28h+0a0000h
+  mov qword ptr [rsp+20h+0a0000h], rbp
+  .savereg rbp, 20h+0a0000h
+  mov qword ptr [rsp+18h+0a0000h], rsi
+  .savereg rsi, 18h+0a0000h
+  mov qword ptr [rsp+10h+0a0000h], rbx
+  .savereg rbx, 10h+0a0000h
+  mov qword ptr [rsp+8+0a0000h], r15
+  .savereg r15, 8+0a0000h
+  ; rsp = [..640k..][..8..][r15][rbx][rsi][rbp][r10][ra]
+
+  mov qword ptr [rsp], rax
+
+  ; rsp = [pfn][..640k..][r15][rbx][rsi][rbp][r10][ra]
+
+  .endprolog
+
+  DoCrash
+
+x64CrashCFITest_SAVE_NONVOL_FAR endp
+
+PaddingBetweenFunctions
+
+; void* x64CrashCFITest_SAVE_XMM128(uint64_t mode);
+;
+; Test correct handling of SAVE_XMM128 unwind code.
+x64CrashCFITest_SAVE_XMM128 proc frame
+  TestHeader
+
+  sub rsp, 30h
+  .allocstack 30h
+  ; rsp = [..30..][ra]
+
+  movdqu [rsp+20h], xmm6
+  .savexmm128 xmm6, 20h
+  ; rsp = [..20..][xmm6][ra]
+
+  movdqu [rsp+10h], xmm15
+  .savexmm128 xmm15, 10h
+  ; rsp = [..10..][xmm15][xmm6][ra]
+
+  mov qword ptr [rsp], rax
+  ; rsp = [pfn][..8..][xmm15][xmm6][ra]
+
+  .endprolog
+
+  DoCrash
+
+x64CrashCFITest_SAVE_XMM128 endp
+
+PaddingBetweenFunctions
+
+; void* x64CrashCFITest_SAVE_XMM128(uint64_t mode);
+;
+; Similar to the test above but adding 640kb to most offsets.
+; Note: ReserveStackSpace() in nsTestCrasher.cpp pre-allocates stack
+; space for this.
+x64CrashCFITest_SAVE_XMM128_FAR proc frame
+  TestHeader
+
+  sub rsp, 0a0030h
+  .allocstack 0a0030h
+  ; rsp = [..640kb..][..30..][ra]
+
+  movdqu [rsp+20h+0a0000h], xmm6
+  .savexmm128 xmm6, 20h+0a0000h
+  ; rsp = [..640kb..][..20..][xmm6][ra]
+
+  movdqu [rsp+10h+0a0000h], xmm6
+  .savexmm128 xmm15, 10h+0a0000h
+  ; rsp = [..640kb..][..10..][xmm15][xmm6][ra]
+
+  mov qword ptr [rsp], rax
+  ; rsp = [pfn][..640kb..][..8..][xmm15][xmm6][ra]
+
+  .endprolog
+
+  DoCrash
+
+x64CrashCFITest_SAVE_XMM128_FAR endp
+
+PaddingBetweenFunctions
+
+; void* x64CrashCFITest_EPILOG(uint64_t mode);
+;
+; The epilog unwind op will also set the unwind version to 2.
+; Test that we don't choke on UWOP_EPILOG or version 2 unwind info.
+x64CrashCFITest_EPILOG proc frame
+  TestHeader
+
+  push rax
+  .allocstack 8
+  ; rsp = [pfn][ra]
+
+  .endprolog
+
+  DoCrash
+
+  .beginepilog
+
+  ret
+
+x64CrashCFITest_EPILOG endp
+
+PaddingBetweenFunctions
+
+; Having an EOF symbol at the end of this file contains symbolication to this
+; file. So addresses beyond this file don't get mistakenly symbolicated as a
+; meaningful function name.
+x64CrashCFITest_EOF proc frame
+  TestHeader
+  .endprolog
+  ret
+x64CrashCFITest_EOF endp
+
+end
--- a/tools/profiler/core/platform.cpp
+++ b/tools/profiler/core/platform.cpp
@@ -102,17 +102,18 @@
 // Android builds use the ARM Exception Handling ABI to unwind.
 #if defined(GP_PLAT_arm_android)
 # define HAVE_NATIVE_UNWIND
 # define USE_EHABI_STACKWALK
 # include "EHABIStackWalk.h"
 #endif
 
 // Linux builds use LUL, which uses DWARF info to unwind stacks.
-#if defined(GP_PLAT_amd64_linux) || defined(GP_PLAT_x86_linux) || defined(GP_PLAT_mips64_linux)
+#if defined(GP_PLAT_amd64_linux) || defined(GP_PLAT_x86_linux) || \
+    defined(GP_PLAT_mips64_linux)
 # define HAVE_NATIVE_UNWIND
 # define USE_LUL_STACKWALK
 # include "lul/LulMain.h"
 # include "lul/platform-linux-lul.h"
 
 // On linux we use LUL for periodic samples and synchronous samples, but we use
 // FramePointerStackWalk for backtrace samples when MOZ_PROFILING is enabled.
 // (See the comment at the top of the file for a definition of
@@ -1128,16 +1129,20 @@ DoLULBacktrace(PSLockRef aLock, const Th
   startRegs.r13 = lul::TaggedUWord(mc->arm_sp);
   startRegs.r12 = lul::TaggedUWord(mc->arm_ip);
   startRegs.r11 = lul::TaggedUWord(mc->arm_fp);
   startRegs.r7  = lul::TaggedUWord(mc->arm_r7);
 #elif defined(GP_PLAT_x86_linux) || defined(GP_PLAT_x86_android)
   startRegs.xip = lul::TaggedUWord(mc->gregs[REG_EIP]);
   startRegs.xsp = lul::TaggedUWord(mc->gregs[REG_ESP]);
   startRegs.xbp = lul::TaggedUWord(mc->gregs[REG_EBP]);
+#elif defined(GP_PLAT_mips64_linux)
+  startRegs.pc = lul::TaggedUWord(mc->pc);
+  startRegs.sp = lul::TaggedUWord(mc->gregs[29]);
+  startRegs.fp = lul::TaggedUWord(mc->gregs[30]);
 #else
 # error "Unknown plat"
 #endif
 
   // Copy up to N_STACK_BYTES from rsp-REDZONE upwards, but not going past the
   // stack's registered top point.  Do some basic sanity checks too.  This
   // assumes that the TaggedUWord holding the stack pointer value is valid, but
   // it should be, since it was constructed that way in the code just above.
@@ -1175,16 +1180,19 @@ DoLULBacktrace(PSLockRef aLock, const Th
     uintptr_t rEDZONE_SIZE = 128;
     uintptr_t start = startRegs.xsp.Value() - rEDZONE_SIZE;
 #elif defined(GP_PLAT_arm_android)
     uintptr_t rEDZONE_SIZE = 0;
     uintptr_t start = startRegs.r13.Value() - rEDZONE_SIZE;
 #elif defined(GP_PLAT_x86_linux) || defined(GP_PLAT_x86_android)
     uintptr_t rEDZONE_SIZE = 0;
     uintptr_t start = startRegs.xsp.Value() - rEDZONE_SIZE;
+#elif defined(GP_PLAT_mips64_linux)
+    uintptr_t rEDZONE_SIZE = 0;
+    uintptr_t start = startRegs.sp.Value() - rEDZONE_SIZE;
 #else
 #   error "Unknown plat"
 #endif
     uintptr_t end = reinterpret_cast<uintptr_t>(aThreadInfo.StackTop());
     uintptr_t ws  = sizeof(void*);
     start &= ~(ws-1);
     end   &= ~(ws-1);
     uintptr_t nToCopy = 0;