Merge inbound to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Wed, 16 Aug 2017 16:59:26 -0700
changeset 647817 63ca686c3f1e870649b6d9c559973d100573aec2
parent 647785 1d38626ba9686d119e489db538ce84f8f9854217 (current diff)
parent 647792 6eedff8e95498ab746581f3daaab5563dc84f37a (diff)
child 647818 7689e377f265643d0260eec9c0f53cdeb47e843b
child 647824 d36aa554e8cd36f2ff62d5ac84ebe1581feb6e12
child 647825 afa3c59eaf68e7dc42554b7ac70dd96e93214d79
child 647835 fc41407fe2b152dbef20e3297951ab17a95afc2b
child 647858 869cce7ce0f532f07531a05bc5337388c96ed3b9
child 647874 208724c54955b35388b841c65b6a2c901e54da5e
child 647895 d2e36fc0df5d8c2f7f459a021007d8a44d452dc0
child 647902 0410152231fa64162d5e81b412d9ee28203af4dd
child 647904 fc4830ba272e5a52849686b0b3594321e118d296
child 647906 c597c944aca8218fccb2643bb1a7406c0fb509f6
child 647912 6d4ad219b7e9ef41743212474199cde9ad4b8065
child 647915 b69bb5e622159974fb05a264616d16d766452431
child 647922 536029addb5a04aa56b0237eeb5d0391833d2021
child 647924 1ed0cafed12f0105b6f180110a50de8d1f6b6e16
child 647938 e458067049f56e9528b2a5c6c9d91587fbb75602
child 647996 ccffcdc99dcbbbaea9960ef8a7c1ca57c9ac6294
child 648025 5328bed0a6322d50ef29e976061d03f7cb2d5645
child 648046 932388b8c22c9775264e543697ce918415db9e23
child 648072 08d59f94bd43b2b55ca2f59029f8bbfe299a65a3
child 648085 7f594c8967d863eb1faefa04cf30b5cfd891e0fa
child 648093 fe2e076ad30bdd33f0f69ae4472375700263cf33
child 648095 6cfc1d5b9f308a1a34915be5e6f4d51d41c78d2a
child 648144 bd0a38932a72cf448591478ab1b8a25d8858d526
child 648173 1d9c236b48191cce07d6b018a43951c6ffb0edd6
child 648304 9e818b0528ed552565cf65e90566d8428d9833da
child 648322 fac786f5e4263653ef49032c914c574d5293bf99
child 648324 23c8b2421f6725779812d6e0869eede3b1398587
child 648391 33ef511586c503eb8babbd27d31ce02faf1bb39c
child 648590 19be49fbe537be7e47285587bd5acecf9ae0ffed
child 649642 01e011dfcc4178aa0a9e0a8bc2e6673c1d3bbc56
child 649684 c96ee59ccca0539420af7e1139345799a9fb06e7
child 650980 a697549e25a35e274753d96dcdbf99957d805c02
push id74552
push userbmo:mh+mozilla@glandium.org
push dateThu, 17 Aug 2017 00:46:53 +0000
reviewersmerge
milestone57.0a1
Merge inbound to central, a=merge MozReview-Commit-ID: GEfEDRZk5bo
browser/base/content/browser.js
browser/extensions/formautofill/FormAutofillHandler.jsm
browser/extensions/formautofill/FormAutofillHeuristics.jsm
browser/extensions/formautofill/test/unit/head.js
browser/extensions/formautofill/test/unit/test_collectFormFields.js
js/src/jit-test/tests/tracelogger/bug1174542.js
js/src/jit-test/tests/tracelogger/bug1231170.js
js/src/jit-test/tests/tracelogger/bug1257194.js
js/src/jit-test/tests/tracelogger/bug1266649.js
js/src/jit-test/tests/tracelogger/bug1282743.js
js/src/jit-test/tests/tracelogger/bug1298541.js
js/src/jit-test/tests/tracelogger/bug1300515.js
js/src/jit-test/tests/tracelogger/bug1302417.js
js/src/jit-test/tests/tracelogger/drainTraceLogger.js
js/src/jit-test/tests/tracelogger/setupTraceLogger.js
mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
taskcluster/ci/test/test-sets.yml
taskcluster/ci/test/tests.yml
--- a/accessible/base/NotificationController.cpp
+++ b/accessible/base/NotificationController.cpp
@@ -266,16 +266,18 @@ void
 NotificationController::DropMutationEvent(AccTreeMutationEvent* aEvent)
 {
   // unset the event bits since the event isn't being fired any more.
   if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_REORDER) {
     aEvent->GetAccessible()->SetReorderEventTarget(false);
   } else if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_SHOW) {
     aEvent->GetAccessible()->SetShowEventTarget(false);
   } else {
+    aEvent->GetAccessible()->SetHideEventTarget(false);
+
     AccHideEvent* hideEvent = downcast_accEvent(aEvent);
     MOZ_ASSERT(hideEvent);
 
     if (hideEvent->NeedsShutdown()) {
       mDocument->ShutdownChildrenInSubtree(aEvent->GetAccessible());
     }
   }
 
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1078,16 +1078,24 @@ function _loadURIWithFlags(browser, uri,
   } catch (e) {
     // createFixupURI throws if it can't create a URI. If that's the case then
     // we still need to pass down the uri because docshell handles this case.
     requiredRemoteType = gMultiProcessBrowser ? E10SUtils.DEFAULT_REMOTE_TYPE
                                               : E10SUtils.NOT_REMOTE;
   }
 
   let mustChangeProcess = requiredRemoteType != currentRemoteType;
+  let newFrameloader = false;
+  if (browser.getAttribute("isPreloadBrowser") == "true" && uri != "about:newtab") {
+    // Leaving about:newtab from a used to be preloaded browser should run the process
+    // selecting algorithm again.
+    mustChangeProcess = true;
+    newFrameloader = true;
+    browser.removeAttribute("isPreloadBrowser");
+  }
 
   // !requiredRemoteType means we're loading in the parent/this process.
   if (!requiredRemoteType) {
     browser.inLoadURI = true;
   }
   try {
     if (!mustChangeProcess) {
       if (params.userContextId) {
@@ -1112,17 +1120,18 @@ function _loadURIWithFlags(browser, uri,
         uri,
         triggeringPrincipal: triggeringPrincipal
           ? gSerializationHelper.serializeToString(triggeringPrincipal)
           : null,
         flags,
         referrer: referrer ? referrer.spec : null,
         referrerPolicy,
         remoteType: requiredRemoteType,
-        postData
+        postData,
+        newFrameloader,
       }
 
       if (params.userContextId) {
         loadParams.userContextId = params.userContextId;
       }
 
       LoadInOtherProcess(browser, loadParams);
     }
@@ -1157,16 +1166,21 @@ function _loadURIWithFlags(browser, uri,
 function LoadInOtherProcess(browser, loadOptions, historyIndex = -1) {
   let tab = gBrowser.getTabForBrowser(browser);
   SessionStore.navigateAndRestore(tab, loadOptions, historyIndex);
 }
 
 // Called when a docshell has attempted to load a page in an incorrect process.
 // This function is responsible for loading the page in the correct process.
 function RedirectLoad({ target: browser, data }) {
+  if (browser.getAttribute("isPreloadBrowser") == "true") {
+    browser.removeAttribute("isPreloadBrowser");
+    data.loadOptions.newFrameloader = true;
+  }
+
   if (data.loadOptions.reloadInFreshProcess) {
     // Convert the fresh process load option into a large allocation remote type
     // to use common processing from this point.
     data.loadOptions.remoteType = E10SUtils.LARGE_ALLOCATION_REMOTE_TYPE;
     data.loadOptions.newFrameloader = true;
   } else if (browser.remoteType == E10SUtils.LARGE_ALLOCATION_REMOTE_TYPE) {
     // If we're in a Large-Allocation process, we prefer switching back into a
     // normal content process, as that way we can clean up the L-A process.
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -2113,16 +2113,20 @@
               }
               b.QueryInterface(Ci.nsIFrameLoaderOwner).presetOpenerWindow(aParams.opener);
             }
 
             if (!aParams.isPreloadBrowser && this.hasAttribute("autocompletepopup")) {
               b.setAttribute("autocompletepopup", this.getAttribute("autocompletepopup"));
             }
 
+            if (aParams.isPreloadBrowser) {
+              b.setAttribute("isPreloadBrowser", "true");
+            }
+
             if (this.hasAttribute("selectmenulist"))
               b.setAttribute("selectmenulist", this.getAttribute("selectmenulist"));
 
             if (this.hasAttribute("datetimepicker")) {
               b.setAttribute("datetimepicker", this.getAttribute("datetimepicker"));
             }
 
             b.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
--- a/browser/base/content/test/performance/browser_startup_images.js
+++ b/browser/base/content/test/performance/browser_startup_images.js
@@ -59,20 +59,16 @@ const whitelist = [
   {
     file: "chrome://browser/skin/places/toolbarDropMarker.png",
     platforms: ["linux", "win", "macosx"],
   },
   {
     file: "chrome://browser/skin/tracking-protection-16.svg#enabled",
     platforms: ["linux", "win", "macosx"],
   },
-  {
-    file: "chrome://global/skin/icons/autoscroll.png",
-    platforms: ["linux", "win", "macosx"],
-  },
 
   {
     file: "chrome://browser/skin/tabbrowser/tabDragIndicator.png",
     hidpi: "chrome://browser/skin/tabbrowser/tabDragIndicator@2x.png",
     platforms: ["linux", "win", "macosx"],
   },
 
   {
--- a/browser/base/content/test/siteIdentity/browser_identity_UI.js
+++ b/browser/base/content/test/siteIdentity/browser_identity_UI.js
@@ -15,21 +15,23 @@ function test() {
 // Greek IDN for 'example.test'.
 var idnDomain = "\u03C0\u03B1\u03C1\u03AC\u03B4\u03B5\u03B9\u03B3\u03BC\u03B1.\u03B4\u03BF\u03BA\u03B9\u03BC\u03AE";
 var tests = [
   {
     name: "normal domain",
     location: "http://test1.example.org/",
     effectiveHost: "test1.example.org"
   },
+  /* This part is perma-crashing, see: Bug 1315092
   {
     name: "view-source",
     location: "view-source:http://example.com/",
     effectiveHost: null
   },
+  */
   {
     name: "normal HTTPS",
     location: "https://example.com/",
     effectiveHost: "example.com",
     isHTTPS: true
   },
   {
     name: "IDN subdomain",
--- a/browser/base/content/test/urlbar/browser_urlbarKeepStateAcrossTabSwitches.js
+++ b/browser/base/content/test/urlbar/browser_urlbarKeepStateAcrossTabSwitches.js
@@ -1,17 +1,17 @@
 "use strict";
 
 /**
  * Verify user typed text remains in the URL bar when tab switching, even when
  * loads fail.
  */
 add_task(async function() {
   let input = "i-definitely-dont-exist.example.com";
-  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:newtab", false);
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank", false);
   // NB: CPOW usage because new tab pages can be preloaded, in which case no
   // load events fire.
   await BrowserTestUtils.waitForCondition(() => !tab.linkedBrowser.contentDocument.hidden)
   let errorPageLoaded = BrowserTestUtils.waitForErrorPage(tab.linkedBrowser);
   gURLBar.value = input;
   gURLBar.select();
   EventUtils.sendKey("return");
   await errorPageLoaded;
@@ -24,17 +24,17 @@ add_task(async function() {
 
 /**
  * Invalid URIs fail differently (that is, immediately, in the loadURI call)
  * if keyword searches are turned off. Test that this works, too.
  */
 add_task(async function() {
   let input = "To be or not to be-that is the question";
   await SpecialPowers.pushPrefEnv({set: [["keyword.enabled", false]]});
-  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:newtab", false);
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank", false);
   // NB: CPOW usage because new tab pages can be preloaded, in which case no
   // load events fire.
   await BrowserTestUtils.waitForCondition(() => !tab.linkedBrowser.contentDocument.hidden)
   let errorPageLoaded = BrowserTestUtils.waitForErrorPage(tab.linkedBrowser);
   gURLBar.value = input;
   gURLBar.select();
   EventUtils.sendKey("return");
   await errorPageLoaded;
--- a/browser/components/customizableui/PanelMultiView.jsm
+++ b/browser/components/customizableui/PanelMultiView.jsm
@@ -424,308 +424,236 @@ this.PanelMultiView = class {
       }
     }
 
     if (!this.panelViews) {
       this._shiftMainView();
     }
   }
 
-  /**
-   * Get a node object given either that node object or a view id.
-   * Ensure the resulting panelview is present in the panelmultiview.
-   *
-   * @param {String|DOMNode} viewId either an ID for the view or the view DOM node itself
-   * @return {DOMNode} the view DOM node we're inserting, or null if we couldn't find one.
-   */
-  _getAndPlaceViewNode(viewId) {
-    // Support passing in the node directly.
-    let viewNode = typeof viewId == "string" ? this.node.querySelector("#" + viewId) : viewId;
-    if (!viewNode) {
-      viewNode = this.document.getElementById(viewId);
-      if (viewNode) {
+  showSubView(aViewId, aAnchor, aPreviousView) {
+    const {document, window} = this;
+    return (async () => {
+      // Support passing in the node directly.
+      let viewNode = typeof aViewId == "string" ? this.node.querySelector("#" + aViewId) : aViewId;
+      if (!viewNode) {
+        viewNode = document.getElementById(aViewId);
+        if (viewNode) {
+          this._placeSubView(viewNode);
+        } else {
+          throw new Error(`Subview ${aViewId} doesn't exist!`);
+        }
+      } else if (viewNode.parentNode == this._panelViewCache) {
         this._placeSubView(viewNode);
-      } else {
-        throw new Error(`Subview ${viewId} doesn't exist!`);
-      }
-    } else if (viewNode.parentNode == this._panelViewCache) {
-      this._placeSubView(viewNode);
-    }
-    return viewNode;
-  }
-
-  /**
-   * Get and cache the size of the previous view node. If there is no main view width set,
-   * set the width to the width of the previous view. Constrain the new view node
-   * to the width of the main view.
-   * @param {panelview} viewNode         the view whose width should be constrained
-   * @param {panelview} previousViewNode the previous view (must not be null)
-   *
-   * @return {DOMRect} the bounds of the previous view.
-   */
-  _cachePreviousRectAndConstrainBounds(viewNode, previousViewNode) {
-    let dwu = this._dwu;
-    let previousRect = previousViewNode.__lastKnownBoundingRect =
-      dwu.getBoundsWithoutFlushing(previousViewNode);
-    if (this.panelViews) {
-      // Here go the measures that have the same caching lifetime as the width
-      // of the main view, i.e. 'forever', during the instance lifetime.
-      if (!this._mainViewWidth) {
-        this._mainViewWidth = previousRect.width;
-        let top = dwu.getBoundsWithoutFlushing(previousViewNode.firstChild || previousViewNode).top;
-        let bottom = dwu.getBoundsWithoutFlushing(previousViewNode.lastChild || previousViewNode).bottom;
-        this._viewVerticalPadding = previousRect.height - (bottom - top);
-      }
-      // Here go the measures that have the same caching lifetime as the height
-      // of the main view, i.e. whilst the panel is shown and/ or visible.
-      if (!this._mainViewHeight) {
-        this._mainViewHeight = previousRect.height;
-        this._viewContainer.style.minHeight = this._mainViewHeight + "px";
       }
-      if (this._mainViewWidth)
-        viewNode.style.maxWidth = viewNode.style.minWidth = this._mainViewWidth + "px";
-    }
-    return previousRect;
-  }
-
-  /**
-   * Dispatch a ViewShowing event to the relevant view node.
-   * @param {panelview} viewNode the viewNode to dispatch the event on.
-   * @param {element}   anchor   the anchor against which the view node is opening, if any.
-   */
-  async _dispatchViewShowing(viewNode, anchor) {
-    this._viewShowing = viewNode;
-
-    // Make sure that new panels always have a title set.
-    if (this.panelViews && anchor) {
-      if (!viewNode.hasAttribute("title"))
-        viewNode.setAttribute("title", anchor.getAttribute("label"));
-      viewNode.classList.add("PanelUI-subView");
-    }
-    // Emit the ViewShowing event so that the widget definition has a chance
-    // to lazily populate the subview with things.
-    let detail = {
-      blockers: new Set(),
-      addBlocker(promise) {
-        this.blockers.add(promise);
-      }
-    };
-    let cancel = this._dispatchViewEvent(viewNode, "ViewShowing", anchor, detail);
-    if (detail.blockers.size) {
-      try {
-        let results = await Promise.all(detail.blockers);
-        cancel = cancel || results.some(val => val === false);
-      } catch (e) {
-        Cu.reportError(e);
-        cancel = true;
-      }
-    }
-
-    this._viewShowing = null;
-    return !cancel;
-  }
-
-  /**
-   * Remove temporary style properties from the view element,
-   * either because the transition has finished or because the
-   * panel is being hidden.
-   * @param {panelview} view The view to remove styling from
-   */
-  _clearTransitionedStyles(view) {
-    view.style.removeProperty("border-inline-start");
-    view.style.removeProperty("transition");
-    view.style.removeProperty("transform");
-    view.style.removeProperty("width");
-    view.style.removeProperty("margin-inline-start");
-  }
 
-  /**
-   * Transition the panel to a different subview. THere's a few different parts to this:
-   *
-   * 1) The main view content gets shifted so that the center of the anchor
-   *    node is at the left-most edge of the panel.
-   * 2) The subview deck slides in so that it takes up almost all of the
-   *    panel.
-   * 3) If the subview is taller then the main panel contents, then the panel
-   *    must grow to meet that new height. Otherwise, it must shrink.
-   *
-   * All three of these actions make use of CSS transformations, so they
-   * should all occur simultaneously.
-   *
-   * @param {panelview} viewNode         the view to transition to
-   * @param {panelview} previousViewNode the view we're transitioning away from.
-   * @param {DOMNode}   anchor           the element to anchor the new view on, if any
-   * @param {DOMRect}   previousRect     the previous view bounds (which inform the new bounds)
-   * @param {Boolean}   reverse          whether we're reverse-transitioning ("back" from a view)
-   *
-   * @return once we fire ViewShown on the new view and are done transitioning, or when
-   * the transition ends prematurely, whichever comes first.
-   */
-  async _transitionToSubView(viewNode, previousViewNode, anchor, previousRect, reverse) {
-    const {window} = this;
-    // Sliding the next subview in means that the previous panelview stays
-    // where it is and the active panelview slides in from the left in LTR
-    // mode, right in RTL mode.
-    let onTransitionEnd = () => {
-      this._dispatchViewEvent(previousViewNode, "ViewHiding");
-      previousViewNode.removeAttribute("current");
-      this.descriptionHeightWorkaround(viewNode);
-    };
-
-    // There's absolutely no need to show off our epic animation skillz when
-    // the panel's not even open.
-    if (this._panel.state != "open") {
-      onTransitionEnd();
-      return;
-    }
-
-    if (anchor)
-      anchor.setAttribute("open", true);
-
-    // Set the viewContainer dimensions to make sure only the current view
-    // is visible.
-    this._viewContainer.style.height = Math.max(previousRect.height, this._mainViewHeight) + "px";
-    this._viewContainer.style.width = previousRect.width + "px";
-    // Lock the dimensions of the window that hosts the popup panel.
-    let rect = this._panel.popupBoxObject.getOuterScreenRect();
-    this._panel.setAttribute("width", rect.width);
-    this._panel.setAttribute("height", rect.height);
-
-    const viewRect = await this._viewBoundsOffscreen(viewNode, previousRect);
-
-    this._transitioning = true;
-    if (this._autoResizeWorkaroundTimer)
-      window.clearTimeout(this._autoResizeWorkaroundTimer);
-    this._viewContainer.setAttribute("transition-reverse", reverse);
-    let nodeToAnimate = reverse ? previousViewNode : viewNode;
-
-    if (!reverse) {
-      // We set the margin here to make sure the view is positioned next
-      // to the view that is currently visible. The animation is taken
-      // care of by transitioning the `transform: translateX()` property
-      // instead.
-      // Once the transition finished, we clean both properties up.
-      nodeToAnimate.style.marginInlineStart = previousRect.width + "px";
-    }
-
-    // Set the transition style and listen for its end to clean up and
-    // make sure the box sizing becomes dynamic again.
-    // Somehow, putting these properties in PanelUI.css doesn't work for
-    // newly shown nodes in a XUL parent node.
-    nodeToAnimate.style.transition = "transform ease-" + (reverse ? "in" : "out") +
-      " var(--panelui-subview-transition-duration)";
-    nodeToAnimate.style.willChange = "transform";
-    nodeToAnimate.style.borderInlineStart = "1px solid var(--panel-separator-color)";
-
-    // Wait until after the first paint to ensure setting 'current=true'
-    // has taken full effect; once both views are visible, we want to
-    // correctly measure rects using `dwu.getBoundsWithoutFlushing`.
-    if (!(await this._promiseEventOnceOrHidden(window, "MozAfterPaint")) ||
-        this._panel.state != "open") {
-      // Popup got hidden, bail.
-      onTransitionEnd();
-      return;
-    }
-    // Now set the viewContainer dimensions to that of the new view, which
-    // kicks of the height animation.
-    this._viewContainer.style.height = Math.max(viewRect.height, this._mainViewHeight) + "px";
-    this._viewContainer.style.width = viewRect.width + "px";
-    this._panel.removeAttribute("width");
-    this._panel.removeAttribute("height");
-
-    // The 'magic' part: build up the amount of pixels to move right or left.
-    let moveToLeft = (this._dir == "rtl" && !reverse) || (this._dir == "ltr" && reverse);
-    let movementX = reverse ? viewRect.width : previousRect.width;
-    let moveX = (moveToLeft ? "" : "-") + movementX;
-    nodeToAnimate.style.transform = "translateX(" + moveX + "px)";
-    // We're setting the width property to prevent flickering during the
-    // sliding animation with smaller views.
-    nodeToAnimate.style.width = viewRect.width + "px";
-
-    let transitionChecker = ev => {
-      // It's quite common that `height` on the view container doesn't need
-      // to transition, so we make sure to do all the work on the transform
-      // transition-end, because that is guaranteed to happen.
-      return ev.target != this._viewStack || ev.propertyName != "transform";
-    }
-    let didTransition = await this._promiseEventOnceOrHidden(this._viewContainer, "transitionend",
-                                                             transitionChecker);
-    onTransitionEnd();
-    this._transitioning = false;
-    this._resetKeyNavigation(previousViewNode);
-    if (!didTransition) {
-      return;
-    }
-
-    // Myeah, panel layout auto-resizing is a funky thing. We'll wait
-    // another few milliseconds to remove the width and height 'fixtures',
-    // to be sure we don't flicker annoyingly.
-    // NB: HACK! Bug 1363756 is there to fix this.
-    this._autoResizeWorkaroundTimer = window.setTimeout(() => {
-      this._viewContainer.style.removeProperty("height");
-      this._viewContainer.style.removeProperty("width");
-    }, 500);
-
-    // Take another breather, just like before, to wait for the 'current'
-    // attribute removal to take effect. This prevents a flicker.
-    // The cleanup we do doesn't affect the display anymore, so we're not
-    // too fussed about the timing here.
-    let popupHidden = !(await this._promiseEventOnceOrHidden(window, "MozAfterPaint"));
-    this._clearTransitionedStyles(nodeToAnimate);
-    if (anchor)
-      anchor.removeAttribute("open");
-
-    this._viewContainer.removeAttribute("transition-reverse");
-    if (!popupHidden) {
-      this._dispatchViewEvent(viewNode, "ViewShown");
-    }
-  }
-
-  /**
-   * Show a view node as the new main view of the panelmultiview.
-   * @param {String|DOMNode} aViewId       the ID of the new view, or the new view's DOM node
-   * @param {DOMNode}        aAnchor       the node in the previous view to anchor against, if any
-   * @param {DOMNode}        aPreviousView the previous view, if any.
-   *
-   * @return {Promise} a Promise that resolves some point after the ViewShowing event has been dispatched.
-   */
-  showSubView(aViewId, aAnchor, aPreviousView) {
-    return (async () => {
-      let viewNode = this._getAndPlaceViewNode(aViewId);
-
+      let reverse = !!aPreviousView;
       let previousViewNode = aPreviousView || this._currentSubView;
       let playTransition = (!!previousViewNode && previousViewNode != viewNode);
 
-      let previousRect;
+      let dwu, previousRect;
       if (playTransition || this.panelViews) {
-        previousRect = this._cachePreviousRectAndConstrainBounds(viewNode, previousViewNode);
+        dwu = this._dwu;
+        previousRect = previousViewNode.__lastKnownBoundingRect =
+          dwu.getBoundsWithoutFlushing(previousViewNode);
+        if (this.panelViews) {
+          // Here go the measures that have the same caching lifetime as the width
+          // of the main view, i.e. 'forever', during the instance lifetime.
+          if (!this._mainViewWidth) {
+            this._mainViewWidth = previousRect.width;
+            let top = dwu.getBoundsWithoutFlushing(previousViewNode.firstChild || previousViewNode).top;
+            let bottom = dwu.getBoundsWithoutFlushing(previousViewNode.lastChild || previousViewNode).bottom;
+            this._viewVerticalPadding = previousRect.height - (bottom - top);
+          }
+          // Here go the measures that have the same caching lifetime as the height
+          // of the main view, i.e. whilst the panel is shown and/ or visible.
+          if (!this._mainViewHeight) {
+            this._mainViewHeight = previousRect.height;
+            this._viewContainer.style.minHeight = this._mainViewHeight + "px";
+          }
+        }
       }
 
-      if (!await this._dispatchViewShowing(viewNode, aAnchor)) {
-        return;
+      this._viewShowing = viewNode;
+
+      // Make sure that new panels always have a title set.
+      if (this.panelViews && aAnchor) {
+        if (!viewNode.hasAttribute("title"))
+          viewNode.setAttribute("title", aAnchor.getAttribute("label"));
+        viewNode.classList.add("PanelUI-subView");
+      }
+      if (this.panelViews && this._mainViewWidth)
+        viewNode.style.maxWidth = viewNode.style.minWidth = this._mainViewWidth + "px";
+
+      // Emit the ViewShowing event so that the widget definition has a chance
+      // to lazily populate the subview with things.
+      let detail = {
+        blockers: new Set(),
+        addBlocker(promise) {
+          this.blockers.add(promise);
+        }
+      };
+      let cancel = this._dispatchViewEvent(viewNode, "ViewShowing", aAnchor, detail);
+      if (detail.blockers.size) {
+        try {
+          let results = await Promise.all(detail.blockers);
+          cancel = cancel || results.some(val => val === false);
+        } catch (e) {
+          Cu.reportError(e);
+          cancel = true;
+        }
       }
 
-      // If this instance was destructed in the meantime, there's no point in
-      // trying to show anything here.
-      if (!this.node) {
+      this._viewShowing = null;
+      if (cancel) {
         return;
       }
 
       this._currentSubView = viewNode;
       viewNode.setAttribute("current", true);
-
       if (this.panelViews) {
         this.node.setAttribute("viewtype", "subview");
-        if (!playTransition) {
+        if (!playTransition)
+          this.descriptionHeightWorkaround(viewNode);
+      }
+
+      // Now we have to transition the panel. There are a few parts to this:
+      //
+      // 1) The main view content gets shifted so that the center of the anchor
+      //    node is at the left-most edge of the panel.
+      // 2) The subview deck slides in so that it takes up almost all of the
+      //    panel.
+      // 3) If the subview is taller then the main panel contents, then the panel
+      //    must grow to meet that new height. Otherwise, it must shrink.
+      //
+      // All three of these actions make use of CSS transformations, so they
+      // should all occur simultaneously.
+      if (this.panelViews && playTransition) {
+        // Sliding the next subview in means that the previous panelview stays
+        // where it is and the active panelview slides in from the left in LTR
+        // mode, right in RTL mode.
+        let onTransitionEnd = () => {
+          this._dispatchViewEvent(previousViewNode, "ViewHiding");
+          previousViewNode.removeAttribute("current");
           this.descriptionHeightWorkaround(viewNode);
-        } else {
-          let reverse = !!aPreviousView;
-          this._transitionToSubView(viewNode, previousViewNode, aAnchor, previousRect, reverse);
+        };
+
+        // There's absolutely no need to show off our epic animation skillz when
+        // the panel's not even open.
+        if (this._panel.state != "open") {
+          onTransitionEnd();
+          return;
         }
-      } else {
+
+        if (aAnchor)
+          aAnchor.setAttribute("open", true);
+
+        // Set the viewContainer dimensions to make sure only the current view
+        // is visible.
+        this._viewContainer.style.height = Math.max(previousRect.height, this._mainViewHeight) + "px";
+        this._viewContainer.style.width = previousRect.width + "px";
+        // Lock the dimensions of the window that hosts the popup panel.
+        let rect = this._panel.popupBoxObject.getOuterScreenRect();
+        this._panel.setAttribute("width", rect.width);
+        this._panel.setAttribute("height", rect.height);
+
+        this._viewBoundsOffscreen(viewNode, previousRect, viewRect => {
+          this._transitioning = true;
+          if (this._autoResizeWorkaroundTimer)
+            window.clearTimeout(this._autoResizeWorkaroundTimer);
+          this._viewContainer.setAttribute("transition-reverse", reverse);
+          let nodeToAnimate = reverse ? previousViewNode : viewNode;
+
+          if (!reverse) {
+            // We set the margin here to make sure the view is positioned next
+            // to the view that is currently visible. The animation is taken
+            // care of by transitioning the `transform: translateX()` property
+            // instead.
+            // Once the transition finished, we clean both properties up.
+            nodeToAnimate.style.marginInlineStart = previousRect.width + "px";
+          }
+
+          // Set the transition style and listen for its end to clean up and
+          // make sure the box sizing becomes dynamic again.
+          // Somehow, putting these properties in PanelUI.css doesn't work for
+          // newly shown nodes in a XUL parent node.
+          nodeToAnimate.style.transition = "transform ease-" + (reverse ? "in" : "out") +
+            " var(--panelui-subview-transition-duration)";
+          nodeToAnimate.style.willChange = "transform";
+          nodeToAnimate.style.borderInlineStart = "1px solid var(--panel-separator-color)";
+
+          // Wait until after the first paint to ensure setting 'current=true'
+          // has taken full effect; once both views are visible, we want to
+          // correctly measure rects using `dwu.getBoundsWithoutFlushing`.
+          window.addEventListener("MozAfterPaint", () => {
+            if (this._panel.state != "open") {
+              onTransitionEnd();
+              return;
+            }
+            // Now set the viewContainer dimensions to that of the new view, which
+            // kicks of the height animation.
+            this._viewContainer.style.height = Math.max(viewRect.height, this._mainViewHeight) + "px";
+            this._viewContainer.style.width = viewRect.width + "px";
+            this._panel.removeAttribute("width");
+            this._panel.removeAttribute("height");
+
+            // The 'magic' part: build up the amount of pixels to move right or left.
+            let moveToLeft = (this._dir == "rtl" && !reverse) || (this._dir == "ltr" && reverse);
+            let movementX = reverse ? viewRect.width : previousRect.width;
+            let moveX = (moveToLeft ? "" : "-") + movementX;
+            nodeToAnimate.style.transform = "translateX(" + moveX + "px)";
+            // We're setting the width property to prevent flickering during the
+            // sliding animation with smaller views.
+            nodeToAnimate.style.width = viewRect.width + "px";
+
+            this._viewContainer.addEventListener("transitionend", this._transitionEndListener = ev => {
+              // It's quite common that `height` on the view container doesn't need
+              // to transition, so we make sure to do all the work on the transform
+              // transition-end, because that is guaranteed to happen.
+              if (ev.target != nodeToAnimate || ev.propertyName != "transform")
+                return;
+
+              this._viewContainer.removeEventListener("transitionend", this._transitionEndListener);
+              this._transitionEndListener = null;
+              onTransitionEnd();
+              this._transitioning = false;
+              this._resetKeyNavigation(previousViewNode);
+
+              // Myeah, panel layout auto-resizing is a funky thing. We'll wait
+              // another few milliseconds to remove the width and height 'fixtures',
+              // to be sure we don't flicker annoyingly.
+              // NB: HACK! Bug 1363756 is there to fix this.
+              this._autoResizeWorkaroundTimer = window.setTimeout(() => {
+                this._viewContainer.style.removeProperty("height");
+                this._viewContainer.style.removeProperty("width");
+              }, 500);
+
+              // Take another breather, just like before, to wait for the 'current'
+              // attribute removal to take effect. This prevents a flicker.
+              // The cleanup we do doesn't affect the display anymore, so we're not
+              // too fussed about the timing here.
+              window.addEventListener("MozAfterPaint", () => {
+                nodeToAnimate.style.removeProperty("border-inline-start");
+                nodeToAnimate.style.removeProperty("transition");
+                nodeToAnimate.style.removeProperty("transform");
+                nodeToAnimate.style.removeProperty("width");
+
+                if (!reverse)
+                  viewNode.style.removeProperty("margin-inline-start");
+                if (aAnchor)
+                  aAnchor.removeAttribute("open");
+
+                this._viewContainer.removeAttribute("transition-reverse");
+
+                this._dispatchViewEvent(viewNode, "ViewShown");
+              }, { once: true });
+            });
+          }, { once: true });
+        });
+      } else if (!this.panelViews) {
         this._transitionHeight(() => {
           viewNode.setAttribute("current", true);
           this.node.setAttribute("viewtype", "subview");
           // Now that the subview is visible, we can check the height of the
           // description elements it contains.
           this.descriptionHeightWorkaround(viewNode);
           this._dispatchViewEvent(viewNode, "ViewShown");
         });
@@ -758,75 +686,59 @@ this.PanelMultiView = class {
       cancelable: eventName == "ViewShowing"
     });
     viewNode.dispatchEvent(evt);
     if (!cancel)
       cancel = evt.defaultPrevented;
     return cancel;
   }
 
-  // Returns a promise that resolves with `false` if the popup got hidden,
-  // and `true` if the event did occur.
-  _promiseEventOnceOrHidden(target, eventType, checkFn) {
-    return new Promise(resolve => {
-      // We also invoke this when the popup hides, in which case `ev` will be null.
-      this._pendingTransitionResolution = ev => {
-        if (ev && checkFn && !checkFn(ev)) {
-          return;
-        }
-        target.removeEventListener(ev, this._pendingTransitionResolution);
-        resolve(!!ev);
-      };
-      target.addEventListener(eventType, this._pendingTransitionResolution);
-    });
-  }
-
   /**
    * Calculate the correct bounds of a panelview node offscreen to minimize the
    * amount of paint flashing and keep the stack vs panel layouts from interfering.
    *
    * @param {panelview} viewNode Node to measure the bounds of.
    * @param {Rect}      previousRect Rect representing the previous view
    *                                 (used to fill in any blanks).
    * @param {Function}  callback Called when we got the measurements in and pass
    *                             them on as its first argument.
    */
-  async _viewBoundsOffscreen(viewNode, previousRect, callback) {
+  _viewBoundsOffscreen(viewNode, previousRect, callback) {
     if (viewNode.__lastKnownBoundingRect) {
-      return viewNode.__lastKnownBoundingRect;
+      callback(viewNode.__lastKnownBoundingRect);
+      return;
     }
 
     if (viewNode.customRectGetter) {
       // Can't use Object.assign directly with a DOM Rect object because its properties
       // aren't enumerable.
       let {height, width} = previousRect;
       let rect = Object.assign({height, width}, viewNode.customRectGetter());
       let {header} = viewNode;
       if (header) {
         rect.height += this._dwu.getBoundsWithoutFlushing(header).height;
       }
-      return rect;
+      callback(rect);
+      return;
     }
 
     let oldSibling = viewNode.nextSibling || null;
     this._offscreenViewStack.appendChild(viewNode);
 
-    return new Promise(resolve => {
-      this.window.addEventListener("MozAfterPaint", () => {
-        let viewRect = this._dwu.getBoundsWithoutFlushing(viewNode);
+    this.window.addEventListener("MozAfterPaint", () => {
+      let viewRect = this._dwu.getBoundsWithoutFlushing(viewNode);
 
-        try {
-          this._viewStack.insertBefore(viewNode, oldSibling);
-        } catch (ex) {
-          this._viewStack.appendChild(viewNode);
-        }
+      try {
+        this._viewStack.insertBefore(viewNode, oldSibling);
+      } catch (ex) {
+        this._viewStack.appendChild(viewNode);
+      }
 
-        resolve(viewRect);
-      }, { once: true });
-    });
+      callback(viewRect);
+    }, { once: true });
   }
 
   /**
    * Applies the height transition for which <panelmultiview> is designed.
    *
    * The height transition involves two elements, the viewContainer and its only
    * immediate child the viewStack. In order for this to work correctly, the
    * viewContainer must have "overflow: hidden;" and the two elements must have
@@ -1030,43 +942,42 @@ this.PanelMultiView = class {
         // description elements it contains.
         this.descriptionHeightWorkaround();
         break;
       case "popuphidden":
         // WebExtensions consumers can hide the popup from viewshowing, or
         // mid-transition, which disrupts our state:
         this._viewShowing = null;
         this._transitioning = false;
-        // If we hide mid-transition, resolve that:
-        if (this._pendingTransitionResolution) {
-          this._pendingTransitionResolution();
-        }
         this.node.removeAttribute("panelopen");
         this.showMainView();
         if (this.panelViews) {
+          if (this._transitionEndListener) {
+            this._viewContainer.removeEventListener("transitionend", this._transitionEndListener);
+            this._transitionEndListener = null;
+          }
           for (let panelView of this._viewStack.children) {
             if (panelView.nodeName != "children") {
               panelView.__lastKnownBoundingRect = null;
               panelView.style.removeProperty("min-width");
               panelView.style.removeProperty("max-width");
-              this._clearTransitionedStyles(panelView);
             }
           }
           this.window.removeEventListener("keydown", this);
           this._panel.removeEventListener("mousemove", this);
           this._resetKeyNavigation();
 
           // Clear the main view size caches. The dimensions could be different
           // when the popup is opened again, e.g. through touch mode sizing.
           this._mainViewHeight = 0;
           this._mainViewWidth = 0;
           this._viewContainer.style.removeProperty("min-height");
-          this._viewContainer.style.removeProperty("height");
-          this._viewContainer.style.removeProperty("width");
           this._viewStack.style.removeProperty("max-height");
+          this._viewContainer.style.removeProperty("min-width");
+          this._viewContainer.style.removeProperty("max-width");
         }
 
         // Always try to layout the panel normally when reopening it. This is
         // also the layout that will be used in customize mode.
         if (this._mainView.hasAttribute("blockinboxworkaround")) {
           this._mainView.style.removeProperty("height");
           this._mainView.removeAttribute("exceeding");
         }
--- a/browser/components/originattributes/test/browser/browser_firstPartyIsolation_about_newtab.js
+++ b/browser/components/originattributes/test/browser/browser_firstPartyIsolation_about_newtab.js
@@ -1,13 +1,19 @@
 add_task(async function setup() {
   Services.prefs.setBoolPref("privacy.firstparty.isolate", true);
+  // activity-stream is only enabled in Nightly, and if activity-stream is not
+  // enabled, about:newtab is loaded without the flag
+  // nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT, so it will be loaded with
+  // System Principal.
+  Services.prefs.setBoolPref("browser.newtabpage.activity-stream.enabled", true);
 
   registerCleanupFunction(function() {
     Services.prefs.clearUserPref("privacy.firstparty.isolate");
+    Services.prefs.clearUserPref("browser.newtabpage.activity-stream.enabled");
   });
 });
 
 /**
  * Test about:newtab will have firstPartytDomain set when we enable the pref.
  *
  * We split about:newtab from browser_firstPartyIsolation_aboutPages.js because
  * about:newtab needs special care.
--- a/browser/extensions/formautofill/FormAutofillHandler.jsm
+++ b/browser/extensions/formautofill/FormAutofillHandler.jsm
@@ -110,23 +110,26 @@ FormAutofillHandler.prototype = {
     // can be recognized as there is no element changed. However, we should
     // improve the function to detect the element changes. e.g. a tel field
     // is changed from type="hidden" to type="tel".
     return this._formFieldCount != this.form.elements.length;
   },
 
   /**
    * Set fieldDetails from the form about fields that can be autofilled.
-
+   *
+   * @param {boolean} allowDuplicates
+   *        true to remain any duplicated field details otherwise to remove the
+   *        duplicated ones.
    * @returns {Array} The valid address and credit card details.
    */
-  collectFormFields() {
+  collectFormFields(allowDuplicates = false) {
     this._cacheValue.allFieldNames = null;
     this._formFieldCount = this.form.elements.length;
-    let fieldDetails = FormAutofillHeuristics.getFormInfo(this.form);
+    let fieldDetails = FormAutofillHeuristics.getFormInfo(this.form, allowDuplicates);
     this.fieldDetails = fieldDetails ? fieldDetails : [];
     log.debug("Collected details on", this.fieldDetails.length, "fields");
 
     this.address.fieldDetails = this.fieldDetails.filter(
       detail => FormAutofillUtils.isAddressField(detail.fieldName)
     );
     this.creditCard.fieldDetails = this.fieldDetails.filter(
       detail => FormAutofillUtils.isCreditCardField(detail.fieldName)
--- a/browser/extensions/formautofill/FormAutofillHeuristics.jsm
+++ b/browser/extensions/formautofill/FormAutofillHeuristics.jsm
@@ -118,16 +118,20 @@ class FieldScanner {
     let fieldInfo = {
       section: info.section,
       addressType: info.addressType,
       contactType: info.contactType,
       fieldName: info.fieldName,
       elementWeakRef: Cu.getWeakReference(element),
     };
 
+    if (info._reason) {
+      fieldInfo._reason = info._reason;
+    }
+
     // Store the association between the field metadata and the element.
     if (this.findSameField(info) != -1) {
       // A field with the same identifier already exists.
       log.debug("Not collecting a field matching another with the same info:", info);
       fieldInfo._duplicated = true;
     }
 
     this.fieldDetails.push(fieldInfo);
@@ -199,17 +203,17 @@ this.FormAutofillHeuristics = {
     let matchingResult;
 
     const GRAMMARS = this.PHONE_FIELD_GRAMMARS;
     for (let i = 0; i < GRAMMARS.length; i++) {
       let detailStart = fieldScanner.parsingIndex;
       let ruleStart = i;
       for (; i < GRAMMARS.length && GRAMMARS[i][0] && fieldScanner.elementExisting(detailStart); i++, detailStart++) {
         let detail = fieldScanner.getFieldDetailByIndex(detailStart);
-        if (!detail || GRAMMARS[i][0] != detail.fieldName) {
+        if (!detail || GRAMMARS[i][0] != detail.fieldName || detail._reason == "autocomplete") {
           break;
         }
         let element = detail.elementWeakRef.get();
         if (!element) {
           break;
         }
         if (GRAMMARS[i][2] && (!element.maxLength || GRAMMARS[i][2] < element.maxLength)) {
           break;
@@ -283,44 +287,65 @@ this.FormAutofillHeuristics = {
       fieldScanner.updateFieldName(fieldScanner.parsingIndex, addressLines[i]);
       fieldScanner.parsingIndex++;
       parsedFields = true;
     }
 
     return parsedFields;
   },
 
-  getFormInfo(form) {
+  /**
+   * This function should provide all field details of a form. The details
+   * contain the autocomplete info (e.g. fieldName, section, etc).
+   *
+   * `allowDuplicates` is used for the xpcshell-test purpose currently because
+   * the heuristics should be verified that some duplicated elements still can
+   * be predicted correctly.
+   *
+   * @param {HTMLFormElement} form
+   *        the elements in this form to be predicted the field info.
+   * @param {boolean} allowDuplicates
+   *        true to remain any duplicated field details otherwise to remove the
+   *        duplicated ones.
+   * @returns {Array<Object>}
+   *        all field details in the form.
+   */
+  getFormInfo(form, allowDuplicates = false) {
     if (form.autocomplete == "off" || form.elements.length <= 0) {
       return [];
     }
 
     let fieldScanner = new FieldScanner(form.elements);
     while (!fieldScanner.parsingFinished) {
       let parsedPhoneFields = this._parsePhoneFields(fieldScanner);
       let parsedAddressFields = this._parseAddressFields(fieldScanner);
 
       // If there is no any field parsed, the parsing cursor can be moved
       // forward to the next one.
       if (!parsedPhoneFields && !parsedAddressFields) {
         fieldScanner.parsingIndex++;
       }
     }
+    if (allowDuplicates) {
+      return fieldScanner.fieldDetails;
+    }
+
     return fieldScanner.trimmedFieldDetail;
   },
 
   getInfo(element) {
     if (!FormAutofillUtils.isFieldEligibleForAutofill(element)) {
       return null;
     }
 
     let info = element.getAutocompleteInfo();
     // An input[autocomplete="on"] will not be early return here since it stll
     // needs to find the field name.
     if (info && info.fieldName && info.fieldName != "on") {
+      info._reason = "autocomplete";
       return info;
     }
 
     if (!this._prefEnabled) {
       return null;
     }
 
     // "email" type of input is accurate for heuristics to determine its Email
--- a/browser/extensions/formautofill/FormAutofillParent.jsm
+++ b/browser/extensions/formautofill/FormAutofillParent.jsm
@@ -171,17 +171,17 @@ FormAutofillParent.prototype = {
 
   /**
    * Handles the message coming from FormAutofillContent.
    *
    * @param   {string} message.name The name of the message.
    * @param   {object} message.data The data of the message.
    * @param   {nsIFrameMessageManager} message.target Caller's message manager.
    */
-  receiveMessage({name, data, target}) {
+  async receiveMessage({name, data, target}) {
     switch (name) {
       case "FormAutofill:InitStorage": {
         this.profileStorage.initialize();
         break;
       }
       case "FormAutofill:GetRecords": {
         this._getRecords(data, target);
         break;
@@ -190,16 +190,17 @@ FormAutofillParent.prototype = {
         if (data.guid) {
           this.profileStorage.addresses.update(data.guid, data.address);
         } else {
           this.profileStorage.addresses.add(data.address);
         }
         break;
       }
       case "FormAutofill:SaveCreditCard": {
+        await this.profileStorage.creditCards.normalizeCCNumberFields(data.creditcard);
         this.profileStorage.creditCards.add(data.creditcard);
         break;
       }
       case "FormAutofill:RemoveAddresses": {
         data.guids.forEach(guid => this.profileStorage.addresses.remove(guid));
         break;
       }
       case "FormAutofill:OnFormSubmit": {
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/MasterPassword.jsm
@@ -0,0 +1,172 @@
+/* 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/. */
+
+/**
+ * Helpers for the Master Password Dialog.
+ * In the future the Master Password implementation may move here.
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+  "MasterPassword",
+];
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "cryptoSDR",
+                                   "@mozilla.org/login-manager/crypto/SDR;1",
+                                   Ci.nsILoginManagerCrypto);
+
+this.MasterPassword = {
+  get _token() {
+    let tokendb = Cc["@mozilla.org/security/pk11tokendb;1"].createInstance(Ci.nsIPK11TokenDB);
+    return tokendb.getInternalKeyToken();
+  },
+
+  /**
+   * @returns {boolean} True if a master password is set and false otherwise.
+   */
+  get isEnabled() {
+    return this._token.hasPassword;
+  },
+
+  /**
+   * Display the master password login prompt no matter it's logged in or not.
+   * If an existing MP prompt is already open, the result from it will be used instead.
+   *
+   * @returns {Promise<boolean>} True if it's logged in or no password is set and false
+   *                             if it's still not logged in (prompt canceled or other error).
+   */
+  async prompt() {
+    if (!this.isEnabled) {
+      return true;
+    }
+
+    // If a prompt is already showing then wait for and focus it.
+    if (Services.logins.uiBusy) {
+      return this.waitForExistingDialog();
+    }
+
+    let token = this._token;
+    try {
+      // 'true' means always prompt for token password. User will be prompted until
+      // clicking 'Cancel' or entering the correct password.
+      token.login(true);
+    } catch (e) {
+      // An exception will be thrown if the user cancels the login prompt dialog.
+      // User is also logged out.
+    }
+
+    // If we triggered a master password prompt, notify observers.
+    if (token.isLoggedIn()) {
+      Services.obs.notifyObservers(null, "passwordmgr-crypto-login");
+    } else {
+      Services.obs.notifyObservers(null, "passwordmgr-crypto-loginCanceled");
+    }
+
+    return token.isLoggedIn();
+  },
+
+  /**
+   * Decrypts cipherText.
+   *
+   * @param   {string} cipherText Encrypted string including the algorithm details.
+   * @param   {boolean} reauth True if we want to force the prompt to show up
+   *                    even if the user is already logged in.
+   * @returns {Promise<string>} resolves to the decrypted string, or rejects otherwise.
+   */
+  async decrypt(cipherText, reauth = false) {
+    let loggedIn = false;
+    if (reauth) {
+      loggedIn = await this.prompt();
+    } else {
+      loggedIn = await this.waitForExistingDialog();
+    }
+
+    if (!loggedIn) {
+      throw Components.Exception("User canceled master password entry", Cr.NS_ERROR_ABORT);
+    }
+
+    return cryptoSDR.decrypt(cipherText);
+  },
+
+  /**
+   * Encrypts a string and returns cipher text containing algorithm information used for decryption.
+   *
+   * @param   {string} plainText Original string without encryption.
+   * @returns {Promise<string>} resolves to the encrypted string (with algorithm), otherwise rejects.
+   */
+  async encrypt(plainText) {
+    if (Services.logins.uiBusy && !await this.waitForExistingDialog()) {
+      throw Components.Exception("User canceled master password entry", Cr.NS_ERROR_ABORT);
+    }
+
+    return cryptoSDR.encrypt(plainText);
+  },
+
+  /**
+   * Resolve when master password dialogs are closed, immediately if none are open.
+   *
+   * An existing MP dialog will be focused and will request attention.
+   *
+   * @returns {Promise<boolean>}
+   *          Resolves with whether the user is logged in to MP.
+   */
+  async waitForExistingDialog() {
+    if (!Services.logins.uiBusy) {
+      log.debug("waitForExistingDialog: Dialog isn't showing. isLoggedIn:",
+                Services.logins.isLoggedIn);
+      return Services.logins.isLoggedIn;
+    }
+
+    return new Promise((resolve) => {
+      log.debug("waitForExistingDialog: Observing the open dialog");
+      let observer = {
+        QueryInterface: XPCOMUtils.generateQI([
+          Ci.nsIObserver,
+          Ci.nsISupportsWeakReference,
+        ]),
+
+        observe(subject, topic, data) {
+          log.debug("waitForExistingDialog: Got notification:", topic);
+          // Only run observer once.
+          Services.obs.removeObserver(this, "passwordmgr-crypto-login");
+          Services.obs.removeObserver(this, "passwordmgr-crypto-loginCanceled");
+          if (topic == "passwordmgr-crypto-loginCanceled") {
+            resolve(false);
+            return;
+          }
+
+          resolve(true);
+        },
+      };
+
+      // Possible leak: it's possible that neither of these notifications
+      // will fire, and if that happens, we'll leak the observer (and
+      // never return). We should guarantee that at least one of these
+      // will fire.
+      // See bug XXX.
+      Services.obs.addObserver(observer, "passwordmgr-crypto-login");
+      Services.obs.addObserver(observer, "passwordmgr-crypto-loginCanceled");
+
+      // Focus and draw attention to the existing master password dialog for the
+      // occassions where it's not attached to the current window.
+      let promptWin = Services.wm.getMostRecentWindow("prompt:promptPassword");
+      promptWin.focus();
+      promptWin.getAttention();
+    });
+  },
+};
+
+XPCOMUtils.defineLazyGetter(this, "log", () => {
+  let ConsoleAPI = Cu.import("resource://gre/modules/Console.jsm", {}).ConsoleAPI;
+  return new ConsoleAPI({
+    maxLogLevelPref: "masterPassword.loglevel",
+    prefix: "Master Password",
+  });
+});
--- a/browser/extensions/formautofill/ProfileStorage.jsm
+++ b/browser/extensions/formautofill/ProfileStorage.jsm
@@ -111,16 +111,18 @@ Cu.import("resource://gre/modules/Servic
 Cu.import("resource://gre/modules/osfile.jsm");
 
 Cu.import("resource://formautofill/FormAutofillUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "JSONFile",
                                   "resource://gre/modules/JSONFile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FormAutofillNameUtils",
                                   "resource://formautofill/FormAutofillNameUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "MasterPassword",
+                                  "resource://formautofill/MasterPassword.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PhoneNumber",
                                   "resource://formautofill/phonenumberutils/PhoneNumber.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator",
                                    "@mozilla.org/uuid-generator;1",
                                    "nsIUUIDGenerator");
 
 XPCOMUtils.defineLazyGetter(this, "REGION_NAMES", function() {
@@ -1466,36 +1468,19 @@ class CreditCards extends AutofillRecord
       creditCard["cc-family-name"] = nameParts.family;
       hasNewComputedFields = true;
     }
 
     return hasNewComputedFields;
   }
 
   _normalizeFields(creditCard) {
-    // Fields that should not be set by content.
-    delete creditCard["cc-number-encrypted"];
-
-    // Validate and encrypt credit card numbers, and calculate the masked numbers
-    if (creditCard["cc-number"]) {
-      let ccNumber = creditCard["cc-number"].replace(/\s/g, "");
-      delete creditCard["cc-number"];
-
-      if (!/^\d+$/.test(ccNumber)) {
-        throw new Error("Credit card number contains invalid characters.");
-      }
-
-      // TODO: Encrypt cc-number here (bug 1337314).
-      // e.g. creditCard["cc-number-encrypted"] = Encrypt(creditCard["cc-number"]);
-
-      if (ccNumber.length > 4) {
-        creditCard["cc-number"] = "*".repeat(ccNumber.length - 4) + ccNumber.substr(-4);
-      } else {
-        creditCard["cc-number"] = ccNumber;
-      }
+    // Check if cc-number is normalized(normalizeCCNumberFields should be called first).
+    if (!creditCard["cc-number-encrypted"] || !creditCard["cc-number"].includes("*")) {
+      throw new Error("Credit card number needs to be normalized first.");
     }
 
     // Normalize name
     if (creditCard["cc-given-name"] || creditCard["cc-additional-name"] || creditCard["cc-family-name"]) {
       if (!creditCard["cc-name"]) {
         creditCard["cc-name"] = FormAutofillNameUtils.joinNameParts({
           given: creditCard["cc-given-name"],
           middle: creditCard["cc-additional-name"],
@@ -1524,16 +1509,48 @@ class CreditCards extends AutofillRecord
       } else if (expYear < 100) {
         // Enforce 4 digits years.
         creditCard["cc-exp-year"] = expYear + 2000;
       } else {
         creditCard["cc-exp-year"] = expYear;
       }
     }
   }
+
+  /**
+   * Normalize credit card number related field for saving. It should always be
+   * called before adding/updating credit card records.
+   *
+   * @param  {Object} creditCard
+   *         The creditCard record with plaintext number only.
+   */
+  async normalizeCCNumberFields(creditCard) {
+    // Fields that should not be set by content.
+    delete creditCard["cc-number-encrypted"];
+
+    // Validate and encrypt credit card numbers, and calculate the masked numbers
+    if (creditCard["cc-number"]) {
+      let ccNumber = creditCard["cc-number"].replace(/\s/g, "");
+      delete creditCard["cc-number"];
+
+      if (!/^\d+$/.test(ccNumber)) {
+        throw new Error("Credit card number contains invalid characters.");
+      }
+
+      // Based on the information on wiki[1], the shortest valid length should be
+      // 12 digits(Maestro).
+      // [1] https://en.wikipedia.org/wiki/Payment_card_number
+      if (ccNumber.length < 12) {
+        throw new Error("Invalid credit card number because length is under 12 digits.");
+      }
+
+      creditCard["cc-number-encrypted"] = await MasterPassword.encrypt(creditCard["cc-number"]);
+      creditCard["cc-number"] = "*".repeat(ccNumber.length - 4) + ccNumber.substr(-4);
+    }
+  }
 }
 
 function ProfileStorage(path) {
   this._path = path;
   this._initializePromise = null;
   this.INTERNAL_FIELDS = INTERNAL_FIELDS;
 }
 
--- a/browser/extensions/formautofill/test/browser/head.js
+++ b/browser/extensions/formautofill/test/browser/head.js
@@ -150,17 +150,20 @@ function getAddresses() {
 }
 
 function saveAddress(address) {
   Services.cpmm.sendAsyncMessage("FormAutofill:SaveAddress", {address});
   return TestUtils.topicObserved("formautofill-storage-changed");
 }
 
 function saveCreditCard(creditcard) {
-  Services.cpmm.sendAsyncMessage("FormAutofill:SaveCreditCard", {creditcard});
+  let creditcardClone = Object.assign({}, creditcard);
+  Services.cpmm.sendAsyncMessage("FormAutofill:SaveCreditCard", {
+    creditcard: creditcardClone,
+  });
   return TestUtils.topicObserved("formautofill-storage-changed");
 }
 function removeAddresses(guids) {
   Services.cpmm.sendAsyncMessage("FormAutofill:RemoveAddresses", {guids});
   return TestUtils.topicObserved("formautofill-storage-changed");
 }
 
 /**
--- a/browser/extensions/formautofill/test/unit/head.js
+++ b/browser/extensions/formautofill/test/unit/head.js
@@ -144,16 +144,17 @@ function runHeuristicsTest(patterns, fix
       Assert.equal(forms.length, testPattern.expectedResult.length, "Expected form count.");
 
       forms.forEach((form, formIndex) => {
         let formInfo = FormAutofillHeuristics.getFormInfo(form);
         do_print("FieldName Prediction Results: " + formInfo.map(i => i.fieldName));
         Assert.equal(formInfo.length, testPattern.expectedResult[formIndex].length, "Expected field count.");
         formInfo.forEach((field, fieldIndex) => {
           let expectedField = testPattern.expectedResult[formIndex][fieldIndex];
+          delete field._reason;
           expectedField.elementWeakRef = field.elementWeakRef;
           Assert.deepEqual(field, expectedField);
         });
       });
     });
   });
 }
 
--- a/browser/extensions/formautofill/test/unit/test_collectFormFields.js
+++ b/browser/extensions/formautofill/test/unit/test_collectFormFields.js
@@ -169,61 +169,62 @@ const TESTCASES = [
                </form>`,
     addressFieldDetails: [],
     creditCardFieldDetails: [],
     validFieldDetails: [],
   },
   {
     description: "Three sets of adjacent phone number fields",
     document: `<form>
-                 <input id="shippingAreaCode" autocomplete="shipping tel" maxlength="3">
-                 <input id="shippingPrefix" autocomplete="shipping tel" maxlength="3">
-                 <input id="shippingSuffix" autocomplete="shipping tel" maxlength="4">
-                 <input id="shippingTelExt" autocomplete="shipping tel-extension">
+                 <input id="shippingAC" name="phone" maxlength="3">
+                 <input id="shippingPrefix" name="phone" maxlength="3">
+                 <input id="shippingSuffix" name="phone" maxlength="4">
+                 <input id="shippingTelExt" name="extension">
 
-                 <input id="billingAreaCode" autocomplete="billing tel" maxlength="3">
-                 <input id="billingPrefix" autocomplete="billing tel" maxlength="3">
-                 <input id="billingSuffix" autocomplete="billing tel" maxlength="4">
+                 <input id="billingAC" name="phone" maxlength="3">
+                 <input id="billingPrefix" name="phone" maxlength="3">
+                 <input id="billingSuffix" name="phone" maxlength="4">
 
-                 <input id="otherCountryCode" autocomplete="tel" maxlength="3">
-                 <input id="otherAreaCode" autocomplete="tel" maxlength="3">
-                 <input id="otherPrefix" autocomplete="tel" maxlength="3">
-                 <input id="otherSuffix" autocomplete="tel" maxlength="4">
+                 <input id="otherCC" name="phone" maxlength="3">
+                 <input id="otherAC" name="phone" maxlength="3">
+                 <input id="otherPrefix" name="phone" maxlength="3">
+                 <input id="otherSuffix" name="phone" maxlength="4">
                </form>`,
+    allowDuplicates: true,
     addressFieldDetails: [
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-area-code"},
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-local-prefix"},
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-local-suffix"},
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-extension"},
-      {"section": "", "addressType": "billing", "contactType": "", "fieldName": "tel-area-code"},
-      {"section": "", "addressType": "billing", "contactType": "", "fieldName": "tel-local-prefix"},
-      {"section": "", "addressType": "billing", "contactType": "", "fieldName": "tel-local-suffix"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-area-code"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-prefix"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-suffix"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-area-code"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-prefix"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-suffix"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-country-code"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-area-code"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-prefix"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-suffix"},
     ],
     creditCardFieldDetails: [],
     validFieldDetails: [
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-area-code"},
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-local-prefix"},
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-local-suffix"},
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-extension"},
-      {"section": "", "addressType": "billing", "contactType": "", "fieldName": "tel-area-code"},
-      {"section": "", "addressType": "billing", "contactType": "", "fieldName": "tel-local-prefix"},
-      {"section": "", "addressType": "billing", "contactType": "", "fieldName": "tel-local-suffix"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-area-code"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-prefix"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-suffix"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-extension"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-area-code"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-prefix"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-suffix"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-country-code"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-area-code"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-prefix"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-suffix"},
     ],
     ids: [
-      "shippingAreaCode", "shippingPrefix", "shippingSuffix", "shippingTelExt",
-      "billingAreaCode", "billingPrefix", "billingSuffix",
-      "otherCountryCode", "otherAreaCode", "otherPrefix", "otherSuffix",
+      "shippingAC", "shippingPrefix", "shippingSuffix", "shippingTelExt",
+      "billingAC", "billingPrefix", "billingSuffix",
+      "otherCC", "otherAC", "otherPrefix", "otherSuffix",
     ],
   },
   {
     description: "Dedup the same field names of the different telephone fields.",
     document: `<form>
                  <input id="i1" autocomplete="shipping given-name">
                  <input id="i2" autocomplete="shipping family-name">
                  <input id="i3" autocomplete="shipping street-address">
@@ -285,16 +286,38 @@ const TESTCASES = [
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-area-code"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-local-prefix"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-local-suffix"},
     ],
     ids: ["i1", "i2", "i3", "i4", "singlePhone",
       "shippingAreaCode", "shippingPrefix", "shippingSuffix"],
   },
+  {
+    description: "Always adopt the info from autocomplete attribute.",
+    document: `<form>
+                 <input id="given-name" autocomplete="shipping given-name">
+                 <input id="family-name" autocomplete="shipping family-name">
+                 <input id="dummyAreaCode" autocomplete="shipping tel" maxlength="3">
+                 <input id="dummyPrefix" autocomplete="shipping tel" maxlength="3">
+                 <input id="dummySuffix" autocomplete="shipping tel" maxlength="4">
+               </form>`,
+    addressFieldDetails: [
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel"},
+    ],
+    creditCardFieldDetails: [],
+    validFieldDetails: [
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel"},
+    ],
+    ids: ["given-name", "family-name", "dummyAreaCode"],
+  },
 ];
 
 for (let tc of TESTCASES) {
   (function() {
     let testcase = tc;
     add_task(async function() {
       do_print("Starting testcase: " + testcase.description);
 
@@ -335,16 +358,16 @@ for (let tc of TESTCASES) {
       }
       [
         testcase.addressFieldDetails,
         testcase.creditCardFieldDetails,
         testcase.validFieldDetails,
       ].forEach(details => setElementWeakRef(details));
 
       let handler = new FormAutofillHandler(formLike);
-      let validFieldDetails = handler.collectFormFields();
+      let validFieldDetails = handler.collectFormFields(testcase.allowDuplicates);
 
       verifyDetails(handler.address.fieldDetails, testcase.addressFieldDetails);
       verifyDetails(handler.creditCard.fieldDetails, testcase.creditCardFieldDetails);
       verifyDetails(validFieldDetails, testcase.validFieldDetails);
     });
   })();
 }
--- a/browser/extensions/formautofill/test/unit/test_creditCardRecords.js
+++ b/browser/extensions/formautofill/test/unit/test_creditCardRecords.js
@@ -31,16 +31,17 @@ const TEST_CREDIT_CARD_3 = {
 const TEST_CREDIT_CARD_WITH_2_DIGITS_YEAR = {
   "cc-number": "1234123412341234",
   "cc-exp-month": 1,
   "cc-exp-year": 12,
 };
 
 const TEST_CREDIT_CARD_WITH_INVALID_FIELD = {
   "cc-name": "John Doe",
+  "cc-number": "1234123412341234",
   invalidField: "INVALID",
 };
 
 const TEST_CREDIT_CARD_WITH_INVALID_EXPIRY_DATE = {
   "cc-name": "John Doe",
   "cc-number": "1111222233334444",
   "cc-exp-month": 13,
   "cc-exp-year": -3,
@@ -51,35 +52,42 @@ const TEST_CREDIT_CARD_WITH_SPACES_BETWE
   "cc-number": "1111 2222 3333 4444",
 };
 
 const TEST_CREDIT_CARD_WITH_INVALID_NUMBERS = {
   "cc-name": "John Doe",
   "cc-number": "abcdefg",
 };
 
+const TEST_CREDIT_CARD_WITH_SHORT_NUMBERS = {
+  "cc-name": "John Doe",
+  "cc-number": "1234567890",
+};
+
 let prepareTestCreditCards = async function(path) {
   let profileStorage = new ProfileStorage(path);
   await profileStorage.initialize();
 
   let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
                                           (subject, data) => data == "add");
-  do_check_true(profileStorage.creditCards.add(TEST_CREDIT_CARD_1));
+  let encryptedCC_1 = Object.assign({}, TEST_CREDIT_CARD_1);
+  await profileStorage.creditCards.normalizeCCNumberFields(encryptedCC_1);
+  do_check_true(profileStorage.creditCards.add(encryptedCC_1));
   await onChanged;
-  do_check_true(profileStorage.creditCards.add(TEST_CREDIT_CARD_2));
+  let encryptedCC_2 = Object.assign({}, TEST_CREDIT_CARD_2);
+  await profileStorage.creditCards.normalizeCCNumberFields(encryptedCC_2);
+  do_check_true(profileStorage.creditCards.add(encryptedCC_2));
   await profileStorage._saveImmediately();
 };
 
 let reCCNumber = /^(\*+)(.{4})$/;
 
 let do_check_credit_card_matches = (creditCardWithMeta, creditCard) => {
   for (let key in creditCard) {
     if (key == "cc-number") {
-      // check "cc-number-encrypted" after encryption lands (bug 1337314).
-
       let matches = reCCNumber.exec(creditCardWithMeta["cc-number"]);
       do_check_neq(matches, null);
       do_check_eq(creditCardWithMeta["cc-number"].length, creditCard["cc-number"].length);
       do_check_eq(creditCard["cc-number"].endsWith(matches[2]), true);
     } else {
       do_check_eq(creditCardWithMeta[key], creditCard[key]);
     }
   }
@@ -158,17 +166,17 @@ add_task(async function test_getByFilter
   let profileStorage = new ProfileStorage(path);
   await profileStorage.initialize();
 
   let filter = {info: {fieldName: "cc-name"}, searchString: "Tim"};
   let creditCards = profileStorage.creditCards.getByFilter(filter);
   do_check_eq(creditCards.length, 1);
   do_check_credit_card_matches(creditCards[0], TEST_CREDIT_CARD_2);
 
-  // TODO: Uncomment this after decryption lands (bug 1337314).
+  // TODO: Uncomment this after decryption lands (bug 1389413).
   // filter = {info: {fieldName: "cc-number"}, searchString: "11"};
   // creditCards = profileStorage.creditCards.getByFilter(filter);
   // do_check_eq(creditCards.length, 1);
   // do_check_credit_card_matches(creditCards[0], TEST_CREDIT_CARD_2);
 });
 
 add_task(async function test_add() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
@@ -186,17 +194,19 @@ add_task(async function test_add() {
 
   do_check_neq(creditCards[0].guid, undefined);
   do_check_eq(creditCards[0].version, 1);
   do_check_neq(creditCards[0].timeCreated, undefined);
   do_check_eq(creditCards[0].timeLastModified, creditCards[0].timeCreated);
   do_check_eq(creditCards[0].timeLastUsed, 0);
   do_check_eq(creditCards[0].timesUsed, 0);
 
-  Assert.throws(() => profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_INVALID_FIELD),
+  let encryptedCC_invalid = Object.assign({}, TEST_CREDIT_CARD_WITH_INVALID_FIELD);
+  await profileStorage.creditCards.normalizeCCNumberFields(encryptedCC_invalid);
+  Assert.throws(() => profileStorage.creditCards.add(encryptedCC_invalid),
     /"invalidField" is not a valid field\./);
 });
 
 add_task(async function test_update() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
   await prepareTestCreditCards(path);
 
   let profileStorage = new ProfileStorage(path);
@@ -205,17 +215,17 @@ add_task(async function test_update() {
   let creditCards = profileStorage.creditCards.getAll();
   let guid = creditCards[1].guid;
   let timeLastModified = creditCards[1].timeLastModified;
 
   let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
                                           (subject, data) => data == "update");
 
   do_check_neq(creditCards[1]["cc-name"], undefined);
-
+  await profileStorage.creditCards.normalizeCCNumberFields(TEST_CREDIT_CARD_3);
   profileStorage.creditCards.update(guid, TEST_CREDIT_CARD_3);
   await onChanged;
   await profileStorage._saveImmediately();
 
   profileStorage = new ProfileStorage(path);
   await profileStorage.initialize();
 
   let creditCard = profileStorage.creditCards.get(guid);
@@ -224,47 +234,61 @@ add_task(async function test_update() {
   do_check_neq(creditCard.timeLastModified, timeLastModified);
   do_check_credit_card_matches(creditCard, TEST_CREDIT_CARD_3);
 
   Assert.throws(
     () => profileStorage.creditCards.update("INVALID_GUID", TEST_CREDIT_CARD_3),
     /No matching record\./
   );
 
+  let encryptedCC_invalid = Object.assign({}, TEST_CREDIT_CARD_WITH_INVALID_FIELD);
+  await profileStorage.creditCards.normalizeCCNumberFields(encryptedCC_invalid);
   Assert.throws(
-    () => profileStorage.creditCards.update(guid, TEST_CREDIT_CARD_WITH_INVALID_FIELD),
+    () => profileStorage.creditCards.update(guid, encryptedCC_invalid),
     /"invalidField" is not a valid field\./
   );
 });
 
 add_task(async function test_validate() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
 
   let profileStorage = new ProfileStorage(path);
   await profileStorage.initialize();
 
+  await profileStorage.creditCards.normalizeCCNumberFields(TEST_CREDIT_CARD_WITH_INVALID_EXPIRY_DATE);
   profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_INVALID_EXPIRY_DATE);
+  await profileStorage.creditCards.normalizeCCNumberFields(TEST_CREDIT_CARD_WITH_2_DIGITS_YEAR);
   profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_2_DIGITS_YEAR);
+  await profileStorage.creditCards.normalizeCCNumberFields(TEST_CREDIT_CARD_WITH_SPACES_BETWEEN_DIGITS);
   profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_SPACES_BETWEEN_DIGITS);
 
   let creditCards = profileStorage.creditCards.getAll();
 
   do_check_eq(creditCards[0]["cc-exp-month"], undefined);
   do_check_eq(creditCards[0]["cc-exp-year"], undefined);
 
   do_check_eq(creditCards[1]["cc-exp-month"], TEST_CREDIT_CARD_WITH_2_DIGITS_YEAR["cc-exp-month"]);
   do_check_eq(creditCards[1]["cc-exp-year"],
     parseInt(TEST_CREDIT_CARD_WITH_2_DIGITS_YEAR["cc-exp-year"], 10) + 2000);
 
   do_check_eq(creditCards[2]["cc-number"].length, 16);
-  // TODO: Check the decrypted numbers should not contain spaces after
-  //       decryption lands (bug 1337314).
 
-  Assert.throws(() => profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_INVALID_NUMBERS),
-    /Credit card number contains invalid characters\./);
+  try {
+    await profileStorage.creditCards.normalizeCCNumberFields(TEST_CREDIT_CARD_WITH_INVALID_NUMBERS);
+    throw new Error("Not receiving invalid characters error");
+  } catch (e) {
+    Assert.equal(e.message, "Credit card number contains invalid characters.");
+  }
+
+  try {
+    await profileStorage.creditCards.normalizeCCNumberFields(TEST_CREDIT_CARD_WITH_SHORT_NUMBERS);
+    throw new Error("Not receiving invalid characters error");
+  } catch (e) {
+    Assert.equal(e.message, "Invalid credit card number because length is under 12 digits.");
+  }
 });
 
 add_task(async function test_notifyUsed() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
   await prepareTestCreditCards(path);
 
   let profileStorage = new ProfileStorage(path);
   await profileStorage.initialize();
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/test/unit/test_masterPassword.js
@@ -0,0 +1,115 @@
+/**
+ * Tests of MasterPassword.jsm
+ */
+
+"use strict";
+const {MockRegistrar} =
+  Cu.import("resource://testing-common/MockRegistrar.jsm", {});
+let {MasterPassword} = Cu.import("resource://formautofill/MasterPassword.jsm", {});
+
+const TESTCASES = [{
+  description: "With master password set",
+  masterPassword: "fakemp",
+  mpEnabled: true,
+},
+{
+  description: "Without master password set",
+  masterPassword: "", // "" means no master password
+  mpEnabled: false,
+}];
+
+
+// Tests that PSM can successfully ask for a password from the user and relay it
+// back to NSS. Does so by mocking out the actual dialog and "filling in" the
+// password. Also tests that providing an incorrect password will fail (well,
+// technically the user will just get prompted again, but if they then cancel
+// the dialog the overall operation will fail).
+
+let gMockPrompter = {
+  passwordToTry: null,
+  numPrompts: 0,
+
+  // This intentionally does not use arrow function syntax to avoid an issue
+  // where in the context of the arrow function, |this != gMockPrompter| due to
+  // how objects get wrapped when going across xpcom boundaries.
+  promptPassword(dialogTitle, text, password, checkMsg, checkValue) {
+    this.numPrompts++;
+    if (this.numPrompts > 1) { // don't keep retrying a bad password
+      return false;
+    }
+    equal(text,
+          "Please enter the master password for the Software Security Device.",
+          "password prompt text should be as expected");
+    equal(checkMsg, null, "checkMsg should be null");
+    ok(this.passwordToTry, "passwordToTry should be non-null");
+    password.value = this.passwordToTry;
+    return true;
+  },
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPrompt]),
+};
+
+// Mock nsIWindowWatcher. PSM calls getNewPrompter on this to get an nsIPrompt
+// to call promptPassword. We return the mock one, above.
+let gWindowWatcher = {
+  getNewPrompter: () => gMockPrompter,
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWindowWatcher]),
+};
+
+// Ensure that the appropriate initialization has happened.
+do_get_profile();
+
+let windowWatcherCID =
+  MockRegistrar.register("@mozilla.org/embedcomp/window-watcher;1",
+                         gWindowWatcher);
+do_register_cleanup(() => {
+  MockRegistrar.unregister(windowWatcherCID);
+});
+
+TESTCASES.forEach(testcase => {
+  let token = MasterPassword._token;
+
+  add_task(async function test_encrypt_decrypt() {
+    do_print("Starting testcase: " + testcase.description);
+    token.initPassword(testcase.masterPassword);
+
+    // Test only: Force the token login without asking for master password
+    token.login(/* force */ false);
+    Assert.equal(testcase.mpEnabled, token.isLoggedIn(), "Token should now be logged into");
+    Assert.equal(MasterPassword.isEnabled, testcase.mpEnabled);
+
+    let testText = "test string";
+    let cipherText = await MasterPassword.encrypt(testText);
+    Assert.notEqual(testText, cipherText);
+    let plainText = await MasterPassword.decrypt(cipherText);
+    Assert.equal(testText, plainText);
+    if (token.isLoggedIn()) {
+      // Reset state.
+      gMockPrompter.numPrompts = 0;
+      token.logoutSimple();
+
+      ok(!token.isLoggedIn(),
+         "Token should be logged out after calling logoutSimple()");
+
+      // Try with the correct password.
+      gMockPrompter.passwordToTry = testcase.masterPassword;
+      await MasterPassword.encrypt(testText);
+      Assert.equal(gMockPrompter.numPrompts, 1, "should have prompted for encryption");
+
+      // Reset state.
+      gMockPrompter.numPrompts = 0;
+      token.logoutSimple();
+
+      try {
+        // Try with the incorrect password.
+        gMockPrompter.passwordToTry = "XXX";
+        await MasterPassword.decrypt(cipherText);
+        throw new Error("Not receiving canceled master password error");
+      } catch (e) {
+        Assert.equal(e.message, "User canceled master password entry");
+      }
+    }
+
+    token.reset();
+  });
+});
--- a/browser/extensions/formautofill/test/unit/test_storage_tombstones.js
+++ b/browser/extensions/formautofill/test/unit/test_storage_tombstones.js
@@ -35,20 +35,24 @@ let do_check_tombstone_record = (profile
                    ["guid", "timeLastModified", "deleted"].sort());
 };
 
 // Like add_task, but actually adds 2 - one for addresses and one for cards.
 function add_storage_task(test_function) {
   add_task(async function() {
     let path = getTempFile(TEST_STORE_FILE_NAME).path;
     let profileStorage = new ProfileStorage(path);
+    let testCC1 = Object.assign({}, TEST_CC_1);
     await profileStorage.initialize();
 
     for (let [storage, record] of [[profileStorage.addresses, TEST_ADDRESS_1],
-                                   [profileStorage.creditCards, TEST_CC_1]]) {
+                                   [profileStorage.creditCards, testCC1]]) {
+      if (storage.normalizeCCNumberFields) {
+        await storage.normalizeCCNumberFields(record);
+      }
       await test_function(storage, record);
     }
   });
 }
 
 add_storage_task(async function test_simple_tombstone(storage, record) {
   do_print("check simple tombstone semantics");
 
--- a/browser/extensions/formautofill/test/unit/test_transformFields.js
+++ b/browser/extensions/formautofill/test/unit/test_transformFields.js
@@ -483,68 +483,82 @@ const ADDRESS_NORMALIZE_TESTCASES = [
   },
 ];
 
 const CREDIT_CARD_COMPUTE_TESTCASES = [
   // Empty
   {
     description: "Empty credit card",
     creditCard: {
+      "cc-number": "1234123412341234", // cc-number won't be verified
     },
     expectedResult: {
     },
   },
 
   // Name
   {
     description: "Has \"cc-name\"",
     creditCard: {
       "cc-name": "Timothy John Berners-Lee",
+      "cc-number": "1234123412341234", // cc-number won't be verified
     },
     expectedResult: {
       "cc-name": "Timothy John Berners-Lee",
       "cc-given-name": "Timothy",
       "cc-additional-name": "John",
       "cc-family-name": "Berners-Lee",
     },
   },
 ];
 
 const CREDIT_CARD_NORMALIZE_TESTCASES = [
   // Empty
   {
-    description: "Empty credit card",
+    description: "No normalizable field",
     creditCard: {
+      "cc-number": "1234123412341234", // cc-number won't be verified
     },
     expectedResult: {
     },
   },
 
   // Name
   {
     description: "Has both \"cc-name\" and the split name fields",
     creditCard: {
       "cc-name": "Timothy John Berners-Lee",
       "cc-given-name": "John",
       "cc-family-name": "Doe",
+      "cc-number": "1234123412341234", // cc-number won't be verified
     },
     expectedResult: {
       "cc-name": "Timothy John Berners-Lee",
     },
   },
   {
     description: "Has only the split name fields",
     creditCard: {
       "cc-given-name": "John",
       "cc-family-name": "Doe",
+      "cc-number": "1234123412341234", // cc-number won't be verified
     },
     expectedResult: {
       "cc-name": "John Doe",
     },
   },
+  {
+    description: "Number should be encrypted and masked",
+    creditCard: {
+      "cc-number": "1234123412341234",
+    },
+    expectedResult: {
+      "cc-number": "************1234",
+    },
+  },
 ];
 
 let do_check_record_matches = (expectedRecord, record) => {
   for (let key in expectedRecord) {
     do_check_eq(expectedRecord[key], record[key]);
   }
 };
 
@@ -589,17 +603,21 @@ add_task(async function test_normalizeAd
 });
 
 add_task(async function test_computeCreditCardFields() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
 
   let profileStorage = new ProfileStorage(path);
   await profileStorage.initialize();
 
-  CREDIT_CARD_COMPUTE_TESTCASES.forEach(testcase => profileStorage.creditCards.add(testcase.creditCard));
+  for (let testcase of CREDIT_CARD_COMPUTE_TESTCASES) {
+    let encryptedCC = Object.assign({}, testcase.creditCard);
+    await profileStorage.creditCards.normalizeCCNumberFields(encryptedCC);
+    profileStorage.creditCards.add(encryptedCC);
+  }
   await profileStorage._saveImmediately();
 
   profileStorage = new ProfileStorage(path);
   await profileStorage.initialize();
 
   let creditCards = profileStorage.creditCards.getAll();
 
   for (let i in creditCards) {
@@ -609,17 +627,21 @@ add_task(async function test_computeCred
 });
 
 add_task(async function test_normalizeCreditCardFields() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
 
   let profileStorage = new ProfileStorage(path);
   await profileStorage.initialize();
 
-  CREDIT_CARD_NORMALIZE_TESTCASES.forEach(testcase => profileStorage.creditCards.add(testcase.creditCard));
+  for (let testcase of CREDIT_CARD_NORMALIZE_TESTCASES) {
+    let encryptedCC = Object.assign({}, testcase.creditCard);
+    await profileStorage.creditCards.normalizeCCNumberFields(encryptedCC);
+    profileStorage.creditCards.add(encryptedCC);
+  }
   await profileStorage._saveImmediately();
 
   profileStorage = new ProfileStorage(path);
   await profileStorage.initialize();
 
   let creditCards = profileStorage.creditCards.getAll();
 
   for (let i in creditCards) {
--- a/browser/extensions/formautofill/test/unit/xpcshell.ini
+++ b/browser/extensions/formautofill/test/unit/xpcshell.ini
@@ -27,16 +27,17 @@ support-files =
 [test_getCategoriesFromFieldNames.js]
 [test_getFormInputDetails.js]
 [test_getInfo.js]
 [test_getRecords.js]
 [test_isAvailable.js]
 [test_isCJKName.js]
 [test_isFieldEligibleForAutofill.js]
 [test_markAsAutofillField.js]
+[test_masterPassword.js]
 [test_migrateRecords.js]
 [test_nameUtils.js]
 [test_onFormSubmitted.js]
 [test_profileAutocompleteResult.js]
 [test_phoneNumber.js]
 [test_reconcile.js]
 [test_savedFieldNames.js]
 [test_toOneLineAddress.js]
--- a/browser/modules/E10SUtils.jsm
+++ b/browser/modules/E10SUtils.jsm
@@ -238,16 +238,24 @@ this.E10SUtils = {
 
       // If not originally loaded in this process allow it if the URI would
       // normally be allowed to load in this process by default.
       let remoteType = Services.appinfo.remoteType;
       return remoteType ==
         this.getRemoteTypeForURIObject(aURI, true, remoteType, webNav.currentURI);
     }
 
+    if (sessionHistory.count == 1 && webNav.currentURI.spec == "about:newtab") {
+      // This is possibly a preloaded browser and we're about to navigate away for
+      // the first time. On the child side there is no way to tell for sure if that
+      // is the case, so let's redirect this request to the parent to decide if a new
+      // process is needed.
+      return false;
+    }
+
     // If the URI can be loaded in the current process then continue
     return this.shouldLoadURIInThisProcess(aURI);
   },
 
   redirectLoad(aDocShell, aURI, aReferrer, aTriggeringPrincipal, aFreshProcess, aFlags) {
     // Retarget the load to the correct process
     let messageManager = aDocShell.QueryInterface(Ci.nsIInterfaceRequestor)
                                   .getInterface(Ci.nsIContentFrameMessageManager);
--- a/devtools/client/animationinspector/animation-inspector.xhtml
+++ b/devtools/client/animationinspector/animation-inspector.xhtml
@@ -17,17 +17,17 @@
     </div>
     <div id="timeline-toolbar" class="theme-toolbar">
       <button id="rewind-timeline" class="devtools-button"></button>
       <button id="pause-resume-timeline" class="devtools-button pause-button paused"></button>
       <span id="timeline-rate" class="devtools-button"></span>
       <span id="timeline-current-time" class="label"></span>
     </div>
     <div id="players"></div>
-    <div id="error-message">
+    <div id="error-message" class="devtools-sidepanel-no-result">
       <p id="error-type"></p>
       <p id="error-hint"></p>
       <button id="element-picker" data-standalone="true" class="devtools-button"></button>
     </div>
     <script type="text/javascript">
       /* eslint-disable */
       var isInChrome = window.location.href.includes("chrome:");
       if (isInChrome) {
--- a/devtools/client/inspector/grids/components/Grid.js
+++ b/devtools/client/inspector/grids/components/Grid.js
@@ -83,15 +83,15 @@ module.exports = createClass({
           grids,
           onShowGridAreaHighlight,
           onShowGridCellHighlight,
         })
       )
       :
       dom.div(
         {
-          className: "layout-no-grids",
+          className: "devtools-sidepanel-no-result",
         },
         getStr("layout.noGridsOnThisPage")
       );
   },
 
 });
--- a/devtools/client/inspector/grids/test/browser_grids_grid-list-no-grids.js
+++ b/devtools/client/inspector/grids/test/browser_grids_grid-list-no-grids.js
@@ -17,17 +17,17 @@ const TEST_URI = `
 
 add_task(function* () {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let { inspector, gridInspector } = yield openLayoutView();
   let { document: doc } = gridInspector;
   let { highlighters } = inspector;
 
   yield selectNode("#grid", inspector);
-  let noGridList = doc.querySelector(".layout-no-grids");
+  let noGridList = doc.querySelector(".grid-pane .devtools-sidepanel-no-result");
   let gridList = doc.getElementById("grid-list");
 
   info("Checking the initial state of the Grid Inspector.");
   ok(noGridList, "The message no grid containers is displayed.");
   ok(!gridList, "No grid containers are listed.");
   ok(!highlighters.highlighters[HIGHLIGHTER_TYPE],
     "No CSS grid highlighter exists in the highlighters overlay.");
   ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
--- a/devtools/client/inspector/grids/test/browser_grids_grid-list-on-mutation-element-removed.js
+++ b/devtools/client/inspector/grids/test/browser_grids_grid-list-on-mutation-element-removed.js
@@ -53,11 +53,11 @@ add_task(function* () {
   testActor.eval(`
     content.document.getElementById("grid").remove();
   `);
   yield onHighlighterHidden;
   yield onCheckboxChange;
 
   info("Checking the CSS grid highlighter is not shown.");
   ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
-  let noGridList = doc.querySelector(".layout-no-grids");
+  let noGridList = doc.querySelector(".grid-pane .devtools-sidepanel-no-result");
   ok(noGridList, "The message no grid containers is displayed.");
 });
--- a/devtools/client/inspector/inspector.xhtml
+++ b/devtools/client/inspector/inspector.xhtml
@@ -124,18 +124,18 @@
                  class="includebrowserstyles"/>
           <label id="browser-style-checkbox-label" for="browser-style-checkbox"
                  data-localization="content=inspector.browserStyles.label"></label>
         </div>
 
         <div id="computed-container">
           <div id="computed-container-focusable" tabindex="-1">
             <div id="boxmodel-wrapper"></div>
-            <div id="computed-property-container" class="theme-separator devtools-monospace" tabindex="0" dir="ltr"></div>
-            <div id="computed-no-results" hidden="" data-localization="content=inspector.noProperties"></div>
+            <div id="computed-property-container" class="devtools-monospace" tabindex="0" dir="ltr"></div>
+            <div id="computed-no-results" class="devtools-sidepanel-no-result" hidden="" data-localization="content=inspector.noProperties"></div>
           </div>
         </div>
       </div>
 
       <div id="sidebar-panel-animationinspector" class="theme-sidebar inspector-tabpanel">
         <iframe class="devtools-inspector-tab-frame"></iframe>
       </div>
     </div>
--- a/devtools/client/inspector/layout/components/Accordion.css
+++ b/devtools/client/inspector/layout/components/Accordion.css
@@ -11,25 +11,28 @@
   background-color: var(--theme-body-background);
   width: 100%;
 }
 
 .accordion ._header {
   background-color: var(--theme-toolbar-background);
   border-bottom: 1px solid var(--theme-splitter-color);
   cursor: pointer;
-  font-size: 11px;
+  font-size: 12px;
   padding: 5px;
   transition: all 0.25s ease;
   width: 100%;
+  align-items: center;
+  display: flex;
+
   -moz-user-select: none;
 }
 
 .accordion ._header:hover {
-  background-color: var(--theme-toolbar-hover);
+  background-color: var(--theme-toolbar-background-hover);
 }
 
 .accordion ._header:hover svg {
   fill: var(--theme-comment-alt);
 }
 
 .accordion ._content {
   border-bottom: 1px solid var(--theme-splitter-color);
--- a/devtools/client/inspector/rules/rules.js
+++ b/devtools/client/inspector/rules/rules.js
@@ -906,16 +906,17 @@ CssRuleView.prototype = {
    */
   _showEmpty: function () {
     if (this.styleDocument.getElementById("ruleview-no-results")) {
       return;
     }
 
     createChild(this.element, "div", {
       id: "ruleview-no-results",
+      class: "devtools-sidepanel-no-result",
       textContent: l10n("rule.empty")
     });
   },
 
   /**
    * Clear the rules.
    */
   _clearRules: function () {
@@ -1052,17 +1053,17 @@ CssRuleView.prototype = {
     if (isOpen) {
       twisty.removeAttribute("open");
     } else {
       twisty.setAttribute("open", "true");
     }
   },
 
   _getRuleViewHeaderClassName: function (isPseudo) {
-    let baseClassName = "theme-gutter ruleview-header";
+    let baseClassName = "ruleview-header";
     return isPseudo ? baseClassName + " ruleview-expandable-header" :
       baseClassName;
   },
 
   /**
    * Creates editor UI for each of the rules in _elementStyle.
    */
   _createEditors: function () {
--- a/devtools/client/inspector/rules/test/browser_rules_inherited-properties_04.js
+++ b/devtools/client/inspector/rules/test/browser_rules_inherited-properties_04.js
@@ -20,14 +20,14 @@ add_task(function* () {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {inspector, view} = yield openRuleView();
   yield selectNode("a", inspector);
   yield getRuleViewSelectorHighlighterIcon(view, "element", 2);
   yield elementStyleInherit(inspector, view);
 });
 
 function* elementStyleInherit(inspector, view) {
-  let gutters = view.element.querySelectorAll(".theme-gutter");
+  let gutters = view.element.querySelectorAll(".ruleview-header");
   is(gutters.length, 2,
     "Gutters should contains 2 sections");
   ok(gutters[0].textContent, "Inherited from div");
   ok(gutters[1].textContent, "Inherited from div");
 }
--- a/devtools/client/inspector/rules/test/browser_rules_keyframes-rule_01.js
+++ b/devtools/client/inspector/rules/test/browser_rules_keyframes-rule_01.js
@@ -85,17 +85,17 @@ function* assertKeyframeRules(selector, 
       keyframeRule.keyframes.name + " has the correct keyframes name");
     ok(keyframeRule.domRule.keyText == expected.keyframeRules[i],
       keyframeRule.domRule.keyText + " selector heading is correct");
     i++;
   }
 }
 
 function assertGutters(view, expected) {
-  let gutters = view.element.querySelectorAll(".theme-gutter");
+  let gutters = view.element.querySelectorAll(".ruleview-header");
 
   is(gutters.length, expected.guttersNbs,
     "There are " + gutters.length + " gutter headings");
 
   let i = 0;
   for (let gutter of gutters) {
     is(gutter.textContent, expected.gutterHeading[i],
       "Correct " + gutter.textContent + " gutter headings");
--- a/devtools/client/inspector/rules/test/browser_rules_pseudo-element_01.js
+++ b/devtools/client/inspector/rules/test/browser_rules_pseudo-element_01.js
@@ -236,17 +236,17 @@ function* assertPseudoElementRulesNumber
      selector + " has the correct number of :before rules");
   is(rules.afterRules.length, ruleNbs.afterRulesNb,
      selector + " has the correct number of :after rules");
 
   return rules;
 }
 
 function getGutters(view) {
-  return view.element.querySelectorAll(".theme-gutter");
+  return view.element.querySelectorAll(".ruleview-header");
 }
 
 function assertGutters(view) {
   let gutters = getGutters(view);
 
   is(gutters.length, 3,
      "There are 3 gutter headings");
   is(gutters[0].textContent, "Pseudo-elements",
--- a/devtools/client/inspector/rules/views/rule-editor.js
+++ b/devtools/client/inspector/rules/views/rule-editor.js
@@ -92,17 +92,17 @@ RuleEditor.prototype = {
 
     // Do not allow editing anonymousselectors until we can
     // detect mutations on  pseudo elements in Bug 1034110.
     return trait && !this.rule.elementStyle.element.isAnonymous;
   },
 
   _create: function () {
     this.element = this.doc.createElement("div");
-    this.element.className = "ruleview-rule theme-separator devtools-monospace";
+    this.element.className = "ruleview-rule devtools-monospace";
     this.element.setAttribute("uneditable", !this.isEditable);
     this.element.setAttribute("unmatched", this.rule.isUnmatched);
     this.element._ruleEditor = this;
 
     // Give a relative position for the inplace editor's measurement
     // span to be placed absolutely against.
     this.element.style.position = "relative";
 
--- a/devtools/client/inspector/test/browser_inspector_pseudoclass-lock.js
+++ b/devtools/client/inspector/test/browser_inspector_pseudoclass-lock.js
@@ -111,18 +111,17 @@ function* assertPseudoAddedToNode(inspec
   let hasLock = yield testActor.hasPseudoClassLock(selector, PSEUDO);
   ok(hasLock, "pseudo-class lock has been applied");
   hasLock = yield testActor.hasPseudoClassLock("#parent-div", PSEUDO);
   ok(hasLock, "pseudo-class lock has been applied");
   hasLock = yield testActor.hasPseudoClassLock("body", PSEUDO);
   ok(hasLock, "pseudo-class lock has been applied");
 
   info("Check that the ruleview contains the pseudo-class rule");
-  let rules = ruleview.element.querySelectorAll(
-    ".ruleview-rule.theme-separator");
+  let rules = ruleview.element.querySelectorAll(".ruleview-rule");
   is(rules.length, 3,
      "rule view is showing 3 rules for pseudo-class locked div");
   is(rules[1]._ruleEditor.rule.selectorText, "div:hover",
      "rule view is showing " + PSEUDO + " rule");
 
   info("Show the highlighter on " + selector);
   yield showPickerOn(selector, inspector);
 
@@ -142,18 +141,17 @@ function* assertPseudoRemovedFromNode(te
   hasLock = yield testActor.hasPseudoClassLock("#parent-div", PSEUDO);
   ok(!hasLock, "pseudo-class lock has been removed");
   hasLock = yield testActor.hasPseudoClassLock("body", PSEUDO);
   ok(!hasLock, "pseudo-class lock has been removed");
 }
 
 function* assertPseudoRemovedFromView(inspector, testActor, ruleview, selector) {
   info("Check that the ruleview no longer contains the pseudo-class rule");
-  let rules = ruleview.element.querySelectorAll(
-    ".ruleview-rule.theme-separator");
+  let rules = ruleview.element.querySelectorAll(".ruleview-rule");
   is(rules.length, 2, "rule view is showing 2 rules after removing lock");
 
   yield showPickerOn(selector, inspector);
 
   let value = yield testActor.getHighlighterNodeTextContent(
     "box-model-infobar-pseudo-classes");
   is(value, "", "pseudo-class removed from infobar selector");
   yield inspector.highlighter.hideBoxModel();
--- a/devtools/client/themes/animationinspector.css
+++ b/devtools/client/themes/animationinspector.css
@@ -152,18 +152,16 @@ body {
 
 [empty] #players {
   display: none;
 }
 
 /* The error message, shown when an invalid/unanimated element is selected */
 
 #error-message {
-  padding-top: 10%;
-  text-align: center;
   flex: 1;
   overflow: auto;
 
   /* The error message is hidden by default */
   display: none;
 }
 
 [empty] #error-message {
--- a/devtools/client/themes/common.css
+++ b/devtools/client/themes/common.css
@@ -1,13 +1,12 @@
 /* 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/. */
 
-
 @import url("resource://devtools/client/themes/splitters.css");
 @namespace html url("http://www.w3.org/1999/xhtml");
 
 :root {
   font: message-box;
 }
 
 :root[platform="mac"] {
@@ -682,8 +681,18 @@ checkbox:-moz-focusring {
 
   border-inline-start: 1px solid var(--theme-splitter-color);
 
   background: var(--theme-tab-toolbar-background);
   background-image: url("chrome://devtools/skin/images/dropmarker.svg");
   background-repeat: no-repeat;
   background-position: center;
 }
+
+/* No result message styles */
+
+.devtools-sidepanel-no-result {
+  font-style: italic;
+  text-align: center;
+  padding: 0.5em;
+  -moz-user-select: none;
+  font-size: 12px;
+}
--- a/devtools/client/themes/dark-theme.css
+++ b/devtools/client/themes/dark-theme.css
@@ -64,26 +64,16 @@ body {
 .cm-s-mozilla .cm-hr,
 .cm-s-mozilla .cm-comment,
 .variable-or-property .token-undefined,
 .variable-or-property .token-null,
 .CodeMirror-Tern-completion-unknown:before {
   color: var(--theme-comment);
 }
 
-.theme-gutter {
-  background-color: var(--theme-tab-toolbar-background);
-  color: var(--theme-content-color3);
-  border-color: var(--theme-splitter-color);
-}
-
-.theme-separator {
-  border-color: var(--theme-splitter-color);
-}
-
 .theme-fg-color1,
 .cm-s-mozilla .cm-number,
 .variable-or-property .token-number,
 .variable-or-property[return] > .title > .name,
 .variable-or-property[scope] > .title > .name {
   color: var(--theme-highlight-red);
 }
 
--- a/devtools/client/themes/inspector.css
+++ b/devtools/client/themes/inspector.css
@@ -183,34 +183,24 @@ window {
   right: 0;
 }
 
 /* Override `-moz-user-focus:ignore;` from toolkit/content/minimal-xul.css */
 .inspector-tabpanel > * {
   -moz-user-focus: normal;
 }
 
-/* "no results" warning message displayed in the ruleview and in the computed view */
-
-#ruleview-no-results,
-#computed-no-results {
-  color: var(--theme-body-color-inactive);
-  text-align: center;
-  margin: 5px;
-}
-
 /* Markup Box */
 
 iframe {
   border: 0;
 }
 
 #markup-box {
   width: 100%;
   flex: 1;
   min-height: 0;
 }
 
 #markup-box > iframe {
   height: 100%;
   width: 100%;
 }
-
--- a/devtools/client/themes/layout.css
+++ b/devtools/client/themes/layout.css
@@ -169,26 +169,16 @@
 .grid-outline-text-icon {
   background: url("chrome://devtools/skin/images/sad-face.svg");
   margin-inline-end: 5px;
   width: 16px;
   height: 16px;
 }
 
 /**
- * Container when no grids are present
- */
-
-.layout-no-grids {
-  font-style: italic;
-  text-align: center;
-  padding: 0.5em;
-}
-
-/**
  * Grid Item
  */
 
 .grid-color-swatch {
   width: 12px;
   height: 12px;
   margin-left: 5px;
   border: 1px solid var(--theme-highlight-gray);
--- a/devtools/client/themes/light-theme.css
+++ b/devtools/client/themes/light-theme.css
@@ -62,26 +62,16 @@ body {
 .cm-s-mozilla .cm-hr,
 .cm-s-mozilla .cm-comment,
 .variable-or-property .token-undefined,
 .variable-or-property .token-null,
 .CodeMirror-Tern-completion-unknown:before {
   color: var(--theme-comment);
 }
 
-.theme-gutter {
-  background-color: var(--theme-tab-toolbar-background);
-  color: var(--theme-content-color3);
-  border-color: var(--theme-splitter-color);
-}
-
-.theme-separator { /* grey */
-  border-color: #cddae5;
-}
-
 .cm-s-mozilla .cm-unused-line {
   text-decoration: line-through;
   text-decoration-color: var(--theme-highlight-bluegrey);
 }
 
 .cm-s-mozilla .cm-executed-line {
   background-color: #fcfffc;
 }
--- a/devtools/client/themes/rules.css
+++ b/devtools/client/themes/rules.css
@@ -141,20 +141,16 @@
 .ruleview-code {
   direction: ltr;
 }
 
 .ruleview-property:not(:hover) > .ruleview-enableproperty {
   pointer-events: none;
 }
 
-.ruleview-expandable-container[hidden] {
-  display: none;
-}
-
 .ruleview-expandable-container {
   display: block;
 }
 
 .ruleview-namecontainer {
   cursor: text;
 }
 
@@ -163,16 +159,17 @@
   padding-right: 5px;
 }
 
 .ruleview-propertyvaluecontainer a {
   cursor: pointer;
 }
 
 .ruleview-computedlist,
+.ruleview-expandable-container[hidden],
 .ruleview-overridden-items[hidden],
 .ruleview-overridden-rule-filter[hidden],
 .ruleview-warning[hidden],
 .ruleview-overridden .ruleview-grid {
   display: none;
 }
 
 .ruleview-computedlist[user-open],
@@ -217,59 +214,67 @@
   color: #0000FF;
 }
 
 .ruleview-rule-source:not([unselectable]):hover {
   text-decoration: underline;
 }
 
 .ruleview-header {
-  border-top-width: 1px;
-  border-bottom-width: 1px;
-  border-top-style: solid;
-  border-bottom-style: solid;
-  padding: 1px 4px;
+  background-color: var(--theme-toolbar-background);
+  border-bottom: 1px solid var(--theme-splitter-color);
+  font-size: 12px;
+  padding: 5px;
+  width: 100%;
+  align-items: center;
+  display: flex;
+
   -moz-user-select: none;
-  word-wrap: break-word;
-  vertical-align: middle;
-  min-height: 1.5em;
-  line-height: 1.5em;
-  margin-top: -1px;
 }
 
-.theme-firebug .theme-gutter.ruleview-header {
+.theme-firebug .ruleview-header {
   font-family: var(--proportional-font-family);
   font-weight: bold;
   color: inherit;
   border: none;
   margin: 4px 0;
   padding: 3px 4px 2px 4px;
   line-height: inherit;
   min-height: 0;
   background: var(--theme-header-background);
 }
 
 :root[platform="win"] .ruleview-header,
 :root[platform="linux"] .ruleview-header {
   margin-top: 4px;
 }
 
-.ruleview-header.ruleview-expandable-header {
+.ruleview-expandable-header {
   cursor: pointer;
 }
 
+.ruleview-expandable-header:hover {
+  background-color: var(--theme-toolbar-background-hover);
+}
+
+
 .ruleview-rule-pseudo-element {
   padding-left:20px;
   border-left: solid 10px;
 }
 
 .ruleview-rule {
+  border-bottom: 1px solid var(--theme-splitter-color);
   padding: 2px 4px;
 }
 
+#ruleview-container-focusable > .ruleview-rule:last-child {
+  border-bottom: none;
+}
+
 /**
  * Display rules that don't match the current selected element and uneditable
  * user agent styles differently
  */
 .ruleview-rule[unmatched=true],
 .ruleview-rule[uneditable=true] {
   background: var(--theme-tab-toolbar-background);
 }
@@ -322,23 +327,18 @@
 .theme-firebug .ruleview-enableproperty:not([checked]) ~ .ruleview-namecontainer *,
 .theme-firebug .ruleview-enableproperty:not([checked]) ~ .ruleview-propertyvaluecontainer,
 .theme-firebug .ruleview-enableproperty:not([checked]) ~ .ruleview-propertyvaluecontainer *,
 .theme-firebug .ruleview-overridden > * > .ruleview-computed:not(.ruleview-overridden),
 .theme-firebug .ruleview-overridden > * > .ruleview-computed:not(.ruleview-overridden) * {
   color: #CCCCCC;
 }
 
-.ruleview-rule + .ruleview-rule {
-  border-top-width: 1px;
-  border-top-style: dotted;
-}
-
 .theme-firebug .ruleview-rule + .ruleview-rule {
-  border-top: none;
+  border-bottom: none;
 }
 
 .ruleview-warning {
   background-image: url(images/alerticon-warning.png);
   background-size: 13px 12px;
   margin-inline-start: 5px;
   display: inline-block;
   width: 13px;
--- a/dom/base/nsContentSink.cpp
+++ b/dom/base/nsContentSink.cpp
@@ -453,16 +453,17 @@ nsContentSink::ProcessLinkHeader(const n
   nsAutoString href;
   nsAutoString rel;
   nsAutoString title;
   nsAutoString titleStar;
   nsAutoString type;
   nsAutoString media;
   nsAutoString anchor;
   nsAutoString crossOrigin;
+  nsAutoString referrerPolicy;
   nsAutoString as;
 
   crossOrigin.SetIsVoid(true);
 
   // copy to work buffer
   nsAutoString stringList(aLinkData);
 
   // put an extra null at the end
@@ -641,63 +642,74 @@ nsContentSink::ProcessLinkHeader(const n
               crossOrigin = value;
               crossOrigin.StripWhitespace();
             }
           } else if (attr.LowerCaseEqualsLiteral("as")) {
             if (as.IsEmpty()) {
               as = value;
               as.CompressWhitespace();
             }
+          } else if (attr.LowerCaseEqualsLiteral("referrerpolicy")) {
+            // https://html.spec.whatwg.org/multipage/urls-and-fetching.html#referrer-policy-attribute
+            // Specs says referrer policy attribute is an enumerated attribute,
+            // case insensitive and includes the empty string
+            // We will parse the value with AttributeReferrerPolicyFromString
+            // later, which will handle parsing it as an enumerated attribute.
+            if (referrerPolicy.IsEmpty()) {
+              referrerPolicy = value;
+            }
           }
         }
       }
     }
 
     if (endCh == kComma) {
       // hit a comma, process what we've got so far
 
       href.Trim(" \t\n\r\f"); // trim HTML5 whitespace
       if (!href.IsEmpty() && !rel.IsEmpty()) {
         rv = ProcessLink(anchor, href, rel,
                          // prefer RFC 5987 variant over non-I18zed version
                          titleStar.IsEmpty() ? title : titleStar,
-                         type, media, crossOrigin, as);
+                         type, media, crossOrigin, referrerPolicy, as);
       }
 
       href.Truncate();
       rel.Truncate();
       title.Truncate();
       type.Truncate();
       media.Truncate();
       anchor.Truncate();
+      referrerPolicy.Truncate();
       crossOrigin.SetIsVoid(true);
 
       seenParameters = false;
     }
 
     start = ++end;
   }
 
   href.Trim(" \t\n\r\f"); // trim HTML5 whitespace
   if (!href.IsEmpty() && !rel.IsEmpty()) {
     rv = ProcessLink(anchor, href, rel,
                      // prefer RFC 5987 variant over non-I18zed version
                      titleStar.IsEmpty() ? title : titleStar,
-                     type, media, crossOrigin, as);
+                     type, media, crossOrigin, referrerPolicy, as);
   }
 
   return rv;
 }
 
 
 nsresult
 nsContentSink::ProcessLink(const nsAString& aAnchor, const nsAString& aHref,
                            const nsAString& aRel, const nsAString& aTitle,
                            const nsAString& aType, const nsAString& aMedia,
                            const nsAString& aCrossOrigin,
+                           const nsAString& aReferrerPolicy,
                            const nsAString& aAs)
 {
   uint32_t linkTypes =
     nsStyleLinkElement::ParseLinkTypes(aRel);
 
   // The link relation may apply to a different resource, specified
   // in the anchor parameter. For the link relations supported so far,
   // we simply abort if the link applies to a resource different to the
@@ -735,26 +747,27 @@ nsContentSink::ProcessLink(const nsAStri
 
   // is it a stylesheet link?
   if (!(linkTypes & nsStyleLinkElement::eSTYLESHEET)) {
     return NS_OK;
   }
 
   bool isAlternate = linkTypes & nsStyleLinkElement::eALTERNATE;
   return ProcessStyleLink(nullptr, aHref, isAlternate, aTitle, aType,
-                          aMedia);
+                          aMedia, aReferrerPolicy);
 }
 
 nsresult
 nsContentSink::ProcessStyleLink(nsIContent* aElement,
                                 const nsAString& aHref,
                                 bool aAlternate,
                                 const nsAString& aTitle,
                                 const nsAString& aType,
-                                const nsAString& aMedia)
+                                const nsAString& aMedia,
+                                const nsAString& aReferrerPolicy)
 {
   if (aAlternate && aTitle.IsEmpty()) {
     // alternates must have title return without error, for now
     return NS_OK;
   }
 
   nsAutoString  mimeType;
   nsAutoString  params;
@@ -784,21 +797,26 @@ nsContentSink::ProcessStyleLink(nsIConte
     aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::integrity, integrity);
   }
   if (!integrity.IsEmpty()) {
     MOZ_LOG(dom::SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug,
             ("nsContentSink::ProcessStyleLink, integrity=%s",
              NS_ConvertUTF16toUTF8(integrity).get()));
   }
 
+  mozilla::net::ReferrerPolicy referrerPolicy =
+    mozilla::net::AttributeReferrerPolicyFromString(aReferrerPolicy);
+  if (referrerPolicy == net::RP_Unset) {
+    referrerPolicy = mDocument->GetReferrerPolicy();
+  }
   // If this is a fragment parser, we don't want to observe.
   // We don't support CORS for processing instructions
   bool isAlternate;
   rv = mCSSLoader->LoadStyleLink(aElement, url, aTitle, aMedia, aAlternate,
-                                 CORS_NONE, mDocument->GetReferrerPolicy(),
+                                 CORS_NONE, referrerPolicy,
                                  integrity, mRunsToCompletion ? nullptr : this,
                                  &isAlternate);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (!isAlternate && !mRunsToCompletion) {
     ++mPendingSheetCount;
     mScriptLoader->AddParserBlockingScriptExecutionBlocker();
   }
--- a/dom/base/nsContentSink.h
+++ b/dom/base/nsContentSink.h
@@ -150,24 +150,26 @@ protected:
   nsresult ProcessHTTPHeaders(nsIChannel* aChannel);
   nsresult ProcessHeaderData(nsIAtom* aHeader, const nsAString& aValue,
                              nsIContent* aContent = nullptr);
   nsresult ProcessLinkHeader(const nsAString& aLinkData);
   nsresult ProcessLink(const nsAString& aAnchor,
                        const nsAString& aHref, const nsAString& aRel,
                        const nsAString& aTitle, const nsAString& aType,
                        const nsAString& aMedia, const nsAString& aCrossOrigin,
+                       const nsAString& aReferrerPolicy,
                        const nsAString& aAs);
 
   virtual nsresult ProcessStyleLink(nsIContent* aElement,
                                     const nsAString& aHref,
                                     bool aAlternate,
                                     const nsAString& aTitle,
                                     const nsAString& aType,
-                                    const nsAString& aMedia);
+                                    const nsAString& aMedia,
+                                    const nsAString& aReferrerPolicy);
 
   void PrefetchPreloadHref(const nsAString &aHref, nsINode *aSource,
                            uint32_t aLinkTypes, const nsAString& aAs,
                            const nsAString& aType,
                            const nsAString& aMedia);
 
   // For PrefetchDNS() aHref can either be the usual
   // URI format or of the form "//www.hostname.com" without a scheme.
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -2226,16 +2226,17 @@ GK_ATOM(mozfixed, "-moz-fixed")
 GK_ATOM(Remote, "remote")
 GK_ATOM(RemoteId, "_remote_id")
 GK_ATOM(RemoteType, "remoteType")
 GK_ATOM(DisplayPort, "_displayport")
 GK_ATOM(DisplayPortMargins, "_displayportmargins")
 GK_ATOM(DisplayPortBase, "_displayportbase")
 GK_ATOM(AsyncScrollLayerCreationFailed, "_asyncscrolllayercreationfailed")
 GK_ATOM(forcemessagemanager, "forcemessagemanager")
+GK_ATOM(isPreloadBrowser, "isPreloadBrowser")
 
 // Names for system metrics
 GK_ATOM(color_picker_available, "color-picker-available")
 GK_ATOM(scrollbar_start_backward, "scrollbar-start-backward")
 GK_ATOM(scrollbar_start_forward, "scrollbar-start-forward")
 GK_ATOM(scrollbar_end_backward, "scrollbar-end-backward")
 GK_ATOM(scrollbar_end_forward, "scrollbar-end-forward")
 GK_ATOM(scrollbar_thumb_proportional, "scrollbar-thumb-proportional")
--- a/dom/base/nsStyleLinkElement.cpp
+++ b/dom/base/nsStyleLinkElement.cpp
@@ -517,16 +517,26 @@ nsStyleLinkElement::DoUpdateStyleSheet(n
   Element* scopeElement = isScoped ? thisContent->GetParentElement() : nullptr;
   if (scopeElement) {
     NS_ASSERTION(isInline, "non-inline style must not have scope element");
     scopeElement->SetIsElementInStyleScopeFlagOnSubtree(true);
   }
 
   bool doneLoading = false;
   nsresult rv = NS_OK;
+
+  // Load the link's referrerpolicy attribute. If the link does not provide a
+  // referrerpolicy attribute, ignore this and use the document's referrer
+  // policy
+
+  net::ReferrerPolicy referrerPolicy = GetLinkReferrerPolicy();
+  if (referrerPolicy == net::RP_Unset) {
+    referrerPolicy = doc->GetReferrerPolicy();
+  }
+
   if (isInline) {
     nsAutoString text;
     if (!nsContentUtils::GetNodeTextContent(thisContent, false, text, fallible)) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
 
     MOZ_ASSERT(thisContent->NodeInfo()->NameAtom() != nsGkAtoms::link,
                "<link> is not 'inline', and needs different CSP checks");
@@ -534,36 +544,28 @@ nsStyleLinkElement::DoUpdateStyleSheet(n
                                            thisContent->NodePrincipal(),
                                            doc->GetDocumentURI(),
                                            mLineNumber, text, &rv))
       return rv;
 
     // Parse the style sheet.
     rv = doc->CSSLoader()->
       LoadInlineStyle(thisContent, text, mLineNumber, title, media,
-                      scopeElement, aObserver, &doneLoading, &isAlternate);
+                      referrerPolicy, scopeElement, aObserver, &doneLoading,
+                      &isAlternate);
   }
   else {
     nsAutoString integrity;
     thisContent->GetAttr(kNameSpaceID_None, nsGkAtoms::integrity, integrity);
     if (!integrity.IsEmpty()) {
       MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug,
               ("nsStyleLinkElement::DoUpdateStyleSheet, integrity=%s",
                NS_ConvertUTF16toUTF8(integrity).get()));
     }
 
-    // if referrer attributes are enabled in preferences, load the link's referrer
-    // attribute. If the link does not provide a referrer attribute, ignore this
-    // and use the document's referrer policy
-
-    net::ReferrerPolicy referrerPolicy = GetLinkReferrerPolicy();
-    if (referrerPolicy == net::RP_Unset) {
-      referrerPolicy = doc->GetReferrerPolicy();
-    }
-
     // XXXbz clone the URI here to work around content policies modifying URIs.
     nsCOMPtr<nsIURI> clonedURI;
     uri->Clone(getter_AddRefs(clonedURI));
     NS_ENSURE_TRUE(clonedURI, NS_ERROR_OUT_OF_MEMORY);
     rv = doc->CSSLoader()->
       LoadStyleLink(thisContent, clonedURI, title, media, isAlternate,
                     GetCORSMode(), referrerPolicy, integrity,
                     aObserver, &isAlternate);
--- a/dom/base/test/browser.ini
+++ b/dom/base/test/browser.ini
@@ -1,11 +1,12 @@
 [DEFAULT]
 support-files =
   audio.ogg
+  dummy.html
   empty.html
   file_audioLoop.html
   file_audioLoopInIframe.html
   file_blocking_image.html
   file_bug902350.html
   file_bug902350_frame.html
   file_bug1011748_redirect.sjs
   file_bug1011748_OK.sjs
@@ -30,16 +31,18 @@ support-files =
 [browser_bug593387.js]
 [browser_bug902350.js]
 tags = mcb
 [browser_bug1011748.js]
 [browser_bug1058164.js]
 [browser_force_process_selector.js]
 skip-if = !e10s # this only makes sense with e10s-multi
 [browser_messagemanager_loadprocessscript.js]
+[browser_aboutnewtab_process_selection.js]
+skip-if = os == 'linux' # Linux x64 JSDCov failure
 [browser_messagemanager_targetframeloader.js]
 [browser_messagemanager_unload.js]
 [browser_pagehide_on_tab_close.js]
 skip-if = e10s # this tests non-e10s behavior. it's not expected to work in e10s.
 [browser_state_notifications.js]
 skip-if = true # Bug 1271028
 [browser_use_counters.js]
 [browser_timeout_throttling_with_audio_playback.js]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/browser_aboutnewtab_process_selection.js
@@ -0,0 +1,76 @@
+const TEST_URL = "http://www.example.com/browser/dom/base/test/dummy.html";
+
+var ppmm = Services.ppmm;
+
+add_task(async function(){
+  // We want to count processes in this test, so let's disable the pre-allocated process manager.
+  await SpecialPowers.pushPrefEnv({"set": [
+    ["dom.ipc.processPrelaunch.enabled", false],
+    ["dom.ipc.processCount", 10],
+    ["dom.ipc.keepProcessesAlive.web", 10],
+  ]});
+});
+
+// Ensure that the preloaded browser exists, and it's finished loading.
+async function ensurePreloaded(gBrowser) {
+  gBrowser._createPreloadBrowser();
+  // We cannot use the regular BrowserTestUtils helper for waiting here, since that
+  // would try to insert the preloaded browser, which would only break things.
+  await BrowserTestUtils.waitForCondition( () => {
+    return gBrowser._preloadedBrowser.contentDocument.readyState == "complete";
+  });
+}
+
+add_task(async function(){
+  // This test is only relevant in e10s.
+  if (!gMultiProcessBrowser)
+    return;
+
+  ppmm.releaseCachedProcesses();
+
+  // Wait for the preloaded browser to load.
+  await ensurePreloaded(gBrowser);
+
+  // Store the number of processes (note: +1 for the parent process).
+  const { childCount: originalChildCount } = ppmm;
+
+  // Use the preloaded browser and create another one.
+  BrowserOpenTab();
+  let tab1 = gBrowser.selectedTab;
+  await ensurePreloaded(gBrowser);
+
+  // Check that the process count did not change.
+  is(ppmm.childCount, originalChildCount, "Preloaded browser should not create a new content process.")
+
+  // Let's do another round.
+  BrowserOpenTab();
+  let tab2 = gBrowser.selectedTab;
+  await ensurePreloaded(gBrowser);
+
+  // Check that the process count did not change.
+  is(ppmm.childCount, originalChildCount, "Preloaded browser should (still) not create a new content process.")
+
+  // Navigate to a content page from the parent side.
+  tab2.linkedBrowser.loadURI(TEST_URL);
+  await BrowserTestUtils.browserLoaded(tab2.linkedBrowser, false, TEST_URL);
+  is(ppmm.childCount, originalChildCount + 1,
+     "Navigating away from the preloaded browser (parent side) should create a new content process.")
+
+  // Navigate to a content page from the child side.
+  await BrowserTestUtils.switchTab(gBrowser, tab1);
+  await ContentTask.spawn(tab1.linkedBrowser, null, async function() {
+    const TEST_URL = "http://www.example.com/browser/dom/base/test/dummy.html";
+    content.location.href = TEST_URL;
+  });
+  await BrowserTestUtils.browserLoaded(tab1.linkedBrowser, false, TEST_URL);
+  is(ppmm.childCount, originalChildCount + 2,
+     "Navigating away from the preloaded browser (child side) should create a new content process.")
+
+  await BrowserTestUtils.removeTab(tab1);
+  await BrowserTestUtils.removeTab(tab2);
+
+  // Since we kept alive all the processes, we can shut down the ones that do
+  // not host any tabs reliably.
+  ppmm.releaseCachedProcesses();
+  is(ppmm.childCount, originalChildCount, "We're back to the original process count.");
+});
new file mode 100644
--- /dev/null
+++ b/dom/base/test/dummy.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+<title>Dummy test page</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
+</head>
+<body>
+<p>Dummy test page</p>
+</body>
+</html>
--- a/dom/events/test/test_bug322588.html
+++ b/dom/events/test/test_bug322588.html
@@ -48,15 +48,19 @@ function done() {
   is(result," unload load","unexpected events"); // The first unload is for about:blank
   if (w)
     w.close();
   SimpleTest.finish();
 }
 
 SimpleTest.waitForExplicitFinish();
 SimpleTest.requestFlakyTimeout("untriaged");
-addLoadEvent(doTest);
+addLoadEvent(function() {
+  SpecialPowers.pushPrefEnv({"set": [
+    ["browser.newtab.preload", false]
+  ]}, doTest);
+});
 
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -757,34 +757,41 @@ ContentParent::MinTabSelect(const nsTArr
   }
 
   return candidate.forget();
 }
 
 /*static*/ already_AddRefed<ContentParent>
 ContentParent::GetNewOrUsedBrowserProcess(const nsAString& aRemoteType,
                                           ProcessPriority aPriority,
-                                          ContentParent* aOpener)
+                                          ContentParent* aOpener,
+                                          bool aPreferUsed)
 {
   nsTArray<ContentParent*>& contentParents = GetOrCreatePool(aRemoteType);
   uint32_t maxContentParents = GetMaxProcessCount(aRemoteType);
-
   if (aRemoteType.EqualsLiteral(LARGE_ALLOCATION_REMOTE_TYPE)) {
     // We never want to re-use Large-Allocation processes.
     if (contentParents.Length() >= maxContentParents) {
       return GetNewOrUsedBrowserProcess(NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE),
                                         aPriority,
                                         aOpener);
     }
   } else {
-    nsTArray<nsIContentProcessInfo*> infos(contentParents.Length());
+    uint32_t numberOfParents = contentParents.Length();
+    nsTArray<nsIContentProcessInfo*> infos(numberOfParents);
     for (auto* cp : contentParents) {
       infos.AppendElement(cp->mScriptableHelper);
     }
 
+    if (aPreferUsed && numberOfParents) {
+      // For the preloaded browser we don't want to create a new process but reuse an
+      // existing one.
+      maxContentParents = numberOfParents;
+    }
+
     nsCOMPtr<nsIContentProcessProvider> cpp =
       do_GetService("@mozilla.org/ipc/processselector;1");
     nsIContentProcessInfo* openerInfo = aOpener ? aOpener->mScriptableHelper.get() : nullptr;
     int32_t index;
     if (cpp &&
         NS_SUCCEEDED(cpp->ProvideProcess(aRemoteType, openerInfo,
                                          infos.Elements(), infos.Length(),
                                          maxContentParents, &index))) {
@@ -1126,32 +1133,40 @@ ContentParent::CreateBrowser(const TabCo
   }
 
   nsAutoString remoteType;
   if (!aFrameElement->GetAttr(kNameSpaceID_None, nsGkAtoms::RemoteType,
                               remoteType)) {
     remoteType.AssignLiteral(DEFAULT_REMOTE_TYPE);
   }
 
+  bool isPreloadBrowser = false;
+  nsAutoString isPreloadBrowserStr;
+  if (aFrameElement->GetAttr(kNameSpaceID_None, nsGkAtoms::isPreloadBrowser,
+                             isPreloadBrowserStr)) {
+    isPreloadBrowser = isPreloadBrowserStr.EqualsLiteral("true");
+  }
+
   RefPtr<nsIContentParent> constructorSender;
   if (isInContentProcess) {
     MOZ_ASSERT(aContext.IsMozBrowserElement() || aContext.IsJSPlugin());
     constructorSender = CreateContentBridgeParent(aContext, initialPriority,
                                                   openerTabId, tabId);
   } else {
     if (aOpenerContentParent) {
       constructorSender = aOpenerContentParent;
     } else {
       if (aContext.IsJSPlugin()) {
         constructorSender =
           GetNewOrUsedJSPluginProcess(aContext.JSPluginId(),
                                       initialPriority);
       } else {
         constructorSender =
-          GetNewOrUsedBrowserProcess(remoteType, initialPriority, nullptr);
+          GetNewOrUsedBrowserProcess(remoteType, initialPriority,
+                                     nullptr, isPreloadBrowser);
       }
       if (!constructorSender) {
         return nullptr;
       }
     }
     ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
     cpm->RegisterRemoteFrame(tabId,
                              ContentParentId(0),
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -167,17 +167,18 @@ public:
    * 1. browser iframe
    * 2. remote xul <browser>
    * 3. normal iframe
    */
   static already_AddRefed<ContentParent>
   GetNewOrUsedBrowserProcess(const nsAString& aRemoteType = NS_LITERAL_STRING(NO_REMOTE_TYPE),
                              hal::ProcessPriority aPriority =
                              hal::ProcessPriority::PROCESS_PRIORITY_FOREGROUND,
-                             ContentParent* aOpener = nullptr);
+                             ContentParent* aOpener = nullptr,
+                             bool aPreferUsed = false);
 
   /**
    * Get or create a content process for a JS plugin. aPluginID is the id of the JS plugin
    * (@see nsFakePlugin::mId). There is a maximum of one process per JS plugin.
    */
   static already_AddRefed<ContentParent>
   GetNewOrUsedJSPluginProcess(uint32_t aPluginID,
                               const hal::ProcessPriority& aPriority);
--- a/dom/media/gmp/ChromiumCDMAdapter.cpp
+++ b/dom/media/gmp/ChromiumCDMAdapter.cpp
@@ -46,45 +46,49 @@ ChromiumCdmHost(int aHostInterfaceVersio
     return nullptr;
   }
   return static_cast<cdm::Host_8*>(aUserData);
 }
 
 #define STRINGIFY(s) _STRINGIFY(s)
 #define _STRINGIFY(s) #s
 
+#ifdef MOZILLA_OFFICIAL
 static cdm::HostFile
 TakeToCDMHostFile(HostFileData& aHostFileData)
 {
   return cdm::HostFile(aHostFileData.mBinary.Path().get(),
                        aHostFileData.mBinary.TakePlatformFile(),
                        aHostFileData.mSig.TakePlatformFile());
 }
+#endif
 
 GMPErr
 ChromiumCDMAdapter::GMPInit(const GMPPlatformAPI* aPlatformAPI)
 {
   CDM_LOG("ChromiumCDMAdapter::GMPInit");
   sPlatform = aPlatformAPI;
   if (!mLib) {
     return GMPGenericErr;
   }
 
+#ifdef MOZILLA_OFFICIAL
   // Note: we must call the VerifyCdmHost_0 function if it's present before
   // we call the initialize function.
   auto verify = reinterpret_cast<decltype(::VerifyCdmHost_0)*>(
     PR_FindFunctionSymbol(mLib, STRINGIFY(VerifyCdmHost_0)));
   if (verify) {
     nsTArray<cdm::HostFile> files;
     for (HostFileData& hostFile : mHostFiles) {
       files.AppendElement(TakeToCDMHostFile(hostFile));
     }
     bool result = verify(files.Elements(), files.Length());
     GMP_LOG("%s VerifyCdmHost_0 returned %d", __func__, result);
   }
+#endif
 
   auto init = reinterpret_cast<decltype(::INITIALIZE_CDM_MODULE)*>(
     PR_FindFunctionSymbol(mLib, STRINGIFY(INITIALIZE_CDM_MODULE)));
   if (!init) {
     return GMPGenericErr;
   }
 
   CDM_LOG(STRINGIFY(INITIALIZE_CDM_MODULE)"()");
--- a/dom/payments/test/mochitest.ini
+++ b/dom/payments/test/mochitest.ini
@@ -8,12 +8,13 @@ support-files =
   ConstructorChromeScript.js
   GeneralChromeScript.js
   ShowPaymentChromeScript.js
 
 [test_abortPayment.html]
 run-if = nightly_build # Bug 1390018: Depends on the Nightly-only UI service
 [test_basiccard.html]
 [test_canMakePayment.html]
+run-if = nightly_build # Bug 1390737: Depends on the Nightly-only UI service
 [test_constructor.html]
 [test_showPayment.html]
 [test_validate_decimal_value.html]
 [test_payment-request-in-iframe.html]
--- a/dom/security/test/mixedcontentblocker/file_frameNavigation_blankTarget.html
+++ b/dom/security/test/mixedcontentblocker/file_frameNavigation_blankTarget.html
@@ -10,23 +10,21 @@ https://bugzilla.mozilla.org/show_bug.cg
 </head>
 <body>
 <a href="http://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation_innermost.html?blankTarget" id="blankTarget" target="_blank">Go to http site</a>
 
 <script>
   var blankTarget = document.getElementById("blankTarget");
   blankTarget.click();
 
-  var os = SpecialPowers.Cc["@mozilla.org/observer-service;1"].
-     getService(SpecialPowers.Components.interfaces.nsIObserverService);
   var observer = {
     observe: function(subject, topic, data) {
       if(topic == "content-document-global-created" && data =="http://example.com") {
          parent.parent.postMessage({"test": "blankTarget", "msg": "opened an http link with target=_blank from a secure page"}, "http://mochi.test:8888");
-         os.removeObserver(observer, "content-document-global-created");
+         SpecialPowers.removeAsyncObserver(observer, "content-document-global-created");
       }
     }
   }
-  os.addObserver(observer, "content-document-global-created");
+  SpecialPowers.addAsyncObserver(observer, "content-document-global-created");
 
 </script>
 </body>
 </html>
--- a/dom/workers/test/serviceworkers/test_fetch_integrity.html
+++ b/dom/workers/test/serviceworkers/test_fetch_integrity.html
@@ -36,16 +36,17 @@ function expect_security_console_message
 }
 
 // (This doesn't really need to be its own task, but it allows the actual test
 // case to be self-contained.)
 add_task(function setupPrefs() {
   return SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true],
+    ["browser.newtab.preload", false],
   ]});
 });
 
 add_task(async function test_integrity_serviceWorker() {
   var filename = make_absolute_url("fetch.js");
   var filename2 = make_absolute_url("fake.html");
 
   // The SW will claim us once it activates; this is async, start listening now.
--- a/dom/xml/nsXMLContentSink.cpp
+++ b/dom/xml/nsXMLContentSink.cpp
@@ -657,17 +657,18 @@ nsXMLContentSink::LoadXSLStyleSheet(nsIU
 }
 
 nsresult
 nsXMLContentSink::ProcessStyleLink(nsIContent* aElement,
                                    const nsAString& aHref,
                                    bool aAlternate,
                                    const nsAString& aTitle,
                                    const nsAString& aType,
-                                   const nsAString& aMedia)
+                                   const nsAString& aMedia,
+                                   const nsAString& aReferrerPolicy)
 {
   nsresult rv = NS_OK;
   mPrettyPrintXML = false;
 
   nsAutoCString cmd;
   if (mParser)
     GetParser()->GetCommand(cmd);
   if (cmd.EqualsASCII(kLoadAsData))
@@ -715,17 +716,17 @@ nsXMLContentSink::ProcessStyleLink(nsICo
       return NS_OK;
     }
 
     return LoadXSLStyleSheet(url);
   }
 
   // Let nsContentSink deal with css.
   rv = nsContentSink::ProcessStyleLink(aElement, aHref, aAlternate,
-                                       aTitle, aType, aMedia);
+                                       aTitle, aType, aMedia, aReferrerPolicy);
 
   // nsContentSink::ProcessStyleLink handles the bookkeeping here wrt
   // pending sheets.
 
   return rv;
 }
 
 void
@@ -1253,17 +1254,19 @@ nsXMLContentSink::HandleProcessingInstru
   nsAutoString href, title, media;
   bool isAlternate = false;
 
   // If there was no href, we can't do anything with this PI
   if (!ParsePIData(data, href, title, media, isAlternate)) {
       return DidProcessATokenImpl();
   }
 
-  rv = ProcessStyleLink(node, href, isAlternate, title, type, media);
+  // <?xml-stylesheet?> processing instructions don't have a referrerpolicy
+  // pseudo-attribute, so we pass in an empty string
+  rv = ProcessStyleLink(node, href, isAlternate, title, type, media, EmptyString());
   return NS_SUCCEEDED(rv) ? DidProcessATokenImpl() : rv;
 }
 
 /* static */
 bool
 nsXMLContentSink::ParsePIData(const nsString &aData, nsString &aHref,
                               nsString &aTitle, nsString &aMedia,
                               bool &aIsAlternate)
--- a/dom/xml/nsXMLContentSink.h
+++ b/dom/xml/nsXMLContentSink.h
@@ -145,17 +145,18 @@ protected:
   }
 
   // nsContentSink override
   virtual nsresult ProcessStyleLink(nsIContent* aElement,
                                     const nsAString& aHref,
                                     bool aAlternate,
                                     const nsAString& aTitle,
                                     const nsAString& aType,
-                                    const nsAString& aMedia) override;
+                                    const nsAString& aMedia,
+                                    const nsAString& aReferrerPolicy) override;
 
   nsresult LoadXSLStyleSheet(nsIURI* aUrl);
 
   bool CanStillPrettyPrint();
 
   nsresult MaybePrettyPrint();
 
   bool IsMonolithicContainer(mozilla::dom::NodeInfo* aNodeInfo);
--- a/dom/xml/nsXMLFragmentContentSink.cpp
+++ b/dom/xml/nsXMLFragmentContentSink.cpp
@@ -91,17 +91,19 @@ protected:
   virtual void MaybeStartLayout(bool aIgnorePendingSheets) override;
 
   // nsContentSink overrides
   virtual nsresult ProcessStyleLink(nsIContent* aElement,
                                     const nsAString& aHref,
                                     bool aAlternate,
                                     const nsAString& aTitle,
                                     const nsAString& aType,
-                                    const nsAString& aMedia) override;
+                                    const nsAString& aMedia,
+                                    const nsAString& aReferrerPolicy) override;
+
   nsresult LoadXSLStyleSheet(nsIURI* aUrl);
   void StartLayout();
 
   nsCOMPtr<nsIDocument> mTargetDocument;
   // the fragment
   nsCOMPtr<nsIContent>  mRoot;
   bool                  mParseError;
 };
@@ -326,17 +328,19 @@ nsXMLFragmentContentSink::ReportError(co
 }
 
 nsresult
 nsXMLFragmentContentSink::ProcessStyleLink(nsIContent* aElement,
                                            const nsAString& aHref,
                                            bool aAlternate,
                                            const nsAString& aTitle,
                                            const nsAString& aType,
-                                           const nsAString& aMedia)
+                                           const nsAString& aMedia,
+                                           const nsAString& aReferrerPolicy)
+
 {
   // don't process until moved to document
   return NS_OK;
 }
 
 nsresult
 nsXMLFragmentContentSink::LoadXSLStyleSheet(nsIURI* aUrl)
 {
--- a/image/imgFrame.cpp
+++ b/image/imgFrame.cpp
@@ -78,16 +78,29 @@ CreateLockedSurface(DataSourceSurface *a
   return nullptr;
 }
 
 static already_AddRefed<DataSourceSurface>
 AllocateBufferForImage(const IntSize& size,
                        SurfaceFormat format,
                        bool aIsAnimated = false)
 {
+#ifdef ANDROID
+  if (aIsAnimated) {
+    // For as long as an animated image is retained, its frames will never be
+    // released to let the OS purge volatile buffers. On Android, a volatile
+    // buffer actually keeps a file handle active, which we would like to avoid
+    // since many images and frames could easily exhaust the pool. As such, we
+    // use the heap. On the other platforms we do not have the file handle
+    // problem, and additionally we may avoid a superfluous memset since the
+    // volatile memory starts out as zero-filled.
+    return Factory::CreateDataSourceSurface(size, format, false);
+  }
+#endif
+
   int32_t stride = VolatileSurfaceStride(size, format);
   if (!aIsAnimated && gfxPrefs::ImageMemShared()) {
     RefPtr<SourceSurfaceSharedData> newSurf = new SourceSurfaceSharedData();
     if (newSurf->Init(size, stride, format)) {
       return newSurf.forget();
     }
   } else {
     RefPtr<SourceSurfaceVolatileData> newSurf= new SourceSurfaceVolatileData();
--- a/js/src/ctypes/CTypes.cpp
+++ b/js/src/ctypes/CTypes.cpp
@@ -290,19 +290,20 @@ namespace ArrayType {
   bool IsArrayOrArrayType(HandleValue v);
 
   static bool Create(JSContext* cx, unsigned argc, Value* vp);
   static bool ConstructData(JSContext* cx, HandleObject obj, const CallArgs& args);
 
   bool ElementTypeGetter(JSContext* cx, const JS::CallArgs& args);
   bool LengthGetter(JSContext* cx, const JS::CallArgs& args);
 
-  static bool Getter(JSContext* cx, HandleObject obj, HandleId idval, MutableHandleValue vp);
-  static bool Setter(JSContext* cx, HandleObject obj, HandleId idval, MutableHandleValue vp,
-                     ObjectOpResult& result);
+  static bool Getter(JSContext* cx, HandleObject obj, HandleId idval, MutableHandleValue vp,
+                     bool *handled);
+  static bool Setter(JSContext* cx, HandleObject obj, HandleId idval, HandleValue v,
+                     ObjectOpResult& result, bool* handled);
   static bool AddressOfElement(JSContext* cx, unsigned argc, Value* vp);
 } // namespace ArrayType
 
 namespace StructType {
   bool IsStruct(HandleValue v);
 
   static bool Create(JSContext* cx, unsigned argc, Value* vp);
   static bool ConstructData(JSContext* cx, HandleObject obj, const CallArgs& args);
@@ -575,17 +576,17 @@ static const JSClassOps sCTypeClassOps =
 static const JSClass sCTypeClass = {
   "CType",
   JSCLASS_HAS_RESERVED_SLOTS(CTYPE_SLOTS) |
   JSCLASS_FOREGROUND_FINALIZE,
   &sCTypeClassOps
 };
 
 static const JSClassOps sCDataClassOps = {
-  nullptr, nullptr, ArrayType::Getter, ArrayType::Setter,
+  nullptr, nullptr, nullptr, nullptr,
   nullptr, nullptr, nullptr, nullptr,
   CData::Finalize, FunctionType::Call, nullptr, FunctionType::Call
 };
 static const JSClass sCDataClass = {
   "CData",
   JSCLASS_HAS_RESERVED_SLOTS(CDATA_SLOTS) |
   JSCLASS_FOREGROUND_FINALIZE,
   &sCDataClassOps
@@ -874,16 +875,68 @@ static const JSFunctionSpec sModuleFunct
   JS_FN("CDataFinalizer", CDataFinalizer::Construct, 2, CTYPESFN_FLAGS),
   JS_FN("open", Library::Open, 1, CTYPESFN_FLAGS),
   JS_FN("cast", CData::Cast, 2, CTYPESFN_FLAGS),
   JS_FN("getRuntime", CData::GetRuntime, 1, CTYPESFN_FLAGS),
   JS_FN("libraryName", Library::Name, 1, CTYPESFN_FLAGS),
   JS_FS_END
 };
 
+// Wrapper for arrays, to intercept indexed gets/sets.
+class CDataArrayProxyHandler : public ForwardingProxyHandler
+{
+  public:
+    static const CDataArrayProxyHandler singleton;
+    static const char family;
+
+    constexpr CDataArrayProxyHandler() : ForwardingProxyHandler(&family) {}
+
+    bool get(JSContext* cx, HandleObject proxy, HandleValue receiver,
+             HandleId id, MutableHandleValue vp) const override;
+    bool set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v,
+             HandleValue receiver, ObjectOpResult& result) const override;
+};
+
+const CDataArrayProxyHandler CDataArrayProxyHandler::singleton;
+const char CDataArrayProxyHandler::family = 0;
+
+bool
+CDataArrayProxyHandler::get(JSContext* cx, HandleObject proxy, HandleValue receiver,
+                            HandleId id, MutableHandleValue vp) const
+{
+    RootedObject target(cx, proxy->as<ProxyObject>().target());
+    bool handled = false;
+    if (!ArrayType::Getter(cx, target, id, vp, &handled))
+        return false;
+    if (handled)
+        return true;
+    return ForwardingProxyHandler::get(cx, proxy, receiver, id, vp);
+}
+
+bool
+CDataArrayProxyHandler::set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v,
+                            HandleValue receiver, ObjectOpResult& result) const
+{
+    RootedObject target(cx, proxy->as<ProxyObject>().target());
+    bool handled = false;
+    if (!ArrayType::Setter(cx, target, id, v, result, &handled))
+        return false;
+    if (handled)
+        return true;
+    return ForwardingProxyHandler::set(cx, proxy, id, v, receiver, result);
+}
+
+static JSObject*
+MaybeUnwrapArrayWrapper(JSObject* obj)
+{
+    if (IsProxy(obj) && obj->as<ProxyObject>().handler() == &CDataArrayProxyHandler::singleton)
+        return obj->as<ProxyObject>().target();
+    return obj;
+}
+
 static MOZ_ALWAYS_INLINE JSString*
 NewUCString(JSContext* cx, const AutoString& from)
 {
   return JS_NewUCStringCopyN(cx, from.begin(), from.length());
 }
 
 /*
  * Return a size rounded up to a multiple of a power of two.
@@ -929,20 +982,23 @@ static const char*
 EncodeLatin1(JSContext* cx, AutoString& str, JSAutoByteString& bytes)
 {
   return bytes.encodeLatin1(cx, NewUCString(cx, str));
 }
 
 static const char*
 CTypesToSourceForError(JSContext* cx, HandleValue val, JSAutoByteString& bytes)
 {
-  if (val.isObject() &&
-      (CType::IsCType(&val.toObject()) || CData::IsCData(&val.toObject()))) {
-    RootedString str(cx, JS_ValueToSource(cx, val));
-    return bytes.encodeLatin1(cx, str);
+  if (val.isObject()) {
+      RootedObject obj(cx, &val.toObject());
+      if (CType::IsCType(obj) || CData::IsCDataMaybeUnwrap(&obj)) {
+          RootedValue v(cx, ObjectValue(*obj));
+          RootedString str(cx, JS_ValueToSource(cx, v));
+          return bytes.encodeLatin1(cx, str);
+      }
   }
   return ValueToSourceForError(cx, val, bytes);
 }
 
 static void
 BuildCStyleFunctionTypeSource(JSContext* cx, HandleObject typeObj,
                               HandleString nameStr, unsigned ptrCount,
                               AutoString& source);
@@ -2705,18 +2761,18 @@ jsvalToInteger(JSContext* cx, HandleValu
   }
   if (val.isDouble()) {
     // Don't silently lose bits here -- check that val really is an
     // integer value, and has the right sign.
     double d = val.toDouble();
     return ConvertExact(d, result);
   }
   if (val.isObject()) {
-    JSObject* obj = &val.toObject();
-    if (CData::IsCData(obj)) {
+    RootedObject obj(cx, &val.toObject());
+    if (CData::IsCDataMaybeUnwrap(&obj)) {
       JSObject* typeObj = CData::GetCType(obj);
       void* data = CData::GetData(obj);
 
       // Check whether the source type is always representable, with exact
       // precision, by the target type. If it is, convert the value.
       switch (CType::GetTypeCode(typeObj)) {
 #define INTEGER_CASE(name, fromType, ffiType)                                  \
       case TYPE_##name:                                                        \
@@ -2795,18 +2851,18 @@ jsvalToFloat(JSContext* cx, HandleValue 
     *result = FloatType(val.toInt32());
     return true;
   }
   if (val.isDouble()) {
     *result = FloatType(val.toDouble());
     return true;
   }
   if (val.isObject()) {
-    JSObject* obj = &val.toObject();
-    if (CData::IsCData(obj)) {
+    RootedObject obj(cx, &val.toObject());
+    if (CData::IsCDataMaybeUnwrap(&obj)) {
       JSObject* typeObj = CData::GetCType(obj);
       void* data = CData::GetData(obj);
 
       // Check whether the source type is always representable, with exact
       // precision, by the target type. If it is, convert the value.
       switch (CType::GetTypeCode(typeObj)) {
 #define NUMERIC_CASE(name, fromType, ffiType)                                  \
       case TYPE_##name:                                                        \
@@ -3348,17 +3404,17 @@ ImplicitConvert(JSContext* cx,
 
   // First, check if val is either a CData object or a CDataFinalizer
   // of type targetType.
   JSObject* sourceData = nullptr;
   JSObject* sourceType = nullptr;
   RootedObject valObj(cx, nullptr);
   if (val.isObject()) {
     valObj = &val.toObject();
-    if (CData::IsCData(valObj)) {
+    if (CData::IsCDataMaybeUnwrap(&valObj)) {
       sourceData = valObj;
       sourceType = CData::GetCType(sourceData);
 
       // If the types are equal, copy the buffer contained within the CData.
       // (Note that the buffers may overlap partially or completely.)
       if (CType::TypesEqual(sourceType, targetType)) {
         size_t size = CType::GetSize(sourceType);
         memmove(buffer, CData::GetData(sourceData), size);
@@ -5227,37 +5283,37 @@ PointerType::IsPointerType(HandleValue v
   return CType::IsCType(obj) && CType::GetTypeCode(obj) == TYPE_pointer;
 }
 
 bool
 PointerType::IsPointer(HandleValue v)
 {
   if (!v.isObject())
     return false;
-  JSObject* obj = &v.toObject();
+  JSObject* obj = MaybeUnwrapArrayWrapper(&v.toObject());
   return CData::IsCData(obj) && CType::GetTypeCode(CData::GetCType(obj)) == TYPE_pointer;
 }
 
 bool
 PointerType::TargetTypeGetter(JSContext* cx, const JS::CallArgs& args)
 {
   RootedObject obj(cx, &args.thisv().toObject());
   args.rval().set(JS_GetReservedSlot(obj, SLOT_TARGET_T));
   MOZ_ASSERT(args.rval().isObject());
   return true;
 }
 
 bool
 PointerType::IsNull(JSContext* cx, unsigned argc, Value* vp)
 {
   CallArgs args = CallArgsFromVp(argc, vp);
-  JSObject* obj = JS_THIS_OBJECT(cx, vp);
+  RootedObject obj(cx, JS_THIS_OBJECT(cx, vp));
   if (!obj)
     return false;
-  if (!CData::IsCData(obj)) {
+  if (!CData::IsCDataMaybeUnwrap(&obj)) {
     return IncompatibleThisProto(cx, "PointerType.prototype.isNull",
                                  args.thisv());
   }
 
   // Get pointer type and base type.
   JSObject* typeObj = CData::GetCType(obj);
   if (CType::GetTypeCode(typeObj) != TYPE_pointer) {
     return IncompatibleThisType(cx, "PointerType.prototype.isNull",
@@ -5270,17 +5326,17 @@ PointerType::IsNull(JSContext* cx, unsig
 }
 
 bool
 PointerType::OffsetBy(JSContext* cx, const CallArgs& args, int offset)
 {
   RootedObject obj(cx, JS_THIS_OBJECT(cx, args.base()));
   if (!obj)
     return false;
-  if (!CData::IsCData(obj)) {
+  if (!CData::IsCDataMaybeUnwrap(&obj)) {
     if (offset == 1) {
       return IncompatibleThisProto(cx, "PointerType.prototype.increment",
                                    args.thisv());
     }
     return IncompatibleThisProto(cx, "PointerType.prototype.decrement",
                                  args.thisv());
   }
 
@@ -5668,17 +5724,17 @@ ArrayType::IsArrayType(HandleValue v)
   return CType::IsCType(obj) && CType::GetTypeCode(obj) == TYPE_array;
 }
 
 bool
 ArrayType::IsArrayOrArrayType(HandleValue v)
 {
   if (!v.isObject())
     return false;
-  JSObject* obj = &v.toObject();
+  JSObject* obj = MaybeUnwrapArrayWrapper(&v.toObject());
 
    // Allow both CTypes and CDatas of the ArrayType persuasion by extracting the
    // CType if we're dealing with a CData.
   if (CData::IsCData(obj)) {
     obj = CData::GetCType(obj);
   }
   return CType::IsCType(obj) && CType::GetTypeCode(obj) == TYPE_array;
 }
@@ -5690,31 +5746,34 @@ ArrayType::ElementTypeGetter(JSContext* 
   args.rval().set(JS_GetReservedSlot(obj, SLOT_ELEMENT_T));
   MOZ_ASSERT(args.rval().isObject());
   return true;
 }
 
 bool
 ArrayType::LengthGetter(JSContext* cx, const JS::CallArgs& args)
 {
-  JSObject* obj = &args.thisv().toObject();
+  RootedObject obj(cx, &args.thisv().toObject());
 
   // This getter exists for both CTypes and CDatas of the ArrayType persuasion.
   // If we're dealing with a CData, get the CType from it.
-  if (CData::IsCData(obj))
+  if (CData::IsCDataMaybeUnwrap(&obj))
     obj = CData::GetCType(obj);
 
   args.rval().set(JS_GetReservedSlot(obj, SLOT_LENGTH));
   MOZ_ASSERT(args.rval().isNumber() || args.rval().isUndefined());
   return true;
 }
 
 bool
-ArrayType::Getter(JSContext* cx, HandleObject obj, HandleId idval, MutableHandleValue vp)
-{
+ArrayType::Getter(JSContext* cx, HandleObject obj, HandleId idval, MutableHandleValue vp,
+                  bool* handled)
+{
+  *handled = false;
+
   // This should never happen, but we'll check to be safe.
   if (!CData::IsCData(obj)) {
     RootedValue objVal(cx, ObjectValue(*obj));
     return IncompatibleThisProto(cx, "ArrayType property getter", objVal);
   }
 
   // Bail early if we're not an ArrayType. (This setter is present for all
   // CData, regardless of CType.)
@@ -5738,26 +5797,30 @@ ArrayType::Getter(JSContext* cx, HandleO
   }
   if (!ok) {
     return InvalidIndexError(cx, idval);
   }
   if (index >= length) {
     return InvalidIndexRangeError(cx, index, length);
   }
 
+  *handled = true;
+
   RootedObject baseType(cx, GetBaseType(typeObj));
   size_t elementSize = CType::GetSize(baseType);
   char* data = static_cast<char*>(CData::GetData(obj)) + elementSize * index;
   return ConvertToJS(cx, baseType, obj, data, false, false, vp);
 }
 
 bool
-ArrayType::Setter(JSContext* cx, HandleObject obj, HandleId idval, MutableHandleValue vp,
-                  ObjectOpResult& result)
-{
+ArrayType::Setter(JSContext* cx, HandleObject obj, HandleId idval, HandleValue vp,
+                  ObjectOpResult& result, bool* handled)
+{
+  *handled = false;
+
   // This should never happen, but we'll check to be safe.
   if (!CData::IsCData(obj)) {
     RootedValue objVal(cx, ObjectValue(*obj));
     return IncompatibleThisProto(cx, "ArrayType property setter", objVal);
   }
 
   // Bail early if we're not an ArrayType. (This setter is present for all
   // CData, regardless of CType.)
@@ -5781,33 +5844,35 @@ ArrayType::Setter(JSContext* cx, HandleO
   }
   if (!ok) {
     return InvalidIndexError(cx, idval);
   }
   if (index >= length) {
     return InvalidIndexRangeError(cx, index, length);
   }
 
+  *handled = true;
+
   RootedObject baseType(cx, GetBaseType(typeObj));
   size_t elementSize = CType::GetSize(baseType);
   char* data = static_cast<char*>(CData::GetData(obj)) + elementSize * index;
   if (!ImplicitConvert(cx, vp, baseType, data, ConversionType::Setter,
                        nullptr, nullptr, 0, typeObj, index))
     return false;
   return result.succeed();
 }
 
 bool
 ArrayType::AddressOfElement(JSContext* cx, unsigned argc, Value* vp)
 {
   CallArgs args = CallArgsFromVp(argc, vp);
   RootedObject obj(cx, JS_THIS_OBJECT(cx, vp));
   if (!obj)
     return false;
-  if (!CData::IsCData(obj)) {
+  if (!CData::IsCDataMaybeUnwrap(&obj)) {
     return IncompatibleThisProto(cx, "ArrayType.prototype.addressOfElement",
                                  args.thisv());
   }
 
   RootedObject typeObj(cx, CData::GetCType(obj));
   if (CType::GetTypeCode(typeObj) != TYPE_array) {
     return IncompatibleThisType(cx, "ArrayType.prototype.addressOfElement",
                                 "non-ArrayType CData", args.thisv());
@@ -6425,17 +6490,17 @@ StructType::FieldGetter(JSContext* cx, u
 {
   CallArgs args = CallArgsFromVp(argc, vp);
 
   if (!args.thisv().isObject()) {
     return IncompatibleThisProto(cx, "StructType property getter", args.thisv());
   }
 
   RootedObject obj(cx, &args.thisv().toObject());
-  if (!CData::IsCData(obj)) {
+  if (!CData::IsCDataMaybeUnwrap(&obj)) {
     return IncompatibleThisProto(cx, "StructType property getter", args.thisv());
   }
 
   JSObject* typeObj = CData::GetCType(obj);
   if (CType::GetTypeCode(typeObj) != TYPE_struct) {
     return IncompatibleThisType(cx, "StructType property getter",
                                 "non-StructType CData", args.thisv());
   }
@@ -6459,17 +6524,17 @@ StructType::FieldSetter(JSContext* cx, u
 {
   CallArgs args = CallArgsFromVp(argc, vp);
 
   if (!args.thisv().isObject()) {
     return IncompatibleThisProto(cx, "StructType property setter", args.thisv());
   }
 
   RootedObject obj(cx, &args.thisv().toObject());
-  if (!CData::IsCData(obj)) {
+  if (!CData::IsCDataMaybeUnwrap(&obj)) {
     return IncompatibleThisProto(cx, "StructType property setter", args.thisv());
   }
 
   RootedObject typeObj(cx, CData::GetCType(obj));
   if (CType::GetTypeCode(typeObj) != TYPE_struct) {
     return IncompatibleThisType(cx, "StructType property setter",
                                 "non-StructType CData", args.thisv());
   }
@@ -6492,17 +6557,17 @@ StructType::FieldSetter(JSContext* cx, u
 
 bool
 StructType::AddressOfField(JSContext* cx, unsigned argc, Value* vp)
 {
   CallArgs args = CallArgsFromVp(argc, vp);
   RootedObject obj(cx, JS_THIS_OBJECT(cx, vp));
   if (!obj)
     return false;
- if (!CData::IsCData(obj)) {
+ if (!CData::IsCDataMaybeUnwrap(&obj)) {
     return IncompatibleThisProto(cx, "StructType.prototype.addressOfField",
                                  args.thisv());
   }
 
   JSObject* typeObj = CData::GetCType(obj);
   if (CType::GetTypeCode(typeObj) != TYPE_struct) {
     return IncompatibleThisType(cx, "StructType.prototype.addressOfField",
                                 "non-StructType CData", args.thisv());
@@ -7025,17 +7090,17 @@ ConvertArgument(JSContext* cx,
 bool
 FunctionType::Call(JSContext* cx,
                    unsigned argc,
                    Value* vp)
 {
   CallArgs args = CallArgsFromVp(argc, vp);
   // get the callee object...
   RootedObject obj(cx, &args.callee());
-  if (!CData::IsCData(obj)) {
+  if (!CData::IsCDataMaybeUnwrap(&obj)) {
     return IncompatibleThisProto(cx, "FunctionType.prototype.call",
                                  args.calleev());
   }
 
   RootedObject typeObj(cx, CData::GetCType(obj));
   if (CType::GetTypeCode(typeObj) != TYPE_pointer) {
     return IncompatibleThisType(cx, "FunctionType.prototype.call",
                                 "non-PointerType CData", args.calleev());
@@ -7082,37 +7147,39 @@ FunctionType::Call(JSContext* cx,
   if (fninfo->mIsVariadic) {
     if (!fninfo->mFFITypes.resize(args.length())) {
       JS_ReportOutOfMemory(cx);
       return false;
     }
 
     RootedObject obj(cx);  // Could reuse obj instead of declaring a second
     RootedObject type(cx); // RootedObject, but readability would suffer.
+    RootedValue arg(cx);
 
     for (uint32_t i = argcFixed; i < args.length(); ++i) {
-      if (args[i].isPrimitive() ||
-          !CData::IsCData(obj = &args[i].toObject())) {
+      obj = args[i].isObject() ? &args[i].toObject() : nullptr;
+      if (!obj || !CData::IsCDataMaybeUnwrap(&obj)) {
         // Since we know nothing about the CTypes of the ... arguments,
         // they absolutely must be CData objects already.
         return VariadicArgumentTypeError(cx, i, args[i]);
       }
       type = CData::GetCType(obj);
       if (!type) {
         // These functions report their own errors.
         return false;
       }
       RootedValue typeVal(cx, ObjectValue(*type));
       type = PrepareType(cx, i, typeVal);
       if (!type) {
         return false;
       }
       // Relying on ImplicitConvert only for the limited purpose of
       // converting one CType to another (e.g., T[] to T*).
-      if (!ConvertArgument(cx, obj, i, args[i], type, &values[i], &strings)) {
+      arg = ObjectValue(*obj);
+      if (!ConvertArgument(cx, obj, i, arg, type, &values[i], &strings)) {
         return false;
       }
       fninfo->mFFITypes[i] = CType::GetFFIType(cx, type);
       if (!fninfo->mFFITypes[i]) {
         return false;
       }
     }
     if (!PrepareCIF(cx, fninfo))
@@ -7628,17 +7695,26 @@ CData::Create(JSContext* cx,
       memset(data, 0, size);
     else
       memcpy(data, source, size);
   }
 
   *buffer = data;
   JS_SetReservedSlot(dataObj, SLOT_DATA, PrivateValue(buffer));
 
-  return dataObj;
+  // If this is an array, wrap it in a proxy so we can intercept element
+  // gets/sets.
+
+  if (CType::GetTypeCode(typeObj) != TYPE_array)
+      return dataObj;
+
+  RootedValue priv(cx, ObjectValue(*dataObj));
+  ProxyOptions options;
+  options.setLazyProto(true);
+  return NewProxyObject(cx, &CDataArrayProxyHandler::singleton, priv, nullptr, options);
 }
 
 void
 CData::Finalize(JSFreeOp* fop, JSObject* obj)
 {
   // Delete our buffer, and the data it contains if we own it.
   Value slot = JS_GetReservedSlot(obj, SLOT_OWNS);
   if (slot.isUndefined())
@@ -7654,47 +7730,59 @@ CData::Finalize(JSFreeOp* fop, JSObject*
   if (owns)
     FreeOp::get(fop)->free_(*buffer);
   FreeOp::get(fop)->delete_(buffer);
 }
 
 JSObject*
 CData::GetCType(JSObject* dataObj)
 {
+  dataObj = MaybeUnwrapArrayWrapper(dataObj);
   MOZ_ASSERT(CData::IsCData(dataObj));
 
   Value slot = JS_GetReservedSlot(dataObj, SLOT_CTYPE);
   JSObject* typeObj = slot.toObjectOrNull();
   MOZ_ASSERT(CType::IsCType(typeObj));
   return typeObj;
 }
 
 void*
 CData::GetData(JSObject* dataObj)
 {
+  dataObj = MaybeUnwrapArrayWrapper(dataObj);
   MOZ_ASSERT(CData::IsCData(dataObj));
 
   Value slot = JS_GetReservedSlot(dataObj, SLOT_DATA);
 
   void** buffer = static_cast<void**>(slot.toPrivate());
   MOZ_ASSERT(buffer);
   MOZ_ASSERT(*buffer);
   return *buffer;
 }
 
 bool
 CData::IsCData(JSObject* obj)
 {
+  // Assert we don't have an array wrapper.
+  MOZ_ASSERT(MaybeUnwrapArrayWrapper(obj) == obj);
+
   return JS_GetClass(obj) == &sCDataClass;
 }
 
 bool
+CData::IsCDataMaybeUnwrap(MutableHandleObject obj)
+{
+  obj.set(MaybeUnwrapArrayWrapper(obj));
+  return IsCData(obj);
+}
+
+bool
 CData::IsCData(HandleValue v)
 {
-  return v.isObject() && CData::IsCData(&v.toObject());
+  return v.isObject() && CData::IsCData(MaybeUnwrapArrayWrapper(&v.toObject()));
 }
 
 bool
 CData::IsCDataProto(JSObject* obj)
 {
   return JS_GetClass(obj) == &sCDataProtoClass;
 }
 
@@ -7723,17 +7811,17 @@ CData::Address(JSContext* cx, unsigned a
   CallArgs args = CallArgsFromVp(argc, vp);
   if (args.length() != 0) {
     return ArgumentLengthError(cx, "CData.prototype.address", "no", "s");
   }
 
   RootedObject obj(cx, JS_THIS_OBJECT(cx, vp));
   if (!obj)
     return false;
-  if (!IsCData(obj)) {
+  if (!IsCDataMaybeUnwrap(&obj)) {
     return IncompatibleThisProto(cx, "CData.prototype.address", args.thisv());
   }
 
   RootedObject typeObj(cx, CData::GetCType(obj));
   RootedObject pointerType(cx, PointerType::CreateInternal(cx, typeObj));
   if (!pointerType)
     return false;
 
@@ -7753,20 +7841,24 @@ CData::Address(JSContext* cx, unsigned a
 bool
 CData::Cast(JSContext* cx, unsigned argc, Value* vp)
 {
   CallArgs args = CallArgsFromVp(argc, vp);
   if (args.length() != 2) {
     return ArgumentLengthError(cx, "ctypes.cast", "two", "s");
   }
 
-  if (args[0].isPrimitive() || !CData::IsCData(&args[0].toObject())) {
+  RootedObject sourceData(cx);
+  if (args[0].isObject()) {
+    sourceData = &args[0].toObject();
+  }
+
+  if (!sourceData || !CData::IsCDataMaybeUnwrap(&sourceData)) {
     return ArgumentTypeMismatch(cx, "first ", "ctypes.cast", "a CData");
   }
-  RootedObject sourceData(cx, &args[0].toObject());
   RootedObject sourceType(cx, CData::GetCType(sourceData));
 
   if (args[1].isPrimitive() || !CType::IsCType(&args[1].toObject())) {
     return ArgumentTypeMismatch(cx, "second ", "ctypes.cast", "a CType");
   }
 
   RootedObject targetType(cx, &args[1].toObject());
   size_t targetSize;
@@ -7828,17 +7920,17 @@ ReadStringCommon(JSContext* cx, InflateU
   if (args.length() != 0) {
     return ArgumentLengthError(cx, funName, "no", "s");
   }
 
   RootedObject obj(cx, JS_THIS_OBJECT(cx, vp));
   if (!obj) {
     return IncompatibleThisProto(cx, funName, args.thisv());
   }
-  if (!CData::IsCData(obj)) {
+  if (!CData::IsCDataMaybeUnwrap(&obj)) {
       if (!CDataFinalizer::IsCDataFinalizer(obj)) {
           return IncompatibleThisProto(cx, funName, args.thisv());
       }
 
       CDataFinalizer::Private* p = (CDataFinalizer::Private*)
                                    JS_GetPrivate(obj);
       if (!p) {
           return EmptyFinalizerCallError(cx, funName);
@@ -7849,17 +7941,17 @@ ReadStringCommon(JSContext* cx, InflateU
           return IncompatibleThisProto(cx, funName, args.thisv());
       }
 
       if (dataVal.isPrimitive()) {
           return IncompatibleThisProto(cx, funName, args.thisv());
       }
 
       obj = dataVal.toObjectOrNull();
-      if (!obj || !CData::IsCData(obj)) {
+      if (!obj || !CData::IsCDataMaybeUnwrap(&obj)) {
           return IncompatibleThisProto(cx, funName, args.thisv());
       }
   }
 
   // Make sure we are a pointer to, or an array of, an 8-bit or 16-bit
   // character or integer type.
   JSObject* baseType;
   JSObject* typeObj = CData::GetCType(obj);
@@ -7973,20 +8065,20 @@ CData::GetSourceString(JSContext* cx, Ha
 bool
 CData::ToSource(JSContext* cx, unsigned argc, Value* vp)
 {
   CallArgs args = CallArgsFromVp(argc, vp);
   if (args.length() != 0) {
     return ArgumentLengthError(cx, "CData.prototype.toSource", "no", "s");
   }
 
-  JSObject* obj = JS_THIS_OBJECT(cx, vp);
+  RootedObject obj(cx, JS_THIS_OBJECT(cx, vp));
   if (!obj)
     return false;
-  if (!CData::IsCData(obj) && !CData::IsCDataProto(obj)) {
+  if (!CData::IsCDataMaybeUnwrap(&obj) && !CData::IsCDataProto(obj)) {
     return IncompatibleThisProto(cx, "CData.prototype.toSource",
                                  InformalValueTypeName(args.thisv()));
   }
 
   JSString* result;
   if (CData::IsCData(obj)) {
     RootedObject typeObj(cx, CData::GetCType(obj));
     void* data = CData::GetData(obj);
@@ -8186,23 +8278,23 @@ CDataFinalizer::Construct(JSContext* cx,
     return ArgumentLengthError(cx, "CDataFinalizer constructor", "two", "s");
   }
 
   JS::HandleValue valCodePtr = args[1];
   if (!valCodePtr.isObject()) {
     return TypeError(cx, "_a CData object_ of a function pointer type",
                      valCodePtr);
   }
-  JSObject* objCodePtr = &valCodePtr.toObject();
+  RootedObject objCodePtr(cx, &valCodePtr.toObject());
 
   //Note: Using a custom argument formatter here would be awkward (requires
   //a destructor just to uninstall the formatter).
 
   // 2. Extract argument type of |objCodePtr|
-  if (!CData::IsCData(objCodePtr)) {
+  if (!CData::IsCDataMaybeUnwrap(&objCodePtr)) {
     return TypeError(cx, "a _CData_ object of a function pointer type",
                      valCodePtr);
   }
   RootedObject objCodePtrType(cx, CData::GetCType(objCodePtr));
   RootedValue valCodePtrType(cx, ObjectValue(*objCodePtrType));
   MOZ_ASSERT(objCodePtrType);
 
   TypeCode typCodePtr = CType::GetTypeCode(objCodePtrType);
@@ -8281,18 +8373,18 @@ CDataFinalizer::Construct(JSContext* cx,
     return false;
   }
 
   // If our argument is a CData, it holds a type.
   // This is the type that we should capture, not that
   // of the function, which may be less precise.
   JSObject* objBestArgType = objArgType;
   if (valData.isObject()) {
-    JSObject* objData = &valData.toObject();
-    if (CData::IsCData(objData)) {
+    RootedObject objData(cx, &valData.toObject());
+    if (CData::IsCDataMaybeUnwrap(&objData)) {
       objBestArgType = CData::GetCType(objData);
       size_t sizeBestArg;
       if (!CType::GetSafeSize(objBestArgType, &sizeBestArg)) {
         MOZ_CRASH("object with unknown size");
       }
       if (sizeBestArg != sizeArg) {
         return FinalizerSizeError(cx, objCodePtrType, valData);
       }
@@ -8727,40 +8819,40 @@ Int64::IsInt64(JSObject* obj)
 {
   return JS_GetClass(obj) == &sInt64Class;
 }
 
 bool
 Int64::ToString(JSContext* cx, unsigned argc, Value* vp)
 {
   CallArgs args = CallArgsFromVp(argc, vp);
-  JSObject* obj = JS_THIS_OBJECT(cx, vp);
+  RootedObject obj(cx, JS_THIS_OBJECT(cx, vp));
   if (!obj)
     return false;
   if (!Int64::IsInt64(obj)) {
-    if (!CData::IsCData(obj)) {
+    if (!CData::IsCDataMaybeUnwrap(&obj)) {
       return IncompatibleThisProto(cx, "Int64.prototype.toString",
                                    InformalValueTypeName(args.thisv()));
     }
     return IncompatibleThisType(cx, "Int64.prototype.toString",
                                 "non-Int64 CData");
   }
 
   return Int64Base::ToString(cx, obj, args, false);
 }
 
 bool
 Int64::ToSource(JSContext* cx, unsigned argc, Value* vp)
 {
   CallArgs args = CallArgsFromVp(argc, vp);
-  JSObject* obj = JS_THIS_OBJECT(cx, vp);
+  RootedObject obj(cx, JS_THIS_OBJECT(cx, vp));
   if (!obj)
     return false;
   if (!Int64::IsInt64(obj)) {
-    if (!CData::IsCData(obj)) {
+    if (!CData::IsCDataMaybeUnwrap(&obj)) {
       return IncompatibleThisProto(cx, "Int64.prototype.toSource",
                                    InformalValueTypeName(args.thisv()));
     }
     return IncompatibleThisType(cx, "Int64.prototype.toSource",
                                 "non-Int64 CData");
   }
 
   return Int64Base::ToSource(cx, obj, args, false);
@@ -8911,40 +9003,40 @@ UInt64::IsUInt64(JSObject* obj)
 {
   return JS_GetClass(obj) == &sUInt64Class;
 }
 
 bool
 UInt64::ToString(JSContext* cx, unsigned argc, Value* vp)
 {
   CallArgs args = CallArgsFromVp(argc, vp);
-  JSObject* obj = JS_THIS_OBJECT(cx, vp);
+  RootedObject obj(cx, JS_THIS_OBJECT(cx, vp));
   if (!obj)
     return false;
   if (!UInt64::IsUInt64(obj)) {
-    if (!CData::IsCData(obj)) {
+    if (!CData::IsCDataMaybeUnwrap(&obj)) {
       return IncompatibleThisProto(cx, "UInt64.prototype.toString",
                                    InformalValueTypeName(args.thisv()));
     }
     return IncompatibleThisType(cx, "UInt64.prototype.toString",
                                 "non-UInt64 CData");
   }
 
   return Int64Base::ToString(cx, obj, args, true);
 }
 
 bool
 UInt64::ToSource(JSContext* cx, unsigned argc, Value* vp)
 {
   CallArgs args = CallArgsFromVp(argc, vp);
-  JSObject* obj = JS_THIS_OBJECT(cx, vp);
+  RootedObject obj(cx, JS_THIS_OBJECT(cx, vp));
   if (!obj)
     return false;
   if (!UInt64::IsUInt64(obj)) {
-    if (!CData::IsCData(obj)) {
+    if (!CData::IsCDataMaybeUnwrap(&obj)) {
       return IncompatibleThisProto(cx, "UInt64.prototype.toSource",
                                    InformalValueTypeName(args.thisv()));
     }
     return IncompatibleThisType(cx, "UInt64.prototype.toSource",
                                 "non-UInt64 CData");
   }
 
   return Int64Base::ToSource(cx, obj, args, true);
--- a/js/src/ctypes/CTypes.h
+++ b/js/src/ctypes/CTypes.h
@@ -512,16 +512,17 @@ namespace CClosure {
 
 namespace CData {
   JSObject* Create(JSContext* cx, HandleObject typeObj, HandleObject refObj,
     void* data, bool ownResult);
 
   JSObject* GetCType(JSObject* dataObj);
   void* GetData(JSObject* dataObj);
   bool IsCData(JSObject* obj);
+  bool IsCDataMaybeUnwrap(MutableHandleObject obj);
   bool IsCData(HandleValue v);
   bool IsCDataProto(JSObject* obj);
 
   // Attached by JSAPI as the function 'ctypes.cast'
   MOZ_MUST_USE bool Cast(JSContext* cx, unsigned argc, Value* vp);
   // Attached by JSAPI as the function 'ctypes.getRuntime'
   MOZ_MUST_USE bool GetRuntime(JSContext* cx, unsigned argc, Value* vp);
 } // namespace CData
--- a/js/src/jit-test/tests/debug/Object-displayName-01.js
+++ b/js/src/jit-test/tests/debug/Object-displayName-01.js
@@ -1,12 +1,10 @@
 // Debugger.Object.prototype.displayName
 
-load(libdir + 'nightly-only.js');
-
 var g = newGlobal();
 var dbg = Debugger(g);
 var name;
 dbg.onDebuggerStatement = function (frame) { name = frame.callee.displayName; };
 
 g.eval("(function f() { debugger; })();");
 assertEq(name, "f");
 g.eval("(function () { debugger; })();");
@@ -14,12 +12,10 @@ assertEq(name, undefined);
 g.eval("Function('debugger;')();");
 assertEq(name, "anonymous");
 g.eval("var f = function() { debugger; }; f()");
 assertEq(name, "f");
 g.eval("var a = {}; a.f = function() { debugger; }; a.f()");
 assertEq(name, "a.f");
 g.eval("(async function grondo() { debugger; })();");
 assertEq(name, "grondo");
-nightlyOnly(g.SyntaxError, () => {
-  g.eval("(async function* estux() { debugger; })().next();");
-  assertEq(name, "estux");
-})
+g.eval("(async function* estux() { debugger; })().next();");
+assertEq(name, "estux");
--- a/js/src/jit-test/tests/debug/Object-environment-02.js
+++ b/js/src/jit-test/tests/debug/Object-environment-02.js
@@ -1,12 +1,10 @@
 // The .environment of a function Debugger.Object is an Environment object.
 
-load(libdir + 'nightly-only.js');
-
 var g = newGlobal()
 var dbg = new Debugger;
 var gDO = dbg.addDebuggee(g);
 
 function check(expr) {
   print("checking " + uneval(expr));
   let completion = gDO.executeInGlobal(expr);
   if (completion.throw)
@@ -16,11 +14,9 @@ function check(expr) {
 
 g.eval('function j(a) { }');
 
 check('j');
 check('(() => { })');
 check('(function f() { })');
 check('(function* g() { })');
 check('(async function m() { })');
-nightlyOnly(g.SyntaxError, () => {
-  check('(async function* n() { })');
-});
+check('(async function* n() { })');
--- a/js/src/jit-test/tests/debug/Object-name-01.js
+++ b/js/src/jit-test/tests/debug/Object-name-01.js
@@ -1,21 +1,17 @@
 // Debugger.Object.prototype.name
 
-load(libdir + 'nightly-only.js');
-
 var g = newGlobal();
 var dbg = Debugger(g);
 var name;
 dbg.onDebuggerStatement = function (frame) { name = frame.callee.name; };
 
 g.eval("(function f() { debugger; })();");
 assertEq(name, "f");
 g.eval("(function () { debugger; })();");
 assertEq(name, undefined);
 g.eval("Function('debugger;')();");
 assertEq(name, "anonymous");
 g.eval("(async function grondo() { debugger; })();");
 assertEq(name, "grondo");
-nightlyOnly(g.SyntaxError, () => {
-  g.eval("(async function* estux() { debugger; })().next();");
-  assertEq(name, "estux");
-});
+g.eval("(async function* estux() { debugger; })().next();");
+assertEq(name, "estux");
--- a/js/src/jit-test/tests/debug/Object-parameterNames.js
+++ b/js/src/jit-test/tests/debug/Object-parameterNames.js
@@ -1,10 +1,9 @@
 load(libdir + 'array-compare.js');
-load(libdir + 'nightly-only.js');
 
 var g = newGlobal();
 var dbg = new Debugger;
 var gDO = dbg.addDebuggee(g);
 var hits = 0;
 
 function check(expr, expected) {
   print("checking " + uneval(expr));
@@ -25,11 +24,9 @@ check('(function (x) {})', ["x"]);
 check('(function (a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z) {})',
       ["a","b","c","d","e","f","g","h","i","j","k","l","m",
        "n","o","p","q","r","s","t","u","v","w","x","y","z"]);
 check('(function (a, [b, c], {d, e:f}) { })',
       ["a", undefined, undefined]);
 check('({a:1})', undefined);
 check('Math.atan2', [undefined, undefined]);
 check('(async function (a, b, c) {})', ["a", "b", "c"]);
-nightlyOnly(g.SyntaxError, () => {
-  check('(async function* (d, e, f) {})', ["d", "e", "f"]);
-});
+check('(async function* (d, e, f) {})', ["d", "e", "f"]);
--- a/js/src/jit-test/tests/debug/Object-script.js
+++ b/js/src/jit-test/tests/debug/Object-script.js
@@ -1,10 +1,8 @@
-load(libdir + 'nightly-only.js');
-
 var g = newGlobal();
 var dbg = new Debugger;
 var gDO = dbg.addDebuggee(g);
 
 function check(expr, expected) {
   print("checking " + uneval(expr) + ", expecting " +
         (expected ? "script" : "no script"));
 
@@ -17,13 +15,11 @@ function check(expr, expected) {
     assertEq(val.script instanceof Debugger.Script, true);
   else
     assertEq(val.script, undefined);
 }
 
 check('(function g(){})', true);
 check('(function* h() {})', true);
 check('(async function j() {})', true);
-nightlyOnly(g.SyntaxError, () => {
-  check('(async function* k() {})', true);
-});
+check('(async function* k() {})', true);
 check('({})', false);
 check('Math.atan2', false);
--- a/js/src/jit-test/tests/debug/isAsyncFunction-isGeneratorFunction.js
+++ b/js/src/jit-test/tests/debug/isAsyncFunction-isGeneratorFunction.js
@@ -1,13 +1,11 @@
 // Debugger.Script.prototype.isAsyncFunction, Debugger.Object.prototype.isAsyncFunction,
 // Debugger.Script.prototype.isGeneratorFunction, Debugger.Object.prototype.isGeneratorFunction
 
-load(libdir + 'nightly-only.js');
-
 var g = newGlobal();
 var dbg = new Debugger();
 var gDO = dbg.addDebuggee(g);
 g.non_debuggee = function non_debuggee () {}
 
 function checkExpr(expr, { isAsync, isGenerator })
 {
   print("Evaluating: " + uneval(expr));
@@ -26,29 +24,25 @@ function checkExpr(expr, { isAsync, isGe
   }
 }
 
 checkExpr('({})', { isAsync: undefined, isGenerator: undefined });
 checkExpr('non_debuggee', { isAsync: undefined, isGenerator: undefined });
 checkExpr('(function(){})', { isAsync: false, isGenerator: false });
 checkExpr('(function*(){})', { isAsync: false, isGenerator: true });
 checkExpr('(async function snerf(){})', { isAsync: true, isGenerator: false });
-nightlyOnly(g.SyntaxError, () => {
-  checkExpr('(async function* omlu(){})', { isAsync: true, isGenerator: true });
-});
+checkExpr('(async function* omlu(){})', { isAsync: true, isGenerator: true });
 
 checkExpr('new Function("1+2")', { isAsync: false, isGenerator: false });
 checkExpr('Object.getPrototypeOf(function*(){}).constructor("1+2")',
           { isAsync: false, isGenerator: true });
 checkExpr('Object.getPrototypeOf(async function(){}).constructor("1+2")',
           { isAsync: true, isGenerator: false });
-nightlyOnly(g.SyntaxError, () => {
-  checkExpr('Object.getPrototypeOf(async function*(){}).constructor("1+2")',
-            { isAsync: true, isGenerator: true });
-});
+checkExpr('Object.getPrototypeOf(async function*(){}).constructor("1+2")',
+          { isAsync: true, isGenerator: true });
 
 // Check eval scripts.
 function checkFrame(expr, type)
 {
   var log = '';
   dbg.onDebuggerStatement = function(frame) {
     log += 'd';
     assertEq(frame.type, type);
deleted file mode 100644
--- a/js/src/jit-test/tests/tracelogger/bug1174542.js
+++ /dev/null
@@ -1,6 +0,0 @@
-var du = new Debugger();
-if (typeof du.setupTraceLogger === "function")
-    du.setupTraceLogger({Scripts: true});
-(function() {
-    for (var i = 0; i < 15; ++i) {}
-})();
deleted file mode 100644
--- a/js/src/jit-test/tests/tracelogger/bug1231170.js
+++ /dev/null
@@ -1,3 +0,0 @@
-var du = new Debugger();
-if (typeof du.drainTraceLogger === "function")
-    du.drainTraceLogger();
deleted file mode 100644
--- a/js/src/jit-test/tests/tracelogger/bug1257194.js
+++ /dev/null
@@ -1,11 +0,0 @@
-// |jit-test| allow-oom
-
-if (!('oomAfterAllocations' in this))
-  quit();
-
-var du = new Debugger();
-if (typeof du.drainTraceLogger == "function") {
-    var obj = du.drainTraceLogger();
-    oomAfterAllocations(1);
-    du.drainTraceLogger().length;
-}
deleted file mode 100644
--- a/js/src/jit-test/tests/tracelogger/bug1266649.js
+++ /dev/null
@@ -1,10 +0,0 @@
-
-var du = new Debugger();
-if (typeof du.setupTraceLogger === "function" &&
-    typeof oomTest === 'function')
-{
-    du.setupTraceLogger({
-        Scripts: true
-    })
-    oomTest(() => function(){});
-}
deleted file mode 100644
--- a/js/src/jit-test/tests/tracelogger/bug1282743.js
+++ /dev/null
@@ -1,14 +0,0 @@
-
-du = new Debugger();
-if (typeof du.setupTraceLogger === "function" &&
-    typeof oomTest === 'function')
-{
-    du.setupTraceLogger({Scripts: true});
-    for (var idx = 0; idx < 1; idx++) {
-      oomTest(function() {
-          m = parseModule("x");
-          m.declarationInstantiation();
-          m.evaluation();
-      })
-    }
-}
deleted file mode 100644
--- a/js/src/jit-test/tests/tracelogger/bug1298541.js
+++ /dev/null
@@ -1,41 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-// Flags: -e "version(170)"
-
-//-----------------------------------------------------------------------------
-
-version(170)
-
-//-----------------------------------------------------------------------------
-
-var du = new Debugger();
-if (typeof du.drainTraceLoggerScriptCalls == "function") {
-    du.setupTraceLoggerScriptCalls();
-
-    du.startTraceLogger();
-    test();
-    du.endTraceLogger();
-
-    var objs = du.drainTraceLoggerScriptCalls();
-    var scripts = 0;
-    var stops = 0;
-    for (var i = 0; i < objs.length; i++) {
-        if (objs[i].logType == "Script") {
-            scripts++;
-        } else if (objs[i].logType == "Stop") {
-            stops++;
-        } else {
-            throw "We shouldn't receive non-script events.";
-        }
-    }
-    assertEq(scripts, stops + 1);
-    // "+ 1" because we get a start for bug1298541.js:1, but not the stop.
-}
-
-function test()
-{
-  for (var i in (function(){ for (var j=0;j<4;++j) { yield ""; } })());
-}
deleted file mode 100644
--- a/js/src/jit-test/tests/tracelogger/bug1300515.js
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-function test1() {
-    test();
-}
-
-function test() {
-    var du = new Debugger();
-    du.setupTraceLoggerScriptCalls();
-    du.startTraceLogger();
-}
-
-var du = new Debugger();
-if (typeof du.setupTraceLoggerScriptCalls == "function")
-    test1();
deleted file mode 100644
--- a/js/src/jit-test/tests/tracelogger/bug1302417.js
+++ /dev/null
@@ -1,20 +0,0 @@
-
-if (!('oomTest' in this))
-    quit();
-
-if (typeof new Debugger().setupTraceLoggerScriptCalls == "function") {
-    lfLogBuffer = `
-        var du = new Debugger;
-        du.setupTraceLoggerScriptCalls();
-        startTraceLogger();
-    `;
-    loadFile(lfLogBuffer);
-    function loadFile(lfVarx) {
-        oomTest(function() {
-                m = parseModule(lfVarx);
-                m.declarationInstantiation();
-                m.evaluation();
-        })
-    }
-}
-
--- a/js/src/jit-test/tests/tracelogger/bug1304641.js
+++ b/js/src/jit-test/tests/tracelogger/bug1304641.js
@@ -1,13 +1,8 @@
-
-var du = new Debugger();
-if (typeof du.startTraceLogger === "function") {
-    var failed = false;
-    try {
-        newGlobal().startTraceLogger();
-        print("z");
-    } catch (e) {
-        failed = true;
-    }
-
-    assertEq(failed, true);
+var failed = false;
+try {
+    newGlobal().startTraceLogger();
+    print("z");
+} catch (e) {
+    failed = true;
 }
+assertEq(failed, true);
deleted file mode 100644
--- a/js/src/jit-test/tests/tracelogger/drainTraceLogger.js
+++ /dev/null
@@ -1,88 +0,0 @@
-function TestDrainTraceLoggerInvariants(obj) {
-    var scripts = 0;
-    var stops = 0;
-    for (var i = 0; i < objs.length; i++) {
-        if (objs[i].logType == "Script") {
-            scripts++;
-            assertEq("fileName" in objs[i], true);
-            assertEq("lineNumber" in objs[i], true);
-            assertEq("columnNumber" in objs[i], true);
-        } else if (objs[i].logType == "Stop") {
-            stops++;
-        } else {
-            assertEq(true, false);
-        }
-    }
-    assertEq(scripts, stops + 1);
-    // "+ 1" because we get a start for drainTraceLogger.js:1, but not the stop.
-}
-
-function GetMaxScriptDepth(obj) {
-    var max_depth = 0;
-    var depth = 0;
-    for (var i = 0; i < objs.length; i++) {
-        if (objs[i].logType == "Stop")
-            depth--;
-        else {
-            depth++;
-            if (depth > max_depth)
-                max_depth = depth;
-        }
-    }
-    return max_depth;
-}
-
-function foo1() {
-    foo2();
-}
-function foo2() {
-
-}
-
-var du = new Debugger();
-if (typeof du.drainTraceLoggerScriptCalls == "function") {
-    // Test normal setup.
-    du = new Debugger();
-    du.setupTraceLoggerScriptCalls();
-
-    du.startTraceLogger();
-    du.endTraceLogger();
-
-    var objs = du.drainTraceLoggerScriptCalls();
-    TestDrainTraceLoggerInvariants(objs);
-
-    // Test basic script.
-    for (var i=0; i<20; i++)
-        foo1();
-
-    du = new Debugger();
-    du.setupTraceLoggerScriptCalls();
-
-    du.startTraceLogger();
-    foo1();
-    du.endTraceLogger();
-
-    var objs = du.drainTraceLoggerScriptCalls();
-    TestDrainTraceLoggerInvariants(objs);
-    assertEq(3, GetMaxScriptDepth(objs), "drainTraceLogger.js:0 + foo1 + foo2");
-    assertEq(5, objs.length, "drainTraceLogger.js:0 + foo1 + foo2 + stop + stop");
-
-    // Test basic script.
-    for (var i=0; i<20; i++)
-        foo1();
-
-    du = new Debugger();
-    du.setupTraceLoggerScriptCalls();
-
-    du.startTraceLogger();
-    for (var i=0; i<100; i++) {
-        foo1();
-    }
-    du.endTraceLogger();
-
-    var objs = du.drainTraceLoggerScriptCalls();
-    TestDrainTraceLoggerInvariants(objs);
-    assertEq(3, GetMaxScriptDepth(objs), "drainTraceLogger.js:0 + foo1 + foo2");
-    assertEq(4*100 + 1, objs.length);
-    assertEq(1 + 4*100, objs.length, "drainTraceLogger.js:0 + 4 * ( foo1 + foo2 + stop + stop )");
-}
deleted file mode 100644
--- a/js/src/jit-test/tests/tracelogger/setupTraceLogger.js
+++ /dev/null
@@ -1,70 +0,0 @@
-
-var du = new Debugger();
-if (typeof du.setupTraceLogger == "function") {
-
-    // Try enabling.
-    assertEq(du.setupTraceLogger({
-        Scripts: true
-    }), true);
-
-    // No fail on re-enabling.
-    assertEq(du.setupTraceLogger({
-        Scripts: true
-    }), true);
-
-    // Try disabling.
-    assertEq(du.setupTraceLogger({
-        Scripts: false
-    }), true);
-
-    // No fail on re-disabling.
-    assertEq(du.setupTraceLogger({
-        Scripts: false
-    }), true);
-
-    // Throw exception if TraceLog item to report isn't found.
-    var success = du.setupTraceLogger({
-        Scripts: false,
-        Test: true
-    });
-    assertEq(success, false);
-
-    // SetupTraceLogger only enables individual items,
-    // when all items can be toggled.
-    du.startTraceLogger();
-    var obj = du.drainTraceLogger();
-    du.setupTraceLogger({
-        Scripts: true,
-        Test: true,
-    });
-    assertEq(du.drainTraceLogger().length, 0);
-    du.endTraceLogger();
-
-    // Expects an object as first argument.
-    succes = du.setupTraceLogger("blaat");
-    assertEq(succes, false);
-
-    // Expects an object as first argument.
-    succes = du.setupTraceLogger("blaat");
-    assertEq(succes, false);
-
-    // Expects an object as first argument.
-    failed = false;
-    try {
-        du.setupTraceLogger();
-    } catch (e) {
-        failed = true;
-    }
-    assertEq(failed, true);
-
-    // No problem with added to many arguments.
-    succes = du.setupTraceLogger({}, "test");
-    assertEq(succes, true);
-}
-
-var du2 = new Debugger();
-if (typeof du2.setupTraceLoggerForTraces == "function") {
-    du2.setupTraceLoggerForTraces({});
-    du2.setupTraceLoggerForTraces("test");
-    du2.setupTraceLoggerForTraces({}, "test");
-}
--- a/js/src/jit/mips32/MacroAssembler-mips32-inl.h
+++ b/js/src/jit/mips32/MacroAssembler-mips32-inl.h
@@ -309,18 +309,19 @@ MacroAssembler::neg64(Register64 reg)
     ma_negu(reg.low, reg.low);
     as_addu(reg.high, reg.high, ScratchRegister);
     ma_negu(reg.high, reg.high);
 }
 
 void
 MacroAssembler::mulBy3(Register src, Register dest)
 {
-    as_addu(dest, src, src);
-    as_addu(dest, dest, src);
+    MOZ_ASSERT(src != ScratchRegister);
+    as_addu(ScratchRegister, src, src);
+    as_addu(dest, ScratchRegister, src);
 }
 
 void
 MacroAssembler::inc64(AbsoluteAddress dest)
 {
     ma_li(ScratchRegister, Imm32((int32_t)dest.addr));
     as_lw(SecondScratchReg, ScratchRegister, 0);
 
--- a/js/src/jit/mips64/MacroAssembler-mips64-inl.h
+++ b/js/src/jit/mips64/MacroAssembler-mips64-inl.h
@@ -286,18 +286,19 @@ MacroAssembler::mul64(const Operand& src
     } else {
         mul64(Register64(src.toReg()), dest, temp);
     }
 }
 
 void
 MacroAssembler::mulBy3(Register src, Register dest)
 {
-    as_daddu(dest, src, src);
-    as_daddu(dest, dest, src);
+    MOZ_ASSERT(src != ScratchRegister);
+    as_daddu(ScratchRegister, src, src);
+    as_daddu(dest, ScratchRegister, src);
 }
 
 void
 MacroAssembler::inc64(AbsoluteAddress dest)
 {
     ma_li(ScratchRegister, ImmWord(uintptr_t(dest.addr)));
     as_ld(SecondScratchReg, ScratchRegister, 0);
     as_daddiu(SecondScratchReg, SecondScratchReg, 1);
--- a/js/src/jswrapper.h
+++ b/js/src/jswrapper.h
@@ -37,41 +37,22 @@ class MOZ_STACK_CLASS WrapperOptions : p
         *proto_ = protoArg;
         return *this;
     }
 
   private:
     mozilla::Maybe<JS::RootedObject> proto_;
 };
 
-/*
- * A wrapper is a proxy with a target object to which it generally forwards
- * operations, but may restrict access to certain operations or augment those
- * operations in various ways.
- *
- * A wrapper can be "unwrapped" in C++, exposing the underlying object.
- * Callers should be careful to avoid unwrapping security wrappers in the wrong
- * context.
- *
- * Important: If you add a method implementation here, you probably also need
- * to add an override in CrossCompartmentWrapper. If you don't, you risk
- * compartment mismatches. See bug 945826 comment 0.
- */
-class JS_FRIEND_API(Wrapper) : public BaseProxyHandler
+// Base class for proxy handlers that want to forward all operations to an
+// object stored in the proxy's private slot.
+class JS_FRIEND_API(ForwardingProxyHandler) : public BaseProxyHandler
 {
-    unsigned mFlags;
-
   public:
-    explicit constexpr Wrapper(unsigned aFlags, bool aHasPrototype = false,
-                                   bool aHasSecurityPolicy = false)
-      : BaseProxyHandler(&family, aHasPrototype, aHasSecurityPolicy),
-        mFlags(aFlags)
-    { }
-
-    virtual bool finalizeInBackground(const Value& priv) const override;
+    using BaseProxyHandler::BaseProxyHandler;
 
     /* Standard internal methods. */
     virtual bool getOwnPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
                                           MutableHandle<PropertyDescriptor> desc) const override;
     virtual bool defineProperty(JSContext* cx, HandleObject proxy, HandleId id,
                                 Handle<PropertyDescriptor> desc,
                                 ObjectOpResult& result) const override;
     virtual bool ownPropertyKeys(JSContext* cx, HandleObject proxy,
@@ -116,19 +97,45 @@ class JS_FRIEND_API(Wrapper) : public Ba
     virtual const char* className(JSContext* cx, HandleObject proxy) const override;
     virtual JSString* fun_toString(JSContext* cx, HandleObject proxy,
                                    bool isToSource) const override;
     virtual RegExpShared* regexp_toShared(JSContext* cx, HandleObject proxy) const override;
     virtual bool boxedValue_unbox(JSContext* cx, HandleObject proxy,
                                   MutableHandleValue vp) const override;
     virtual bool isCallable(JSObject* obj) const override;
     virtual bool isConstructor(JSObject* obj) const override;
+};
+
+/*
+ * A wrapper is a proxy with a target object to which it generally forwards
+ * operations, but may restrict access to certain operations or augment those
+ * operations in various ways.
+ *
+ * A wrapper can be "unwrapped" in C++, exposing the underlying object.
+ * Callers should be careful to avoid unwrapping security wrappers in the wrong
+ * context.
+ *
+ * Important: If you add a method implementation here, you probably also need
+ * to add an override in CrossCompartmentWrapper. If you don't, you risk
+ * compartment mismatches. See bug 945826 comment 0.
+ */
+class JS_FRIEND_API(Wrapper) : public ForwardingProxyHandler
+{
+    unsigned mFlags;
+
+  public:
+    explicit constexpr Wrapper(unsigned aFlags, bool aHasPrototype = false,
+                               bool aHasSecurityPolicy = false)
+      : ForwardingProxyHandler(&family, aHasPrototype, aHasSecurityPolicy),
+        mFlags(aFlags)
+    { }
+
+    virtual bool finalizeInBackground(const Value& priv) const override;
     virtual JSObject* weakmapKeyDelegate(JSObject* proxy) const override;
 
-  public:
     using BaseProxyHandler::Action;
 
     enum Flags {
         CROSS_COMPARTMENT = 1 << 0,
         LAST_USED_FLAG = CROSS_COMPARTMENT
     };
 
     static JSObject* New(JSContext* cx, JSObject* obj, const Wrapper* handler,
--- a/js/src/proxy/Wrapper.cpp
+++ b/js/src/proxy/Wrapper.cpp
@@ -38,144 +38,150 @@ Wrapper::finalizeInBackground(const Valu
         wrappedKind = wrapped->allocKindForTenure(rt->gc.nursery());
     } else {
         wrappedKind = wrapped->asTenured().getAllocKind();
     }
     return IsBackgroundFinalized(wrappedKind);
 }
 
 bool
-Wrapper::getOwnPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
-                                  MutableHandle<PropertyDescriptor> desc) const
+ForwardingProxyHandler::getOwnPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
+                                                 MutableHandle<PropertyDescriptor> desc) const
 {
     assertEnteredPolicy(cx, proxy, id, GET | SET | GET_PROPERTY_DESCRIPTOR);
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     return GetOwnPropertyDescriptor(cx, target, id, desc);
 }
 
 bool
-Wrapper::defineProperty(JSContext* cx, HandleObject proxy, HandleId id,
-                        Handle<PropertyDescriptor> desc, ObjectOpResult& result) const
+ForwardingProxyHandler::defineProperty(JSContext* cx, HandleObject proxy, HandleId id,
+                                       Handle<PropertyDescriptor> desc,
+                                       ObjectOpResult& result) const
 {
     assertEnteredPolicy(cx, proxy, id, SET);
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     return DefineProperty(cx, target, id, desc, result);
 }
 
 bool
-Wrapper::ownPropertyKeys(JSContext* cx, HandleObject proxy, AutoIdVector& props) const
+ForwardingProxyHandler::ownPropertyKeys(JSContext* cx, HandleObject proxy,
+                                        AutoIdVector& props) const
 {
     assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE);
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     return GetPropertyKeys(cx, target, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, &props);
 }
 
 bool
-Wrapper::delete_(JSContext* cx, HandleObject proxy, HandleId id, ObjectOpResult& result) const
+ForwardingProxyHandler::delete_(JSContext* cx, HandleObject proxy, HandleId id,
+                                ObjectOpResult& result) const
 {
     assertEnteredPolicy(cx, proxy, id, SET);
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     return DeleteProperty(cx, target, id, result);
 }
 
 JSObject*
-Wrapper::enumerate(JSContext* cx, HandleObject proxy) const
+ForwardingProxyHandler::enumerate(JSContext* cx, HandleObject proxy) const
 {
     assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE);
     MOZ_ASSERT(!hasPrototype()); // Should never be called if there's a prototype.
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     return GetIterator(cx, target, 0);
 }
 
 bool
-Wrapper::getPrototype(JSContext* cx, HandleObject proxy, MutableHandleObject protop) const
+ForwardingProxyHandler::getPrototype(JSContext* cx, HandleObject proxy,
+                                     MutableHandleObject protop) const
 {
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     return GetPrototype(cx, target, protop);
 }
 
 bool
-Wrapper::setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto,
-                                 ObjectOpResult& result) const
+ForwardingProxyHandler::setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto,
+                                     ObjectOpResult& result) const
 {
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     return SetPrototype(cx, target, proto, result);
 }
 
 bool
-Wrapper::getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy,
-                                           bool* isOrdinary, MutableHandleObject protop) const
+ForwardingProxyHandler::getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy,
+                                               bool* isOrdinary, MutableHandleObject protop) const
 {
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     return GetPrototypeIfOrdinary(cx, target, isOrdinary, protop);
 }
 
 bool
-Wrapper::setImmutablePrototype(JSContext* cx, HandleObject proxy, bool* succeeded) const
+ForwardingProxyHandler::setImmutablePrototype(JSContext* cx, HandleObject proxy,
+                                              bool* succeeded) const
 {
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     return SetImmutablePrototype(cx, target, succeeded);
 }
 
 bool
-Wrapper::preventExtensions(JSContext* cx, HandleObject proxy, ObjectOpResult& result) const
+ForwardingProxyHandler::preventExtensions(JSContext* cx, HandleObject proxy,
+                                          ObjectOpResult& result) const
 {
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     return PreventExtensions(cx, target, result);
 }
 
 bool
-Wrapper::isExtensible(JSContext* cx, HandleObject proxy, bool* extensible) const
+ForwardingProxyHandler::isExtensible(JSContext* cx, HandleObject proxy, bool* extensible) const
 {
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     return IsExtensible(cx, target, extensible);
 }
 
 bool
-Wrapper::has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const
+ForwardingProxyHandler::has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const
 {
     assertEnteredPolicy(cx, proxy, id, GET);
     MOZ_ASSERT(!hasPrototype()); // Should never be called if there's a prototype.
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     return HasProperty(cx, target, id, bp);
 }
 
 bool
-Wrapper::get(JSContext* cx, HandleObject proxy, HandleValue receiver, HandleId id,
-             MutableHandleValue vp) const
+ForwardingProxyHandler::get(JSContext* cx, HandleObject proxy, HandleValue receiver, HandleId id,
+                            MutableHandleValue vp) const
 {
     assertEnteredPolicy(cx, proxy, id, GET);
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     return GetProperty(cx, target, receiver, id, vp);
 }
 
 bool
-Wrapper::set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v, HandleValue receiver,
-             ObjectOpResult& result) const
+ForwardingProxyHandler::set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v,
+                            HandleValue receiver, ObjectOpResult& result) const
 {
     assertEnteredPolicy(cx, proxy, id, SET);
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     return SetProperty(cx, target, id, v, receiver, result);
 }
 
 bool
-Wrapper::call(JSContext* cx, HandleObject proxy, const CallArgs& args) const
+ForwardingProxyHandler::call(JSContext* cx, HandleObject proxy, const CallArgs& args) const
 {
     assertEnteredPolicy(cx, proxy, JSID_VOID, CALL);
     RootedValue target(cx, proxy->as<ProxyObject>().private_());
 
     InvokeArgs iargs(cx);
     if (!FillArgumentsFromArraylike(cx, iargs, args))
         return false;
 
     return js::Call(cx, target, args.thisv(), iargs, args.rval());
 }
 
 bool
-Wrapper::construct(JSContext* cx, HandleObject proxy, const CallArgs& args) const
+ForwardingProxyHandler::construct(JSContext* cx, HandleObject proxy, const CallArgs& args) const
 {
     assertEnteredPolicy(cx, proxy, JSID_VOID, CALL);
 
     RootedValue target(cx, proxy->as<ProxyObject>().private_());
     if (!IsConstructor(target)) {
         ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, JSDVG_IGNORE_STACK, target, nullptr);
         return false;
     }
@@ -188,117 +194,118 @@ Wrapper::construct(JSContext* cx, Handle
     if (!Construct(cx, target, cargs, args.newTarget(), &obj))
         return false;
 
     args.rval().setObject(*obj);
     return true;
 }
 
 bool
-Wrapper::getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
-                               MutableHandle<PropertyDescriptor> desc) const
+ForwardingProxyHandler::getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
+                                              MutableHandle<PropertyDescriptor> desc) const
 {
     assertEnteredPolicy(cx, proxy, id, GET | SET | GET_PROPERTY_DESCRIPTOR);
     MOZ_ASSERT(!hasPrototype()); // Should never be called if there's a prototype.
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     return GetPropertyDescriptor(cx, target, id, desc);
 }
 
 bool
-Wrapper::hasOwn(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const
+ForwardingProxyHandler::hasOwn(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const
 {
     assertEnteredPolicy(cx, proxy, id, GET);
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     return HasOwnProperty(cx, target, id, bp);
 }
 
 bool
-Wrapper::getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy,
-                                                 AutoIdVector& props) const
+ForwardingProxyHandler::getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy,
+                                                     AutoIdVector& props) const
 {
     assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE);
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     return GetPropertyKeys(cx, target, JSITER_OWNONLY, &props);
 }
 
 bool
-Wrapper::nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl,
-                               const CallArgs& args) const
+ForwardingProxyHandler::nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl,
+                                   const CallArgs& args) const
 {
     args.setThis(ObjectValue(*args.thisv().toObject().as<ProxyObject>().target()));
     if (!test(args.thisv())) {
         ReportIncompatible(cx, args);
         return false;
     }
 
     return CallNativeImpl(cx, impl, args);
 }
 
 bool
-Wrapper::hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v,
-                                bool* bp) const
+ForwardingProxyHandler::hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v,
+                                    bool* bp) const
 {
     assertEnteredPolicy(cx, proxy, JSID_VOID, GET);
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     return HasInstance(cx, target, v, bp);
 }
 
 bool
-Wrapper::getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls) const
+ForwardingProxyHandler::getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls) const
 {
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     return GetBuiltinClass(cx, target, cls);
 }
 
 bool
-Wrapper::isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer) const
+ForwardingProxyHandler::isArray(JSContext* cx, HandleObject proxy, JS::IsArrayAnswer* answer) const
 {
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     return IsArray(cx, target, answer);
 }
 
 const char*
-Wrapper::className(JSContext* cx, HandleObject proxy) const
+ForwardingProxyHandler::className(JSContext* cx, HandleObject proxy) const
 {
     assertEnteredPolicy(cx, proxy, JSID_VOID, GET);
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     return GetObjectClassName(cx, target);
 }
 
 JSString*
-Wrapper::fun_toString(JSContext* cx, HandleObject proxy, bool isToSource) const
+ForwardingProxyHandler::fun_toString(JSContext* cx, HandleObject proxy, bool isToSource) const
 {
     assertEnteredPolicy(cx, proxy, JSID_VOID, GET);
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     return fun_toStringHelper(cx, target, isToSource);
 }
 
 RegExpShared*
-Wrapper::regexp_toShared(JSContext* cx, HandleObject proxy) const
+ForwardingProxyHandler::regexp_toShared(JSContext* cx, HandleObject proxy) const
 {
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     return RegExpToShared(cx, target);
 }
 
 bool
-Wrapper::boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) const
+ForwardingProxyHandler::boxedValue_unbox(JSContext* cx, HandleObject proxy,
+                                         MutableHandleValue vp) const
 {
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     return Unbox(cx, target, vp);
 }
 
 bool
-Wrapper::isCallable(JSObject* obj) const
+ForwardingProxyHandler::isCallable(JSObject* obj) const
 {
     JSObject * target = obj->as<ProxyObject>().target();
     return target->isCallable();
 }
 
 bool
-Wrapper::isConstructor(JSObject* obj) const
+ForwardingProxyHandler::isConstructor(JSObject* obj) const
 {
     // For now, all wrappers are constructable if they are callable. We will want to eventually
     // decouple this behavior, but none of the Wrapper infrastructure is currently prepared for
     // that.
     return isCallable(obj);
 }
 
 JSObject*
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -5049,279 +5049,16 @@ Debugger::makeGlobalObjectReference(JSCo
                                   JSMSG_DEBUG_INVISIBLE_COMPARTMENT);
         return false;
     }
 
     args.rval().setObject(*global);
     return dbg->wrapDebuggeeValue(cx, args.rval());
 }
 
-#ifdef JS_TRACE_LOGGING
-static bool
-DefineProperty(JSContext* cx, HandleObject obj, HandleId id, const char* value, size_t n)
-{
-    JSString* text = JS_NewStringCopyN(cx, value, n);
-    if (!text)
-        return false;
-
-    RootedValue str(cx, StringValue(text));
-    return JS_DefinePropertyById(cx, obj, id, str, JSPROP_ENUMERATE);
-}
-
-# ifdef NIGHTLY_BUILD
-bool
-Debugger::setupTraceLogger(JSContext* cx, unsigned argc, Value* vp)
-{
-    THIS_DEBUGGER(cx, argc, vp, "setupTraceLogger", args, dbg);
-    if (!args.requireAtLeast(cx, "Debugger.setupTraceLogger", 1))
-        return false;
-
-    RootedObject obj(cx, ToObject(cx, args[0]));
-    if (!obj)
-        return false;
-
-    AutoIdVector ids(cx);
-    if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY, &ids))
-        return false;
-
-    if (ids.length() == 0) {
-        args.rval().setBoolean(true);
-        return true;
-    }
-
-    Vector<uint32_t> textIds(cx);
-    if (!textIds.reserve(ids.length()))
-        return false;
-
-    Vector<bool> values(cx);
-    if (!values.reserve(ids.length()))
-        return false;
-
-    for (size_t i = 0; i < ids.length(); i++) {
-        if (!JSID_IS_STRING(ids[i])) {
-            args.rval().setBoolean(false);
-            return true;
-        }
-
-        JSString* id = JSID_TO_STRING(ids[i]);
-        JSLinearString* linear = id->ensureLinear(cx);
-        if (!linear)
-            return false;
-
-        uint32_t textId = TLStringToTextId(linear);
-
-        if (!TLTextIdIsTogglable(textId)) {
-            args.rval().setBoolean(false);
-            return true;
-        }
-
-        RootedValue v(cx);
-        if (!GetProperty(cx, obj, obj, ids[i], &v))
-            return false;
-
-        textIds.infallibleAppend(textId);
-        values.infallibleAppend(ToBoolean(v));
-    }
-
-    MOZ_ASSERT(ids.length() == textIds.length());
-    MOZ_ASSERT(textIds.length() == values.length());
-
-    for (size_t i = 0; i < textIds.length(); i++) {
-        if (values[i])
-            TraceLogEnableTextId(cx, textIds[i]);
-        else
-            TraceLogDisableTextId(cx, textIds[i]);
-    }
-
-    args.rval().setBoolean(true);
-    return true;
-}
-
-bool
-Debugger::drainTraceLogger(JSContext* cx, unsigned argc, Value* vp)
-{
-    THIS_DEBUGGER(cx, argc, vp, "drainTraceLogger", args, dbg);
-
-    TraceLoggerThread* logger = TraceLoggerForCurrentThread(cx);
-    bool lostEvents = logger->lostEvents(dbg->traceLoggerLastDrainedIteration,
-                                         dbg->traceLoggerLastDrainedSize);
-
-    size_t numEvents;
-    EventEntry* events = logger->getEventsStartingAt(&dbg->traceLoggerLastDrainedIteration,
-                                                     &dbg->traceLoggerLastDrainedSize,
-                                                     &numEvents);
-
-    RootedObject array(cx, NewDenseEmptyArray(cx));
-    if (!array)
-        return false;
-
-    JSAtom* dataAtom = Atomize(cx, "data", strlen("data"));
-    if (!dataAtom)
-        return false;
-
-    RootedId dataId(cx, AtomToId(dataAtom));
-
-    /* Add all events to the array. */
-    uint32_t index = 0;
-    for (EventEntry* eventItem = events; eventItem < events + numEvents; eventItem++, index++) {
-        RootedObject item(cx, NewObjectWithGivenProto(cx, &PlainObject::class_, nullptr));
-        if (!item)
-            return false;
-
-        const char* eventText = logger->eventText(eventItem->textId);
-        if (!DefineProperty(cx, item, dataId, eventText, strlen(eventText)))
-            return false;
-
-        RootedValue obj(cx, ObjectValue(*item));
-        if (!JS_DefineElement(cx, array, index, obj, JSPROP_ENUMERATE))
-            return false;
-    }
-
-    /* Add "lostEvents" indicating if there are events that were lost. */
-    RootedValue lost(cx, BooleanValue(lostEvents));
-    if (!JS_DefineProperty(cx, array, "lostEvents", lost, JSPROP_ENUMERATE))
-        return false;
-
-    args.rval().setObject(*array);
-
-    return true;
-}
-# endif // NIGHTLY_BUILD
-
-bool
-Debugger::setupTraceLoggerScriptCalls(JSContext* cx, unsigned argc, Value* vp)
-{
-    THIS_DEBUGGER(cx, argc, vp, "setupTraceLoggerScriptCalls", args, dbg);
-    if (!args.requireAtLeast(cx, "Debugger.setupTraceLoggerScriptCalls", 0))
-        return false;
-
-    TraceLogEnableTextId(cx, TraceLogger_Scripts);
-    TraceLogEnableTextId(cx, TraceLogger_InlinedScripts);
-    TraceLogDisableTextId(cx, TraceLogger_AnnotateScripts);
-
-    args.rval().setBoolean(true);
-
-    return true;
-}
-
-bool
-Debugger::startTraceLogger(JSContext* cx, unsigned argc, Value* vp)
-{
-    THIS_DEBUGGER(cx, argc, vp, "startTraceLogger", args, dbg);
-    if (!args.requireAtLeast(cx, "Debugger.startTraceLogger", 0))
-        return false;
-
-    TraceLoggerThread* logger = TraceLoggerForCurrentThread(cx);
-    if (!TraceLoggerEnable(logger, cx))
-        return false;
-
-    args.rval().setUndefined();
-
-    return true;
-}
-
-bool
-Debugger::endTraceLogger(JSContext* cx, unsigned argc, Value* vp)
-{
-    THIS_DEBUGGER(cx, argc, vp, "endTraceLogger", args, dbg);
-    if (!args.requireAtLeast(cx, "Debugger.endTraceLogger", 0))
-        return false;
-
-    TraceLoggerThread* logger = TraceLoggerForCurrentThread(cx);
-    TraceLoggerDisable(logger);
-
-    args.rval().setUndefined();
-
-    return true;
-}
-
-bool
-Debugger::drainTraceLoggerScriptCalls(JSContext* cx, unsigned argc, Value* vp)
-{
-    THIS_DEBUGGER(cx, argc, vp, "drainTraceLoggerScriptCalls", args, dbg);
-
-    TraceLoggerThread* logger = TraceLoggerForCurrentThread(cx);
-    bool lostEvents = logger->lostEvents(dbg->traceLoggerScriptedCallsLastDrainedIteration,
-                                         dbg->traceLoggerScriptedCallsLastDrainedSize);
-
-    size_t numEvents;
-    EventEntry* events = logger->getEventsStartingAt(
-                                         &dbg->traceLoggerScriptedCallsLastDrainedIteration,
-                                         &dbg->traceLoggerScriptedCallsLastDrainedSize,
-                                         &numEvents);
-
-    RootedObject array(cx, NewDenseEmptyArray(cx));
-    if (!array)
-        return false;
-
-    JSAtom* logTypeAtom = Atomize(cx, "logType", strlen("logType"));
-    if (!logTypeAtom)
-        return false;
-
-    RootedId fileNameId(cx, AtomToId(cx->names().fileName));
-    RootedId lineNumberId(cx, AtomToId(cx->names().lineNumber));
-    RootedId columnNumberId(cx, AtomToId(cx->names().columnNumber));
-    RootedId logTypeId(cx, AtomToId(logTypeAtom));
-
-    /* Add all events to the array. */
-    uint32_t index = 0;
-    for (EventEntry* eventItem = events; eventItem < events + numEvents; eventItem++) {
-        RootedObject item(cx, NewObjectWithGivenProto(cx, &PlainObject::class_, nullptr));
-        if (!item)
-            return false;
-
-        // Filter out internal time.
-        uint32_t textId = eventItem->textId;
-        if (textId == TraceLogger_Internal) {
-            eventItem++;
-            MOZ_ASSERT(eventItem->textId == TraceLogger_Stop);
-            continue;
-        }
-
-        if (textId != TraceLogger_Stop && !logger->textIdIsScriptEvent(textId))
-            continue;
-
-        const char* type = (textId == TraceLogger_Stop) ? "Stop" : "Script";
-        if (!DefineProperty(cx, item, logTypeId, type, strlen(type)))
-            return false;
-
-        if (textId != TraceLogger_Stop) {
-            const char* filename;
-            const char* lineno;
-            const char* colno;
-            size_t filename_len, lineno_len, colno_len;
-            logger->extractScriptDetails(textId, &filename, &filename_len, &lineno, &lineno_len,
-                                         &colno, &colno_len);
-
-            if (!DefineProperty(cx, item, fileNameId, filename, filename_len))
-                return false;
-            if (!DefineProperty(cx, item, lineNumberId, lineno, lineno_len))
-                return false;
-            if (!DefineProperty(cx, item, columnNumberId, colno, colno_len))
-                return false;
-        }
-
-        RootedValue obj(cx, ObjectValue(*item));
-        if (!JS_DefineElement(cx, array, index, obj, JSPROP_ENUMERATE))
-            return false;
-
-        index++;
-    }
-
-    /* Add "lostEvents" indicating if there are events that were lost. */
-    RootedValue lost(cx, BooleanValue(lostEvents));
-    if (!JS_DefineProperty(cx, array, "lostEvents", lost, JSPROP_ENUMERATE))
-        return false;
-
-    args.rval().setObject(*array);
-
-    return true;
-}
-#endif
-
 bool
 Debugger::isCompilableUnit(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     if (!args.requireAtLeast(cx, "Debugger.isCompilableUnit", 1))
         return false;
 
@@ -5429,26 +5166,16 @@ const JSFunctionSpec Debugger::methods[]
     JS_FN("hasDebuggee", Debugger::hasDebuggee, 1, 0),
     JS_FN("getDebuggees", Debugger::getDebuggees, 0, 0),
     JS_FN("getNewestFrame", Debugger::getNewestFrame, 0, 0),
     JS_FN("clearAllBreakpoints", Debugger::clearAllBreakpoints, 0, 0),
     JS_FN("findScripts", Debugger::findScripts, 1, 0),
     JS_FN("findObjects", Debugger::findObjects, 1, 0),
     JS_FN("findAllGlobals", Debugger::findAllGlobals, 0, 0),
     JS_FN("makeGlobalObjectReference", Debugger::makeGlobalObjectReference, 1, 0),
-#ifdef JS_TRACE_LOGGING
-    JS_FN("setupTraceLoggerScriptCalls", Debugger::setupTraceLoggerScriptCalls, 0, 0),
-    JS_FN("drainTraceLoggerScriptCalls", Debugger::drainTraceLoggerScriptCalls, 0, 0),
-    JS_FN("startTraceLogger", Debugger::startTraceLogger, 0, 0),
-    JS_FN("endTraceLogger", Debugger::endTraceLogger, 0, 0),
-# ifdef NIGHTLY_BUILD
-    JS_FN("setupTraceLogger", Debugger::setupTraceLogger, 1, 0),
-    JS_FN("drainTraceLogger", Debugger::drainTraceLogger, 0, 0),
-# endif
-#endif
     JS_FN("adoptDebuggeeValue", Debugger::adoptDebuggeeValue, 1, 0),
     JS_FS_END
 };
 
 const JSFunctionSpec Debugger::static_methods[] {
     JS_FN("isCompilableUnit", Debugger::isCompilableUnit, 1, 0),
     JS_FS_END
 };
--- a/layout/style/Loader.cpp
+++ b/layout/style/Loader.cpp
@@ -1981,16 +1981,17 @@ Loader::DoSheetComplete(SheetLoadData* a
 }
 
 nsresult
 Loader::LoadInlineStyle(nsIContent* aElement,
                         const nsAString& aBuffer,
                         uint32_t aLineNumber,
                         const nsAString& aTitle,
                         const nsAString& aMedia,
+                        ReferrerPolicy aReferrerPolicy,
                         Element* aScopeElement,
                         nsICSSLoaderObserver* aObserver,
                         bool* aCompleted,
                         bool* aIsAlternate)
 {
   LOG(("css::Loader::LoadInlineStyle"));
   MOZ_ASSERT(mParsingDatas.IsEmpty(), "We're in the middle of a parse?");
 
@@ -2003,21 +2004,22 @@ Loader::LoadInlineStyle(nsIContent* aEle
 
   NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_INITIALIZED);
 
   nsCOMPtr<nsIStyleSheetLinkingElement> owningElement(do_QueryInterface(aElement));
   NS_ASSERTION(owningElement, "Element is not a style linking element!");
 
   // Since we're not planning to load a URI, no need to hand a principal to the
   // load data or to CreateSheet().  Also, OK to use CORS_NONE for the CORS
-  // mode and mDocument's ReferrerPolicy.
+  // mode.
+
   StyleSheetState state;
   RefPtr<StyleSheet> sheet;
   nsresult rv = CreateSheet(nullptr, aElement, nullptr, eAuthorSheetFeatures,
-                            CORS_NONE, mDocument->GetReferrerPolicy(),
+                            CORS_NONE, aReferrerPolicy,
                             EmptyString(), // no inline integrity checks
                             false, false, aTitle, state, aIsAlternate,
                             &sheet);
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ASSERTION(state == eSheetNeedsParser,
                "Inline sheets should not be cached");
 
   LOG(("  Sheet is alternate: %d", *aIsAlternate));
--- a/layout/style/Loader.h
+++ b/layout/style/Loader.h
@@ -224,27 +224,29 @@ public:
    * stylesheet list of this CSSLoader's document.
    *
    * @param aElement the element linking to the stylesheet.  This must not be
    *                 null and must implement nsIStyleSheetLinkingElement.
    * @param aBuffer the stylesheet data
    * @param aLineNumber the line number at which the stylesheet data started.
    * @param aTitle the title of the sheet.
    * @param aMedia the media string for the sheet.
+   * @param aReferrerPolicy the referrer policy for loading the sheet.
    * @param aObserver the observer to notify when the load completes.
    *        May be null.
    * @param [out] aCompleted whether parsing of the sheet completed.
    * @param [out] aIsAlternate whether the stylesheet ended up being an
    *        alternate sheet.
    */
   nsresult LoadInlineStyle(nsIContent* aElement,
                            const nsAString& aBuffer,
                            uint32_t aLineNumber,
                            const nsAString& aTitle,
                            const nsAString& aMedia,
+                           ReferrerPolicy aReferrerPolicy,
                            mozilla::dom::Element* aScopeElement,
                            nsICSSLoaderObserver* aObserver,
                            bool* aCompleted,
                            bool* aIsAlternate);
 
   /**
    * Load a linked (document) stylesheet.  If a successful result is returned,
    * aObserver is guaranteed to be notified asynchronously once the sheet is
--- a/layout/tools/reftest/remotereftest.py
+++ b/layout/tools/reftest/remotereftest.py
@@ -149,24 +149,26 @@ class RemoteReftest(RefTest):
         RefTest.__init__(self)
         self.automation = automation
         self._devicemanager = devicemanager
         self.scriptDir = scriptDir
         self.remoteApp = options.app
         self.remoteProfile = options.remoteProfile
         self.remoteTestRoot = options.remoteTestRoot
         self.remoteLogFile = options.remoteLogFile
+        self.remoteCache = os.path.join(options.remoteTestRoot, "cache/")
         self.localLogName = options.localLogName
         self.pidFile = options.pidFile
         if self.automation.IS_DEBUG_BUILD:
             self.SERVER_STARTUP_TIMEOUT = 180
         else:
             self.SERVER_STARTUP_TIMEOUT = 90
         self.automation.deleteANRs()
         self.automation.deleteTombstones()
+        self._devicemanager.removeDir(self.remoteCache)
 
         self._populate_logger(options)
         outputHandler = OutputHandler(self.log, options.utilityPath, options.symbolsPath)
         # RemoteAutomation.py's 'messageLogger' is also used by mochitest. Mimic a mochitest
         # MessageLogger object to re-use this code path.
         outputHandler.write = outputHandler.__call__
         self.automation._processArgs['messageLogger'] = outputHandler
 
@@ -247,16 +249,18 @@ class RemoteReftest(RefTest):
                    "reftest yet.")
         profileDir = profile.profile
 
         prefs = {}
         prefs["app.update.url.android"] = ""
         prefs["browser.firstrun.show.localepicker"] = False
         prefs["reftest.remote"] = True
         prefs["datareporting.policy.dataSubmissionPolicyBypassAcceptance"] = True
+        # move necko cache to a location that can be cleaned up
+        prefs["browser.cache.disk.parent_directory"] = self.remoteCache
 
         prefs["layout.css.devPixelsPerPx"] = "1.0"
         # Because Fennec is a little wacky (see bug 1156817) we need to load the
         # reftest pages at 1.0 zoom, rather than zooming to fit the CSS viewport.
         prefs["apz.allow_zooming"] = False
 
         # Set the extra prefs.
         profile.set_preferences(prefs)
@@ -327,16 +331,17 @@ class RemoteReftest(RefTest):
         # Pull results back from device
         if self.remoteLogFile and \
                 self._devicemanager.fileExists(self.remoteLogFile):
             self._devicemanager.getFile(self.remoteLogFile, self.localLogName)
         else:
             print "WARNING: Unable to retrieve log file (%s) from remote " \
                 "device" % self.remoteLogFile
         self._devicemanager.removeDir(self.remoteProfile)
+        self._devicemanager.removeDir(self.remoteCache)
         self._devicemanager.removeDir(self.remoteTestRoot)
         RefTest.cleanup(self, profileDir)
         if (self.pidFile != ""):
             try:
                 os.remove(self.pidFile)
                 os.remove(self.pidFile + ".xpcshell.pid")
             except:
                 print ("Warning: cleaning up pidfile '%s' was unsuccessful "
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -1604,18 +1604,16 @@ public class BrowserApp extends GeckoApp
         if (mFormAssistPopup != null)
             mFormAssistPopup.destroy();
         if (mTextSelection != null)
             mTextSelection.destroy();
         NotificationHelper.destroy();
         IntentHelper.destroy();
         GeckoNetworkManager.destroy();
 
-        EventDispatcher.getInstance().dispatch("Browser:ZombifyTabs", null);
-
         super.onDestroy();
     }
 
     @Override
     protected void initializeChrome() {
         super.initializeChrome();
 
         mDoorHangerPopup.setAnchor(mBrowserToolbar.getDoorHangerAnchor());
--- a/mobile/android/chrome/content/InputWidgetHelper.js
+++ b/mobile/android/chrome/content/InputWidgetHelper.js
@@ -81,22 +81,19 @@ var InputWidgetHelper = {
       return true;
     }
 
     return false;
   },
 
   fireOnChange: function(aElement) {
     let win = aElement.ownerGlobal;
-    let evt = aElement.ownerDocument.createEvent("Events");
-    evt.initEvent("change", true, true, aElement.defaultView, 0,
-                  false, false,
-                  false, false, null);
     win.setTimeout(function() {
-      aElement.dispatchEvent(evt);
+      aElement.dispatchEvent(new win.Event("input", { bubbles: true }));
+      aElement.dispatchEvent(new win.Event("change", { bubbles: true }));
     }, 0);
   },
 
   _isDisabledElement: function(aElement) {
     let currentElement = aElement;
     while (currentElement) {
       if (currentElement.disabled)
         return true;
--- a/mobile/android/chrome/content/SelectHelper.js
+++ b/mobile/android/chrome/content/SelectHelper.js
@@ -137,21 +137,19 @@ var SelectHelper = {
           this.forVisibleOptions(child, aFunction, child);
         }
       }
     }
   },
 
   fireOnChange: function(element) {
     let win = element.ownerGlobal;
-    let event = element.ownerDocument.createEvent("Events");
-    event.initEvent("change", true, true, element.defaultView, 0,
-        false, false, false, false, null);
     win.setTimeout(function() {
-      element.dispatchEvent(event);
+      element.dispatchEvent(new win.Event("input", { bubbles: true }));
+      element.dispatchEvent(new win.Event("change", { bubbles: true }));
     }, 0);
   },
 
   fireOnCommand: function(element) {
     let win = element.ownerGlobal;
     let event = element.ownerDocument.createEvent("XULCommandEvent");
     event.initCommandEvent("command", true, true, element.defaultView, 0,
         false, false, false, false, null, 0);
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -392,17 +392,16 @@ var BrowserApp = {
       "Tab:Selected",
       "Tab:Closed",
       "Tab:Move",
     ]);
 
     GlobalEventDispatcher.registerListener(this, [
       "Browser:LoadManifest",
       "Browser:Quit",
-      "Browser:ZombifyTabs",
       "Fonts:Reload",
       "FormHistory:Init",
       "FullScreen:Exit",
       "Locale:OS",
       "Locale:Changed",
       "Passwords:Init",
       "Sanitize:ClearData",
       "SaveAs:PDF",
@@ -1675,23 +1674,16 @@ var BrowserApp = {
         installManifest(browser, data);
         break;
       }
 
       case "Browser:Quit":
         this.quit(data);
         break;
 
-      case "Browser:ZombifyTabs":
-        let tabs = this._tabs;
-        for (let i = 0; i < tabs.length; i++) {
-          tabs[i].zombify();
-        }
-        break;
-
       case "Fonts:Reload":
         FontEnumerator.updateFontList();
         break;
 
       case "FormHistory:Init": {
         // Force creation/upgrade of formhistory.sqlite
         FormHistory.count({});
         GlobalEventDispatcher.unregisterListener(this, event);
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditableChild.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoEditableChild.java
@@ -129,23 +129,17 @@ final class GeckoEditableChild extends J
 
     @WrapForJNI(calledFrom = "gecko")
     private void notifyIME(final int type) {
         if (DEBUG) {
             ThreadUtils.assertOnGeckoThread();
             Log.d(LOGTAG, "notifyIME(" + GeckoEditable.getConstantName(
                           GeckoEditableListener.class, "NOTIFY_IME_", type) + ")");
         }
-        if (type == GeckoEditableListener.NOTIFY_IME_TO_COMMIT_COMPOSITION) {
-            // Gecko already committed its composition. However, Android keyboards
-            // have trouble dealing with us removing the composition manually on
-            // the Java side. Therefore, we keep the composition intact on the Java
-            // side. The text content should still be in-sync on both sides.
-            return;
-        } else if (type == GeckoEditableListener.NOTIFY_IME_TO_CANCEL_COMPOSITION) {
+        if (type == GeckoEditableListener.NOTIFY_IME_TO_CANCEL_COMPOSITION) {
             // Composition should have been canceled on the parent side through text
             // update notifications. We cannot verify that here because we don't
             // keep track of spans on the child side, but it's simple to add the
             // check to the parent side if ever needed.
             return;
         }
 
         try {
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoInputConnection.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoInputConnection.java
@@ -26,16 +26,17 @@ import android.media.AudioManager;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.SystemClock;
 import android.text.Editable;
 import android.text.InputType;
 import android.text.Selection;
 import android.text.SpannableString;
+import android.text.Spanned;
 import android.text.method.KeyListener;
 import android.text.method.TextKeyListener;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.inputmethod.BaseInputConnection;
 import android.view.inputmethod.CursorAnchorInfo;
@@ -929,16 +930,37 @@ class GeckoInputConnection
                 // Showing/hiding vkb is done in notifyIMEContext
                 mFocused = false;
                 break;
 
             case NOTIFY_IME_OPEN_VKB:
                 showSoftInput();
                 break;
 
+            case GeckoEditableListener.NOTIFY_IME_TO_COMMIT_COMPOSITION: {
+                // Gecko already committed its composition. However, Android keyboards
+                // have trouble dealing with us removing the composition manually on the
+                // Java side. Therefore, we keep the composition intact on the Java side.
+                // The text content should still be in-sync on both sides.
+                //
+                // Nevertheless, if we somehow lost the composition, we must force the
+                // keyboard to reset.
+                final Editable editable = getEditable();
+                final Object[] spans = editable.getSpans(0, editable.length(), Object.class);
+                for (final Object span : spans) {
+                    if ((editable.getSpanFlags(span) & Spanned.SPAN_COMPOSING) != 0) {
+                        // Still have composition; no need to reset.
+                        return;
+                    }
+                }
+                // No longer have composition; perform reset.
+                restartInput();
+                break;
+            }
+
             default:
                 if (DEBUG) {
                     throw new IllegalArgumentException("Unexpected NOTIFY_IME=" + type);
                 }
                 break;
         }
     }
 
--- a/mobile/android/tests/browser/robocop/robocop_input.html
+++ b/mobile/android/tests/browser/robocop/robocop_input.html
@@ -4,17 +4,17 @@
     <meta charset="utf-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <title>Robocop Input</title>
   </head>
   <body>
     <p>Input: <input id="input" type="text"></p>
     <p>Text area: <textarea id="text-area"></textarea></p>
     <p>Content editable: <div id="content-editable" contentEditable="true"></div></p>
-    <p>Design mode: <iframe id="design-mode" src="data:text/html;charset=utf-8,<html><body></body></html>"></iframe></p>
+    <p>Design mode: <iframe id="design-mode" srcdoc="<html><body></body></html>"></iframe></p>
     <p>Resetting input: <input id="resetting-input" type="text"></p>
     <p>Hiding input: <input id="hiding-input" type="text"></p>
     <script type="application/javascript" src="robocop_head.js"></script>
     <script type="application/javascript">
       let input = document.getElementById("input");
       let textArea = document.getElementById("text-area");
       let contentEditable = document.getElementById("content-editable");
 
--- a/netwerk/base/nsLoadGroup.cpp
+++ b/netwerk/base/nsLoadGroup.cpp
@@ -980,26 +980,28 @@ nsLoadGroup::TelemetryReportChannel(nsIT
         if (!requestStart.IsNull() && !responseEnd.IsNull()) {                 \
             Telemetry::AccumulateTimeDelta(                                    \
                 Telemetry::HTTP_##prefix##_REVALIDATION,                       \
                 requestStart, responseEnd);                                    \
         }                                                                      \
     }                                                                          \
                                                                                \
     if (!cacheReadEnd.IsNull()) {                                              \
-        Telemetry::AccumulateTimeDelta(                                        \
-            Telemetry::HTTP_##prefix##_COMPLETE_LOAD,                          \
-            asyncOpen, cacheReadEnd);                                          \
-                                                                               \
         if (!CacheObserver::UseNewCache()) {                                   \
             Telemetry::AccumulateTimeDelta(                                    \
+                Telemetry::HTTP_##prefix##_COMPLETE_LOAD,                      \
+                asyncOpen, cacheReadEnd);                                      \
+            Telemetry::AccumulateTimeDelta(                                    \
                 Telemetry::HTTP_##prefix##_COMPLETE_LOAD_CACHED,               \
                 asyncOpen, cacheReadEnd);                                      \
         } else {                                                               \
             Telemetry::AccumulateTimeDelta(                                    \
+                Telemetry::HTTP_##prefix##_COMPLETE_LOAD_V2,                   \
+                asyncOpen, cacheReadEnd);                                      \
+            Telemetry::AccumulateTimeDelta(                                    \
                 Telemetry::HTTP_##prefix##_COMPLETE_LOAD_CACHED_V2,            \
                 asyncOpen, cacheReadEnd);                                      \
         }                                                                      \
     }                                                                          \
     else if (!responseEnd.IsNull()) {                                          \
         if (!CacheObserver::UseNewCache()) {                                   \
             Telemetry::AccumulateTimeDelta(                                    \
                 Telemetry::HTTP_##prefix##_COMPLETE_LOAD,                      \
--- a/netwerk/cookie/CookieServiceChild.cpp
+++ b/netwerk/cookie/CookieServiceChild.cpp
@@ -5,16 +5,17 @@
 
 #include "mozilla/net/CookieServiceChild.h"
 #include "mozilla/net/NeckoChannelParams.h"
 #include "mozilla/LoadInfo.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/ipc/URIUtils.h"
 #include "mozilla/net/NeckoChild.h"
+#include "mozilla/SystemGroup.h"
 #include "nsCookie.h"
 #include "nsCookieService.h"
 #include "nsContentUtils.h"
 #include "nsNetCID.h"
 #include "nsIChannel.h"
 #include "nsICookiePermission.h"
 #include "nsIEffectiveTLDService.h"
 #include "nsIURI.h"
@@ -64,18 +65,23 @@ CookieServiceChild::CookieServiceChild()
     static_cast<mozilla::dom::ContentChild*>(gNeckoChild->Manager());
   if (cc->IsShuttingDown()) {
     return;
   }
 
   // This corresponds to Release() in DeallocPCookieService.
   NS_ADDREF_THIS();
 
+  NeckoChild::InitNeckoChild();
+
+  gNeckoChild->SetEventTargetForActor(
+    this,
+    SystemGroup::EventTargetFor(TaskCategory::Other));
+
   // Create a child PCookieService actor.
-  NeckoChild::InitNeckoChild();
   gNeckoChild->SendPCookieServiceConstructor(this);
 
   mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
   NS_ASSERTION(mTLDService, "couldn't get TLDService");
 
   // Init our prefs and observer.
   nsCOMPtr<nsIPrefBranch> prefBranch =
     do_GetService(NS_PREFSERVICE_CONTRACTID);
--- a/parser/html/nsHtml5SpeculativeLoad.cpp
+++ b/parser/html/nsHtml5SpeculativeLoad.cpp
@@ -50,17 +50,18 @@ nsHtml5SpeculativeLoad::Perform(nsHtml5T
       aExecutor->PreloadScript(mUrl, mCharset, mTypeOrCharsetSourceOrDocumentMode,
                                mCrossOrigin, mIntegrity, false);
       break;
     case eSpeculativeLoadScriptFromHead:
       aExecutor->PreloadScript(mUrl, mCharset, mTypeOrCharsetSourceOrDocumentMode,
                                mCrossOrigin, mIntegrity, true);
       break;
     case eSpeculativeLoadStyle:
-      aExecutor->PreloadStyle(mUrl, mCharset, mCrossOrigin, mIntegrity);
+      aExecutor->PreloadStyle(mUrl, mCharset, mCrossOrigin, mReferrerPolicy,
+                              mIntegrity);
       break;
     case eSpeculativeLoadManifest:  
       aExecutor->ProcessOfflineManifest(mUrl);
       break;
     case eSpeculativeLoadSetDocumentCharset: {
         nsAutoCString narrowName;
         CopyUTF16toUTF8(mCharset, narrowName);
         NS_ASSERTION(mTypeOrCharsetSourceOrDocumentMode.Length() == 1,
--- a/parser/html/nsHtml5SpeculativeLoad.h
+++ b/parser/html/nsHtml5SpeculativeLoad.h
@@ -139,24 +139,26 @@ class nsHtml5SpeculativeLoad {
       aType.ToString(mTypeOrCharsetSourceOrDocumentMode);
       aCrossOrigin.ToString(mCrossOrigin);
       aIntegrity.ToString(mIntegrity);
     }
 
     inline void InitStyle(nsHtml5String aUrl,
                           nsHtml5String aCharset,
                           nsHtml5String aCrossOrigin,
+                          nsHtml5String aReferrerPolicy,
                           nsHtml5String aIntegrity)
     {
       NS_PRECONDITION(mOpCode == eSpeculativeLoadUninitialized,
                       "Trying to reinitialize a speculative load!");
       mOpCode = eSpeculativeLoadStyle;
       aUrl.ToString(mUrl);
       aCharset.ToString(mCharset);
       aCrossOrigin.ToString(mCrossOrigin);
+      aReferrerPolicy.ToString(mReferrerPolicy);
       aIntegrity.ToString(mIntegrity);
     }
 
     /**
      * "Speculative" manifest loads aren't truly speculative--if a manifest
      * gets loaded, we are committed to it. There can never be a <script>
      * before the manifest, so the situation of having to undo a manifest due
      * to document.write() never arises. The reason why a parser
--- a/parser/html/nsHtml5TreeBuilderCppSupplement.h
+++ b/parser/html/nsHtml5TreeBuilderCppSupplement.h
@@ -207,18 +207,20 @@ nsHtml5TreeBuilder::createElement(int32_
                 aAttributes->getValue(nsHtml5AttributeName::ATTR_HREF);
               if (url) {
                 nsHtml5String charset =
                   aAttributes->getValue(nsHtml5AttributeName::ATTR_CHARSET);
                 nsHtml5String crossOrigin =
                   aAttributes->getValue(nsHtml5AttributeName::ATTR_CROSSORIGIN);
                 nsHtml5String integrity =
                   aAttributes->getValue(nsHtml5AttributeName::ATTR_INTEGRITY);
+                nsHtml5String referrerPolicy =
+                  aAttributes->getValue(nsHtml5AttributeName::ATTR_REFERRERPOLICY);
                 mSpeculativeLoadQueue.AppendElement()->InitStyle(
-                  url, charset, crossOrigin, integrity);
+                  url, charset, crossOrigin, referrerPolicy, integrity);
               }
             } else if (rel.LowerCaseEqualsASCII("preconnect")) {
               nsHtml5String url =
                 aAttributes->getValue(nsHtml5AttributeName::ATTR_HREF);
               if (url) {
                 nsHtml5String crossOrigin =
                   aAttributes->getValue(nsHtml5AttributeName::ATTR_CROSSORIGIN);
                 mSpeculativeLoadQueue.AppendElement()->InitPreconnect(
@@ -306,18 +308,20 @@ nsHtml5TreeBuilder::createElement(int32_
 
           nsHtml5String url =
             aAttributes->getValue(nsHtml5AttributeName::ATTR_XLINK_HREF);
           if (url) {
             nsHtml5String crossOrigin =
               aAttributes->getValue(nsHtml5AttributeName::ATTR_CROSSORIGIN);
             nsHtml5String integrity =
               aAttributes->getValue(nsHtml5AttributeName::ATTR_INTEGRITY);
+            nsHtml5String referrerPolicy =
+              aAttributes->getValue(nsHtml5AttributeName::ATTR_REFERRERPOLICY);
             mSpeculativeLoadQueue.AppendElement()->InitStyle(
-              url, nullptr, crossOrigin, integrity);
+              url, nullptr, crossOrigin, referrerPolicy, integrity);
           }
         }
         break;
     }
   } else if (aNamespace != kNameSpaceID_MathML) {
     // No speculative loader--just line numbers and defer/async check
     if (nsGkAtoms::style == aName) {
       nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
--- a/parser/html/nsHtml5TreeOpExecutor.cpp
+++ b/parser/html/nsHtml5TreeOpExecutor.cpp
@@ -961,24 +961,33 @@ nsHtml5TreeOpExecutor::PreloadScript(con
                                            aIntegrity, aScriptFromHead,
                                            mSpeculationReferrerPolicy);
 }
 
 void
 nsHtml5TreeOpExecutor::PreloadStyle(const nsAString& aURL,
                                     const nsAString& aCharset,
                                     const nsAString& aCrossOrigin,
+                                    const nsAString& aReferrerPolicy,
                                     const nsAString& aIntegrity)
 {
   nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
   if (!uri) {
     return;
   }
-  mDocument->PreloadStyle(uri, aCharset, aCrossOrigin,
-                          mSpeculationReferrerPolicy, aIntegrity);
+
+  mozilla::net::ReferrerPolicy referrerPolicy = mSpeculationReferrerPolicy;
+  mozilla::net::ReferrerPolicy styleReferrerPolicy =
+    mozilla::net::AttributeReferrerPolicyFromString(aReferrerPolicy);
+  if (styleReferrerPolicy != mozilla::net::RP_Unset) {
+    referrerPolicy = styleReferrerPolicy;
+  }
+
+  mDocument->PreloadStyle(uri, aCharset, aCrossOrigin, referrerPolicy,
+                          aIntegrity);
 }
 
 void
 nsHtml5TreeOpExecutor::PreloadImage(const nsAString& aURL,
                                     const nsAString& aCrossOrigin,
                                     const nsAString& aSrcset,
                                     const nsAString& aSizes,
                                     const nsAString& aImageReferrerPolicy)
--- a/parser/html/nsHtml5TreeOpExecutor.h
+++ b/parser/html/nsHtml5TreeOpExecutor.h
@@ -251,16 +251,17 @@ class nsHtml5TreeOpExecutor final : publ
                        const nsAString& aCharset,
                        const nsAString& aType,
                        const nsAString& aCrossOrigin,
                        const nsAString& aIntegrity,
                        bool aScriptFromHead);
 
     void PreloadStyle(const nsAString& aURL, const nsAString& aCharset,
                       const nsAString& aCrossOrigin,
+                      const nsAString& aReferrerPolicy,
                       const nsAString& aIntegrity);
 
     void PreloadImage(const nsAString& aURL,
                       const nsAString& aCrossOrigin,
                       const nsAString& aSrcset,
                       const nsAString& aSizes,
                       const nsAString& aImageReferrerPolicy);
 
--- a/taskcluster/ci/test/test-platforms.yml
+++ b/taskcluster/ci/test/test-platforms.yml
@@ -172,25 +172,27 @@ windows7-32/debug:
     test-sets:
         - windows-tests
 windows7-32/opt:
     build-platform: win32/opt
     test-sets:
         - awsy
         - desktop-screenshot-capture
         - windows-talos
+        - windows-talos-stylo
         - windows-tests
 
 windows7-32-pgo/opt:
     build-platform: win32-pgo/opt
     test-sets:
         - awsy
         - desktop-screenshot-capture
         - windows-tests
         - windows-talos
+        - windows-talos-stylo
 
 windows7-32-nightly/opt:
     build-platform: win32-nightly/opt
     test-sets:
         - awsy
         - desktop-screenshot-capture
         - windows-tests
 
@@ -208,24 +210,26 @@ windows10-64/debug:
         - windows-tests
 
 windows10-64/opt:
     build-platform: win64/opt
     test-sets:
         - awsy
         - desktop-screenshot-capture
         - windows-talos
+        - windows-talos-stylo
         - windows-tests
 
 windows10-64-pgo/opt:
     build-platform: win64-pgo/opt
     test-sets:
         - awsy
         - desktop-screenshot-capture
         - windows-talos
+        - windows-talos-stylo
         - windows-tests
 
 windows10-64-nightly/opt:
     build-platform: win64-nightly/opt
     test-sets:
         - awsy
         - desktop-screenshot-capture
         - windows-tests
--- a/taskcluster/ci/test/test-sets.yml
+++ b/taskcluster/ci/test/test-sets.yml
@@ -170,16 +170,30 @@ windows-talos:
     - talos-g5
     - talos-other
     - talos-perf-reftest
     - talos-perf-reftest-singletons
     - talos-svgr
     - talos-tp5o
     - talos-xperf
     - talos-tp6
+
+windows-talos-stylo:
+    - talos-chrome-stylo
+    - talos-dromaeojs-stylo
+    - talos-g1-stylo
+    - talos-g2-stylo
+    - talos-g4-stylo
+    - talos-g5-stylo
+    - talos-other-stylo
+    - talos-perf-reftest-stylo
+    - talos-perf-reftest-singletons-stylo
+    - talos-svgr-stylo
+    - talos-tp5o-stylo
+    - talos-xperf-stylo
     - talos-tp6-stylo
     - talos-tp6-stylo-threads
 
 macosx64-tests:
     - cppunit
     - crashtest
     - firefox-ui-functional-local
     - firefox-ui-functional-remote
--- a/taskcluster/ci/test/tests.yml
+++ b/taskcluster/ci/test/tests.yml
@@ -1266,16 +1266,43 @@ talos-chrome:
                     - talos/windows_config.py
                 default:
                     - talos/linux_config.py
         extra-options:
             - --suite=chromez
             - --add-option
             - --webServer,localhost
 
+talos-chrome-stylo:
+    description: "Talos Stylo chrome"
+    suite: talos
+    try-name: chromez-stylo
+    treeherder-symbol: tc-Ts(c)
+    virtualization: hardware
+    run-on-projects:
+        by-test-platform:
+            windows.*: ['mozilla-central', 'try']
+            default: []
+    max-run-time: 3600
+    mozharness:
+        script: talos_script.py
+        no-read-buildbot-config: true
+        config:
+            by-test-platform:
+                macosx.*:
+                    - talos/mac_config.py
+                windows.*:
+                    - talos/windows_config.py
+                default:
+                    - talos/linux_config.py
+        extra-options:
+            - --suite=chromez-stylo
+            - --add-option
+            - --webServer,localhost
+
 talos-dromaeojs:
     description: "Talos dromaeojs"
     suite: talos
     try-name: dromaeojs
     treeherder-symbol: tc-T(d)
     virtualization: hardware
     run-on-projects:
         by-test-platform:
@@ -1294,16 +1321,43 @@ talos-dromaeojs:
                     - talos/windows_config.py
                 default:
                     - talos/linux_config.py
         extra-options:
             - --suite=dromaeojs
             - --add-option
             - --webServer,localhost
 
+talos-dromaeojs-stylo:
+    description: "Talos Stylo dromaeojs"
+    suite: talos
+    try-name: dromaeojs-stylo
+    treeherder-symbol: tc-Ts(d)
+    virtualization: hardware
+    run-on-projects:
+        by-test-platform:
+            windows.*: ['mozilla-central', 'try']
+            default: []
+    max-run-time: 3600
+    mozharness:
+        script: talos_script.py
+        no-read-buildbot-config: true
+        config:
+            by-test-platform:
+                macosx.*:
+                    - talos/mac_config.py
+                windows.*:
+                    - talos/windows_config.py
+                default:
+                    - talos/linux_config.py
+        extra-options:
+            - --suite=dromaeojs-stylo
+            - --add-option
+            - --webServer,localhost
+
 talos-g1:
     description: "Talos g1"
     suite: talos
     try-name: g1
     treeherder-symbol: tc-T(g1)
     virtualization: hardware
     run-on-projects:
         by-test-platform:
@@ -1322,16 +1376,43 @@ talos-g1:
                     - talos/windows_config.py
                 default:
                     - talos/linux_config.py
         extra-options:
             - --suite=g1
             - --add-option
             - --webServer,localhost
 
+talos-g1-stylo:
+    description: "Talos Stylo g1"
+    suite: talos
+    try-name: g1-stylo
+    treeherder-symbol: tc-Ts(g1)
+    virtualization: hardware
+    run-on-projects:
+        by-test-platform:
+            windows.*: ['mozilla-central', 'try']
+            default: []
+    max-run-time: 7200
+    mozharness:
+        script: talos_script.py
+        no-read-buildbot-config: true
+        config:
+            by-test-platform:
+                macosx.*:
+                    - talos/mac_config.py
+                windows.*:
+                    - talos/windows_config.py
+                default:
+                    - talos/linux_config.py
+        extra-options:
+            - --suite=g1-stylo
+            - --add-option
+            - --webServer,localhost
+
 talos-g2:
     description: "Talos g2"
     suite: talos
     try-name: g2
     treeherder-symbol: tc-T(g2)
     virtualization: hardware
     max-run-time: 7200
     run-on-projects:
@@ -1350,16 +1431,43 @@ talos-g2:
                     - talos/windows_config.py
                 default:
                     - talos/linux_config.py
         extra-options:
             - --suite=g2
             - --add-option
             - --webServer,localhost
 
+talos-g2-stylo:
+    description: "Talos Stylo g2"
+    suite: talos
+    try-name: g2-stylo
+    treeherder-symbol: tc-Ts(g2)
+    virtualization: hardware
+    max-run-time: 7200
+    run-on-projects:
+        by-test-platform:
+            windows.*: ['mozilla-central', 'try']
+            default: []
+    mozharness:
+        script: talos_script.py
+        no-read-buildbot-config: true
+        config:
+            by-test-platform:
+                macosx.*:
+                    - talos/mac_config.py
+                windows.*:
+                    - talos/windows_config.py
+                default:
+                    - talos/linux_config.py
+        extra-options:
+            - --suite=g2-stylo
+            - --add-option
+            - --webServer,localhost
+
 talos-g3:
     description: "Talos g3"
     suite: talos
     try-name: g3
     treeherder-symbol: tc-T(g3)
     virtualization: hardware
     run-on-projects:
         by-test-platform:
@@ -1406,16 +1514,43 @@ talos-g4:
                     - talos/windows_config.py
                 default:
                     - talos/linux_config.py
         extra-options:
             - --suite=g4
             - --add-option
             - --webServer,localhost
 
+talos-g4-stylo:
+    description: "Talos Stylo g4"
+    suite: talos
+    try-name: g4-stylo
+    treeherder-symbol: tc-Ts(g4)
+    virtualization: hardware
+    run-on-projects:
+        by-test-platform:
+            windows.*: ['mozilla-central', 'try']
+            default: []
+    max-run-time: 3600
+    mozharness:
+        script: talos_script.py
+        no-read-buildbot-config: true
+        config:
+            by-test-platform:
+                macosx.*:
+                    - talos/mac_config.py
+                windows.*:
+                    - talos/windows_config.py
+                default:
+                    - talos/linux_config.py
+        extra-options:
+            - --suite=g4-stylo
+            - --add-option
+            - --webServer,localhost
+
 talos-g5:
     description: "Talos g5"
     suite: talos
     try-name: g5
     treeherder-symbol: tc-T(g5)
     virtualization: hardware
     run-on-projects:
         by-test-platform:
@@ -1435,16 +1570,44 @@ talos-g5:
                 default:
                     - talos/linux_config.py
                     - remove_executables.py
         extra-options:
             - --suite=g5
             - --add-option
             - --webServer,localhost
 
+talos-g5-stylo:
+    description: "Talos Stylo g5"
+    suite: talos
+    try-name: g5-stylo
+    treeherder-symbol: tc-Ts(g5)
+    virtualization: hardware
+    run-on-projects:
+        by-test-platform:
+            windows.*: ['mozilla-central', 'try']
+            default: []
+    max-run-time: 3600
+    mozharness:
+        script: talos_script.py
+        no-read-buildbot-config: true
+        config:
+            by-test-platform:
+                macosx.*:
+                    - talos/mac_config.py
+                windows.*:
+                    - talos/windows_config.py
+                default:
+                    - talos/linux_config.py
+                    - remove_executables.py
+        extra-options:
+            - --suite=g5-stylo
+            - --add-option
+            - --webServer,localhost
+
 talos-other:
     description: "Talos other"
     suite: talos
     try-name: other
     treeherder-symbol: tc-T(o)
     virtualization: hardware
     run-on-projects:
         by-test-platform:
@@ -1463,16 +1626,43 @@ talos-other:
                     - talos/windows_config.py
                 default:
                     - talos/linux_config.py
         extra-options:
             - --suite=other
             - --add-option
             - --webServer,localhost
 
+talos-other-stylo:
+    description: "Talos Stylo other"
+    suite: talos
+    try-name: other-stylo
+    treeherder-symbol: tc-Ts(o)
+    virtualization: hardware
+    run-on-projects:
+        by-test-platform:
+            windows.*: ['mozilla-central', 'try']
+            default: []
+    max-run-time: 3600
+    mozharness:
+        script: talos_script.py
+        no-read-buildbot-config: true
+        config:
+            by-test-platform:
+                macosx.*:
+                    - talos/mac_config.py
+                windows.*:
+                    - talos/windows_config.py
+                default:
+                    - talos/linux_config.py
+        extra-options:
+            - --suite=other-stylo
+            - --add-option
+            - --webServer,localhost
+
 talos-perf-reftest:
     description: "Talos perf-reftest"
     suite: talos
     try-name: perf-reftest
     treeherder-symbol: tc-T(p)
     virtualization: hardware
     run-on-projects:
         by-test-platform:
@@ -1513,16 +1703,64 @@ talos-perf-reftest-singletons:
                     - talos/mac_config.py
                 windows.*:
                     - talos/windows_config.py
                 default:
                     - talos/linux_config.py
         extra-options:
             - --suite=perf-reftest-singletons
 
+talos-perf-reftest-singletons-stylo:
+    description: "Talos Stylo perf-reftest singletons"
+    suite: talos
+    try-name: perf-reftest-singletons-stylo
+    treeherder-symbol: tc-Ts(ps)
+    virtualization: hardware
+    run-on-projects:
+        by-test-platform:
+            windows.*: ['mozilla-central', 'try']
+            default: []
+    max-run-time: 3600
+    mozharness:
+        script: talos_script.py
+        no-read-buildbot-config: true
+        config:
+            by-test-platform:
+                macosx.*:
+                    - talos/mac_config.py
+                windows.*:
+                    - talos/windows_config.py
+                default:
+                    - talos/linux_config.py
+        extra-options:
+            - --suite=perf-reftest-singletons-stylo
+
+talos-perf-reftest-stylo:
+    description: "Talos Stylo perf-reftest"
+    suite: talos
+    try-name: perf-reftest-stylo
+    treeherder-symbol: tc-Ts(p)
+    virtualization: hardware
+    run-on-projects:
+        by-test-platform:
+            windows.*: ['mozilla-central', 'try']
+            default: []
+    max-run-time: 3600
+    mozharness:
+        script: talos_script.py
+        no-read-buildbot-config: true
+        config:
+            by-test-platform:
+                windows.*:
+                    - talos/windows_config.py
+                default:
+                    - talos/linux_config.py
+        extra-options:
+            - --suite=perf-reftest-stylo
+
 talos-svgr:
     description: "Talos svgr"
     suite: talos
     try-name: svgr
     treeherder-symbol: tc-T(s)
     virtualization: hardware
     run-on-projects:
         by-test-platform:
@@ -1541,16 +1779,43 @@ talos-svgr:
                     - talos/windows_config.py
                 default:
                     - talos/linux_config.py
         extra-options:
             - --suite=svgr
             - --add-option
             - --webServer,localhost
 
+talos-svgr-stylo:
+    description: "Talos Stylo svgr"
+    suite: talos
+    try-name: svgr-stylo
+    treeherder-symbol: tc-Ts(s)
+    virtualization: hardware
+    run-on-projects:
+        by-test-platform:
+            windows.*: ['mozilla-central', 'try']
+            default: []
+    max-run-time: 3600
+    mozharness:
+        script: talos_script.py
+        no-read-buildbot-config: true
+        config:
+            by-test-platform:
+                macosx.*:
+                    - talos/mac_config.py
+                windows.*:
+                    - talos/windows_config.py
+                default:
+                    - talos/linux_config.py
+        extra-options:
+            - --suite=svgr-stylo
+            - --add-option
+            - --webServer,localhost
+
 talos-tp5o:
     description: "Talos tp5o"
     suite: talos
     try-name: tp5o
     treeherder-symbol: tc-T(tp)
     virtualization: hardware
     run-on-projects:
         by-test-platform:
@@ -1569,16 +1834,43 @@ talos-tp5o:
                     - talos/windows_config.py
                 default:
                     - talos/linux_config.py
         extra-options:
             - --suite=tp5o
             - --add-option
             - --webServer,localhost
 
+talos-tp5o-stylo:
+    description: "Talos Stylo tp5o"
+    suite: talos
+    try-name: tp5o-stylo
+    treeherder-symbol: tc-Ts(tp)
+    virtualization: hardware
+    run-on-projects:
+        by-test-platform:
+            windows.*: ['mozilla-central', 'try']
+            default: []
+    max-run-time: 3600
+    mozharness:
+        script: talos_script.py
+        no-read-buildbot-config: true
+        config:
+            by-test-platform:
+                macosx.*:
+                    - talos/mac_config.py
+                windows.*:
+                    - talos/windows_config.py
+                default:
+                    - talos/linux_config.py
+        extra-options:
+            - --suite=tp5o-stylo
+            - --add-option
+            - --webServer,localhost
+
 talos-tp6:
     description: "Talos Tp6"
     suite: talos
     try-name: tp6
     treeherder-symbol: tc-T(tp6)
     run-on-projects:
         by-test-platform:
             windows.*: ['mozilla-beta', 'mozilla-central', 'mozilla-inbound', 'autoland', 'try', 'date']
@@ -1596,20 +1888,20 @@ talos-tp6:
                 default:
                     - talos/linux_config.py
         extra-options:
             - --suite=tp6
             - --add-option
             - --webServer,localhost
 
 talos-tp6-stylo:
-    description: "Talos Tp6 Stylo"
+    description: "Talos Stylo Tp6"
     suite: talos
     try-name: tp6-stylo
-    treeherder-symbol: tc-T(tp6s)
+    treeherder-symbol: tc-Ts(tp6s)
     virtualization: hardware
     run-on-projects:
         by-test-platform:
             windows.*: ['mozilla-beta', 'mozilla-central', 'mozilla-inbound', 'autoland', 'try']
             macosx64-stylo/.*: []
             default: []
     max-run-time: 3600
     mozharness:
@@ -1622,20 +1914,20 @@ talos-tp6-stylo:
                 windows.*:
                     - talos/windows_config.py
                 default:
                     - talos/linux_config.py
         extra-options:
             - --suite=tp6-stylo
 
 talos-tp6-stylo-threads:
-    description: "Talos Tp6 Stylo Threads"
+    description: "Talos Stylo Threads Tp6"
     suite: talos
     try-name: tp6-stylo-threads
-    treeherder-symbol: tc-T(tp6st)
+    treeherder-symbol: tc-Ts(tp6st)
     virtualization: hardware
     run-on-projects:
         by-test-platform:
             windows.*: ['mozilla-beta', 'mozilla-central', 'mozilla-inbound', 'autoland', 'try']
             macosx64-stylo/.*: []
             default: []
     max-run-time: 3600
     mozharness:
@@ -1674,16 +1966,42 @@ talos-xperf:
                     - talos/windows_config.py
                 default:
                     - talos/linux_config.py
         extra-options:
             - --suite=xperf
             - --add-option
             - --webServer,localhost
 
+talos-xperf-stylo:
+    description: "Talos Stylo xperf"
+    suite: talos
+    try-name: xperf-stylo
+    treeherder-symbol: tc-Ts(x)
+    run-on-projects:
+        by-test-platform:
+            windows7-32.*: ['mozilla-central', 'try']
+            default: []
+    max-run-time: 3600
+    mozharness:
+        script: talos_script.py
+        no-read-buildbot-config: true
+        config:
+            by-test-platform:
+                macosx.*:
+                    - talos/mac_config.py
+                windows.*:
+                    - talos/windows_config.py
+                default:
+                    - talos/linux_config.py
+        extra-options:
+            - --suite=xperf-stylo
+            - --add-option
+            - --webServer,localhost
+
 telemetry-tests-client:
     description: "Telemetry tests client run"
     suite: telemetry-tests-client
     treeherder-symbol: tc-e10s
     max-run-time: 5400
     checkout: true
     tier: 3
     mozharness:
--- a/taskcluster/taskgraph/transforms/task.py
+++ b/taskcluster/taskgraph/transforms/task.py
@@ -439,17 +439,19 @@ GROUP_NAMES = {
     'tc-Fxfn-r': 'Firefox functional tests (remote) executed by TaskCluster',
     'tc-Fxfn-r-e10s': 'Firefox functional tests (remote) executed by TaskCluster with e10s',
     'tc-M': 'Mochitests executed by TaskCluster',
     'tc-M-e10s': 'Mochitests executed by TaskCluster with e10s',
     'tc-M-V': 'Mochitests on Valgrind executed by TaskCluster',
     'tc-R': 'Reftests executed by TaskCluster',
     'tc-R-e10s': 'Reftests executed by TaskCluster with e10s',
     'tc-T': 'Talos performance tests executed by TaskCluster',
+    'tc-Ts': 'Talos Stylo performance tests executed by TaskCluster',
     'tc-T-e10s': 'Talos performance tests executed by TaskCluster with e10s',
+    'tc-Ts-e10s': 'Talos Stylo performance tests executed by TaskCluster with e10s',
     'tc-tt-c': 'Telemetry client marionette tests',
     'tc-tt-c-e10s': 'Telemetry client marionette tests with e10s',
     'tc-SY-e10s': 'Are we slim yet tests by TaskCluster with e10s',
     'tc-SY-stylo-e10s': 'Are we slim yet tests by TaskCluster with e10s, stylo',
     'tc-SY-stylo-seq-e10s': 'Are we slim yet tests by TaskCluster with e10s, stylo sequential',
     'tc-VP': 'VideoPuppeteer tests executed by TaskCluster',
     'tc-W': 'Web platform tests executed by TaskCluster',
     'tc-W-e10s': 'Web platform tests executed by TaskCluster with e10s',
--- a/testing/mochitest/runtestsremote.py
+++ b/testing/mochitest/runtestsremote.py
@@ -31,42 +31,47 @@ class MochiRemote(MochitestDesktop):
     def __init__(self, automation, devmgr, options):
         MochitestDesktop.__init__(self, options.flavor, options)
 
         self._automation = automation
         self._dm = devmgr
         self.environment = self._automation.environment
         self.remoteProfile = os.path.join(options.remoteTestRoot, "profile/")
         self.remoteModulesDir = os.path.join(options.remoteTestRoot, "modules/")
+        self.remoteCache = os.path.join(options.remoteTestRoot, "cache/")
         self._automation.setRemoteProfile(self.remoteProfile)
         self.remoteLog = options.remoteLogFile
         self.localLog = options.logFile
         self._automation.deleteANRs()
         self._automation.deleteTombstones()
         self.certdbNew = True
         self.remoteMozLog = os.path.join(options.remoteTestRoot, "mozlog")
         self._dm.removeDir(self.remoteMozLog)
         self._dm.mkDir(self.remoteMozLog)
         self.remoteChromeTestDir = os.path.join(
             options.remoteTestRoot,
             "chrome")
         self._dm.removeDir(self.remoteChromeTestDir)
         self._dm.mkDir(self.remoteChromeTestDir)
         self._dm.removeDir(self.remoteProfile)
+        self._dm.removeDir(self.remoteCache)
+        # move necko cache to a location that can be cleaned up
+        options.extraPrefs += ["browser.cache.disk.parent_directory=%s" % self.remoteCache]
 
     def cleanup(self, options):
         if self._dm.fileExists(self.remoteLog):
             self._dm.getFile(self.remoteLog, self.localLog)
             self._dm.removeFile(self.remoteLog)
         else:
             self.log.warning(
                 "Unable to retrieve log file (%s) from remote device" %
                 self.remoteLog)
+        self._dm.removeDir(self.remoteChromeTestDir)
         self._dm.removeDir(self.remoteProfile)
-        self._dm.removeDir(self.remoteChromeTestDir)
+        self._dm.removeDir(self.remoteCache)
         blobberUploadDir = os.environ.get('MOZ_UPLOAD_DIR', None)
         if blobberUploadDir:
             self._dm.getDirectory(self.remoteMozLog, blobberUploadDir)
         MochitestDesktop.cleanup(self, options)
 
     def findPath(self, paths, filename=None):
         for path in paths:
             p = path
--- a/testing/talos/talos.json
+++ b/testing/talos/talos.json
@@ -1,58 +1,115 @@
 {
     "suites": {
         "chromez-e10s": {
             "tests": ["tresize", "tcanvasmark"]
         },
+        "chromez-stylo-e10s": {
+            "talos_options": ["--stylo"],
+            "tests": ["tresize", "tcanvasmark"]
+        },
         "dromaeojs-e10s": {
             "tests": ["dromaeo_css", "kraken"]
         },
+        "dromaeojs-stylo-e10s": {
+            "talos_options": ["--stylo"],
+            "tests": ["dromaeo_css", "kraken"]
+        },
         "other-e10s": {
             "tests": ["a11yr", "ts_paint", "tpaint", "sessionrestore", "sessionrestore_many_windows", "sessionrestore_no_auto_restore", "tabpaint"]
         },
+        "other-stylo-e10s": {
+            "talos_options": ["--stylo"],
+            "tests": ["a11yr", "ts_paint", "tpaint", "sessionrestore", "sessionrestore_many_windows", "sessionrestore_no_auto_restore", "tabpaint"]
+        },
         "g1-e10s": {
             "tests": ["tp5o_scroll", "glterrain"],
             "pagesets_name": "tp5n.zip"
         },
+        "g1-stylo-e10s": {
+            "tests": ["tp5o_scroll", "glterrain"],
+            "talos_options": ["--stylo"],
+            "pagesets_name": "tp5n.zip"
+        },
         "g2-e10s": {
             "tests": ["damp", "tps"],
             "pagesets_name": "tp5n.zip"
         },
+        "g2-stylo-e10s": {
+            "tests": ["damp", "tps"],
+            "talos_options": ["--stylo"],
+            "pagesets_name": "tp5n.zip"
+        },
         "g3-e10s": {
             "tests": ["dromaeo_dom"]
         },
         "g4-e10s": {
             "tests": ["basic_compositor_video", "glvideo"]
         },
+        "g4-stylo-e10s": {
+            "tests": ["basic_compositor_video", "glvideo"],
+            "talos_options": ["--stylo"]
+        },
         "g5-e10s": {
             "tests": ["ts_paint_webext", "tp5o_webext"],
             "pagesets_name": "tp5n.zip"
         },
+        "g5-stylo-e10s": {
+            "tests": ["ts_paint_webext", "tp5o_webext"],
+            "talos_options": ["--stylo"],
+            "pagesets_name": "tp5n.zip"
+        },
         "svgr-e10s": {
             "tests": ["tsvgx", "tsvgr_opacity", "tart", "tscrollx", "tsvg_static"]
         },
+        "svgr-stylo-e10s": {
+            "tests": ["tsvgx", "tsvgr_opacity", "tart", "tscrollx", "tsvg_static"],
+            "talos_options": ["--stylo"]
+        },
         "perf-reftest-e10s": {
             "tests": ["bloom_basic"]
         },
+        "perf-reftest-stylo-e10s": {
+            "tests": ["bloom_basic"],
+            "talos_options": ["--stylo"]
+        },
         "perf-reftest-singletons-e10s": {
             "tests": ["bloom_basic_singleton"]
         },
+        "perf-reftest-singletons-stylo-e10s": {
+            "tests": ["bloom_basic_singleton"],
+            "talos_options": ["--stylo"]
+        },
         "tp5o-e10s": {
             "tests": ["tp5o"],
             "pagesets_name": "tp5n.zip"
         },
+        "tp5o-stylo-e10s": {
+            "tests": ["tp5o"],
+            "pagesets_name": "tp5n.zip",
+            "talos_options": ["--stylo"]
+        },
         "xperf-e10s": {
             "tests": ["tp5n"],
             "pagesets_name": "tp5n.zip",
             "talos_options": [
                 "--xperf_path",
                 "\"c:/Program Files/Microsoft Windows Performance Toolkit/xperf.exe\""
             ]
         },
+        "xperf-stylo-e10s": {
+            "tests": ["tp5n"],
+            "pagesets_name": "tp5n.zip",
+            "talos_options": [
+                "--stylo",
+                "--xperf_path",
+                "\"c:/Program Files/Microsoft Windows Performance Toolkit/xperf.exe\""
+            ]
+        },
         "tp6-e10s": {
             "tests": ["tp6_google", "tp6_youtube", "tp6_amazon", "tp6_facebook"],
             "mitmproxy_recording_set": "mitmproxy-recording-set-win10.zip",
             "talos_options": [
                 "--mitmproxy",
                 "mitmproxy-recording-google.mp mitmproxy-recording-youtube.mp mitmproxy-recording-amazon.mp mitmproxy-recording-facebook.mp",
                 "--firstNonBlankPaint"
             ]
--- a/testing/xpcshell/mach_commands.py
+++ b/testing/xpcshell/mach_commands.py
@@ -129,17 +129,17 @@ class XPCShellRunner(MozbuildObject):
             if isinstance(v, unicode_type):
                 v = v.encode('utf-8')
 
             if isinstance(k, unicode_type):
                 k = k.encode('utf-8')
 
             filtered_args[k] = v
 
-        result = xpcshell.runTests(**filtered_args)
+        result = xpcshell.runTests(filtered_args)
 
         self.log_manager.disable_unstructured()
 
         if not result and not xpcshell.sequential:
             print("Tests were run in parallel. Try running with --sequential "
                   "to make sure the failures were not caused by this.")
         return int(not result)
 
@@ -201,22 +201,20 @@ class AndroidXPCShellRunner(MozbuildObje
                     print ("using APK: %s" % kwargs["localAPK"])
                     break
             else:
                 raise Exception("APK not found in objdir. You must specify an APK.")
 
         if not kwargs["sequential"]:
             kwargs["sequential"] = True
 
-        options = argparse.Namespace(**kwargs)
-        xpcshell = remotexpcshelltests.XPCShellRemote(dm, options, log)
+        xpcshell = remotexpcshelltests.XPCShellRemote(dm, kwargs, log)
 
-        result = xpcshell.runTests(testClass=remotexpcshelltests.RemoteXPCShellTestThread,
-                                   mobileArgs=xpcshell.mobileArgs,
-                                   **vars(options))
+        result = xpcshell.runTests(kwargs, testClass=remotexpcshelltests.RemoteXPCShellTestThread,
+                                   mobileArgs=xpcshell.mobileArgs)
 
         self.log_manager.disable_unstructured()
 
         return int(not result)
 
 
 def get_parser():
     build_obj = MozbuildObject.from_environment(cwd=here)
--- a/testing/xpcshell/remotexpcshelltests.py
+++ b/testing/xpcshell/remotexpcshelltests.py
@@ -6,16 +6,17 @@
 
 import logging
 import posixpath
 import sys, os
 import subprocess
 import runxpcshelltests as xpcshell
 import tempfile
 import time
+from argparse import Namespace
 from zipfile import ZipFile
 from mozlog import commandline
 import shutil
 import mozdevice
 import mozfile
 import mozinfo
 
 from xpcshellcommandline import parser_remote
@@ -117,17 +118,17 @@ class RemoteXPCShellTestThread(xpcshell.
         self.headJSPath = remoteJoin(self.remoteScriptsDir, 'head.js')
         self.httpdJSPath = remoteJoin(self.remoteComponentsDir, 'httpd.js')
         self.httpdManifest = remoteJoin(self.remoteComponentsDir, 'httpd.manifest')
         self.testingModulesDir = self.remoteModulesDir
         self.testharnessdir = self.remoteScriptsDir
         xpcshell.XPCShellTestThread.buildXpcsCmd(self)
         # remove "-g <dir> -a <dir>" and add "--greomni <apk>"
         del(self.xpcsCmd[1:5])
-        if self.options.localAPK:
+        if self.options['localAPK']:
             self.xpcsCmd.insert(3, '--greomni')
             self.xpcsCmd.insert(4, self.remoteAPK)
 
         if self.remoteDebugger:
             # for example, "/data/local/gdbserver" "localhost:12345"
             self.xpcsCmd = [
               self.remoteDebugger,
               self.remoteDebuggerArgs,
@@ -235,18 +236,18 @@ class XPCShellRemote(xpcshell.XPCShellTe
     def __init__(self, devmgr, options, log):
         xpcshell.XPCShellTests.__init__(self, log)
 
         # Add Android version (SDK level) to mozinfo so that manifest entries
         # can be conditional on android_version.
         androidVersion = devmgr.shellCheckOutput(['getprop', 'ro.build.version.sdk'])
         mozinfo.info['android_version'] = androidVersion
 
-        self.localLib = options.localLib
-        self.localBin = options.localBin
+        self.localLib = options['localLib']
+        self.localBin = options['localBin']
         self.options = options
         self.device = devmgr
         self.pathMapping = []
         self.remoteTestRoot = "%s/xpc" % self.device.deviceRoot
         # remoteBinDir contains xpcshell and its wrapper script, both of which must
         # be executable. Since +x permissions cannot usually be set on /mnt/sdcard,
         # and the test root may be on /mnt/sdcard, remoteBinDir is set to be on
         # /data/local, always.
@@ -258,40 +259,40 @@ class XPCShellRemote(xpcshell.XPCShellTe
         # line can be quite complex.
         self.remoteTmpDir = remoteJoin(self.remoteTestRoot, "tmp")
         self.remoteScriptsDir = self.remoteTestRoot
         self.remoteComponentsDir = remoteJoin(self.remoteTestRoot, "c")
         self.remoteModulesDir = remoteJoin(self.remoteTestRoot, "m")
         self.remoteMinidumpDir = remoteJoin(self.remoteTestRoot, "minidumps")
         self.remoteClearDirScript = remoteJoin(self.remoteBinDir, "cleardir")
         self.profileDir = remoteJoin(self.remoteTestRoot, "p")
-        self.remoteDebugger = options.debugger
-        self.remoteDebuggerArgs = options.debuggerArgs
-        self.testingModulesDir = options.testingModulesDir
+        self.remoteDebugger = options['debugger']
+        self.remoteDebuggerArgs = options['debuggerArgs']
+        self.testingModulesDir = options['testingModulesDir']
 
         self.env = {}
 
-        if self.options.objdir:
-            self.xpcDir = os.path.join(self.options.objdir, "_tests/xpcshell")
+        if options['objdir']:
+            self.xpcDir = os.path.join(options['objdir'], "_tests/xpcshell")
         elif os.path.isdir(os.path.join(here, 'tests')):
             self.xpcDir = os.path.join(here, 'tests')
         else:
             print >> sys.stderr, "Couldn't find local xpcshell test directory"
             sys.exit(1)
 
-        if options.localAPK:
-            self.localAPKContents = ZipFile(options.localAPK)
-        if options.setup:
+        if options['localAPK']:
+            self.localAPKContents = ZipFile(options['localAPK'])
+        if options['setup']:
             self.setupTestDir()
             self.setupUtilities()
             self.setupModules()
         self.setupMinidumpDir()
         self.remoteAPK = None
-        if options.localAPK:
-            self.remoteAPK = remoteJoin(self.remoteBinDir, os.path.basename(options.localAPK))
+        if options['localAPK']:
+            self.remoteAPK = remoteJoin(self.remoteBinDir, os.path.basename(options['localAPK']))
             self.setAppRoot()
 
         # data that needs to be passed to the RemoteXPCShellTestThread
         self.mobileArgs = {
             'device': self.device,
             'remoteBinDir': self.remoteBinDir,
             'remoteScriptsDir': self.remoteScriptsDir,
             'remoteComponentsDir': self.remoteComponentsDir,
@@ -348,32 +349,32 @@ class XPCShellRemote(xpcshell.XPCShellTe
         os.remove(localWrapper)
 
         self.device.chmodDir(self.remoteBinDir)
 
     def buildEnvironment(self):
         self.buildCoreEnvironment()
         self.setLD_LIBRARY_PATH()
         self.env["MOZ_LINKER_CACHE"] = self.remoteBinDir
-        if self.options.localAPK and self.appRoot:
+        if self.options['localAPK'] and self.appRoot:
             self.env["GRE_HOME"] = self.appRoot
         self.env["XPCSHELL_TEST_PROFILE_DIR"] = self.profileDir
         self.env["TMPDIR"] = self.remoteTmpDir
         self.env["HOME"] = self.profileDir
         self.env["XPCSHELL_TEST_TEMP_DIR"] = self.remoteTmpDir
         self.env["XPCSHELL_MINIDUMP_DIR"] = self.remoteMinidumpDir
-        if self.options.setup:
+        if self.options['setup']:
             self.pushWrapper()
 
     def setAppRoot(self):
         # Determine the application root directory associated with the package
         # name used by the Fennec APK.
         self.appRoot = None
         packageName = None
-        if self.options.localAPK:
+        if self.options['localAPK']:
             try:
                 packageName = self.localAPKContents.read("package-name.txt")
                 if packageName:
                     self.appRoot = self.device.getAppRoot(packageName.strip())
             except Exception as detail:
                 print "unable to determine app root: " + str(detail)
                 pass
         return None
@@ -429,25 +430,25 @@ class XPCShellRemote(xpcshell.XPCShellTe
         local = os.path.join(self.localBin, "components/httpd.manifest")
         remoteFile = remoteJoin(self.remoteComponentsDir, "httpd.manifest")
         self.device.pushFile(local, remoteFile)
 
         local = os.path.join(self.localBin, "components/test_necko.xpt")
         remoteFile = remoteJoin(self.remoteComponentsDir, "test_necko.xpt")
         self.device.pushFile(local, remoteFile)
 
-        if self.options.localAPK:
-            remoteFile = remoteJoin(self.remoteBinDir, os.path.basename(self.options.localAPK))
-            self.device.pushFile(self.options.localAPK, remoteFile)
+        if self.options['localAPK']:
+            remoteFile = remoteJoin(self.remoteBinDir, os.path.basename(self.options['localAPK']))
+            self.device.pushFile(self.options['localAPK'], remoteFile)
 
         self.pushLibs()
 
     def pushLibs(self):
         pushed_libs_count = 0
-        if self.options.localAPK:
+        if self.options['localAPK']:
             try:
                 dir = tempfile.mkdtemp()
                 for info in self.localAPKContents.infolist():
                     if info.filename.endswith(".so"):
                         print >> sys.stderr, "Pushing %s.." % info.filename
                         remoteFile = remoteJoin(self.remoteBinDir, os.path.basename(info.filename))
                         self.localAPKContents.extract(info, dir)
                         localFile = os.path.join(dir, info.filename)
@@ -503,54 +504,57 @@ class XPCShellRemote(xpcshell.XPCShellTe
             # Foopies have an older mozdevice ver without retryLimit
             self.device.pushDir(self.xpcDir, self.remoteScriptsDir)
 
     def setupMinidumpDir(self):
         if self.device.dirExists(self.remoteMinidumpDir):
             self.device.removeDir(self.remoteMinidumpDir)
         self.device.mkDir(self.remoteMinidumpDir)
 
-    def buildTestList(self, test_tags=None, test_paths=None):
-        xpcshell.XPCShellTests.buildTestList(self, test_tags=test_tags, test_paths=test_paths)
+    def buildTestList(self, test_tags=None, test_paths=None, verify=False):
+        xpcshell.XPCShellTests.buildTestList(self, test_tags=test_tags, test_paths=test_paths, verify=verify)
         uniqueTestPaths = set([])
         for test in self.alltests:
             uniqueTestPaths.add(test['here'])
         for testdir in uniqueTestPaths:
             abbrevTestDir = os.path.relpath(testdir, self.xpcDir)
             remoteScriptDir = remoteJoin(self.remoteScriptsDir, abbrevTestDir)
             self.pathMapping.append(PathMapping(testdir, remoteScriptDir))
 
 def verifyRemoteOptions(parser, options):
-    if options.localLib is None:
-        if options.localAPK and options.objdir:
+    if isinstance(options, Namespace):
+        options = vars(options)
+
+    if options['localLib'] is None:
+        if options['localAPK'] and options['objdir']:
             for path in ['dist/fennec', 'fennec/lib']:
-                options.localLib = os.path.join(options.objdir, path)
-                if os.path.isdir(options.localLib):
+                options['localLib'] = os.path.join(options['objdir'], path)
+                if os.path.isdir(options['localLib']):
                     break
             else:
                 parser.error("Couldn't find local library dir, specify --local-lib-dir")
-        elif options.objdir:
-            options.localLib = os.path.join(options.objdir, 'dist/bin')
+        elif options['objdir']:
+            options['localLib'] = os.path.join(options['objdir'], 'dist/bin')
         elif os.path.isfile(os.path.join(here, '..', 'bin', 'xpcshell')):
             # assume tests are being run from a tests.zip
-            options.localLib = os.path.abspath(os.path.join(here, '..', 'bin'))
+            options['localLib'] = os.path.abspath(os.path.join(here, '..', 'bin'))
         else:
             parser.error("Couldn't find local library dir, specify --local-lib-dir")
 
-    if options.localBin is None:
-        if options.objdir:
+    if options['localBin'] is None:
+        if options['objdir']:
             for path in ['dist/bin', 'bin']:
-                options.localBin = os.path.join(options.objdir, path)
-                if os.path.isdir(options.localBin):
+                options['localBin'] = os.path.join(options['objdir'], path)
+                if os.path.isdir(options['localBin']):
                     break
             else:
                 parser.error("Couldn't find local binary dir, specify --local-bin-dir")
         elif os.path.isfile(os.path.join(here, '..', 'bin', 'xpcshell')):
             # assume tests are being run from a tests.zip
-            options.localBin = os.path.abspath(os.path.join(here, '..', 'bin'))
+            options['localBin'] = os.path.abspath(os.path.join(here, '..', 'bin'))
         else:
             parser.error("Couldn't find local binary dir, specify --local-bin-dir")
     return options
 
 class PathMapping:
 
     def __init__(self, localDir, remoteDir):
         self.local = localDir
@@ -574,36 +578,36 @@ def main():
             print >>sys.stderr, "Error: please specify an APK"
             sys.exit(1)
 
     options = verifyRemoteOptions(parser, options)
     log = commandline.setup_logging("Remote XPCShell",
                                     options,
                                     {"tbpl": sys.stdout})
 
-    dm_args = {'deviceRoot': options.remoteTestRoot}
-    if options.deviceIP:
-        dm_args['host'] = options.deviceIP
-        dm_args['port'] = options.devicePort
-    if options.log_tbpl_level == 'debug' or options.log_mach_level == 'debug':
+    dm_args = {'deviceRoot': options['remoteTestRoot']}
+    if options['deviceIP']:
+        dm_args['host'] = options['deviceIP']
+        dm_args['port'] = options['devicePort']
+    if options['log_tbpl_level'] == 'debug' or options['log_mach_level'] == 'debug':
         dm_args['logLevel'] = logging.DEBUG
     dm = mozdevice.DroidADB(**dm_args)
 
-    if options.interactive and not options.testPath:
+    if options['interactive'] and not options['testPath']:
         print >>sys.stderr, "Error: You must specify a test filename in interactive mode!"
         sys.exit(1)
 
-    if options.xpcshell is None:
-        options.xpcshell = "xpcshell"
+    if options['xpcshell'] is None:
+        options['xpcshell'] = "xpcshell"
 
     xpcsh = XPCShellRemote(dm, options, log)
 
     # we don't run concurrent tests on mobile
-    options.sequential = True
+    options['sequential'] = True
 
-    if not xpcsh.runTests(testClass=RemoteXPCShellTestThread,
-                          mobileArgs=xpcsh.mobileArgs,
-                          **vars(options)):
+    if not xpcsh.runTests(options,
+                          testClass=RemoteXPCShellTestThread,
+                          mobileArgs=xpcsh.mobileArgs):
         sys.exit(1)
 
 
 if __name__ == '__main__':
     main()
--- a/testing/xpcshell/runxpcshelltests.py
+++ b/testing/xpcshell/runxpcshelltests.py
@@ -15,18 +15,19 @@ import random
 import re
 import shutil
 import signal
 import sys
 import tempfile
 import time
 import traceback
 
-from argparse import ArgumentParser
+from argparse import ArgumentParser, Namespace
 from collections import defaultdict, deque, namedtuple
+from datetime import datetime, timedelta
 from distutils import dir_util
 from functools import partial
 from multiprocessing import cpu_count
 from subprocess import Popen, PIPE, STDOUT
 from tempfile import mkdtemp, gettempdir
 from threading import (
     Timer,
     Thread,
@@ -104,26 +105,25 @@ def cleanup_encoding(s):
 
 """ Control-C handling """
 gotSIGINT = False
 def markGotSIGINT(signum, stackFrame):
     global gotSIGINT
     gotSIGINT = True
 
 class XPCShellTestThread(Thread):
-    def __init__(self, test_object, event, cleanup_dir_list, retry=True,
-            app_dir_key=None, interactive=False,
-            verbose=False, pStdout=None, pStderr=None, keep_going=False,
-            log=None, usingTSan=False, **kwargs):
+    def __init__(self, test_object, retry=True, verbose=False, usingTSan=False,
+            **kwargs):
         Thread.__init__(self)
         self.daemon = True
 
         self.test_object = test_object
-        self.cleanup_dir_list = cleanup_dir_list
         self.retry = retry
+        self.verbose = verbose
+        self.usingTSan = usingTSan
 
         self.appPath = kwargs.get('appPath')
         self.xrePath = kwargs.get('xrePath')
         self.utility_path = kwargs.get('utility_path')
         self.testingModulesDir = kwargs.get('testingModulesDir')
         self.debuggerInfo = kwargs.get('debuggerInfo')
         self.jsDebuggerInfo = kwargs.get('jsDebuggerInfo')
         self.pluginsPath = kwargs.get('pluginsPath')
@@ -137,25 +137,23 @@ class XPCShellTestThread(Thread):
         self.symbolsPath = kwargs.get('symbolsPath')
         self.logfiles = kwargs.get('logfiles')
         self.xpcshell = kwargs.get('xpcshell')
         self.xpcsRunArgs = kwargs.get('xpcsRunArgs')
         self.failureManifest = kwargs.get('failureManifest')
         self.jscovdir = kwargs.get('jscovdir')
         self.stack_fixer_function = kwargs.get('stack_fixer_function')
         self._rootTempDir = kwargs.get('tempDir')
-
-        self.app_dir_key = app_dir_key
-        self.interactive = interactive
-        self.verbose = verbose
-        self.pStdout = pStdout
-        self.pStderr = pStderr
-        self.keep_going = keep_going
-        self.log = log
-        self.usingTSan = usingTSan
+        self.cleanup_dir_list = kwargs.get('cleanup_dir_list')
+        self.pStdout = kwargs.get('pStdout')
+        self.pStderr = kwargs.get('pStderr')
+        self.keep_going = kwargs.get('keep_going')
+        self.log = kwargs.get('log')
+        self.app_dir_key = kwargs.get('app_dir_key')
+        self.interactive = kwargs.get('interactive')
 
         # only one of these will be set to 1. adding them to the totals in
         # the harness
         self.passCount = 0
         self.todoCount = 0
         self.failCount = 0
 
         # Context for output processing
@@ -163,17 +161,17 @@ class XPCShellTestThread(Thread):
         self.has_failure_output = False
         self.saw_proc_start = False
         self.saw_proc_end = False
         self.complete_command = None
         self.harness_timeout = kwargs.get('harness_timeout')
         self.timedout = False
 
         # event from main thread to signal work done
-        self.event = event
+        self.event = kwargs.get('event')
         self.done = False # explicitly set flag so we don't rely on thread.isAlive
 
     def run(self):
         try:
             self.run_test()
         except Exception as e:
             self.exception = e
             self.traceback = traceback.format_exc()
@@ -844,28 +842,28 @@ class XPCShellTests(object):
             test_object['manifest'] = os.path.relpath(test_object['manifest'], root)
 
         if os.sep != '/':
             for key in ('id', 'manifest'):
                 test_object[key] = test_object[key].replace(os.sep, '/')
 
         return test_object
 
-    def buildTestList(self, test_tags=None, test_paths=None):
+    def buildTestList(self, test_tags=None, test_paths=None, verify=False):
         """
           read the xpcshell.ini manifest and set self.alltests to be
           an array of test objects.
 
           if we are chunking tests, it will be done here as well
         """
 
         if test_paths is None:
             test_paths = []
 
-        if len(test_paths) == 1 and test_paths[0].endswith(".js"):
+        if len(test_paths) == 1 and test_paths[0].endswith(".js") and not verify:
             self.singleFile = os.path.basename(test_paths[0])
         else:
             self.singleFile = None
 
         mp = self.getTestManifest(self.manifest)
 
         root = mp.rootdir
         if build and not root:
@@ -1109,141 +1107,17 @@ class XPCShellTests(object):
         else:
             self.xpcsRunArgs = ['-e', '_execute_test(); quit(0);']
 
     def addTestResults(self, test):
         self.passCount += test.passCount
         self.failCount += test.failCount
         self.todoCount += test.todoCount
 
-    def runTests(self, xpcshell=None, xrePath=None, appPath=None, symbolsPath=None,
-                 manifest=None, testPaths=None, mobileArgs=None, tempDir=None,
-                 interactive=False, verbose=False, keepGoing=False, logfiles=True,
-                 thisChunk=1, totalChunks=1, debugger=None,
-                 debuggerArgs=None, debuggerInteractive=False,
-                 profileName=None, mozInfo=None, sequential=False, shuffle=False,
-                 testingModulesDir=None, pluginsPath=None,
-                 testClass=XPCShellTestThread, failureManifest=None,
-                 log=None, stream=None, jsDebugger=False, jsDebuggerPort=0,
-                 test_tags=None, dump_tests=None, utility_path=None,
-                 rerun_failures=False, threadCount=NUM_THREADS,
-                 failure_manifest=None, jscovdir=None, **otherOptions):
-        """Run xpcshell tests.
-
-        |xpcshell|, is the xpcshell executable to use to run the tests.
-        |xrePath|, if provided, is the path to the XRE to use.
-        |appPath|, if provided, is the path to an application directory.
-        |symbolsPath|, if provided is the path to a directory containing
-          breakpad symbols for processing crashes in tests.
-        |manifest|, if provided, is a file containing a list of
-          test directories to run.
-        |testPaths|, if provided, is a list of paths to files or directories containing
-                     tests to run.
-        |pluginsPath|, if provided, custom plugins directory to be returned from
-          the xpcshell dir svc provider for NS_APP_PLUGINS_DIR_LIST.
-        |interactive|, if set to True, indicates to provide an xpcshell prompt
-          instead of automatically executing the test.
-        |verbose|, if set to True, will cause stdout/stderr from tests to
-          be printed always
-        |logfiles|, if set to False, indicates not to save output to log files.
-          Non-interactive only option.
-        |debugger|, if set, specifies the name of the debugger that will be used
-          to launch xpcshell.
-        |debuggerArgs|, if set, specifies arguments to use with the debugger.
-        |debuggerInteractive|, if set, allows the debugger to be run in interactive
-          mode.
-        |profileName|, if set, specifies the name of the application for the profile
-          directory if running only a subset of tests.
-        |mozInfo|, if set, specifies specifies build configuration information, either as a filename containing JSON, or a dict.
-        |shuffle|, if True, execute tests in random order.
-        |testingModulesDir|, if provided, specifies where JS modules reside.
-          xpcshell will register a resource handler mapping this path.
-        |tempDir|, if provided, specifies a temporary directory to use.
-        |otherOptions| may be present for the convenience of subclasses
-        """
-
-        global gotSIGINT
-
-        # Try to guess modules directory.
-        # This somewhat grotesque hack allows the buildbot machines to find the
-        # modules directory without having to configure the buildbot hosts. This
-        # code path should never be executed in local runs because the build system
-        # should always set this argument.
-        if not testingModulesDir:
-            possible = os.path.join(here, os.path.pardir, 'modules')
-
-            if os.path.isdir(possible):
-                testingModulesDir = possible
-
-        if rerun_failures:
-            if os.path.exists(failure_manifest):
-                rerun_manifest = os.path.join(os.path.dirname(failure_manifest), "rerun.ini")
-                shutil.copyfile(failure_manifest, rerun_manifest)
-                os.remove(failure_manifest)
-                manifest = rerun_manifest
-            else:
-                print >> sys.stderr, "No failures were found to re-run."
-                sys.exit(1)
-
-        if testingModulesDir:
-            # The resource loader expects native paths. Depending on how we were
-            # invoked, a UNIX style path may sneak in on Windows. We try to
-            # normalize that.
-            testingModulesDir = os.path.normpath(testingModulesDir)
-
-            if not os.path.isabs(testingModulesDir):
-                testingModulesDir = os.path.abspath(testingModulesDir)
-
-            if not testingModulesDir.endswith(os.path.sep):
-                testingModulesDir += os.path.sep
-
-        self.debuggerInfo = None
-
-        if debugger:
-            self.debuggerInfo = mozdebug.get_debugger_info(debugger, debuggerArgs, debuggerInteractive)
-
-        self.jsDebuggerInfo = None
-        if jsDebugger:
-            # A namedtuple let's us keep .port instead of ['port']
-            JSDebuggerInfo = namedtuple('JSDebuggerInfo', ['port'])
-            self.jsDebuggerInfo = JSDebuggerInfo(port=jsDebuggerPort)
-
-        self.xpcshell = xpcshell
-        self.xrePath = xrePath
-        self.utility_path = utility_path
-        self.appPath = appPath
-        self.symbolsPath = symbolsPath
-        self.tempDir = os.path.normpath(tempDir or tempfile.gettempdir())
-        self.manifest = manifest
-        self.dump_tests = dump_tests
-        self.interactive = interactive
-        self.verbose = verbose
-        self.keepGoing = keepGoing
-        self.logfiles = logfiles
-        self.totalChunks = totalChunks
-        self.thisChunk = thisChunk
-        self.profileName = profileName or "xpcshell"
-        self.mozInfo = mozInfo
-        self.testingModulesDir = testingModulesDir
-        self.pluginsPath = pluginsPath
-        self.sequential = sequential
-        self.failure_manifest = failure_manifest
-        self.threadCount = threadCount or NUM_THREADS
-        self.jscovdir = jscovdir
-
-        self.testCount = 0
-        self.passCount = 0
-        self.failCount = 0
-        self.todoCount = 0
-
-        self.setAbsPath()
-        self.buildXpcsRunArgs()
-
-        self.event = Event()
-
+    def updateMozinfo(self):
         # Handle filenames in mozInfo
         if not isinstance(self.mozInfo, dict):
             mozInfoFile = self.mozInfo
             if not os.path.isfile(mozInfoFile):
                 self.log.error("Error: couldn't find mozinfo.json at '%s'. Perhaps you need to use --build-info-json?" % mozInfoFile)
                 return False
             self.mozInfo = json.load(open(mozInfoFile))
 
@@ -1258,16 +1132,112 @@ class XPCShellTests(object):
         self.mozInfo = fixedInfo
 
         mozinfo.update(self.mozInfo)
 
         # Add a flag to mozinfo to indicate that code coverage is enabled.
         if self.jscovdir:
             mozinfo.update({"coverage": True})
 
+        return True
+
+    def runTests(self, options, testClass=XPCShellTestThread, mobileArgs=None):
+        """
+          Run xpcshell tests.
+        """
+
+        global gotSIGINT
+
+        # Number of times to repeat test(s) in --verify mode
+        VERIFY_REPEAT = 20
+
+        if isinstance(options, Namespace):
+            options = vars(options)
+
+        # Try to guess modules directory.
+        # This somewhat grotesque hack allows the buildbot machines to find the
+        # modules directory without having to configure the buildbot hosts. This
+        # code path should never be executed in local runs because the build system
+        # should always set this argument.
+        if not options.get('testingModulesDir'):
+            possible = os.path.join(here, os.path.pardir, 'modules')
+
+            if os.path.isdir(possible):
+                testingModulesDir = possible
+
+        if options.get('rerun_failures'):
+            if os.path.exists(options.get('failure_manifest')):
+                rerun_manifest = os.path.join(os.path.dirname(options['failure_manifest']), "rerun.ini")
+                shutil.copyfile(options['failure_manifest'], rerun_manifest)
+                os.remove(options['failure_manifest'])
+                manifest = rerun_manifest
+            else:
+                print >> sys.stderr, "No failures were found to re-run."
+                sys.exit(1)
+
+        if options.get('testingModulesDir'):
+            # The resource loader expects native paths. Depending on how we were
+            # invoked, a UNIX style path may sneak in on Windows. We try to
+            # normalize that.
+            testingModulesDir = os.path.normpath(options['testingModulesDir'])
+
+            if not os.path.isabs(testingModulesDir):
+                testingModulesDir = os.path.abspath(testingModulesDir)
+
+            if not testingModulesDir.endswith(os.path.sep):
+                testingModulesDir += os.path.sep
+
+        self.debuggerInfo = None
+
+        if options.get('debugger'):
+            self.debuggerInfo = mozdebug.get_debugger_info(options.get('debugger'),
+                options.get('debuggerArgs'), options.get('debuggerInteractive'))
+
+        self.jsDebuggerInfo = None
+        if options.get('jsDebugger'):
+            # A namedtuple let's us keep .port instead of ['port']
+            JSDebuggerInfo = namedtuple('JSDebuggerInfo', ['port'])
+            self.jsDebuggerInfo = JSDebuggerInfo(port=options['jsDebuggerPort'])
+
+        self.xpcshell = options.get('xpcshell')
+        self.xrePath = options.get('xrePath')
+        self.utility_path = options.get('utility_path')
+        self.appPath = options.get('appPath')
+        self.symbolsPath = options.get('symbolsPath')
+        self.tempDir = os.path.normpath(options.get('tempDir') or tempfile.gettempdir())
+        self.manifest = options.get('manifest')
+        self.dump_tests = options.get('dump_tests')
+        self.interactive = options.get('interactive')
+        self.verbose = options.get('verbose')
+        self.keepGoing = options.get('keepGoing')
+        self.logfiles = options.get('logfiles')
+        self.totalChunks = options.get('totalChunks')
+        self.thisChunk = options.get('thisChunk')
+        self.profileName = options.get('profileName') or "xpcshell"
+        self.mozInfo = options.get('mozInfo')
+        self.testingModulesDir = testingModulesDir
+        self.pluginsPath = options.get('pluginsPath')
+        self.sequential = options.get('sequential')
+        self.failure_manifest = options.get('failure_manifest')
+        self.threadCount = options.get('threadCount') or NUM_THREADS
+        self.jscovdir = options.get('jscovdir')
+
+        self.testCount = 0
+        self.passCount = 0
+        self.failCount = 0
+        self.todoCount = 0
+
+        self.setAbsPath()
+        self.buildXpcsRunArgs()
+
+        self.event = Event()
+
+        if not self.updateMozinfo():
+            return False
+
         self.stack_fixer_function = None
         if self.utility_path and os.path.exists(self.utility_path):
             self.stack_fixer_function = get_stack_fixer_function(self.utility_path, self.symbolsPath)
 
         # buildEnvironment() needs mozInfo, so we call it after mozInfo is initialized.
         self.buildEnvironment()
 
         # The appDirKey is a optional entry in either the default or individual test
@@ -1279,25 +1249,24 @@ class XPCShellTests(object):
             appDirKey = self.mozInfo["appname"] + "-appdir"
 
         # We have to do this before we run tests that depend on having the node
         # http/2 server.
         self.trySetupNode()
 
         pStdout, pStderr = self.getPipes()
 
-        self.buildTestList(test_tags, testPaths)
+        self.buildTestList(options.get('test_tags'), options.get('testPaths'), options.get('verify'))
         if self.singleFile:
             self.sequential = True
 
-        if shuffle:
+        if options.get('shuffle'):
             random.shuffle(self.alltests)
 
         self.cleanup_dir_list = []
-        self.try_again_list = []
 
         kwargs = {
             'appPath': self.appPath,
             'xrePath': self.xrePath,
             'utility_path': self.utility_path,
             'testingModulesDir': self.testingModulesDir,
             'debuggerInfo': self.debuggerInfo,
             'jsDebuggerInfo': self.jsDebuggerInfo,
@@ -1313,16 +1282,24 @@ class XPCShellTests(object):
             'symbolsPath': self.symbolsPath,
             'logfiles': self.logfiles,
             'xpcshell': self.xpcshell,
             'xpcsRunArgs': self.xpcsRunArgs,
             'failureManifest': self.failure_manifest,
             'jscovdir': self.jscovdir,
             'harness_timeout': self.harness_timeout,
             'stack_fixer_function': self.stack_fixer_function,
+            'event': self.event,
+            'cleanup_dir_list': self.cleanup_dir_list,
+            'pStdout': pStdout,
+            'pStderr': pStderr,
+            'keep_going': self.keepGoing,
+            'log': self.log,
+            'interactive': self.interactive,
+            'app_dir_key': appDirKey
         }
 
         if self.sequential:
             # Allow user to kill hung xpcshell subprocess with SIGINT
             # when we are only running tests sequentially.
             signal.signal(signal.SIGINT, markGotSIGINT)
 
         if self.debuggerInfo:
@@ -1348,51 +1325,127 @@ class XPCShellTests(object):
         # The test itself needs to know whether it is a tsan build, since
         # that has an effect on interpretation of the process return value.
         usingTSan = "tsan" in self.mozInfo and self.mozInfo["tsan"]
 
         # create a queue of all tests that will run
         tests_queue = deque()
         # also a list for the tests that need to be run sequentially
         sequential_tests = []
-        for test_object in self.alltests:
-            # Test identifiers are provided for the convenience of logging. These
-            # start as path names but are rewritten in case tests from the same path
-            # are re-run.
+        status = None
+        if not options.get('verify'):
+            for test_object in self.alltests:
+                # Test identifiers are provided for the convenience of logging. These
+                # start as path names but are rewritten in case tests from the same path
+                # are re-run.
+
+                path = test_object['path']
+
+                if self.singleFile and not path.endswith(self.singleFile):
+                    continue
+
+                self.testCount += 1
 
-            path = test_object['path']
+                test = testClass(test_object,
+                        verbose=self.verbose or test_object.get("verbose") == "true",
+                        usingTSan=usingTSan,
+                        mobileArgs=mobileArgs, **kwargs)
+                if 'run-sequentially' in test_object or self.sequential:
+                    sequential_tests.append(test)
+                else:
+                    tests_queue.append(test)
 
-            if self.singleFile and not path.endswith(self.singleFile):
-                continue
+            status = self.runTestList(tests_queue, sequential_tests, testClass,
+                mobileArgs, **kwargs)
+        else:
+            #
+            # Test verification: Run each test many times, in various configurations,
+            # in hopes of finding intermittent failures.
+            #
 
-            self.testCount += 1
+            def step1():
+                # Run tests sequentially. Parallel mode would also work, except that
+                # the logging system gets confused when 2 or more tests with the same
+                # name run at the same time.
+                sequential_tests = []
+                for i in xrange(VERIFY_REPEAT):
+                    self.testCount += 1
+                    test = testClass(test_object, retry=False,
+                        mobileArgs=mobileArgs, **kwargs)
+                    sequential_tests.append(test)
+                status = self.runTestList(tests_queue, sequential_tests,
+                    testClass, mobileArgs, **kwargs)
+                return status
 
-            test = testClass(test_object, self.event, self.cleanup_dir_list,
-                    app_dir_key=appDirKey,
-                    interactive=interactive,
-                    verbose=verbose or test_object.get("verbose") == "true",
-                    pStdout=pStdout, pStderr=pStderr,
-                    keep_going=keepGoing, log=self.log, usingTSan=usingTSan,
-                    mobileArgs=mobileArgs, **kwargs)
-            if 'run-sequentially' in test_object or self.sequential:
-                sequential_tests.append(test)
-            else:
-                tests_queue.append(test)
+            def step2():
+                # Run tests sequentially, with MOZ_CHAOSMODE enabled.
+                sequential_tests = []
+                self.env["MOZ_CHAOSMODE"] = ""
+                for i in xrange(VERIFY_REPEAT):
+                    self.testCount += 1
+                    test = testClass(test_object, retry=False,
+                        mobileArgs=mobileArgs, **kwargs)
+                    sequential_tests.append(test)
+                status = self.runTestList(tests_queue, sequential_tests,
+                    testClass, mobileArgs, **kwargs)
+                return status
+
+            steps = [
+                ("1. Run each test %d times, sequentially." % VERIFY_REPEAT,
+                 step1),
+                ("2. Run each test %d times, sequentially, in chaos mode." % VERIFY_REPEAT,
+                 step2),
+            ]
+            startTime = datetime.now()
+            maxTime = timedelta(seconds=options['verifyMaxTime'])
+            for test_object in self.alltests:
+                stepResults = {}
+                for (descr, step) in steps:
+                    stepResults[descr] = "not run / incomplete"
+                finalResult = "PASSED"
+                for (descr, step) in steps:
+                    if (datetime.now() - startTime) > maxTime:
+                        self.log.info("::: Test verification is taking too long: Giving up!")
+                        self.log.info("::: So far, all checks passed, but not all checks were run.")
+                        break
+                    self.log.info(':::')
+                    self.log.info('::: Running test verification step "%s"...' % descr)
+                    self.log.info(':::')
+                    status = step()
+                    if status != True:
+                        stepResults[descr] = "FAIL"
+                        finalResult = "FAILED!"
+                        break
+                    stepResults[descr] = "Pass"
+                self.log.info(':::')
+                self.log.info('::: Test verification summary for: %s' % test_object['path'])
+                self.log.info(':::')
+                for descr in sorted(stepResults.keys()):
+                    self.log.info('::: %s : %s' % (descr, stepResults[descr]))
+                self.log.info(':::')
+                self.log.info('::: Test verification %s' % finalResult)
+                self.log.info(':::')
+
+        return status
+
+    def runTestList(self, tests_queue, sequential_tests, testClass,
+            mobileArgs, **kwargs):
 
         if self.sequential:
             self.log.info("Running tests sequentially.")
         else:
             self.log.info("Using at most %d threads." % self.threadCount)
 
         # keep a set of threadCount running tests and start running the
         # tests in the queue at most threadCount at a time
         running_tests = set()
         keep_going = True
         exceptions = []
         tracebacks = []
+        self.try_again_list = []
 
         tests_by_manifest = defaultdict(list)
         for test in self.alltests:
             tests_by_manifest[test['manifest']].append(test['id'])
         self.log.suite_start(tests_by_manifest)
 
         while tests_queue or running_tests:
             # if we're not supposed to continue and all of the running tests
@@ -1456,21 +1509,20 @@ class XPCShellTests(object):
                     tracebacks.append(test.traceback)
                     break
                 keep_going = test.keep_going
 
         # retry tests that failed when run in parallel
         if self.try_again_list:
             self.log.info("Retrying tests that failed when run in parallel.")
         for test_object in self.try_again_list:
-            test = testClass(test_object, self.event, self.cleanup_dir_list,
+            test = testClass(test_object,
                     retry=False,
-                    app_dir_key=appDirKey, interactive=interactive,
-                    verbose=verbose, pStdout=pStdout, pStderr=pStderr,
-                    keep_going=keepGoing, log=self.log, mobileArgs=mobileArgs,
+                    verbose=self.verbose,
+                    mobileArgs=mobileArgs,
                     **kwargs)
             test.start()
             test.join()
             self.addTestResults(test)
             # did the test encounter any exception?
             if test.exception:
                 exceptions.append(test.exception)
                 tracebacks.append(test.traceback)
@@ -1526,13 +1578,13 @@ def main():
         print >> sys.stderr, """Must provide path to xpcshell using --xpcshell"""
 
     xpcsh = XPCShellTests(log)
 
     if options.interactive and not options.testPath:
         print >>sys.stderr, "Error: You must specify a test filename in interactive mode!"
         sys.exit(1)
 
-    if not xpcsh.runTests(**vars(options)):
+    if not xpcsh.runTests(options):
         sys.exit(1)
 
 if __name__ == '__main__':
     main()
--- a/testing/xpcshell/selftest.py
+++ b/testing/xpcshell/selftest.py
@@ -498,26 +498,28 @@ tail =
 
 """ + "\n".join(testlines))
 
     def assertTestResult(self, expected, shuffle=False, verbose=False):
         """
         Assert that self.x.runTests with manifest=self.manifest
         returns |expected|.
         """
+        kwargs = {}
+        kwargs['xpcshell'] = xpcshellBin
+        kwargs['symbolsPath'] = self.symbols_path
+        kwargs['manifest'] = self.manifest
+        kwargs['mozInfo'] = mozinfo.info
+        kwargs['shuffle'] = shuffle
+        kwargs['verbose'] = verbose
+        kwargs['sequential'] = True
+        kwargs['testingModulesDir'] = os.path.join(objdir, '_tests', 'modules')
+        kwargs['utility_path'] = self.utility_path
         self.assertEquals(expected,
-                          self.x.runTests(xpcshellBin,
-                                          symbolsPath=self.symbols_path,
-                                          manifest=self.manifest,
-                                          mozInfo=mozinfo.info,
-                                          shuffle=shuffle,
-                                          verbose=verbose,
-                                          sequential=True,
-                                          testingModulesDir=os.path.join(objdir, '_tests', 'modules'),
-                                          utility_path=self.utility_path),
+                          self.x.runTests(kwargs),
                           msg="""Tests should have %s, log:
 ========
 %s
 ========
 """ % ("passed" if expected else "failed", self.log.getvalue()))
 
     def _assertLog(self, s, expected):
         l = self.log.getvalue()
--- a/testing/xpcshell/xpcshellcommandline.py
+++ b/testing/xpcshell/xpcshellcommandline.py
@@ -114,16 +114,23 @@ def add_common_arguments(parser):
                         "(with --rerun-failure) or in which to record failed tests")
     parser.add_argument("--threads",
                         type=int, dest="threadCount", default=0,
                         help="override the number of jobs (threads) when running tests in parallel, "
                              "the default is CPU x 1.5 when running via mach and CPU x 4 when running "
                              "in automation")
     parser.add_argument("testPaths", nargs="*", default=None,
                         help="Paths of tests to run.")
+    parser.add_argument("--verify",
+                        action="store_true", default=False,
+                        help="Verify test(s) by running multiple times.")
+    parser.add_argument("--verify-max-time",
+                        dest="verifyMaxTime",
+                        type=int, default=3600,
+                        help="Maximum time, in seconds, to run in --verify mode.")
 
 def add_remote_arguments(parser):
     parser.add_argument("--deviceIP", action="store", type=str, dest="deviceIP",
                         help="ip address of remote device to test")
 
     parser.add_argument("--devicePort", action="store", type=str, dest="devicePort",
                         default=20701, help="port of remote device to test")
 
--- a/toolkit/components/backgroundhangmonitor/BHRTelemetryService.js
+++ b/toolkit/components/backgroundhangmonitor/BHRTelemetryService.js
@@ -28,17 +28,17 @@ BHRTelemetryService.prototype = Object.f
   resetPayload() {
     this.payload = {
       modules: [],
       hangs: [],
     };
   },
 
   recordHang({duration, thread, runnableName, process, stack,
-              modules, annotations, pseudoStack}) {
+              modules, annotations}) {
     if (!Services.telemetry.canRecordExtended) {
       return;
     }
 
     // Create a mapping from module indicies in the original nsIHangDetails
     // object to this.payload.modules indicies.
     let moduleIdxs = modules.map(module => {
       let idx = this.payload.modules.findIndex(m => {
@@ -61,17 +61,16 @@ BHRTelemetryService.prototype = Object.f
 
     // Create the hang object to record in the payload.
     this.payload.hangs.push({
       duration,
       thread,
       runnableName,
       process,
       annotations,
-      pseudoStack,
       stack,
     });
 
     // If we have collected enough hangs, we can submit the hangs we have
     // collected to telemetry.
     if (this.payload.hangs.length > this.TRANSMIT_HANG_COUNT) {
       this.submit();
     }
--- a/toolkit/components/backgroundhangmonitor/BackgroundHangMonitor.cpp
+++ b/toolkit/components/backgroundhangmonitor/BackgroundHangMonitor.cpp
@@ -182,18 +182,16 @@ public:
   // Is the thread in a waiting state
   bool mWaiting;
   // Is the thread dedicated to a single BackgroundHangMonitor
   BackgroundHangMonitor::ThreadType mThreadType;
   // Platform-specific helper to get hang stacks
   ThreadStackHelper mStackHelper;
   // Stack of current hang
   HangStack mHangStack;
-  // Native stack of current hang
-  NativeHangStack mNativeHangStack;
   // Annotations for the current hang
   HangMonitor::HangAnnotations mAnnotations;
   // Annotators registered for this thread
   HangMonitor::Observer::Annotators mAnnotators;
   // The name of the runnable which is hanging the current process
   nsCString mRunnableName;
   // The name of the thread which is being monitored
   nsCString mThreadName;
@@ -339,28 +337,21 @@ BackgroundHangManager::RunMonitorThread(
         currentThread->mWaiting = true;
         currentThread->mHanging = false;
         currentThread->ReportPermaHang();
         continue;
       }
 
       if (MOZ_LIKELY(!currentThread->mHanging)) {
         if (MOZ_UNLIKELY(hangTime >= currentThread->mTimeout)) {
-          // A hang started
-#ifdef NIGHTLY_BUILD
-          // NOTE: In nightly builds of firefox we want to collect native stacks
-          // for all hangs, not just permahangs.
-          currentThread->mStackHelper.GetPseudoAndNativeStack(
+          // A hang started, collect a stack
+          currentThread->mStackHelper.GetStack(
             currentThread->mHangStack,
-            currentThread->mNativeHangStack,
-            currentThread->mRunnableName);
-#else
-          currentThread->mStackHelper.GetPseudoStack(currentThread->mHangStack,
-                                                     currentThread->mRunnableName);
-#endif
+            currentThread->mRunnableName,
+            true);
           currentThread->mHangStart = interval;
           currentThread->mHanging = true;
           currentThread->mAnnotations =
             currentThread->mAnnotators.GatherAnnotations();
         }
       } else {
         if (MOZ_LIKELY(interval != currentThread->mHangStart)) {
           // A hang ended
@@ -442,52 +433,28 @@ BackgroundHangThread::~BackgroundHangThr
 }
 
 void
 BackgroundHangThread::ReportHang(PRIntervalTime aHangTime)
 {
   // Recovered from a hang; called on the monitor thread
   // mManager->mLock IS locked
 
-  // Remove unwanted "js::RunScript" frame from the stack
-  for (size_t i = 0; i < mHangStack.length(); ) {
-    const char** f = mHangStack.begin() + i;
-    if (!mHangStack.IsInBuffer(*f) && !strcmp(*f, "js::RunScript")) {
-      mHangStack.erase(f);
-    } else {
-      i++;
-    }
-  }
-
-  // Collapse duplicated "(chrome script)" and "(content script)" entries in the stack.
-  auto it = std::unique(mHangStack.begin(), mHangStack.end(), StackScriptEntriesCollapser);
-  mHangStack.erase(it, mHangStack.end());
-
-  // Limit the depth of the reported stack if greater than our limit. Only keep its
-  // last entries, since the most recent frames are at the end of the vector.
-  if (mHangStack.length() > kMaxThreadHangStackDepth) {
-    const int elementsToRemove = mHangStack.length() - kMaxThreadHangStackDepth;
-    // Replace the oldest frame with a known label so that we can tell this stack
-    // was limited.
-    mHangStack[0] = "(reduced stack)";
-    mHangStack.erase(mHangStack.begin() + 1, mHangStack.begin() + elementsToRemove);
-  }
-
   HangDetails hangDetails(aHangTime,
                           XRE_GetProcessType(),
                           mThreadName,
                           mRunnableName,
                           Move(mHangStack),
                           Move(mAnnotations));
   // If we have the stream transport service avaliable, we can process the
   // native stack on it. Otherwise, we are unable to report a native stack, so
   // we just report without one.
   if (mManager->mSTS) {
     nsCOMPtr<nsIRunnable> processHangStackRunnable =
-      new ProcessHangStackRunnable(Move(hangDetails), Move(mNativeHangStack));
+      new ProcessHangStackRunnable(Move(hangDetails));
     mManager->mSTS->Dispatch(processHangStackRunnable.forget());
   } else {
     NS_WARNING("Unable to report native stack without a StreamTransportService");
     RefPtr<nsHangDetails> hd = new nsHangDetails(Move(hangDetails));
     hd->Submit();
   }
 }
 
--- a/toolkit/components/backgroundhangmonitor/HangDetails.cpp
+++ b/toolkit/components/backgroundhangmonitor/HangDetails.cpp
@@ -32,86 +32,16 @@ nsHangDetails::GetRunnableName(nsACStrin
 NS_IMETHODIMP
 nsHangDetails::GetProcess(nsACString& aName)
 {
   aName.AssignASCII(XRE_ChildProcessTypeToString(mDetails.mProcess));
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsHangDetails::GetStack(JSContext* aCx, JS::MutableHandleValue aVal)
-{
-  size_t length = mDetails.mStack.GetStackSize();
-  JS::RootedObject retObj(aCx, JS_NewArrayObject(aCx, length));
-  if (!retObj) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  for (size_t i = 0; i < length; ++i) {
-    auto& frame = mDetails.mStack.GetFrame(i);
-    JS::RootedObject jsFrame(aCx, JS_NewArrayObject(aCx, 2));
-    if (!jsFrame) {
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-
-    if (!JS_DefineElement(aCx, jsFrame, 0, frame.mModIndex, JSPROP_ENUMERATE)) {
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-
-    nsPrintfCString hexString("%" PRIxPTR, frame.mOffset);
-    JS::RootedString hex(aCx, JS_NewStringCopyZ(aCx, hexString.get()));
-    if (!hex || !JS_DefineElement(aCx, jsFrame, 1, hex, JSPROP_ENUMERATE)) {
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-
-    if (!JS_DefineElement(aCx, retObj, i, jsFrame, JSPROP_ENUMERATE)) {
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-  }
-
-  aVal.setObject(*retObj);
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsHangDetails::GetModules(JSContext* aCx, JS::MutableHandleValue aVal)
-{
-  size_t length = mDetails.mStack.GetNumModules();
-  JS::RootedObject retObj(aCx, JS_NewArrayObject(aCx, length));
-  if (!retObj) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  for (size_t i = 0; i < length; ++i) {
-    auto& module = mDetails.mStack.GetModule(i);
-    JS::RootedObject jsModule(aCx, JS_NewArrayObject(aCx, 2));
-    if (!jsModule) {
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-
-    JS::RootedString name(aCx, JS_NewUCStringCopyZ(aCx, module.mName.get()));
-    if (!name || !JS_DefineElement(aCx, jsModule, 0, name, JSPROP_ENUMERATE)) {
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-
-    JS::RootedString id(aCx, JS_NewStringCopyZ(aCx, module.mBreakpadId.c_str()));
-    if (!id || !JS_DefineElement(aCx, jsModule, 1, id, JSPROP_ENUMERATE)) {
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-
-    if (!JS_DefineElement(aCx, retObj, i, jsModule, JSPROP_ENUMERATE)) {
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-  }
-
-  aVal.setObject(*retObj);
-  return NS_OK;
-}
-
-NS_IMETHODIMP
 nsHangDetails::GetAnnotations(JSContext* aCx, JS::MutableHandleValue aVal)
 {
   // We create an object with { "key" : "value" } string pairs for each item in
   // our annotations object.
   JS::RootedObject jsAnnotation(aCx, JS_NewPlainObject(aCx));
   if (!jsAnnotation) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
@@ -129,37 +59,126 @@ nsHangDetails::GetAnnotations(JSContext*
     }
   }
 
   aVal.setObject(*jsAnnotation);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsHangDetails::GetPseudoStack(JSContext* aCx, JS::MutableHandle<JS::Value> aVal)
+nsHangDetails::GetStack(JSContext* aCx, JS::MutableHandle<JS::Value> aVal)
 {
-  JS::RootedObject ret(aCx, JS_NewArrayObject(aCx, mDetails.mPseudoStack.length()));
+  JS::RootedObject ret(aCx, JS_NewArrayObject(aCx, mDetails.mStack.length()));
   if (!ret) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
-  for (size_t i = 0; i < mDetails.mPseudoStack.length(); ++i) {
-    JSString* jsString = JS_NewStringCopyZ(aCx, mDetails.mPseudoStack[i]);
-    if (!jsString) {
+  for (size_t i = 0; i < mDetails.mStack.length(); ++i) {
+    const HangStack::Frame& frame = mDetails.mStack[i];
+    switch (frame.GetKind()) {
+      case HangStack::Frame::Kind::STRING: {
+        JSString* jsString = JS_NewStringCopyZ(aCx, frame.AsString());
+        if (!jsString) {
+          return NS_ERROR_OUT_OF_MEMORY;
+        }
+        JS::RootedString string(aCx, jsString);
+        if (!string) {
+          return NS_ERROR_OUT_OF_MEMORY;
+        }
+        if (!JS_DefineElement(aCx, ret, i, string, JSPROP_ENUMERATE)) {
+          return NS_ERROR_OUT_OF_MEMORY;
+        }
+        break;
+      }
+
+      case HangStack::Frame::Kind::MODOFFSET: {
+        JS::RootedObject jsFrame(aCx, JS_NewArrayObject(aCx, 2));
+        if (!jsFrame) {
+          return NS_ERROR_OUT_OF_MEMORY;
+        }
+
+        if (!JS_DefineElement(aCx, jsFrame, 0, frame.AsModOffset().mModule, JSPROP_ENUMERATE)) {
+          return NS_ERROR_OUT_OF_MEMORY;
+        }
+
+        nsPrintfCString hexString("%" PRIxPTR, (uintptr_t)frame.AsModOffset().mOffset);
+        JS::RootedString hex(aCx, JS_NewStringCopyZ(aCx, hexString.get()));
+        if (!hex || !JS_DefineElement(aCx, jsFrame, 1, hex, JSPROP_ENUMERATE)) {
+          return NS_ERROR_OUT_OF_MEMORY;
+        }
+
+        if (!JS_DefineElement(aCx, ret, i, jsFrame, JSPROP_ENUMERATE)) {
+          return NS_ERROR_OUT_OF_MEMORY;
+        }
+        break;
+      }
+
+      case HangStack::Frame::Kind::PC: {
+        JS::RootedObject jsFrame(aCx, JS_NewArrayObject(aCx, 2));
+        if (!jsFrame) {
+          return NS_ERROR_OUT_OF_MEMORY;
+        }
+
+        if (!JS_DefineElement(aCx, jsFrame, 0, -1, JSPROP_ENUMERATE)) {
+          return NS_ERROR_OUT_OF_MEMORY;
+        }
+
+        nsPrintfCString hexString("%" PRIxPTR, frame.AsPC());
+        JS::RootedString hex(aCx, JS_NewStringCopyZ(aCx, hexString.get()));
+        if (!hex || !JS_DefineElement(aCx, jsFrame, 1, hex, JSPROP_ENUMERATE)) {
+          return NS_ERROR_OUT_OF_MEMORY;
+        }
+
+        if (!JS_DefineElement(aCx, ret, i, jsFrame, JSPROP_ENUMERATE)) {
+          return NS_ERROR_OUT_OF_MEMORY;
+        }
+        break;
+      }
+      default:
+        MOZ_ASSERT_UNREACHABLE("Invalid variant");
+        break;
+    }
+  }
+
+  aVal.setObject(*ret);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHangDetails::GetModules(JSContext* aCx, JS::MutableHandleValue aVal)
+{
+  auto& modules = mDetails.mStack.GetModules();
+  size_t length = modules.Length();
+  JS::RootedObject retObj(aCx, JS_NewArrayObject(aCx, length));
+  if (!retObj) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  for (size_t i = 0; i < length; ++i) {
+    const HangStack::Module& module = modules[i];
+    JS::RootedObject jsModule(aCx, JS_NewArrayObject(aCx, 2));
+    if (!jsModule) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
-    JS::RootedString string(aCx, jsString);
-    if (!string) {
+
+    JS::RootedString name(aCx, JS_NewUCStringCopyN(aCx, module.mName.BeginReading(), module.mName.Length()));
+    if (!JS_DefineElement(aCx, jsModule, 0, name, JSPROP_ENUMERATE)) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
-    if (!JS_DefineElement(aCx, ret, i, string, JSPROP_ENUMERATE)) {
+
+    JS::RootedString breakpadId(aCx, JS_NewStringCopyN(aCx, module.mBreakpadId.BeginReading(), module.mBreakpadId.Length()));
+    if (!JS_DefineElement(aCx, jsModule, 1, breakpadId, JSPROP_ENUMERATE)) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+
+    if (!JS_DefineElement(aCx, retObj, i, jsModule, JSPROP_ENUMERATE)) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
   }
 
-  aVal.setObject(*ret);
+  aVal.setObject(*retObj);
   return NS_OK;
 }
 
 // Processing and submitting the stack as an observer notification.
 
 void
 nsHangDetails::Submit()
 {
@@ -209,19 +228,19 @@ nsHangDetails::Submit()
   MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
 }
 
 NS_IMPL_ISUPPORTS(nsHangDetails, nsIHangDetails)
 
 NS_IMETHODIMP
 ProcessHangStackRunnable::Run()
 {
-  // NOTE: This is an expensive operation on some platforms, so we do it off of
-  // any other critical path, moving it onto the StreamTransportService.
-  mHangDetails.mStack = Telemetry::GetStackAndModules(mNativeStack);
+  // NOTE: Reading module information can take a long time, which is why we do
+  // it off-main-thread.
+  mHangDetails.mStack.ReadModuleInformation();
 
   RefPtr<nsHangDetails> hangDetails = new nsHangDetails(Move(mHangDetails));
   hangDetails->Submit();
 
   return NS_OK;
 }
 
 } // namespace mozilla
@@ -234,44 +253,18 @@ namespace IPC {
 
 void
 ParamTraits<mozilla::HangDetails>::Write(Message* aMsg, const mozilla::HangDetails& aParam)
 {
   WriteParam(aMsg, aParam.mDuration);
   WriteParam(aMsg, aParam.mProcess);
   WriteParam(aMsg, aParam.mThreadName);
   WriteParam(aMsg, aParam.mRunnableName);
-  WriteParam(aMsg, aParam.mPseudoStack);
-
+  WriteParam(aMsg, aParam.mStack);
   WriteParam(aMsg, aParam.mAnnotations);
-
-  // NOTE: ProcessedStack will stop being used for BHR in bug 1367406, so this
-  // inline serialization will survive until then.
-
-  // Write out the native stack module information
-  {
-    size_t length = aParam.mStack.GetNumModules();
-    WriteParam(aMsg, length);
-    for (size_t i = 0; i < length; ++i) {
-      auto& module = aParam.mStack.GetModule(i);
-      WriteParam(aMsg, module.mName);
-      WriteParam(aMsg, module.mBreakpadId);
-    }
-  }
-
-  // Native stack frame information
-  {
-    size_t length = aParam.mStack.GetStackSize();
-    WriteParam(aMsg, length);
-    for (size_t i = 0; i < length; ++i) {
-      auto& frame = aParam.mStack.GetFrame(i);
-      WriteParam(aMsg, frame.mOffset);
-      WriteParam(aMsg, frame.mModIndex);
-    }
-  }
 }
 
 bool
 ParamTraits<mozilla::HangDetails>::Read(const Message* aMsg,
                                         PickleIterator* aIter,
                                         mozilla::HangDetails* aResult)
 {
   if (!ReadParam(aMsg, aIter, &aResult->mDuration)) {
@@ -281,62 +274,19 @@ ParamTraits<mozilla::HangDetails>::Read(
     return false;
   }
   if (!ReadParam(aMsg, aIter, &aResult->mThreadName)) {
     return false;
   }
   if (!ReadParam(aMsg, aIter, &aResult->mRunnableName)) {
     return false;
   }
-  if (!ReadParam(aMsg, aIter, &aResult->mPseudoStack)) {
-    return false;
-  }
-
-  // Annotation information
-  if (!ReadParam(aMsg, aIter, &aResult->mAnnotations)) {
+  if (!ReadParam(aMsg, aIter, &aResult->mStack)) {
     return false;
   }
-
-  // NOTE: ProcessedStack will stop being used for BHR in bug 1367406, so this
-  // inline serialization will survive until then.
-
-  // Native Stack Module Information
-  {
-    size_t length;
-    if (!ReadParam(aMsg, aIter, &length)) {
-      return false;
-    }
-
-    for (size_t i = 0; i < length; ++i) {
-      mozilla::Telemetry::ProcessedStack::Module module;
-      if (!ReadParam(aMsg, aIter, &module.mName)) {
-        return false;
-      }
-      if (!ReadParam(aMsg, aIter, &module.mBreakpadId)) {
-        return false;
-      }
-      aResult->mStack.AddModule(module);
-    }
-  }
-
-  // Native stack frame information
-  {
-    size_t length;
-    if (!ReadParam(aMsg, aIter, &length)) {
-      return false;
-    }
-
-    for (size_t i = 0; i < length; ++i) {
-      mozilla::Telemetry::ProcessedStack::Frame frame;
-      if (!ReadParam(aMsg, aIter, &frame.mOffset)) {
-        return false;
-      }
-      if (!ReadParam(aMsg, aIter, &frame.mModIndex)) {
-        return false;
-      }
-      aResult->mStack.AddFrame(frame);
-    }
+  if (!ReadParam(aMsg, aIter, &aResult->mAnnotations)) {
+    return false;
   }
 
   return true;
 }
 
 } // namespace IPC
--- a/toolkit/components/backgroundhangmonitor/HangDetails.h
+++ b/toolkit/components/backgroundhangmonitor/HangDetails.h
@@ -35,37 +35,32 @@ public:
   {}
 
   HangDetails(const HangDetails& aOther) = default;
   HangDetails(HangDetails&& aOther) = default;
   HangDetails(uint32_t aDuration,
               GeckoProcessType aProcess,
               const nsACString& aThreadName,
               const nsACString& aRunnableName,
-              HangStack&& aPseudoStack,
+              HangStack&& aStack,
               HangMonitor::HangAnnotations&& aAnnotations)
     : mDuration(aDuration)
     , mProcess(aProcess)
     , mThreadName(aThreadName)
     , mRunnableName(aRunnableName)
-    , mPseudoStack(Move(aPseudoStack))
+    , mStack(Move(aStack))
     , mAnnotations(Move(aAnnotations))
   {}
 
-  // NOTE: Once we get merged stacks, we will move ProcessedStack into HangStack
-  // and simplify everything. (bug 1367406).
   uint32_t mDuration;
   GeckoProcessType mProcess;
   nsCString mThreadName;
   nsCString mRunnableName;
-  HangStack mPseudoStack;
+  HangStack mStack;
   HangMonitor::HangAnnotations mAnnotations;
-
-  // NOTE: Initialized by ProcessHangStackRunnable.
-  Telemetry::ProcessedStack mStack;
 };
 
 /**
  * HangDetails is the concrete implementaion of nsIHangDetails, and contains the
  * infromation which we want to expose to observers of the bhr-thread-hang
  * observer notification.
  */
 class nsHangDetails : public nsIHangDetails
@@ -95,28 +90,25 @@ private:
  * an observer notification.
  *
  * This object should have the only remaining reference to aHangDetails, as it
  * will access its fields without synchronization.
  */
 class ProcessHangStackRunnable final : public Runnable
 {
 public:
-  ProcessHangStackRunnable(HangDetails&& aHangDetails,
-                           std::vector<uintptr_t>&& aNativeStack)
+  explicit ProcessHangStackRunnable(HangDetails&& aHangDetails)
     : Runnable("ProcessHangStackRunnable")
     , mHangDetails(Move(aHangDetails))
-    , mNativeStack(Move(aNativeStack))
   {}
 
   NS_IMETHOD Run() override;
 
 private:
   HangDetails mHangDetails;
-  std::vector<uintptr_t> mNativeStack;
 };
 
 } // namespace mozilla
 
 // We implement the ability to send the HangDetails object over IPC. We need to
 // do this rather than rely on StructuredClone of the objects created by the
 // XPCOM getters on nsHangDetails because we want to run BHR in the GPU process
 // which doesn't run any JS.
--- a/toolkit/components/backgroundhangmonitor/HangStack.cpp
+++ b/toolkit/components/backgroundhangmonitor/HangStack.cpp
@@ -1,103 +1,298 @@
 #include "HangStack.h"
+#include "shared-libraries.h"
 
 namespace mozilla {
 
 HangStack::HangStack(const HangStack& aOther)
+  : mModules(aOther.mModules)
 {
   if (NS_WARN_IF(!mBuffer.reserve(aOther.mBuffer.length()) ||
                  !mImpl.reserve(aOther.mImpl.length()))) {
     return;
   }
   // XXX: I should be able to just memcpy the other stack's mImpl and mBuffer,
   // and then just re-offset pointers.
   for (size_t i = 0; i < aOther.length(); ++i) {
-    const char* s = aOther[i];
-    if (aOther.IsInBuffer(s)) {
-      InfallibleAppendViaBuffer(s, strlen(s));
-    } else {
-      infallibleAppend(s);
+    const Frame& frame = aOther[i];
+
+    // If the source string is a reference to aOther's buffer, we have to append
+    // via buffer, otherwise we can just copy the entry over.
+    if (frame.GetKind() == Frame::Kind::STRING) {
+      const char* s = frame.AsString();
+      if (aOther.IsInBuffer(s)) {
+        InfallibleAppendViaBuffer(s, strlen(s));
+        continue;
+      }
     }
+
+    infallibleAppend(frame);
   }
   MOZ_ASSERT(mImpl.length() == aOther.mImpl.length());
   MOZ_ASSERT(mBuffer.length() == aOther.mBuffer.length());
 }
 
-const char*
+void
 HangStack::InfallibleAppendViaBuffer(const char* aText, size_t aLength)
 {
   MOZ_ASSERT(this->canAppendWithoutRealloc(1));
   // Include null-terminator in length count.
   MOZ_ASSERT(mBuffer.canAppendWithoutRealloc(aLength + 1));
 
   const char* const entry = mBuffer.end();
   mBuffer.infallibleAppend(aText, aLength);
   mBuffer.infallibleAppend('\0'); // Explicitly append null-terminator
-  this->infallibleAppend(entry);
-  return entry;
+
+  this->infallibleAppend(Frame(entry));
 }
 
-const char*
+bool
 HangStack::AppendViaBuffer(const char* aText, size_t aLength)
 {
   if (!this->reserve(this->length() + 1)) {
-    return nullptr;
+    return false;
   }
 
   // Keep track of the previous buffer in case we need to adjust pointers later.
   const char* const prevStart = mBuffer.begin();
   const char* const prevEnd = mBuffer.end();
 
   // Include null-terminator in length count.
   if (!mBuffer.reserve(mBuffer.length() + aLength + 1)) {
-    return nullptr;
+    return false;
   }
 
   if (prevStart != mBuffer.begin()) {
     // The buffer has moved; we have to adjust pointers in the stack.
-    for (auto & entry : *this) {
-      if (entry >= prevStart && entry < prevEnd) {
-        // Move from old buffer to new buffer.
-        entry += mBuffer.begin() - prevStart;
+    for (auto & frame : *this) {
+      if (frame.GetKind() == Frame::Kind::STRING) {
+        const char*& entry = frame.AsString();
+        if (entry >= prevStart && entry < prevEnd) {
+          // Move from old buffer to new buffer.
+          entry += mBuffer.begin() - prevStart;
+        }
       }
     }
   }
 
-  return InfallibleAppendViaBuffer(aText, aLength);
+  InfallibleAppendViaBuffer(aText, aLength);
+  return true;
+}
+
+namespace {
+
+// Sorting comparator used by ReadModuleInformation. Sorts PC Frames by their
+// PC.
+struct PCFrameComparator {
+  bool LessThan(HangStack::Frame* const& a, HangStack::Frame* const& b) const {
+    return a->AsPC() < b->AsPC();
+  }
+  bool Equals(HangStack::Frame* const& a, HangStack::Frame* const& b) const {
+    return a->AsPC() == b->AsPC();
+  }
+};
+
+} // anonymous namespace
+
+void
+HangStack::ReadModuleInformation()
+{
+  // mModules should be empty when we start filling it.
+  mModules.Clear();
+
+  // Create a sorted list of the PCs in the current stack.
+  AutoTArray<Frame*, 100> frames;
+  for (auto& frame : *this) {
+    if (frame.GetKind() == Frame::Kind::PC) {
+      frames.AppendElement(&frame);
+    }
+  }
+  PCFrameComparator comparator;
+  frames.Sort(comparator);
+
+  SharedLibraryInfo rawModules = SharedLibraryInfo::GetInfoForSelf();
+  rawModules.SortByAddress();
+
+  size_t frameIdx = 0;
+  for (size_t i = 0; i < rawModules.GetSize(); ++i) {
+    const SharedLibrary& info = rawModules.GetEntry(i);
+    uintptr_t moduleStart = info.GetStart();
+    uintptr_t moduleEnd = info.GetEnd() - 1;
+    // the interval is [moduleStart, moduleEnd)
+
+    bool moduleReferenced = false;
+    for (; frameIdx < frames.Length(); ++frameIdx) {
+      auto& frame = frames[frameIdx];
+      // We've moved past this frame, let's go to the next one.
+      if (frame->AsPC() >= moduleEnd) {
+        break;
+      }
+      if (frame->AsPC() >= moduleStart) {
+        uint64_t offset = frame->AsPC() - moduleStart;
+        if (NS_WARN_IF(offset > UINT32_MAX)) {
+          continue; // module/offset can only hold 32-bit offsets into shared libraries.
+        }
+
+        // If we found the module, rewrite the Frame entry to instead be a
+        // ModOffset one. mModules.Length() will be the index of the module when
+        // we append it below, and we set moduleReferenced to true to ensure
+        // that we do.
+        moduleReferenced = true;
+        uint32_t module = mModules.Length();
+        ModOffset modOffset = {
+          module,
+          static_cast<uint32_t>(offset)
+        };
+        *frame = Frame(modOffset);
+      }
+    }
+
+    if (moduleReferenced) {
+      nsDependentCString cstr(info.GetBreakpadId().c_str());
+      Module module = {
+        info.GetDebugName(),
+        cstr
+      };
+      mModules.AppendElement(module);
+    }
+  }
 }
 
 } // namespace mozilla
 
 namespace IPC {
 
 void
+ParamTraits<mozilla::HangStack::ModOffset>::Write(Message* aMsg, const mozilla::HangStack::ModOffset& aParam)
+{
+  WriteParam(aMsg, aParam.mModule);
+  WriteParam(aMsg, aParam.mOffset);
+}
+
+bool
+ParamTraits<mozilla::HangStack::ModOffset>::Read(const Message* aMsg,
+                                                 PickleIterator* aIter,
+                                                 mozilla::HangStack::ModOffset* aResult)
+{
+  if (!ReadParam(aMsg, aIter, &aResult->mModule)) {
+    return false;
+  }
+  if (!ReadParam(aMsg, aIter, &aResult->mOffset)) {
+    return false;
+  }
+  return true;
+}
+
+void
+ParamTraits<mozilla::HangStack::Module>::Write(Message* aMsg, const mozilla::HangStack::Module& aParam)
+{
+  WriteParam(aMsg, aParam.mName);
+  WriteParam(aMsg, aParam.mBreakpadId);
+}
+
+bool
+ParamTraits<mozilla::HangStack::Module>::Read(const Message* aMsg, PickleIterator* aIter,
+                                              mozilla::HangStack::Module* aResult)
+{
+  if (!ReadParam(aMsg, aIter, &aResult->mName)) {
+    return false;
+  }
+  if (!ReadParam(aMsg, aIter, &aResult->mBreakpadId)) {
+    return false;
+  }
+  return true;
+}
+
+void
 ParamTraits<mozilla::HangStack>::Write(Message* aMsg, const mozilla::HangStack& aParam)
 {
+  typedef mozilla::HangStack::Frame Frame;
+
   size_t length = aParam.length();
   WriteParam(aMsg, length);
   for (size_t i = 0; i < length; ++i) {
-    nsDependentCString str(aParam[i]);
-    WriteParam(aMsg, static_cast<nsACString&>(str));
+    const Frame& frame = aParam[i];
+    WriteParam(aMsg, frame.GetKind());
+
+    switch (frame.GetKind()) {
+      case Frame::Kind::STRING: {
+        nsDependentCString str(frame.AsString());
+        WriteParam(aMsg, static_cast<nsACString&>(str));
+        break;
+      }
+      case Frame::Kind::MODOFFSET: {
+        WriteParam(aMsg, frame.AsModOffset());
+        break;
+      }
+      case Frame::Kind::PC: {
+        WriteParam(aMsg, frame.AsPC());
+        break;
+      }
+      default: {
+        MOZ_RELEASE_ASSERT(false, "Invalid kind for HangStack Frame");
+        break;
+      }
+    }
   }
+
+  WriteParam(aMsg, aParam.GetModules());
 }
 
 bool
 ParamTraits<mozilla::HangStack>::Read(const Message* aMsg,
                                       PickleIterator* aIter,
                                       mozilla::HangStack* aResult)
 {
+  // Shorten the name of Frame
+  typedef mozilla::HangStack::Frame Frame;
+
   size_t length;
   if (!ReadParam(aMsg, aIter, &length)) {
     return false;
   }
 
+  aResult->reserve(length);
   for (size_t i = 0; i < length; ++i) {
-    nsAutoCString str;
-    if (!ReadParam(aMsg, aIter, &str)) {
+    Frame::Kind kind;
+    if (!ReadParam(aMsg, aIter, &kind)) {
       return false;
     }
-    aResult->AppendViaBuffer(str.get(), str.Length());
+
+    switch (kind) {
+      case Frame::Kind::STRING: {
+        nsAutoCString str;
+        if (!ReadParam(aMsg, aIter, static_cast<nsACString*>(&str))) {
+          return false;
+        }
+        aResult->AppendViaBuffer(str.get(), str.Length());
+        break;
+      }
+      case Frame::Kind::MODOFFSET: {
+        mozilla::HangStack::ModOffset modOff;
+        if (!ReadParam(aMsg, aIter, &modOff)) {
+          return false;
+        }
+        aResult->infallibleAppend(Frame(modOff));
+        break;
+      }
+      case Frame::Kind::PC: {
+        uintptr_t pc;
+        if (!ReadParam(aMsg, aIter, &pc)) {
+          return false;
+        }
+        aResult->infallibleAppend(Frame(pc));
+        break;
+      }
+      default:
+        // We can't deserialize other kinds!
+        return false;
+    }
   }
+
+  if (!ReadParam(aMsg, aIter, &aResult->GetModules())) {
+    return false;
+  }
+
   return true;
 }
 
 } // namespace IPC
--- a/toolkit/components/backgroundhangmonitor/HangStack.h
+++ b/toolkit/components/backgroundhangmonitor/HangStack.h
@@ -26,88 +26,206 @@ class HangStack
 {
 public:
   static const size_t sMaxInlineStorage = 8;
 
   // The maximum depth for the native stack frames that we might collect.
   // XXX: Consider moving this to a different object?
   static const size_t sMaxNativeFrames = 150;
 
+  struct ModOffset {
+    uint32_t mModule;
+    uint32_t mOffset;
+
+    bool operator==(const ModOffset& aOther) const {
+      return mModule == aOther.mModule && mOffset == aOther.mOffset;
+    }
+  };
+
+  // A HangStack frame is one of the following types:
+  // * Kind::STRING(const char*) : A string representing a pseudostack or chrome JS stack frame.
+  // * Kind::MODOFFSET(ModOffset) : A module index and offset into that module.
+  // * Kind::PC(uintptr_t) : A raw program counter which has not been mapped to a module.
+  //
+  // NOTE: A manually rolled tagged enum is used instead of mozilla::Variant
+  // here because we cannot use mozilla::Variant's IPC serialization directly.
+  // Unfortunately the const char* variant needs the context of the HangStack
+  // which it is in order to correctly deserialize. For this reason, a Frame by
+  // itself does not have a ParamTraits implementation.
+  class Frame
+  {
+  public:
+    enum class Kind {
+      STRING,
+      MODOFFSET,
+      PC,
+      END // Marker
+    };
+
+    Frame()
+      : mKind(Kind::STRING)
+      , mString("")
+    {}
+
+    explicit Frame(const char* aString)
+      : mKind(Kind::STRING)
+      , mString(aString)
+    {}
+
+    explicit Frame(ModOffset aModOffset)
+      : mKind(Kind::MODOFFSET)
+      , mModOffset(aModOffset)
+    {}
+
+    explicit Frame(uintptr_t aPC)
+      : mKind(Kind::PC)
+      , mPC(aPC)
+    {}
+
+    Kind GetKind() const {
+      return mKind;
+    }
+
+    const char*& AsString() {
+      MOZ_ASSERT(mKind == Kind::STRING);
+      return mString;
+    }
+
+    const char* const& AsString() const {
+      MOZ_ASSERT(mKind == Kind::STRING);
+      return mString;
+    }
+
+    const ModOffset& AsModOffset() const {
+      MOZ_ASSERT(mKind == Kind::MODOFFSET);
+      return mModOffset;
+    }
+
+    const uintptr_t& AsPC() const {
+      MOZ_ASSERT(mKind == Kind::PC);
+      return mPC;
+    }
+
+  private:
+    Kind mKind;
+    union {
+      const char* mString;
+      ModOffset mModOffset;
+      uintptr_t mPC;
+    };
+  };
+
+  struct Module {
+    // The file name, /foo/bar/libxul.so for example.
+    // It can contain unicode characters.
+    nsString mName;
+    nsCString mBreakpadId;
+
+    bool operator==(const Module& aOther) const {
+      return mName == aOther.mName && mBreakpadId == aOther.mBreakpadId;
+    }
+  };
+
 private:
-  typedef mozilla::Vector<const char*, sMaxInlineStorage> Impl;
+  typedef mozilla::Vector<Frame, sMaxInlineStorage> Impl;
   Impl mImpl;
 
   // Stack entries can either be a static const char*
   // or a pointer to within this buffer.
   mozilla::Vector<char, 0> mBuffer;
+  nsTArray<Module> mModules;
 
 public:
   HangStack() {}
 
   HangStack(const HangStack& aOther);
   HangStack(HangStack&& aOther)
     : mImpl(mozilla::Move(aOther.mImpl))
     , mBuffer(mozilla::Move(aOther.mBuffer))
+    , mModules(mozilla::Move(aOther.mModules))
   {
   }
 
   HangStack& operator=(HangStack&& aOther) {
     mImpl = mozilla::Move(aOther.mImpl);
     mBuffer = mozilla::Move(aOther.mBuffer);
+    mModules = mozilla::Move(aOther.mModules);
     return *this;
   }
 
   bool operator==(const HangStack& aOther) const {
     for (size_t i = 0; i < length(); i++) {
       if (!IsSameAsEntry(operator[](i), aOther[i])) {
         return false;
       }
     }
     return true;
   }
 
   bool operator!=(const HangStack& aOther) const {
     return !operator==(aOther);
   }
 
-  const char*& operator[](size_t aIndex) {
+  Frame& operator[](size_t aIndex) {
     return mImpl[aIndex];
   }
 
-  const char* const& operator[](size_t aIndex) const {
+  Frame const& operator[](size_t aIndex) const {
     return mImpl[aIndex];
   }
 
   size_t capacity() const { return mImpl.capacity(); }
   size_t length() const { return mImpl.length(); }
   bool empty() const { return mImpl.empty(); }
   bool canAppendWithoutRealloc(size_t aNeeded) const {
     return mImpl.canAppendWithoutRealloc(aNeeded);
   }
-  void infallibleAppend(const char* aEntry) { mImpl.infallibleAppend(aEntry); }
+  void infallibleAppend(Frame aEntry) { mImpl.infallibleAppend(aEntry); }
   bool reserve(size_t aRequest) { return mImpl.reserve(aRequest); }
-  const char** begin() { return mImpl.begin(); }
-  const char* const* begin() const { return mImpl.begin(); }
-  const char** end() { return mImpl.end(); }
-  const char* const* end() const { return mImpl.end(); }
-  const char*& back() { return mImpl.back(); }
-  void erase(const char** aEntry) { mImpl.erase(aEntry); }
-  void erase(const char** aBegin, const char** aEnd) {
+  Frame* begin() { return mImpl.begin(); }
+  Frame const* begin() const { return mImpl.begin(); }
+  Frame* end() { return mImpl.end(); }
+  Frame const* end() const { return mImpl.end(); }
+  Frame& back() { return mImpl.back(); }
+  void erase(Frame* aEntry) { mImpl.erase(aEntry); }
+  void erase(Frame* aBegin, Frame* aEnd) {
     mImpl.erase(aBegin, aEnd);
   }
 
   void clear() {
     mImpl.clear();
     mBuffer.clear();
+    mModules.Clear();
   }
 
   bool IsInBuffer(const char* aEntry) const {
     return aEntry >= mBuffer.begin() && aEntry < mBuffer.end();
   }
 
+  bool IsSameAsEntry(const Frame& aFrame, const Frame& aOther) const {
+    if (aFrame.GetKind() != aOther.GetKind()) {
+      return false;
+    }
+
+    switch (aFrame.GetKind()) {
+      case Frame::Kind::STRING:
+        // If the entry came from the buffer, we need to compare its content;
+        // otherwise we only need to compare its pointer.
+        return IsInBuffer(aFrame.AsString()) ?
+          !strcmp(aFrame.AsString(), aOther.AsString()) :
+          (aFrame.AsString() == aOther.AsString());
+      case Frame::Kind::MODOFFSET:
+        return aFrame.AsModOffset() == aOther.AsModOffset();
+      case Frame::Kind::PC:
+        return aFrame.AsPC() == aOther.AsPC();
+      default:
+        MOZ_CRASH();
+    }
+  }
+
   bool IsSameAsEntry(const char* aEntry, const char* aOther) const {
     // If the entry came from the buffer, we need to compare its content;
     // otherwise we only need to compare its pointer.
     return IsInBuffer(aEntry) ? !strcmp(aEntry, aOther) : (aEntry == aOther);
   }
 
   size_t AvailableBufferSize() const {
     return mBuffer.capacity() - mBuffer.length();
@@ -115,28 +233,73 @@ public:
 
   bool EnsureBufferCapacity(size_t aCapacity) {
     // aCapacity is the minimal capacity and Vector may make the actual
     // capacity larger, in which case we want to use up all the space.
     return mBuffer.reserve(aCapacity) &&
            mBuffer.reserve(mBuffer.capacity());
   }
 
-  const char* InfallibleAppendViaBuffer(const char* aText, size_t aLength);
-  const char* AppendViaBuffer(const char* aText, size_t aLength);
+  void InfallibleAppendViaBuffer(const char* aText, size_t aLength);
+  bool AppendViaBuffer(const char* aText, size_t aLength);
+
+  const nsTArray<Module>& GetModules() const {
+    return mModules;
+  }
+  nsTArray<Module>& GetModules() {
+    return mModules;
+  }
+
+  /**
+   * Get the current list of loaded modules, and use it to transform Kind::PC
+   * stack frames from within these modules into Kind::MODOFFSET stack entries.
+   *
+   * This method also populates the mModules list, which should be empty when
+   * this method is called.
+   */
+  void ReadModuleInformation();
 };
 
 } // namespace mozilla
 
 namespace IPC {
 
 template<>
-class ParamTraits<mozilla::HangStack>
+class ParamTraits<mozilla::HangStack::ModOffset>
 {
 public:
+  typedef mozilla::HangStack::ModOffset paramType;
+  static void Write(Message* aMsg, const paramType& aParam);
+  static bool Read(const Message* aMsg,
+                   PickleIterator* aIter,
+                   paramType* aResult);
+};
+
+template<>
+struct ParamTraits<mozilla::HangStack::Frame::Kind>
+  : public ContiguousEnumSerializer<
+            mozilla::HangStack::Frame::Kind,
+            mozilla::HangStack::Frame::Kind::STRING,
+            mozilla::HangStack::Frame::Kind::END>
+{};
+
+template<>
+struct ParamTraits<mozilla::HangStack::Module>
+{
+public:
+  typedef mozilla::HangStack::Module paramType;
+  static void Write(Message* aMsg, const paramType& aParam);
+  static bool Read(const Message* aMsg,
+                   PickleIterator* aIter,
+                   paramType* aResult);
+};
+
+template<>
+struct ParamTraits<mozilla::HangStack>
+{
   typedef mozilla::HangStack paramType;
   static void Write(Message* aMsg, const paramType& aParam);
   static bool Read(const Message* aMsg,
                    PickleIterator* aIter,
                    paramType* aResult);
 };
 
 } // namespace IPC
--- a/toolkit/components/backgroundhangmonitor/ThreadStackHelper.cpp
+++ b/toolkit/components/backgroundhangmonitor/ThreadStackHelper.cpp
@@ -17,16 +17,17 @@
 #include "mozilla/Attributes.h"
 #include "mozilla/IntegerPrintfMacros.h"
 #include "mozilla/Move.h"
 #include "mozilla/Scoped.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/MemoryChecking.h"
 #include "mozilla/Sprintf.h"
 #include "nsThread.h"
+#include "mozilla/HangStack.h"
 
 #ifdef __GNUC__
 # pragma GCC diagnostic push
 # pragma GCC diagnostic ignored "-Wshadow"
 #endif
 
 #if defined(MOZ_VALGRIND)
 # include <valgrind/valgrind.h>
@@ -61,161 +62,154 @@
 #ifndef SYS_rt_tgsigqueueinfo
 #define SYS_rt_tgsigqueueinfo __NR_rt_tgsigqueueinfo
 #endif
 #endif
 
 namespace mozilla {
 
 ThreadStackHelper::ThreadStackHelper()
-#ifdef MOZ_THREADSTACKHELPER_PSEUDO
   : mStackToFill(nullptr)
-  , mPseudoStack(profiler_get_pseudo_stack())
-  , mMaxStackSize(Stack::sMaxInlineStorage)
+  , mMaxStackSize(HangStack::sMaxInlineStorage)
   , mMaxBufferSize(512)
-#endif
-#ifdef MOZ_THREADSTACKHELPER_NATIVE
-  , mNativeStackToFill(nullptr)
-#endif
+  , mDesiredStackSize(0)
+  , mDesiredBufferSize(0)
 {
   mThreadId = profiler_current_thread_id();
 }
 
+bool
+ThreadStackHelper::PrepareStackBuffer(HangStack& aStack)
+{
+  // If we need to grow because we used more than we could store last time,
+  // increase our maximum sizes for this time.
+  if (mDesiredBufferSize > mMaxBufferSize) {
+    mMaxBufferSize = mDesiredBufferSize;
+  }
+  if (mDesiredStackSize > mMaxStackSize) {
+    mMaxStackSize = mDesiredStackSize;
+  }
+  mDesiredBufferSize = 0;
+  mDesiredStackSize = 0;
+
+  // Return false to skip getting the stack and return an empty stack
+  aStack.clear();
+#ifdef MOZ_THREADSTACKHELPER_PSEUDO
+  if (!aStack.reserve(mMaxStackSize) ||
+      !aStack.reserve(aStack.capacity()) || // reserve up to the capacity
+      !aStack.EnsureBufferCapacity(mMaxBufferSize)) {
+    return false;
+  }
+  return true;
+#else
+  return false;
+#endif
+}
+
 namespace {
 template<typename T>
 class ScopedSetPtr
 {
 private:
   T*& mPtr;
 public:
   ScopedSetPtr(T*& p, T* val) : mPtr(p) { mPtr = val; }
   ~ScopedSetPtr() { mPtr = nullptr; }
 };
 } // namespace
 
 void
-ThreadStackHelper::GetPseudoStack(Stack& aStack, nsACString& aRunnableName)
-{
-  GetStacksInternal(&aStack, nullptr, aRunnableName);
-}
-
-void
-ThreadStackHelper::GetNativeStack(NativeStack& aNativeStack, nsACString& aRunnableName)
-{
-  GetStacksInternal(nullptr, &aNativeStack, aRunnableName);
-}
-
-void
-ThreadStackHelper::GetPseudoAndNativeStack(Stack& aStack,
-                                           NativeStack& aNativeStack,
-                                           nsACString& aRunnableName)
-{
-  GetStacksInternal(&aStack, &aNativeStack, aRunnableName);
-}
-
-void
-ThreadStackHelper::GetStacksInternal(Stack* aStack,
-                                     NativeStack* aNativeStack,
-                                     nsACString& aRunnableName)
+ThreadStackHelper::GetStack(HangStack& aStack, nsACString& aRunnableName, bool aStackWalk)
 {
   aRunnableName.AssignLiteral("???");
 
-#if defined(MOZ_THREADSTACKHELPER_PSEUDO) || defined(MOZ_THREADSTACKHELPER_NATIVE)
-  // Always run PrepareStackBuffer first to clear aStack
-  if (aStack && !PrepareStackBuffer(*aStack)) {
-    // Skip and return empty aStack
+  if (!PrepareStackBuffer(aStack)) {
     return;
   }
 
-  // Prepare the native stack
-  if (aNativeStack) {
-    aNativeStack->clear();
-    aNativeStack->reserve(HangStack::sMaxNativeFrames);
-  }
-
-#ifdef MOZ_THREADSTACKHELPER_PSEUDO
-  ScopedSetPtr<Stack> stackPtr(mStackToFill, aStack);
-#endif
-#ifdef MOZ_THREADSTACKHELPER_NATIVE
-  ScopedSetPtr<NativeStack> nativeStackPtr(mNativeStackToFill, aNativeStack);
-#endif
-
   Array<char, nsThread::kRunnableNameBufSize> runnableName;
   runnableName[0] = '\0';
-  auto callback = [&, this] (void** aPCs, size_t aCount, bool aIsMainThread) {
-    // NOTE: We cannot allocate any memory in this callback, as the target
-    // thread is suspended, so we first copy it into a stack-allocated buffer,
-    // and then once the target thread is resumed, we can copy it into a real
-    // nsCString.
-    //
-    // Currently we only store the names of runnables which are running on the
-    // main thread, so we only want to read sMainThreadRunnableName and copy its
-    // value in the case that we are currently suspending the main thread.
-    if (aIsMainThread) {
-      runnableName = nsThread::sMainThreadRunnableName;
-    }
+
+  ScopedSetPtr<HangStack> _stackGuard(mStackToFill, &aStack);
+  ScopedSetPtr<Array<char, nsThread::kRunnableNameBufSize>>
+    _runnableGuard(mRunnableNameBuffer, &runnableName);
 
-#ifdef MOZ_THREADSTACKHELPER_PSEUDO
-    if (mStackToFill) {
-      FillStackBuffer();
-    }
-#endif
-
-#ifdef MOZ_THREADSTACKHELPER_NATIVE
-    if (mNativeStackToFill) {
-      while (aCount-- &&
-             mNativeStackToFill->size() < mNativeStackToFill->capacity()) {
-        mNativeStackToFill->push_back(reinterpret_cast<uintptr_t>(aPCs[aCount]));
-      }
-    }
-#endif
-  };
-
-  if (mStackToFill || mNativeStackToFill) {
-    profiler_suspend_and_sample_thread(mThreadId,
-                                       callback,
-                                       /* aSampleNative = */ !!aNativeStack);
-  }
+  // XXX: We don't need to pass in ProfilerFeature::StackWalk to trigger
+  // stackwalking, as that is instead controlled by the last argument.
+  profiler_suspend_and_sample_thread(
+    mThreadId, ProfilerFeature::Privacy, *this, aStackWalk);
 
   // Copy the name buffer allocation into the output string. We explicitly set
   // the last byte to null in case we read in some corrupted data without a null
   // terminator.
   runnableName[nsThread::kRunnableNameBufSize - 1] = '\0';
   aRunnableName.AssignASCII(runnableName.cbegin());
-#endif
+}
+
+void
+ThreadStackHelper::SetIsMainThread()
+{
+  MOZ_RELEASE_ASSERT(mRunnableNameBuffer);
+
+  // NOTE: We cannot allocate any memory in this callback, as the target
+  // thread is suspended, so we first copy it into a stack-allocated buffer,
+  // and then once the target thread is resumed, we can copy it into a real
+  // nsCString.
+  //
+  // Currently we only store the names of runnables which are running on the
+  // main thread, so we only want to read sMainThreadRunnableName and copy its
+  // value in the case that we are currently suspending the main thread.
+  *mRunnableNameBuffer = nsThread::sMainThreadRunnableName;
 }
 
-bool
-ThreadStackHelper::PrepareStackBuffer(Stack& aStack)
+void
+ThreadStackHelper::TryAppendFrame(HangStack::Frame aFrame)
 {
-  // Return false to skip getting the stack and return an empty stack
-  aStack.clear();
-#ifdef MOZ_THREADSTACKHELPER_PSEUDO
-  MOZ_ASSERT(mPseudoStack);
-  if (!aStack.reserve(mMaxStackSize) ||
-      !aStack.reserve(aStack.capacity()) || // reserve up to the capacity
-      !aStack.EnsureBufferCapacity(mMaxBufferSize)) {
-    return false;
+  MOZ_RELEASE_ASSERT(mStackToFill);
+
+  // Record that we _want_ to use another frame entry. If this exceeds
+  // mMaxStackSize, we'll allocate more room on the next hang.
+  mDesiredStackSize += 1;
+
+  // Perform the append if we have enough space to do so.
+  if (mStackToFill->canAppendWithoutRealloc(1)) {
+    mStackToFill->infallibleAppend(aFrame);
   }
-  return true;
-#else
-  return false;
-#endif
 }
 
-#ifdef MOZ_THREADSTACKHELPER_PSEUDO
+void
+ThreadStackHelper::CollectNativeLeafAddr(void* aAddr)
+{
+  MOZ_RELEASE_ASSERT(mStackToFill);
+  TryAppendFrame(HangStack::Frame(reinterpret_cast<uintptr_t>(aAddr)));
+}
+
+void
+ThreadStackHelper::CollectJitReturnAddr(void* aAddr)
+{
+  MOZ_RELEASE_ASSERT(mStackToFill);
+  TryAppendFrame(HangStack::Frame("(jit frame)"));
+}
+
+void
+ThreadStackHelper::CollectWasmFrame(const char* aLabel)
+{
+  MOZ_RELEASE_ASSERT(mStackToFill);
+  // We don't want to collect WASM frames, as they are probably for content, so
+  // we just add a "(content wasm)" frame.
+  TryAppendFrame(HangStack::Frame("(wasm)"));
+}
 
 namespace {
 
 bool
 IsChromeJSScript(JSScript* aScript)
 {
   // May be called from another thread or inside a signal handler.
   // We assume querying the script is safe but we must not manipulate it.
-
   nsIScriptSecurityManager* const secman =
     nsScriptSecurityManager::GetScriptSecurityManager();
   NS_ENSURE_TRUE(secman, false);
 
   JSPrincipals* const principals = JS_GetScriptPrincipals(aScript);
   return secman->IsSystemPrincipal(nsJSPrincipals::get(principals));
 }
 
@@ -245,127 +239,81 @@ GetPathAfterComponent(const char* filena
     // Resume searching before the separator '/'.
     next = strstr(found - 1, component);
   }
   return found;
 }
 
 } // namespace
 
-const char*
-ThreadStackHelper::AppendJSEntry(const js::ProfileEntry* aEntry,
-                                 intptr_t& aAvailableBufferSize,
-                                 const char* aPrevLabel)
+void
+ThreadStackHelper::CollectPseudoEntry(const js::ProfileEntry& aEntry)
 {
-  // May be called from another thread or inside a signal handler.
-  // We assume querying the script is safe but we must not manupulate it.
-  // Also we must not allocate any memory from heap.
-  MOZ_ASSERT(aEntry->isJs());
+  // For non-js frames we just include the raw label.
+  if (!aEntry.isJs()) {
+    const char* label = aEntry.label();
+    TryAppendFrame(HangStack::Frame(label));
+    return;
+  }
 
-  const char* label;
-  JSScript* script = aEntry->script();
-  if (!script) {
-    label = "(profiling suppressed)";
-  } else if (IsChromeJSScript(aEntry->script())) {
-    const char* filename = JS_GetScriptFilename(aEntry->script());
-    const unsigned lineno = JS_PCToLineNumber(aEntry->script(), aEntry->pc());
-    MOZ_ASSERT(filename);
-
-    char buffer[128]; // Enough to fit longest js file name from the tree
-
-    // Some script names are in the form "foo -> bar -> baz".
-    // Here we find the origin of these redirected scripts.
-    const char* basename = GetPathAfterComponent(filename, " -> ");
-    if (basename) {
-      filename = basename;
-    }
+  if (!aEntry.script()) {
+    TryAppendFrame(HangStack::Frame("(profiling suppressed)"));
+    return;
+  }
 
-    basename = GetFullPathForScheme(filename, "chrome://");
-    if (!basename) {
-      basename = GetFullPathForScheme(filename, "resource://");
-    }
-    if (!basename) {
-      // If the (add-on) script is located under the {profile}/extensions
-      // directory, extract the path after the /extensions/ part.
-      basename = GetPathAfterComponent(filename, "/extensions/");
-    }
-    if (!basename) {
-      // Only keep the file base name for paths outside the above formats.
-      basename = strrchr(filename, '/');
-      basename = basename ? basename + 1 : filename;
-      // Look for Windows path separator as well.
-      filename = strrchr(basename, '\\');
-      if (filename) {
-        basename = filename + 1;
-      }
-    }
+  if (!IsChromeJSScript(aEntry.script())) {
+    TryAppendFrame(HangStack::Frame("(content script)"));
+    return;
+  }
 
-    size_t len = SprintfLiteral(buffer, "%s:%u", basename, lineno);
-    if (len < sizeof(buffer)) {
-      if (mStackToFill->IsSameAsEntry(aPrevLabel, buffer)) {
-        return aPrevLabel;
-      }
+  // Rather than using the profiler's dynamic string, we compute our own string.
+  // This is because we want to do some size-saving strategies, and throw out
+  // information which won't help us as much.
+  // XXX: We currently don't collect the function name which hung.
+  const char* filename = JS_GetScriptFilename(aEntry.script());
+  unsigned lineno = JS_PCToLineNumber(aEntry.script(), aEntry.pc());
 
-      // Keep track of the required buffer size
-      aAvailableBufferSize -= (len + 1);
-      if (aAvailableBufferSize >= 0) {
-        // Buffer is big enough.
-        return mStackToFill->InfallibleAppendViaBuffer(buffer, len);
-      }
-      // Buffer is not big enough; fall through to using static label below.
-    }
-    // snprintf failed or buffer is not big enough.
-    label = "(chrome script)";
-  } else {
-    label = "(content script)";
+  // Some script names are in the form "foo -> bar -> baz".
+  // Here we find the origin of these redirected scripts.
+  const char* basename = GetPathAfterComponent(filename, " -> ");
+  if (basename) {
+    filename = basename;
   }
 
-  if (mStackToFill->IsSameAsEntry(aPrevLabel, label)) {
-    return aPrevLabel;
+  // Strip chrome:// or resource:// off of the filename if present.
+  basename = GetFullPathForScheme(filename, "chrome://");
+  if (!basename) {
+    basename = GetFullPathForScheme(filename, "resource://");
+  }
+  if (!basename) {
+    // If we're in an add-on script, under the {profile}/extensions
+    // directory, extract the path after the /extensions/ part.
+    basename = GetPathAfterComponent(filename, "/extensions/");
   }
-  mStackToFill->infallibleAppend(label);
-  return label;
-}
-
-#endif // MOZ_THREADSTACKHELPER_PSEUDO
-
-void
-ThreadStackHelper::FillStackBuffer()
-{
-#ifdef MOZ_THREADSTACKHELPER_PSEUDO
-  MOZ_ASSERT(mStackToFill->empty());
-
-  size_t reservedSize = mStackToFill->capacity();
-  size_t reservedBufferSize = mStackToFill->AvailableBufferSize();
-  intptr_t availableBufferSize = intptr_t(reservedBufferSize);
-
-  // Go from front to back
-  const js::ProfileEntry* entry = mPseudoStack->entries;
-  const js::ProfileEntry* end = entry + mPseudoStack->stackSize();
-  // Deduplicate identical, consecutive frames
-  const char* prevLabel = nullptr;
-  for (; reservedSize-- && entry != end; entry++) {
-    if (entry->isJs()) {
-      prevLabel = AppendJSEntry(entry, availableBufferSize, prevLabel);
-      continue;
+  if (!basename) {
+    // Only keep the file base name for paths outside the above formats.
+    basename = strrchr(filename, '/');
+    basename = basename ? basename + 1 : filename;
+    // Look for Windows path separator as well.
+    filename = strrchr(basename, '\\');
+    if (filename) {
+      basename = filename + 1;
     }
-    const char* const label = entry->label();
-    if (mStackToFill->IsSameAsEntry(prevLabel, label)) {
-      // Avoid duplicate labels to save space in the stack.
-      continue;
-    }
-    mStackToFill->infallibleAppend(label);
-    prevLabel = label;
   }
 
-  // end != entry if we exited early due to not enough reserved frames.
-  // Expand the number of reserved frames for next time.
-  mMaxStackSize = mStackToFill->capacity() + (end - entry);
+  mDesiredStackSize += 1;
+  char buffer[128]; // Enough to fit longest js file name from the tree
+  size_t len = SprintfLiteral(buffer, "%s:%u", basename, lineno);
+  if (len < sizeof(buffer)) {
+    mDesiredBufferSize += len + 1;
+    if (mStackToFill->canAppendWithoutRealloc(1) &&
+        mStackToFill->AvailableBufferSize() >= len + 1) {
+      mStackToFill->InfallibleAppendViaBuffer(buffer, len);
+      return;
+    }
+  }
 
-  // availableBufferSize < 0 if we needed a larger buffer than we reserved.
-  // Calculate a new reserve size for next time.
-  if (availableBufferSize < 0) {
-    mMaxBufferSize = reservedBufferSize - availableBufferSize;
+  if (mStackToFill->canAppendWithoutRealloc(1)) {
+    mStackToFill->infallibleAppend(HangStack::Frame("(chrome script)"));
   }
-#endif
 }
 
 } // namespace mozilla
--- a/toolkit/components/backgroundhangmonitor/ThreadStackHelper.h
+++ b/toolkit/components/backgroundhangmonitor/ThreadStackHelper.h
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_ThreadStackHelper_h
 #define mozilla_ThreadStackHelper_h
 
 #include "js/ProfilingStack.h"
 #include "HangDetails.h"
+#include "nsThread.h"
 
 #include <stddef.h>
 
 #if defined(XP_LINUX)
 #include <signal.h>
 #include <semaphore.h>
 #include <sys/types.h>
 #elif defined(XP_WIN)
@@ -45,90 +46,64 @@ namespace mozilla {
  * thread, as an alternative of using the profiler to take a profile.
  * The target thread first declares an ThreadStackHelper instance;
  * then another thread can call ThreadStackHelper::GetStack to retrieve
  * the pseudo-stack of the target thread at that instant.
  *
  * Only non-copying labels are included in the stack, which means labels
  * with custom text and markers are not included.
  */
-class ThreadStackHelper
+class ThreadStackHelper : public ProfilerStackCollector
 {
 public:
-  typedef HangStack Stack;
-
   // When a native stack is gathered, this vector holds the raw program counter
   // values that FramePointerStackWalk will return to us after it walks the
   // stack. When gathering the Telemetry payload, Telemetry will take care of
   // mapping these program counters to proper addresses within modules.
   typedef NativeHangStack NativeStack;
 
 private:
-#ifdef MOZ_THREADSTACKHELPER_PSEUDO
-  Stack* mStackToFill;
-  const PseudoStack* const mPseudoStack;
+  HangStack* mStackToFill;
+  Array<char, nsThread::kRunnableNameBufSize>* mRunnableNameBuffer;
+  // const PseudoStack* const mPseudoStack;
   size_t mMaxStackSize;
   size_t mMaxBufferSize;
-#endif
-#ifdef MOZ_THREADSTACKHELPER_NATIVE
-  NativeStack* mNativeStackToFill;
-#endif
+  size_t mDesiredStackSize;
+  size_t mDesiredBufferSize;
 
-  bool PrepareStackBuffer(Stack& aStack);
-  void FillStackBuffer();
-#ifdef MOZ_THREADSTACKHELPER_PSEUDO
-  const char* AppendJSEntry(const js::ProfileEntry* aEntry,
-                            intptr_t& aAvailableBufferSize,
-                            const char* aPrevLabel);
-#endif
+  bool PrepareStackBuffer(HangStack& aStack);
 
 public:
   /**
    * Create a ThreadStackHelper instance targeting the current thread.
    */
   ThreadStackHelper();
 
   /**
-   * Retrieve the current pseudostack of the thread associated
-   * with this ThreadStackHelper.
+   * Retrieve the current interleaved stack of the thread associated with this ThreadStackHelper.
    *
-   * @param aStack         Stack instance to be filled.
-   * @param aRunnableName  The name of the current runnable on the target thread.
-   */
-  void GetPseudoStack(Stack& aStack, nsACString& aRunnableName);
-
-  /**
-   * Retrieve the current native stack of the thread associated
-   * with this ThreadStackHelper.
-   *
-   * @param aNativeStack   NativeStack instance to be filled.
-   * @param aRunnableName  The name of the current runnable on the target thread.
+   * @param aStack        HangStack instance to be filled.
+   * @param aRunnableName The name of the current runnable on the target thread.
+   * @param aStackWalk    If true, native stack frames will be collected
+   *                      along with pseudostack frames.
    */
-  void GetNativeStack(NativeStack& aNativeStack, nsACString& aRunnableName);
+  void GetStack(HangStack& aStack, nsACString& aRunnableName, bool aStackWalk);
 
+protected:
   /**
-   * Retrieve the current pseudostack and native stack of the thread associated
-   * with this ThreadStackHelper. This method only pauses the target thread once
-   * to get both stacks.
-   *
-   * @param aStack         Stack instance to be filled with the pseudostack.
-   * @param aNativeStack   NativeStack instance to be filled with the native stack.
-   * @param aRunnableName  The name of the current runnable on the target thread.
+   * ProfilerStackCollector
    */
-  void GetPseudoAndNativeStack(Stack& aStack,
-                               NativeStack& aNativeStack,
-                               nsACString& aRunnableName);
+  virtual void SetIsMainThread() override;
+  virtual void CollectNativeLeafAddr(void* aAddr) override;
+  virtual void CollectJitReturnAddr(void* aAddr) override;
+  virtual void CollectWasmFrame(const char* aLabel) override;
+  virtual void CollectPseudoEntry(const js::ProfileEntry& aEntry) override;
 
 private:
-  // Fill in the passed aStack and aNativeStack datastructures with backtraces.
-  // If only aStack needs to be collected, nullptr may be passed for
-  // aNativeStack, and vice versa.
-  void GetStacksInternal(Stack* aStack,
-                         NativeStack* aNativeStack,
-                         nsACString& aRunnableName);
+  void TryAppendFrame(mozilla::HangStack::Frame aFrame);
 
   // The profiler's unique thread identifier for the target thread.
   int mThreadId;
 };
 
 } // namespace mozilla
 
 #endif // mozilla_ThreadStackHelper_h
--- a/toolkit/components/backgroundhangmonitor/nsIHangDetails.idl
+++ b/toolkit/components/backgroundhangmonitor/nsIHangDetails.idl
@@ -58,14 +58,9 @@ interface nsIHangDetails : nsISupports
    */
   [implicit_jscontext] readonly attribute jsval modules;
 
   /**
    * The hang annotations which were captured when the hang occured. This
    * attribute is a JS object of key-value pairs.
    */
   [implicit_jscontext] readonly attribute jsval annotations;
-
-  /**
-   * A getter for the pseudoStack. This attribute is a JS array of strings.
-   */
-  [implicit_jscontext] readonly attribute jsval pseudoStack;
 };
--- a/toolkit/components/backgroundhangmonitor/tests/test_BHRObserver.js
+++ b/toolkit/components/backgroundhangmonitor/tests/test_BHRObserver.js
@@ -77,23 +77,28 @@ add_task(async function test_BHRObserver
   equal(hangs.length, 3);
   hangs.forEach(hang => {
     ok(hang.duration > 0);
     ok(hang.thread == "Gecko" || hang.thread == "Gecko_Child");
     equal(typeof hang.runnableName, "string");
 
     // hang.stack
     ok(Array.isArray(hang.stack));
+    ok(hang.stack.length > 0);
     hang.stack.forEach(entry => {
-      // XXX: Once we get merged stacks, we might get pseudostack or js frames
-      // in here too.
-      ok(Array.isArray(entry));
-      equal(entry.length, 2);
-      equal(typeof entry[0], "number");
-      equal(typeof entry[1], "string");
+      // Each stack frame entry is either a native or pseudostack entry. A
+      // native stack entry is an array with module index (number), and offset
+      // (hex string), while the pseudostack entry is a bare string.
+      if (Array.isArray(entry)) {
+        equal(entry.length, 2);
+        equal(typeof entry[0], "number");
+        equal(typeof entry[1], "string");
+      } else {
+        equal(typeof entry, "string");
+      }
     });
 
     // hang.modules
     ok(Array.isArray(hang.modules));
     hang.modules.forEach(module => {
       ok(Array.isArray(module));
       equal(module.length, 2);
       equal(typeof module[0], "string");
@@ -101,20 +106,13 @@ add_task(async function test_BHRObserver
     });
 
     // hang.annotations
     equal(typeof hang.annotations, "object");
     Object.keys(hang.annotations).forEach(key => {
       equal(typeof hang.annotations[key], "string");
     });
 
-    // hang.pseudoStack
-    // XXX: This will go away once we get merged stacks
-    ok(Array.isArray(hang.pseudoStack));
-    ok(hang.pseudoStack.length > 0);
-    hang.pseudoStack.forEach(entry => {
-      equal(typeof entry, "string");
-    });
   });
 
   do_send_remote_message("bhr_hangs_detected");
   await childDone;
 });
--- a/toolkit/components/prompts/src/CommonDialog.jsm
+++ b/toolkit/components/prompts/src/CommonDialog.jsm
@@ -86,16 +86,20 @@ CommonDialog.prototype = {
             // Clear the label, since the message presumably indicates its purpose.
             this.ui.password1Label.setAttribute("value", "");
             break;
           default:
             Cu.reportError("commonDialog opened for unknown type: " + this.args.promptType);
             throw "unknown dialog type";
         }
 
+        if (xulDialog) {
+            xulDialog.setAttribute("windowtype", "prompt:" + this.args.promptType);
+        }
+
         // set the document title
         let title = this.args.title;
         // OS X doesn't have a title on modal dialogs, this is hidden on other platforms.
         let infoTitle = this.ui.infoTitle;
         infoTitle.appendChild(infoTitle.ownerDocument.createTextNode(title));
         if (xulDialog)
             xulDialog.ownerDocument.title = title;
 
--- a/toolkit/content/widgets/browser.xml
+++ b/toolkit/content/widgets/browser.xml
@@ -1263,16 +1263,17 @@
           <![CDATA[
             const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
             var popup = document.createElementNS(XUL_NS, "panel");
             popup.className = "autoscroller";
             // We set this attribute on the element so that mousemove
             // events can be handled by browser-content.js.
             popup.setAttribute("mousethrough", "always");
             popup.setAttribute("rolluponmousewheel", "true");
+            popup.setAttribute("hidden", "true");
             return popup;
           ]]>
         </body>
       </method>
 
       <method name="startScroll">
         <parameter name="scrolldir"/>
         <parameter name="screenX"/>
@@ -1293,16 +1294,17 @@
             // we need these attributes so themers don't need to create per-platform packages
             if (screen.colorDepth > 8) { // need high color for transparency
               // Exclude second-rate platforms
               this._autoScrollPopup.setAttribute("transparent", !/BeOS|OS\/2/.test(navigator.appVersion));
               // Enable translucency on Windows and Mac
               this._autoScrollPopup.setAttribute("translucent", /Win|Mac/.test(navigator.platform));
             }
 
+            this._autoScrollPopup.removeAttribute("hidden");
             this._autoScrollPopup.setAttribute("noautofocus", "true");
             this._autoScrollPopup.setAttribute("scrolldir", scrolldir);
             this._autoScrollPopup.addEventListener("popuphidden", this, true);
             this._autoScrollPopup.showPopup(document.documentElement,
                                             screenX,
                                             screenY,
                                             "popup", null, null);
             this._ignoreMouseEvents = true;
--- a/tools/profiler/core/ProfileBuffer.cpp
+++ b/tools/profiler/core/ProfileBuffer.cpp
@@ -2,16 +2,19 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ProfileBuffer.h"
 
 #include "ProfilerMarker.h"
+#include "jsfriendapi.h"
+#include "nsScriptSecurityManager.h"
+#include "nsJSPrincipals.h"
 
 using namespace mozilla;
 
 ProfileBuffer::ProfileBuffer(int aEntrySize)
   : mEntries(mozilla::MakeUnique<ProfileBufferEntry[]>(aEntrySize))
   , mWritePos(0)
   , mReadPos(0)
   , mEntrySize(aEntrySize)
@@ -60,28 +63,16 @@ ProfileBuffer::AddThreadIdEntry(int aThr
 void
 ProfileBuffer::AddStoredMarker(ProfilerMarker *aStoredMarker)
 {
   aStoredMarker->SetGeneration(mGeneration);
   mStoredMarkers.insert(aStoredMarker);
 }
 
 void
-ProfileBuffer::CollectNativeLeafAddr(void* aAddr)
-{
-  AddEntry(ProfileBufferEntry::NativeLeafAddr(aAddr));
-}
-
-void
-ProfileBuffer::CollectJitReturnAddr(void* aAddr)
-{
-  AddEntry(ProfileBufferEntry::JitReturnAddr(aAddr));
-}
-
-void
 ProfileBuffer::CollectCodeLocation(
   const char* aLabel, const char* aStr, int aLineNumber,
   const Maybe<js::ProfileEntry::Category>& aCategory)
 {
   AddEntry(ProfileBufferEntry::Label(aLabel));
 
   if (aStr) {
     // Store the string using one or more DynamicStringFragment entries.
@@ -137,8 +128,100 @@ ProfileBuffer::SizeOfIncludingThis(mozil
   // Measurement of the following members may be added later if DMD finds it
   // is worthwhile:
   // - memory pointed to by the elements within mEntries
   // - mStoredMarkers
 
   return n;
 }
 
+/* ProfileBufferCollector */
+
+static bool
+IsChromeJSScript(JSScript* aScript)
+{
+  // WARNING: this function runs within the profiler's "critical section".
+
+  nsIScriptSecurityManager* const secman =
+    nsScriptSecurityManager::GetScriptSecurityManager();
+  NS_ENSURE_TRUE(secman, false);
+
+  JSPrincipals* const principals = JS_GetScriptPrincipals(aScript);
+  return secman->IsSystemPrincipal(nsJSPrincipals::get(principals));
+}
+
+Maybe<uint32_t>
+ProfileBufferCollector::Generation()
+{
+  return Some(mBuf.mGeneration);
+}
+
+void
+ProfileBufferCollector::CollectNativeLeafAddr(void* aAddr)
+{
+  mBuf.AddEntry(ProfileBufferEntry::NativeLeafAddr(aAddr));
+}
+
+void
+ProfileBufferCollector::CollectJitReturnAddr(void* aAddr)
+{
+  mBuf.AddEntry(ProfileBufferEntry::JitReturnAddr(aAddr));
+}
+
+void
+ProfileBufferCollector::CollectWasmFrame(const char* aLabel)
+{
+  mBuf.CollectCodeLocation("", aLabel, -1, Nothing());
+}
+
+void
+ProfileBufferCollector::CollectPseudoEntry(const js::ProfileEntry& aEntry)
+{
+  // WARNING: this function runs within the profiler's "critical section".
+
+  MOZ_ASSERT(aEntry.kind() == js::ProfileEntry::Kind::CPP_NORMAL ||
+             aEntry.kind() == js::ProfileEntry::Kind::JS_NORMAL);
+
+  const char* label = aEntry.label();
+  const char* dynamicString = aEntry.dynamicString();
+  bool isChromeJSEntry = false;
+  int lineno = -1;
+
+  if (aEntry.isJs()) {
+    // There are two kinds of JS frames that get pushed onto the PseudoStack.
+    //
+    // - label = "", dynamic string = <something>
+    // - label = "js::RunScript", dynamic string = nullptr
+    //
+    // The line number is only interesting in the first case.
+
+    if (label[0] == '\0') {
+      MOZ_ASSERT(dynamicString);
+
+      // We call aEntry.script() repeatedly -- rather than storing the result in
+      // a local variable in order -- to avoid rooting hazards.
+      if (aEntry.script()) {
+        isChromeJSEntry = IsChromeJSScript(aEntry.script());
+        if (aEntry.pc()) {
+          lineno = JS_PCToLineNumber(aEntry.script(), aEntry.pc());
+        }
+      }
+
+    } else {
+      MOZ_ASSERT(strcmp(label, "js::RunScript") == 0 && !dynamicString);
+    }
+  } else {
+    MOZ_ASSERT(aEntry.isCpp());
+    lineno = aEntry.line();
+  }
+
+  if (dynamicString) {
+    // Adjust the dynamic string as necessary.
+    if (ProfilerFeature::HasPrivacy(mFeatures) && !isChromeJSEntry) {
+      dynamicString = "(private)";
+    } else if (strlen(dynamicString) >= ProfileBuffer::kMaxFrameKeyLength) {
+      dynamicString = "(too long)";
+    }
+  }
+
+  mBuf.CollectCodeLocation(label, dynamicString, lineno,
+                           Some(aEntry.category()));
+}
--- a/tools/profiler/core/ProfileBuffer.h
+++ b/tools/profiler/core/ProfileBuffer.h
@@ -8,17 +8,17 @@
 
 #include "platform.h"
 #include "ProfileBufferEntry.h"
 #include "ProfilerMarker.h"
 #include "ProfileJSONWriter.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/RefCounted.h"
 
-class ProfileBuffer final : public ProfilerStackCollector
+class ProfileBuffer final
 {
 public:
   explicit ProfileBuffer(int aEntrySize);
 
   ~ProfileBuffer();
 
   // LastSample is used to record the buffer location of the most recent
   // sample for each thread. It is used for periodic samples stored in the
@@ -37,26 +37,19 @@ public:
 
   // Add |aEntry| to the buffer, ignoring what kind of entry it is.
   void AddEntry(const ProfileBufferEntry& aEntry);
 
   // Add to the buffer a sample start (ThreadId) entry for aThreadId. Also,
   // record the resulting generation and index in |aLS| if it's non-null.
   void AddThreadIdEntry(int aThreadId, LastSample* aLS = nullptr);
 
-  virtual mozilla::Maybe<uint32_t> Generation() override
-  {
-    return mozilla::Some(mGeneration);
-  }
-
-  virtual void CollectNativeLeafAddr(void* aAddr) override;
-  virtual void CollectJitReturnAddr(void* aAddr) override;
-  virtual void CollectCodeLocation(
+  void CollectCodeLocation(
     const char* aLabel, const char* aStr, int aLineNumber,
-    const mozilla::Maybe<js::ProfileEntry::Category>& aCategory) override;
+    const mozilla::Maybe<js::ProfileEntry::Category>& aCategory);
 
   // Maximum size of a frameKey string that we'll handle.
   static const size_t kMaxFrameKeyLength = 512;
 
   void StreamSamplesToJSON(SpliceableJSONWriter& aWriter, int aThreadId,
                            double aSinceTime, double* aOutFirstSampleTime,
                            JSContext* cx,
                            UniqueStacks& aUniqueStacks) const;
@@ -100,9 +93,34 @@ public:
 
   // How many times mWritePos has wrapped around.
   uint32_t mGeneration;
 
   // Markers that marker entries in the buffer might refer to.
   ProfilerMarkerLinkedList mStoredMarkers;
 };
 
+/**
+ * Helper type used to implement ProfilerStackCollector. This type is used as
+ * the collector for MergeStacks by ProfileBuffer. It holds a reference to the
+ * buffer, as well as additional feature flags which are needed to control the
+ * data collection strategy
+ */
+class ProfileBufferCollector final : public ProfilerStackCollector
+{
+public:
+  ProfileBufferCollector(ProfileBuffer& aBuf, uint32_t aFeatures)
+    : mBuf(aBuf)
+    , mFeatures(aFeatures)
+  {}
+
+  virtual mozilla::Maybe<uint32_t> Generation() override;
+  virtual void CollectNativeLeafAddr(void* aAddr) override;
+  virtual void CollectJitReturnAddr(void* aAddr) override;
+  virtual void CollectWasmFrame(const char* aLabel) override;
+  virtual void CollectPseudoEntry(const js::ProfileEntry& aEntry) override;
+
+private:
+  ProfileBuffer& mBuf;
+  uint32_t mFeatures;
+};
+
 #endif
--- a/tools/profiler/core/platform.cpp
+++ b/tools/profiler/core/platform.cpp
@@ -711,90 +711,16 @@ public:
   Address mLR;    // ARM link register.
</