Merge mozilla-central to inbound. a=merge CLOSED TREE
authorGurzau Raul <rgurzau@mozilla.com>
Wed, 15 May 2019 19:02:08 +0300
changeset 535940 fd32e1c1f0edf5dcbe8eeb592d00052ff03dc5e7
parent 535939 b1523c5d75ba8b3bd05103c6252f1356e0c238ad (current diff)
parent 535807 d865d7a290f8c93bfca4ad532878fbc0e4403b62 (diff)
child 536049 091801115a02d2bcfbacada8a3367c3bdd805a9a
push id2082
push userffxbld-merge
push dateMon, 01 Jul 2019 08:34:18 +0000
treeherdermozilla-release@2fb19d0466d2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to inbound. a=merge CLOSED TREE
dom/ipc/tests/browser_JSWindowActor.js
dom/ipc/tests/test_JSWindowActor.xul
gfx/wr/wrench/reftests/border/border-overlapping-ref.yaml
gfx/wr/wrench/reftests/border/border-overlapping.yaml
toolkit/components/search/tests/xpcshell/searchconfigs/xpcshell.ini
--- a/browser/components/search/extensions/ebay/_locales/be/messages.json
+++ b/browser/components/search/extensions/ebay/_locales/be/messages.json
@@ -4,14 +4,14 @@
   },
   "extensionDescription": {
     "message": "eBay - Online auctions"
   },
   "searchUrl": {
     "message": "https://rover.ebay.com/rover/1/1553-53471-19255-0/1"
   },
   "searchForm": {
-    "message": "https://www.ebay.at/"
+    "message": "https://www.befr.ebay.be/"
   },
   "searchUrlGetParams": {
     "message": "ff3=4&toolid=20004&campid=5338192028&customid=&mpre=https://www.befr.ebay.be/sch/{searchTerms}"
   }
-}
\ No newline at end of file
+}
--- a/devtools/client/aboutdebugging-new/src/components/connect/ConnectPage.css
+++ b/devtools/client/aboutdebugging-new/src/components/connect/ConnectPage.css
@@ -33,8 +33,18 @@
 }
 .connect-page__usb-section__heading__status {
   grid-area: status;
   line-height: 1;
   font-size: var(--caption-20-font-size);
   font-weight: var(--caption-20-font-weight);
   color: var(--caption-20-color);
 }
+
+.connect-page__troubleshoot {
+  font-size: var(--body-10-font-size);
+  font-weight: var(--body-10-font-weight);
+  margin-block-start: calc(var(--base-unit) * 2);
+}
+
+.connect-page__troubleshoot--network {
+  padding-inline: calc(var(--base-unit) * 6);
+}
--- a/devtools/client/aboutdebugging-new/src/components/connect/ConnectPage.js
+++ b/devtools/client/aboutdebugging-new/src/components/connect/ConnectPage.js
@@ -26,16 +26,21 @@ const NetworkLocationsForm = createFacto
 const NetworkLocationsList = createFactory(require("./NetworkLocationsList"));
 
 const { PAGE_TYPES, RUNTIMES } = require("../../constants");
 const Types = require("../../types/index");
 
 const USB_ICON_SRC = "chrome://devtools/skin/images/aboutdebugging-usb-icon.svg";
 const GLOBE_ICON_SRC = "chrome://devtools/skin/images/aboutdebugging-globe-icon.svg";
 
+const TROUBLESHOOT_USB_URL =
+  "https://developer.mozilla.org/docs/Tools/Remote_Debugging/Debugging_over_USB";
+const TROUBLESHOOT_NETWORK_URL =
+  "https://developer.mozilla.org/docs/Tools/Remote_Debugging/Debugging_over_a_network";
+
 class ConnectPage extends PureComponent {
   static get propTypes() {
     return {
       adbAddonStatus: Types.adbAddonStatus,
       dispatch: PropTypes.func.isRequired,
       networkLocations: PropTypes.arrayOf(Types.location).isRequired,
     };
   }
@@ -140,43 +145,43 @@ class ConnectPage extends PureComponent 
           this.renderUsbToggleButton(),
         ),
       },
       isAddonInstalled
         ? ConnectSteps(
           {
             steps: [
               {
-                localizationId: "about-debugging-setup-usb-step-enable-dev-menu",
-                url: "https://developer.mozilla.org/docs/Tools/Remote_Debugging/Debugging_over_USB",
+                localizationId: "about-debugging-setup-usb-step-enable-dev-menu2",
+              },
+              {
+                localizationId: "about-debugging-setup-usb-step-enable-debug2",
               },
               {
-                localizationId: "about-debugging-setup-usb-step-enable-debug",
-                url: "https://developer.mozilla.org/docs/Tools/Remote_Debugging/Debugging_over_USB",
+                localizationId: "about-debugging-setup-usb-step-enable-debug-firefox2",
               },
               {
-                localizationId: "about-debugging-setup-usb-step-enable-debug-firefox",
-                url: "https://developer.mozilla.org/docs/Tools/Remote_Debugging/Debugging_over_USB",
+                localizationId: "about-debugging-setup-usb-step-plug-device",
               },
-              { localizationId: "about-debugging-setup-usb-step-plug-device" },
             ],
           }
         )
         : Localized(
           {
             id: "about-debugging-setup-usb-disabled",
           },
           dom.aside(
             {
               className: "qa-connect-usb-disabled-message",
             },
             "Enabling this will download and add the required Android USB debugging " +
               "components to Firefox."
           )
         ),
+        this.renderTroubleshootText(RUNTIMES.USB),
     );
   }
 
   renderNetwork() {
     const { dispatch, networkLocations } = this.props;
 
     return Localized(
       {
@@ -186,18 +191,54 @@ class ConnectPage extends PureComponent 
       ConnectSection({
         className: "connect-page__breather",
         icon: GLOBE_ICON_SRC,
         title: "Network Location",
         extraContent: dom.div(
           {},
           NetworkLocationsList({ dispatch, networkLocations }),
           NetworkLocationsForm({ dispatch, networkLocations }),
+          this.renderTroubleshootText(RUNTIMES.NETWORK),
         ),
-      })
+      },
+      )
+    );
+  }
+
+  renderTroubleshootText(connectionType) {
+    const localizationId = connectionType === RUNTIMES.USB
+      ? "about-debugging-setup-usb-troubleshoot"
+      : "about-debugging-setup-network-troubleshoot";
+
+    const className = "connect-page__troubleshoot connect-page__troubleshoot--" +
+      `${connectionType === RUNTIMES.USB ? "usb" : "network"}`;
+
+    const url = connectionType === RUNTIMES.USB
+      ? TROUBLESHOOT_USB_URL
+      : TROUBLESHOOT_NETWORK_URL;
+
+    return dom.aside(
+      {
+        className,
+      },
+      Localized(
+        {
+          id: localizationId,
+          a: dom.a(
+            {
+              href: url,
+              target: "_blank",
+            }
+          ),
+        },
+        dom.p(
+          {},
+          localizationId,
+        ),
+      )
     );
   }
 
   render() {
     return dom.article(
       {
         className: "page connect-page qa-connect-page",
       },
@@ -216,42 +257,25 @@ class ConnectPage extends PureComponent 
         {
           id: "about-debugging-setup-intro",
         },
         dom.p(
           {},
           "Configure the connection method you wish to remotely debug your device with."
         )
       ),
-      dom.p(
-        {},
-        Localized(
-          {
-            id: "about-debugging-setup-link-android-devices",
-          },
-          dom.a(
-            {
-              href: "https://support.mozilla.org/kb/will-firefox-work-my-mobile-device#w_android-devices",
-              target: "_blank",
-            },
-            "View list of supported android devices"
-          )
-        ),
-      ),
       Localized(
         {
           id: "about-debugging-setup-this-firefox",
           a: Link({
             to: `/runtime/${RUNTIMES.THIS_FIREFOX}`,
           }),
         },
         dom.p(
-          {
-            className: "connect-page__breather",
-          },
+          {},
           "about-debugging-setup-this-firefox",
         ),
       ),
       dom.section(
         {
           className: "connect-page__breather",
         },
         Localized(
--- a/devtools/client/aboutdebugging-new/src/components/connect/ConnectSteps.js
+++ b/devtools/client/aboutdebugging-new/src/components/connect/ConnectSteps.js
@@ -12,42 +12,37 @@ const FluentReact = require("devtools/cl
 const Localized = createFactory(FluentReact.Localized);
 
 class ConnectSteps extends PureComponent {
   static get propTypes() {
     return {
       steps: PropTypes.arrayOf(
         PropTypes.shape({
           localizationId: PropTypes.string.isRequired,
-          url: PropTypes.string,
         }).isRequired,
       ),
     };
   }
 
   render() {
     return dom.ul(
       {
         className: "connect-page__step-list",
       },
       ...this.props.steps.map(step =>
         Localized(
           {
             id: step.localizationId,
-            a: step.url ? dom.a({
-              href: step.url,
-              target: "_blank",
-            }) : null,
           },
           dom.li(
             {
               className: "connect-page__step",
-              key: step,
+              key: step.localizationId,
             },
-            step
+            step.localizationId
           )
         )
       )
     );
   }
 }
 
 module.exports = ConnectSteps;
--- a/devtools/client/accessibility/accessibility.css
+++ b/devtools/client/accessibility/accessibility.css
@@ -523,16 +523,23 @@ body {
   background-color: var(--theme-selection-background);
 }
 
 .accessible .tree:focus .node.focused *,
 .accessible .tree .tree-node-active .node.focused * {
   color: var(--theme-selection-color);
 }
 
+/* Invert text selection color in selected rows */
+.accessible .tree:focus .node.focused ::selection,
+.accessible .tree .tree-node-active .node.focused ::selection {
+  color: var(--theme-selection-background);
+  background-color: var(--theme-selection-color);
+}
+
 .accessible .tree:focus .node.focused .open-inspector,
 .accessible .tree .tree-node-active .node.focused .open-inspector {
   background-color: var(--grey-30);
 }
 
 .accessible .tree:focus .node.focused:hover .open-inspector,
 .accessible .tree .tree-node-active .node.focused:hover .open-inspector {
   background-color: var(--theme-selection-color);
--- a/devtools/client/debugger/dist/vendors.css
+++ b/devtools/client/debugger/dist/vendors.css
@@ -34,20 +34,22 @@
 .tree-indent {
   display: inline-block;
   width: 12px;
   margin-inline-start: 3px;
   border-inline-start: 1px solid #a2d1ff;
   flex-shrink: 0;
 }
 
-.debugger .tree-indent {
-  width: 16px;
-  margin-inline-start: 0px;
-  border-inline-start: 0;
+.tree-node[data-expandable="false"] .tree-last-indent {
+  /* The 13px value is taken from the total width and margins of the arrow
+  element of expandables nodes (10px width + 3px margins). That way the
+  node's text are indented the same for both expandable and non-expandable
+  nodes */
+  margin-inline-end: 13px;
 }
 
 /* For non expandable root nodes, we don't have .tree-indent elements, so we declare
    the margin on the start of the node */
 .tree-node[data-expandable="false"][aria-level="1"] {
   padding-inline-start: 15px;
 }
 
@@ -73,18 +75,24 @@
   transform: rotate(-90deg);
 }
 
 html[dir="rtl"] .tree-node button:not(.expanded) {
   transform: rotate(90deg);
 }
 
 .tree .tree-node.focused {
-  color: white;
-  background-color: var(--theme-selection-background, #0a84ff);
+  color: var(--theme-selection-color);
+  background-color: var(--theme-selection-background);
+}
+
+/* Invert text selection color in selected rows */
+.tree .tree-node.focused ::selection {
+  color: var(--theme-selection-background);
+  background-color: var(--theme-selection-color);
 }
 
 .tree-node.focused button.arrow {
   background-color: currentColor;
 }
 /* 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/. */
--- a/devtools/client/debugger/dist/vendors.js
+++ b/devtools/client/debugger/dist/vendors.js
@@ -229,16 +229,17 @@ class ArrowExpander extends Component {
     }
     return _reactDomFactories2.default.button({
       className: classNames.join(" ")
     });
   }
 }
 
 const treeIndent = _reactDomFactories2.default.span({ className: "tree-indent" }, "\u200B");
+const treeLastIndent = _reactDomFactories2.default.span({ className: "tree-indent tree-last-indent" }, "\u200B");
 
 class TreeNode extends Component {
   static get propTypes() {
     return {
       id: _propTypes2.default.any.isRequired,
       index: _propTypes2.default.number.isRequired,
       depth: _propTypes2.default.number.isRequired,
       focused: _propTypes2.default.bool.isRequired,
@@ -354,17 +355,23 @@ class TreeNode extends Component {
     let ariaExpanded;
     if (this.props.isExpandable) {
       ariaExpanded = false;
     }
     if (this.props.expanded) {
       ariaExpanded = true;
     }
 
-    const indents = Array.from({ length: depth }).fill(treeIndent);
+    const indents = Array.from({ length: depth }, (_, i) => {
+      if (i == depth - 1) {
+        return treeLastIndent;
+      }
+      return treeIndent;
+    });
+
     const items = indents.concat(renderItem(item, depth, focused, arrow, expanded));
 
     return _reactDomFactories2.default.div({
       id,
       className: `tree-node${focused ? " focused" : ""}${active ? " active" : ""}`,
       onClick: this.props.onClick,
       onKeyDownCapture: active ? this._onKeyDown : null,
       role: "treeitem",
--- a/devtools/client/debugger/packages/devtools-components/src/tree.css
+++ b/devtools/client/debugger/packages/devtools-components/src/tree.css
@@ -34,20 +34,22 @@
 .tree-indent {
   display: inline-block;
   width: 12px;
   margin-inline-start: 3px;
   border-inline-start: 1px solid #a2d1ff;
   flex-shrink: 0;
 }
 
-.debugger .tree-indent {
-  width: 16px;
-  margin-inline-start: 0px;
-  border-inline-start: 0;
+.tree-node[data-expandable="false"] .tree-last-indent {
+  /* The 13px value is taken from the total width and margins of the arrow
+  element of expandables nodes (10px width + 3px margins). That way the
+  node's text are indented the same for both expandable and non-expandable
+  nodes */
+  margin-inline-end: 13px;
 }
 
 /* For non expandable root nodes, we don't have .tree-indent elements, so we declare
    the margin on the start of the node */
 .tree-node[data-expandable="false"][aria-level="1"] {
   padding-inline-start: 15px;
 }
 
@@ -73,15 +75,21 @@
   transform: rotate(-90deg);
 }
 
 html[dir="rtl"] .tree-node button:not(.expanded) {
   transform: rotate(90deg);
 }
 
 .tree .tree-node.focused {
-  color: white;
-  background-color: var(--theme-selection-background, #0a84ff);
+  color: var(--theme-selection-color);
+  background-color: var(--theme-selection-background);
+}
+
+/* Invert text selection color in selected rows */
+.tree .tree-node.focused ::selection {
+  color: var(--theme-selection-background);
+  background-color: var(--theme-selection-color);
 }
 
 .tree-node.focused button.arrow {
   background-color: currentColor;
 }
--- a/devtools/client/debugger/packages/devtools-components/src/tree.js
+++ b/devtools/client/debugger/packages/devtools-components/src/tree.js
@@ -48,16 +48,20 @@ class ArrowExpander extends Component {
     }
     return dom.button({
       className: classNames.join(" ")
     });
   }
 }
 
 const treeIndent = dom.span({ className: "tree-indent" }, "\u200B");
+const treeLastIndent = dom.span(
+  { className: "tree-indent tree-last-indent" },
+  "\u200B"
+);
 
 class TreeNode extends Component {
   static get propTypes() {
     return {
       id: PropTypes.any.isRequired,
       index: PropTypes.number.isRequired,
       depth: PropTypes.number.isRequired,
       focused: PropTypes.bool.isRequired,
@@ -185,17 +189,23 @@ class TreeNode extends Component {
     let ariaExpanded;
     if (this.props.isExpandable) {
       ariaExpanded = false;
     }
     if (this.props.expanded) {
       ariaExpanded = true;
     }
 
-    const indents = Array.from({ length: depth }).fill(treeIndent);
+    const indents = Array.from({length: depth}, (_, i) => {
+      if (i == depth - 1) {
+        return treeLastIndent;
+      }
+      return treeIndent;
+    })
+
     const items = indents.concat(
       renderItem(item, depth, focused, arrow, expanded)
     );
 
     return dom.div(
       {
         id,
         className: `tree-node${focused ? " focused" : ""}${
--- a/devtools/client/debugger/src/components/App.css
+++ b/devtools/client/debugger/src/components/App.css
@@ -24,16 +24,22 @@ button:focus {
 }
 
 .debugger {
   display: flex;
   flex: 1;
   height: 100%;
 }
 
+.debugger .tree-indent {
+  width: 16px;
+  margin-inline-start: 0;
+  border-inline-start: 0;
+}
+
 .editor-pane {
   display: flex;
   position: relative;
   flex: 1;
   background-color: var(--theme-body-background);
   height: 100%;
   overflow: hidden;
 }
--- a/devtools/client/debugger/src/components/shared/ManagedTree.css
+++ b/devtools/client/debugger/src/components/shared/ManagedTree.css
@@ -25,17 +25,17 @@
 }
 
 .managed-tree .tree .node {
   padding: 2px 3px 2px 3px;
   position: relative;
 }
 
 .managed-tree .tree .node.focused {
-  color: white;
+  color: var(--theme-selection-color);
   background-color: var(--theme-selection-background);
 }
 
 html:not([dir="rtl"]) .managed-tree .tree .node > div {
   margin-left: 10px;
 }
 
 html[dir="rtl"] .managed-tree .tree .node > div {
--- a/devtools/client/locales/en-US/aboutdebugging.ftl
+++ b/devtools/client/locales/en-US/aboutdebugging.ftl
@@ -96,20 +96,16 @@ about-debugging-refresh-usb-devices-butt
 # Setup Page strings
 
 # Title of the Setup page.
 about-debugging-setup-title = Setup
 
 # Introduction text in the Setup page to explain how to configure remote debugging.
 about-debugging-setup-intro = Configure the connection method you wish to remotely debug your device with.
 
-# Link displayed in the Setup page that leads to MDN page with list of supported devices.
-# Temporarily leads to https://support.mozilla.org/en-US/kb/will-firefox-work-my-mobile-device#w_android-devices
-about-debugging-setup-link-android-devices = View list of supported Android devices
-
 # Explanatory text in the Setup page about what the 'This Firefox' page is for
 about-debugging-setup-this-firefox = Use <a>{ about-debugging-this-firefox-runtime-name }</a> to debug tabs, extensions and service workers on this version of { -brand-shorter-name }.
 
 # Title of the heading Connect section of the Setup page.
 about-debugging-setup-connect-heading = Connect a Device
 
 # USB section of the Setup page
 about-debugging-setup-usb-title = USB
@@ -129,31 +125,39 @@ about-debugging-setup-usb-disable-button
 about-debugging-setup-usb-updating-button = Updating…
 
 # USB section of the Setup page (USB status)
 about-debugging-setup-usb-status-enabled = Enabled
 about-debugging-setup-usb-status-disabled = Disabled
 about-debugging-setup-usb-status-updating = Updating…
 
 # USB section step by step guide
-about-debugging-setup-usb-step-enable-dev-menu = Enable Developer menu on your Android device. <a>Learn how</a>
+about-debugging-setup-usb-step-enable-dev-menu2 = Enable Developer menu on your Android device.
 
 # USB section step by step guide
-about-debugging-setup-usb-step-enable-debug = Enable USB Debugging in the Android Developer Menu. <a>Learn how</a>
+about-debugging-setup-usb-step-enable-debug2 = Enable USB Debugging in the Android Developer Menu.
 
 # USB section step by step guide
-about-debugging-setup-usb-step-enable-debug-firefox = Enable USB Debugging in Firefox on the Android device. <a>Learn how</a>
+about-debugging-setup-usb-step-enable-debug-firefox2 = Enable USB Debugging in Firefox on the Android device.
 
 # USB section step by step guide
 about-debugging-setup-usb-step-plug-device = Connect the Android device to your computer.
 
+# Text shown in the USB section of the setup page with a link to troubleshoot connection errors.
+# The link goes to https://developer.mozilla.org/docs/Tools/Remote_Debugging/Debugging_over_USB
+about-debugging-setup-usb-troubleshoot = Problems connecting to the USB device? <a>Troubleshoot</a>
+
 # Network section of the Setup page
 about-debugging-setup-network =
   .title = Network Location
 
+# Text shown in the Network section of the setup page with a link to troubleshoot connection errors.
+# The link goes to https://developer.mozilla.org/en-US/docs/Tools/Remote_Debugging/Debugging_over_a_network
+about-debugging-setup-network-troubleshoot = Problems connecting via network location? <a>Troubleshoot</a>
+
 # Text of a button displayed after the network locations "Host" input.
 # Clicking on it will add the new network location to the list.
 about-debugging-network-locations-add-button = Add
 
 # Text to display when there are no locations to show.
 about-debugging-network-locations-empty-text = No network locations have been added yet.
 
 # Text of the label for the text input that allows users to add new network locations in
--- a/devtools/client/netmonitor/src/components/RequestListContent.js
+++ b/devtools/client/netmonitor/src/components/RequestListContent.js
@@ -124,16 +124,26 @@ class RequestListContent extends Compone
   }
 
   componentWillUnmount() {
     this.refs.scrollEl.removeEventListener("scroll", this.onScroll, true);
 
     // Uninstall the tooltip event handler
     this.tooltip.stopTogglingOnHover();
     window.removeEventListener("resize", this.onResize);
+
+    // Performance optimization. React is slow when removing long lists
+    // of elements (i.e. the list of HTTP requests in this case), and so
+    // let's clean up the content through `innerHTML` property in advance.
+    // This significantly speeds up Network panel closing time.
+    // This isn't React friendly since React has no way to know the DOM
+    // has been modified. But, the panel is closing at this point anyway.
+    // See also: Bug 1546513 - Closing the network panel with many
+    // entries takes multiple seconds
+    this.refs.rowGroupEl.innerHTML = "";
   }
 
   /*
    * Removing onResize() method causes perf regression - too many repaints of the panel.
    * So it is needed in ComponentDidMount and ComponentDidUpdate. See Bug 1532914.
    */
   onResize() {
     const parent = this.refs.scrollEl.parentNode;
--- a/devtools/client/netmonitor/src/utils/request-utils.js
+++ b/devtools/client/netmonitor/src/utils/request-utils.js
@@ -310,23 +310,28 @@ function getUrlDetails(url) {
  * @param {string} query - query string of a url portion
  * @return {array} array of query params { name, value }
  */
 function parseQueryString(query) {
   if (!query) {
     return null;
   }
 
-  return query.replace(/^[?&]/, "").split("&").map(e => {
-    const param = e.split("=");
-    return {
-      name: param[0] ? getUnicodeUrlPath(param[0]) : "",
-      value: param[1] ? getUnicodeUrlPath(param.slice(1).join("=")) : "",
-    };
-  });
+  return query
+    .replace(/^[?&]/, "")
+    .split("&")
+    .map(e => {
+      const param = e.split("=");
+      return {
+        name: param[0] ? getUnicodeUrlPath(param[0]) : "",
+        value: param[1]
+          ? getUnicodeUrlPath(param.slice(1).join("=")).replace(/\+/g, " ")
+          : "",
+      };
+    });
 }
 
 /**
  * Parse a string of formdata sections into its components
  *
  * @param {string} sections - sections of formdata joined by &
  * @return {array} array of formdata params { name, value }
  */
--- a/devtools/client/netmonitor/test/browser.ini
+++ b/devtools/client/netmonitor/test/browser.ini
@@ -26,16 +26,17 @@ support-files =
   html_maps-test-page.html
   html_navigate-test-page.html
   html_params-test-page.html
   html_pause-test-page.html
   html_post-data-test-page.html
   html_post-array-data-test-page.html
   html_post-json-test-page.html
   html_post-raw-test-page.html
+  html_header-test-page.html
   html_post-raw-with-headers-test-page.html
   html_simple-test-page.html
   html_single-get-page.html
   html_send-beacon.html
   html_sorting-test-page.html
   html_statistics-test-page.html
   html_status-codes-test-page.html
   html_tracking-protection.html
@@ -133,16 +134,18 @@ skip-if = (os == 'mac') || (os == 'win' 
 [browser_net_filter-01.js]
 [browser_net_filter-02.js]
 [browser_net_filter-03.js]
 [browser_net_filter-04.js]
 [browser_net_filter-autocomplete.js]
 [browser_net_filter-flags.js]
 [browser_net_footer-summary.js]
 [browser_net_header-ref-policy.js]
+[browser_net_decode-url.js]
+[browser_net_decode-params.js]
 [browser_net_headers-alignment.js]
 [browser_net_headers_filter.js]
 [browser_net_headers_sorted.js]
 [browser_net_headers-resize.js]
 [browser_net_image-tooltip.js]
 [browser_net_json-b64.js]
 [browser_net_json-empty.js]
 [browser_net_json-null.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_decode-params.js
@@ -0,0 +1,39 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if "+" is replaces with spaces in the Params panel.
+ */
+add_task(async function() {
+  const { tab, monitor } = await initNetMonitor(POST_RAW_URL_WITH_HASH);
+  info("Starting test... ");
+
+  const { document, store, windowRequire } = monitor.panelWin;
+  const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
+  store.dispatch(Actions.batchEnable(false));
+
+  // Execute request.
+  await performRequests(monitor, tab, 1);
+
+  // Wait until the tab panel summary is displayed
+  wait = waitUntil(() =>
+    document.querySelectorAll(".tabpanel-summary-label")[0]);
+  EventUtils.sendMouseEvent({ type: "mousedown" },
+  document.querySelectorAll(".request-list-item")[0]);
+  await wait;
+
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector("#params-tab"));
+
+  // The Params panel should render the following:
+  // Query String:
+  // file    foo # bar
+  const keyValue = document.querySelectorAll(".treeTable .treeRow")[1];
+
+  is(keyValue.innerText,
+  "file\tfoo # bar", "'+' in params in correctly decoded.");
+
+  return teardown(monitor);
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_decode-url.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if "Request URL" containing "#" in its query is correctly decoded.
+ */
+add_task(async function() {
+  const { tab, monitor } = await initNetMonitor(POST_RAW_URL_WITH_HASH);
+  info("Starting test... ");
+
+  const { document, store, windowRequire } = monitor.panelWin;
+  const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
+  store.dispatch(Actions.batchEnable(false));
+
+  // Execute request.
+  await performRequests(monitor, tab, 1);
+
+  // Wait until the tab panel summary is displayed
+  wait = waitUntil(() =>
+    document.querySelectorAll(".tabpanel-summary-label")[0]);
+  EventUtils.sendMouseEvent({ type: "mousedown" },
+  document.querySelectorAll(".request-list-item")[0]);
+  await wait;
+
+  const requestURL = document.querySelectorAll(".tabpanel-summary-value")[0];
+
+  is(requestURL.textContent.endsWith("foo+%23+bar"),
+  true, "\"Request URL\" containing '#' is correctly decoded.");
+
+  return teardown(monitor);
+});
--- a/devtools/client/netmonitor/test/head.js
+++ b/devtools/client/netmonitor/test/head.js
@@ -50,16 +50,17 @@ const NAVIGATE_URL = EXAMPLE_URL + "html
 const CONTENT_TYPE_WITHOUT_CACHE_URL = EXAMPLE_URL + "html_content-type-without-cache-test-page.html";
 const CONTENT_TYPE_WITHOUT_CACHE_REQUESTS = 8;
 const CYRILLIC_URL = EXAMPLE_URL + "html_cyrillic-test-page.html";
 const STATUS_CODES_URL = EXAMPLE_URL + "html_status-codes-test-page.html";
 const POST_DATA_URL = EXAMPLE_URL + "html_post-data-test-page.html";
 const POST_ARRAY_DATA_URL = EXAMPLE_URL + "html_post-array-data-test-page.html";
 const POST_JSON_URL = EXAMPLE_URL + "html_post-json-test-page.html";
 const POST_RAW_URL = EXAMPLE_URL + "html_post-raw-test-page.html";
+const POST_RAW_URL_WITH_HASH = EXAMPLE_URL + "html_header-test-page.html";
 const POST_RAW_WITH_HEADERS_URL = EXAMPLE_URL + "html_post-raw-with-headers-test-page.html";
 const PARAMS_URL = EXAMPLE_URL + "html_params-test-page.html";
 const JSONP_URL = EXAMPLE_URL + "html_jsonp-test-page.html";
 const JSON_LONG_URL = EXAMPLE_URL + "html_json-long-test-page.html";
 const JSON_MALFORMED_URL = EXAMPLE_URL + "html_json-malformed-test-page.html";
 const JSON_CUSTOM_MIME_URL = EXAMPLE_URL + "html_json-custom-mime-test-page.html";
 const JSON_TEXT_MIME_URL = EXAMPLE_URL + "html_json-text-mime-test-page.html";
 const JSON_B64_URL = EXAMPLE_URL + "html_json-b64.html";
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_header-test-page.html
@@ -0,0 +1,43 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+    <meta http-equiv="Pragma" content="no-cache" />
+    <meta http-equiv="Expires" content="0" />
+    <title>Network Monitor test page</title>
+  </head>
+
+  <body>
+    <p>POST raw test</p>
+
+    <script type="text/javascript">
+      /* exported performRequests */
+      "use strict";
+
+      function post(address, message, callback) {
+        const xhr = new XMLHttpRequest();
+        xhr.open("POST", address, true);
+        xhr.setRequestHeader("content-type", "application/x-www-form-urlencoded");
+
+        xhr.onreadystatechange = function() {
+          if (this.readyState == this.DONE) {
+            callback();
+          }
+        };
+        xhr.send(message);
+      }
+
+      function performRequests() {
+        const rawData = "";
+        post("sjs_simple-test-server.sjs?file=foo+%23+bar#home", rawData, function() {
+          // Done.
+        });
+      }
+    </script>
+  </body>
+
+</html>
--- a/devtools/client/shared/components/reps/reps.css
+++ b/devtools/client/shared/components/reps/reps.css
@@ -34,20 +34,22 @@
 .tree-indent {
   display: inline-block;
   width: 12px;
   margin-inline-start: 3px;
   border-inline-start: 1px solid #a2d1ff;
   flex-shrink: 0;
 }
 
-.debugger .tree-indent {
-  width: 16px;
-  margin-inline-start: 0px;
-  border-inline-start: 0;
+.tree-node[data-expandable="false"] .tree-last-indent {
+  /* The 13px value is taken from the total width and margins of the arrow
+  element of expandables nodes (10px width + 3px margins). That way the
+  node's text are indented the same for both expandable and non-expandable
+  nodes */
+  margin-inline-end: 13px;
 }
 
 /* For non expandable root nodes, we don't have .tree-indent elements, so we declare
    the margin on the start of the node */
 .tree-node[data-expandable="false"][aria-level="1"] {
   padding-inline-start: 15px;
 }
 
@@ -73,18 +75,24 @@
   transform: rotate(-90deg);
 }
 
 html[dir="rtl"] .tree-node button:not(.expanded) {
   transform: rotate(90deg);
 }
 
 .tree .tree-node.focused {
-  color: white;
-  background-color: var(--theme-selection-background, #0a84ff);
+  color: var(--theme-selection-color);
+  background-color: var(--theme-selection-background);
+}
+
+/* Invert text selection color in selected rows */
+.tree .tree-node.focused ::selection {
+  color: var(--theme-selection-background);
+  background-color: var(--theme-selection-color);
 }
 
 .tree-node.focused button.arrow {
   background-color: currentColor;
 }
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* 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
--- a/devtools/client/shared/components/reps/reps.js
+++ b/devtools/client/shared/components/reps/reps.js
@@ -170,16 +170,17 @@ class ArrowExpander extends Component {
     }
     return _reactDomFactories2.default.button({
       className: classNames.join(" ")
     });
   }
 }
 
 const treeIndent = _reactDomFactories2.default.span({ className: "tree-indent" }, "\u200B");
+const treeLastIndent = _reactDomFactories2.default.span({ className: "tree-indent tree-last-indent" }, "\u200B");
 
 class TreeNode extends Component {
   static get propTypes() {
     return {
       id: _propTypes2.default.any.isRequired,
       index: _propTypes2.default.number.isRequired,
       depth: _propTypes2.default.number.isRequired,
       focused: _propTypes2.default.bool.isRequired,
@@ -295,17 +296,23 @@ class TreeNode extends Component {
     let ariaExpanded;
     if (this.props.isExpandable) {
       ariaExpanded = false;
     }
     if (this.props.expanded) {
       ariaExpanded = true;
     }
 
-    const indents = Array.from({ length: depth }).fill(treeIndent);
+    const indents = Array.from({ length: depth }, (_, i) => {
+      if (i == depth - 1) {
+        return treeLastIndent;
+      }
+      return treeIndent;
+    });
+
     const items = indents.concat(renderItem(item, depth, focused, arrow, expanded));
 
     return _reactDomFactories2.default.div({
       id,
       className: `tree-node${focused ? " focused" : ""}${active ? " active" : ""}`,
       onClick: this.props.onClick,
       onKeyDownCapture: active ? this._onKeyDown : null,
       role: "treeitem",
--- a/devtools/client/shared/components/tree/TreeView.css
+++ b/devtools/client/shared/components/tree/TreeView.css
@@ -89,16 +89,22 @@
 }
 
 .treeTable .treeRow.selected *,
 .treeTable .treeRow.selected .treeLabelCell::after {
   color: var(--theme-selection-color);
   fill: currentColor;
 }
 
+/* Invert text selection color in selected rows */
+.treeTable .treeRow.selected :not(input):not(textarea)::selection {
+  color: var(--theme-selection-background);
+  background-color: var(--theme-selection-color);
+}
+
 /* Filtering */
 .treeTable .treeRow.hidden {
   display: none !important;
 }
 
 .treeTable .treeValueCellDivider {
   display: flex;
   flex-wrap: wrap;
--- a/devtools/client/shared/unicode-url.js
+++ b/devtools/client/shared/unicode-url.js
@@ -72,17 +72,17 @@ function getUnicodeUrlPath(urlPath) {
  *
  * If the `url` is a readable ASCII URL, such as http://example.org/a/b/c.js,
  * then this function will simply return the original `url`.
  *
  * If the `url` includes either an unreadable Punycode domain name or an
  * unreadable URI-encoded pathname, such as
  * http://xn--g6w.xn--8pv/%E8%A9%A6/%E6%B8%AC.js, then this function will return
  * the readable URL by decoding all its unreadable URL components to Unicode
- * characters.
+ * characters. The character `#` is not decoded from escape sequences.
  *
  * If the `url` is a malformed URL, then this function will return the original
  * `url`.
  *
  * If the `url` is a data: URI, then this function will return the original
  * `url`.
  *
  * @param {string}  url
@@ -95,17 +95,17 @@ function getUnicodeUrlPath(urlPath) {
 function getUnicodeUrl(url) {
   try {
     const { protocol, hostname } = new URL(url);
     if (protocol === "data:") {
       // Never convert a data: URI.
       return url;
     }
     const readableHostname = getUnicodeHostname(hostname);
-    url = decodeURIComponent(url);
+    url = decodeURI(url);
     return url.replace(hostname, readableHostname);
   } catch (err) {
   }
   return url;
 }
 
 module.exports = {
   getUnicodeHostname,
--- a/devtools/client/themes/common.css
+++ b/devtools/client/themes/common.css
@@ -54,16 +54,21 @@
  * Customize scrollbar colors on Linux + light theme, to avoid visual conflicts
  * between the light theme and the selected GTK theme (see bug 1471163).
  * This removes GTK scrollbars and uses a fallback design (see bug 1464723).
  */
 :root[platform="linux"].theme-light {
   scrollbar-color: var(--grey-40) var(--grey-20);
 }
 
+::selection {
+  background-color: var(--theme-selection-background);
+  color: var(--theme-selection-color);
+}
+
 .devtools-monospace {
   font-family: var(--monospace-font-family);
   font-size: var(--theme-code-font-size);
 }
 
 /**
  * For text that needs to be cut with an ellipsis …
  */
--- a/devtools/client/themes/dark-theme.css
+++ b/devtools/client/themes/dark-theme.css
@@ -17,21 +17,16 @@ body {
   color: var(--theme-body-color);
 }
 
 .theme-sidebar {
   background: var(--theme-sidebar-background);
   color: var(--theme-body-color);
 }
 
-::-moz-selection {
-  background-color: var(--theme-selection-background);
-  color: var(--theme-selection-color);
-}
-
 .theme-selected,
 .CodeMirror-hint-active {
   background-color: var(--theme-selection-background);
   color: var(--theme-selection-color);
 }
 
 .theme-bg-contrast,
 .variable-or-property:not([overridden])[changed] {
--- a/devtools/client/themes/light-theme.css
+++ b/devtools/client/themes/light-theme.css
@@ -17,21 +17,16 @@ body {
   color: var(--theme-body-color);
 }
 
 .theme-sidebar {
   background: var(--theme-sidebar-background);
   color: var(--theme-body-color);
 }
 
-::-moz-selection {
-  background-color: var(--theme-selection-background);
-  color: var(--theme-selection-color);
-}
-
 .theme-selected,
 .CodeMirror-hint-active {
   background-color: var(--theme-selection-background);
   color: var(--theme-selection-color);
 }
 
 .theme-bg-contrast,
 .variable-or-property:not([overridden])[changed] {
--- a/dom/base/nsDocumentEncoder.cpp
+++ b/dom/base/nsDocumentEncoder.cpp
@@ -1162,58 +1162,16 @@ nsHTMLCopyEncoder::SetSelection(Selectio
   for (nsCOMPtr<nsIContent> selContent(do_QueryInterface(commonParent));
        selContent; selContent = selContent->GetParent()) {
     // checking for selection inside a plaintext form widget
     if (selContent->IsAnyOfHTMLElements(nsGkAtoms::input,
                                         nsGkAtoms::textarea)) {
       mIsTextWidget = true;
       break;
     }
-#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
-    else if (selContent->IsHTMLElement(nsGkAtoms::body)) {
-      // Currently, setting mIsTextWidget to 'true' will result in the selection
-      // being encoded/copied as pre-formatted plain text.
-      // This is fine for copying pre-formatted plain text with Firefox, it is
-      // already not correct for copying pre-formatted "rich" text (bold,
-      // colour) with Firefox. As long as the serialisers aren't fixed, copying
-      // pre-formatted text in Firefox is broken. If we set mIsTextWidget,
-      // pre-formatted plain text is copied, but pre-formatted "rich" text loses
-      // the "rich" formatting. If we don't set mIsTextWidget, "rich" text
-      // attributes aren't lost, but white-space is lost.
-      // So far the story for Firefox.
-      //
-      // Thunderbird has two *conflicting* requirements.
-      // Case 1:
-      // When selecting and copying text, even pre-formatted text, as a quote
-      // to be placed into a reply, we *always* expect HTML to be copied.
-      // Case 2:
-      // When copying text in a so-called "plain text" message, that is
-      // one where the body carries style "white-space:pre-wrap", the text
-      // should be copied as pre-formatted plain text.
-      //
-      // Therefore the following code checks for "pre-wrap" on the body.
-      // This is a terrible hack.
-      //
-      // The proper fix would be this:
-      // For case 1:
-      // Communicate the fact that HTML is required to EncodeToString(),
-      // bug 1141786.
-      // For case 2:
-      // Wait for Firefox to get fixed to detect pre-formatting correctly,
-      // bug 1174452.
-      nsAutoString styleVal;
-      if (selContent->IsElement() &&
-          selContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::style,
-                                           styleVal) &&
-          styleVal.Find(NS_LITERAL_STRING("pre-wrap")) != kNotFound) {
-        mIsTextWidget = true;
-        break;
-      }
-    }
-#endif
   }
 
   // normalize selection if we are not in a widget
   if (mIsTextWidget) {
     mSelection = aSelection;
     mMimeType.AssignLiteral("text/plain");
     return NS_OK;
   }
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -195,17 +195,21 @@ if CONFIG['OS_ARCH'] != 'WINNT':
 
 DEFINES['BIN_SUFFIX'] = '"%s"' % CONFIG['BIN_SUFFIX']
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
     DEFINES['MOZ_ENABLE_FREETYPE'] = True
 
 JAR_MANIFESTS += ['jar.mn']
 
-BROWSER_CHROME_MANIFESTS += ['tests/browser.ini']
+BROWSER_CHROME_MANIFESTS += [
+    'tests/browser.ini',
+    'tests/JSWindowActor/browser.ini',
+]
+
 MOCHITEST_CHROME_MANIFESTS += ['tests/chrome.ini']
 MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
 XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell.ini']
 
 CXXFLAGS += CONFIG['TK_CFLAGS']
 
 if CONFIG['CC_TYPE'] in ('clang', 'gcc'):
     CXXFLAGS += ['-Wno-error=shadow']
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/JSWindowActor/browser.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+support-files =
+  head.js
+
+[browser_destroy_callbacks.js]
+[browser_event_listener.js]
+[browser_getActor.js]
+[browser_getActor_filter.js]
+[browser_observer_notification.js]
+[browser_registerWindowActor.js]
+[browser_sendAsyncMessage.js]
+[browser_sendQuery.js]
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/JSWindowActor/browser_destroy_callbacks.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+declTest("destroy actor by iframe remove", {
+  allFrames: true,
+
+  async test(browser) {
+    await ContentTask.spawn(browser, {}, async function() {
+      // Create and append an iframe into the window's document.
+      let frame = content.document.createElement("iframe");
+      frame.id = "frame";
+      content.document.body.appendChild(frame);
+      await ContentTaskUtils.waitForEvent(frame, "load");
+      is(content.window.frames.length, 1, "There should be an iframe.");
+      let child = frame.contentWindow.window.getWindowGlobalChild();
+      let actorChild = child.getActor("Test");
+      ok(actorChild, "JSWindowActorChild should have value.");
+
+      let willDestroyPromise = new Promise(resolve => {
+        const TOPIC = "test-js-window-actor-willdestroy";
+        Services.obs.addObserver(function obs(subject, topic, data) {
+          ok(data, "willDestroyCallback data should be true.");
+
+          Services.obs.removeObserver(obs, TOPIC);
+          resolve();
+        }, TOPIC);
+      });
+
+      let didDestroyPromise = new Promise(resolve => {
+        const TOPIC = "test-js-window-actor-diddestroy";
+        Services.obs.addObserver(function obs(subject, topic, data) {
+          ok(data, "didDestroyCallback data should be true.");
+
+          Services.obs.removeObserver(obs, TOPIC);
+          resolve();
+        }, TOPIC);
+      });
+
+      info("Remove frame");
+      content.document.getElementById("frame").remove();
+      await Promise.all([willDestroyPromise, didDestroyPromise]);
+
+      Assert.throws(() => child.getActor("Test"),
+        /InvalidStateError/, "Should throw if frame destroy.");
+    });
+  },
+});
+
+declTest("destroy actor by page navigates", {
+  allFrames: true,
+
+  async test(browser) {
+    info("creating an in-process frame");
+    await ContentTask.spawn(browser, URL, async function(url) {
+      let frame = content.document.createElement("iframe");
+      frame.src = url;
+      content.document.body.appendChild(frame);
+    });
+
+    info("navigating page");
+    await ContentTask.spawn(browser, TEST_URL, async function(url) {
+      let frame = content.document.querySelector("iframe");
+      frame.contentWindow.location = url;
+      let child = frame.contentWindow.window.getWindowGlobalChild();
+      let actorChild = child.getActor("Test");
+      ok(actorChild, "JSWindowActorChild should have value.");
+      await ContentTaskUtils.waitForEvent(frame, "load");
+
+      Assert.throws(() => child.getActor("Test"),
+              /InvalidStateError/, "Should throw if frame destroy.");
+    });
+  },
+});
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/JSWindowActor/browser_event_listener.js
@@ -0,0 +1,39 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+declTest("test event triggering actor creation", {
+  async test(browser) {
+    // Add a select element to the DOM of the loaded document.
+    await ContentTask.spawn(browser, {}, async function() {
+      content.document.body.innerHTML += `
+        <select id="testSelect">
+          <option>A</option>
+          <option>B</option>
+        </select>`;
+    });
+
+    // Wait for the observer notification.
+    let observePromise = new Promise(resolve => {
+      const TOPIC = "test-js-window-actor-parent-event";
+      Services.obs.addObserver(function obs(subject, topic, data) {
+        is(topic, TOPIC, "topic matches");
+
+        Services.obs.removeObserver(obs, TOPIC);
+        resolve({subject, data});
+      }, TOPIC);
+    });
+
+    // Click on the select to show the dropdown.
+    await BrowserTestUtils.synthesizeMouseAtCenter("#testSelect", {}, browser);
+
+    // Wait for the observer notification to fire, and inspect the results.
+    let {subject, data} = await observePromise;
+    is(data, "mozshowdropdown");
+
+    let parent = browser.browsingContext.currentWindowGlobal;
+    let actorParent = parent.getActor("Test");
+    ok(actorParent, "JSWindowActorParent should have value.");
+    is(subject.wrappedJSObject, actorParent, "Should have been recieved on the right actor");
+  },
+});
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/JSWindowActor/browser_getActor.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+declTest("getActor on both sides", {
+  async test(browser) {
+    let parent = browser.browsingContext.currentWindowGlobal;
+    ok(parent, "WindowGlobalParent should have value.");
+    let actorParent = parent.getActor("Test");
+    is(actorParent.show(), "TestParent", "actor show should have vaule.");
+    is(actorParent.manager, parent, "manager should match WindowGlobalParent.");
+
+    await ContentTask.spawn(browser, {}, async function() {
+      let child = content.window.getWindowGlobalChild();
+      ok(child, "WindowGlobalChild should have value.");
+      is(child.isInProcess, false, "Actor should be loaded in the content process.");
+      let actorChild = child.getActor("Test");
+      is(actorChild.show(), "TestChild", "actor show should have vaule.");
+      is(actorChild.manager, child, "manager should match WindowGlobalChild.");
+    });
+  },
+});
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/JSWindowActor/browser_getActor_filter.js
@@ -0,0 +1,163 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+declTest("getActor with mismatch", {
+  matches: ["*://*/*"],
+
+  async test(browser) {
+    let parent = browser.browsingContext.currentWindowGlobal;
+    ok(parent, "WindowGlobalParent should have value.");
+    Assert.throws(() => parent.getActor("Test"),
+          /NS_ERROR_NOT_AVAILABLE/, "Should throw if it doesn't match.");
+
+    await ContentTask.spawn(browser, {}, async function() {
+      let child = content.window.getWindowGlobalChild();
+      ok(child, "WindowGlobalChild should have value.");
+
+      Assert.throws(() => child.getActor("Test"),
+        /NS_ERROR_NOT_AVAILABLE/, "Should throw if it doesn't match.");
+    });
+  },
+});
+
+declTest("getActor with matches", {
+  matches: ["*://*/*"],
+  url: TEST_URL,
+
+  async test(browser) {
+    let parent = browser.browsingContext.currentWindowGlobal;
+    ok(parent.getActor("Test"), "JSWindowActorParent should have value.");
+
+    await ContentTask.spawn(browser, {}, async function() {
+      let child = content.window.getWindowGlobalChild();
+      ok(child, "WindowGlobalChild should have value.");
+      ok(child.getActor("Test"), "JSWindowActorChild should have value.");
+    });
+  },
+});
+
+declTest("getActor with iframe matches", {
+  allFrames: true,
+  matches: ["*://*/*"],
+
+  async test(browser) {
+    await ContentTask.spawn(browser, TEST_URL, async function(url) {
+      // Create and append an iframe into the window's document.
+      let frame = content.document.createElement("iframe");
+      frame.src = url;
+      content.document.body.appendChild(frame);
+      await ContentTaskUtils.waitForEvent(frame, "load");
+
+      is(content.window.frames.length, 1, "There should be an iframe.");
+      let child = frame.contentWindow.window.getWindowGlobalChild();
+      ok(child.getActor("Test"), "JSWindowActorChild should have value.");
+    });
+  },
+});
+
+declTest("getActor with iframe mismatch", {
+  allFrames: true,
+  matches: ["about:home"],
+
+  async test(browser) {
+    await ContentTask.spawn(browser, TEST_URL, async function(url) {
+      // Create and append an iframe into the window's document.
+      let frame = content.document.createElement("iframe");
+      frame.src = url;
+      content.document.body.appendChild(frame);
+      await ContentTaskUtils.waitForEvent(frame, "load");
+
+      is(content.window.frames.length, 1, "There should be an iframe.");
+      let child = frame.contentWindow.window.getWindowGlobalChild();
+      Assert.throws(() => child.getActor("Test"),
+        /NS_ERROR_NOT_AVAILABLE/, "Should throw if it doesn't match.");
+    });
+  },
+});
+
+declTest("getActor with remoteType match", {
+  remoteTypes: ["web"],
+
+  async test(browser) {
+    let parent = browser.browsingContext.currentWindowGlobal;
+    ok(parent.getActor("Test"), "JSWindowActorParent should have value.");
+
+    await ContentTask.spawn(browser, {}, async function() {
+      let child = content.window.getWindowGlobalChild();
+      ok(child, "WindowGlobalChild should have value.");
+      ok(child.getActor("Test"), "JSWindowActorChild should have value.");
+    });
+  },
+});
+
+declTest("getActor with remoteType mismatch", {
+  remoteTypes: ["privileged"],
+  url: TEST_URL,
+
+  async test(browser) {
+    let parent = browser.browsingContext.currentWindowGlobal;
+    Assert.throws(() => parent.getActor("Test"),
+          /NS_ERROR_NOT_AVAILABLE/, "Should throw if its remoteTypes don't match.");
+
+    await ContentTask.spawn(browser, {}, async function() {
+      let child = content.window.getWindowGlobalChild();
+      ok(child, "WindowGlobalChild should have value.");
+      Assert.throws(() => child.getActor("Test"),
+          /NS_ERROR_NOT_AVAILABLE/, "Should throw if its remoteTypes don't match.");
+    });
+  },
+});
+
+declTest("getActor without allFrames", {
+  allFrames: false,
+
+  async test(browser) {
+    await ContentTask.spawn(browser, {}, async function() {
+      // Create and append an iframe into the window's document.
+      let frame = content.document.createElement("iframe");
+      content.document.body.appendChild(frame);
+      is(content.window.frames.length, 1, "There should be an iframe.");
+      let child = frame.contentWindow.window.getWindowGlobalChild();
+      Assert.throws(() => child.getActor("Test"),
+          /NS_ERROR_NOT_AVAILABLE/, "Should throw if allFrames is false.");
+    });
+  },
+});
+
+declTest("getActor with allFrames", {
+  allFrames: true,
+
+  async test(browser) {
+    await ContentTask.spawn(browser, {}, async function() {
+      // Create and append an iframe into the window's document.
+      let frame = content.document.createElement("iframe");
+      content.document.body.appendChild(frame);
+      is(content.window.frames.length, 1, "There should be an iframe.");
+      let child = frame.contentWindow.window.getWindowGlobalChild();
+      let actorChild = child.getActor("Test");
+      ok(actorChild, "JSWindowActorChild should have value.");
+    });
+  },
+});
+
+declTest("getActor without includeChrome", {
+  includeChrome: false,
+
+  async test(_browser, win) {
+    let parent = win.docShell.browsingContext.currentWindowGlobal;
+    SimpleTest.doesThrow(() =>
+      parent.getActor("Test"),
+      "Should throw if includeChrome is false.");
+  },
+});
+
+declTest("getActor with includeChrome", {
+  includeChrome: true,
+
+  async test(_browser, win) {
+    let parent = win.docShell.browsingContext.currentWindowGlobal;
+    let actorParent = parent.getActor("Test");
+    ok(actorParent, "JSWindowActorParent should have value.");
+  },
+});
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/JSWindowActor/browser_observer_notification.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+declTest("test observer triggering actor creation", {
+  async test(browser) {
+    await ContentTask.spawn(browser, {}, async function() {
+      const TOPIC = "test-js-window-actor-child-observer";
+      Services.obs.notifyObservers(content.window, TOPIC, "dataString");
+
+      let child = content.window.getWindowGlobalChild();
+      let actorChild = child.getActor("Test");
+      ok(actorChild, "JSWindowActorChild should have value.");
+      let {subject, topic, data} = actorChild.lastObserved;
+
+      is(subject.getWindowGlobalChild().getActor("Test"), actorChild, "Should have been recieved on the right actor");
+      is(topic, TOPIC, "Topic matches");
+      is(data, "dataString", "Data matches");
+    });
+  },
+});
+
+declTest("test observers with null data", {
+  async test(browser) {
+    await ContentTask.spawn(browser, {}, async function() {
+      const TOPIC = "test-js-window-actor-child-observer";
+      Services.obs.notifyObservers(content.window, TOPIC);
+
+      let child = content.window.getWindowGlobalChild();
+      let actorChild = child.getActor("Test");
+      ok(actorChild, "JSWindowActorChild should have value.");
+      let {subject, topic, data} = actorChild.lastObserved;
+
+      is(subject.getWindowGlobalChild().getActor("Test"), actorChild, "Should have been recieved on the right actor");
+      is(topic, TOPIC, "Topic matches");
+      is(data, null, "Data matches");
+    });
+  },
+});
+
+declTest("observers don't notify with wrong window", {
+  async test(browser) {
+    await ContentTask.spawn(browser, {}, async function() {
+      const TOPIC = "test-js-window-actor-child-observer";
+      Services.obs.notifyObservers(null, TOPIC);
+      let child = content.window.getWindowGlobalChild();
+      let actorChild = child.getActor("Test");
+      ok(actorChild, "JSWindowActorChild should have value.");
+      is(actorChild.lastObserved, undefined, "Should not receive wrong window's observer notification!");
+    });
+  },
+});
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/JSWindowActor/browser_registerWindowActor.js
@@ -0,0 +1,11 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+declTest("double register", {
+  async test() {
+    SimpleTest.doesThrow(() =>
+      ChromeUtils.registerWindowActor("Test", windowActorOptions),
+      "Should throw if register has duplicate name.");
+  },
+});
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/JSWindowActor/browser_sendAsyncMessage.js
@@ -0,0 +1,51 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+declTest("asyncMessage testing", {
+  async test(browser) {
+    let parent = browser.browsingContext.currentWindowGlobal;
+    let actorParent = parent.getActor("Test");
+    ok(actorParent, "JSWindowActorParent should have value.");
+
+    await ContentTask.spawn(browser, {}, async function() {
+      let child = content.window.getWindowGlobalChild();
+      let actorChild = child.getActor("Test");
+      ok(actorChild, "JSWindowActorChild should have value.");
+
+      let promise = new Promise(resolve => {
+        actorChild.sendAsyncMessage("init", {});
+        actorChild.done = (data) => resolve(data);
+      }).then(data => {
+        ok(data.initial, "Initial should be true.");
+        ok(data.toParent, "ToParent should be true.");
+        ok(data.toChild, "ToChild should be true.");
+      });
+
+      await promise;
+    });
+  },
+});
+
+declTest("asyncMessage without both sides", {
+  async test(browser) {
+    // If we don't create a parent actor, make sure the parent actor
+    // gets created by having sent the message.
+    await ContentTask.spawn(browser, {}, async function() {
+      let child = content.window.getWindowGlobalChild();
+      let actorChild = child.getActor("Test");
+      ok(actorChild, "JSWindowActorChild should have value.");
+
+      let promise = new Promise(resolve => {
+        actorChild.sendAsyncMessage("init", {});
+        actorChild.done = (data) => resolve(data);
+      }).then(data => {
+        ok(data.initial, "Initial should be true.");
+        ok(data.toParent, "ToParent should be true.");
+        ok(data.toChild, "ToChild should be true.");
+      });
+
+      await promise;
+    });
+  },
+});
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/JSWindowActor/browser_sendQuery.js
@@ -0,0 +1,14 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+declTest("sendQuery testing", {
+  async test(browser) {
+    let parent = browser.browsingContext.currentWindowGlobal;
+    let actorParent = parent.getActor("Test");
+    ok(actorParent, "JSWindowActorParent should have value.");
+
+    let {result} = await actorParent.sendQuery("asyncAdd", {a: 10, b: 20});
+    is(result, 30);
+  },
+});
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/JSWindowActor/head.js
@@ -0,0 +1,75 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Provide infrastructure for JSWindowActor tests.
+ */
+
+const URL = "about:blank";
+const TEST_URL = "http://test2.example.org/";
+let windowActorOptions = {
+  parent: {
+    moduleURI: "resource://testing-common/TestParent.jsm",
+  },
+  child: {
+    moduleURI: "resource://testing-common/TestChild.jsm",
+
+    events: {
+      "mozshowdropdown": {},
+    },
+
+    observers: [
+      "test-js-window-actor-child-observer",
+    ],
+  },
+};
+
+function declTest(name, cfg) {
+  let {
+    url = "about:blank",
+    allFrames = false,
+    includeChrome = false,
+    matches,
+    remoteTypes,
+    fission,
+    test,
+  } = cfg;
+
+  // Build the actor options object which will be used to register & unregister our window actor.
+  let actorOptions = {
+    parent: Object.assign({}, windowActorOptions.parent),
+    child: Object.assign({}, windowActorOptions.child),
+  };
+  actorOptions.allFrames = allFrames;
+  actorOptions.includeChrome = includeChrome;
+  if (matches !== undefined) {
+    actorOptions.matches = matches;
+  }
+  if (remoteTypes !== undefined) {
+    actorOptions.remoteTypes = remoteTypes;
+  }
+
+  // Add a new task for the actor test declared here.
+  add_task(async function() {
+    info("Entering test: " + name);
+
+    // Create a fresh window with the correct settings, and register our actor.
+    let win = await BrowserTestUtils.openNewBrowserWindow({remote: true, fission});
+    ChromeUtils.registerWindowActor("Test", actorOptions);
+
+    // Wait for the provided URL to load in our browser
+    let browser = win.gBrowser.selectedBrowser;
+    BrowserTestUtils.loadURI(browser, url);
+    await BrowserTestUtils.browserLoaded(browser);
+
+    // Run the provided test
+    info("browser ready");
+    await Promise.resolve(test(browser, win));
+
+    // Clean up after we're done.
+    ChromeUtils.unregisterWindowActor("Test");
+    await BrowserTestUtils.closeWindow(win);
+
+    info("Exiting test: " + name);
+  });
+}
--- a/dom/ipc/tests/browser.ini
+++ b/dom/ipc/tests/browser.ini
@@ -4,11 +4,10 @@ support-files =
   file_domainPolicy_base.html
   file_cancel_content_js.html
 
 [browser_domainPolicy.js]
 [browser_memory_distribution_telemetry.js]
 skip-if = !e10 # This is an e10s only probe.
 [browser_remote_navigation_delay_telemetry.js]
 skip-if = !e10s # This is an e10s only probe.
-[browser_JSWindowActor.js]
 [browser_cancel_content_js.js]
 skip-if = !e10s # This is an e10s only probe.
\ No newline at end of file
deleted file mode 100644
--- a/dom/ipc/tests/browser_JSWindowActor.js
+++ /dev/null
@@ -1,479 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-"use strict";
-
-// This test opens and closes a large number of windows, which can be slow
-// especially on debug builds. This decreases the likelihood of the test timing
-// out.
-requestLongerTimeout(4);
-
-const URL = "about:blank";
-const TEST_URL = "http://test2.example.org/";
-let windowActorOptions = {
-  parent: {
-    moduleURI: "resource://testing-common/TestParent.jsm",
-  },
-  child: {
-    moduleURI: "resource://testing-common/TestChild.jsm",
-
-    events: {
-      "mozshowdropdown": {},
-    },
-
-    observers: [
-      "test-js-window-actor-child-observer",
-    ],
-  },
-};
-
-function declTest(name, cfg) {
-  let {
-    url = "about:blank",
-    allFrames = false,
-    includeChrome = false,
-    matches,
-    remoteTypes,
-    fission,
-    test,
-  } = cfg;
-
-  // Build the actor options object which will be used to register & unregister our window actor.
-  let actorOptions = {
-    parent: Object.assign({}, windowActorOptions.parent),
-    child: Object.assign({}, windowActorOptions.child),
-  };
-  actorOptions.allFrames = allFrames;
-  actorOptions.includeChrome = includeChrome;
-  if (matches !== undefined) {
-    actorOptions.matches = matches;
-  }
-  if (remoteTypes !== undefined) {
-    actorOptions.remoteTypes = remoteTypes;
-  }
-
-  // Add a new task for the actor test declared here.
-  add_task(async function() {
-    info("Entering test: " + name);
-
-    // Create a fresh window with the correct settings, and register our actor.
-    let win = await BrowserTestUtils.openNewBrowserWindow({remote: true, fission});
-    ChromeUtils.registerWindowActor("Test", actorOptions);
-
-    // Wait for the provided URL to load in our browser
-    let browser = win.gBrowser.selectedBrowser;
-    BrowserTestUtils.loadURI(browser, url);
-    await BrowserTestUtils.browserLoaded(browser);
-
-    // Run the provided test
-    info("browser ready");
-    await Promise.resolve(test(browser, win));
-
-    // Clean up after we're done.
-    ChromeUtils.unregisterWindowActor("Test");
-    await BrowserTestUtils.closeWindow(win);
-
-    info("Exiting test: " + name);
-  });
-}
-
-declTest("double register", {
-  async test() {
-    SimpleTest.doesThrow(() =>
-      ChromeUtils.registerWindowActor("Test", windowActorOptions),
-      "Should throw if register has duplicate name.");
-  },
-});
-
-declTest("getActor on both sides", {
-  async test(browser) {
-    let parent = browser.browsingContext.currentWindowGlobal;
-    ok(parent, "WindowGlobalParent should have value.");
-    let actorParent = parent.getActor("Test");
-    is(actorParent.show(), "TestParent", "actor show should have vaule.");
-    is(actorParent.manager, parent, "manager should match WindowGlobalParent.");
-
-    await ContentTask.spawn(browser, {}, async function() {
-      let child = content.window.getWindowGlobalChild();
-      ok(child, "WindowGlobalChild should have value.");
-      is(child.isInProcess, false, "Actor should be loaded in the content process.");
-      let actorChild = child.getActor("Test");
-      is(actorChild.show(), "TestChild", "actor show should have vaule.");
-      is(actorChild.manager, child, "manager should match WindowGlobalChild.");
-    });
-  },
-});
-
-declTest("asyncMessage testing", {
-  async test(browser) {
-    let parent = browser.browsingContext.currentWindowGlobal;
-    let actorParent = parent.getActor("Test");
-    ok(actorParent, "JSWindowActorParent should have value.");
-
-    await ContentTask.spawn(browser, {}, async function() {
-      let child = content.window.getWindowGlobalChild();
-      let actorChild = child.getActor("Test");
-      ok(actorChild, "JSWindowActorChild should have value.");
-
-      let promise = new Promise(resolve => {
-        actorChild.sendAsyncMessage("init", {});
-        actorChild.done = (data) => resolve(data);
-      }).then(data => {
-        ok(data.initial, "Initial should be true.");
-        ok(data.toParent, "ToParent should be true.");
-        ok(data.toChild, "ToChild should be true.");
-      });
-
-      await promise;
-    });
-  },
-});
-
-declTest("sendQuery testing", {
-  async test(browser) {
-    let parent = browser.browsingContext.currentWindowGlobal;
-    let actorParent = parent.getActor("Test");
-    ok(actorParent, "JSWindowActorParent should have value.");
-
-    let {result} = await actorParent.sendQuery("asyncAdd", {a: 10, b: 20});
-    is(result, 30);
-  },
-});
-
-declTest("asyncMessage without both sides", {
-  async test(browser) {
-    // If we don't create a parent actor, make sure the parent actor
-    // gets created by having sent the message.
-    await ContentTask.spawn(browser, {}, async function() {
-      let child = content.window.getWindowGlobalChild();
-      let actorChild = child.getActor("Test");
-      ok(actorChild, "JSWindowActorChild should have value.");
-
-      let promise = new Promise(resolve => {
-        actorChild.sendAsyncMessage("init", {});
-        actorChild.done = (data) => resolve(data);
-      }).then(data => {
-        ok(data.initial, "Initial should be true.");
-        ok(data.toParent, "ToParent should be true.");
-        ok(data.toChild, "ToChild should be true.");
-      });
-
-      await promise;
-    });
-  },
-});
-
-declTest("test event triggering actor creation", {
-  async test(browser) {
-    // Add a select element to the DOM of the loaded document.
-    await ContentTask.spawn(browser, {}, async function() {
-      content.document.body.innerHTML += `
-        <select id="testSelect">
-          <option>A</option>
-          <option>B</option>
-        </select>`;
-    });
-
-    // Wait for the observer notification.
-    let observePromise = new Promise(resolve => {
-      const TOPIC = "test-js-window-actor-parent-event";
-      Services.obs.addObserver(function obs(subject, topic, data) {
-        is(topic, TOPIC, "topic matches");
-
-        Services.obs.removeObserver(obs, TOPIC);
-        resolve({subject, data});
-      }, TOPIC);
-    });
-
-    // Click on the select to show the dropdown.
-    await BrowserTestUtils.synthesizeMouseAtCenter("#testSelect", {}, browser);
-
-    // Wait for the observer notification to fire, and inspect the results.
-    let {subject, data} = await observePromise;
-    is(data, "mozshowdropdown");
-
-    let parent = browser.browsingContext.currentWindowGlobal;
-    let actorParent = parent.getActor("Test");
-    ok(actorParent, "JSWindowActorParent should have value.");
-    is(subject.wrappedJSObject, actorParent, "Should have been recieved on the right actor");
-  },
-});
-
-declTest("test observer triggering actor creation", {
-  async test(browser) {
-    await ContentTask.spawn(browser, {}, async function() {
-      const TOPIC = "test-js-window-actor-child-observer";
-      Services.obs.notifyObservers(content.window, TOPIC, "dataString");
-
-      let child = content.window.getWindowGlobalChild();
-      let actorChild = child.getActor("Test");
-      ok(actorChild, "JSWindowActorChild should have value.");
-      let {subject, topic, data} = actorChild.lastObserved;
-
-      is(subject.getWindowGlobalChild().getActor("Test"), actorChild, "Should have been recieved on the right actor");
-      is(topic, TOPIC, "Topic matches");
-      is(data, "dataString", "Data matches");
-    });
-  },
-});
-
-declTest("test observers with null data", {
-  async test(browser) {
-    await ContentTask.spawn(browser, {}, async function() {
-      const TOPIC = "test-js-window-actor-child-observer";
-      Services.obs.notifyObservers(content.window, TOPIC);
-
-      let child = content.window.getWindowGlobalChild();
-      let actorChild = child.getActor("Test");
-      ok(actorChild, "JSWindowActorChild should have value.");
-      let {subject, topic, data} = actorChild.lastObserved;
-
-      is(subject.getWindowGlobalChild().getActor("Test"), actorChild, "Should have been recieved on the right actor");
-      is(topic, TOPIC, "Topic matches");
-      is(data, null, "Data matches");
-    });
-  },
-});
-
-declTest("observers don't notify with wrong window", {
-  async test(browser) {
-    await ContentTask.spawn(browser, {}, async function() {
-      const TOPIC = "test-js-window-actor-child-observer";
-      Services.obs.notifyObservers(null, TOPIC);
-      let child = content.window.getWindowGlobalChild();
-      let actorChild = child.getActor("Test");
-      ok(actorChild, "JSWindowActorChild should have value.");
-      is(actorChild.lastObserved, undefined, "Should not receive wrong window's observer notification!");
-    });
-  },
-});
-
-declTest("getActor with mismatch", {
-  matches: ["*://*/*"],
-
-  async test(browser) {
-    let parent = browser.browsingContext.currentWindowGlobal;
-    ok(parent, "WindowGlobalParent should have value.");
-    Assert.throws(() => parent.getActor("Test"),
-          /NS_ERROR_NOT_AVAILABLE/, "Should throw if it doesn't match.");
-
-    await ContentTask.spawn(browser, {}, async function() {
-      let child = content.window.getWindowGlobalChild();
-      ok(child, "WindowGlobalChild should have value.");
-
-      Assert.throws(() => child.getActor("Test"),
-        /NS_ERROR_NOT_AVAILABLE/, "Should throw if it doesn't match.");
-    });
-  },
-});
-
-declTest("getActor with matches", {
-  matches: ["*://*/*"],
-  url: TEST_URL,
-
-  async test(browser) {
-    let parent = browser.browsingContext.currentWindowGlobal;
-    ok(parent.getActor("Test"), "JSWindowActorParent should have value.");
-
-    await ContentTask.spawn(browser, {}, async function() {
-      let child = content.window.getWindowGlobalChild();
-      ok(child, "WindowGlobalChild should have value.");
-      ok(child.getActor("Test"), "JSWindowActorChild should have value.");
-    });
-  },
-});
-
-declTest("getActor with iframe matches", {
-  allFrames: true,
-  matches: ["*://*/*"],
-
-  async test(browser) {
-    await ContentTask.spawn(browser, TEST_URL, async function(url) {
-      // Create and append an iframe into the window's document.
-      let frame = content.document.createElement("iframe");
-      frame.src = url;
-      content.document.body.appendChild(frame);
-      await ContentTaskUtils.waitForEvent(frame, "load");
-
-      is(content.window.frames.length, 1, "There should be an iframe.");
-      let child = frame.contentWindow.window.getWindowGlobalChild();
-      ok(child.getActor("Test"), "JSWindowActorChild should have value.");
-    });
-  },
-});
-
-declTest("getActor with iframe mismatch", {
-  allFrames: true,
-  matches: ["about:home"],
-
-  async test(browser) {
-    await ContentTask.spawn(browser, TEST_URL, async function(url) {
-      // Create and append an iframe into the window's document.
-      let frame = content.document.createElement("iframe");
-      frame.src = url;
-      content.document.body.appendChild(frame);
-      await ContentTaskUtils.waitForEvent(frame, "load");
-
-      is(content.window.frames.length, 1, "There should be an iframe.");
-      let child = frame.contentWindow.window.getWindowGlobalChild();
-      Assert.throws(() => child.getActor("Test"),
-        /NS_ERROR_NOT_AVAILABLE/, "Should throw if it doesn't match.");
-    });
-  },
-});
-
-declTest("getActor with remoteType match", {
-  remoteTypes: ["web"],
-
-  async test(browser) {
-    let parent = browser.browsingContext.currentWindowGlobal;
-    ok(parent.getActor("Test"), "JSWindowActorParent should have value.");
-
-    await ContentTask.spawn(browser, {}, async function() {
-      let child = content.window.getWindowGlobalChild();
-      ok(child, "WindowGlobalChild should have value.");
-      ok(child.getActor("Test"), "JSWindowActorChild should have value.");
-    });
-  },
-});
-
-declTest("getActor with remoteType mismatch", {
-  remoteTypes: ["privileged"],
-  url: TEST_URL,
-
-  async test(browser) {
-    let parent = browser.browsingContext.currentWindowGlobal;
-    Assert.throws(() => parent.getActor("Test"),
-          /NS_ERROR_NOT_AVAILABLE/, "Should throw if its remoteTypes don't match.");
-
-    await ContentTask.spawn(browser, {}, async function() {
-      let child = content.window.getWindowGlobalChild();
-      ok(child, "WindowGlobalChild should have value.");
-      Assert.throws(() => child.getActor("Test"),
-          /NS_ERROR_NOT_AVAILABLE/, "Should throw if its remoteTypes don't match.");
-    });
-  },
-});
-
-declTest("getActor without allFrames", {
-  allFrames: false,
-
-  async test(browser) {
-    await ContentTask.spawn(browser, {}, async function() {
-      // Create and append an iframe into the window's document.
-      let frame = content.document.createElement("iframe");
-      content.document.body.appendChild(frame);
-      is(content.window.frames.length, 1, "There should be an iframe.");
-      let child = frame.contentWindow.window.getWindowGlobalChild();
-      Assert.throws(() => child.getActor("Test"),
-          /NS_ERROR_NOT_AVAILABLE/, "Should throw if allFrames is false.");
-    });
-  },
-});
-
-declTest("getActor with allFrames", {
-  allFrames: true,
-
-  async test(browser) {
-    await ContentTask.spawn(browser, {}, async function() {
-      // Create and append an iframe into the window's document.
-      let frame = content.document.createElement("iframe");
-      content.document.body.appendChild(frame);
-      is(content.window.frames.length, 1, "There should be an iframe.");
-      let child = frame.contentWindow.window.getWindowGlobalChild();
-      let actorChild = child.getActor("Test");
-      ok(actorChild, "JSWindowActorChild should have value.");
-    });
-  },
-});
-
-declTest("getActor without includeChrome", {
-  includeChrome: false,
-
-  async test(_browser, win) {
-    let parent = win.docShell.browsingContext.currentWindowGlobal;
-    SimpleTest.doesThrow(() =>
-      parent.getActor("Test"),
-      "Should throw if includeChrome is false.");
-  },
-});
-
-declTest("getActor with includeChrome", {
-  includeChrome: true,
-
-  async test(_browser, win) {
-    let parent = win.docShell.browsingContext.currentWindowGlobal;
-    let actorParent = parent.getActor("Test");
-    ok(actorParent, "JSWindowActorParent should have value.");
-  },
-});
-
-declTest("destroy actor by iframe remove", {
-  allFrames: true,
-
-  async test(browser) {
-    await ContentTask.spawn(browser, {}, async function() {
-      // Create and append an iframe into the window's document.
-      let frame = content.document.createElement("iframe");
-      frame.id = "frame";
-      content.document.body.appendChild(frame);
-      await ContentTaskUtils.waitForEvent(frame, "load");
-      is(content.window.frames.length, 1, "There should be an iframe.");
-      let child = frame.contentWindow.window.getWindowGlobalChild();
-      let actorChild = child.getActor("Test");
-      ok(actorChild, "JSWindowActorChild should have value.");
-
-      let willDestroyPromise = new Promise(resolve => {
-        const TOPIC = "test-js-window-actor-willdestroy";
-        Services.obs.addObserver(function obs(subject, topic, data) {
-          ok(data, "willDestroyCallback data should be true.");
-
-          Services.obs.removeObserver(obs, TOPIC);
-          resolve();
-        }, TOPIC);
-      });
-
-      let didDestroyPromise = new Promise(resolve => {
-        const TOPIC = "test-js-window-actor-diddestroy";
-        Services.obs.addObserver(function obs(subject, topic, data) {
-          ok(data, "didDestroyCallback data should be true.");
-
-          Services.obs.removeObserver(obs, TOPIC);
-          resolve();
-        }, TOPIC);
-      });
-
-      info("Remove frame");
-      content.document.getElementById("frame").remove();
-      await Promise.all([willDestroyPromise, didDestroyPromise]);
-
-      Assert.throws(() => child.getActor("Test"),
-        /InvalidStateError/, "Should throw if frame destroy.");
-    });
-  },
-});
-
-declTest("destroy actor by page navigates", {
-  allFrames: true,
-
-  async test(browser) {
-    info("creating an in-process frame");
-    await ContentTask.spawn(browser, URL, async function(url) {
-      let frame = content.document.createElement("iframe");
-      frame.src = url;
-      content.document.body.appendChild(frame);
-    });
-
-    info("navigating page");
-    await ContentTask.spawn(browser, TEST_URL, async function(url) {
-      let frame = content.document.querySelector("iframe");
-      frame.contentWindow.location = url;
-      let child = frame.contentWindow.window.getWindowGlobalChild();
-      let actorChild = child.getActor("Test");
-      ok(actorChild, "JSWindowActorChild should have value.");
-      await ContentTaskUtils.waitForEvent(frame, "load");
-
-      Assert.throws(() => child.getActor("Test"),
-              /InvalidStateError/, "Should throw if frame destroy.");
-    });
-  },
-});
--- a/dom/ipc/tests/chrome.ini
+++ b/dom/ipc/tests/chrome.ini
@@ -1,8 +1,7 @@
 [DEFAULT]
 skip-if = os == 'android'
 support-files =
   process_error.xul
 
 [test_process_error.xul]
-skip-if = !crashreporter
-[test_JSWindowActor.xul]
\ No newline at end of file
+skip-if = !crashreporter
\ No newline at end of file
deleted file mode 100644
--- a/dom/ipc/tests/test_JSWindowActor.xul
+++ /dev/null
@@ -1,96 +0,0 @@
-<?xml version="1.0"?>
-<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
-<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
-                 type="text/css"?>
-<window title="Test JSWindowActor"
-  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
-  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-
-  <!-- test results are displayed in the html:body -->
-  <body xmlns="http://www.w3.org/1999/xhtml">
-  </body>
-
-  <!-- test code goes here -->
-  <script type="application/javascript"><![CDATA[
-  const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
-  const URL = "about:blank";
-  let windowActorOptions = {
-    allFrames: true,
-    parent: {
-      moduleURI: "resource://testing-common/TestParent.jsm",
-    },
-    child: {
-      moduleURI: "resource://testing-common/TestChild.jsm",
-      observers: [
-        "test-js-window-actor-child-observer",
-      ],
-    },
-  };
-
-  add_task(async function registerWindowActor() {
-    ok(ChromeUtils, "Should be able to get the ChromeUtils interface");
-    ChromeUtils.registerWindowActor("Test", windowActorOptions);
-    SimpleTest.doesThrow(() =>
-      ChromeUtils.registerWindowActor("Test", windowActorOptions),
-      "Should throw if register has duplicate name.");
-    ChromeUtils.unregisterWindowActor("Test");
-  });
-
-  add_task(async function getActor() {
-    // Test in-process getActor function
-    ChromeUtils.registerWindowActor("Test", windowActorOptions);
-    let parent = this.window.docShell.browsingContext.currentWindowGlobal;
-    ok(parent, "WindowGlobalParent should have value.");
-    let actorParent = parent.getActor("Test");
-    is(actorParent.show(), "TestParent", "actor show should have vaule.");
-    is(actorParent.manager, parent, "manager should match WindowGlobalParent.");
-
-    let child = this.window.getWindowGlobalChild();
-    ok(child, "WindowGlobalChild should have value.");
-    is(child.isInProcess, true, "Actor should be in-process.");
-    let actorChild = child.getActor("Test");
-
-    is(actorChild.show(), "TestChild", "actor show should have vaule.");
-    is(actorChild.manager, child, "manager should match WindowGlobalChild.");
-    ok(parent.childActor===child, "Actor should be the same.");
-    ok(parent.childActor.getActor("Test")===child.getActor("Test"), "GetActor should be the same.");
-    ChromeUtils.unregisterWindowActor("Test");
-  });
-
-  add_task(async function asyncMessage() {
-    // Test in-process send/recvAsyncMessage function
-    ChromeUtils.registerWindowActor("Test", windowActorOptions);
-    let child = this.window.getWindowGlobalChild();
-    let actorChild = child.getActor("Test");
-    let promise = new Promise(resolve => {
-      actorChild.sendAsyncMessage("init", {});
-      actorChild.done = (data) => resolve(data);
-    }).then(data => {
-      ok(data.initial, "Initial should be true.");
-      ok(data.toParent, "ToParent should be true.");
-      ok(data.toChild, "ToChild should be true.");
-    });
-
-    await promise;
-    ChromeUtils.unregisterWindowActor("Test");
-  });
-
-  add_task(async function observers() {
-    // Test in-process observers notification
-    ChromeUtils.registerWindowActor("Test", windowActorOptions);
-    const TOPIC = "test-js-window-actor-child-observer";
-    Services.obs.notifyObservers(content.window, TOPIC, "dataString");
-
-    let child = content.window.getWindowGlobalChild();
-    let actorChild = child.getActor("Test");
-    ok(actorChild, "JSWindowActorChild should have value.");
-    let {subject, topic, data} = actorChild.lastObserved;
-
-    is(subject.getWindowGlobalChild().getActor("Test"), actorChild, "Should have been recieved on the right actor");
-    is(topic, TOPIC, "Topic matches");
-    is(data, "dataString", "Data matches");
-    ChromeUtils.unregisterWindowActor("Test");
-  });
-
-  ]]></script>
-</window>
--- a/gfx/wr/webrender/src/border.rs
+++ b/gfx/wr/webrender/src/border.rs
@@ -649,16 +649,27 @@ pub fn create_border_segments(
     border_segments: &mut Vec<BorderSegmentInfo>,
     brush_segments: &mut Vec<BrushSegment>,
 ) {
     let rect = LayoutRect::new(
         LayoutPoint::zero(),
         size,
     );
 
+    let overlap = LayoutSize::new(
+        (widths.left + widths.right - size.width).max(0.0),
+        (widths.top + widths.bottom - size.height).max(0.0),
+    );
+    let non_overlapping_widths = LayoutSideOffsets::new(
+        widths.top - overlap.height / 2.0,
+        widths.right - overlap.width / 2.0,
+        widths.bottom - overlap.height / 2.0,
+        widths.left - overlap.width / 2.0,
+    );
+
     let local_size_tl = LayoutSize::new(
         border.radius.top_left.width.max(widths.left),
         border.radius.top_left.height.max(widths.top),
     );
     let local_size_tr = LayoutSize::new(
         border.radius.top_right.width.max(widths.right),
         border.radius.top_right.height.max(widths.top),
     );
@@ -692,70 +703,70 @@ pub fn create_border_segments(
         widths.right,
         rect.size.height - local_size_tr.height - local_size_br.height,
     );
 
     add_edge_segment(
         LayoutRect::from_floats(
             rect.origin.x,
             rect.origin.y + local_size_tl.height + left_edge_info.local_offset,
-            rect.origin.x + widths.left,
+            rect.origin.x + non_overlapping_widths.left,
             rect.origin.y + local_size_tl.height + left_edge_info.local_offset + left_edge_info.local_size,
         ),
         &left_edge_info,
         border.left,
-        widths.left,
+        non_overlapping_widths.left,
         BorderSegment::Left,
         EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::RIGHT,
         brush_segments,
         border_segments,
         border.do_aa,
     );
     add_edge_segment(
         LayoutRect::from_floats(
             rect.origin.x + local_size_tl.width + top_edge_info.local_offset,
             rect.origin.y,
             rect.origin.x + local_size_tl.width + top_edge_info.local_offset + top_edge_info.local_size,
-            rect.origin.y + widths.top,
+            rect.origin.y + non_overlapping_widths.top,
         ),
         &top_edge_info,
         border.top,
-        widths.top,
+        non_overlapping_widths.top,
         BorderSegment::Top,
         EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM,
         brush_segments,
         border_segments,
         border.do_aa,
     );
     add_edge_segment(
         LayoutRect::from_floats(
-            rect.origin.x + rect.size.width - widths.right,
+            rect.origin.x + rect.size.width - non_overlapping_widths.right,
             rect.origin.y + local_size_tr.height + right_edge_info.local_offset,
             rect.origin.x + rect.size.width,
             rect.origin.y + local_size_tr.height + right_edge_info.local_offset + right_edge_info.local_size,
         ),
         &right_edge_info,
         border.right,
-        widths.right,
+        non_overlapping_widths.right,
         BorderSegment::Right,
         EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::LEFT,
         brush_segments,
         border_segments,
         border.do_aa,
     );
     add_edge_segment(
         LayoutRect::from_floats(
             rect.origin.x + local_size_bl.width + bottom_edge_info.local_offset,
-            rect.origin.y + rect.size.height - widths.bottom,
+            rect.origin.y + rect.size.height - non_overlapping_widths.bottom,
             rect.origin.x + local_size_bl.width + bottom_edge_info.local_offset + bottom_edge_info.local_size,
             rect.origin.y + rect.size.height,
         ),
         &bottom_edge_info,
         border.bottom,
-        widths.bottom,
+        non_overlapping_widths.bottom,
         BorderSegment::Bottom,
         EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::TOP,
         brush_segments,
         border_segments,
         border.do_aa,
     );
 
     add_corner_segment(
@@ -763,18 +774,18 @@ pub fn create_border_segments(
             rect.origin.x,
             rect.origin.y,
             rect.origin.x + local_size_tl.width,
             rect.origin.y + local_size_tl.height,
         ),
         LayoutRect::from_floats(
             rect.origin.x,
             rect.origin.y,
-            rect.max_x() - widths.right,
-            rect.max_y() - widths.bottom
+            rect.max_x() - non_overlapping_widths.right,
+            rect.max_y() - non_overlapping_widths.bottom
         ),
         border.left,
         border.top,
         LayoutSize::new(widths.left, widths.top),
         border.radius.top_left,
         BorderSegment::TopLeft,
         EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::LEFT,
         rect.top_right(),
@@ -788,20 +799,20 @@ pub fn create_border_segments(
     add_corner_segment(
         LayoutRect::from_floats(
             rect.origin.x + rect.size.width - local_size_tr.width,
             rect.origin.y,
             rect.origin.x + rect.size.width,
             rect.origin.y + local_size_tr.height,
         ),
         LayoutRect::from_floats(
-            rect.origin.x + widths.left,
+            rect.origin.x + non_overlapping_widths.left,
             rect.origin.y,
             rect.max_x(),
-            rect.max_y() - widths.bottom,
+            rect.max_y() - non_overlapping_widths.bottom,
         ),
         border.top,
         border.right,
         LayoutSize::new(widths.right, widths.top),
         border.radius.top_right,
         BorderSegment::TopRight,
         EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT,
         rect.origin,
@@ -815,18 +826,18 @@ pub fn create_border_segments(
     add_corner_segment(
         LayoutRect::from_floats(
             rect.origin.x + rect.size.width - local_size_br.width,
             rect.origin.y + rect.size.height - local_size_br.height,
             rect.origin.x + rect.size.width,
             rect.origin.y + rect.size.height,
         ),
         LayoutRect::from_floats(
-            rect.origin.x + widths.left,
-            rect.origin.y + widths.top,
+            rect.origin.x + non_overlapping_widths.left,
+            rect.origin.y + non_overlapping_widths.top,
             rect.max_x(),
             rect.max_y(),
         ),
         border.right,
         border.bottom,
         LayoutSize::new(widths.right, widths.bottom),
         border.radius.bottom_right,
         BorderSegment::BottomRight,
@@ -843,18 +854,18 @@ pub fn create_border_segments(
         LayoutRect::from_floats(
             rect.origin.x,
             rect.origin.y + rect.size.height - local_size_bl.height,
             rect.origin.x + local_size_bl.width,
             rect.origin.y + rect.size.height,
         ),
         LayoutRect::from_floats(
             rect.origin.x,
-            rect.origin.y + widths.top,
-            rect.max_x() - widths.right,
+            rect.origin.y + non_overlapping_widths.top,
+            rect.max_x() - non_overlapping_widths.right,
             rect.max_y(),
         ),
         border.bottom,
         border.left,
         LayoutSize::new(widths.left, widths.bottom),
         border.radius.bottom_left,
         BorderSegment::BottomLeft,
         EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::LEFT,
rename from gfx/wr/wrench/reftests/border/border-overlapping-ref.yaml
rename to gfx/wr/wrench/reftests/border/border-overlapping-corner-ref.yaml
rename from gfx/wr/wrench/reftests/border/border-overlapping.yaml
rename to gfx/wr/wrench/reftests/border/border-overlapping-corner.yaml
--- a/gfx/wr/wrench/reftests/border/border-overlapping.yaml
+++ b/gfx/wr/wrench/reftests/border/border-overlapping-corner.yaml
@@ -1,9 +1,9 @@
----
+--- # Checks that corners are clipped correctly when they overlap with an adjacent corner
 root:
   items:
     - type: stacking-context
       bounds: [0, 0, 200, 200]
       items:
         - type: border
           bounds: [ 10, 10, 180, 180 ]
           width: 90
new file mode 100644
--- /dev/null
+++ b/gfx/wr/wrench/reftests/border/border-overlapping-edge-ref.yaml
@@ -0,0 +1,9 @@
+---
+root:
+  items:
+    - type: stacking-context
+      bounds: [0, 0, 120, 40]
+      items:
+        - type: rect
+          bounds: [ 10, 10, 100, 20 ]
+          color: [ 0, 0, 255, 0.5 ]
new file mode 100644
--- /dev/null
+++ b/gfx/wr/wrench/reftests/border/border-overlapping-edge.yaml
@@ -0,0 +1,12 @@
+--- # Checks that segments are clipped correctly when opposite edges of the border overlap
+root:
+  items:
+    - type: stacking-context
+      bounds: [0, 0, 120, 40]
+      items:
+        - type: border
+          bounds: [ 10, 10, 100, 20 ]
+          width: 15
+          border-type: normal
+          style: solid
+          color: [ [0, 0, 255, 0.5] ]
--- a/gfx/wr/wrench/reftests/border/reftest.list
+++ b/gfx/wr/wrench/reftests/border/reftest.list
@@ -1,16 +1,17 @@
 platform(linux,mac) == border-clamp-corner-radius.yaml border-clamp-corner-radius.png
 fuzzy(1,790) == border-gradient-simple.yaml border-gradient-simple-ref.yaml
 platform(linux,mac) == border-gradient-nine-patch.yaml border-gradient-nine-patch.png
 == border-radial-gradient-simple.yaml border-radial-gradient-simple-ref.yaml
 platform(linux,mac) == border-radial-gradient-nine-patch.yaml border-radial-gradient-nine-patch.png
 == border-radii.yaml border-radii.png
 == border-none.yaml border-none-ref.yaml
-fuzzy(1,68) == border-overlapping.yaml border-overlapping-ref.yaml
+fuzzy(1,68) == border-overlapping-corner.yaml border-overlapping-corner-ref.yaml
+== border-overlapping-edge.yaml border-overlapping-edge-ref.yaml
 == border-invisible.yaml border-invisible-ref.yaml
 platform(linux,mac) == border-suite.yaml border-suite.png
 platform(linux,mac) fuzzy(8,8) == border-suite-2.yaml border-suite-2.png
 platform(linux,mac) == border-suite-3.yaml border-suite-3.png
 == border-double-simple.yaml border-double-simple-ref.yaml
 == border-double-simple-2.yaml border-double-simple-2-ref.yaml
 fuzzy(64,24) == border-groove-simple.yaml border-groove-simple-ref.yaml
 fuzzy(64,24) == border-ridge-simple.yaml border-ridge-simple-ref.yaml
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -158,18 +158,17 @@ MethodStatus BaselineCompiler::compile()
           script->filename(), script->lineno(), script->column());
 
   TraceLoggerThread* logger = TraceLoggerForCurrentThread(cx);
   TraceLoggerEvent scriptEvent(TraceLogger_AnnotateScripts, script);
   AutoTraceLog logScript(logger, scriptEvent);
   AutoTraceLog logCompile(logger, TraceLogger_BaselineCompilation);
 
   AutoKeepTypeScripts keepTypes(cx);
-  if (!script->ensureHasTypes(cx, keepTypes) ||
-      !script->ensureHasAnalyzedArgsUsage(cx)) {
+  if (!script->ensureHasTypes(cx, keepTypes)) {
     return Method_Error;
   }
 
   // When code coverage is only enabled for optimizations, or when a Debugger
   // set the collectCoverageInfo flag, we have to create the ScriptCounts if
   // they do not exist.
   if (!script->hasScriptCounts() && cx->realm()->collectCoverage()) {
     if (!script->initScriptCounts(cx)) {
@@ -411,17 +410,17 @@ static void LoadUint24Operand(MacroAssem
   masm.rshift32(Imm32(8), dest);
 }
 
 static void LoadInlineValueOperand(MacroAssembler& masm, Register pc,
                                    ValueOperand dest) {
   // Note: the Value might be unaligned but as above we rely on all our
   // platforms having appropriate support for unaligned accesses (except for
   // floating point instructions on ARM).
-  masm.loadValue(Address(pc, sizeof(jsbytecode)), dest);
+  masm.loadUnalignedValue(Address(pc, sizeof(jsbytecode)), dest);
 }
 
 template <>
 void BaselineCompilerCodeGen::loadScript(Register dest) {
   masm.movePtr(ImmGCPtr(handler.script()), dest);
 }
 
 template <>
@@ -792,16 +791,48 @@ void BaselineInterpreterCodeGen::emitIsD
 
   Label skipCheck;
   CodeOffset toggleOffset = masm.toggledJump(&skipCheck);
   EmitCallFrameIsDebuggeeCheck(masm);
   masm.bind(&skipCheck);
   handler.setDebuggeeCheckOffset(toggleOffset);
 }
 
+static void MaybeIncrementCodeCoverageCounter(MacroAssembler& masm,
+                                              JSScript* script,
+                                              jsbytecode* pc) {
+  if (!script->hasScriptCounts()) {
+    return;
+  }
+  PCCounts* counts = script->maybeGetPCCounts(pc);
+  uint64_t* counterAddr = &counts->numExec();
+  masm.inc64(AbsoluteAddress(counterAddr));
+}
+
+template <>
+bool BaselineCompilerCodeGen::emitHandleCodeCoverageAtPrologue() {
+  // If the main instruction is not a jump target, then we emit the
+  // corresponding code coverage counter.
+  JSScript* script = handler.script();
+  jsbytecode* main = script->main();
+  if (!BytecodeIsJumpTarget(JSOp(*main))) {
+    MaybeIncrementCodeCoverageCounter(masm, script, main);
+  }
+  return true;
+}
+
+template <>
+bool BaselineInterpreterCodeGen::emitHandleCodeCoverageAtPrologue() {
+  Label skipCoverage;
+  CodeOffset toggleOffset = masm.toggledJump(&skipCoverage);
+  masm.call(handler.codeCoverageAtPrologueLabel());
+  masm.bind(&skipCoverage);
+  return handler.codeCoverageOffsets().append(toggleOffset.offset());
+}
+
 template <>
 void BaselineCompilerCodeGen::subtractScriptSlotsSize(Register reg,
                                                       Register scratch) {
   uint32_t slotsSize = handler.script()->nslots() * sizeof(Value);
   masm.subPtr(Imm32(slotsSize), reg);
 }
 
 template <>
@@ -6106,31 +6137,33 @@ bool BaselineCodeGen<Handler>::emit_JSOP
 template <typename Handler>
 bool BaselineCodeGen<Handler>::emit_JSOP_IS_CONSTRUCTING() {
   frame.push(MagicValue(JS_IS_CONSTRUCTING));
   return true;
 }
 
 template <>
 bool BaselineCompilerCodeGen::emit_JSOP_JUMPTARGET() {
-  JSScript* script = handler.script();
-  if (!script->hasScriptCounts()) {
-    return true;
-  }
-  PCCounts* counts = script->maybeGetPCCounts(handler.pc());
-  uint64_t* counterAddr = &counts->numExec();
-  masm.inc64(AbsoluteAddress(counterAddr));
+  MaybeIncrementCodeCoverageCounter(masm, handler.script(), handler.pc());
   return true;
 }
 
 template <>
 bool BaselineInterpreterCodeGen::emit_JSOP_JUMPTARGET() {
   Register scratch1 = R0.scratchReg();
   Register scratch2 = R1.scratchReg();
 
+  Label skipCoverage;
+  CodeOffset toggleOffset = masm.toggledJump(&skipCoverage);
+  masm.call(handler.codeCoverageAtPCLabel());
+  masm.bind(&skipCoverage);
+  if (!handler.codeCoverageOffsets().append(toggleOffset.offset())) {
+    return false;
+  }
+
   // Load icIndex in scratch1.
   LoadInt32Operand(masm, PCRegAtStart, scratch1);
 
   // scratch1 := scratch1 * sizeof(ICEntry)
   static_assert(sizeof(ICEntry) == 8 || sizeof(ICEntry) == 16,
                 "shift below depends on ICEntry size");
   uint32_t shift = (sizeof(ICEntry) == 16) ? 4 : 3;
   masm.lshiftPtr(Imm32(shift), scratch1);
@@ -6462,16 +6495,20 @@ bool BaselineCodeGen<Handler>::emitProlo
   if (!emitStackCheck()) {
     return false;
   }
 
   if (!emitDebugPrologue()) {
     return false;
   }
 
+  if (!emitHandleCodeCoverageAtPrologue()) {
+    return false;
+  }
+
   if (!emitWarmUpCounterIncrement()) {
     return false;
   }
 
   warmUpCheckPrologueOffset_ = CodeOffset(masm.currentOffset());
 
   if (!emitArgumentTypeChecks()) {
     return false;
@@ -6581,24 +6618,16 @@ MethodStatus BaselineCompiler::emitBody(
     if (MOZ_UNLIKELY(!this->emit_##OP())) return Method_Error; \
     break;
         OPCODE_LIST(EMIT_OP)
 #undef EMIT_OP
     }
 
     MOZ_ASSERT(masm.framePushed() == 0);
 
-    // If the main instruction is not a jump target, then we emit the
-    // corresponding code coverage counter.
-    if (handler.pc() == script->main() && !BytecodeIsJumpTarget(op)) {
-      if (!emit_JSOP_JUMPTARGET()) {
-        return Method_Error;
-      }
-    }
-
     // Test if last instructions and stop emitting in that case.
     handler.moveToNextPC();
     if (handler.pc() >= script->codeEnd()) {
       break;
     }
 
     emittedOps++;
     lastOpUnreachable = false;
@@ -6625,19 +6654,157 @@ bool BaselineInterpreterGenerator::emitD
     ReportOutOfMemory(cx);
     return false;
   }
 
   return true;
 }
 
 bool BaselineInterpreterGenerator::emitInterpreterLoop() {
-  MOZ_CRASH("NYI: interpreter emitInterpreterLoop");
-
-  return true;
+  Register scratch1 = R0.scratchReg();
+  Register scratch2 = R1.scratchReg();
+
+  Address pcAddr = frame.addressOfInterpreterPC();
+
+  // Entry point for interpreting a bytecode op. No registers are live. PC is
+  // loaded from frame->interpreterPC.
+  masm.bind(handler.interpretOpLabel());
+  interpretOpOffset_ = masm.currentOffset();
+
+  // Emit a patchable call for debugger breakpoints/stepping.
+  if (!emitDebugTrap()) {
+    return false;
+  }
+
+  // Load pc, bytecode op.
+  masm.loadPtr(pcAddr, PCRegAtStart);
+  masm.load8ZeroExtend(Address(PCRegAtStart, 0), scratch1);
+
+  // Jump to table[op].
+  {
+    CodeOffset label = masm.movWithPatch(ImmWord(uintptr_t(-1)), scratch2);
+    if (!tableLabels_.append(label)) {
+      return false;
+    }
+    BaseIndex pointer(scratch2, scratch1, ScalePointer);
+    masm.branchToComputedAddress(pointer);
+  }
+
+  // At the end of each op, emit code to bump the pc and jump to the
+  // next op (this is also known as a threaded interpreter).
+  auto opEpilogue = [&](JSOp op, size_t opLength) -> bool {
+    MOZ_ASSERT(masm.framePushed() == 0);
+
+    if (!BytecodeFallsThrough(op)) {
+      // Nothing to do.
+      masm.assumeUnreachable("unexpected fall through");
+      return true;
+    }
+
+    // Bump frame->interpreterICEntry if needed.
+    if (BytecodeOpHasIC(op)) {
+      masm.addPtr(Imm32(sizeof(ICEntry)), frame.addressOfInterpreterICEntry());
+    }
+
+    // Bump frame->interpreterPC, keep pc in PCRegAtStart.
+    masm.loadPtr(pcAddr, PCRegAtStart);
+    masm.addPtr(Imm32(opLength), PCRegAtStart);
+    masm.storePtr(PCRegAtStart, pcAddr);
+
+    if (!emitDebugTrap()) {
+      return false;
+    }
+
+    // Load the opcode, jump to table[op].
+    masm.load8ZeroExtend(Address(PCRegAtStart, 0), scratch1);
+    CodeOffset label = masm.movWithPatch(ImmWord(uintptr_t(-1)), scratch2);
+    if (!tableLabels_.append(label)) {
+      return false;
+    }
+    BaseIndex pointer(scratch2, scratch1, ScalePointer);
+    masm.branchToComputedAddress(pointer);
+    return true;
+  };
+
+  // Emit code for each bytecode op.
+  Label opLabels[JSOP_LIMIT];
+#define EMIT_OP(OP)                     \
+  {                                     \
+    masm.bind(&opLabels[OP]);           \
+    if (!this->emit_##OP()) {           \
+      return false;                     \
+    }                                   \
+    if (!opEpilogue(OP, OP##_LENGTH)) { \
+      return false;                     \
+    }                                   \
+  }
+  OPCODE_LIST(EMIT_OP)
+#undef EMIT_OP
+
+  // Emit code for JSOP_UNUSED* ops.
+  Label invalidOp;
+  masm.bind(&invalidOp);
+  masm.assumeUnreachable("Invalid op");
+
+  // Emit the table.
+  masm.haltingAlign(sizeof(void*));
+
+#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64)
+  size_t numInstructions = JSOP_LIMIT * (sizeof(uintptr_t) / sizeof(uint32_t));
+  AutoForbidPoolsAndNops afp(&masm, numInstructions);
+#endif
+
+  tableOffset_ = masm.currentOffset();
+
+  for (size_t i = 0; i < JSOP_LIMIT; i++) {
+    // Store a pointer to the code for the current op. If the op's label is not
+    // bound it must be a JSOP_UNUSED* op and we use |invalidOp| instead.
+    const Label& opLabel = opLabels[i];
+    uint32_t opOffset = opLabel.bound() ? opLabel.offset() : invalidOp.offset();
+    CodeLabel cl;
+    masm.writeCodePointer(&cl);
+    cl.target()->bind(opOffset);
+    masm.addCodeLabel(cl);
+  }
+
+  return true;
+}
+
+void BaselineInterpreterGenerator::emitOutOfLineCodeCoverageInstrumentation() {
+  masm.bind(handler.codeCoverageAtPrologueLabel());
+#ifdef JS_USE_LINK_REGISTER
+  masm.pushReturnAddress();
+#endif
+
+  masm.Push(BaselineFrameReg);
+  masm.setupUnalignedABICall(R0.scratchReg());
+  masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
+  masm.passABIArg(R0.scratchReg());
+  masm.callWithABI(
+      JS_FUNC_TO_DATA_PTR(void*, jit::HandleCodeCoverageAtPrologue));
+  masm.Pop(BaselineFrameReg);
+
+  masm.ret();
+
+  masm.bind(handler.codeCoverageAtPCLabel());
+#ifdef JS_USE_LINK_REGISTER
+  masm.pushReturnAddress();
+#endif
+
+  masm.Push(BaselineFrameReg);
+  masm.Push(PCRegAtStart);
+  masm.setupUnalignedABICall(R0.scratchReg());
+  masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
+  masm.passABIArg(R0.scratchReg());
+  masm.passABIArg(PCRegAtStart);
+  masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, jit::HandleCodeCoverageAtPC));
+  masm.Pop(PCRegAtStart);
+  masm.Pop(BaselineFrameReg);
+
+  masm.ret();
 }
 
 bool BaselineInterpreterGenerator::generate(BaselineInterpreter& interpreter) {
   if (!emitPrologue()) {
     return false;
   }
 
   if (!emitInterpreterLoop()) {
@@ -6647,51 +6814,71 @@ bool BaselineInterpreterGenerator::gener
   if (!emitEpilogue()) {
     return false;
   }
 
   if (!emitOutOfLinePostBarrierSlot()) {
     return false;
   }
 
-  Linker linker(masm, "BaselineInterpreter");
-  if (masm.oom()) {
-    ReportOutOfMemory(cx);
-    return false;
-  }
-
-  JitCode* code = linker.newCode(cx, CodeKind::Other);
-  if (!code) {
-    return false;
-  }
+  emitOutOfLineCodeCoverageInstrumentation();
+
+  {
+    Linker linker(masm, "BaselineInterpreter");
+    if (masm.oom()) {
+      ReportOutOfMemory(cx);
+      return false;
+    }
+
+    JitCode* code = linker.newCode(cx, CodeKind::Other);
+    if (!code) {
+      return false;
+    }
+
+    // Patch loads now that we know the tableswitch base address.
+    for (CodeOffset off : tableLabels_) {
+      Assembler::PatchDataWithValueCheck(CodeLocationLabel(code, off),
+                                         ImmPtr(code->raw() + tableOffset_),
+                                         ImmPtr((void*)-1));
+    }
 
 #ifdef JS_ION_PERF
-  writePerfSpewerJitCodeProfile(code, "BaselineInterpreter");
+    writePerfSpewerJitCodeProfile(code, "BaselineInterpreter");
 #endif
 
 #ifdef MOZ_VTUNE
-  vtune::MarkStub(code, "BaselineInterpreter");
+    vtune::MarkStub(code, "BaselineInterpreter");
 #endif
 
-  interpreter.init(
-      code, interpretOpOffset_, profilerEnterFrameToggleOffset_.offset(),
-      profilerExitFrameToggleOffset_.offset(),
-      handler.debuggeeCheckOffset().offset(), std::move(debugTrapOffsets_));
+    interpreter.init(
+        code, interpretOpOffset_, profilerEnterFrameToggleOffset_.offset(),
+        profilerExitFrameToggleOffset_.offset(),
+        handler.debuggeeCheckOffset().offset(), std::move(debugTrapOffsets_),
+        std::move(handler.codeCoverageOffsets()));
+  }
+
+  if (coverage::IsLCovEnabled()) {
+    interpreter.toggleCodeCoverageInstrumentationUnchecked(true);
+  }
 
   return true;
 }
 
 JitCode* JitRuntime::generateDebugTrapHandler(JSContext* cx,
                                               DebugTrapHandlerKind kind) {
   StackMacroAssembler masm;
 
   AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All());
   regs.takeUnchecked(BaselineFrameReg);
   regs.takeUnchecked(ICStubReg);
   regs.takeUnchecked(PCRegAtStart);
+#ifdef JS_CODEGEN_ARM
+  regs.takeUnchecked(BaselineSecondScratchReg);
+  masm.setSecondScratchReg(BaselineSecondScratchReg);
+#endif
   Register scratch1 = regs.takeAny();
   Register scratch2 = regs.takeAny();
   Register scratch3 = regs.takeAny();
 
   if (kind == DebugTrapHandlerKind::Interpreter) {
     // The interpreter calls this for every script when debugging, so check if
     // the script has any breakpoints or is in step mode before calling into
     // C++.
--- a/js/src/jit/BaselineCompiler.h
+++ b/js/src/jit/BaselineCompiler.h
@@ -492,16 +492,18 @@ class BaselineCodeGen {
   MOZ_MUST_USE bool initEnvironmentChainHelper(const F1& initFunctionEnv,
                                                const F2& initGlobalOrEvalEnv,
                                                Register scratch);
   MOZ_MUST_USE bool initEnvironmentChain();
 
   MOZ_MUST_USE bool emitTraceLoggerEnter();
   MOZ_MUST_USE bool emitTraceLoggerExit();
 
+  MOZ_MUST_USE bool emitHandleCodeCoverageAtPrologue();
+
   void emitInitFrameFields();
   void emitIsDebuggeeCheck();
   void emitInitializeLocals();
   void emitPreInitEnvironmentChain(Register nonFunctionEnv);
 
   void emitProfilerEnterFrame();
   void emitProfilerExitFrame();
 };
@@ -651,24 +653,34 @@ class BaselineCompiler final : private B
 };
 
 // Interface used by BaselineCodeGen for BaselineInterpreterGenerator.
 class BaselineInterpreterHandler {
   InterpreterFrameInfo frame_;
   Label interpretOp_;
   CodeOffset debuggeeCheckOffset_;
 
+  // Offsets of toggled jumps for code coverage instrumentation.
+  using CodeOffsetVector = Vector<uint32_t, 0, SystemAllocPolicy>;
+  CodeOffsetVector codeCoverageOffsets_;
+  Label codeCoverageAtPrologueLabel_;
+  Label codeCoverageAtPCLabel_;
+
  public:
   using FrameInfoT = InterpreterFrameInfo;
 
   explicit BaselineInterpreterHandler(JSContext* cx, MacroAssembler& masm);
 
   InterpreterFrameInfo& frame() { return frame_; }
 
   Label* interpretOpLabel() { return &interpretOp_; }
+  Label* codeCoverageAtPrologueLabel() { return &codeCoverageAtPrologueLabel_; }
+  Label* codeCoverageAtPCLabel() { return &codeCoverageAtPCLabel_; }
+
+  CodeOffsetVector& codeCoverageOffsets() { return codeCoverageOffsets_; }
 
   // Interpreter doesn't know the script and pc statically.
   jsbytecode* maybePC() const { return nullptr; }
   bool isDefinitelyLastOp() const { return false; }
   JSScript* maybeScript() const { return nullptr; }
   JSFunction* maybeFunction() const { return nullptr; }
 
   void setDebuggeeCheckOffset(CodeOffset offset) {
@@ -692,27 +704,35 @@ class BaselineInterpreterHandler {
 
   JSObject* maybeNoCloneSingletonObject() { return nullptr; }
 };
 
 using BaselineInterpreterCodeGen = BaselineCodeGen<BaselineInterpreterHandler>;
 
 class BaselineInterpreterGenerator final : private BaselineInterpreterCodeGen {
   // Offsets of patchable call instructions for debugger breakpoints/stepping.
-  js::Vector<uint32_t, 0, SystemAllocPolicy> debugTrapOffsets_;
+  Vector<uint32_t, 0, SystemAllocPolicy> debugTrapOffsets_;
+
+  // Offsets of move instructions for tableswitch base address.
+  Vector<CodeOffset, 0, SystemAllocPolicy> tableLabels_;
+
+  // Offset of the first tableswitch entry.
+  uint32_t tableOffset_ = 0;
 
   // Offset of the code to start interpreting a bytecode op.
   uint32_t interpretOpOffset_ = 0;
 
  public:
   explicit BaselineInterpreterGenerator(JSContext* cx);
 
   MOZ_MUST_USE bool generate(BaselineInterpreter& interpreter);
 
  private:
   MOZ_MUST_USE bool emitInterpreterLoop();
   MOZ_MUST_USE bool emitDebugTrap();
+
+  void emitOutOfLineCodeCoverageInstrumentation();
 };
 
 }  // namespace jit
 }  // namespace js
 
 #endif /* jit_BaselineCompiler_h */
--- a/js/src/jit/BaselineJIT.cpp
+++ b/js/src/jit/BaselineJIT.cpp
@@ -1153,16 +1153,43 @@ void BaselineInterpreter::toggleDebugger
 
   // Toggle DebugTrapHandler calls.
   for (uint32_t offset : debugTrapOffsets_) {
     CodeLocationLabel trapLocation(code_, CodeOffset(offset));
     Assembler::ToggleCall(trapLocation, enable);
   }
 }
 
+void BaselineInterpreter::toggleCodeCoverageInstrumentationUnchecked(
+    bool enable) {
+  if (!JitOptions.baselineInterpreter) {
+    return;
+  }
+
+  AutoWritableJitCode awjc(code_);
+
+  for (uint32_t offset : codeCoverageOffsets_) {
+    CodeLocationLabel label(code_, CodeOffset(offset));
+    if (enable) {
+      Assembler::ToggleToCmp(label);
+    } else {
+      Assembler::ToggleToJmp(label);
+    }
+  }
+}
+
+void BaselineInterpreter::toggleCodeCoverageInstrumentation(bool enable) {
+  if (coverage::IsLCovEnabled()) {
+    // Instrumentation is enabled no matter what.
+    return;
+  }
+
+  toggleCodeCoverageInstrumentationUnchecked(enable);
+}
+
 void ICScript::purgeOptimizedStubs(JSScript* script) {
   MOZ_ASSERT(script->icScript() == this);
 
   Zone* zone = script->zone();
   if (zone->isGCSweeping() && IsAboutToBeFinalizedDuringSweep(*script)) {
     // We're sweeping and the script is dead. Don't purge optimized stubs
     // because (1) accessing CacheIRStubInfo pointers in ICStubs is invalid
     // because we may have swept them already when we started (incremental)
@@ -1421,23 +1448,25 @@ void jit::MarkActiveTypeScripts(Zone* zo
     }
   }
 }
 
 void BaselineInterpreter::init(JitCode* code, uint32_t interpretOpOffset,
                                uint32_t profilerEnterToggleOffset,
                                uint32_t profilerExitToggleOffset,
                                uint32_t debuggeeCheckOffset,
-                               DebugTrapOffsets&& debugTrapOffsets) {
+                               CodeOffsetVector&& debugTrapOffsets,
+                               CodeOffsetVector&& codeCoverageOffsets) {
   code_ = code;
   interpretOpOffset_ = interpretOpOffset;
   profilerEnterToggleOffset_ = profilerEnterToggleOffset;
   profilerExitToggleOffset_ = profilerExitToggleOffset;
   debuggeeCheckOffset_ = debuggeeCheckOffset;
   debugTrapOffsets_ = std::move(debugTrapOffsets);
+  codeCoverageOffsets_ = std::move(codeCoverageOffsets);
 }
 
 bool jit::GenerateBaselineInterpreter(JSContext* cx,
                                       BaselineInterpreter& interpreter) {
   // Temporary JitOptions check to prevent crashes for now.
   if (JitOptions.baselineInterpreter) {
     BaselineInterpreterGenerator generator(cx);
     return generator.generate(interpreter);
--- a/js/src/jit/BaselineJIT.h
+++ b/js/src/jit/BaselineJIT.h
@@ -664,38 +664,45 @@ class BaselineInterpreter {
   uint32_t profilerExitToggleOffset_ = 0;
 
   // The offset for the toggledJump instruction for the debugger's
   // IsDebuggeeCheck code in the prologue.
   uint32_t debuggeeCheckOffset_ = 0;
 
   // Offsets of toggled calls to the DebugTrapHandler trampoline (for
   // breakpoints and stepping).
-  using DebugTrapOffsets = js::Vector<uint32_t, 0, SystemAllocPolicy>;
-  DebugTrapOffsets debugTrapOffsets_;
+  using CodeOffsetVector = Vector<uint32_t, 0, SystemAllocPolicy>;
+  CodeOffsetVector debugTrapOffsets_;
+
+  // Offsets of toggled jumps for code coverage.
+  CodeOffsetVector codeCoverageOffsets_;
 
  public:
   BaselineInterpreter() = default;
 
   BaselineInterpreter(const BaselineInterpreter&) = delete;
   void operator=(const BaselineInterpreter&) = delete;
 
   void init(JitCode* code, uint32_t interpretOpOffset,
             uint32_t profilerEnterToggleOffset,
             uint32_t profilerExitToggleOffset, uint32_t debuggeeCheckOffset,
-            DebugTrapOffsets&& debugTrapOffsets);
+            CodeOffsetVector&& debugTrapOffsets,
+            CodeOffsetVector&& codeCoverageOffsets);
 
   uint8_t* codeRaw() const { return code_->raw(); }
 
   TrampolinePtr interpretOpAddr() const {
     return TrampolinePtr(codeRaw() + interpretOpOffset_);
   }
 
   void toggleProfilerInstrumentation(bool enable);
   void toggleDebuggerInstrumentation(bool enable);
+
+  void toggleCodeCoverageInstrumentationUnchecked(bool enable);
+  void toggleCodeCoverageInstrumentation(bool enable);
 };
 
 MOZ_MUST_USE bool GenerateBaselineInterpreter(JSContext* cx,
                                               BaselineInterpreter& interpreter);
 
 }  // namespace jit
 }  // namespace js
 
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -1048,24 +1048,16 @@ bool GlobalNameConflictsCheckFromIon(JSC
 }
 
 bool InitFunctionEnvironmentObjects(JSContext* cx, BaselineFrame* frame) {
   return frame->initFunctionEnvironmentObjects(cx);
 }
 
 bool NewArgumentsObject(JSContext* cx, BaselineFrame* frame,
                         MutableHandleValue res) {
-  // BaselineCompiler calls ensureHasAnalyzedArgsUsage at compile time. The
-  // interpreters have to do this as part of JSOP_ARGUMENTS.
-  if (frame->runningInInterpreter()) {
-    if (!frame->script()->ensureHasAnalyzedArgsUsage(cx)) {
-      return false;
-    }
-  }
-
   ArgumentsObject* obj = ArgumentsObject::createExpected(cx, frame);
   if (!obj) {
     return false;
   }
   res.setObject(*obj);
   return true;
 }
 
@@ -1910,16 +1902,52 @@ bool HasNativeElementPure(JSContext* cx,
     vp[0].setBoolean(uint32_t(index) < obj->as<TypedArrayObject>().length());
     return true;
   }
 
   vp[0].setBoolean(false);
   return true;
 }
 
+void HandleCodeCoverageAtPC(BaselineFrame* frame, jsbytecode* pc) {
+  AutoUnsafeCallWithABI unsafe(UnsafeABIStrictness::AllowPendingExceptions);
+
+  MOZ_ASSERT(frame->runningInInterpreter());
+
+  JSScript* script = frame->script();
+  MOZ_ASSERT(pc == script->main() || BytecodeIsJumpTarget(JSOp(*pc)));
+
+  if (!script->hasScriptCounts()) {
+    if (!script->realm()->collectCoverageForDebug()) {
+      return;
+    }
+    JSContext* cx = script->runtimeFromMainThread()->mainContextFromOwnThread();
+    AutoEnterOOMUnsafeRegion oomUnsafe;
+    if (!script->initScriptCounts(cx)) {
+      oomUnsafe.crash("initScriptCounts");
+    }
+  }
+
+  PCCounts* counts = script->maybeGetPCCounts(pc);
+  MOZ_ASSERT(counts);
+  counts->numExec()++;
+}
+
+void HandleCodeCoverageAtPrologue(BaselineFrame* frame) {
+  AutoUnsafeCallWithABI unsafe;
+
+  MOZ_ASSERT(frame->runningInInterpreter());
+
+  JSScript* script = frame->script();
+  jsbytecode* main = script->main();
+  if (!BytecodeIsJumpTarget(JSOp(*main))) {
+    HandleCodeCoverageAtPC(frame, main);
+  }
+}
+
 JSString* TypeOfObject(JSObject* obj, JSRuntime* rt) {
   AutoUnsafeCallWithABI unsafe;
   JSType type = js::TypeOfObject(obj);
   return TypeName(type, *rt->commonNames);
 }
 
 bool GetPrototypeOf(JSContext* cx, HandleObject target,
                     MutableHandleValue rval) {
--- a/js/src/jit/VMFunctions.h
+++ b/js/src/jit/VMFunctions.h
@@ -1063,16 +1063,19 @@ MOZ_MUST_USE bool CallNativeGetterByValu
 MOZ_MUST_USE bool CallNativeSetter(JSContext* cx, HandleFunction callee,
                                    HandleObject obj, HandleValue rhs);
 
 MOZ_MUST_USE bool EqualStringsHelperPure(JSString* str1, JSString* str2);
 
 MOZ_MUST_USE bool CheckIsCallable(JSContext* cx, HandleValue v,
                                   CheckIsCallableKind kind);
 
+void HandleCodeCoverageAtPC(BaselineFrame* frame, jsbytecode* pc);
+void HandleCodeCoverageAtPrologue(BaselineFrame* frame);
+
 template <bool HandleMissing>
 bool GetNativeDataPropertyPure(JSContext* cx, JSObject* obj, PropertyName* name,
                                Value* vp);
 
 template <bool HandleMissing>
 bool GetNativeDataPropertyByValuePure(JSContext* cx, JSObject* obj, Value* vp);
 
 template <bool HasOwn>
--- a/js/src/jit/arm/MacroAssembler-arm-inl.h
+++ b/js/src/jit/arm/MacroAssembler-arm-inl.h
@@ -1861,28 +1861,30 @@ void MacroAssembler::branchTestMagic(Con
     branchTestMagic(Assembler::NotEqual, valaddr, label);
   }
 
   branch32(cond, ToPayload(valaddr), Imm32(why), label);
   bind(&notMagic);
 }
 
 void MacroAssembler::branchToComputedAddress(const BaseIndex& addr) {
-  MOZ_ASSERT(addr.base == pc, "Unsupported jump from any other addresses.");
   MOZ_ASSERT(
       addr.offset == 0,
       "NYI: offsets from pc should be shifted by the number of instructions.");
 
   Register base = addr.base;
   uint32_t scale = Imm32::ShiftOf(addr.scale).value;
 
   ma_ldr(DTRAddr(base, DtrRegImmShift(addr.index, LSL, scale)), pc);
-  // When loading from pc, the pc is shifted to the next instruction, we
-  // add one extra instruction to accomodate for this shifted offset.
-  breakpoint();
+
+  if (base == pc) {
+    // When loading from pc, the pc is shifted to the next instruction, we
+    // add one extra instruction to accomodate for this shifted offset.
+    breakpoint();
+  }
 }
 
 void MacroAssembler::cmp32Move32(Condition cond, Register lhs, Register rhs,
                                  Register src, Register dest) {
   cmp32(lhs, rhs);
   ma_mov(src, dest, LeaveCC, cond);
 }
 
--- a/js/src/jit/arm/MacroAssembler-arm.cpp
+++ b/js/src/jit/arm/MacroAssembler-arm.cpp
@@ -3045,19 +3045,16 @@ void MacroAssemblerARMCompat::loadValue(
     }
   } else {
     ma_alu(addr.base, lsl(addr.index, addr.scale), scratch, OpAdd);
     loadValue(Address(scratch, addr.offset), val);
   }
 }
 
 void MacroAssemblerARMCompat::loadValue(Address src, ValueOperand val) {
-  Address payload = ToPayload(src);
-  Address type = ToType(src);
-
   // TODO: copy this code into a generic function that acts on all sequences
   // of memory accesses
   if (isValueDTRDCandidate(val)) {
     // If the value we want is in two consecutive registers starting with an
     // even register, they can be combined as a single ldrd.
     int offset = src.offset;
     if (offset < 256 && offset > -256) {
       ma_ldrd(EDtrAddr(src.base, EDtrOffImm(src.offset)), val.payloadReg(),
@@ -3091,26 +3088,35 @@ void MacroAssemblerARMCompat::loadValue(
       }
       startDataTransferM(IsLoad, src.base, mode);
       transferReg(val.payloadReg());
       transferReg(val.typeReg());
       finishDataTransfer();
       return;
     }
   }
+
+  loadUnalignedValue(src, val);
+}
+
+void MacroAssemblerARMCompat::loadUnalignedValue(const Address& src,
+                                                 ValueOperand dest) {
+  Address payload = ToPayload(src);
+  Address type = ToType(src);
+
   // Ensure that loading the payload does not erase the pointer to the Value
   // in memory.
-  if (type.base != val.payloadReg()) {
+  if (type.base != dest.payloadReg()) {
     SecondScratchRegisterScope scratch2(asMasm());
-    ma_ldr(payload, val.payloadReg(), scratch2);
-    ma_ldr(type, val.typeReg(), scratch2);
+    ma_ldr(payload, dest.payloadReg(), scratch2);
+    ma_ldr(type, dest.typeReg(), scratch2);
   } else {
     SecondScratchRegisterScope scratch2(asMasm());
-    ma_ldr(type, val.typeReg(), scratch2);
-    ma_ldr(payload, val.payloadReg(), scratch2);
+    ma_ldr(type, dest.typeReg(), scratch2);
+    ma_ldr(payload, dest.payloadReg(), scratch2);
   }
 }
 
 void MacroAssemblerARMCompat::tagValue(JSValueType type, Register payload,
                                        ValueOperand dest) {
   MOZ_ASSERT(dest.typeReg() != dest.payloadReg());
   if (payload != dest.payloadReg()) {
     ma_mov(payload, dest.payloadReg());
--- a/js/src/jit/arm/MacroAssembler-arm.h
+++ b/js/src/jit/arm/MacroAssembler-arm.h
@@ -1055,16 +1055,21 @@ class MacroAssemblerARMCompat : public M
     store32(temp, ToPayload(dest));
   }
 
   void loadValue(Address src, ValueOperand val);
   void loadValue(Operand dest, ValueOperand val) {
     loadValue(dest.toAddress(), val);
   }
   void loadValue(const BaseIndex& addr, ValueOperand val);
+
+  // Like loadValue but guaranteed to not use LDRD or LDM instructions (these
+  // don't support unaligned accesses).
+  void loadUnalignedValue(const Address& src, ValueOperand dest);
+
   void tagValue(JSValueType type, Register payload, ValueOperand dest);
 
   void pushValue(ValueOperand val);
   void popValue(ValueOperand val);
   void pushValue(const Value& val) {
     push(Imm32(val.toNunboxTag()));
     if (val.isGCThing()) {
       push(ImmGCPtr(val.toGCThing()));
--- a/js/src/jit/arm64/MacroAssembler-arm64.h
+++ b/js/src/jit/arm64/MacroAssembler-arm64.h
@@ -292,16 +292,19 @@ class MacroAssemblerCompat : public vixl
     Ldr(ARMRegister(val, 64), MemOperand(src));
   }
   void loadValue(Address src, ValueOperand val) {
     Ldr(ARMRegister(val.valueReg(), 64), MemOperand(src));
   }
   void loadValue(const BaseIndex& src, ValueOperand val) {
     doBaseIndex(ARMRegister(val.valueReg(), 64), src, vixl::LDR_x);
   }
+  void loadUnalignedValue(const Address& src, ValueOperand dest) {
+    loadValue(src, dest);
+  }
   void tagValue(JSValueType type, Register payload, ValueOperand dest) {
     // This could be cleverer, but the first attempt had bugs.
     Orr(ARMRegister(dest.valueReg(), 64), ARMRegister(payload, 64),
         Operand(ImmShiftedTag(type).value));
   }
   void pushValue(ValueOperand val) {
     vixl::MacroAssembler::Push(ARMRegister(val.valueReg(), 64));
   }
--- a/js/src/jit/mips32/MacroAssembler-mips32.h
+++ b/js/src/jit/mips32/MacroAssembler-mips32.h
@@ -513,16 +513,21 @@ class MacroAssemblerMIPSCompat : public 
     store32(temp, ToPayload(dest));
   }
 
   void loadValue(Address src, ValueOperand val);
   void loadValue(Operand dest, ValueOperand val) {
     loadValue(dest.toAddress(), val);
   }
   void loadValue(const BaseIndex& addr, ValueOperand val);
+
+  void loadUnalignedValue(const Address& src, ValueOperand dest) {
+    loadValue(src, dest);
+  }
+
   void tagValue(JSValueType type, Register payload, ValueOperand dest);
 
   void pushValue(ValueOperand val);
   void popValue(ValueOperand val);
 #if MOZ_LITTLE_ENDIAN
   void pushValue(const Value& val) {
     push(Imm32(val.toNunboxTag()));
     if (val.isGCThing()) {
--- a/js/src/jit/mips64/MacroAssembler-mips64.h
+++ b/js/src/jit/mips64/MacroAssembler-mips64.h
@@ -555,16 +555,21 @@ class MacroAssemblerMIPS64Compat : publi
     storePtr(temp, dest);
   }
 
   void loadValue(Address src, ValueOperand val);
   void loadValue(Operand dest, ValueOperand val) {
     loadValue(dest.toAddress(), val);
   }
   void loadValue(const BaseIndex& addr, ValueOperand val);
+
+  void loadUnalignedValue(const Address& src, ValueOperand dest) {
+    loadValue(src, dest);
+  }
+
   void tagValue(JSValueType type, Register payload, ValueOperand dest);
 
   void pushValue(ValueOperand val);
   void popValue(ValueOperand val);
   void pushValue(const Value& val) {
     if (val.isGCThing()) {
       writeDataRelocation(val);
       movWithPatch(ImmWord(val.asRawBits()), ScratchRegister);
--- a/js/src/jit/none/MacroAssembler-none.h
+++ b/js/src/jit/none/MacroAssembler-none.h
@@ -284,16 +284,20 @@ class MacroAssemblerNone : public Assemb
   template <typename T, typename S, typename U>
   void storeValue(T, S, U) {
     MOZ_CRASH();
   }
   template <typename T, typename S>
   void loadValue(T, S) {
     MOZ_CRASH();
   }
+  template <typename T, typename S>
+  void loadUnalignedValue(T, S) {
+    MOZ_CRASH();
+  }
   template <typename T>
   void pushValue(const T&) {
     MOZ_CRASH();
   }
   template <typename T, typename S>
   void pushValue(T, S) {
     MOZ_CRASH();
   }
--- a/js/src/jit/x64/MacroAssembler-x64.h
+++ b/js/src/jit/x64/MacroAssembler-x64.h
@@ -191,16 +191,19 @@ class MacroAssemblerX64 : public MacroAs
   }
   void loadValue(Operand src, ValueOperand val) { movq(src, val.valueReg()); }
   void loadValue(Address src, ValueOperand val) {
     loadValue(Operand(src), val);
   }
   void loadValue(const BaseIndex& src, ValueOperand val) {
     loadValue(Operand(src), val);
   }
+  void loadUnalignedValue(const Address& src, ValueOperand dest) {
+    loadValue(src, dest);
+  }
   void tagValue(JSValueType type, Register payload, ValueOperand dest) {
     ScratchRegisterScope scratch(asMasm());
     MOZ_ASSERT(dest.valueReg() != scratch);
     if (payload != dest.valueReg()) {
       movq(payload, dest.valueReg());
     }
     mov(ImmShiftedTag(type), scratch);
     orq(scratch, dest.valueReg());
--- a/js/src/jit/x86/MacroAssembler-x86.h
+++ b/js/src/jit/x86/MacroAssembler-x86.h
@@ -213,16 +213,19 @@ class MacroAssemblerX86 : public MacroAs
     }
   }
   void loadValue(Address src, ValueOperand val) {
     loadValue(Operand(src), val);
   }
   void loadValue(const BaseIndex& src, ValueOperand val) {
     loadValue(Operand(src), val);
   }
+  void loadUnalignedValue(const Address& src, ValueOperand dest) {
+    loadValue(src, dest);
+  }
   void tagValue(JSValueType type, Register payload, ValueOperand dest) {
     MOZ_ASSERT(dest.typeReg() != dest.payloadReg());
     if (payload != dest.payloadReg()) {
       movl(payload, dest.payloadReg());
     }
     movl(ImmType(type), dest.typeReg());
   }
   void pushValue(ValueOperand val) {
--- a/js/src/vm/Realm.cpp
+++ b/js/src/vm/Realm.cpp
@@ -788,16 +788,19 @@ void Realm::setIsDebuggee() {
   if (!isDebuggee()) {
     debugModeBits_ |= IsDebuggee;
     runtimeFromMainThread()->incrementNumDebuggeeRealms();
   }
 }
 
 void Realm::unsetIsDebuggee() {
   if (isDebuggee()) {
+    if (debuggerObservesCoverage()) {
+      runtime_->decrementNumDebuggeeRealmsObservingCoverage();
+    }
     debugModeBits_ &= ~DebuggerObservesMask;
     DebugEnvironments::onRealmUnsetIsDebuggee(this);
     runtimeFromMainThread()->decrementNumDebuggeeRealms();
   }
 }
 
 void Realm::updateDebuggerObservesCoverage() {
   bool previousState = debuggerObservesCoverage();
@@ -810,19 +813,22 @@ void Realm::updateDebuggerObservesCovera
     // Interrupt any running interpreter frame. The scriptCounts are
     // allocated on demand when a script resumes its execution.
     JSContext* cx = TlsContext.get();
     for (ActivationIterator iter(cx); !iter.done(); ++iter) {
       if (iter->isInterpreter()) {
         iter->asInterpreter()->enableInterruptsUnconditionally();
       }
     }
+    runtime_->incrementNumDebuggeeRealmsObservingCoverage();
     return;
   }
 
+  runtime_->decrementNumDebuggeeRealmsObservingCoverage();
+
   // If code coverage is enabled by any other means, keep it.
   if (collectCoverage()) {
     return;
   }
 
   clearScriptCounts();
   clearScriptNames();
 }
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -117,16 +117,17 @@ JSRuntime::JSRuntime(JSRuntime* parentRu
       scriptDataLock(mutexid::RuntimeScriptData),
 #ifdef DEBUG
       activeThreadHasScriptDataAccess(false),
 #endif
       numActiveHelperThreadZones(0),
       heapState_(JS::HeapState::Idle),
       numRealms(0),
       numDebuggeeRealms_(0),
+      numDebuggeeRealmsObservingCoverage_(0),
       localeCallbacks(nullptr),
       defaultLocale(nullptr),
       profilingScripts(false),
       scriptAndCountsVector(nullptr),
       lcovOutput_(),
       jitRuntime_(nullptr),
       selfHostingGlobal_(nullptr),
       gc(thisFromCtor()),
@@ -185,16 +186,17 @@ JSRuntime::~JSRuntime() {
 
   MOZ_ASSERT(wasmInstances.lock()->empty());
 
   MOZ_ASSERT(offThreadParsesRunning_ == 0);
   MOZ_ASSERT(!offThreadParsingBlocked_);
 
   MOZ_ASSERT(numRealms == 0);
   MOZ_ASSERT(numDebuggeeRealms_ == 0);
+  MOZ_ASSERT(numDebuggeeRealmsObservingCoverage_ == 0);
 }
 
 bool JSRuntime::init(JSContext* cx, uint32_t maxbytes,
                      uint32_t maxNurseryBytes) {
 #ifdef DEBUG
   MOZ_ASSERT(!initialized_);
   initialized_ = true;
 #endif
@@ -782,16 +784,36 @@ void JSRuntime::decrementNumDebuggeeReal
   MOZ_ASSERT(numDebuggeeRealms_ > 0);
   numDebuggeeRealms_--;
 
   if (numDebuggeeRealms_ == 0) {
     jitRuntime()->baselineInterpreter().toggleDebuggerInstrumentation(false);
   }
 }
 
+void JSRuntime::incrementNumDebuggeeRealmsObservingCoverage() {
+  if (numDebuggeeRealmsObservingCoverage_ == 0) {
+    jit::BaselineInterpreter& interp = jitRuntime()->baselineInterpreter();
+    interp.toggleCodeCoverageInstrumentation(true);
+  }
+
+  numDebuggeeRealmsObservingCoverage_++;
+  MOZ_ASSERT(numDebuggeeRealmsObservingCoverage_ <= numRealms);
+}
+
+void JSRuntime::decrementNumDebuggeeRealmsObservingCoverage() {
+  MOZ_ASSERT(numDebuggeeRealmsObservingCoverage_ > 0);
+  numDebuggeeRealmsObservingCoverage_--;
+
+  if (numDebuggeeRealmsObservingCoverage_ == 0) {
+    jit::BaselineInterpreter& interp = jitRuntime()->baselineInterpreter();
+    interp.toggleCodeCoverageInstrumentation(false);
+  }
+}
+
 bool js::CurrentThreadCanAccessRuntime(const JSRuntime* rt) {
   return rt->mainContextFromAnyThread() == TlsContext.get();
 }
 
 bool js::CurrentThreadCanAccessZone(Zone* zone) {
   // Helper thread zones can only be used by their owning thread.
   if (zone->usedByHelperThread()) {
     return zone->ownedByCurrentHelperThread();
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -522,24 +522,30 @@ struct JSRuntime : public js::MallocProv
   // off-thread context realms, so it isn't necessarily equal to the
   // number of realms visited by RealmsIter.
   js::MainThreadData<size_t> numRealms;
 
  private:
   // Number of debuggee realms in the runtime.
   js::MainThreadData<size_t> numDebuggeeRealms_;
 
+  // Number of debuggee realms in the runtime observing code coverage.
+  js::MainThreadData<size_t> numDebuggeeRealmsObservingCoverage_;
+
  public:
   void incrementNumDebuggeeRealms();
   void decrementNumDebuggeeRealms();
 
   size_t numDebuggeeRealms() const {
     return numDebuggeeRealms_;
   }
 
+  void incrementNumDebuggeeRealmsObservingCoverage();
+  void decrementNumDebuggeeRealmsObservingCoverage();
+
   /* Locale-specific callbacks for string conversion. */
   js::MainThreadData<const JSLocaleCallbacks*> localeCallbacks;
 
   /* Default locale for Internationalization API */
   js::MainThreadData<js::UniqueChars> defaultLocale;
 
   /* If true, new scripts must be created with PC counter information. */
   js::MainThreadOrIonCompileData<bool> profilingScripts;
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -3492,16 +3492,27 @@ bool JSScript::makeTypes(JSContext* cx) 
   cx->check(this);
 
   // Scripts that will never run in the Baseline Interpreter or the JITs don't
   // need a TypeScript.
   MOZ_ASSERT(!hasForceInterpreterOp());
 
   AutoEnterAnalysis enter(cx);
 
+  // Run the arguments-analysis if needed. Both the Baseline Interpreter and
+  // Compiler rely on this.
+  if (!ensureHasAnalyzedArgsUsage(cx)) {
+    return false;
+  }
+
+  // If ensureHasAnalyzedArgsUsage allocated the TypeScript we're done.
+  if (types_) {
+    return true;
+  }
+
   UniquePtr<jit::ICScript> icScript(jit::ICScript::create(cx, this));
   if (!icScript) {
     return false;
   }
 
   // We need to call prepareForDestruction on ICScript before we |delete| it.
   auto prepareForDestruction = mozilla::MakeScopeExit(
       [&] { icScript->prepareForDestruction(cx->zone()); });
@@ -3521,16 +3532,17 @@ bool JSScript::makeTypes(JSContext* cx) 
   auto typeScript =
       reinterpret_cast<TypeScript*>(cx->pod_malloc<uint8_t>(allocSize));
   if (!typeScript) {
     return false;
   }
 
   prepareForDestruction.release();
 
+  MOZ_ASSERT(!types_);
   types_ = new (typeScript) TypeScript(this, std::move(icScript), numTypeSets);
 
   // We have a TypeScript so we can set the script's jitCodeRaw_ pointer to the
   // Baseline Interpreter code.
   updateJitCodeRaw(cx->runtime());
 
 #ifdef DEBUG
   StackTypeSet* typeArray = typeScript->typeArrayDontCheckGeneration();
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -10916,17 +10916,25 @@ void PresShell::SetIsUnderHiddenEmbedder
   mUnderHiddenEmbedderElement = aUnderHiddenEmbedderElement;
 
   if (nsCOMPtr<nsIDocShell> docShell = mPresContext->GetDocShell()) {
     BrowsingContext* bc = nsDocShell::Cast(docShell)->GetBrowsingContext();
 
     // Propagate to children.
     for (BrowsingContext* child : bc->GetChildren()) {
       Element* embedderElement = child->GetEmbedderElement();
-      MOZ_ASSERT(embedderElement);
+      if (!embedderElement) {
+        // TODO: We shouldn't need to null check here since `child` and the
+        // element returned by `child->GetEmbedderElement()` are in our
+        // process (the actual browsing context represented by `child` may not
+        // be, but that doesn't matter).  However, there are currently a very
+        // small number of crashes due to `embedderElement` being null, somehow
+        // - see bug 1551241.  For now we wallpaper the crash.
+        continue;
+      }
 
       bool embedderFrameIsHidden = true;
       if (auto embedderFrame = embedderElement->GetPrimaryFrame()) {
         embedderFrameIsHidden =
             !embedderFrame->StyleVisibility()->IsVisible();
       }
 
       if (nsIDocShell* childDocShell = child->GetDocShell()) {
--- a/layout/style/ServoBindings.toml
+++ b/layout/style/ServoBindings.toml
@@ -486,19 +486,16 @@ mapped-generic-types = [
     { generic = true, gecko = "mozilla::RustCell", servo = "::std::cell::Cell" },
     { generic = false, gecko = "ServoNodeData", servo = "AtomicRefCell<ElementData>" },
     { generic = false, gecko = "mozilla::ServoWritingMode", servo = "::logical_geometry::WritingMode" },
     { generic = false, gecko = "mozilla::ServoCustomPropertiesMap", servo = "Option<::servo_arc::Arc<::custom_properties::CustomPropertiesMap>>" },
     { generic = false, gecko = "mozilla::ServoRuleNode", servo = "Option<::rule_tree::StrongRuleNode>" },
     { generic = false, gecko = "mozilla::ServoVisitedStyle", servo = "Option<::servo_arc::RawOffsetArc<::properties::ComputedValues>>" },
     { generic = false, gecko = "mozilla::ServoComputedValueFlags", servo = "::properties::computed_value_flags::ComputedValueFlags" },
     { generic = true, gecko = "mozilla::ServoRawOffsetArc", servo = "::servo_arc::RawOffsetArc" },
+    { generic = true, gecko = "mozilla::ServoManuallyDrop", servo = "::std::mem::ManuallyDrop" },
     { generic = false, gecko = "nsACString", servo = "nsstring::nsACString" },
     { generic = false, gecko = "nsAString", servo = "nsstring::nsAString" },
     { generic = false, gecko = "nsCString", servo = "nsstring::nsCString" },
-    { generic = false, gecko = "nsString", servo = "nsstring::nsStringRepr" },
+    { generic = false, gecko = "nsString", servo = "nsstring::nsString" },
 ]
 
 whitelist-functions = ["Servo_.*", "Gecko_.*"]
-
-fixups = [
-    { pat = "\\broot\\s*::\\s*nsTString\\s*<\\s*u16\\s*>", rep = "::nsstring::nsStringRepr" },
-]
--- a/layout/style/ServoComputedData.h
+++ b/layout/style/ServoComputedData.h
@@ -21,16 +21,25 @@ namespace mozilla {
 
 template <typename T>
 struct ServoRawOffsetArc {
   // This is a pointer to a T that lives inside a servo_arc::Arc<T>, and
   // which already has had its reference count incremented.
   T* mPtr;
 };
 
+// A wrapper that gets replaced by ManuallyDrop<T> by bindgen.
+//
+// NOTE(emilio): All this file is a bit gross, and most of this we make cleaner
+// using cbindgen and such.
+template <typename T>
+struct ServoManuallyDrop {
+  T mInner;
+};
+
 struct ServoWritingMode {
   uint8_t mBits;
 };
 
 struct ServoCustomPropertiesMap {
   uintptr_t mPtr;
 };
 
--- a/layout/style/ServoComputedDataInlines.h
+++ b/layout/style/ServoComputedDataInlines.h
@@ -8,22 +8,22 @@
 #define mozilla_ServoComputedDataInlines_h
 
 #include "mozilla/ServoComputedData.h"
 #include "nsStyleStruct.h"
 
 namespace mozilla {
 #define STYLE_STRUCT(name_) \
   struct Gecko##name_ {     \
-    nsStyle##name_ gecko;   \
+    ServoManuallyDrop<nsStyle##name_> gecko;   \
   };
 #include "nsStyleStructList.h"
 #undef STYLE_STRUCT
 }  // namespace mozilla
 
 #define STYLE_STRUCT(name_)                                          \
   const nsStyle##name_* ServoComputedData::GetStyle##name_() const { \
-    return &name_.mPtr->gecko;                                       \
+    return &name_.mPtr->gecko.mInner;                                \
   }
 #include "nsStyleStructList.h"
 #undef STYLE_STRUCT
 
 #endif  // mozilla_ServoComputedDataInlines_h
--- a/mobile/android/base/AppConstants.java.in
+++ b/mobile/android/base/AppConstants.java.in
@@ -2,16 +2,17 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * 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/. */
 
 package org.mozilla.gecko;
 
 import android.os.Build;
+import android.support.v4.os.BuildCompat;
 
 /**
  * A collection of constants that pertain to the build and runtime state of the
  * application. Typically these are sourced from build-time definitions (see
  * Makefile.in). This is a Java-side substitute for nsIXULAppInfo, amongst
  * other things.
  *
  * See also SysInfo.java, which includes some of the values available from
@@ -51,16 +52,18 @@ public class AppConstants {
         public static final boolean feature16Plus = MIN_SDK_VERSION >= 16 || (MAX_SDK_VERSION >= 16 && Build.VERSION.SDK_INT >= 16);
         public static final boolean feature17Plus = MIN_SDK_VERSION >= 17 || (MAX_SDK_VERSION >= 17 && Build.VERSION.SDK_INT >= 17);
         public static final boolean feature19Plus = MIN_SDK_VERSION >= 19 || (MAX_SDK_VERSION >= 19 && Build.VERSION.SDK_INT >= 19);
         public static final boolean feature20Plus = MIN_SDK_VERSION >= 20 || (MAX_SDK_VERSION >= 20 && Build.VERSION.SDK_INT >= 20);
         public static final boolean feature21Plus = MIN_SDK_VERSION >= 21 || (MAX_SDK_VERSION >= 21 && Build.VERSION.SDK_INT >= 21);
         public static final boolean feature23Plus = MIN_SDK_VERSION >= 23 || (MAX_SDK_VERSION >= 23 && Build.VERSION.SDK_INT >= 23);
         public static final boolean feature24Plus = MIN_SDK_VERSION >= 24 || (MAX_SDK_VERSION >= 24 && Build.VERSION.SDK_INT >= 24);
         public static final boolean feature26Plus = MIN_SDK_VERSION >= 26 || (MAX_SDK_VERSION >= 26 && Build.VERSION.SDK_INT >= 26);
+        // Given that Android Q is not yet released it shares API level 28 with Android P
+        public static final boolean feature29Plus = BuildCompat.isAtLeastQ();
 
         /*
          * If our MIN_SDK_VERSION is 14 or higher, we must be an ICS device.
          * If our MAX_SDK_VERSION is lower than ICS, we must not be an ICS device.
          * Otherwise, we need a range check.
          */
         public static final boolean preMarshmallow = MAX_SDK_VERSION < 23 || (MIN_SDK_VERSION < 23 && Build.VERSION.SDK_INT < 23);
         public static final boolean preLollipopMR1 = MAX_SDK_VERSION < 22 || (MIN_SDK_VERSION < 22 && Build.VERSION.SDK_INT < 22);
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/GlobalConstants.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/GlobalConstants.java
@@ -37,19 +37,31 @@ public class GlobalConstants {
    *
    * ELB cipher suites:
    * <http://docs.aws.amazon.com/ElasticLoadBalancing/latest/DeveloperGuide/elb-security-policy-table.html>
    */
   public static final String[] DEFAULT_CIPHER_SUITES;
   public static final String[] DEFAULT_PROTOCOLS;
 
   static {
-    // Prioritize 128 over 256 as a tradeoff between device CPU/battery and the minor
-    // increase in strength.
-    if (Versions.feature26Plus) {
+    // ChaCha20-Poly1305 seems fastest on mobile.
+    // Otherwise prioritize 128 over 256 as a tradeoff between device CPU/battery
+    // and the minor increase in strength.
+    if (Versions.feature29Plus) {
+      DEFAULT_CIPHER_SUITES = new String[]
+          {
+          "TLS_CHACHA20_POLY1305_SHA256",               // 29+
+          "TLS_AES_128_GCM_SHA256",                     // 29+
+          "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",      // 20+
+          "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",         // 20+
+          "TLS_AES_256_GCM_SHA384",                     // 29+
+          "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",      // 20+
+          "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",         // 11+
+          };
+    } else if (Versions.feature26Plus) {
       DEFAULT_CIPHER_SUITES = new String[]
           {
            "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",   // 20+
            "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",     // 20+
            "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",     // 20+
            "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",        // 11+
            "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",     // 20+
            "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",     // 20+
@@ -81,24 +93,24 @@ public class GlobalConstants {
            "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",        // 11+
 
            // For Sync 1.1.
            "TLS_DHE_RSA_WITH_AES_128_CBC_SHA",  // 9+
            "TLS_RSA_WITH_AES_128_CBC_SHA",      // 9+
           };
     }
 
-    if (Versions.feature16Plus) {
+    if (Versions.feature29Plus) {
+      DEFAULT_PROTOCOLS = new String[]
+          {
+          "TLSv1.3",
+          "TLSv1.2",
+          };
+    } else {
       DEFAULT_PROTOCOLS = new String[]
           {
            "TLSv1.2",
            "TLSv1.1",
            "TLSv1",             // We would like to remove this, and will do so when we can.
           };
-    } else {
-      // Fall back to TLSv1 if there's nothing better.
-      DEFAULT_PROTOCOLS = new String[]
-          {
-           "TLSv1",
-          };
     }
   }
 }
--- a/remote/domains/content/Runtime.jsm
+++ b/remote/domains/content/Runtime.jsm
@@ -65,16 +65,30 @@ class Runtime extends ContentProcessDoma
     }
     if (typeof(request.expression) != "string") {
       throw new Error(`Expecting 'expression' attribute to be a string. ` +
         `But was: ${typeof(request.expression)}`);
     }
     return context.evaluate(request.expression);
   }
 
+  releaseObject({ objectId }) {
+    let context = null;
+    for (const ctx of this.contexts.values()) {
+      if (ctx.hasRemoteObject(objectId)) {
+        context = ctx;
+        break;
+      }
+    }
+    if (!context) {
+      throw new Error(`Unable to get execution context by ID: ${objectId}`);
+    }
+    context.releaseObject(objectId);
+  }
+
   callFunctionOn(request) {
     let context = null;
     // When an `objectId` is passed, we want to execute the function of a given object
     // So we first have to find its ExecutionContext
     if (request.objectId) {
       for (const ctx of this.contexts.values()) {
         if (ctx.hasRemoteObject(request.objectId)) {
           context = ctx;
--- a/remote/domains/content/runtime/ExecutionContext.jsm
+++ b/remote/domains/content/runtime/ExecutionContext.jsm
@@ -43,16 +43,20 @@ class ExecutionContext {
   destructor() {
     this._debugger.removeDebuggee(this._debuggee);
   }
 
   hasRemoteObject(id) {
     return this._remoteObjects.has(id);
   }
 
+  releaseObject(id) {
+    return this._remoteObjects.delete(id);
+  }
+
   /**
    * Evaluate a Javascript expression.
    *
    * @param {String} expression
    *   The JS expression to evaluate against the JS context.
    * @return {Object} A multi-form object depending if the execution succeed or failed.
    *   If the expression failed to evaluate, it will return an object with an
    *   `exceptionDetails` attribute matching the `ExceptionDetails` CDP type.
@@ -257,17 +261,17 @@ class ExecutionContext {
       } else if (cls == "WeakSet") {
         subtype = "weakset";
       } else if (cls == "Error") {
         subtype = "error";
       } else if (cls == "Promise") {
         subtype = "promise";
       } else if (TYPED_ARRAY_CLASSES.includes(cls)) {
         subtype = "typedarray";
-      } else if (cls == "Object" && Node.isInstance(rawObj)) {
+      } else if (Node.isInstance(rawObj)) {
         subtype = "node";
       }
 
       const type = typeof rawObj;
       return {objectId, type, subtype};
     }
 
     // Now, handle all values that Debugger API isn't wrapping into Debugger.API.
--- a/remote/test/browser/browser.ini
+++ b/remote/test/browser/browser.ini
@@ -6,13 +6,14 @@ support-files =
   head.js
 skip-if = debug || asan # bug 1546945
 
 [browser_cdp.js]
 [browser_main_target.js]
 [browser_page_frameNavigated.js]
 [browser_page_runtime_events.js]
 [browser_runtime_evaluate.js]
+[browser_runtime_remote_objects.js]
 [browser_runtime_callFunctionOn.js]
 [browser_runtime_executionContext.js]
 skip-if = os == "mac" || (verify && os == 'win') # bug 1547961
 [browser_tabs.js]
 [browser_target.js]
--- a/remote/test/browser/browser_runtime_evaluate.js
+++ b/remote/test/browser/browser_runtime_evaluate.js
@@ -183,16 +183,19 @@ async function testObjectTypes(testFunct
     { expression: "new WeakMap()", type: "object", subtype: "weakmap" },
     { expression: "new WeakSet()", type: "object", subtype: "weakset" },
     { expression: "new Map()", type: "object", subtype: "map" },
     { expression: "new Set()", type: "object", subtype: "set" },
     { expression: "/foo/", type: "object", subtype: "regexp" },
     { expression: "[1, 2]", type: "object", subtype: "array" },
     { expression: "new Proxy({}, {})", type: "object", subtype: "proxy" },
     { expression: "new Date()", type: "object", subtype: "date" },
+    { expression: "document", type: "object", subtype: "node" },
+    { expression: "document.documentElement", type: "object", subtype: "node" },
+    { expression: "document.createElement('div')", type: "object", subtype: "node" },
   ];
 
   for (const { expression, type, subtype } of expressions) {
     const { result } = await testFunction(expression);
     is(result.subtype, subtype, `Evaluating '${expression}' has the expected subtype`);
     is(result.type, type, "The type is correct");
     ok(!!result.objectId, "Got an object id");
   }
new file mode 100644
--- /dev/null
+++ b/remote/test/browser/browser_runtime_remote_objects.js
@@ -0,0 +1,101 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test the Runtime remote object
+
+const TEST_URI = "data:text/html;charset=utf-8,default-test-page";
+
+add_task(async function() {
+  // Open a test page, to prevent debugging the random default page
+  await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URI);
+
+  // Start the CDP server
+  await RemoteAgent.listen(Services.io.newURI("http://localhost:9222"));
+
+  // Retrieve the chrome-remote-interface library object
+  const CDP = await getCDP();
+
+  // Connect to the server
+  const client = await CDP({
+    target(list) {
+      // Ensure debugging the right target, i.e. the one for our test tab.
+      return list.find(target => target.url == TEST_URI);
+    },
+  });
+  ok(true, "CDP client has been instantiated");
+
+  const firstContext = await testRuntimeEnable(client);
+  const contextId = firstContext.id;
+
+  await testObjectRelease(client, contextId);
+
+  await client.close();
+  ok(true, "The client is closed");
+
+  BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+  await RemoteAgent.close();
+});
+
+async function testRuntimeEnable({ Runtime }) {
+  // Enable watching for new execution context
+  await Runtime.enable();
+  ok(true, "Runtime domain has been enabled");
+
+  // Calling Runtime.enable will emit executionContextCreated for the existing contexts
+  const { context } = await Runtime.executionContextCreated();
+  ok(!!context.id, "The execution context has an id");
+  ok(context.auxData.isDefault, "The execution context is the default one");
+  ok(!!context.auxData.frameId, "The execution context has a frame id set");
+
+  return context;
+}
+
+async function testObjectRelease({ Runtime }, contextId) {
+  const { result } = await Runtime.evaluate({ contextId, expression: "({ foo: 42 })" });
+  is(result.subtype, null, "JS Object have no subtype");
+  is(result.type, "object", "The type is correct");
+  ok(!!result.objectId, "Got an object id");
+
+  const { result: result2 } = await Runtime.callFunctionOn({
+    executionContextId: contextId,
+    functionDeclaration: "obj => JSON.stringify(obj)",
+    arguments: [{ objectId: result.objectId }],
+  });
+  is(result2.type, "string", "The type is correct");
+  is(result2.value, JSON.stringify({ foo: 42 }), "Got the object's JSON");
+
+  const { result: result3 } = await Runtime.callFunctionOn({
+    objectId: result.objectId,
+    functionDeclaration: "function () { return this.foo; }",
+  });
+  is(result3.type, "number", "The type is correct");
+  is(result3.value, 42, "Got the object's foo attribute");
+
+  await Runtime.releaseObject({
+    objectId: result.objectId,
+  });
+  ok(true, "Object is released");
+
+  try {
+    await Runtime.callFunctionOn({
+      executionContextId: contextId,
+      functionDeclaration: "() => {}",
+      arguments: [{ objectId: result.objectId }],
+    });
+    ok(false, "callFunctionOn with a released object as argument should throw");
+  } catch (e) {
+    ok(e.message.includes("Cannot find object with ID:"), "callFunctionOn throws on released argument");
+  }
+  try {
+    await Runtime.callFunctionOn({
+      objectId: result.objectId,
+      functionDeclaration: "() => {}",
+    });
+    ok(false, "callFunctionOn with a released object as target should throw");
+  } catch (e) {
+    ok(e.message.includes("Unable to get the context for object with id"), "callFunctionOn throws on released target");
+  }
+}
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -45,17 +45,17 @@ use crate::gecko::values::round_border_t
 use crate::logical_geometry::WritingMode;
 use crate::media_queries::Device;
 use crate::properties::computed_value_flags::*;
 use crate::properties::longhands;
 use crate::rule_tree::StrongRuleNode;
 use crate::selector_parser::PseudoElement;
 use servo_arc::{Arc, RawOffsetArc};
 use std::marker::PhantomData;
-use std::mem::{forget, uninitialized, zeroed};
+use std::mem::{forget, uninitialized, zeroed, ManuallyDrop};
 use std::{cmp, ops, ptr};
 use crate::values::{self, CustomIdent, Either, KeyframesName, None_};
 use crate::values::computed::{NonNegativeLength, Percentage, TransitionProperty};
 use crate::values::computed::BorderStyle;
 use crate::values::computed::font::FontSize;
 use crate::values::computed::effects::{BoxShadow, Filter, SimpleShadow};
 use crate::values::generics::column::ColumnCount;
 use crate::values::generics::transform::TransformStyle;
@@ -1090,38 +1090,38 @@ pub fn clone_transform_from_list(
 <%def name="impl_logical(name, **kwargs)">
     ${helpers.logical_setter(name)}
 </%def>
 
 <%def name="impl_style_struct(style_struct)">
 impl ${style_struct.gecko_struct_name} {
     #[allow(dead_code, unused_variables)]
     pub fn default(document: &structs::Document) -> Arc<Self> {
-        let mut result = Arc::new(${style_struct.gecko_struct_name} { gecko: unsafe { zeroed() } });
+        let mut result = Arc::new(${style_struct.gecko_struct_name} { gecko: ManuallyDrop::new(unsafe { zeroed() }) });
         unsafe {
             Gecko_Construct_Default_${style_struct.gecko_ffi_name}(
-                &mut Arc::get_mut(&mut result).unwrap().gecko,
+                &mut *Arc::get_mut(&mut result).unwrap().gecko,
                 document,
             );
         }
         result
     }
 }
 impl Drop for ${style_struct.gecko_struct_name} {
     fn drop(&mut self) {
         unsafe {
-            Gecko_Destroy_${style_struct.gecko_ffi_name}(&mut self.gecko);
+            Gecko_Destroy_${style_struct.gecko_ffi_name}(&mut *self.gecko);
         }
     }
 }
 impl Clone for ${style_struct.gecko_struct_name} {
     fn clone(&self) -> Self {
         unsafe {
-            let mut result = ${style_struct.gecko_struct_name} { gecko: zeroed() };
-            Gecko_CopyConstruct_${style_struct.gecko_ffi_name}(&mut result.gecko, &self.gecko);
+            let mut result = ${style_struct.gecko_struct_name} { gecko: ManuallyDrop::new(zeroed()) };
+            Gecko_CopyConstruct_${style_struct.gecko_ffi_name}(&mut *result.gecko, &*self.gecko);
             result
         }
     }
 }
 
 </%def>
 
 <%def name="impl_simple_type_with_conversion(ident, gecko_ffi_name=None)">
@@ -1540,18 +1540,19 @@ fn static_assert() {
                     Some(self.gecko.${value.gecko}.mInteger)
                 },
         }
     }
     % endfor
 
     % for kind in ["rows", "columns"]:
     pub fn set_grid_auto_${kind}(&mut self, v: longhands::grid_auto_${kind}::computed_value::T) {
-        v.to_gecko_style_coords(&mut self.gecko.mGridAuto${kind.title()}Min,
-                                &mut self.gecko.mGridAuto${kind.title()}Max)
+        let gecko = &mut *self.gecko;
+        v.to_gecko_style_coords(&mut gecko.mGridAuto${kind.title()}Min,
+                                &mut gecko.mGridAuto${kind.title()}Max)
     }
 
     pub fn copy_grid_auto_${kind}_from(&mut self, other: &Self) {
         self.gecko.mGridAuto${kind.title()}Min.copy_from(&other.gecko.mGridAuto${kind.title()}Min);
         self.gecko.mGridAuto${kind.title()}Max.copy_from(&other.gecko.mGridAuto${kind.title()}Max);
     }
 
     pub fn reset_grid_auto_${kind}(&mut self, other: &Self) {
@@ -1561,24 +1562,24 @@ fn static_assert() {
     pub fn clone_grid_auto_${kind}(&self) -> longhands::grid_auto_${kind}::computed_value::T {
         crate::values::generics::grid::TrackSize::from_gecko_style_coords(&self.gecko.mGridAuto${kind.title()}Min,
                                                                      &self.gecko.mGridAuto${kind.title()}Max)
     }
 
     pub fn set_grid_template_${kind}(&mut self, v: longhands::grid_template_${kind}::computed_value::T) {
         <% self_grid = "self.gecko.mGridTemplate%s" % kind.title() %>
         use crate::gecko_bindings::structs::{nsTArray, nsStyleGridLine_kMaxLine};
-        use nsstring::nsStringRepr;
+        use nsstring::nsString;
         use std::usize;
         use crate::values::CustomIdent;
         use crate::values::generics::grid::TrackListType::Auto;
         use crate::values::generics::grid::{GridTemplateComponent, RepeatCount};
 
         #[inline]
-        fn set_line_names(servo_names: &[CustomIdent], gecko_names: &mut nsTArray<nsStringRepr>) {
+        fn set_line_names(servo_names: &[CustomIdent], gecko_names: &mut nsTArray<nsString>) {
             unsafe {
                 bindings::Gecko_ResizeTArrayForStrings(gecko_names, servo_names.len() as u32);
             }
 
             for (servo_name, gecko_name) in servo_names.iter().zip(gecko_names.iter_mut()) {
                 gecko_name.assign(servo_name.0.as_slice());
             }
         }
@@ -1688,36 +1689,36 @@ fn static_assert() {
 
     pub fn reset_grid_template_${kind}(&mut self, other: &Self) {
         self.copy_grid_template_${kind}_from(other)
     }
 
     pub fn clone_grid_template_${kind}(&self) -> longhands::grid_template_${kind}::computed_value::T {
         <% self_grid = "self.gecko.mGridTemplate%s" % kind.title() %>
         use crate::gecko_bindings::structs::nsTArray;
-        use nsstring::nsStringRepr;
+        use nsstring::nsString;
         use crate::values::CustomIdent;
         use crate::values::generics::grid::{GridTemplateComponent, LineNameList, RepeatCount};
         use crate::values::generics::grid::{TrackList, TrackListType, TrackListValue, TrackRepeat, TrackSize};
 
         let value = match unsafe { ${self_grid}.mPtr.as_ref() } {
             None => return GridTemplateComponent::None,
             Some(value) => value,
         };
 
         #[inline]
-        fn to_boxed_customident_slice(gecko_names: &nsTArray<nsStringRepr>) -> Box<[CustomIdent]> {
+        fn to_boxed_customident_slice(gecko_names: &nsTArray<nsString>) -> Box<[CustomIdent]> {
             let idents: Vec<CustomIdent> = gecko_names.iter().map(|gecko_name| {
                 CustomIdent(Atom::from(gecko_name.to_string()))
             }).collect();
             idents.into_boxed_slice()
         }
 
         #[inline]
-        fn to_line_names_vec(gecko_line_names: &nsTArray<nsTArray<nsStringRepr>>)
+        fn to_line_names_vec(gecko_line_names: &nsTArray<nsTArray<nsString>>)
             -> Vec<Box<[CustomIdent]>> {
             gecko_line_names.iter().map(|gecko_names| {
                 to_boxed_customident_slice(gecko_names)
             }).collect()
         }
 
         let repeat_auto_index = value.mRepeatAutoIndex as usize;
         if value.mIsSubgrid() {
@@ -2124,24 +2125,24 @@ fn static_assert() {
         T::from_gecko_adjust(self.gecko.mFont.sizeAdjust)
     }
 
     #[allow(non_snake_case)]
     pub fn set__x_lang(&mut self, v: longhands::_x_lang::computed_value::T) {
         let ptr = v.0.as_ptr();
         forget(v);
         unsafe {
-            Gecko_nsStyleFont_SetLang(&mut self.gecko, ptr);
+            Gecko_nsStyleFont_SetLang(&mut *self.gecko, ptr);
         }
     }
 
     #[allow(non_snake_case)]
     pub fn copy__x_lang_from(&mut self, other: &Self) {
         unsafe {
-            Gecko_nsStyleFont_CopyLangFrom(&mut self.gecko, &other.gecko);
+            Gecko_nsStyleFont_CopyLangFrom(&mut *self.gecko, &*other.gecko);
         }
     }
 
     #[allow(non_snake_case)]
     pub fn reset__x_lang(&mut self, other: &Self) {
         self.copy__x_lang_from(other)
     }
 
@@ -2514,17 +2515,17 @@ fn static_assert() {
                           transform-style
                           rotate scroll-snap-points-x scroll-snap-points-y
                           scroll-snap-coordinate -moz-binding will-change
                           offset-path shape-outside
                           translate scale -webkit-line-clamp""" %>
 <%self:impl_trait style_struct_name="Box" skip_longhands="${skip_box_longhands}">
     #[inline]
     pub fn generate_combined_transform(&mut self) {
-        unsafe { bindings::Gecko_StyleDisplay_GenerateCombinedTransform(&mut self.gecko) };
+        unsafe { bindings::Gecko_StyleDisplay_GenerateCombinedTransform(&mut *self.gecko) };
     }
 
     #[inline]
     pub fn set_display(&mut self, v: longhands::display::computed_value::T) {
         self.gecko.mDisplay = v;
         self.gecko.mOriginalDisplay = v;
     }
 
@@ -2831,42 +2832,42 @@ fn static_assert() {
 
     pub fn set_will_change(&mut self, v: longhands::will_change::computed_value::T) {
         use crate::gecko_bindings::bindings::{Gecko_AppendWillChange, Gecko_ClearWillChange};
         use crate::values::specified::box_::{WillChangeBits, WillChange};
 
         match v {
             WillChange::AnimateableFeatures { features, bits } => {
                 unsafe {
-                    Gecko_ClearWillChange(&mut self.gecko, features.len());
+                    Gecko_ClearWillChange(&mut *self.gecko, features.len());
                 }
 
                 for feature in features.iter() {
                     unsafe {
-                        Gecko_AppendWillChange(&mut self.gecko, feature.0.as_ptr())
+                        Gecko_AppendWillChange(&mut *self.gecko, feature.0.as_ptr())
                     }
                 }
 
                 self.gecko.mWillChangeBitField = bits;
             },
             WillChange::Auto => {
                 unsafe {
-                    Gecko_ClearWillChange(&mut self.gecko, 0);
+                    Gecko_ClearWillChange(&mut *self.gecko, 0);
                 }
                 self.gecko.mWillChangeBitField = WillChangeBits::empty();
             },
         };
     }
 
     pub fn copy_will_change_from(&mut self, other: &Self) {
         use crate::gecko_bindings::bindings::Gecko_CopyWillChangeFrom;
 
         self.gecko.mWillChangeBitField = other.gecko.mWillChangeBitField;
         unsafe {
-            Gecko_CopyWillChangeFrom(&mut self.gecko, &other.gecko);
+            Gecko_CopyWillChangeFrom(&mut *self.gecko, &*other.gecko);
         }
     }
 
     pub fn reset_will_change(&mut self, other: &Self) {
         self.copy_will_change_from(other)
     }
 
     pub fn clone_will_change(&self) -> longhands::will_change::computed_value::T {
@@ -3302,32 +3303,32 @@ fn static_assert() {
 
 <%self:impl_trait style_struct_name="List"
                   skip_longhands="list-style-image list-style-type quotes -moz-image-region">
 
     pub fn set_list_style_image(&mut self, image: longhands::list_style_image::computed_value::T) {
         match image {
             UrlOrNone::None => {
                 unsafe {
-                    Gecko_SetListStyleImageNone(&mut self.gecko);
+                    Gecko_SetListStyleImageNone(&mut *self.gecko);
                 }
             }
             UrlOrNone::Url(ref url) => {
                 unsafe {
                     Gecko_SetListStyleImageImageValue(
-                        &mut self.gecko,
+                        &mut *self.gecko,
                         url.url_value_ptr(),
                     );
                 }
             }
         }
     }
 
     pub fn copy_list_style_image_from(&mut self, other: &Self) {
-        unsafe { Gecko_CopyListStyleImageFrom(&mut self.gecko, &other.gecko); }
+        unsafe { Gecko_CopyListStyleImageFrom(&mut *self.gecko, &*other.gecko); }
     }
 
     pub fn reset_list_style_image(&mut self, other: &Self) {
         self.copy_list_style_image_from(other)
     }
 
     pub fn clone_list_style_image(&self) -> longhands::list_style_image::computed_value::T {
         use crate::values::computed::url::ComputedImageUrl;
@@ -3639,17 +3640,17 @@ fn static_assert() {
 
         fn fill_filter(m_type: u32, value: CoordDataValue, gecko_filter: &mut nsStyleFilter){
             gecko_filter.mType = m_type;
             gecko_filter.mFilterParameter.set_value(value);
         }
 
         let v = v.into_iter();
         unsafe {
-            Gecko_ResetFilters(&mut self.gecko, v.len());
+            Gecko_ResetFilters(&mut *self.gecko, v.len());
         }
         debug_assert_eq!(v.len(), self.gecko.mFilters.len());
 
         for (servo, gecko_filter) in v.zip(self.gecko.mFilters.iter_mut()) {
             match servo {
                 % for func in FILTER_FUNCTIONS:
                 ${func}(factor) => fill_filter(NS_STYLE_FILTER_${func.upper()},
                                                CoordDataValue::Factor(factor.0),
@@ -3685,17 +3686,17 @@ fn static_assert() {
                     }
                 },
             }
         }
     }
 
     pub fn copy_filter_from(&mut self, other: &Self) {
         unsafe {
-            Gecko_CopyFiltersFrom(&other.gecko as *const _ as *mut _, &mut self.gecko);
+            Gecko_CopyFiltersFrom(&other.gecko as *const _ as *mut _, &mut *self.gecko);
         }
     }
 
     pub fn reset_filter(&mut self, other: &Self) {
         self.copy_filter_from(other)
     }
 
     pub fn clone_filter(&self) -> longhands::filter::computed_value::T {
@@ -4134,35 +4135,35 @@ clip-path
         use crate::gecko_bindings::structs::nsStyleSVG_STROKE_DASHARRAY_CONTEXT as CONTEXT_VALUE;
         use crate::values::generics::svg::SVGStrokeDashArray;
 
         match v {
             SVGStrokeDashArray::Values(v) => {
                 let v = v.into_iter();
                 self.gecko.mContextFlags &= !CONTEXT_VALUE;
                 unsafe {
-                    bindings::Gecko_nsStyleSVG_SetDashArrayLength(&mut self.gecko, v.len() as u32);
+                    bindings::Gecko_nsStyleSVG_SetDashArrayLength(&mut *self.gecko, v.len() as u32);
                 }
                 for (gecko, servo) in self.gecko.mStrokeDasharray.iter_mut().zip(v) {
                     *gecko = servo;
                 }
             }
             SVGStrokeDashArray::ContextValue => {
                 self.gecko.mContextFlags |= CONTEXT_VALUE;
                 unsafe {
-                    bindings::Gecko_nsStyleSVG_SetDashArrayLength(&mut self.gecko, 0);
+                    bindings::Gecko_nsStyleSVG_SetDashArrayLength(&mut *self.gecko, 0);
                 }
             }
         }
     }
 
     pub fn copy_stroke_dasharray_from(&mut self, other: &Self) {
         use crate::gecko_bindings::structs::nsStyleSVG_STROKE_DASHARRAY_CONTEXT as CONTEXT_VALUE;
         unsafe {
-            bindings::Gecko_nsStyleSVG_CopyDashArray(&mut self.gecko, &other.gecko);
+            bindings::Gecko_nsStyleSVG_CopyDashArray(&mut *self.gecko, &*other.gecko);
         }
         self.gecko.mContextFlags =
             (self.gecko.mContextFlags & !CONTEXT_VALUE) |
             (other.gecko.mContextFlags & CONTEXT_VALUE);
     }
 
     pub fn reset_stroke_dasharray(&mut self, other: &Self) {
         self.copy_stroke_dasharray_from(other)
@@ -4199,55 +4200,56 @@ clip-path
     #[allow(non_snake_case)]
     pub fn set__moz_context_properties<I>(&mut self, v: I)
     where
         I: IntoIterator<Item = longhands::_moz_context_properties::computed_value::single_value::T>,
         I::IntoIter: ExactSizeIterator
     {
         let v = v.into_iter();
         unsafe {
-            bindings::Gecko_nsStyleSVG_SetContextPropertiesLength(&mut self.gecko, v.len() as u32);
+            bindings::Gecko_nsStyleSVG_SetContextPropertiesLength(&mut *self.gecko, v.len() as u32);
         }
 
-        self.gecko.mContextPropsBits = 0;
-        for (gecko, servo) in self.gecko.mContextProps.iter_mut().zip(v) {
+        let s = &mut *self.gecko;
+        s.mContextPropsBits = 0;
+        for (gecko, servo) in s.mContextProps.iter_mut().zip(v) {
             if (servo.0).0 == atom!("fill") {
-                self.gecko.mContextPropsBits |= structs::NS_STYLE_CONTEXT_PROPERTY_FILL as u8;
+                s.mContextPropsBits |= structs::NS_STYLE_CONTEXT_PROPERTY_FILL as u8;
             } else if (servo.0).0 == atom!("stroke") {
-                self.gecko.mContextPropsBits |= structs::NS_STYLE_CONTEXT_PROPERTY_STROKE as u8;
+                s.mContextPropsBits |= structs::NS_STYLE_CONTEXT_PROPERTY_STROKE as u8;
             } else if (servo.0).0 == atom!("fill-opacity") {
-                self.gecko.mContextPropsBits |= structs::NS_STYLE_CONTEXT_PROPERTY_FILL_OPACITY as u8;
+                s.mContextPropsBits |= structs::NS_STYLE_CONTEXT_PROPERTY_FILL_OPACITY as u8;
             } else if (servo.0).0 == atom!("stroke-opacity") {
-                self.gecko.mContextPropsBits |= structs::NS_STYLE_CONTEXT_PROPERTY_STROKE_OPACITY as u8;
+                s.mContextPropsBits |= structs::NS_STYLE_CONTEXT_PROPERTY_STROKE_OPACITY as u8;
             }
             gecko.mRawPtr = (servo.0).0.into_addrefed();
         }
     }
 
     #[allow(non_snake_case)]
     pub fn copy__moz_context_properties_from(&mut self, other: &Self) {
         unsafe {
-            bindings::Gecko_nsStyleSVG_CopyContextProperties(&mut self.gecko, &other.gecko);
+            bindings::Gecko_nsStyleSVG_CopyContextProperties(&mut *self.gecko, &*other.gecko);
         }
     }
 
     #[allow(non_snake_case)]
     pub fn reset__moz_context_properties(&mut self, other: &Self) {
         self.copy__moz_context_properties_from(other)
     }
 </%self:impl_trait>
 
 <%self:impl_trait style_struct_name="Color">
 </%self:impl_trait>
 
 <%self:impl_trait style_struct_name="InheritedUI" skip_longhands="cursor">
     pub fn set_cursor(&mut self, v: longhands::cursor::computed_value::T) {
         self.gecko.mCursor = v.keyword;
         unsafe {
-            Gecko_SetCursorArrayLength(&mut self.gecko, v.images.len());
+            Gecko_SetCursorArrayLength(&mut *self.gecko, v.images.len());
         }
         for i in 0..v.images.len() {
             unsafe {
                 Gecko_SetCursorImageValue(
                     &mut self.gecko.mCursorImages[i],
                     v.images[i].url.url_value_ptr(),
                 );
             }
@@ -4263,17 +4265,17 @@ clip-path
                 }
             }
         }
     }
 
     pub fn copy_cursor_from(&mut self, other: &Self) {
         self.gecko.mCursor = other.gecko.mCursor;
         unsafe {
-            Gecko_CopyCursorArrayFrom(&mut self.gecko, &other.gecko);
+            Gecko_CopyCursorArrayFrom(&mut *self.gecko, &*other.gecko);
         }
     }
 
     pub fn reset_cursor(&mut self, other: &Self) {
         self.copy_cursor_from(other)
     }
 
     pub fn clone_cursor(&self) -> longhands::cursor::computed_value::T {
@@ -4382,30 +4384,30 @@ clip-path
         }
 
         match v {
             Content::None |
             Content::Normal => {
                 // Ensure destructors run, otherwise we could leak.
                 if !self.gecko.mContents.is_empty() {
                     unsafe {
-                        Gecko_ClearAndResizeStyleContents(&mut self.gecko, 0);
+                        Gecko_ClearAndResizeStyleContents(&mut *self.gecko, 0);
                     }
                 }
             },
             Content::MozAltContent => {
                 unsafe {
-                    Gecko_ClearAndResizeStyleContents(&mut self.gecko, 1);
+                    Gecko_ClearAndResizeStyleContents(&mut *self.gecko, 1);
                     *self.gecko.mContents[0].mContent.mString.as_mut() = ptr::null_mut();
                 }
                 self.gecko.mContents[0].mType = StyleContentType::AltContent;
             },
             Content::Items(items) => {
                 unsafe {
-                    Gecko_ClearAndResizeStyleContents(&mut self.gecko,
+                    Gecko_ClearAndResizeStyleContents(&mut *self.gecko,
                                                       items.len() as u32);
                 }
                 for (i, item) in items.into_vec().into_iter().enumerate() {
                     // NB: Gecko compares the mString value if type is not image
                     // or URI independently of whatever gets there. In the quote
                     // cases, they set it to null, so do the same here.
                     unsafe {
                         *self.gecko.mContents[i].mContent.mString.as_mut() = ptr::null_mut();
@@ -4476,17 +4478,17 @@ clip-path
                 }
             }
         }
     }
 
     pub fn copy_content_from(&mut self, other: &Self) {
         use crate::gecko_bindings::bindings::Gecko_CopyStyleContentsFrom;
         unsafe {
-            Gecko_CopyStyleContentsFrom(&mut self.gecko, &other.gecko)
+            Gecko_CopyStyleContentsFrom(&mut *self.gecko, &*other.gecko)
         }
     }
 
     pub fn reset_content(&mut self, other: &Self) {
         self.copy_content_from(other)
     }
 
     pub fn clone_content(&self) -> longhands::content::computed_value::T {
@@ -4570,29 +4572,29 @@ clip-path
     }
 
     % for counter_property in ["Increment", "Reset", "Set"]:
         pub fn set_counter_${counter_property.lower()}(
             &mut self,
             v: longhands::counter_${counter_property.lower()}::computed_value::T
         ) {
             unsafe {
-                bindings::Gecko_ClearAndResizeCounter${counter_property}s(&mut self.gecko, v.len() as u32);
+                bindings::Gecko_ClearAndResizeCounter${counter_property}s(&mut *self.gecko, v.len() as u32);
                 for (i, pair) in v.0.into_vec().into_iter().enumerate() {
                     self.gecko.m${counter_property}s[i].mCounter.set_move(
                         RefPtr::from_addrefed(pair.name.0.into_addrefed())
                     );
                     self.gecko.m${counter_property}s[i].mValue = pair.value;
                 }
             }
         }
 
         pub fn copy_counter_${counter_property.lower()}_from(&mut self, other: &Self) {
             unsafe {
-                bindings::Gecko_CopyCounter${counter_property}sFrom(&mut self.gecko, &other.gecko)
+                bindings::Gecko_CopyCounter${counter_property}sFrom(&mut *self.gecko, &*other.gecko)
             }
         }
 
         pub fn reset_counter_${counter_property.lower()}(&mut self, other: &Self) {
             self.copy_counter_${counter_property.lower()}_from(other)
         }
 
         pub fn clone_counter_${counter_property.lower()}(
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -2,17 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
 
 use super::error_reporter::ErrorReporter;
 use super::stylesheet_loader::{AsyncStylesheetParser, StylesheetLoader};
 use cssparser::ToCss as ParserToCss;
 use cssparser::{ParseErrorKind, Parser, ParserInput, SourceLocation, UnicodeRange};
 use malloc_size_of::MallocSizeOfOps;
-use nsstring::{nsCString, nsString, nsStringRepr};
+use nsstring::{nsCString, nsString};
 use selectors::matching::{matches_selector, MatchingContext, MatchingMode};
 use selectors::{NthIndexCache, SelectorList};
 use servo_arc::{Arc, ArcBorrow, RawOffsetArc};
 use smallvec::SmallVec;
 use std::cell::RefCell;
 use std::collections::BTreeSet;
 use std::fmt::Write;
 use std::iter;
@@ -1143,19 +1143,17 @@ pub unsafe extern "C" fn Servo_Property_
     if values.contains("transparent") {
         // This is a special value devtools use to avoid inserting the
         // long list of color keywords. We need to prepend it to values.
         extras.push("COLOR");
     }
 
     let result = result.as_mut().unwrap();
     let len = extras.len() + values.len();
-    // FIXME(emilio): This is one place where our nsString -> nsStringRepr
-    // conversion during bindgen goes bad.
-    bindings::Gecko_ResizeTArrayForStrings(result as *mut _ as *mut nsTArray<nsStringRepr>, len as u32);
+    bindings::Gecko_ResizeTArrayForStrings(result as *mut _ as *mut nsTArray<nsString>, len as u32);
 
     for (src, dest) in extras.iter().chain(values.iter()).zip(result.iter_mut()) {
         dest.write_str(src).unwrap();
     }
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_Property_IsAnimatable(prop: nsCSSPropertyID) -> bool {
--- a/taskcluster/ci/test/raptor.yml
+++ b/taskcluster/ci/test/raptor.yml
@@ -287,33 +287,43 @@ raptor-tp6m-1-geckoview:
 
 raptor-tp6m-1-fenix:
     description: "Raptor tp6m-1 on Fenix"
     try-name: raptor-tp6m-1-fenix
     treeherder-symbol: Rap-fenix(tp6m-1)
     run-on-projects: ['try']
     e10s: true
     target:
-        index: project.mobile.fenix.v2.branch.master.latest.raptor.arm
-        name: target.apk
+        by-test-platform:
+            android-hw.*-aarch64.*/.*:
+                index: project.mobile.fenix.v2.branch.master.latest.release-raptor.aarch64
+                name: target.apk
+            default:
+                index: project.mobile.fenix.v2.branch.master.latest.raptor.arm
+                name: target.apk
     tier: 2
     mozharness:
         extra-options:
             - --test=raptor-tp6m-1
             - --app=fenix
             - --binary-path=org.mozilla.fenix.raptor
             - --activity=org.mozilla.fenix.browser.BrowserPerformanceTestActivity
 
 raptor-tp6m-1-refbrow:
     description: "Raptor tp6m-1 on the reference browser"
     treeherder-symbol: Rap-refbrow(tp6m-1)
     run-on-projects: ['try']
     target:
-        index: project.mobile.reference-browser.signed-nightly.nightly.latest
-        name: target.arm.apk
+        by-test-platform:
+            android-hw.*-aarch64.*/.*:
+                index: project.mobile.reference-browser.signed-nightly.nightly.latest
+                name: target.aarch64.apk
+            default:
+                index: project.mobile.reference-browser.signed-nightly.nightly.latest
+                name: target.arm.apk
     tier: 2
     mozharness:
         extra-options:
             - --test=raptor-tp6m-1
             - --app=refbrow
             - --binary-path=org.mozilla.reference.browser
             - --activity=org.mozilla.reference.browser.BrowserTestActivity
 
@@ -733,239 +743,309 @@ raptor-tp6m-10-fennec:
 
 raptor-tp6m-1-fenix-cold:
     description: "Raptor tp6m-1 cold page-load on Fenix"
     try-name: raptor-tp6m-1-fenix-cold
     treeherder-symbol: Rap-fenix(tp6m-c-1)
     run-on-projects: ['try']
     e10s: true
     target:
-        index: project.mobile.fenix.v2.branch.master.latest.raptor.arm
-        name: target.apk
+        by-test-platform:
+            android-hw.*-aarch64.*/.*:
+                index: project.mobile.fenix.v2.branch.master.latest.release-raptor.aarch64
+                name: target.apk
+            default:
+                index: project.mobile.fenix.v2.branch.master.latest.raptor.arm
+                name: target.apk
     tier: 2
     mozharness:
         extra-options:
             - --test=raptor-tp6m-cold-1
             - --app=fenix
             - --binary-path=org.mozilla.fenix.raptor
             - --activity=org.mozilla.fenix.browser.BrowserPerformanceTestActivity
 
 raptor-tp6m-2-fenix-cold:
     description: "Raptor tp6m-2 cold page-load on Fenix"
     try-name: raptor-tp6m-2-fenix-cold
     treeherder-symbol: Rap-fenix(tp6m-c-2)
     run-on-projects: ['try']
     e10s: true
     target:
-        index: project.mobile.fenix.v2.branch.master.latest.raptor.arm
-        name: target.apk
+        by-test-platform:
+            android-hw.*-aarch64.*/.*:
+                index: project.mobile.fenix.v2.branch.master.latest.release-raptor.aarch64
+                name: target.apk
+            default:
+                index: project.mobile.fenix.v2.branch.master.latest.raptor.arm
+                name: target.apk
     tier: 2
     mozharness:
         extra-options:
             - --test=raptor-tp6m-cold-2
             - --app=fenix
             - --binary-path=org.mozilla.fenix.raptor
             - --activity=org.mozilla.fenix.browser.BrowserPerformanceTestActivity
 
 raptor-tp6m-3-fenix-cold:
     description: "Raptor tp6m-3 cold page-load on Fenix"
     try-name: raptor-tp6m-3-fenix-cold
     treeherder-symbol: Rap-fenix(tp6m-c-3)
     run-on-projects: ['try']
     e10s: true
     target:
-        index: project.mobile.fenix.v2.branch.master.latest.raptor.arm
-        name: target.apk
+        by-test-platform:
+            android-hw.*-aarch64.*/.*:
+                index: project.mobile.fenix.v2.branch.master.latest.release-raptor.aarch64
+                name: target.apk
+            default:
+                index: project.mobile.fenix.v2.branch.master.latest.raptor.arm
+                name: target.apk
     tier: 2
     mozharness:
         extra-options:
             - --test=raptor-tp6m-cold-3
             - --app=fenix
             - --binary-path=org.mozilla.fenix.raptor
             - --activity=org.mozilla.fenix.browser.BrowserPerformanceTestActivity
 
 raptor-tp6m-4-fenix-cold:
     description: "Raptor tp6m-4 cold page-load on Fenix"
     try-name: raptor-tp6m-4-fenix-cold
     treeherder-symbol: Rap-fenix(tp6m-c-4)
     run-on-projects: ['try']
     e10s: true
     target:
-        index: project.mobile.fenix.v2.branch.master.latest.raptor.arm
-        name: target.apk
+        by-test-platform:
+            android-hw.*-aarch64.*/.*:
+                index: project.mobile.fenix.v2.branch.master.latest.release-raptor.aarch64
+                name: target.apk
+            default:
+                index: project.mobile.fenix.v2.branch.master.latest.raptor.arm
+                name: target.apk
     tier: 2
     mozharness:
         extra-options:
             - --test=raptor-tp6m-cold-4
             - --app=fenix
             - --binary-path=org.mozilla.fenix.raptor
             - --activity=org.mozilla.fenix.browser.BrowserPerformanceTestActivity
 
 raptor-tp6m-5-fenix-cold:
     description: "Raptor tp6m-5 cold page-load on Fenix"
     try-name: raptor-tp6m-5-fenix-cold
     treeherder-symbol: Rap-fenix(tp6m-c-5)
     run-on-projects: ['try']
     e10s: true
     target:
-        index: project.mobile.fenix.v2.branch.master.latest.raptor.arm
-        name: target.apk
+        by-test-platform:
+            android-hw.*-aarch64.*/.*:
+                index: project.mobile.fenix.v2.branch.master.latest.release-raptor.aarch64
+                name: target.apk
+            default:
+                index: project.mobile.fenix.v2.branch.master.latest.raptor.arm
+                name: target.apk
     tier: 2
     mozharness:
         extra-options:
             - --test=raptor-tp6m-cold-5
             - --app=fenix
             - --binary-path=org.mozilla.fenix.raptor
             - --activity=org.mozilla.fenix.browser.BrowserPerformanceTestActivity
 
 raptor-tp6m-6-fenix-cold:
     description: "Raptor tp6m-6 cold page-load on Fenix"
     try-name: raptor-tp6m-6-fenix-cold
     treeherder-symbol: Rap-fenix(tp6m-c-6)
     run-on-projects: ['try']
     e10s: true
     target:
-        index: project.mobile.fenix.v2.branch.master.latest.raptor.arm
-        name: target.apk
+        by-test-platform:
+            android-hw.*-aarch64.*/.*:
+                index: project.mobile.fenix.v2.branch.master.latest.release-raptor.aarch64
+                name: target.apk
+            default:
+                index: project.mobile.fenix.v2.branch.master.latest.raptor.arm
+                name: target.apk
     tier: 2
     mozharness:
         extra-options:
             - --test=raptor-tp6m-cold-6
             - --app=fenix
             - --binary-path=org.mozilla.fenix.raptor
             - --activity=org.mozilla.fenix.browser.BrowserPerformanceTestActivity
 
 raptor-tp6m-7-fenix-cold:
     description: "Raptor tp6m-7 cold page-load on Fenix"
     try-name: raptor-tp6m-7-fenix-cold
     treeherder-symbol: Rap-fenix(tp6m-c-7)
     run-on-projects: ['try']
     e10s: true
     target:
-        index: project.mobile.fenix.v2.branch.master.latest.raptor.arm
-        name: target.apk
+        by-test-platform:
+            android-hw.*-aarch64.*/.*:
+                index: project.mobile.fenix.v2.branch.master.latest.release-raptor.aarch64
+                name: target.apk
+            default:
+                index: project.mobile.fenix.v2.branch.master.latest.raptor.arm
+                name: target.apk
     tier: 2
     mozharness:
         extra-options:
             - --test=raptor-tp6m-cold-7
             - --app=fenix
             - --binary-path=org.mozilla.fenix.raptor
             - --activity=org.mozilla.fenix.browser.BrowserPerformanceTestActivity
 
 raptor-tp6m-8-fenix-cold:
     description: "Raptor tp6m-8 cold page-load on Fenix"
     try-name: raptor-tp6m-8-fenix-cold
     treeherder-symbol: Rap-fenix(tp6m-c-8)
     run-on-projects: ['try']
     e10s: true
     target:
-        index: project.mobile.fenix.v2.branch.master.latest.raptor.arm
-        name: target.apk
+        by-test-platform:
+            android-hw.*-aarch64.*/.*:
+                index: project.mobile.fenix.v2.branch.master.latest.release-raptor.aarch64
+                name: target.apk
+            default:
+                index: project.mobile.fenix.v2.branch.master.latest.raptor.arm
+                name: target.apk
     tier: 2
     mozharness:
         extra-options:
             - --test=raptor-tp6m-cold-8
             - --app=fenix
             - --binary-path=org.mozilla.fenix.raptor
             - --activity=org.mozilla.fenix.browser.BrowserPerformanceTestActivity
 
 raptor-tp6m-9-fenix-cold:
     description: "Raptor tp6m-9 cold page-load on Fenix"
     try-name: raptor-tp6m-9-fenix-cold
     treeherder-symbol: Rap-fenix(tp6m-c-9)
     run-on-projects: ['try']
     e10s: true
     target:
-        index: project.mobile.fenix.v2.branch.master.latest.raptor.arm
-        name: target.apk
+        by-test-platform:
+            android-hw.*-aarch64.*/.*:
+                index: project.mobile.fenix.v2.branch.master.latest.release-raptor.aarch64
+                name: target.apk
+            default:
+                index: project.mobile.fenix.v2.branch.master.latest.raptor.arm
+                name: target.apk
     tier: 2
     mozharness:
         extra-options:
             - --test=raptor-tp6m-cold-9
             - --app=fenix
             - --binary-path=org.mozilla.fenix.raptor
             - --activity=org.mozilla.fenix.browser.BrowserPerformanceTestActivity
 
 raptor-tp6m-10-fenix-cold:
     description: "Raptor tp6m-10 cold page-load on Fenix"
     try-name: raptor-tp6m-10-fenix-cold
     treeherder-symbol: Rap-fenix(tp6m-c-10)
     run-on-projects: ['try']
     e10s: true
     target:
-        index: project.mobile.fenix.v2.branch.master.latest.raptor.arm
-        name: target.apk
+        by-test-platform:
+            android-hw.*-aarch64.*/.*:
+                index: project.mobile.fenix.v2.branch.master.latest.release-raptor.aarch64
+                name: target.apk
+            default:
+                index: project.mobile.fenix.v2.branch.master.latest.raptor.arm
+                name: target.apk
     tier: 2
     mozharness:
         extra-options:
             - --test=raptor-tp6m-cold-10
             - --app=fenix
             - --binary-path=org.mozilla.fenix.raptor
             - --activity=org.mozilla.fenix.browser.BrowserPerformanceTestActivity
 
 raptor-tp6m-11-fenix-cold:
     description: "Raptor tp6m-11 cold page-load on Fenix"
     try-name: raptor-tp6m-11-fenix-cold
     treeherder-symbol: Rap-fenix(tp6m-c-11)
     run-on-projects: ['try']
     e10s: true
     target:
-        index: project.mobile.fenix.v2.branch.master.latest.raptor.arm
-        name: target.apk
+        by-test-platform:
+            android-hw.*-aarch64.*/.*:
+                index: project.mobile.fenix.v2.branch.master.latest.release-raptor.aarch64
+                name: target.apk
+            default:
+                index: project.mobile.fenix.v2.branch.master.latest.raptor.arm
+                name: target.apk
     tier: 2
     mozharness:
         extra-options:
             - --test=raptor-tp6m-cold-11
             - --app=fenix
             - --binary-path=org.mozilla.fenix.raptor
             - --activity=org.mozilla.fenix.browser.BrowserPerformanceTestActivity
 
 raptor-tp6m-12-fenix-cold:
     description: "Raptor tp6m-12 cold page-load on Fenix"
     try-name: raptor-tp6m-12-fenix-cold
     treeherder-symbol: Rap-fenix(tp6m-c-12)
     run-on-projects: ['try']
     e10s: true
     target:
-        index: project.mobile.fenix.v2.branch.master.latest.raptor.arm
-        name: target.apk
+        by-test-platform:
+            android-hw.*-aarch64.*/.*:
+                index: project.mobile.fenix.v2.branch.master.latest.release-raptor.aarch64
+                name: target.apk
+            default:
+                index: project.mobile.fenix.v2.branch.master.latest.raptor.arm
+                name: target.apk
     tier: 2
     mozharness:
         extra-options:
             - --test=raptor-tp6m-cold-12
             - --app=fenix
             - --binary-path=org.mozilla.fenix.raptor
             - --activity=org.mozilla.fenix.browser.BrowserPerformanceTestActivity
 
 raptor-tp6m-13-fenix-cold:
     description: "Raptor tp6m-13 cold page-load on Fenix"
     try-name: raptor-tp6m-13-fenix-cold
     treeherder-symbol: Rap-fenix(tp6m-c-13)
     run-on-projects: ['try']
     e10s: true
     target:
-        index: project.mobile.fenix.v2.branch.master.latest.raptor.arm
-        name: target.apk
+        by-test-platform:
+            android-hw.*-aarch64.*/.*:
+                index: project.mobile.fenix.v2.branch.master.latest.release-raptor.aarch64
+                name: target.apk
+            default:
+                index: project.mobile.fenix.v2.branch.master.latest.raptor.arm
+                name: target.apk
     tier: 2
     mozharness:
         extra-options:
             - --test=raptor-tp6m-cold-13
             - --app=fenix
             - --binary-path=org.mozilla.fenix.raptor
             - --activity=org.mozilla.fenix.browser.BrowserPerformanceTestActivity
 
 raptor-tp6m-14-fenix-cold:
     description: "Raptor tp6m-14 cold page-load on Fenix"
     try-name: raptor-tp6m-14-fenix-cold
     treeherder-symbol: Rap-fenix(tp6m-c-14)
     run-on-projects: ['try']
     e10s: true
     target:
-        index: project.mobile.fenix.v2.branch.master.latest.raptor.arm
-        name: target.apk
+        by-test-platform:
+            android-hw.*-aarch64.*/.*:
+                index: project.mobile.fenix.v2.branch.master.latest.release-raptor.aarch64
+                name: target.apk
+            default:
+                index: project.mobile.fenix.v2.branch.master.latest.raptor.arm
+                name: target.apk
     tier: 2
     mozharness:
         extra-options:
             - --test=raptor-tp6m-cold-14
             - --app=fenix
             - --binary-path=org.mozilla.fenix.raptor
             - --activity=org.mozilla.fenix.browser.BrowserPerformanceTestActivity
 
@@ -1086,18 +1166,23 @@ raptor-speedometer-fennec:
             - --app=fennec
             - --binary=org.mozilla.fennec_aurora
 
 raptor-speedometer-refbrow:
     description: "Raptor Speedometer on the reference browser"
     treeherder-symbol: Rap-refbrow(sp)
     run-on-projects: ['try']
     target:
-        index: project.mobile.reference-browser.signed-nightly.nightly.latest
-        name: target.arm.apk
+        by-test-platform:
+            android-hw.*-aarch64.*/.*:
+                index: project.mobile.reference-browser.signed-nightly.nightly.latest
+                name: target.aarch64.apk
+            default:
+                index: project.mobile.reference-browser.signed-nightly.nightly.latest
+                name: target.arm.apk
     tier: 2
     mozharness:
         extra-options:
             - --test=raptor-speedometer
             - --app=refbrow
             - --binary-path=org.mozilla.reference.browser
             - --activity=org.mozilla.reference.browser.BrowserTestActivity
 
@@ -1249,18 +1334,23 @@ raptor-unity-webgl-geckoview:
         fetch:
             - unity-webgl
 
 raptor-unity-webgl-refbrow:
     description: "Raptor Unity WebGL on the reference browser"
     treeherder-symbol: Rap-refbrow(ugl)
     run-on-projects: ['try']
     target:
-        index: project.mobile.reference-browser.signed-nightly.nightly.latest
-        name: target.arm.apk
+        by-test-platform:
+            android-hw.*-aarch64.*/.*:
+                index: project.mobile.reference-browser.signed-nightly.nightly.latest
+                name: target.aarch64.apk
+            default:
+                index: project.mobile.reference-browser.signed-nightly.nightly.latest
+                name: target.arm.apk
     tier: 2
     mozharness:
         extra-options:
             - --test=raptor-unity-webgl
             - --app=refbrow
             - --binary-path=org.mozilla.reference.browser
             - --activity=org.mozilla.reference.browser.BrowserTestActivity
     fetches:
--- a/taskcluster/taskgraph/transforms/tests.py
+++ b/taskcluster/taskgraph/transforms/tests.py
@@ -856,16 +856,17 @@ def handle_keyed_by(config, tests):
         'run-on-projects',
         'os-groups',
         'run-as-administrator',
         'workdir',
         'worker-type',
         'virtualization',
         'fetches.fetch',
         'fetches.toolchain',
+        'target',
     ]
     for test in tests:
         for field in fields:
             resolve_keyed_by(test, field, item_name=test['test-name'],
                              project=config.params['project'])
         yield test
 
 
--- a/toolkit/components/search/moz.build
+++ b/toolkit/components/search/moz.build
@@ -1,16 +1,19 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 XPCSHELL_TESTS_MANIFESTS += [
-    'tests/xpcshell/searchconfigs/xpcshell.ini',
+    'tests/xpcshell/searchconfigs/xpcshell-1.ini',
+    'tests/xpcshell/searchconfigs/xpcshell-2.ini',
+    'tests/xpcshell/searchconfigs/xpcshell-3.ini',
+    'tests/xpcshell/searchconfigs/xpcshell-4.ini',
     'tests/xpcshell/xpcshell.ini',
 ]
 
 XPIDL_SOURCES += [
     'nsISearchService.idl',
 ]
 
 XPIDL_MODULE = 'toolkit_search'
new file mode 100644
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/head_chunk1.js
@@ -0,0 +1,8 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* import-globals-from head_searchconfig.js */
+
+"use strict";
+
+Services.prefs.setIntPref("browser.search.config.test.section", 1);
new file mode 100644
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/head_chunk2.js
@@ -0,0 +1,8 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* import-globals-from head_searchconfig.js */
+
+"use strict";
+
+Services.prefs.setIntPref("browser.search.config.test.section", 2);
new file mode 100644
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/head_chunk3.js
@@ -0,0 +1,8 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* import-globals-from head_searchconfig.js */
+
+"use strict";
+
+Services.prefs.setIntPref("browser.search.config.test.section", 3);
new file mode 100644
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/head_chunk4.js
@@ -0,0 +1,8 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* import-globals-from head_searchconfig.js */
+
+"use strict";
+
+Services.prefs.setIntPref("browser.search.config.test.section", 4);
--- a/toolkit/components/search/tests/xpcshell/searchconfigs/head_searchconfig.js
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/head_searchconfig.js
@@ -8,16 +8,19 @@ XPCOMUtils.defineLazyModuleGetters(this,
   AddonTestUtils: "resource://testing-common/AddonTestUtils.jsm",
   OS: "resource://gre/modules/osfile.jsm",
   SearchTestUtils: "resource://testing-common/SearchTestUtils.jsm",
   Services: "resource://gre/modules/Services.jsm",
 });
 
 const GLOBAL_SCOPE = this;
 
+const URLTYPE_SUGGEST_JSON = "application/x-suggestions+json";
+const URLTYPE_SEARCH_HTML  = "text/html";
+
 /**
  * This class implements the test harness for search configuration tests.
  * These tests are designed to ensure that the correct search engines are
  * loaded for the various region/locale configurations.
  *
  * The configuration for each test is represented by an object having the
  * following properties:
  *
@@ -77,16 +80,18 @@ class SearchConfigTest {
 
     // Disable region checks.
     Services.prefs.setBoolPref("browser.search.geoSpecificDefaults", false);
     Services.prefs.setCharPref("browser.search.geoip.url", "");
 
     await AddonTestUtils.promiseStartupManager();
     await Services.search.init();
 
+    // Note: we don't use the helper function here, so that we have at least
+    // one message output per process.
     Assert.ok(Services.search.isInitialized,
       "Should have correctly initialized the search service");
   }
 
   /**
    * Runs the test.
    */
   async run() {
@@ -96,21 +101,25 @@ class SearchConfigTest {
     // We loop on region and then locale, so that we always cause a re-init
     // when updating the requested/available locales.
     for (let region of regions) {
       for (let locale of locales) {
         info(`Checking region: "${region}" locale: "${locale}"`);
         await this._reinit(region, locale);
 
         this._assertDefaultEngines(region, locale);
-        await this._assertAvailableEngines(region, locale);
+        const engines = await Services.search.getVisibleEngines();
+        const isPresent = this._assertAvailableEngines(region, locale, engines);
+        if (isPresent) {
+          this._assertCorrectDomains(region, locale, engines);
+        }
       }
     }
 
-    Assert.ok(!this._testDebug, "Should not have test debug turned on in production");
+    this.assertOk(!this._testDebug, "Should not have test debug turned on in production");
   }
 
   /**
    * Causes re-initialization of the SearchService with the new region and locale.
    *
    * @param {string} region
    *   The two-letter region code.
    * @param {string} locale
@@ -120,28 +129,32 @@ class SearchConfigTest {
     Services.prefs.setStringPref("browser.search.region", region.toUpperCase());
     const reinitCompletePromise =
       SearchTestUtils.promiseSearchNotification("reinit-complete");
     Services.locale.availableLocales = [locale];
     Services.locale.requestedLocales = [locale];
     Services.search.reInit();
     await reinitCompletePromise;
 
-    Assert.ok(Services.search.isInitialized,
+    this.assertOk(Services.search.isInitialized,
       "Should have completely re-initialization, if it fails check logs for if reinit was successful");
   }
 
   /**
    * @returns {Set} the list of regions for the tests to run with.
    */
   get _regions() {
     if (this._testDebug) {
       return new Set(["by", "cn", "kz", "us", "ru", "tr"]);
     }
-    return Services.intl.getAvailableLocaleDisplayNames("region");
+    const chunk = Services.prefs.getIntPref("browser.search.config.test.section", -1) - 1;
+    const regions = Services.intl.getAvailableLocaleDisplayNames("region");
+    const chunkSize =  Math.ceil(regions.length / 4);
+    const startPoint = chunk * chunkSize;
+    return regions.slice(startPoint, startPoint + chunkSize);
   }
 
   /**
    * @returns {array} the list of locales for the tests to run with.
    */
   async _getLocales() {
     if (this._testDebug) {
       return ["be", "en-US", "kk", "tr", "ru", "zh-CN"];
@@ -196,96 +209,161 @@ class SearchConfigTest {
       if (inRegions && inLocales) {
         return true;
       }
     }
     return false;
   }
 
   /**
-   * Helper function to determine if an engine matches within a list.
+   * Helper function to find an engine from within a list.
    * Due to Amazon's current complex setup with three different identifiers,
    * if the identifier is 'amazon', then we do a startsWith match. Otherwise
    * we expect the names to equal.
    *
    * @param {Array} engines
    *   The list of engines to check.
    * @param {string} identifier
    *   The identifier to look for in the list.
-   * @returns {boolean}
-   *   Returns true if the engine is found within the list.
+   * @returns {Engine}
+   *   Returns the engine if found, null otherwise.
    */
-  _enginesMatch(engines, identifier) {
+  _findEngine(engines, identifier) {
     if (identifier == "amazon") {
-      return !!engines.find(engine => engine.startsWith(identifier));
+      return engines.find(engine => engine.identifier.startsWith(identifier));
     }
-    return engines.includes(identifier);
+    return engines.find(engine => engine.identifier == identifier);
   }
 
   /**
    * Asserts whether the engines rules defined in the configuration are met.
    *
    * @param {Array} engines
    *   The list of engines to check.
    * @param {string} region
    *   The two-letter region code.
    * @param {string} locale
    *   The two-letter locale code.
    * @param {string} section
    *   The section of the configuration to check.
+   * @returns {boolean}
+   *   Returns true if the engine is expected to be present, false otherwise.
    */
   _assertEngineRules(engines, region, locale, section) {
     const infoString = `region: "${region}" locale: "${locale}"`;
     const config = this._config[section];
     const hasIncluded = "included" in config;
     const hasExcluded = "excluded" in config;
-    const identifierIncluded = this._enginesMatch(engines, this._config.identifier);
+    const identifierIncluded = !!this._findEngine(engines, this._config.identifier);
 
     // If there's not included/excluded, then this shouldn't be the default anywhere.
     if (section == "default" && !hasIncluded && !hasExcluded) {
-      Assert.ok(!identifierIncluded,
+      this.assertOk(!identifierIncluded,
         `Should not be ${section} for any locale/region,
          currently set for ${infoString}`);
-      return;
+      return false;
     }
 
     // If there's no included section, we assume the engine is default everywhere
     // and we should apply the exclusions instead.
     let included = (hasIncluded &&
       this._localeRegionInSection(config.included, region, locale));
 
     let notExcluded = (hasExcluded &&
      !this._localeRegionInSection(config.excluded, region, locale));
 
     if (included || notExcluded) {
-      Assert.ok(identifierIncluded, `Should be ${section} for ${infoString}`);
-      return;
+      this.assertOk(identifierIncluded, `Should be ${section} for ${infoString}`);
+      return true;
     }
-    Assert.ok(!identifierIncluded, `Should not be ${section} for ${infoString}`);
+    this.assertOk(!identifierIncluded, `Should not be ${section} for ${infoString}`);
+    return false;
   }
 
   /**
    * Asserts whether the engine is correctly set as default or not.
    *
    * @param {string} region
    *   The two-letter region code.
    * @param {string} locale
    *   The two-letter locale code.
    */
   _assertDefaultEngines(region, locale) {
-    const identifier = Services.search.originalDefaultEngine.identifier;
-    this._assertEngineRules([identifier], region, locale, "default");
+    this._assertEngineRules([Services.search.originalDefaultEngine], region,
+                            locale, "default");
   }
 
   /**
    * Asserts whether the engine is correctly available or not.
    *
    * @param {string} region
    *   The two-letter region code.
    * @param {string} locale
    *   The two-letter locale code.
+   * @param {array} engines
+   *   The current visible engines.
+   * @returns {boolean}
+   *   Returns true if the engine is expected to be present, false otherwise.
    */
-  async _assertAvailableEngines(region, locale) {
-    const engines = await Services.search.getVisibleEngines();
-    const engineNames = engines.map(engine => engine._shortName);
-    this._assertEngineRules(engineNames, region, locale, "available");
+  _assertAvailableEngines(region, locale, engines) {
+    return this._assertEngineRules(engines, region, locale, "available");
+  }
+
+  /**
+   * Asserts whether the engine is using the correct domains or not.
+   *
+   * @param {string} region
+   *   The two-letter region code.
+   * @param {string} locale
+   *   The two-letter locale code.
+   * @param {array} engines
+   *   The current visible engines.
+   */
+  _assertCorrectDomains(region, locale, engines) {
+    const [expectedDomain, domainConfig] =
+      Object.entries(this._config.domains).find(([key, value]) =>
+        this._localeRegionInSection(value.included, region, locale));
+
+    this.assertOk(expectedDomain,
+      `Should have an expectedDomain for the engine in region: "${region}" locale: "${locale}"`);
+
+    const engine = this._findEngine(engines, this._config.identifier);
+    this.assertOk(engine, "Should have an engine present");
+
+    const searchForm = new URL(engine.searchForm);
+    this.assertOk(searchForm.host.endsWith(expectedDomain),
+      `Should have the correct search form domain for region: "${region}" locale: "${locale}".
+       Got "${searchForm.host}", expected to end with "${expectedDomain}".`);
+
+    for (const urlType of [URLTYPE_SUGGEST_JSON, URLTYPE_SEARCH_HTML]) {
+      const submission = engine.getSubmission("test", urlType);
+      if (urlType == URLTYPE_SUGGEST_JSON &&
+          (this._config.noSuggestionsURL || domainConfig.noSuggestionsURL)) {
+        this.assertOk(!submission, "Should not have a submission url");
+      } else if (this._config.searchUrlBase) {
+          this.assertEqual(submission.uri.prePath + submission.uri.filePath,
+            this._config.searchUrlBase + domainConfig.searchUrlEnd,
+            `Should have the correct domain for type: ${urlType} region: "${region}" locale: "${locale}".`);
+      } else {
+        this.assertOk(submission.uri.host.endsWith(expectedDomain),
+          `Should have the correct domain for type: ${urlType} region: "${region}" locale: "${locale}".
+           Got "${submission.uri.host}", expected to end with "${expectedDomain}".`);
+      }
+    }
+  }
+
+  /**
+   * Helper functions which avoid outputting test results when there are no
+   * failures. These help the tests to run faster, and avoid clogging up the
+   * python test runner process.
+   */
+  assertOk(value, message) {
+    if (!value || this._testDebug) {
+      Assert.ok(value, message);
+    }
+  }
+
+  assertEqual(actual, expected, message) {
+    if (actual != expected || this._testDebug) {
+      Assert.equal(actual, expected, message);
+    }
   }
 }
--- a/toolkit/components/search/tests/xpcshell/searchconfigs/test_amazon.js
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/test_amazon.js
@@ -24,16 +24,157 @@ const test = new SearchConfigTest({
           "ml", "mr", "ms", "my", "nb-NO", "nn-NO", "or", "pa-IN", "pt-PT", "ro",
           "si", "son", "sq", "sr", "ta", "te", "th", "tl", "trs", "ur", "uz",
           "wo", "zh-CN",
         ],
         startsWith: ["en"],
       },
     }],
   },
+  domains: {
+    // Note: These should be based on region, but we don't currently enforce that.
+    // Note: the order here is important. A region/locale match higher up in the
+    // list will override a region/locale match lower down.
+    "amazon.com.au": {
+      included: [{
+        regions: ["au"],
+        locales: {
+          matches: [
+            "ach", "af", "ar", "as", "az", "bg", "bn-IN", "cak", "eo", "en-US",
+            "en-ZA", "es-AR", "fa", "gn", "hy-AM", "ia", "is", "ka", "km", "lt",
+            "mk", "ms", "my", "ro", "si", "th", "tl", "trs", "uz",
+          ],
+        },
+      }, {
+        regions: ["au"],
+        locales: {
+          matches: [
+            "cy", "da", "el", "en-GB", "eu", "ga-IE", "gd", "gl", "hr", "nb-NO",
+            "nn-NO", "pt-PT", "sq", "sr",
+          ],
+        },
+      }],
+      noSuggestionsURL: true,
+    },
+    "amazon.ca": {
+      included: [{
+        locales: {
+          matches: ["ca", "en-CA"],
+        },
+      }, {
+        regions: ["ca"],
+        locales: {
+          matches: [
+            "ach", "af", "ar", "as", "az", "bg", "bn-IN", "cak", "eo", "en-US",
+            "en-ZA", "es-AR", "fa", "gn", "hy-AM", "ia", "is", "ka", "km", "lt",
+            "mk", "ms", "my", "ro", "si", "th", "tl", "trs", "uz",
+          ],
+        },
+      }, {
+        regions: ["ca"],
+        locales: {
+          matches: [
+            "br", "fr", "ff", "son", "wo",
+          ],
+        },
+      }],
+      noSuggestionsURL: true,
+    },
+    "amazon.fr": {
+      included: [{
+        locales: {
+          matches: ["br", "fr", "ff", "son", "wo"],
+        },
+      }, {
+        regions: ["fr"],
+        locales: {
+          matches: [
+            "ach", "af", "ar", "as", "az", "bg", "bn-IN", "cak", "eo", "en-US",
+            "en-ZA", "es-AR", "fa", "gn", "hy-AM", "ia", "is", "ka", "km", "lt",
+            "mk", "ms", "my", "ro", "si", "th", "tl", "trs", "uz",
+          ],
+        },
+      }],
+      noSuggestionsURL: true,
+    },
+    "amazon.co.uk": {
+      included: [{
+        locales: {
+          matches: [
+            "cy", "da", "el", "en-GB", "eu", "ga-IE", "gd", "gl", "hr", "nb-NO",
+            "nn-NO", "pt-PT", "sq", "sr",
+          ],
+        },
+      }, {
+        regions: ["gb"],
+        locales: {
+          matches: [
+            "ach", "af", "ar", "as", "az", "bg", "bn-IN", "cak", "eo", "en-US",
+            "en-ZA", "es-AR", "fa", "gn", "hy-AM", "ia", "is", "ka", "km", "lt",
+            "mk", "ms", "my", "ro", "si", "th", "tl", "trs", "uz",
+          ],
+        },
+      }],
+      noSuggestionsURL: true,
+    },
+    "amazon.com": {
+      included: [{
+        locales: {
+          matches: [
+            "ach", "af", "ar", "as", "az", "bg", "bn-IN", "cak", "eo", "en-US",
+            "en-ZA", "es-AR", "fa", "gn", "hy-AM", "ia", "is", "ka", "km", "lt",
+            "mk", "ms", "my", "ro", "si", "th", "tl", "trs", "uz",
+          ],
+        },
+      }],
+    },
+    "amazon.cn": {
+      included: [{
+        locales: {
+          matches: ["zh-CN"],
+        },
+      }],
+      noSuggestionsURL: true,
+    },
+    "amazon.co.jp": {
+      included: [{
+        locales: {
+          startsWith: ["ja"],
+        },
+      }],
+      noSuggestionsURL: true,
+    },
+    "amazon.de": {
+      included: [{
+        locales: {
+          matches: ["de", "dsb", "hsb"],
+        },
+      }],
+      noSuggestionsURL: true,
+    },
+    "amazon.in": {
+      included: [{
+        locales: {
+          matches: [
+            "bn", "gu-IN", "kn", "mai", "ml", "mr", "or", "pa-IN", "ta",
+            "te", "ur",
+          ],
+        },
+      }],
+      noSuggestionsURL: true,
+    },
+    "amazon.it": {
+      included: [{
+        locales: {
+          matches: ["it", "lij"],
+        },
+      }],
+      noSuggestionsURL: true,
+    },
+  },
 });
 
 add_task(async function setup() {
   // We only need to do setup on one of the tests.
   await test.setup();
 });
 
 add_task(async function test_searchConfig_amazon() {
--- a/toolkit/components/search/tests/xpcshell/searchconfigs/test_baidu.js
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/test_baidu.js
@@ -17,16 +17,21 @@ const test = new SearchConfigTest({
   },
   available: {
     included: [{
       locales: {
         matches: ["zh-CN"],
       },
     }],
   },
+  domains: {
+    "baidu.com": {
+      included: [{}],
+    },
+  },
 });
 
 add_task(async function setup() {
   await test.setup();
 });
 
 add_task(async function test_searchConfig_baidu() {
   await test.run();
--- a/toolkit/components/search/tests/xpcshell/searchconfigs/test_bing.js
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/test_bing.js
@@ -27,16 +27,21 @@ const test = new SearchConfigTest({
           "nn-NO", "oc", "or", "pa-IN", "pt-BR", "rm", "ro", "son",
           "sq", "sr", "sv-SE", "th", "tl", "trs", "uk", "ur", "uz",
           "wo", "xh", "zh-CN",
         ],
         startsWith: ["bn", "en"],
       },
     }],
   },
+  domains: {
+    "bing.com": {
+      included: [{}],
+    },
+  },
 });
 
 add_task(async function setup() {
   await test.setup();
 });
 
 add_task(async function test_searchConfig_bing() {
   await test.run();
--- a/toolkit/components/search/tests/xpcshell/searchconfigs/test_duckduckgo.js
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/test_duckduckgo.js
@@ -8,16 +8,21 @@ const test = new SearchConfigTest({
   default: {
     // Not included anywhere.
   },
   available: {
     excluded: [
       // Should be available everywhere.
     ],
   },
+  domains: {
+    "duckduckgo.com": {
+      included: [{}],
+    },
+  },
 });
 
 add_task(async function setup() {
   await test.setup();
 });
 
 add_task(async function test_searchConfig_duckduckgo() {
   await test.run();
--- a/toolkit/components/search/tests/xpcshell/searchconfigs/test_ebay.js
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/test_ebay.js
@@ -14,16 +14,113 @@ const test = new SearchConfigTest({
       // regions: [
       //   "us", "gb", "ca", "ie", "fr", "it", "de", "at", "es", "nl", "ch", "au"
       // ],
       locales: {
         matches: ["an", "en-US", "ast", "br", "ca", "cy", "de", "dsb", "en-CA", "en-GB", "es-ES", "eu", "fr", "fy-NL", "ga-IE", "gd", "gl", "hsb", "it", "lij", "nl", "rm", "wo"],
       },
     }],
   },
+  searchUrlBase: "https://rover.ebay.com/rover/1/",
+  domains: {
+    // Note: These should be based on region, but we don't currently enforce that.
+    // Note: the order here is important. A region/locale match higher up in the
+    // list will override a region/locale match lower down.
+    "befr.ebay.be": {
+      included: [{
+        regions: ["be"],
+        locales: { matches: ["br", "fr", "fy-NL", "nl", "wo"]},
+      }],
+      searchUrlEnd: "1553-53471-19255-0/1",
+    },
+    "ebay.at": {
+      included: [{
+        regions: ["at"],
+        locales: { matches: ["de", "dsb", "hsb"]},
+      }],
+      searchUrlEnd: "5221-53469-19255-0/1",
+    },
+    "ebay.ca": {
+      included: [{
+        locales: { matches: ["en-CA"] },
+      }, {
+        regions: ["ca"],
+        locales: { matches: ["br", "fr", "wo"]},
+      }],
+      searchUrlEnd: "706-53473-19255-0/1",
+    },
+    "ebay.ch": {
+      included: [{
+        locales: { matches: ["rm"] },
+      }, {
+        regions: ["ch"],
+        locales: { matches: ["br", "de", "dsb", "fr", "hsb", "wo"]},
+      }],
+      searchUrlEnd: "5222-53480-19255-0/1",
+    },
+    "ebay.com": {
+      included: [{
+        locales: { matches: ["en-US"] },
+      }],
+      searchUrlEnd: "711-53200-19255-0/1",
+    },
+    "ebay.com.au": {
+      included: [{
+        regions: ["au"],
+        locales: { matches: ["cy", "en-GB", "gd"]},
+      }],
+      searchUrlEnd: "705-53470-19255-0/1",
+    },
+    "ebay.ie": {
+      included: [{
+        locales: { matches: ["ga-IE", "ie"] },
+      }, {
+        regions: ["ie"],
+        locales: { matches: ["cy", "en-GB", "gd"]},
+      }],
+      searchUrlEnd: "5282-53468-19255-0/1",
+    },
+    "ebay.co.uk": {
+      included: [{
+        locales: { matches: ["cy", "en-GB", "gd"] },
+      }],
+      searchUrlEnd: "710-53481-19255-0/1",
+    },
+    "ebay.de": {
+      included: [{
+        locales: { matches: ["de", "dsb", "hsb"] },
+      }],
+      searchUrlEnd: "707-53477-19255-0/1",
+    },
+    "ebay.es": {
+      included: [{
+        locales: { matches: ["an", "ast", "ca", "es-ES", "eu", "gl"] },
+      }],
+      searchUrlEnd: "1185-53479-19255-0/1",
+    },
+    "ebay.fr": {
+      included: [{
+        locales: { matches: ["br", "fr", "wo"] },
+      }],
+      searchUrlEnd: "709-53476-19255-0/1",
+    },
+    "ebay.it": {
+      included: [{
+        locales: { matches: ["it", "lij"] },
+      }],
+      searchUrlEnd: "724-53478-19255-0/1",
+    },
+    "ebay.nl": {
+      included: [{
+        locales: { matches: ["fy-NL", "nl"] },
+      }],
+      searchUrlEnd: "1346-53482-19255-0/1",
+    },
+  },
+  noSuggestionsURL: true,
 });
 
 add_task(async function setup() {
   await test.setup();
 });
 
 add_task(async function test_searchConfig_ebay() {
   await test.run();
--- a/toolkit/components/search/tests/xpcshell/searchconfigs/test_google.js
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/test_google.js
@@ -24,16 +24,21 @@ const test = new SearchConfigTest({
       },
     }],
   },
   available: {
     excluded: [
       // Should be available everywhere.
     ],
   },
+  domains: {
+    "google.com": {
+      included: [{}],
+    },
+  },
 });
 
 add_task(async function setup() {
   await test.setup();
 });
 
 add_task(async function test_searchConfig_google() {
   await test.run();
--- a/toolkit/components/search/tests/xpcshell/searchconfigs/test_yandex.js
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/test_yandex.js
@@ -19,16 +19,48 @@ const test = new SearchConfigTest({
   },
   available: {
     included: [{
       locales: {
         matches: ["az", "ru", "be", "kk", "tr"],
       },
     }],
   },
+  domains: {
+    "yandex.az": {
+      included: [{
+        locales: { matches: ["az"] },
+      }],
+    },
+    "yandex.com": {
+      included: [{
+        locales: { startsWith: ["en"] },
+      }],
+    },
+    "yandex.ru": {
+      included: [{
+        locales: { matches: ["ru"] },
+      }],
+    },
+    "yandex.by": {
+      included: [{
+        locales: { matches: ["be"] },
+      }],
+    },
+    "yandex.kz": {
+      included: [{
+        locales: { matches: ["kk"] },
+      }],
+    },
+    "yandex.com.tr": {
+      included: [{
+        locales: { matches: ["tr"] },
+      }],
+    },
+  },
 });
 
 add_task(async function setup() {
   await test.setup();
 });
 
 add_task(async function test_searchConfig_yandex() {
   await test.run();
rename from toolkit/components/search/tests/xpcshell/searchconfigs/xpcshell.ini
rename to toolkit/components/search/tests/xpcshell/searchconfigs/xpcshell-1.ini
--- a/toolkit/components/search/tests/xpcshell/searchconfigs/xpcshell.ini
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/xpcshell-1.ini
@@ -1,44 +1,10 @@
 [DEFAULT]
 firefox-appdir = browser
-head = head_searchconfig.js
-skip-if = toolkit == 'android'
+head = head_searchconfig.js head_chunk1.js
+dupe-manifest =
 support-files =
   ../../../../../../browser/locales/all-locales
-tags=searchconfig
+tags=searchconfig searchconfig1
 
-[test_amazon.js]
-# This is an extensive test and currently takes a long time. Therefore skip on
-# debug/asan and extend the timeout length otherwise.
-skip-if = debug || asan
-requesttimeoutfactor = 2
-[test_baidu.js]
-# This is an extensive test and currently takes a long time. Therefore skip on
-# debug/asan and extend the timeout length otherwise.
-skip-if = debug || asan
-requesttimeoutfactor = 2
-[test_bing.js]
-# This is an extensive test and currently takes a long time. Therefore skip on
-# debug/asan and extend the timeout length otherwise.
-skip-if = debug || asan
-requesttimeoutfactor = 2
-[test_duckduckgo.js]
-# This is an extensive test and currently takes a long time. Therefore skip on
-# debug/asan and extend the timeout length otherwise.
-skip-if = debug || asan
-requesttimeoutfactor = 2
-[test_ebay.js]
-# This is an extensive test and currently takes a long time. Therefore skip on
-# debug/asan and extend the timeout length otherwise.
-# Thunderbird doesn't provide eBay search engines.
-skip-if = debug || asan || appname == "thunderbird"
-requesttimeoutfactor = 2
-[test_google.js]
-# This is an extensive test and currently takes a long time. Therefore skip on
-# debug/asan and extend the timeout length otherwise.
-skip-if = debug || asan
-requesttimeoutfactor = 2
-[test_yandex.js]
-# This is an extensive test and currently takes a long time. Therefore skip on
-# debug/asan and extend the timeout length otherwise.
-skip-if = debug || asan
-requesttimeoutfactor = 2
+[include:xpcshell-common.ini]
+skip-if = toolkit == 'android'
copy from toolkit/components/search/tests/xpcshell/searchconfigs/xpcshell.ini
copy to toolkit/components/search/tests/xpcshell/searchconfigs/xpcshell-2.ini
--- a/toolkit/components/search/tests/xpcshell/searchconfigs/xpcshell.ini
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/xpcshell-2.ini
@@ -1,44 +1,10 @@
 [DEFAULT]
 firefox-appdir = browser
-head = head_searchconfig.js
-skip-if = toolkit == 'android'
+head = head_searchconfig.js head_chunk2.js
+dupe-manifest =
 support-files =
   ../../../../../../browser/locales/all-locales
-tags=searchconfig
+tags=searchconfig searchconfig2
 
-[test_amazon.js]
-# This is an extensive test and currently takes a long time. Therefore skip on
-# debug/asan and extend the timeout length otherwise.
-skip-if = debug || asan
-requesttimeoutfactor = 2
-[test_baidu.js]
-# This is an extensive test and currently takes a long time. Therefore skip on
-# debug/asan and extend the timeout length otherwise.
-skip-if = debug || asan
-requesttimeoutfactor = 2
-[test_bing.js]
-# This is an extensive test and currently takes a long time. Therefore skip on
-# debug/asan and extend the timeout length otherwise.
-skip-if = debug || asan
-requesttimeoutfactor = 2
-[test_duckduckgo.js]
-# This is an extensive test and currently takes a long time. Therefore skip on
-# debug/asan and extend the timeout length otherwise.
-skip-if = debug || asan
-requesttimeoutfactor = 2
-[test_ebay.js]
-# This is an extensive test and currently takes a long time. Therefore skip on
-# debug/asan and extend the timeout length otherwise.
-# Thunderbird doesn't provide eBay search engines.
-skip-if = debug || asan || appname == "thunderbird"
-requesttimeoutfactor = 2
-[test_google.js]
-# This is an extensive test and currently takes a long time. Therefore skip on
-# debug/asan and extend the timeout length otherwise.
-skip-if = debug || asan
-requesttimeoutfactor = 2
-[test_yandex.js]
-# This is an extensive test and currently takes a long time. Therefore skip on
-# debug/asan and extend the timeout length otherwise.
-skip-if = debug || asan
-requesttimeoutfactor = 2
+[include:xpcshell-common.ini]
+skip-if = toolkit == 'android'
new file mode 100644
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/xpcshell-3.ini
@@ -0,0 +1,10 @@
+[DEFAULT]
+firefox-appdir = browser
+head = head_searchconfig.js head_chunk3.js
+dupe-manifest =
+support-files =
+  ../../../../../../browser/locales/all-locales
+tags=searchconfig searchconfig3
+
+[include:xpcshell-common.ini]
+skip-if = toolkit == 'android'
new file mode 100644
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/xpcshell-4.ini
@@ -0,0 +1,10 @@
+[DEFAULT]
+firefox-appdir = browser
+head = head_searchconfig.js head_chunk4.js
+dupe-manifest =
+support-files =
+  ../../../../../../browser/locales/all-locales
+tags=searchconfig searchconfig4
+
+[include:xpcshell-common.ini]
+skip-if = toolkit == 'android'
copy from toolkit/components/search/tests/xpcshell/searchconfigs/xpcshell.ini
copy to toolkit/components/search/tests/xpcshell/searchconfigs/xpcshell-common.ini
--- a/toolkit/components/search/tests/xpcshell/searchconfigs/xpcshell.ini
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/xpcshell-common.ini
@@ -1,44 +1,36 @@
-[DEFAULT]
-firefox-appdir = browser
-head = head_searchconfig.js
-skip-if = toolkit == 'android'
-support-files =
-  ../../../../../../browser/locales/all-locales
-tags=searchconfig
-
 [test_amazon.js]
 # This is an extensive test and currently takes a long time. Therefore skip on
 # debug/asan and extend the timeout length otherwise.
 skip-if = debug || asan
-requesttimeoutfactor = 2
+requesttimeoutfactor = 3
 [test_baidu.js]
 # This is an extensive test and currently takes a long time. Therefore skip on
 # debug/asan and extend the timeout length otherwise.
 skip-if = debug || asan
-requesttimeoutfactor = 2
+requesttimeoutfactor = 3
 [test_bing.js]
 # This is an extensive test and currently takes a long time. Therefore skip on
 # debug/asan and extend the timeout length otherwise.
 skip-if = debug || asan
-requesttimeoutfactor = 2
+requesttimeoutfactor = 3
 [test_duckduckgo.js]
 # This is an extensive test and currently takes a long time. Therefore skip on
 # debug/asan and extend the timeout length otherwise.
 skip-if = debug || asan
-requesttimeoutfactor = 2
+requesttimeoutfactor = 3
 [test_ebay.js]
 # This is an extensive test and currently takes a long time. Therefore skip on
 # debug/asan and extend the timeout length otherwise.
 # Thunderbird doesn't provide eBay search engines.
 skip-if = debug || asan || appname == "thunderbird"
-requesttimeoutfactor = 2
+requesttimeoutfactor = 3
 [test_google.js]
 # This is an extensive test and currently takes a long time. Therefore skip on
 # debug/asan and extend the timeout length otherwise.
 skip-if = debug || asan
-requesttimeoutfactor = 2
+requesttimeoutfactor = 3
 [test_yandex.js]
 # This is an extensive test and currently takes a long time. Therefore skip on
 # debug/asan and extend the timeout length otherwise.
 skip-if = debug || asan
-requesttimeoutfactor = 2
+requesttimeoutfactor = 3