merge autoland to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Mon, 06 Mar 2017 10:51:51 +0100
changeset 394966 7099e03837e84970d07d0c7fbdf6724643cc6f30
parent 394823 8d026c60151005ad942e3d4389318fe28a0c8c54 (current diff)
parent 394965 911024a9cf3c072f0e11bf5f6dafce3db07bd881 (diff)
child 395024 966464a68a2cb3ca1125808e34abb5c1d34e3797
push id1468
push userasasaki@mozilla.com
push dateMon, 05 Jun 2017 19:31:07 +0000
treeherdermozilla-release@0641fc6ee9d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone54.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge autoland to mozilla-central a=merge
browser/base/content/test/webextensions/browser_extension_update_interactive.js
browser/components/newtab/tests/browser/browser_newtabmessages.js
browser/components/newtab/tests/browser/browser_newtabwebchannel.js
browser/components/newtab/tests/browser/newtabmessages_places.html
browser/components/newtab/tests/browser/newtabmessages_prefs.html
browser/components/newtab/tests/browser/newtabmessages_preview.html
browser/components/newtab/tests/browser/newtabmessages_search.html
browser/components/newtab/tests/browser/newtabwebchannel_basic.html
devtools/client/responsive.html/images/close.svg
layout/style/AnimationCommon.cpp
mobile/android/tests/background/junit4/src/org/mozilla/gecko/db/BrowserProviderHighlightsTest.java
netwerk/base/nsNetUtilInlines.h
services/common/stringbundle.js
services/sync/tests/unit/test_utils_lazyStrings.js
testing/web-platform/meta/webvtt/webvtt-api-for-browsers/vttcue-interface/getCueAsHTML.html.ini
tools/lint/eslint/eslint-plugin-mozilla/lib/rules/.eslintrc.js
--- a/.eslintignore
+++ b/.eslintignore
@@ -50,27 +50,26 @@ xulrunner/**
 # b2g exclusions (pref files).
 b2g/app/b2g.js
 b2g/graphene/graphene.js
 b2g/locales/en-US/b2g-l10n.js
 
 # browser/ exclusions
 browser/app/**
 browser/branding/**/firefox-branding.js
-browser/base/content/browser-social.js
 browser/base/content/nsContextMenu.js
 browser/base/content/sanitizeDialog.js
 browser/base/content/test/general/file_csp_block_all_mixedcontent.html
 browser/base/content/test/urlbar/file_blank_but_not_blank.html
 browser/base/content/newtab/**
 browser/components/downloads/**
 browser/components/sessionstore/**
 browser/components/tabview/**
-# generated files in cld2
-browser/components/translation/cld2/cld-worker.js
+# generated & special files in cld2
+browser/components/translation/cld2/**
 browser/extensions/pdfjs/content/build**
 browser/extensions/pdfjs/content/web**
 # generated or library files in pocket
 browser/extensions/pocket/content/panels/js/tmpl.js
 browser/extensions/pocket/content/panels/js/vendor/**
 browser/locales/**
 # imported from chromium
 browser/extensions/mortar/**
--- a/addon-sdk/source/test/test-l10n-locale.js
+++ b/addon-sdk/source/test/test-l10n-locale.js
@@ -105,18 +105,17 @@ exports.testPreferedContentLocale = func
   prefs.reset(PREF_ACCEPT_LANGUAGES);
 }
 
 exports.testPreferedOsLocale = function(assert) {
   prefs.set(PREF_MATCH_OS_LOCALE, true);
   prefs.set(PREF_SELECTED_LOCALE, "");
   prefs.set(PREF_ACCEPT_LANGUAGES, "");
 
-  let expectedLocale = Services.locale.getLocaleComponentForUserAgent().
-    toLowerCase();
+  let expectedLocale = Services.locale.getAppLocale().toLowerCase();
   let expectedLocaleList = [expectedLocale];
 
   // Add default "en-us" fallback if the main language is not already en-us
   if (expectedLocale != "en-us")
     expectedLocaleList.push("en-us");
 
   assertPrefered(assert, expectedLocaleList, "Ensure that we select OS locale when related preference is set");
 
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1185,30 +1185,18 @@ pref("browser.newtabpage.rows", 3);
 pref("browser.newtabpage.columns", 5);
 
 // directory tiles download URL
 pref("browser.newtabpage.directory.source", "https://tiles.services.mozilla.com/v3/links/fetch/%LOCALE%/%CHANNEL%");
 
 // endpoint to send newtab click and view pings
 pref("browser.newtabpage.directory.ping", "https://tiles.services.mozilla.com/v3/links/");
 
-// activates the remote-hosted newtab page
-pref("browser.newtabpage.remote", false);
-
-// remote newtab version targeted
-pref("browser.newtabpage.remote.version", "1");
-
-// Toggles endpoints allowed for remote newtab communications
-pref("browser.newtabpage.remote.mode", "production");
-
-// content-signature tests for remote newtab
-pref("browser.newtabpage.remote.content-signing-test", false);
-
-// verification keys for remote-hosted newtab page
-pref("browser.newtabpage.remote.keys", "");
+// activates Activity Stream
+pref("browser.newtabpage.activity-stream.enabled", false);
 
 // Enable the DOM fullscreen API.
 pref("full-screen-api.enabled", true);
 
 // Startup Crash Tracking
 // number of startup crashes that can occur before starting into safe mode automatically
 // (this pref has no effect if more than 6 hours have passed since the last crash)
 pref("toolkit.startup.max_resumed_crashes", 3);
--- a/browser/base/content/browser-menubar.inc
+++ b/browser/base/content/browser-menubar.inc
@@ -292,16 +292,17 @@
                           accesskey="&fullScreenCmd.accesskey;"
                           label="&fullScreenCmd.label;"
                           key="key_fullScreen"
                           type="checkbox"
                           observes="View:FullScreen"/>
 #endif
                 <menuitem id="menu_readerModeItem"
                           observes="View:ReaderView"
+                          key="key_toggleReaderMode"
                           hidden="true"/>
                 <menuitem id="menu_showAllTabs"
                           hidden="true"
                           accesskey="&showAllTabsCmd.accesskey;"
                           label="&showAllTabsCmd.label;"
                           command="Browser:ShowAllTabs"
                           key="key_showAllTabs"/>
                 <menuseparator hidden="true" id="documentDirection-separator"/>
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -279,17 +279,17 @@
     <key id="showAllHistoryKb" key="&showAllHistoryCmd.commandkey;" command="Browser:ShowAllHistory" modifiers="accel,shift"/>
     <key keycode="VK_F5" command="Browser:ReloadSkipCache" modifiers="accel"/>
     <key id="key_fullScreen" keycode="VK_F11" command="View:FullScreen"/>
 #else
     <key id="key_fullScreen" key="&fullScreenCmd.macCommandKey;" command="View:FullScreen" modifiers="accel,control"/>
     <key id="key_fullScreen_old" key="&fullScreenCmd.macCommandKey;" command="View:FullScreen" modifiers="accel,shift"/>
     <key keycode="VK_F11" command="View:FullScreen"/>
 #endif
-    <key id="toggleReaderMode" key="&toggleReaderMode.key;" command="View:ReaderView" modifiers="accel,alt" disabled="true"/>
+    <key id="key_toggleReaderMode" key="&toggleReaderMode.key;" command="View:ReaderView" modifiers="accel,alt" disabled="true"/>
     <key key="&reloadCmd.commandkey;" command="Browser:Reload" modifiers="accel" id="key_reload"/>
     <key key="&reloadCmd.commandkey;" command="Browser:ReloadSkipCache" modifiers="accel,shift"/>
     <key id="key_viewSource" key="&pageSourceCmd.commandkey;" command="View:PageSource" modifiers="accel"/>
 #ifndef XP_WIN
     <key id="key_viewInfo"   key="&pageInfoCmd.commandkey;"   command="View:PageInfo"   modifiers="accel"/>
 #endif
     <key id="key_find" key="&findOnCmd.commandkey;" command="cmd_find" modifiers="accel"/>
     <key id="key_findAgain" key="&findAgainCmd.commandkey;" command="cmd_findAgain" modifiers="accel"/>
--- a/browser/base/content/browser-social.js
+++ b/browser/base/content/browser-social.js
@@ -1,31 +1,39 @@
 /* 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/. */
 
+/* eslint-env mozilla/browser-window */
+/* eslint no-undef: "error" */
+/* global OpenGraphBuilder:false, DynamicResizeWatcher:false */
+
 // the "exported" symbols
 var SocialUI,
     SocialShare,
     SocialActivationListener;
 
 (function() {
+"use strict";
 
 XPCOMUtils.defineLazyGetter(this, "OpenGraphBuilder", function() {
   let tmp = {};
   Cu.import("resource:///modules/Social.jsm", tmp);
   return tmp.OpenGraphBuilder;
 });
 
 XPCOMUtils.defineLazyGetter(this, "DynamicResizeWatcher", function() {
   let tmp = {};
   Cu.import("resource:///modules/Social.jsm", tmp);
   return tmp.DynamicResizeWatcher;
 });
 
+let messageManager = window.messageManager;
+let openUILinkIn = window.openUILinkIn;
+
 SocialUI = {
   _initialized: false,
 
   // Called on delayed startup to initialize the UI
   init: function SocialUI_init() {
     if (this._initialized) {
       return;
     }
@@ -62,26 +70,26 @@ SocialUI = {
   observe: function SocialUI_observe(subject, topic, data) {
     switch (topic) {
       case "social:providers-changed":
         this._providersChanged();
         break;
     }
   },
 
-  _providersChanged: function() {
+  _providersChanged() {
     SocialShare.populateProviderMenu();
   },
 
-  showLearnMore: function() {
+  showLearnMore() {
     let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "social-api";
     openUILinkIn(url, "tab");
   },
 
-  closeSocialPanelForLinkTraversal: function (target, linkNode) {
+  closeSocialPanelForLinkTraversal(target, linkNode) {
     // No need to close the panel if this traversal was not retargeted
     if (target == "" || target == "_self")
       return;
 
     // Check to see whether this link traversal was in a social panel
     let win = linkNode.ownerGlobal;
     let container = win.QueryInterface(Ci.nsIInterfaceRequestor)
                                   .getInterface(Ci.nsIWebNavigation)
@@ -98,36 +106,36 @@ SocialUI = {
   },
 
   get _chromeless() {
     // Is this a popup window that doesn't want chrome shown?
     let docElem = document.documentElement;
     // extrachrome is not restored during session restore, so we need
     // to check for the toolbar as well.
     let chromeless = docElem.getAttribute("chromehidden").includes("extrachrome") ||
-                     docElem.getAttribute('chromehidden').includes("toolbar");
+                     docElem.getAttribute("chromehidden").includes("toolbar");
     // This property is "fixed" for a window, so avoid doing the check above
     // multiple times...
     delete this._chromeless;
     this._chromeless = chromeless;
     return chromeless;
   },
 
   get enabled() {
     // Returns whether social is enabled *for this window*.
     if (this._chromeless)
       return false;
     return Social.providers.length > 0;
   },
 
-  canSharePage: function(aURI) {
-    return (aURI && (aURI.schemeIs('http') || aURI.schemeIs('https')));
+  canSharePage(aURI) {
+    return (aURI && (aURI.schemeIs("http") || aURI.schemeIs("https")));
   },
 
-  onCustomizeEnd: function(aWindow) {
+  onCustomizeEnd(aWindow) {
     if (aWindow != window)
       return;
     // customization mode gets buttons out of sync with command updating, fix
     // the disabled state
     let canShare = this.canSharePage(gBrowser.currentURI);
     let shareButton = SocialShare.shareButton;
     if (shareButton) {
       if (canShare) {
@@ -135,30 +143,30 @@ SocialUI = {
       } else {
         shareButton.setAttribute("disabled", "true")
       }
     }
   },
 
   // called on tab/urlbar/location changes and after customization. Update
   // anything that is tab specific.
-  updateState: function() {
+  updateState() {
     goSetCommandEnabled("Social:PageShareable", this.canSharePage(gBrowser.currentURI));
   }
 }
 
 // message manager handlers
 SocialActivationListener = {
-  init: function() {
+  init() {
     messageManager.addMessageListener("Social:Activation", this);
   },
-  uninit: function() {
+  uninit() {
     messageManager.removeMessageListener("Social:Activation", this);
   },
-  receiveMessage: function(aMessage) {
+  receiveMessage(aMessage) {
     let data = aMessage.json;
     let browser = aMessage.target;
     data.window = window;
     // if the source if the message is the share panel, we do a one-click
     // installation. The source of activations is controlled by the
     // social.directories preference
     let options;
     if (browser == SocialShare.iframe && Services.prefs.getBoolPref("social.share.activationPanelEnabled")) {
@@ -180,18 +188,18 @@ SocialActivationListener = {
             CustomizableUI.addWidgetToArea("social-share-button", CustomizableUI.AREA_NAVBAR);
             // Ensure correct state.
             SocialUI.onCustomizeEnd(window);
           }
 
           // make this new provider the selected provider. If the panel hasn't
           // been opened, we need to make the frame first.
           SocialShare._createFrame();
-          SocialShare.iframe.setAttribute('src', 'data:text/plain;charset=utf8,');
-          SocialShare.iframe.setAttribute('origin', provider.origin);
+          SocialShare.iframe.setAttribute("src", "data:text/plain;charset=utf8,");
+          SocialShare.iframe.setAttribute("origin", provider.origin);
           // get the right button selected
           SocialShare.populateProviderMenu();
           if (SocialShare.panel.state == "open") {
             SocialShare.sharePage(provider.origin);
           }
         }
         if (provider.postActivationURL) {
           // if activated from an open share panel, we load the landing page in
@@ -224,28 +232,28 @@ SocialShare = {
   },
 
   get iframe() {
     // panel.firstChild is our toolbar hbox, panel.lastChild is the iframe
     // container hbox used for an interstitial "loading" graphic
     return this.panel.lastChild.firstChild;
   },
 
-  uninit: function () {
+  uninit() {
     if (this.iframe) {
       let mm = this.messageManager;
       mm.removeMessageListener("PageVisibility:Show", this);
       mm.removeMessageListener("PageVisibility:Hide", this);
       mm.removeMessageListener("Social:DOMWindowClose", this);
       this.iframe.removeEventListener("load", this);
       this.iframe.remove();
     }
   },
 
-  _createFrame: function() {
+  _createFrame() {
     let panel = this.panel;
     if (this.iframe)
       return;
     this.panel.hidden = false;
     // create and initialize the panel for this window
     let iframe = document.createElement("browser");
     iframe.setAttribute("type", "content");
     iframe.setAttribute("class", "social-share-frame");
@@ -268,58 +276,58 @@ SocialShare = {
   },
 
   get messageManager() {
     // The xbl bindings for the iframe may not exist yet, so we can't
     // access iframe.messageManager directly - but can get at it with this dance.
     return this.iframe.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader.messageManager;
   },
 
-  receiveMessage: function(aMessage) {
+  receiveMessage(aMessage) {
     let iframe = this.iframe;
-    switch(aMessage.name) {
+    switch (aMessage.name) {
       case "PageVisibility:Show":
         SocialShare._dynamicResizer.start(iframe.parentNode, iframe);
         break;
       case "PageVisibility:Hide":
         SocialShare._dynamicResizer.stop();
         break;
       case "Social:DOMWindowClose":
         this.panel.hidePopup();
         break;
     }
   },
 
-  handleEvent: function(event) {
+  handleEvent(event) {
     switch (event.type) {
       case "load": {
         this.iframe.parentNode.removeAttribute("loading");
         if (this.currentShare)
           SocialShare.messageManager.sendAsyncMessage("Social:OpenGraphData", this.currentShare);
       }
     }
   },
 
-  getSelectedProvider: function() {
+  getSelectedProvider() {
     let provider;
     let lastProviderOrigin = this.iframe && this.iframe.getAttribute("origin");
     if (lastProviderOrigin) {
       provider = Social._getProviderFromOrigin(lastProviderOrigin);
     }
     return provider;
   },
 
-  createTooltip: function(event) {
+  createTooltip(event) {
     let tt = event.target;
     let provider = Social._getProviderFromOrigin(tt.triggerNode.getAttribute("origin"));
     tt.firstChild.setAttribute("value", provider.name);
     tt.lastChild.setAttribute("value", provider.origin);
   },
 
-  populateProviderMenu: function() {
+  populateProviderMenu() {
     if (!this.iframe)
       return;
     let providers = Social.providers.filter(p => p.shareURL);
     let hbox = document.getElementById("social-share-provider-buttons");
     // remove everything before the add-share-provider button (which should also
     // be lastChild if any share providers were added)
     let addButton = document.getElementById("add-share-provider");
     while (hbox.lastChild != addButton) {
@@ -354,106 +362,106 @@ SocialShare = {
     if (!window.CustomizableUI)
       return null;
     let widget = CustomizableUI.getWidget("social-share-button");
     if (!widget || !widget.areaType)
       return null;
     return widget.forWindow(window).node;
   },
 
-  _onclick: function() {
+  _onclick() {
     Services.telemetry.getHistogramById("SOCIAL_PANEL_CLICKS").add(0);
   },
 
-  onShowing: function() {
+  onShowing() {
     (this._currentAnchor || this.anchor).setAttribute("open", "true");
     this.iframe.addEventListener("click", this._onclick, true);
   },
 
-  onHidden: function() {
+  onHidden() {
     (this._currentAnchor || this.anchor).removeAttribute("open");
     this._currentAnchor = null;
     this.iframe.docShellIsActive = false;
     this.iframe.removeEventListener("click", this._onclick, true);
     this.iframe.setAttribute("src", "data:text/plain;charset=utf8,");
     // make sure that the frame is unloaded after it is hidden
     this.messageManager.sendAsyncMessage("Social:ClearFrame");
     this.currentShare = null;
     // share panel use is over, purge any history
     this.iframe.purgeSessionHistory();
   },
 
-  sharePage: function(providerOrigin, graphData, target, anchor) {
+  sharePage(providerOrigin, graphData, target, anchor) {
     // if providerOrigin is undefined, we use the last-used provider, or the
     // current/default provider.  The provider selection in the share panel
     // will call sharePage with an origin for us to switch to.
     this._createFrame();
     let iframe = this.iframe;
 
     // graphData is an optional param that either defines the full set of data
     // to be shared, or partial data about the current page. It is set by a call
     // in mozSocial API, or via nsContentMenu calls. If it is present, it MUST
     // define at least url. If it is undefined, we're sharing the current url in
     // the browser tab.
-    let pageData = graphData ? graphData : this.currentShare;
-    let sharedURI = pageData ? Services.io.newURI(pageData.url) :
+    let sharedPageData = graphData || this.currentShare;
+    let sharedURI = sharedPageData ? Services.io.newURI(sharedPageData.url) :
                                 gBrowser.currentURI;
     if (!SocialUI.canSharePage(sharedURI))
       return;
 
     let browserMM = gBrowser.selectedBrowser.messageManager;
 
     // the point of this action type is that we can use existing share
     // endpoints (e.g. oexchange) that do not support additional
     // socialapi functionality.  One tweak is that we shoot an event
     // containing the open graph data.
     let _dataFn;
-    if (!pageData || sharedURI == gBrowser.currentURI) {
+    if (!sharedPageData || sharedURI == gBrowser.currentURI) {
       browserMM.addMessageListener("PageMetadata:PageDataResult", _dataFn = (msg) => {
         browserMM.removeMessageListener("PageMetadata:PageDataResult", _dataFn);
         let pageData = msg.json;
         if (graphData) {
           // overwrite data retreived from page with data given to us as a param
           for (let p in graphData) {
             pageData[p] = graphData[p];
           }
         }
         this.sharePage(providerOrigin, pageData, target, anchor);
       });
       browserMM.sendAsyncMessage("PageMetadata:GetPageData", null, { target });
       return;
     }
     // if this is a share of a selected item, get any microformats
-    if (!pageData.microformats && target) {
+    if (!sharedPageData.microformats && target) {
       browserMM.addMessageListener("PageMetadata:MicroformatsResult", _dataFn = (msg) => {
         browserMM.removeMessageListener("PageMetadata:MicroformatsResult", _dataFn);
-        pageData.microformats = msg.data;
-        this.sharePage(providerOrigin, pageData, target, anchor);
+        sharedPageData.microformats = msg.data;
+        this.sharePage(providerOrigin, sharedPageData, target, anchor);
       });
       browserMM.sendAsyncMessage("PageMetadata:GetMicroformats", null, { target });
       return;
     }
-    this.currentShare = pageData;
+    this.currentShare = sharedPageData;
 
     let provider;
     if (providerOrigin)
       provider = Social._getProviderFromOrigin(providerOrigin);
     else
       provider = this.getSelectedProvider();
     if (!provider || !provider.shareURL) {
       this.showDirectory(anchor);
       return;
     }
     // check the menu button
     let hbox = document.getElementById("social-share-provider-buttons");
     let btn = hbox.querySelector("[origin='" + provider.origin + "']");
     if (btn)
       btn.checked = true;
 
-    let shareEndpoint = OpenGraphBuilder.generateEndpointURL(provider.shareURL, pageData);
+    let shareEndpoint = OpenGraphBuilder.generateEndpointURL(provider.shareURL, sharedPageData);
 
     this._dynamicResizer.stop();
     let size = provider.getPageSize("share");
     if (size) {
       // let the css on the share panel define width, but height
       // calculations dont work on all sites, so we allow that to be
       // defined.
       delete size.width;
@@ -469,35 +477,34 @@ SocialShare = {
     } else {
       iframe.parentNode.setAttribute("loading", "true");
     }
     // if the user switched between share providers we do not want that history
     // available.
     iframe.purgeSessionHistory();
 
     // always ensure that origin belongs to the endpoint
-    let uri = Services.io.newURI(shareEndpoint);
     iframe.setAttribute("origin", provider.origin);
     iframe.setAttribute("src", shareEndpoint);
     this._openPanel(anchor);
   },
 
-  showDirectory: function(anchor) {
+  showDirectory(anchor) {
     this._createFrame();
     let iframe = this.iframe;
     if (iframe.getAttribute("src") == "about:providerdirectory")
       return;
     iframe.removeAttribute("origin");
     iframe.parentNode.setAttribute("loading", "true");
 
     iframe.setAttribute("src", "about:providerdirectory");
     this._openPanel(anchor);
   },
 
-  _openPanel: function(anchor) {
+  _openPanel(anchor) {
     this._currentAnchor = anchor || this.anchor;
     anchor = document.getAnonymousElementByAttribute(this._currentAnchor, "class", "toolbarbutton-icon");
     this.panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false);
     Services.telemetry.getHistogramById("SOCIAL_TOOLBAR_BUTTONS").add(0);
   }
 };
 
-})();
+}).call(this);
--- a/browser/base/content/browser-syncui.js
+++ b/browser/base/content/browser-syncui.js
@@ -1,13 +1,14 @@
 /* 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/. */
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
 
 if (AppConstants.MOZ_SERVICES_CLOUDSYNC) {
   XPCOMUtils.defineLazyModuleGetter(this, "CloudSync",
                                     "resource://gre/modules/CloudSync.jsm");
 }
 
 XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
                                   "resource://gre/modules/FxAccounts.jsm");
@@ -35,18 +36,16 @@ var gSyncUI = {
 
   _unloaded: false,
   // The last sync start time. Used to calculate the leftover animation time
   // once syncing completes (bug 1239042).
   _syncStartTime: 0,
   _syncAnimationTimer: 0,
 
   init() {
-    Cu.import("resource://services-common/stringbundle.js");
-
     // Proceed to set up the UI if Sync has already started up.
     // Otherwise we'll do it when Sync is firing up.
     if (this.weaveService.ready) {
       this.initUI();
       return;
     }
 
     // Sync isn't ready yet, but we can still update the UI with an initial
@@ -219,18 +218,19 @@ var gSyncUI = {
     this.updateUI();
   },
 
   onLogout: function SUI_onLogout() {
     this.updateUI();
   },
 
   _getAppName() {
-    let brand = new StringBundle("chrome://branding/locale/brand.properties");
-    return brand.get("brandShortName");
+    let brand = Services.strings.createBundle(
+      "chrome://branding/locale/brand.properties");
+    return brand.GetStringFromName("brandShortName");
   },
 
   // Commands
   // doSync forces a sync - it *does not* return a promise as it is called
   // via the various UI components.
   doSync() {
     this._needsSetup().then(needsSetup => {
       if (!needsSetup) {
@@ -470,19 +470,18 @@ var gSyncUI = {
     Ci.nsIObserver,
     Ci.nsISupportsWeakReference
   ])
 };
 
 XPCOMUtils.defineLazyGetter(gSyncUI, "_stringBundle", function() {
   // XXXzpao these strings should probably be moved from /services to /browser... (bug 583381)
   //        but for now just make it work
-  return Cc["@mozilla.org/intl/stringbundle;1"].
-         getService(Ci.nsIStringBundleService).
-         createBundle("chrome://weave/locale/services/sync.properties");
+  return Services.strings.createBundle(
+    "chrome://weave/locale/services/sync.properties");
 });
 
 XPCOMUtils.defineLazyGetter(gSyncUI, "log", function() {
   return Log.repository.getLogger("browserwindow.syncui");
 });
 
 XPCOMUtils.defineLazyGetter(gSyncUI, "weaveService", function() {
   return Components.classes["@mozilla.org/weave/service;1"]
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -446,21 +446,21 @@ toolbar:not(#TabsToolbar) > #personal-bo
 }
 
 %ifdef XP_MACOSX
 #main-window[inFullscreen="true"] {
   padding-top: 0; /* override drawintitlebar="true" */
 }
 %endif
 
-#browser-bottombox[lwthemefooter="true"] {
+:root[lwthemefooter=true] #browser-bottombox:-moz-lwtheme {
   background-repeat: no-repeat;
   background-position: bottom left;
   background-color: var(--lwt-accent-color);
-  background-image: var(--lwt-header-image);
+  background-image: var(--lwt-footer-image);
 }
 
 .menuitem-iconic-tooltip {
   -moz-binding: url("chrome://browser/content/urlbarBindings.xml#menuitem-iconic-tooltip");
 }
 
 /* Hide menu elements intended for keyboard access support */
 #main-menubar[openedwithkey=false] .show-only-for-keyboard {
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -2535,21 +2535,18 @@ function URLBarSetURI(aURI) {
   if (value == null) {
     let uri = aURI || gBrowser.currentURI;
     // Strip off "wyciwyg://" and passwords for the location bar
     try {
       uri = Services.uriFixup.createExposableURI(uri);
     } catch (e) {}
 
     // Replace initial page URIs with an empty string
-    // 1. only if there's no opener (bug 370555).
-    // 2. if remote newtab is enabled and it's the default remote newtab page
-    let defaultRemoteURL = gAboutNewTabService.remoteEnabled &&
-                           uri.spec === gAboutNewTabService.newTabURL;
-    if ((gInitialPages.includes(uri.spec) || defaultRemoteURL) &&
+    // only if there's no opener (bug 370555).
+    if (gInitialPages.includes(uri.spec) &&
         checkEmptyPageOrigin(gBrowser.selectedBrowser, uri)) {
       value = "";
     } else {
       // We should deal with losslessDecodeURI throwing for exotic URIs
       try {
         value = losslessDecodeURI(uri);
       } catch (ex) {
         value = "about:blank";
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -47,17 +47,16 @@
         chromemargin="0,2,2,2"
 #else
         chromemargin="0,-1,-1,-1"
 #endif
         tabsintitlebar="true"
 #endif
         titlemenuseparator="&mainWindow.titlemodifiermenuseparator;"
         lightweightthemes="true"
-        lightweightthemesfooter="browser-bottombox"
         windowtype="navigator:browser"
         macanimationtype="document"
         screenX="4" screenY="4"
         fullscreenbutton="true"
         sizemode="normal"
         retargetdocumentfocus="urlbar"
         persist="screenX screenY width height sizemode">
 
--- a/browser/base/content/test/general/browser_misused_characters_in_strings.js
+++ b/browser/base/content/test/general/browser_misused_characters_in_strings.js
@@ -187,19 +187,21 @@ function* getAllTheFiles(extension) {
 add_task(function* checkAllTheProperties() {
   // This asynchronously produces a list of URLs (sadly, mostly sync on our
   // test infrastructure because it runs against jarfiles there, and
   // our zipreader APIs are all sync)
   let uris = yield getAllTheFiles(".properties");
   ok(uris.length, `Found ${uris.length} .properties files to scan for misused characters`);
 
   for (let uri of uris) {
-    let bundle = new StringBundle(uri.spec);
-    let entities = bundle.getAll();
-    for (let entity of entities) {
+    let bundle = Services.strings.createBundle(uri.spec);
+    let enumerator = bundle.getSimpleEnumeration();
+
+    while (enumerator.hasMoreElements()) {
+      let entity = enumerator.getNext().QueryInterface(Ci.nsIPropertyElement);
       testForErrors(uri.spec, entity.key, entity.value);
     }
   }
 });
 
 var checkDTD = Task.async(function* (aURISpec) {
   let rawContents = yield fetchFile(aURISpec);
   // The regular expression below is adapted from:
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/siteIdentity/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+  "extends": [
+    "../../../../../testing/mochitest/browser.eslintrc.js"
+  ]
+};
--- a/browser/base/content/test/webextensions/browser.ini
+++ b/browser/base/content/test/webextensions/browser.ini
@@ -12,13 +12,14 @@ support-files =
   browser_webext_update_icon2.xpi
   browser_webext_update_perms1.xpi
   browser_webext_update_perms2.xpi
   browser_webext_update.json
   browser_webext_search.xml
 
 [browser_extension_sideloading.js]
 [browser_extension_update_background.js]
-[browser_extension_update_interactive.js]
 [browser_permissions_addons_search.js]
 [browser_permissions_installTrigger.js]
 [browser_permissions_local_file.js]
 [browser_permissions_mozAddonManager.js]
+[browser_update_interactive.js]
+[browser_update_interactive_noprompt.js]
rename from browser/base/content/test/webextensions/browser_extension_update_interactive.js
rename to browser/base/content/test/webextensions/browser_update_interactive.js
--- a/browser/base/content/test/webextensions/browser_extension_update_interactive.js
+++ b/browser/base/content/test/webextensions/browser_update_interactive.js
@@ -1,12 +1,11 @@
 const {AddonManagerPrivate} = Cu.import("resource://gre/modules/AddonManager.jsm", {});
 
 const ID = "update2@tests.mozilla.org";
-const ID_LEGACY = "legacy_update@tests.mozilla.org";
 
 // Set some prefs that apply to all the tests in this file
 add_task(function* setup() {
   yield SpecialPowers.pushPrefEnv({set: [
     // We don't have pre-pinned certificates for the local mochitest server
     ["extensions.install.requireBuiltInCerts", false],
     ["extensions.update.requireBuiltInCerts", false],
 
@@ -129,48 +128,8 @@ add_task(() => interactiveUpdateTest(fal
 // Invoke an invidual extension's "Find Updates" menu item
 function checkOne(win, addon) {
   win.gViewController.doCommand("cmd_findItemUpdates", addon);
 }
 
 // Test "Find Updates" with both auto-update settings
 add_task(() => interactiveUpdateTest(true, checkOne));
 add_task(() => interactiveUpdateTest(false, checkOne));
-
-// Check that an update from a legacy extension to a webextensino
-// does not display a prompt
-add_task(async function() {
-  await SpecialPowers.pushPrefEnv({set: [
-    // Point updates to the local mochitest server
-    ["extensions.update.url", `${BASE}/browser_webext_update.json`],
-  ]});
-
-  // Navigate away to ensure that BrowserOpenAddonMgr() opens a new tab
-  gBrowser.selectedBrowser.loadURI("about:robots");
-  await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
-
-  // Install initial version of the test extension
-  let addon = await promiseInstallAddon(`${BASE}/browser_legacy.xpi`);
-  ok(addon, "Addon was installed");
-  is(addon.version, "1.1", "Version 1 of the addon is installed");
-
-  // Go to Extensions in about:addons
-  let win = await BrowserOpenAddonsMgr("addons://list/extension");
-
-  let sawPopup = false;
-  PopupNotifications.panel.addEventListener("popupshown",
-                                            () => sawPopup = true,
-                                            {once: true});
-
-  // Trigger an update check, we should see the update get applied
-  let updatePromise = promiseInstallEvent(addon, "onInstallEnded");
-  win.gViewController.doCommand("cmd_findAllUpdates");
-  await updatePromise;
-
-  addon = await AddonManager.getAddonByID(ID_LEGACY);
-  is(addon.version, "2.0", "Should have upgraded");
-
-  ok(!sawPopup, "Should not have seen a permission notification");
-
-  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
-  addon.uninstall();
-  await SpecialPowers.popPrefEnv();
-});
copy from browser/base/content/test/webextensions/browser_extension_update_interactive.js
copy to browser/base/content/test/webextensions/browser_update_interactive_noprompt.js
--- a/browser/base/content/test/webextensions/browser_extension_update_interactive.js
+++ b/browser/base/content/test/webextensions/browser_update_interactive_noprompt.js
@@ -1,176 +1,62 @@
-const {AddonManagerPrivate} = Cu.import("resource://gre/modules/AddonManager.jsm", {});
-
-const ID = "update2@tests.mozilla.org";
-const ID_LEGACY = "legacy_update@tests.mozilla.org";
 
 // Set some prefs that apply to all the tests in this file
 add_task(function* setup() {
   yield SpecialPowers.pushPrefEnv({set: [
     // We don't have pre-pinned certificates for the local mochitest server
     ["extensions.install.requireBuiltInCerts", false],
     ["extensions.update.requireBuiltInCerts", false],
 
+    // Point updates to the local mochitest server
+    ["extensions.update.url", `${BASE}/browser_webext_update.json`],
+
     // XXX remove this when prompts are enabled by default
     ["extensions.webextPermissionPrompts", true],
   ]});
 });
 
-// Helper function to test a specific scenario for interactive updates.
-// `checkFn` is a callable that triggers a check for updates.
-// `autoUpdate` specifies whether the test should be run with
-// updates applied automatically or not.
-function* interactiveUpdateTest(autoUpdate, checkFn) {
-  yield SpecialPowers.pushPrefEnv({set: [
-    ["extensions.update.autoUpdateDefault", autoUpdate],
-
-    // Point updates to the local mochitest server
-    ["extensions.update.url", `${BASE}/browser_webext_update.json`],
-  ]});
-
-  // Trigger an update check, manually applying the update if we're testing
-  // without auto-update.
-  async function triggerUpdate(win, addon) {
-    let manualUpdatePromise;
-    if (!autoUpdate) {
-      manualUpdatePromise = new Promise(resolve => {
-        let listener = {
-          onNewInstall() {
-            AddonManager.removeInstallListener(listener);
-            resolve();
-          },
-        };
-        AddonManager.addInstallListener(listener);
-      });
-    }
-
-    let promise = checkFn(win, addon);
-
-    if (manualUpdatePromise) {
-      await manualUpdatePromise;
-
-      let list = win.document.getElementById("addon-list");
-
-      // Make sure we have XBL bindings
-      list.clientHeight;
-
-      let item = list.children.find(_item => _item.value == ID);
-      EventUtils.synthesizeMouseAtCenter(item._updateBtn, {}, win);
-    }
-
-    return {promise};
-  }
-
-  // Navigate away from the starting page to force about:addons to load
-  // in a new tab during the tests below.
-  gBrowser.selectedBrowser.loadURI("about:robots");
-  yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
-
-  // Install version 1.0 of the test extension
-  let addon = yield promiseInstallAddon(`${BASE}/browser_webext_update1.xpi`);
-  ok(addon, "Addon was installed");
-  is(addon.version, "1.0", "Version 1 of the addon is installed");
-
-  let win = yield BrowserOpenAddonsMgr("addons://list/extension");
-
-  // Trigger an update check
-  let popupPromise = promisePopupNotificationShown("addon-webext-permissions");
-  let {promise: checkPromise} = yield triggerUpdate(win, addon);
-  let panel = yield popupPromise;
-
-  // Click the cancel button, wait to see the cancel event
-  let cancelPromise = promiseInstallEvent(addon, "onInstallCancelled");
-  panel.secondaryButton.click();
-  yield cancelPromise;
-
-  addon = yield AddonManager.getAddonByID(ID);
-  is(addon.version, "1.0", "Should still be running the old version");
-
-  // Make sure the update check is completely finished.
-  yield checkPromise;
-
-  // Trigger a new update check
-  popupPromise = promisePopupNotificationShown("addon-webext-permissions");
-  checkPromise = (yield triggerUpdate(win, addon)).promise;
-
-  // This time, accept the upgrade
-  let updatePromise = promiseInstallEvent(addon, "onInstallEnded");
-  panel = yield popupPromise;
-  panel.button.click();
-
-  addon = yield updatePromise;
-  is(addon.version, "2.0", "Should have upgraded");
-
-  yield checkPromise;
-
-  yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
-  addon.uninstall();
-  yield SpecialPowers.popPrefEnv();
-}
-
-// Invoke the "Check for Updates" menu item
-function checkAll(win) {
-  win.gViewController.doCommand("cmd_findAllUpdates");
-  return new Promise(resolve => {
-    let observer = {
-      observe(subject, topic, data) {
-        Services.obs.removeObserver(observer, "EM-update-check-finished");
-        resolve();
-      },
-    };
-    Services.obs.addObserver(observer, "EM-update-check-finished", false);
-  });
-}
-
-// Test "Check for Updates" with both auto-update settings
-add_task(() => interactiveUpdateTest(true, checkAll));
-add_task(() => interactiveUpdateTest(false, checkAll));
-
-
-// Invoke an invidual extension's "Find Updates" menu item
-function checkOne(win, addon) {
-  win.gViewController.doCommand("cmd_findItemUpdates", addon);
-}
-
-// Test "Find Updates" with both auto-update settings
-add_task(() => interactiveUpdateTest(true, checkOne));
-add_task(() => interactiveUpdateTest(false, checkOne));
-
-// Check that an update from a legacy extension to a webextensino
-// does not display a prompt
-add_task(async function() {
-  await SpecialPowers.pushPrefEnv({set: [
-    // Point updates to the local mochitest server
-    ["extensions.update.url", `${BASE}/browser_webext_update.json`],
-  ]});
-
+// Helper to test that an update of a given extension does not
+// generate any permission prompts.
+async function testUpdateNoPrompt(filename, id,
+                                  initialVersion = "1.0", updateVersion = "2.0") {
   // Navigate away to ensure that BrowserOpenAddonMgr() opens a new tab
   gBrowser.selectedBrowser.loadURI("about:robots");
   await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
 
   // Install initial version of the test extension
-  let addon = await promiseInstallAddon(`${BASE}/browser_legacy.xpi`);
+  let addon = await promiseInstallAddon(`${BASE}/${filename}`);
   ok(addon, "Addon was installed");
-  is(addon.version, "1.1", "Version 1 of the addon is installed");
+  is(addon.version, initialVersion, "Version 1 of the addon is installed");
 
   // Go to Extensions in about:addons
   let win = await BrowserOpenAddonsMgr("addons://list/extension");
 
   let sawPopup = false;
-  PopupNotifications.panel.addEventListener("popupshown",
-                                            () => sawPopup = true,
-                                            {once: true});
+  function popupListener() {
+    sawPopup = true;
+  }
+  PopupNotifications.panel.addEventListener("popupshown", popupListener);
 
   // Trigger an update check, we should see the update get applied
   let updatePromise = promiseInstallEvent(addon, "onInstallEnded");
   win.gViewController.doCommand("cmd_findAllUpdates");
   await updatePromise;
 
-  addon = await AddonManager.getAddonByID(ID_LEGACY);
-  is(addon.version, "2.0", "Should have upgraded");
+  addon = await AddonManager.getAddonByID(id);
+  is(addon.version, updateVersion, "Should have upgraded");
 
   ok(!sawPopup, "Should not have seen a permission notification");
+  PopupNotifications.panel.removeEventListener("popupshown", popupListener);
 
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
   addon.uninstall();
-  await SpecialPowers.popPrefEnv();
-});
+}
+
+// Test that we don't see a prompt when updating from a legacy
+// extension to a webextension.
+add_task(() => testUpdateNoPrompt("browser_legacy.xpi",
+                                  "legacy_update@tests.mozilla.org", "1.1"));
+
+// Test that we don't see a prompt when no new promptable permissions
+// are added.
+add_task(() => testUpdateNoPrompt("browser_webext_update_perms1.xpi",
+                                  "update_perms@tests.mozilla.org"));
--- a/browser/components/about/AboutRedirector.cpp
+++ b/browser/components/about/AboutRedirector.cpp
@@ -144,26 +144,16 @@ AboutRedirector::NewChannel(nsIURI* aURI
 
       if (path.EqualsLiteral("newtab")) {
         // let the aboutNewTabService decide where to redirect
         nsCOMPtr<nsIAboutNewTabService> aboutNewTabService =
           do_GetService("@mozilla.org/browser/aboutnewtab-service;1", &rv);
         NS_ENSURE_SUCCESS(rv, rv);
         rv = aboutNewTabService->GetDefaultURL(url);
         NS_ENSURE_SUCCESS(rv, rv);
-
-        // if about:newtab points to an external resource we have to make sure
-        // the content is signed and trusted
-        bool remoteEnabled = false;
-        rv = aboutNewTabService->GetRemoteEnabled(&remoteEnabled);
-        NS_ENSURE_SUCCESS(rv, rv);
-        if (remoteEnabled) {
-          NS_ENSURE_ARG_POINTER(aLoadInfo);
-          aLoadInfo->SetVerifySignedContent(true);
-        }
       }
       // fall back to the specified url in the map
       if (url.IsEmpty()) {
         url.AssignASCII(redir.url);
       }
 
       nsCOMPtr<nsIChannel> tempChannel;
       nsCOMPtr<nsIURI> tempURI;
new file mode 100644
--- /dev/null
+++ b/browser/components/customizableui/content/.eslintrc.js
@@ -0,0 +1,11 @@
+"use strict";
+
+module.exports = {  // eslint-disable-line no-undef
+  "env": {
+    "mozilla/browser-window": true,
+  },
+
+  "plugins": [
+    "mozilla",
+  ]
+};
--- a/browser/components/extensions/ext-browserAction.js
+++ b/browser/components/extensions/ext-browserAction.js
@@ -35,22 +35,24 @@ function isAncestorOrSelf(target, node) 
     }
   }
   return false;
 }
 
 // WeakMap[Extension -> BrowserAction]
 const browserActionMap = new WeakMap();
 
-const browserAreas = {
-  "navbar": CustomizableUI.AREA_NAVBAR,
-  "menupanel": CustomizableUI.AREA_PANEL,
-  "tabstrip": CustomizableUI.AREA_TABSTRIP,
-  "personaltoolbar": CustomizableUI.AREA_BOOKMARKS,
-};
+XPCOMUtils.defineLazyGetter(this, "browserAreas", () => {
+  return {
+    "navbar": CustomizableUI.AREA_NAVBAR,
+    "menupanel": CustomizableUI.AREA_PANEL,
+    "tabstrip": CustomizableUI.AREA_TABSTRIP,
+    "personaltoolbar": CustomizableUI.AREA_BOOKMARKS,
+  };
+});
 
 // Responsible for the browser_action section of the manifest as well
 // as the associated popup.
 function BrowserAction(options, extension) {
   this.extension = extension;
 
   let widgetId = makeWidgetId(extension.id);
   this.id = `${widgetId}-browser-action`;
--- a/browser/components/extensions/ext-c-devtools-panels.js
+++ b/browser/components/extensions/ext-c-devtools-panels.js
@@ -39,16 +39,23 @@ class ChildDevToolsPanel extends EventEm
     if (this._panelContext) {
       return this._panelContext;
     }
 
     for (let view of this.context.extension.devtoolsViews) {
       if (view.viewType === "devtools_panel" &&
           view.devtoolsToolboxInfo.toolboxPanelId === this.id) {
         this._panelContext = view;
+
+        // Reset the cached _panelContext property when the view is closed.
+        view.callOnClose({
+          close: () => {
+            this._panelContext = null;
+          },
+        });
         return view;
       }
     }
 
     return null;
   }
 
   receiveMessage({name, data}) {
--- a/browser/components/extensions/ext-devtools-network.js
+++ b/browser/components/extensions/ext-devtools-network.js
@@ -16,20 +16,20 @@ extensions.registerSchemaAPI("devtools.n
       network: {
         onNavigated: new SingletonEventManager(context, "devtools.onNavigated", fire => {
           let listener = (event, data) => {
             fire.async(data.url);
           };
 
           let targetPromise = getDevToolsTargetForContext(context);
           targetPromise.then(target => {
-            target.on("will-navigate", listener);
+            target.on("navigate", listener);
           });
           return () => {
             targetPromise.then(target => {
-              target.off("will-navigate", listener);
+              target.off("navigate", listener);
             });
           };
         }).api(),
       },
     },
   };
 });
--- a/browser/components/extensions/ext-devtools-panels.js
+++ b/browser/components/extensions/ext-devtools-panels.js
@@ -165,31 +165,36 @@ class ParentDevToolsPanel {
     //   its ParentDevToolsPanel instance is still valid, but the built devtools panel is removed from
     //   the toolbox (and re-built again if the user re-enable it from the toolbox preferences panel)
     // - when the creator context has been destroyed, the ParentDevToolsPanel close method is called,
     //   it remove the tool definition from the toolbox, which will call this destroy method.
     return () => {
       unwatchExtensionProxyContextLoad();
       browser.remove();
       toolbox.off("select", this.onToolboxPanelSelect);
+
+      // If the panel has been disabled from the toolbox preferences,
+      // we need to re-initialize the waitTopLevelContext Promise.
+      this.waitTopLevelContext = new Promise(resolve => {
+        this._resolveTopLevelContext = resolve;
+      });
     };
   }
 
   onToolboxReady() {
     if (!this.panelAdded) {
       this.panelAdded = true;
       this.addPanel();
     }
   }
 
   onToolboxPanelSelect(what, id) {
     if (!this.waitTopLevelContext || !this.panelAdded) {
       return;
     }
-
     if (!this.visible && id === this.id) {
       // Wait that the panel is fully loaded and emit show.
       this.waitTopLevelContext.then(() => {
         this.visible = true;
         this.context.parentMessageManager.sendAsyncMessage("Extension:DevToolsPanelShown", {
           toolboxPanelId: this.id,
         });
       });
@@ -213,16 +218,18 @@ class ParentDevToolsPanel {
     // Explicitly remove the panel if it is registered and the toolbox is not
     // closing itself.
     if (toolbox.isToolRegistered(this.id) && !toolbox._destroyer) {
       toolbox.removeAdditionalTool(this.id);
     }
 
     this.context = null;
     this.toolbox = null;
+    this.waitTopLevelContext = null;
+    this._resolveTopLevelContext = null;
   }
 }
 
 extensions.registerSchemaAPI("devtools.panels", "devtools_parent", context => {
   // An incremental "per context" id used in the generated devtools panel id.
   let nextPanelId = 0;
 
   return {
--- a/browser/components/extensions/ext-devtools.js
+++ b/browser/components/extensions/ext-devtools.js
@@ -35,16 +35,17 @@ let initDevTools;
  *   A devtools extension proxy context.
  *
  * @returns {Promise<TabTarget>}
  *   The cloned devtools target associated to the context.
  */
 global.getDevToolsTargetForContext = (context) => {
   return Task.spawn(function* asyncGetTabTarget() {
     if (context.devToolsTarget) {
+      yield context.devToolsTarget.makeRemote();
       return context.devToolsTarget;
     }
 
     if (!context.devToolsToolbox || !context.devToolsToolbox.target) {
       throw new Error("Unable to get a TabTarget for a context not associated to any toolbox");
     }
 
     if (!context.devToolsToolbox.target.isLocalTab) {
--- a/browser/components/extensions/ext-tabs.js
+++ b/browser/components/extensions/ext-tabs.js
@@ -14,17 +14,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/PromiseUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 
 var {
   SingletonEventManager,
-  ignoreEvent,
 } = ExtensionUtils;
 
 // This function is pretty tightly tied to Extension.jsm.
 // Its job is to fill in the |tab| property of the sender.
 function getSender(extension, target, sender) {
   let tabId;
   if ("tabId" in sender) {
     // The message came from a privileged extension page running in a tab. In
@@ -209,17 +208,19 @@ extensions.registerSchemaAPI("tabs", "ad
         };
 
         tabTracker.on("tab-removed", listener);
         return () => {
           tabTracker.off("tab-removed", listener);
         };
       }).api(),
 
-      onReplaced: ignoreEvent(context, "tabs.onReplaced"),
+      onReplaced: new SingletonEventManager(context, "tabs.onReplaced", fire => {
+        return () => {};
+      }).api(),
 
       onMoved: new SingletonEventManager(context, "tabs.onMoved", fire => {
         // There are certain circumstances where we need to ignore a move event.
         //
         // Namely, the first time the tab is moved after it's created, we need
         // to report the final position as the initial position in the tab's
         // onAttached or onCreated event. This is because most tabs are inserted
         // in a temporary location and then moved after the TabOpen event fires,
--- a/browser/components/extensions/test/browser/browser_ext_devtools_panel.js
+++ b/browser/components/extensions/test/browser/browser_ext_devtools_panel.js
@@ -130,16 +130,66 @@ add_task(function* test_devtools_page_pa
   yield gDevTools.showToolbox(target, "webconsole");
   const secondCycleResults = yield extension.awaitMessage("devtools_panel_hidden");
   info("Addon Devtools Panel hidden - second cycle");
 
   is(secondCycleResults.panelCreated, 1, "devtools.panel.create callback has been called once");
   is(secondCycleResults.panelShown, 2, "panel.onShown listener has been called twice");
   is(secondCycleResults.panelHidden, 2, "panel.onHidden listener has been called twice");
 
+  // Turn off the addon devtools panel using the visibilityswitch.
+  const waitToolVisibilityOff = new Promise(resolve => {
+    toolbox.once("tool-unregistered", resolve);
+  });
+
+  Services.prefs.setBoolPref(`devtools.webext-${panelId}.enabled`, false);
+  gDevTools.emit("tool-unregistered", panelId);
+
+  yield waitToolVisibilityOff;
+
+  ok(toolbox.hasAdditionalTool(panelId),
+     "The tool has not been removed on visibilityswitch set to false");
+
+  is(toolbox.visibleAdditionalTools.filter(tool => tool.id == panelId).length, 0,
+     "The tool is not visible on visibilityswitch set to false");
+
+  // Turn on the addon devtools panel using the visibilityswitch.
+  const waitToolVisibilityOn = new Promise(resolve => {
+    toolbox.once("tool-registered", resolve);
+  });
+
+  Services.prefs.setBoolPref(`devtools.webext-${panelId}.enabled`, true);
+  gDevTools.emit("tool-registered", panelId);
+
+  yield waitToolVisibilityOn;
+
+  ok(toolbox.hasAdditionalTool(panelId),
+     "The tool has been added on visibilityswitch set to true");
+  is(toolbox.visibleAdditionalTools.filter(toolId => toolId == panelId).length, 1,
+     "The tool is visible on visibilityswitch set to true");
+
+  // Test devtools panel is loaded correctly after being toggled and
+  // devtools panel events has been fired as expected.
+  yield gDevTools.showToolbox(target, panelId);
+  yield extension.awaitMessage("devtools_panel_shown");
+  info("Addon Devtools Panel shown - after visibilityswitch toggled");
+
+  info("Wait until the Addon Devtools Panel has been loaded - after visibilityswitch toggled");
+  const panelTabIdAfterToggle = yield extension.awaitMessage("devtools_panel_inspectedWindow_tabId");
+  is(panelTabIdAfterToggle, devtoolsPageTabId,
+     "Got the same devtools.inspectedWindow.tabId from devtools panel after visibility toggled");
+
+  yield gDevTools.showToolbox(target, "webconsole");
+  const toolToggledResults = yield extension.awaitMessage("devtools_panel_hidden");
+  info("Addon Devtools Panel hidden - after visibilityswitch toggled");
+
+  is(toolToggledResults.panelCreated, 1, "devtools.panel.create callback has been called once");
+  is(toolToggledResults.panelShown, 3, "panel.onShown listener has been called three times");
+  is(toolToggledResults.panelHidden, 3, "panel.onHidden listener has been called three times");
+
   yield gDevTools.closeToolbox(target);
 
   yield target.destroy();
 
   yield extension.unload();
 
   yield BrowserTestUtils.removeTab(tab);
 });
--- a/browser/components/extensions/test/browser/browser_ext_tabs_create.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_create.js
@@ -162,28 +162,37 @@ add_task(function* test_create_options()
   yield extension.unload();
 
   yield BrowserTestUtils.removeTab(tab);
 });
 
 add_task(function* test_urlbar_focus() {
   const extension = ExtensionTestUtils.loadExtension({
     background() {
+      browser.tabs.onUpdated.addListener(function onUpdated(_, info) {
+        if (info.status === "complete") {
+          browser.test.sendMessage("complete");
+          browser.tabs.onUpdated.removeListener(onUpdated);
+        }
+      });
       browser.test.onMessage.addListener(async (cmd, ...args) => {
         const result = await browser.tabs[cmd](...args);
         browser.test.sendMessage("result", result);
       });
     },
   });
 
   yield extension.startup();
 
   // Test content is focused after opening a regular url
   extension.sendMessage("create", {url: "https://example.com"});
-  const tab1 = yield extension.awaitMessage("result");
+  const [tab1] = yield Promise.all([
+    extension.awaitMessage("result"),
+    extension.awaitMessage("complete"),
+  ]);
 
   is(document.activeElement.tagName, "browser", "Content focused after opening a web page");
 
   extension.sendMessage("remove", tab1.id);
   yield extension.awaitMessage("result");
 
   // Test urlbar is focused after opening an empty tab
   extension.sendMessage("create", {});
--- a/browser/components/newtab/NewTabPrefsProvider.jsm
+++ b/browser/components/newtab/NewTabPrefsProvider.jsm
@@ -12,27 +12,23 @@ Cu.import("resource://gre/modules/XPCOMU
 
 XPCOMUtils.defineLazyGetter(this, "EventEmitter", function() {
   const {EventEmitter} = Cu.import("resource://devtools/shared/event-emitter.js", {});
   return EventEmitter;
 });
 
 // Supported prefs and data type
 const gPrefsMap = new Map([
-  ["browser.newtabpage.remote", "bool"],
-  ["browser.newtabpage.remote.mode", "str"],
-  ["browser.newtabpage.remote.version", "str"],
+  ["browser.newtabpage.activity-stream.enabled", "bool"],
   ["browser.newtabpage.enabled", "bool"],
   ["browser.newtabpage.enhanced", "bool"],
   ["browser.newtabpage.introShown", "bool"],
   ["browser.newtabpage.updateIntroShown", "bool"],
   ["browser.newtabpage.pinned", "str"],
   ["browser.newtabpage.blocked", "str"],
-  ["intl.locale.matchOS", "bool"],
-  ["general.useragent.locale", "localized"],
   ["browser.search.hiddenOneOffs", "str"],
 ]);
 
 // prefs that are important for the newtab page
 const gNewtabPagePrefs = new Set([
   "browser.newtabpage.enabled",
   "browser.newtabpage.enhanced",
   "browser.newtabpage.pinned",
--- a/browser/components/newtab/PlacesProvider.jsm
+++ b/browser/components/newtab/PlacesProvider.jsm
@@ -237,9 +237,8 @@ const gLinks = new Links(); // jshint ig
 
 let PlacesProvider = {
   links: gLinks,
 };
 
 // Kept only for backwards-compatibility
 XPCOMUtils.defineLazyGetter(PlacesProvider, "LinkChecker",
   () => NewTabUtils.linkChecker);
-
--- a/browser/components/newtab/aboutNewTabService.js
+++ b/browser/components/newtab/aboutNewTabService.js
@@ -1,289 +1,168 @@
 /*
  * 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/.
 */
 
-/* globals XPCOMUtils, NewTabPrefsProvider, Services,
-  Locale, UpdateUtils, NewTabRemoteResources
-*/
+/* globals XPCOMUtils, NewTabPrefsProvider, Services */
 "use strict";
 
 const {utils: Cu, interfaces: Ci} = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
-                                  "resource://gre/modules/UpdateUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NewTabPrefsProvider",
                                   "resource:///modules/NewTabPrefsProvider.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Locale",
-                                  "resource://gre/modules/Locale.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "NewTabRemoteResources",
-                                  "resource:///modules/NewTabRemoteResources.jsm");
 
 const LOCAL_NEWTAB_URL = "chrome://browser/content/newtab/newTab.xhtml";
 
-const REMOTE_NEWTAB_PATH = "/newtab/v%VERSION%/%CHANNEL%/%LOCALE%/index.html";
+const ACTIVITY_STREAM_URL = "resource://activity-stream/data/content/activity-stream.html";
 
 const ABOUT_URL = "about:newtab";
 
-// Pref that tells if remote newtab is enabled
-const PREF_REMOTE_ENABLED = "browser.newtabpage.remote";
-
-// Pref branch necesssary for testing
-const PREF_REMOTE_CS_TEST = "browser.newtabpage.remote.content-signing-test";
-
-// The preference that tells whether to match the OS locale
-const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS";
-
-// The preference that tells what locale the user selected
-const PREF_SELECTED_LOCALE = "general.useragent.locale";
-
-// The preference that tells what remote mode is enabled.
-const PREF_REMOTE_MODE = "browser.newtabpage.remote.mode";
-
-// The preference that tells which remote version is expected.
-const PREF_REMOTE_VERSION = "browser.newtabpage.remote.version";
-
-const VALID_CHANNELS = new Set(["esr", "release", "beta", "aurora", "nightly"]);
+// Pref that tells if activity stream is enabled
+const PREF_ACTIVITY_STREAM_ENABLED = "browser.newtabpage.activity-stream.enabled";
 
 function AboutNewTabService() {
-  NewTabPrefsProvider.prefs.on(PREF_REMOTE_ENABLED, this._handleToggleEvent.bind(this));
-
-  this._updateRemoteMaybe = this._updateRemoteMaybe.bind(this);
-
-  // trigger remote change if needed, according to pref
-  this.toggleRemote(Services.prefs.getBoolPref(PREF_REMOTE_ENABLED));
+  NewTabPrefsProvider.prefs.on(PREF_ACTIVITY_STREAM_ENABLED, this._handleToggleEvent.bind(this));
+  this.toggleActivityStream(Services.prefs.getBoolPref(PREF_ACTIVITY_STREAM_ENABLED));
 }
 
 /*
  * A service that allows for the overriding, at runtime, of the newtab page's url.
- * Additionally, the service manages pref state between a remote and local newtab page.
+ * Additionally, the service manages pref state between a activity stream, or the regular
+ * about:newtab page.
  *
  * There is tight coupling with browser/about/AboutRedirector.cpp.
  *
  * 1. Browser chrome access:
  *
  * When the user issues a command to open a new tab page, usually clicking a button
  * in the browser chrome or using shortcut keys, the browser chrome code invokes the
  * service to obtain the newtab URL. It then loads that URL in a new tab.
  *
  * When not overridden, the default URL emitted by the service is "about:newtab".
  * When overridden, it returns the overriden URL.
  *
  * 2. Redirector Access:
  *
  * When the URL loaded is about:newtab, the default behavior, or when entered in the
  * URL bar, the redirector is hit. The service is then called to return either of
- * two URLs, a chrome or remote one, based on the browser.newtabpage.remote pref.
+ * two URLs, a chrome or the activity stream one, based on the
+ * browser.newtabpage.activity-stream.enabled pref.
  *
  * NOTE: "about:newtab" will always result in a default newtab page, and never an overridden URL.
  *
  * Access patterns:
  *
  * The behavior is different when accessing the service via browser chrome or via redirector
  * largely to maintain compatibility with expectations of add-on developers.
  *
  * Loading a chrome resource, or an about: URL in the redirector with either the
  * LOAD_NORMAL or LOAD_REPLACE flags yield unexpected behaviors, so a roundtrip
  * to the redirector from browser chrome is avoided.
  */
 AboutNewTabService.prototype = {
 
   _newTabURL: ABOUT_URL,
-  _remoteEnabled: false,
-  _remoteURL: null,
+  _activityStreamEnabled: false,
   _overridden: false,
 
   classID: Components.ID("{dfcd2adc-7867-4d3a-ba70-17501f208142}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutNewTabService]),
   _xpcom_categories: [{
     service: true
   }],
 
   _handleToggleEvent(prefName, stateEnabled, forceState) { // jshint unused:false
-    if (this.toggleRemote(stateEnabled, forceState)) {
+    if (this.toggleActivityStream(stateEnabled, forceState)) {
       Services.obs.notifyObservers(null, "newtab-url-changed", ABOUT_URL);
     }
   },
 
   /**
-   * React to changes to the remote newtab pref.
+   * React to changes to the activity stream pref.
    *
-   * If browser.newtabpage.remote is true, this will change the default URL to the
-   * remote newtab page URL. If browser.newtabpage.remote is false, the default URL
+   * If browser.newtabpage.activity-stream.enabled is true, this will change the default URL to the
+   * activity stream page URL. If browser.newtabpage.activity-stream.enabled is false, the default URL
    * will be a local chrome URL.
    *
    * This will only act if there is a change of state and if not overridden.
    *
    * @returns {Boolean} Returns if there has been a state change
    *
-   * @param {Boolean}   stateEnabled    remote state to set to
+   * @param {Boolean}   stateEnabled    activity stream enabled state to set to
    * @param {Boolean}   forceState      force state change
    */
-  toggleRemote(stateEnabled, forceState) {
+  toggleActivityStream(stateEnabled, forceState) {
 
-    if (!forceState && (this._overriden || stateEnabled === this._remoteEnabled)) {
+    if (!forceState && (this.overridden || stateEnabled === this.activityStreamEnabled)) {
       // exit there is no change of state
       return false;
     }
-
-    let csTest = Services.prefs.getBoolPref(PREF_REMOTE_CS_TEST);
     if (stateEnabled) {
-      if (!csTest) {
-        this._remoteURL = this.generateRemoteURL();
-      } else {
-        this._remoteURL = this._newTabURL;
-      }
-      NewTabPrefsProvider.prefs.on(
-        PREF_SELECTED_LOCALE,
-        this._updateRemoteMaybe);
-      NewTabPrefsProvider.prefs.on(
-        PREF_MATCH_OS_LOCALE,
-        this._updateRemoteMaybe);
-      NewTabPrefsProvider.prefs.on(
-        PREF_REMOTE_MODE,
-        this._updateRemoteMaybe);
-      NewTabPrefsProvider.prefs.on(
-        PREF_REMOTE_VERSION,
-        this._updateRemoteMaybe);
-      this._remoteEnabled = true;
+      this._activityStreamEnabled = true;
     } else {
-      NewTabPrefsProvider.prefs.off(PREF_SELECTED_LOCALE, this._updateRemoteMaybe);
-      NewTabPrefsProvider.prefs.off(PREF_MATCH_OS_LOCALE, this._updateRemoteMaybe);
-      NewTabPrefsProvider.prefs.off(PREF_REMOTE_MODE, this._updateRemoteMaybe);
-      NewTabPrefsProvider.prefs.off(PREF_REMOTE_VERSION, this._updateRemoteMaybe);
-      this._remoteEnabled = false;
+      this._activityStreamEnabled = false;
     }
-    if (!csTest) {
-      this._newTabURL = ABOUT_URL;
-    }
+    this._newtabURL = ABOUT_URL;
     return true;
   },
 
   /*
-   * Generate a default url based on remote mode, version, locale and update channel
-   */
-  generateRemoteURL() {
-    let releaseName = this.releaseFromUpdateChannel(UpdateUtils.UpdateChannel);
-    let path = REMOTE_NEWTAB_PATH
-      .replace("%VERSION%", this.remoteVersion)
-      .replace("%LOCALE%", Locale.getLocale())
-      .replace("%CHANNEL%", releaseName);
-    let mode = Services.prefs.getCharPref(PREF_REMOTE_MODE);
-    if (!(mode in NewTabRemoteResources.MODE_CHANNEL_MAP)) {
-      mode = "production";
-    }
-    return NewTabRemoteResources.MODE_CHANNEL_MAP[mode].origin + path;
-  },
-
-  /*
    * Returns the default URL.
    *
-   * This URL only depends on the browser.newtabpage.remote pref. Overriding
+   * This URL only depends on the browser.newtabpage.activity-stream.enabled pref. Overriding
    * the newtab page has no effect on the result of this function.
    *
-   * The result is also the remote URL if this is in a test (PREF_REMOTE_CS_TEST)
-   *
-   * @returns {String} the default newtab URL, remote or local depending on browser.newtabpage.remote
+   * @returns {String} the default newtab URL, activity-stream or regular depending on browser.newtabpage.activity-stream.enabled
    */
   get defaultURL() {
-    let csTest = Services.prefs.getBoolPref(PREF_REMOTE_CS_TEST);
-    if (this._remoteEnabled || csTest) {
-      return this._remoteURL;
+    if (this.activityStreamEnabled) {
+      return this.activityStreamURL;
     }
     return LOCAL_NEWTAB_URL;
   },
 
-  /*
-   * Updates the remote location when the page is not overriden.
-   *
-   * Useful when there is a dependent pref change
-   */
-  _updateRemoteMaybe() {
-    if (!this._remoteEnabled || this._overridden) {
-      return;
-    }
-
-    let url = this.generateRemoteURL();
-    if (url !== this._remoteURL) {
-      this._remoteURL = url;
-      Services.obs.notifyObservers(null, "newtab-url-changed",
-        this._remoteURL);
-    }
-  },
-
-  /**
-   * Returns the release name from an Update Channel name
-   *
-   * @returns {String} a release name based on the update channel. Defaults to nightly
-   */
-  releaseFromUpdateChannel(channelName) {
-    return VALID_CHANNELS.has(channelName) ? channelName : "nightly";
-  },
-
   get newTabURL() {
     return this._newTabURL;
   },
 
-  get remoteVersion() {
-    return Services.prefs.getCharPref(PREF_REMOTE_VERSION);
-  },
-
-  get remoteReleaseName() {
-    return this.releaseFromUpdateChannel(UpdateUtils.UpdateChannel);
-  },
-
   set newTabURL(aNewTabURL) {
-    let csTest = Services.prefs.getBoolPref(PREF_REMOTE_CS_TEST);
     aNewTabURL = aNewTabURL.trim();
     if (aNewTabURL === ABOUT_URL) {
       // avoid infinite redirects in case one sets the URL to about:newtab
       this.resetNewTabURL();
       return;
     } else if (aNewTabURL === "") {
       aNewTabURL = "about:blank";
     }
-    let remoteURL = this.generateRemoteURL();
-    let prefRemoteEnabled = Services.prefs.getBoolPref(PREF_REMOTE_ENABLED);
-    let isResetLocal = !prefRemoteEnabled && aNewTabURL === LOCAL_NEWTAB_URL;
-    let isResetRemote = prefRemoteEnabled && aNewTabURL === remoteURL;
 
-    if (isResetLocal || isResetRemote) {
-      if (this._overriden && !csTest) {
-        // only trigger a reset if previously overridden and this is no test
-        this.resetNewTabURL();
-      }
-      return;
-    }
-    // turn off remote state if needed
-    if (!csTest) {
-      this.toggleRemote(false);
-    } else {
-      // if this is a test, we want the remoteURL to be set
-      this._remoteURL = aNewTabURL;
-    }
+    this.toggleActivityStream(false);
     this._newTabURL = aNewTabURL;
     this._overridden = true;
     Services.obs.notifyObservers(null, "newtab-url-changed", this._newTabURL);
   },
 
   get overridden() {
     return this._overridden;
   },
 
-  get remoteEnabled() {
-    return this._remoteEnabled;
+  get activityStreamEnabled() {
+    return this._activityStreamEnabled;
+  },
+
+  get activityStreamURL() {
+    return ACTIVITY_STREAM_URL;
   },
 
   resetNewTabURL() {
     this._overridden = false;
     this._newTabURL = ABOUT_URL;
-    this.toggleRemote(Services.prefs.getBoolPref(PREF_REMOTE_ENABLED), true);
+    this.toggleActivityStream(Services.prefs.getBoolPref(PREF_ACTIVITY_STREAM_ENABLED), true);
     Services.obs.notifyObservers(null, "newtab-url-changed", this._newTabURL);
   }
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AboutNewTabService]);
--- a/browser/components/newtab/nsIAboutNewTabService.idl
+++ b/browser/components/newtab/nsIAboutNewTabService.idl
@@ -14,50 +14,34 @@ interface nsIAboutNewTabService : nsISup
 {
   /**
    * Returns the url of the resource for the newtab page if not overridden,
    * otherwise a string represenation of the new URL.
    */
   attribute ACString newTabURL;
 
   /**
-   * Returns the default URL (remote or local depending on pref)
+   * Returns the default URL (local or activity stream depending on pref)
    */
   attribute ACString defaultURL;
 
   /**
    * Returns true if the default resource got overridden.
    */
   readonly attribute bool overridden;
 
   /**
-   * Returns true if the default resource is remotely hosted and isn't
+   * Returns true if the default resource is activity stream and isn't
    * overridden
    */
-  readonly attribute bool remoteEnabled;
-
-
-  /**
-  * Returns the version of the remote newtab page expected
-  */
-  readonly attribute ACString remoteVersion;
+  readonly attribute bool activityStreamEnabled;
 
   /**
-   * Returns the expected channel for the remote the newtab page
-   */
-  readonly attribute ACString remoteReleaseName;
-
-  /**
-   * Generates and returns the remote newtab page url
+   * Returns the activity stream resource URL for the newtab page
    */
-  ACString generateRemoteURL();
-
-  /**
-   * Returns a remote new tab release name given an update channel name
-   */
-  ACString releaseFromUpdateChannel(in ACString channelName);
+  readonly attribute ACString activityStreamURL;
 
   /**
    * Resets to the default resource and also resets the
    * overridden attribute to false.
    */
   void resetNewTabURL();
 };
--- a/browser/components/newtab/tests/browser/browser.ini
+++ b/browser/components/newtab/tests/browser/browser.ini
@@ -1,16 +1,8 @@
 [DEFAULT]
 support-files =
   blue_page.html
   dummy_page.html
-  newtabwebchannel_basic.html
-  newtabmessages_places.html
-  newtabmessages_prefs.html
-  newtabmessages_preview.html
-  newtabmessages_search.html
 
 [browser_PreviewProvider.js]
 [browser_remotenewtab_pageloads.js]
 [browser_newtab_overrides.js]
-[browser_newtabmessages.js]
-skip-if = true # Bug 1271177, bug 1262719
-[browser_newtabwebchannel.js]
--- a/browser/components/newtab/tests/browser/browser_newtab_overrides.js
+++ b/browser/components/newtab/tests/browser/browser_newtab_overrides.js
@@ -18,17 +18,17 @@ Cu.import("resource://gre/modules/XPCOMU
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
                                    "@mozilla.org/browser/aboutnewtab-service;1",
                                    "nsIAboutNewTabService");
 
 registerCleanupFunction(function() {
-  Services.prefs.setBoolPref("browser.newtabpage.remote", false);
+  Services.prefs.setBoolPref("browser.newtabpage.activity-stream.enabled", false);
   aboutNewTabService.resetNewTabURL();
 });
 
 /*
  * Tests that the default newtab page is always returned when one types "about:newtab" in the URL bar,
  * even when overridden.
  */
 add_task(function* redirector_ignores_override() {
deleted file mode 100644
--- a/browser/components/newtab/tests/browser/browser_newtabmessages.js
+++ /dev/null
@@ -1,222 +0,0 @@
-/* globals Cu, XPCOMUtils, Preferences, is, registerCleanupFunction, NewTabWebChannel,
-PlacesTestUtils, NewTabMessages, ok, Services, PlacesUtils, NetUtil, Task */
-
-"use strict";
-
-Cu.import("resource://gre/modules/Preferences.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "NewTabWebChannel",
-                                  "resource:///modules/NewTabWebChannel.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "NewTabMessages",
-                                  "resource:///modules/NewTabMessages.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
-                                  "resource://testing-common/PlacesTestUtils.jsm");
-
-let setup = Task.async(function*() {
-  Preferences.set("browser.newtabpage.enhanced", true);
-  Preferences.set("browser.newtabpage.remote.mode", "test");
-  Preferences.set("browser.newtabpage.remote", true);
-  NewTabMessages.init();
-  yield PlacesTestUtils.clearHistory();
-});
-
-let cleanup = Task.async(function*() {
-  NewTabMessages.uninit();
-  Preferences.set("browser.newtabpage.remote", false);
-  Preferences.set("browser.newtabpage.remote.mode", "production");
-});
-registerCleanupFunction(cleanup);
-
-/*
- * Sanity tests for pref messages
- */
-add_task(function* prefMessages_request() {
-  yield setup();
-
-  let testURL = "https://example.com/browser/browser/components/newtab/tests/browser/newtabmessages_prefs.html";
-
-  let tabOptions = {
-    gBrowser,
-    url: testURL
-  };
-
-  let prefResponseAck = new Promise(resolve => {
-    NewTabWebChannel.once("responseAck", () => {
-      ok(true, "a request response has been received");
-      resolve();
-    });
-  });
-
-  yield BrowserTestUtils.withNewTab(tabOptions, function*() {
-    yield prefResponseAck;
-    let prefChangeAck = new Promise(resolve => {
-      NewTabWebChannel.once("responseAck", () => {
-        ok(true, "a change response has been received");
-        resolve();
-      });
-    });
-    Preferences.set("browser.newtabpage.enhanced", false);
-    yield prefChangeAck;
-  });
-  yield cleanup();
-});
-
-/*
- * Sanity tests for preview messages
- */
-add_task(function* previewMessages_request() {
-  yield setup();
-  var oldEnabledPref = Services.prefs.getBoolPref("browser.pagethumbnails.capturing_disabled");
-  Services.prefs.setBoolPref("browser.pagethumbnails.capturing_disabled", false);
-
-  let testURL = "https://example.com/browser/browser/components/newtab/tests/browser/newtabmessages_preview.html";
-
-  let tabOptions = {
-    gBrowser,
-    url: testURL
-  };
-
-  let previewResponseAck = new Promise(resolve => {
-    NewTabWebChannel.once("responseAck", () => {
-      ok(true, "a request response has been received");
-      resolve();
-    });
-  });
-
-  yield BrowserTestUtils.withNewTab(tabOptions, function*() {
-    yield previewResponseAck;
-  });
-  yield cleanup();
-  Services.prefs.setBoolPref("browser.pagethumbnails.capturing_disabled", oldEnabledPref);
-});
-
-/*
- * Sanity tests for places messages
- */
-add_task(function* placesMessages_request() {
-  yield setup();
-  let testURL = "https://example.com/browser/browser/components/newtab/tests/browser/newtabmessages_places.html";
-
-  // url prefix for test history population
-  const TEST_URL = "https://mozilla.com/";
-  // time when the test starts execution
-  const TIME_NOW = (new Date()).getTime();
-
-  // utility function to compute past timestamp
-  function timeDaysAgo(numDays) {
-    return TIME_NOW - (numDays * 24 * 60 * 60 * 1000);
-  }
-
-  // utility function to make a visit for insertion into places db
-  function makeVisit(index, daysAgo, isTyped, domain = TEST_URL) {
-    let {
-      TRANSITION_TYPED,
-      TRANSITION_LINK
-    } = PlacesUtils.history;
-
-    return {
-      uri: NetUtil.newURI(`${domain}${index}`),
-      visitDate: timeDaysAgo(daysAgo),
-      transition: (isTyped) ? TRANSITION_TYPED : TRANSITION_LINK,
-    };
-  }
-
-  yield PlacesTestUtils.clearHistory();
-
-  // all four visits must come from different domains to avoid deduplication
-  let visits = [
-    makeVisit(0, 0, true, "http://bar.com/"), // frecency 200, today
-    makeVisit(1, 0, true, "http://foo.com/"), // frecency 200, today
-    makeVisit(2, 2, true, "http://buz.com/"), // frecency 200, 2 days ago
-    makeVisit(3, 2, false, "http://aaa.com/"), // frecency 10, 2 days ago, transition
-  ];
-
-  yield PlacesTestUtils.addVisits(visits);
-
-  /** Test Begins **/
-
-  let tabOptions = {
-    gBrowser,
-    url: testURL
-  };
-
-  let placesResponseAck = new Promise(resolve => {
-    NewTabWebChannel.once("numItemsAck", (_, msg) => {
-      ok(true, "a request response has been received");
-      is(msg.data, visits.length + 1, "received an expected number of history items");
-      resolve();
-    });
-  });
-
-  yield BrowserTestUtils.withNewTab(tabOptions, function*() {
-    yield placesResponseAck;
-    ok(true, "a change response has been received");
-    let placesChangeAck = new Promise(resolve => {
-      NewTabWebChannel.once("clearHistoryAck", (_, msg) => {
-        is(msg.data, "clearHistory", "a clear history message has been received");
-        resolve();
-      });
-    });
-    yield PlacesTestUtils.clearHistory();
-    yield placesChangeAck;
-  });
-  yield cleanup();
-});
-
-/*
- * Sanity tests for search messages
- */
-add_task(function* searchMessages_request() {
-  yield setup();
-  let testURL = "https://example.com/browser/browser/components/newtab/tests/browser/newtabmessages_search.html";
-
-  // create dummy test engines
-  Services.search.addEngineWithDetails("Engine1", "", "", "", "GET",
-    "http://example.com/?q={searchTerms}");
-  Services.search.addEngineWithDetails("Engine2", "", "", "", "GET",
-    "http://example.com/?q={searchTerms}");
-
-  let tabOptions = {
-    gBrowser,
-    url: testURL
-  };
-
-  let UIStringsResponseAck = new Promise(resolve => {
-    NewTabWebChannel.once("UIStringsAck", (_, msg) => {
-      ok(true, "a search request response for UI string has been received");
-      ok(msg.data, "received the UI Strings");
-      resolve();
-    });
-  });
-  let suggestionsResponseAck = new Promise(resolve => {
-    NewTabWebChannel.once("suggestionsAck", (_, msg) => {
-      ok(true, "a search request response for suggestions has been received");
-      ok(msg.data, "received the suggestions");
-      resolve();
-    });
-  });
-  let stateResponseAck = new Promise(resolve => {
-    NewTabWebChannel.once("stateAck", (_, msg) => {
-      ok(true, "a search request response for state has been received");
-      ok(msg.data, "received a state object");
-      resolve();
-    });
-  });
-  let currentEngineResponseAck = new Promise(resolve => {
-    NewTabWebChannel.once("currentEngineAck", (_, msg) => {
-      ok(true, "a search request response for current engine has been received");
-      ok(msg.data, "received a current engine");
-      resolve();
-    });
-  });
-
-  yield BrowserTestUtils.withNewTab(tabOptions, function*() {
-    yield UIStringsResponseAck;
-    yield suggestionsResponseAck;
-    yield stateResponseAck;
-    yield currentEngineResponseAck;
-  });
-
-  cleanup();
-});
deleted file mode 100644
--- a/browser/components/newtab/tests/browser/browser_newtabwebchannel.js
+++ /dev/null
@@ -1,251 +0,0 @@
-/* globals XPCOMUtils, Cu, Preferences, NewTabWebChannel, is, registerCleanupFunction */
-
-"use strict";
-
-Cu.import("resource://gre/modules/Preferences.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "NewTabWebChannel",
-                                  "resource:///modules/NewTabWebChannel.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "NewTabMessages",
-                                  "resource:///modules/NewTabMessages.jsm");
-
-const TEST_URL = "https://example.com/browser/browser/components/newtab/tests/browser/newtabwebchannel_basic.html";
-const TEST_URL_2 = "http://mochi.test:8888/browser/browser/components/newtab/tests/browser/newtabwebchannel_basic.html";
-
-function setup(mode = "test") {
-  Preferences.set("browser.newtabpage.remote.mode", mode);
-  Preferences.set("browser.newtabpage.remote", true);
-  NewTabWebChannel.init();
-  NewTabMessages.init();
-}
-
-function cleanup() {
-  NewTabMessages.uninit();
-  NewTabWebChannel.uninit();
-  Preferences.set("browser.newtabpage.remote", false);
-  Preferences.set("browser.newtabpage.remote.mode", "production");
-}
-registerCleanupFunction(cleanup);
-
-/*
- * Tests flow of messages from newtab to chrome and chrome to newtab
- */
-add_task(function* open_webchannel_basic() {
-  setup();
-
-  let tabOptions = {
-    gBrowser,
-    url: TEST_URL
-  };
-
-  let messagePromise = new Promise(resolve => {
-    NewTabWebChannel.once("foo", function(name, msg) {
-      is(name, "foo", "Correct message type sent: foo");
-      is(msg.data, "bar", "Correct data sent: bar");
-      resolve(msg.target);
-    });
-  });
-
-  let replyPromise = new Promise(resolve => {
-    NewTabWebChannel.once("reply", function(name, msg) {
-      is(name, "reply", "Correct message type sent: reply");
-      is(msg.data, "quuz", "Correct data sent: quuz");
-      resolve(msg.target);
-    });
-  });
-
-  let unloadPromise = new Promise(resolve => {
-    NewTabWebChannel.once("targetUnload", function(name) {
-      is(name, "targetUnload", "Correct message type sent: targetUnload");
-      resolve();
-    });
-  });
-
-  is(NewTabWebChannel.numBrowsers, 0, "Sanity check");
-  yield BrowserTestUtils.withNewTab(tabOptions, function*(browser) {
-    let target = yield messagePromise;
-    is(NewTabWebChannel.numBrowsers, 1, "One target expected");
-    is(target.browser, browser, "Same browser");
-    NewTabWebChannel.send("respond", null, target);
-    yield replyPromise;
-  });
-
-  Cu.forceGC();
-  is(NewTabWebChannel.numBrowsers, 0, "Sanity check");
-  yield unloadPromise;
-  cleanup();
-});
-
-/*
- * Tests message broadcast reaches all open newtab pages
- */
-add_task(function* webchannel_broadcast() {
-  setup();
-
-  let countingMessagePromise = new Promise(resolve => {
-    let count = 0;
-    NewTabWebChannel.on("foo", function test_message(name, msg) {
-      count += 1;
-      if (count === 2) {
-        NewTabWebChannel.off("foo", test_message);
-        resolve(msg.target);
-      }
-    });
-  });
-
-  let countingReplyPromise = new Promise(resolve => {
-    let count = 0;
-    NewTabWebChannel.on("reply", function test_message(name, msg) {
-      count += 1;
-      if (count === 2) {
-        NewTabWebChannel.off("reply", test_message);
-        resolve(msg.target);
-      }
-    });
-  });
-
-  let countingUnloadPromise = new Promise(resolve => {
-    let count = 0;
-    NewTabWebChannel.on("targetUnload", function test_message() {
-      count += 1;
-      if (count === 2) {
-        NewTabWebChannel.off("targetUnload", test_message);
-        resolve();
-      }
-    });
-  });
-
-  let tabs = [];
-  is(NewTabWebChannel.numBrowsers, 0, "Sanity check");
-  tabs.push(yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL));
-  tabs.push(yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL));
-
-  yield countingMessagePromise;
-  is(NewTabWebChannel.numBrowsers, 2, "Two targets expected");
-
-  NewTabWebChannel.broadcast("respond", null);
-  yield countingReplyPromise;
-
-  for (let tab of tabs) {
-    yield BrowserTestUtils.removeTab(tab);
-  }
-  Cu.forceGC();
-
-  is(NewTabWebChannel.numBrowsers, 0, "Sanity check");
-  yield countingUnloadPromise;
-  cleanup();
-});
-
-/*
- * Tests switching modes
- */
-add_task(function* webchannel_switch() {
-  setup();
-
-  function newMessagePromise() {
-    return new Promise(resolve => {
-      NewTabWebChannel.once("foo", function(name, msg) {
-        resolve(msg.target);
-      });
-    });
-  }
-
-  let replyCount = 0;
-  function newReplyPromise() {
-    return new Promise(resolve => {
-      NewTabWebChannel.on("reply", function() {
-        replyCount += 1;
-        resolve();
-      });
-    });
-  }
-
-  let unloadPromise = new Promise(resolve => {
-    NewTabWebChannel.once("targetUnload", function() {
-      resolve();
-    });
-  });
-
-  let unloadAllPromise = new Promise(resolve => {
-    NewTabWebChannel.once("targetUnloadAll", function() {
-      resolve();
-    });
-  });
-
-  let tabs = [];
-  let messagePromise;
-  is(NewTabWebChannel.numBrowsers, 0, "Sanity check");
-
-  messagePromise = newMessagePromise();
-  tabs.push(yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL));
-  yield messagePromise;
-  is(NewTabWebChannel.numBrowsers, 1, "Correct number of targets");
-
-  messagePromise = newMessagePromise();
-  Preferences.set("browser.newtabpage.remote.mode", "test2");
-  tabs.push(yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL_2));
-  yield unloadAllPromise;
-  yield messagePromise;
-  is(NewTabWebChannel.numBrowsers, 1, "Correct number of targets");
-
-  NewTabWebChannel.broadcast("respond", null);
-  yield newReplyPromise();
-  is(replyCount, 1, "only current channel is listened to for replies");
-
-  const webchannelWhitelistPref = "webchannel.allowObject.urlWhitelist";
-  let origWhitelist = Services.prefs.getCharPref(webchannelWhitelistPref);
-  let newWhitelist = origWhitelist + " http://mochi.test:8888";
-  Services.prefs.setCharPref(webchannelWhitelistPref, newWhitelist);
-  try {
-    NewTabWebChannel.broadcast("respond_object", null);
-    yield newReplyPromise();
-  } finally {
-    Services.prefs.clearUserPref(webchannelWhitelistPref);
-  }
-
-  for (let tab of tabs) {
-    yield BrowserTestUtils.removeTab(tab);
-  }
-
-  Cu.forceGC();
-  is(NewTabWebChannel.numBrowsers, 0, "Sanity check");
-  yield unloadPromise;
-  cleanup();
-});
-
-add_task(function* open_webchannel_reload() {
-  setup();
-
-  let tabOptions = {
-    gBrowser,
-    url: TEST_URL
-  };
-
-  let messagePromise = new Promise(resolve => {
-    NewTabWebChannel.once("foo", function(name, msg) {
-      is(name, "foo", "Correct message type sent: foo");
-      is(msg.data, "bar", "Correct data sent: bar");
-      resolve(msg.target);
-    });
-  });
-  let unloadPromise = new Promise(resolve => {
-    NewTabWebChannel.once("targetUnload", function() {
-      resolve();
-    });
-  });
-
-  is(NewTabWebChannel.numBrowsers, 0, "Sanity check");
-  yield BrowserTestUtils.withNewTab(tabOptions, function*(browser) {
-    let target = yield messagePromise;
-    is(NewTabWebChannel.numBrowsers, 1, "One target expected");
-    is(target.browser, browser, "Same browser");
-
-    browser.reload();
-  });
-
-  Cu.forceGC();
-  is(NewTabWebChannel.numBrowsers, 0, "Sanity check");
-  yield unloadPromise;
-  cleanup();
-});
deleted file mode 100644
--- a/browser/components/newtab/tests/browser/newtabmessages_places.html
+++ /dev/null
@@ -1,49 +0,0 @@
-<html>
-  <head>
-    <meta charset="utf8">
-    <title>Newtab WebChannel test</title>
-  </head>
-  <body>
-    <script>
-      window.addEventListener("WebChannelMessageToContent", function(e) {
-        if (e.detail.message) {
-          let reply;
-          switch (e.detail.message.type) {
-            case "RECEIVE_FRECENT":
-              reply = new window.CustomEvent("WebChannelMessageToChrome", {
-                detail: JSON.stringify({
-                  id: "newtab",
-                  message: JSON.stringify({type: "numItemsAck", data: e.detail.message.data.length}),
-                })
-              });
-              window.dispatchEvent(reply);
-              break;
-            case "RECEIVE_PLACES_CHANGE":
-              if (e.detail.message.data.type === "clearHistory") {
-                reply = new window.CustomEvent("WebChannelMessageToChrome", {
-                  detail: JSON.stringify({
-                    id: "newtab",
-                    message: JSON.stringify({type: "clearHistoryAck", data: e.detail.message.data.type}),
-                  })
-                });
-                window.dispatchEvent(reply);
-              }
-              break;
-          }
-        }
-      }, true);
-
-      document.onreadystatechange = function() {
-        if (document.readyState === "complete") {
-          let msg = new window.CustomEvent("WebChannelMessageToChrome", {
-            detail: JSON.stringify({
-              id: "newtab",
-              message: JSON.stringify({type: "REQUEST_FRECENT"}),
-            })
-          });
-          window.dispatchEvent(msg);
-        }
-      }
-    </script>
-  </body>
-</html>
deleted file mode 100644
--- a/browser/components/newtab/tests/browser/newtabmessages_prefs.html
+++ /dev/null
@@ -1,32 +0,0 @@
-<html>
-    <head>
-        <meta charset="utf8">
-        <title>Newtab WebChannel test</title>
-    </head>
-    <body>
-        <script>
-            window.addEventListener("WebChannelMessageToContent", function(e) {
-                if (e.detail.message && e.detail.message.type === "RECEIVE_PREFS") {
-                    let reply = new window.CustomEvent("WebChannelMessageToChrome", {
-                        detail: JSON.stringify({
-                            id: "newtab",
-                            message: JSON.stringify({type: "responseAck"}),
-                        })
-                    });
-                    window.dispatchEvent(reply);
-                }
-            }, true);
-
-            document.onreadystatechange = function() {
-                let msg = new window.CustomEvent("WebChannelMessageToChrome", {
-                    detail: JSON.stringify({
-                        id: "newtab",
-                        message: JSON.stringify({type: "REQUEST_PREFS"}),
-                    })
-                });
-                window.dispatchEvent(msg);
-            };
-
-        </script>
-    </body>
-</html>
deleted file mode 100644
--- a/browser/components/newtab/tests/browser/newtabmessages_preview.html
+++ /dev/null
@@ -1,37 +0,0 @@
-<html>
-  <head>
-    <meta charset="utf8">
-    <title>Newtab WebChannel test</title>
-  </head>
-  <body>
-    <script>
-      let thumbURL = "https://example.com/browser/browser/components/newtab/tests/browser/blue_page.html";
-
-      window.addEventListener("WebChannelMessageToContent", function(e) {
-        if (e.detail.message && e.detail.message.type === "RECEIVE_THUMB") {
-          if (e.detail.message.data.imgData && e.detail.message.data.url === thumbURL) {
-            let reply = new window.CustomEvent("WebChannelMessageToChrome", {
-              detail: JSON.stringify({
-                id: "newtab",
-                message: JSON.stringify({type: "responseAck"}),
-              })
-            });
-            window.dispatchEvent(reply);
-          }
-        }
-      }, true);
-
-      document.onreadystatechange = function() {
-        if (document.readyState === "complete") {
-          let msg = new window.CustomEvent("WebChannelMessageToChrome", {
-            detail: JSON.stringify({
-              id: "newtab",
-              message: JSON.stringify({type: "REQUEST_THUMB", data: thumbURL}),
-            })
-          });
-          window.dispatchEvent(msg);
-        }
-      };
-    </script>
-  </body>
-</html>
deleted file mode 100644
--- a/browser/components/newtab/tests/browser/newtabmessages_search.html
+++ /dev/null
@@ -1,113 +0,0 @@
-<html>
-  <head>
-    <meta charset="utf8">
-    <title>Newtab WebChannel test</title>
-  </head>
-  <body>
-    <script>
-      let suggestionsData = {
-        engineName: "Engine1",
-        searchString: "test",
-      };
-      let removeFormHistoryData = "test";
-      let performSearchData = {
-        engineName: "Engine1",
-        healthReportKey: "1",
-        searchPurpose: "d",
-        searchString: "test",
-      };
-      let cycleEngineData = "Engine2";
-
-      window.addEventListener("WebChannelMessageToContent", function(e) {
-        if (e.detail.message) {
-          let reply;
-          switch (e.detail.message.type) {
-            case "RECEIVE_UISTRINGS":
-              reply = new window.CustomEvent("WebChannelMessageToChrome", {
-                detail: {
-                  id: "newtab",
-                  message: JSON.stringify({type: "UIStringsAck", data: e.detail.message.data}),
-                }
-              });
-              window.dispatchEvent(reply);
-              break;
-            case "RECEIVE_SEARCH_SUGGESTIONS":
-              reply = new window.CustomEvent("WebChannelMessageToChrome", {
-                detail: {
-                  id: "newtab",
-                  message: JSON.stringify({type: "suggestionsAck", data: e.detail.message.data}),
-                }
-              });
-              window.dispatchEvent(reply);
-              break;
-            case "RECEIVE_SEARCH_STATE":
-              reply = new window.CustomEvent("WebChannelMessageToChrome", {
-                detail: {
-                  id: "newtab",
-                  message: JSON.stringify({type: "stateAck", data: e.detail.message.data}),
-                }
-              });
-              window.dispatchEvent(reply);
-              break;
-            case "RECEIVE_CURRENT_ENGINE":
-              reply = new window.CustomEvent("WebChannelMessageToChrome", {
-                detail: {
-                  id: "newtab",
-                  message: JSON.stringify({type: "currentEngineAck", data: e.detail.message.data}),
-                }
-              });
-              window.dispatchEvent(reply);
-              break;
-          }
-        }
-      }, true);
-
-      document.onreadystatechange = function() {
-        if (document.readyState === "complete") {
-          let msg = new window.CustomEvent("WebChannelMessageToChrome", {
-            detail: {
-              id: "newtab",
-              message: JSON.stringify({type: "REQUEST_UISTRINGS"}),
-            }
-          });
-          window.dispatchEvent(msg);
-          msg = new window.CustomEvent("WebChannelMessageToChrome", {
-            detail: {
-              id: "newtab",
-              message: JSON.stringify({type: "REQUEST_SEARCH_SUGGESTIONS", data: suggestionsData}),
-            }
-          });
-          window.dispatchEvent(msg);
-          msg = new window.CustomEvent("WebChannelMessageToChrome", {
-            detail: {
-              id: "newtab",
-              message: JSON.stringify({type: "REQUEST_SEARCH_STATE"}),
-            }
-          });
-          window.dispatchEvent(msg);
-          msg = new window.CustomEvent("WebChannelMessageToChrome", {
-            detail: {
-              id: "newtab",
-              message: JSON.stringify({type: "REQUEST_REMOVE_FORM_HISTORY", data: removeFormHistoryData}),
-            }
-          });
-          window.dispatchEvent(msg);
-          msg = new window.CustomEvent("WebChannelMessageToChrome", {
-            detail: {
-              id: "newtab",
-              message: JSON.stringify({type: "REQUEST_PERFORM_SEARCH", data: performSearchData}),
-            }
-          });
-          window.dispatchEvent(msg);
-          msg = new window.CustomEvent("WebChannelMessageToChrome", {
-            detail: {
-              id: "newtab",
-              message: JSON.stringify({type: "REQUEST_CYCLE_ENGINE", data: cycleEngineData}),
-            }
-          });
-          window.dispatchEvent(msg);
-        }
-      }
-    </script>
-  </body>
-</html>
deleted file mode 100644
--- a/browser/components/newtab/tests/browser/newtabwebchannel_basic.html
+++ /dev/null
@@ -1,36 +0,0 @@
-<html>
-    <head>
-        <meta charset="utf8">
-        <title>Newtab WebChannel test</title>
-    </head>
-    <body>
-        <script>
-            document.onreadystatechange = function() {
-                let msg = new window.CustomEvent("WebChannelMessageToChrome", {
-                    detail: JSON.stringify({
-                        id: "newtab",
-                        message: JSON.stringify({type: "foo", data: "bar"}),
-                    })
-                });
-                window.dispatchEvent(msg);
-            };
-
-            window.addEventListener("WebChannelMessageToContent", function(e) {
-                if (e.detail.message && e.detail.message.type.startsWith("respond")) {
-                    var detail = {
-                        id: "newtab",
-                        message: JSON.stringify({type: "reply", data: "quuz"}),
-                    };
-                    if (e.detail.message.type !== "respond_object") {
-                        detail = JSON.stringify(detail);
-                    }
-                    let reply = new window.CustomEvent("WebChannelMessageToChrome", {
-                        detail
-                    });
-                    window.dispatchEvent(reply);
-                }
-            }, true);
-            
-        </script>
-    </body>
-</html>
--- a/browser/components/newtab/tests/xpcshell/test_AboutNewTabService.js
+++ b/browser/components/newtab/tests/xpcshell/test_AboutNewTabService.js
@@ -13,223 +13,129 @@ Cu.import("resource://gre/modules/Prefer
 
 XPCOMUtils.defineLazyModuleGetter(this, "NewTabPrefsProvider",
                                   "resource:///modules/NewTabPrefsProvider.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
                                    "@mozilla.org/browser/aboutnewtab-service;1",
                                    "nsIAboutNewTabService");
 
-XPCOMUtils.defineLazyModuleGetter(this, "Locale",
-                                  "resource://gre/modules/Locale.jsm");
-
-const DEFAULT_HREF = aboutNewTabService.generateRemoteURL();
+const DEFAULT_HREF = aboutNewTabService.activityStreamURL;
 const DEFAULT_CHROME_URL = "chrome://browser/content/newtab/newTab.xhtml";
 const DOWNLOADS_URL = "chrome://browser/content/downloads/contentAreaDownloadsView.xul";
-const DEFAULT_VERSION = aboutNewTabService.remoteVersion;
 
 function cleanup() {
-  Services.prefs.setBoolPref("browser.newtabpage.remote", false);
-  Services.prefs.setCharPref("browser.newtabpage.remote.version", DEFAULT_VERSION);
+  Services.prefs.setBoolPref("browser.newtabpage.activity-stream.enabled", false);
   aboutNewTabService.resetNewTabURL();
   NewTabPrefsProvider.prefs.uninit();
 }
 
 do_register_cleanup(cleanup);
 
 /**
  * Test the overriding of the default URL
  */
-add_task(function* test_override_remote_disabled() {
+add_task(function* test_override_activity_stream_disabled() {
   NewTabPrefsProvider.prefs.init();
   let notificationPromise;
-  Services.prefs.setBoolPref("browser.newtabpage.remote", false);
+  Services.prefs.setBoolPref("browser.newtabpage.activity-stream.enabled", false);
 
   // tests default is the local newtab resource
   Assert.equal(aboutNewTabService.defaultURL, DEFAULT_CHROME_URL,
                `Default newtab URL should be ${DEFAULT_CHROME_URL}`);
 
   // override with some remote URL
   let url = "http://example.com/";
   notificationPromise = nextChangeNotificationPromise(url);
   aboutNewTabService.newTabURL = url;
   yield notificationPromise;
   Assert.ok(aboutNewTabService.overridden, "Newtab URL should be overridden");
-  Assert.ok(!aboutNewTabService.remoteEnabled, "Newtab remote should not be enabled");
+  Assert.ok(!aboutNewTabService.activityStreamEnabled, "Newtab activity stream should not be enabled");
   Assert.equal(aboutNewTabService.newTabURL, url, "Newtab URL should be the custom URL");
 
-  // test reset with remote disabled
+  // test reset with activity stream disabled
   notificationPromise = nextChangeNotificationPromise("about:newtab");
   aboutNewTabService.resetNewTabURL();
   yield notificationPromise;
   Assert.ok(!aboutNewTabService.overridden, "Newtab URL should not be overridden");
   Assert.equal(aboutNewTabService.newTabURL, "about:newtab", "Newtab URL should be the default");
 
   // test override to a chrome URL
   notificationPromise = nextChangeNotificationPromise(DOWNLOADS_URL);
   aboutNewTabService.newTabURL = DOWNLOADS_URL;
   yield notificationPromise;
   Assert.ok(aboutNewTabService.overridden, "Newtab URL should be overridden");
   Assert.equal(aboutNewTabService.newTabURL, DOWNLOADS_URL, "Newtab URL should be the custom URL");
 
   cleanup();
 });
 
-add_task(function* test_override_remote_enabled() {
+add_task(function* test_override_activity_stream_enabled() {
   NewTabPrefsProvider.prefs.init();
   let notificationPromise;
-  // change newtab page to remote
+  // change newtab page to activity stream
   notificationPromise = nextChangeNotificationPromise("about:newtab");
-  Services.prefs.setBoolPref("browser.newtabpage.remote", true);
+  Services.prefs.setBoolPref("browser.newtabpage.activity-stream.enabled", true);
   yield notificationPromise;
-  let remoteHref = aboutNewTabService.generateRemoteURL();
-  Assert.equal(aboutNewTabService.defaultURL, remoteHref, "Newtab URL should be the default remote URL");
+  let activityStreamURL = aboutNewTabService.activityStreamURL;
+  Assert.equal(aboutNewTabService.defaultURL, activityStreamURL, "Newtab URL should be the default activity stream URL");
   Assert.ok(!aboutNewTabService.overridden, "Newtab URL should not be overridden");
-  Assert.ok(aboutNewTabService.remoteEnabled, "Newtab remote should be enabled");
+  Assert.ok(aboutNewTabService.activityStreamEnabled, "Activity Stream should be enabled");
 
-  // change to local newtab page while remote is enabled
+  // change to local newtab page while activity stream is enabled
   notificationPromise = nextChangeNotificationPromise(DEFAULT_CHROME_URL);
   aboutNewTabService.newTabURL = DEFAULT_CHROME_URL;
   yield notificationPromise;
   Assert.equal(aboutNewTabService.newTabURL, DEFAULT_CHROME_URL,
                "Newtab URL set to chrome url");
   Assert.equal(aboutNewTabService.defaultURL, DEFAULT_CHROME_URL,
                "Newtab URL defaultURL set to the default chrome URL");
   Assert.ok(aboutNewTabService.overridden, "Newtab URL should be overridden");
-  Assert.ok(!aboutNewTabService.remoteEnabled, "Newtab remote should not be enabled");
+  Assert.ok(!aboutNewTabService.activityStreamEnabled, "Activity Stream should not be enabled");
 
   cleanup();
 });
 
 /**
  * Tests reponse to updates to prefs
  */
 add_task(function* test_updates() {
   /*
    * Simulates a "cold-boot" situation, with some pref already set before testing a series
    * of changes.
    */
-  Preferences.set("browser.newtabpage.remote", true);
+  Preferences.set("browser.newtabpage.activity-stream.enabled", true);
   aboutNewTabService.resetNewTabURL(); // need to set manually because pref notifs are off
   let notificationPromise;
-  let productionModeBaseUrl = "https://content.cdn.mozilla.net";
-  let testModeBaseUrl = "https://example.com";
-  let expectedPath = `/newtab` +
-                     `/v${aboutNewTabService.remoteVersion}` +
-                     `/${aboutNewTabService.remoteReleaseName}` +
-                     "/en-GB" +
-                     "/index.html";
-  let expectedHref = productionModeBaseUrl + expectedPath;
-  Preferences.set("intl.locale.matchOS", true);
-  Preferences.set("general.useragent.locale", "en-GB");
-  Preferences.set("browser.newtabpage.remote.mode", "production");
   NewTabPrefsProvider.prefs.init();
 
-  // test update checks for prefs
-  notificationPromise = nextChangeNotificationPromise(
-    expectedHref, "Remote href should be updated");
-  Preferences.set("intl.locale.matchOS", false);
-  yield notificationPromise;
-
-  notificationPromise = nextChangeNotificationPromise(
-    DEFAULT_HREF, "Remote href changes back to default");
-  Preferences.set("general.useragent.locale", "en-US");
-  yield notificationPromise;
-
-  // test update fires when mode is changed
-  expectedPath = expectedPath.replace("/en-GB/", "/en-US/");
-  notificationPromise = nextChangeNotificationPromise(
-    testModeBaseUrl + expectedPath, "Remote href changes back to origin of test mode");
-  Preferences.set("browser.newtabpage.remote.mode", "test");
-  yield notificationPromise;
-
-  // test invalid mode ends up pointing to production url
-  notificationPromise = nextChangeNotificationPromise(
-    DEFAULT_HREF, "Remote href changes back to production default");
-  Preferences.set("browser.newtabpage.remote.mode", "invalid");
-  yield notificationPromise;
-
   // test update fires on override and reset
   let testURL = "https://example.com/";
   notificationPromise = nextChangeNotificationPromise(
     testURL, "a notification occurs on override");
   aboutNewTabService.newTabURL = testURL;
   yield notificationPromise;
 
   // from overridden to default
   notificationPromise = nextChangeNotificationPromise(
     "about:newtab", "a notification occurs on reset");
   aboutNewTabService.resetNewTabURL();
-  Assert.ok(aboutNewTabService.remoteEnabled, "Newtab remote should be enabled");
-  Assert.equal(aboutNewTabService.defaultURL, DEFAULT_HREF, "Default URL should be the remote page");
+  Assert.ok(aboutNewTabService.activityStreamEnabled, "Activity Stream should be enabled");
+  Assert.equal(aboutNewTabService.defaultURL, DEFAULT_HREF, "Default URL should be the activity stream page");
   yield notificationPromise;
 
-  // override to default URL from default URL
-  notificationPromise = nextChangeNotificationPromise(
-    testURL, "a notification only occurs for a change in overridden urls");
-  aboutNewTabService.newTabURL = aboutNewTabService.generateRemoteURL();
-  Assert.ok(aboutNewTabService.remoteEnabled, "Newtab remote should be enabled");
-  aboutNewTabService.newTabURL = testURL;
-  yield notificationPromise;
-  Assert.ok(!aboutNewTabService.remoteEnabled, "Newtab remote should not be enabled");
-
   // reset twice, only one notification for default URL
   notificationPromise = nextChangeNotificationPromise(
     "about:newtab", "reset occurs");
   aboutNewTabService.resetNewTabURL();
   yield notificationPromise;
 
   cleanup();
 });
 
-/**
- * Verifies that releaseFromUpdateChannel
- * Returns the correct release names
- */
-add_task(function* test_release_names() {
-  let valid_channels = ["esr", "release", "beta", "aurora", "nightly"];
-  let invalid_channels = new Set(["default", "invalid"]);
-
-  for (let channel of valid_channels) {
-    Assert.equal(channel, aboutNewTabService.releaseFromUpdateChannel(channel),
-          "release == channel name when valid");
-  }
-
-  for (let channel of invalid_channels) {
-    Assert.equal("nightly", aboutNewTabService.releaseFromUpdateChannel(channel),
-          "release == nightly when invalid");
-  }
-});
-
-/**
- * Verifies that remote version updates changes the remote newtab url
- */
-add_task(function* test_version_update() {
-  NewTabPrefsProvider.prefs.init();
-
-  Services.prefs.setBoolPref("browser.newtabpage.remote", true);
-  Assert.ok(aboutNewTabService.remoteEnabled, "remote mode enabled");
-
-  let productionModeBaseUrl = "https://content.cdn.mozilla.net";
-  let version_incr = String(parseInt(DEFAULT_VERSION) + 1);
-  let expectedPath = `/newtab` +
-                     `/v${version_incr}` +
-                     `/${aboutNewTabService.remoteReleaseName}` +
-                     `/${Locale.getLocale()}` +
-                     `/index.html`;
-  let expectedHref = productionModeBaseUrl + expectedPath;
-
-  let notificationPromise;
-  notificationPromise = nextChangeNotificationPromise(expectedHref);
-  Preferences.set("browser.newtabpage.remote.version", version_incr);
-  yield notificationPromise;
-
-  cleanup();
-});
-
 function nextChangeNotificationPromise(aNewURL, testMessage) {
   return new Promise(resolve => {
     Services.obs.addObserver(function observer(aSubject, aTopic, aData) {  // jshint unused:false
       Services.obs.removeObserver(observer, aTopic);
       Assert.equal(aData, aNewURL, testMessage);
       resolve();
     }, "newtab-url-changed", false);
   });
--- a/browser/components/newtab/tests/xpcshell/test_NewTabURL.js
+++ b/browser/components/newtab/tests/xpcshell/test_NewTabURL.js
@@ -12,35 +12,35 @@ Cu.import("resource://gre/modules/Servic
 XPCOMUtils.defineLazyModuleGetter(this, "NewTabPrefsProvider",
                                   "resource:///modules/NewTabPrefsProvider.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
                                    "@mozilla.org/browser/aboutnewtab-service;1",
                                    "nsIAboutNewTabService");
 
 add_task(function*() {
   let defaultURL = aboutNewTabService.newTabURL;
-  Services.prefs.setBoolPref("browser.newtabpage.remote", false);
+  Services.prefs.setBoolPref("browser.newtabpage.activity-stream.enabled", false);
 
   Assert.equal(NewTabURL.get(), defaultURL, `Default newtab URL should be ${defaultURL}`);
   let url = "http://example.com/";
   let notificationPromise = promiseNewtabURLNotification(url);
   NewTabURL.override(url);
   yield notificationPromise;
   Assert.ok(NewTabURL.overridden, "Newtab URL should be overridden");
   Assert.equal(NewTabURL.get(), url, "Newtab URL should be the custom URL");
 
   notificationPromise = promiseNewtabURLNotification(defaultURL);
   NewTabURL.reset();
   yield notificationPromise;
   Assert.ok(!NewTabURL.overridden, "Newtab URL should not be overridden");
   Assert.equal(NewTabURL.get(), defaultURL, "Newtab URL should be the default");
 
-  // change newtab page to remote
+  // change newtab page to activity stream
   NewTabPrefsProvider.prefs.init();
-  Services.prefs.setBoolPref("browser.newtabpage.remote", true);
+  Services.prefs.setBoolPref("browser.newtabpage.activity-stream.enabled", true);
   Assert.equal(NewTabURL.get(), "about:newtab", `Newtab URL should be about:newtab`);
   Assert.ok(!NewTabURL.overridden, "Newtab URL should not be overridden");
   NewTabPrefsProvider.prefs.uninit();
 });
 
 function promiseNewtabURLNotification(aNewURL) {
   return new Promise(resolve => {
     Services.obs.addObserver(function observer(aSubject, aTopic, aData) { // jshint ignore:line
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -18,17 +18,18 @@ XPCOMUtils.defineLazyServiceGetter(this,
 XPCOMUtils.defineLazyServiceGetter(this, "AlertsService", "@mozilla.org/alerts-service;1", "nsIAlertsService");
 
 // lazy module getters
 
 /* global AboutHome:false, AboutNewTab:false, AddonManager:false, AddonWatcher:false,
           AsyncShutdown:false, AutoCompletePopup:false, BookmarkHTMLUtils:false,
           BookmarkJSONUtils:false, BrowserUITelemetry:false, BrowserUsageTelemetry:false,
           ContentClick:false, ContentPrefServiceParent:false, ContentSearch:false,
-          DateTimePickerHelper:false, DirectoryLinksProvider:false, Feeds:false,
+          DateTimePickerHelper:false, DirectoryLinksProvider:false,
+          ExtensionsUI:false, Feeds:false,
           FileUtils:false, FormValidationHandler:false, Integration:false,
           LightweightThemeManager:false, LoginHelper:false, LoginManagerParent:false,
           NetUtil:false, NewTabMessages:false, NewTabUtils:false, OS:false,
           PageThumbs:false, PdfJs:false, PermissionUI:false, PlacesBackups:false,
           PlacesUtils:false, PluralForm:false, PrivateBrowsingUtils:false,
           ProcessHangMonitor:false, ReaderParent:false, RecentWindow:false,
           RemotePrompt:false, SelfSupportBackend:false, SessionStore:false,
           ShellService:false, SimpleServiceDiscovery:false, TabCrashHandler:false,
--- a/browser/components/originattributes/test/browser/browser_firstPartyIsolation.js
+++ b/browser/components/originattributes/test/browser/browser_firstPartyIsolation.js
@@ -1,8 +1,11 @@
+// This file spawns content tasks.
+/* eslint-env mozilla/frame-script */
+
 const BASE_URL = "http://mochi.test:8888/browser/browser/components/originattributes/test/browser/";
 const BASE_DOMAIN = "mochi.test";
 
 add_task(function* setup() {
   Services.prefs.setBoolPref("privacy.firstparty.isolate", true);
   registerCleanupFunction(function() {
     Services.prefs.clearUserPref("privacy.firstparty.isolate");
   });
new file mode 100644
--- /dev/null
+++ b/browser/components/places/content/.eslintrc.js
@@ -0,0 +1,12 @@
+"use strict";
+
+module.exports = {
+  "env": {
+    // Everything in this directory is loaded alongside the places overlay.
+    "mozilla/places-overlay": true
+  },
+
+  "plugins": [
+    "mozilla",
+  ]
+};
--- a/browser/components/places/content/bookmarkProperties.js
+++ b/browser/components/places/content/bookmarkProperties.js
@@ -52,16 +52,18 @@
  *     - "tags"
  *     - "loadInSidebar"
  *     - "folderPicker" - hides both the tree and the menu.
  *
  * window.arguments[0].performed is set to true if any transaction has
  * been performed by the dialog.
  */
 
+/* import-globals-from editBookmarkOverlay.js */
+
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
                                   "resource://gre/modules/PromiseUtils.jsm");
 
--- a/browser/components/places/content/browserPlacesViews.js
+++ b/browser/components/places/content/browserPlacesViews.js
@@ -1,12 +1,14 @@
 /* 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/. */
 
+/* eslint-env mozilla/browser-window */
+
 Components.utils.import("resource://gre/modules/AppConstants.jsm");
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 /**
  * The base view implements everything that's common to the toolbar and
  * menu views.
  */
--- a/browser/components/places/content/editBookmarkOverlay.js
+++ b/browser/components/places/content/editBookmarkOverlay.js
@@ -281,19 +281,23 @@ var gEditItemOverlay = {
 
     // The focusedElement possible values are:
     //  * preferred: focus the field that the user touched first the last
     //    time the pane was shown (either namePicker or tagsField)
     //  * first: focus the first non collapsed textbox
     // Note: since all controls are collapsed by default, we don't get the
     // default XUL dialog behavior, that selects the first control, so we set
     // the focus explicitly.
+    // Note: If focusedElement === "preferred", this file expects gPrefService
+    // to be defined in the global scope.
     let elt;
     if (focusedElement === "preferred") {
+      /* eslint-disable no-undef */
       elt = this._element(gPrefService.getCharPref("browser.bookmarks.editDialog.firstEditField"));
+      /* eslint-enable no-undef */
     } else if (focusedElement === "first") {
       elt = document.querySelector("textbox:not([collapsed=true])");
     }
     if (elt) {
       elt.focus();
       elt.select();
     }
   },
@@ -894,17 +898,17 @@ var gEditItemOverlay = {
                                          : null;
 
     while (tagsSelector.hasChildNodes()) {
       tagsSelector.removeChild(tagsSelector.lastChild);
     }
 
     let tagsInField = this._getTagsArrayFromTagsInputField();
     let allTags = PlacesUtils.tagging.allTags;
-    for (tag of allTags) {
+    for (let tag of allTags) {
       let elt = document.createElement("listitem");
       elt.setAttribute("type", "checkbox");
       elt.setAttribute("label", tag);
       if (tagsInField.includes(tag))
         elt.setAttribute("checked", "true");
       tagsSelector.appendChild(elt);
       if (selectedTag === tag)
         selectedIndex = tagsSelector.getIndexOfItem(elt);
@@ -1059,17 +1063,17 @@ var gEditItemOverlay = {
     if (aItemId == this._paneInfo.itemId) {
       this._paneInfo.title = aNewTitle;
       this._initTextField(this._namePicker, aNewTitle);
     } else if (this._paneInfo.visibleRows.has("folderRow")) {
       // If the title of a folder which is listed within the folders
       // menulist has been changed, we need to update the label of its
       // representing element.
       let menupopup = this._folderMenuList.menupopup;
-      for (menuitem of menupopup.childNodes) {
+      for (let menuitem of menupopup.childNodes) {
         if ("folderId" in menuitem && menuitem.folderId == aItemId) {
           menuitem.label = aNewTitle;
           break;
         }
       }
     }
   },
 
--- a/browser/components/places/content/places.js
+++ b/browser/components/places/content/places.js
@@ -1,13 +1,17 @@
 /* -*- 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/. */
 
+/* import-globals-from editBookmarkOverlay.js */
+// Via downloadsViewOverlay.xul -> allDownloadsViewOverlay.xul
+/* import-globals-from ../../../../toolkit/content/contentAreaUtils.js */
+
 Components.utils.import("resource://gre/modules/AppConstants.jsm");
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/TelemetryStopwatch.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "MigrationUtils",
                                   "resource:///modules/MigrationUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "BookmarkJSONUtils",
--- a/browser/components/places/content/places.xul
+++ b/browser/components/places/content/places.xul
@@ -40,18 +40,16 @@
         width="&places.library.width;" height="&places.library.height;"
         screenX="10" screenY="10"
         toggletoolbar="true"
         persist="width height screenX screenY sizemode">
 
   <script type="application/javascript"
           src="chrome://browser/content/places/places.js"/>
   <script type="application/javascript"
-          src="chrome://browser/content/utilityOverlay.js"/>
-  <script type="application/javascript"
           src="chrome://browser/content/places/editBookmarkOverlay.js"/>
 
   <stringbundleset id="placesStringSet">
     <stringbundle id="brandStrings" src="chrome://branding/locale/brand.properties"/>
   </stringbundleset>
 
 
 #ifdef XP_MACOSX
@@ -84,17 +82,17 @@
              oncommand="PlacesOrganizer.forward();"/>
   </commandset>
 
 
   <keyset id="placesOrganizerKeyset">
     <!-- Instantiation Keys -->
     <key id="placesKey_close" key="&cmd.close.key;" modifiers="accel"
          oncommand="close();"/>
-         
+
     <!-- Command Keys -->
     <key id="placesKey_find:all"
          command="OrganizerCommand_find:all"
          key="&cmd.find.key;"
          modifiers="accel"/>
 
     <!-- Back/Forward Keys Support -->
 #ifndef XP_MACOSX
@@ -370,17 +368,17 @@
               flatList="true"
               selectfirstnode="true"
               enableColumnDrag="true"
               onfocus="PlacesOrganizer.updateDetailsPane(event)"
               onselect="PlacesOrganizer.updateDetailsPane(event)"
               onkeypress="ContentTree.onKeyPress(event);"
               onopenflatcontainer="PlacesOrganizer.openFlatContainer(aContainer);">
           <treecols id="placeContentColumns" context="placesColumnsContext">
-            <treecol label="&col.name.label;" id="placesContentTitle" anonid="title" flex="5" primary="true" ordinal="1" 
+            <treecol label="&col.name.label;" id="placesContentTitle" anonid="title" flex="5" primary="true" ordinal="1"
                       persist="width hidden ordinal sortActive sortDirection"/>
             <splitter class="tree-splitter"/>
             <treecol label="&col.tags.label;" id="placesContentTags" anonid="tags" flex="2"
                       persist="width hidden ordinal sortActive sortDirection"/>
             <splitter class="tree-splitter"/>
             <treecol label="&col.url.label;" id="placesContentUrl" anonid="url" flex="5"
                       persist="width hidden ordinal sortActive sortDirection"/>
             <splitter class="tree-splitter"/>
--- a/browser/components/places/content/placesOverlay.xul
+++ b/browser/components/places/content/placesOverlay.xul
@@ -18,16 +18,17 @@
   <script type="application/javascript"
           src="chrome://browser/content/utilityOverlay.js"/>
   <script type="application/javascript"><![CDATA[
     // TODO: Bug 406371.
     // A bunch of browser code depends on us defining these, sad but true :(
     var Cc = Components.classes;
     var Ci = Components.interfaces;
     var Cr = Components.results;
+    var Cu = Components.utils;
 
     Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
     Components.utils.import("resource://gre/modules/Task.jsm");
     Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
     XPCOMUtils.defineLazyModuleGetter(window,
       "PlacesUIUtils", "resource:///modules/PlacesUIUtils.jsm");
     XPCOMUtils.defineLazyModuleGetter(window,
       "PlacesTransactions", "resource://gre/modules/PlacesTransactions.jsm");
@@ -60,17 +61,17 @@
              oncommand="goDoPlacesCommand('placesCmd_open:tab');"/>
 
     <command id="placesCmd_new:bookmark"
              oncommand="goDoPlacesCommand('placesCmd_new:bookmark');"/>
     <command id="placesCmd_new:folder"
              oncommand="goDoPlacesCommand('placesCmd_new:folder');"/>
     <command id="placesCmd_new:separator"
              oncommand="goDoPlacesCommand('placesCmd_new:separator');"/>
-    <command id="placesCmd_show:info" 
+    <command id="placesCmd_show:info"
              oncommand="goDoPlacesCommand('placesCmd_show:info');"/>
     <command id="placesCmd_rename"
              oncommand="goDoPlacesCommand('placesCmd_show:info');"
              observes="placesCmd_show:info"/>
     <command id="placesCmd_reload"
              oncommand="goDoPlacesCommand('placesCmd_reload');"/>
     <command id="placesCmd_sortBy:name"
              oncommand="goDoPlacesCommand('placesCmd_sortBy:name');"/>
@@ -161,17 +162,17 @@
     <menuseparator id="placesContext_newSeparator"/>
     <menuitem id="placesContext_createBookmark"
               command="placesCmd_createBookmark"
               selection="link"
               forcehideselection="bookmark|tagChild"/>
     <menuitem id="placesContext_cut"
               command="placesCmd_cut"
               label="&cutCmd.label;"
-              accesskey="&cutCmd.accesskey;" 
+              accesskey="&cutCmd.accesskey;"
               closemenu="single"
               selection="bookmark|folder|separator|query"
               forcehideselection="tagChild|livemarkChild"/>
     <menuitem id="placesContext_copy"
               command="placesCmd_copy"
               label="&copyCmd.label;"
               closemenu="single"
               accesskey="&copyCmd.accesskey;"
@@ -215,15 +216,15 @@
               command="placesCmd_reload"
               label="&cmd.reloadLivebookmark.label;"
               accesskey="&cmd.reloadLivebookmark.accesskey;"
               closemenu="single"
               selection="livemark/feedURI"/>
     <menuseparator id="placesContext_sortSeparator"/>
     <menuitem id="placesContext_show:info"
               command="placesCmd_show:info"
-              label="&cmd.properties.label;" 
+              label="&cmd.properties.label;"
               accesskey="&cmd.properties.accesskey;"
               selection="bookmark|folder|query"
               forcehideselection="livemarkChild"/>
   </menupopup>
 
 </overlay>
--- a/browser/components/preferences/in-content/advanced.js
+++ b/browser/components/preferences/in-content/advanced.js
@@ -1,12 +1,14 @@
 /* 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-globals-from preferences.js */
+
 // Load DownloadUtils module for convertByteUnits
 Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
 Components.utils.import("resource://gre/modules/LoadContextInfo.jsm");
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "SiteDataManager",
                                   "resource:///modules/SiteDataManager.jsm");
 
--- a/browser/components/preferences/in-content/applications.js
+++ b/browser/components/preferences/in-content/applications.js
@@ -1,12 +1,14 @@
 /* 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-globals-from preferences.js */
+
 "use strict";
 
 // Constants & Enumeration Values
 
 Components.utils.import("resource://gre/modules/Services.jsm");
 Components.utils.import("resource://gre/modules/AppConstants.jsm");
 const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
 const TYPE_MAYBE_VIDEO_FEED = "application/vnd.mozilla.maybe.video.feed";
--- a/browser/components/preferences/in-content/containers.js
+++ b/browser/components/preferences/in-content/containers.js
@@ -1,12 +1,14 @@
 /* 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-globals-from preferences.js */
+
 Components.utils.import("resource://gre/modules/AppConstants.jsm");
 Components.utils.import("resource://gre/modules/ContextualIdentityService.jsm");
 
 const containersBundle = Services.strings.createBundle("chrome://browser/locale/preferences/containers.properties");
 
 const defaultContainerIcon = "fingerprint";
 const defaultContainerColor = "blue";
 
--- a/browser/components/preferences/in-content/content.js
+++ b/browser/components/preferences/in-content/content.js
@@ -1,12 +1,15 @@
 /* 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-globals-from preferences.js */
+/* import-globals-from ../../../../toolkit/mozapps/preferences/fontbuilder.js */
+
 XPCOMUtils.defineLazyGetter(this, "AlertsServiceDND", function() {
   try {
     let alertsService = Cc["@mozilla.org/alerts-service;1"]
                           .getService(Ci.nsIAlertsService)
                           .QueryInterface(Ci.nsIAlertsDoNotDisturb);
     // This will throw if manualDoNotDisturb isn't implemented.
     alertsService.manualDoNotDisturb;
     return alertsService;
--- a/browser/components/preferences/in-content/main.js
+++ b/browser/components/preferences/in-content/main.js
@@ -1,12 +1,14 @@
 /* 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-globals-from preferences.js */
+
 Components.utils.import("resource://gre/modules/Downloads.jsm");
 Components.utils.import("resource://gre/modules/FileUtils.jsm");
 Components.utils.import("resource://gre/modules/Task.jsm");
 Components.utils.import("resource:///modules/ShellService.jsm");
 Components.utils.import("resource:///modules/TransientPrefs.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
--- a/browser/components/preferences/in-content/preferences.js
+++ b/browser/components/preferences/in-content/preferences.js
@@ -2,16 +2,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/. */
 
 // Import globals from the files imported by the .xul files.
 /* import-globals-from subdialogs.js */
 /* import-globals-from advanced.js */
 /* import-globals-from main.js */
 /* import-globals-from search.js */
+/* import-globals-from containers.js */
 /* import-globals-from content.js */
 /* import-globals-from privacy.js */
 /* import-globals-from applications.js */
 /* import-globals-from security.js */
 /* import-globals-from sync.js */
 /* import-globals-from ../../../base/content/utilityOverlay.js */
 
 "use strict";
--- a/browser/components/preferences/in-content/privacy.js
+++ b/browser/components/preferences/in-content/privacy.js
@@ -1,12 +1,14 @@
 /* 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-globals-from preferences.js */
+
 Components.utils.import("resource://gre/modules/AppConstants.jsm");
 Components.utils.import("resource://gre/modules/PluralForm.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
                                   "resource://gre/modules/ContextualIdentityService.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
                                   "resource://gre/modules/PluralForm.jsm");
 
--- a/browser/components/preferences/in-content/search.js
+++ b/browser/components/preferences/in-content/search.js
@@ -1,12 +1,14 @@
 /* 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-globals-from preferences.js */
+
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 
 const ENGINE_FLAVOR = "text/x-moz-search-engine";
 
--- a/browser/components/preferences/in-content/security.js
+++ b/browser/components/preferences/in-content/security.js
@@ -1,12 +1,14 @@
 /* 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-globals-from preferences.js */
+
 XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
  "resource://gre/modules/LoginHelper.jsm");
 
 Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 var gSecurityPane = {
   _pane: null,
 
--- a/browser/components/preferences/in-content/subdialogs.js
+++ b/browser/components/preferences/in-content/subdialogs.js
@@ -1,12 +1,15 @@
 /* - 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-globals-from ../../../base/content/utilityOverlay.js */
+/* import-globals-from preferences.js */
+
 "use strict";
 
 var gSubDialog = {
   _closingCallback: null,
   _closingEvent: null,
   _isClosing: false,
   _frame: null,
   _overlay: null,
--- a/browser/components/preferences/in-content/sync.js
+++ b/browser/components/preferences/in-content/sync.js
@@ -1,12 +1,14 @@
 /* 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-globals-from preferences.js */
+
 Components.utils.import("resource://services-sync/main.js");
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "FxAccountsCommon", function() {
   return Components.utils.import("resource://gre/modules/FxAccountsCommon.js", {});
 });
 
 XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
--- a/browser/components/preferences/in-content/tests/privacypane_tests_perwindow.js
+++ b/browser/components/preferences/in-content/tests/privacypane_tests_perwindow.js
@@ -1,8 +1,11 @@
+// This file gets imported into the same scope as head.js.
+/* import-globals-from head.js */
+
 function* runTestOnPrivacyPrefPane(testFunc) {
   info("runTestOnPrivacyPrefPane entered");
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:preferences", true, true);
   let browser = tab.linkedBrowser;
   info("loaded about:preferences");
   browser.contentWindow.gotoPref("panePrivacy");
   info("viewing privacy pane, executing testFunc");
   testFunc(browser.contentWindow);
--- a/browser/components/preferences/siteDataSettings.xul
+++ b/browser/components/preferences/siteDataSettings.xul
@@ -22,19 +22,19 @@
 
   <stringbundle id="bundlePreferences"
                 src="chrome://browser/locale/preferences/preferences.properties"/>
 
   <vbox flex="1">
     <description>&settings.description;</description>
     <separator class="thin"/>
 
-    <hbox id="searchBoxContainer" align="center">
-      <label accesskey="&search.accesskey;" control="searchBox">&search.label;</label>
-      <textbox id="searchBox" type="search" flex="1"/>
+    <hbox id="searchBoxContainer">
+      <textbox id="searchBox" type="search" flex="1"
+        placeholder="&searchPlaceHolder;" accesskey="&searchPlaceHolder.accesskey;"/>
     </hbox>
     <separator class="thin"/>
 
     <richlistbox id="sitesList" orient="vertical" flex="1">
       <listheader>
         <treecol flex="4" width="50" label="&hostCol.label;" id="hostCol"/>
         <treecol flex="2" width="50" label="&statusCol.label;" id="statusCol"/>
         <treecol flex="1" width="50" label="&usageCol.label;" id="usageCol"/>
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_context_and_chromeFlags.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_context_and_chromeFlags.js
@@ -1,10 +1,12 @@
 "use strict";
 
+/* eslint-env mozilla/frame-script */
+
 /**
  * Given some window in the parent process, ensure that
  * the nsIXULWindow has the CHROME_PRIVATE_WINDOW chromeFlag,
  * and that the usePrivateBrowsing property is set to true on
  * both the window's nsILoadContext, as well as on the initial
  * browser's content docShell nsILoadContext.
  *
  * @param win (nsIDOMWindow)
--- a/browser/components/search/content/search.xml
+++ b/browser/components/search/content/search.xml
@@ -1,13 +1,19 @@
 <?xml version="1.0"?>
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
+<!-- This file is imported into the browser window. -->
+<!-- eslint-env mozilla/browser-window -->
+
+<!-- XULCommandEvent is a specialised global. -->
+<!-- global XULCommandEvent -->
+
 <!DOCTYPE bindings [
 <!ENTITY % searchBarDTD SYSTEM "chrome://browser/locale/searchbar.dtd" >
 %searchBarDTD;
 <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
 %browserDTD;
 ]>
 
 <bindings id="SearchBindings"
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -4211,19 +4211,20 @@ var SessionStoreInternal = {
    *
    * @param aTabState
    *        The current tab state
    * @returns boolean
    */
   _shouldSaveTab: function ssi_shouldSaveTab(aTabState) {
     // If the tab has one of the following transient about: history entry,
     // then we don't actually want to write this tab's data to disk.
-    return aTabState.entries.length &&
-           !(aTabState.entries[0].url == "about:printpreview" ||
-             aTabState.entries[0].url == "about:privatebrowsing");
+    return aTabState.userTypedValue ||
+           (aTabState.entries.length &&
+            !(aTabState.entries[0].url == "about:printpreview" ||
+              aTabState.entries[0].url == "about:privatebrowsing"));
   },
 
   /**
    * This is going to take a state as provided at startup (via
    * nsISessionStartup.state) and split it into 2 parts. The first part
    * (defaultState) will be a state that should still be restored at startup,
    * while the second part (state) is a state that should be saved for later.
    * defaultState will be comprised of windows with only pinned tabs, extracted
--- a/browser/components/syncedtabs/sidebar.js
+++ b/browser/components/syncedtabs/sidebar.js
@@ -8,17 +8,17 @@ const {classes: Cc, interfaces: Ci, util
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://services-sync/SyncedTabs.jsm");
 Cu.import("resource:///modules/syncedtabs/SyncedTabsDeckComponent.js");
 
 XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
                                   "resource://gre/modules/FxAccounts.jsm");
 
-this.syncedTabsDeckComponent = new SyncedTabsDeckComponent({window, SyncedTabs, fxAccounts});
+var syncedTabsDeckComponent = new SyncedTabsDeckComponent({window, SyncedTabs, fxAccounts});
 
 let onLoaded = () => {
   syncedTabsDeckComponent.init();
   document.getElementById("template-container").appendChild(syncedTabsDeckComponent.container);
 };
 
 let onUnloaded = () => {
   removeEventListener("DOMContentLoaded", onLoaded);
--- a/browser/components/translation/translation-infobar.xml
+++ b/browser/components/translation/translation-infobar.xml
@@ -1,13 +1,15 @@
 <?xml version="1.0"?>
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
+<!-- eslint-env mozilla/browser-window -->
+
 <!DOCTYPE bindings [
 <!ENTITY % notificationDTD SYSTEM "chrome://global/locale/notification.dtd">
 %notificationDTD;
 <!ENTITY % translationDTD SYSTEM "chrome://browser/locale/translation.dtd" >
 %translationDTD;
 ]>
 
 <bindings id="translationBindings"
--- a/browser/components/uitour/UITour-lib.js
+++ b/browser/components/uitour/UITour-lib.js
@@ -321,11 +321,12 @@ if (typeof Mozilla == "undefined") {
    * displaying a goodbye message or a button to restart the tour.
    */
   Mozilla.UITour.closeTab = function() {
     _sendEvent("closeTab");
   };
 })();
 
 // Make this library Require-able.
+/* eslint-env commonjs */
 if (typeof module !== "undefined" && module.exports) {
   module.exports = Mozilla.UITour;
 }
--- a/browser/components/uitour/content-UITour.js
+++ b/browser/components/uitour/content-UITour.js
@@ -1,15 +1,16 @@
 /* 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/. */
 
 /* eslint-env mozilla/frame-script */
 
 var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+Cu.import("resource://gre/modules/Services.jsm");
 
 const PREF_TEST_WHITELIST = "browser.uitour.testingOrigins";
 const UITOUR_PERMISSION   = "uitour";
 
 var UITourListener = {
   handleEvent(event) {
     if (!Services.prefs.getBoolPref("browser.uitour.enabled")) {
       return;
--- a/browser/components/uitour/test/head.js
+++ b/browser/components/uitour/test/head.js
@@ -1,10 +1,16 @@
 "use strict";
 
+// This file spawns a content task.
+/* eslint-env mozilla/frame-script */
+
+// This file expects these globals to be defined by the test case.
+/* global gTestTab:true, gContentAPI:true, gContentWindow:true, tests:false */
+
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "UITour",
                                   "resource:///modules/UITour.jsm");
 
 
 const SINGLE_TRY_TIMEOUT = 100;
 const NUMBER_OF_TRIES = 30;
--- a/browser/config/mozconfigs/macosx64/debug-static-analysis
+++ b/browser/config/mozconfigs/macosx64/debug-static-analysis
@@ -1,29 +1,31 @@
 MOZ_AUTOMATION_BUILD_SYMBOLS=0
 MOZ_AUTOMATION_PACKAGE_TESTS=0
 MOZ_AUTOMATION_L10N_CHECK=0
 
-# The toolchain installed on our OSX 10.7 build machines is too old to support
-# MachO LC_DATA_IN_CODE load command, which newer LLVM generates, so we need to
-# use a newer toolchain that we build.
-#
-# Unfortunately setting $PATH is not enough, because the build system hardcodes
-# the default values for some of the build tools, which we also need to
-# override below.  The default value for host ar and host ranlib is also
-# hardcoded so we need to override those separately.
-CCTOOLS_DIR="$topsrcdir/cctools/bin"
-export PATH="$CCTOOLS_DIR:$PATH"
-export AR="$CCTOOLS_DIR/ar"
-export HOST_AR="$CCTOOLS_DIR/ar"
-export RANLIB="$CCTOOLS_DIR/ranlib"
-export HOST_RANLIB="$CCTOOLS_DIR/ranlib"
-export LIPO="$CCTOOLS_DIR/lipo"
-export OTOOL="$CCTOOLS_DIR/otool"
-export STRIP="$CCTOOLS_DIR/strip"
+if test `uname -s` = Darwin; then
+  # The toolchain installed on our OSX 10.7 build machines is too old to support
+  # MachO LC_DATA_IN_CODE load command, which newer LLVM generates, so we need to
+  # use a newer toolchain that we build.
+  #
+  # Unfortunately setting $PATH is not enough, because the build system hardcodes
+  # the default values for some of the build tools, which we also need to
+  # override below.  The default value for host ar and host ranlib is also
+  # hardcoded so we need to override those separately.
+  CCTOOLS_DIR="$topsrcdir/cctools/bin"
+  export PATH="$CCTOOLS_DIR:$PATH"
+  export AR="$CCTOOLS_DIR/ar"
+  export HOST_AR="$CCTOOLS_DIR/ar"
+  export RANLIB="$CCTOOLS_DIR/ranlib"
+  export HOST_RANLIB="$CCTOOLS_DIR/ranlib"
+  export LIPO="$CCTOOLS_DIR/lipo"
+  export OTOOL="$CCTOOLS_DIR/otool"
+  export STRIP="$CCTOOLS_DIR/strip"
+fi
 
 . $topsrcdir/build/macosx/mozconfig.common
 
 ac_add_options --enable-debug
 ac_add_options --enable-dmd
 
 ac_add_options --enable-clang-plugin
 
--- a/browser/locales/en-US/chrome/browser/preferences/siteDataSettings.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/siteDataSettings.dtd
@@ -2,18 +2,18 @@
    - 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/. -->
 
 <!ENTITY     window.title                  "Settings - Site Data">
 <!ENTITY     settings.description          "The following websites asked to store site data in your disk. You can specify which websites are allowed to store site data. Default site data is temporary and could be deleted automatically.">
 <!ENTITY     hostCol.label                 "Site">
 <!ENTITY     statusCol.label               "Status">
 <!ENTITY     usageCol.label                "Storage">
-<!ENTITY     search.label                  "Search:">
-<!ENTITY     search.accesskey              "S">
+<!ENTITY     searchPlaceHolder             "Search">
+<!ENTITY     searchPlaceHolder.accesskey   "S">
 <!ENTITY     removeSelected.label          "Remove Selected">
 <!ENTITY     removeSelected.accesskey      "r">
 <!ENTITY     save.label                    "Save Changes">
 <!ENTITY     save.accesskey                "a">
 <!ENTITY     cancel.label                  "Cancel">
 <!ENTITY     cancel.accesskey              "C">
 <!ENTITY     removingDialog.title          "Removing Site Data">
 <!ENTITY     removingDialog.description    "Removing site data will also remove cookies. This may log you out of websites and remove offline web content. Are you sure you want to make the changes?">
--- a/browser/modules/DirectoryLinksProvider.jsm
+++ b/browser/modules/DirectoryLinksProvider.jsm
@@ -189,17 +189,18 @@ var DirectoryLinksProvider = {
    */
   get locale() {
     let matchOS;
     try {
       matchOS = Services.prefs.getBoolPref(PREF_MATCH_OS_LOCALE);
     } catch (e) {}
 
     if (matchOS) {
-      return Services.locale.getLocaleComponentForUserAgent();
+      return Cc["@mozilla.org/intl/ospreferences;1"].
+             getService(Ci.mozIOSPreferences).getSystemLocale();
     }
 
     try {
       let locale = Services.prefs.getComplexValue(PREF_SELECTED_LOCALE,
                                                   Ci.nsIPrefLocalizedString);
       if (locale) {
         return locale.data;
       }
--- a/browser/modules/ExtensionsUI.jsm
+++ b/browser/modules/ExtensionsUI.jsm
@@ -136,16 +136,22 @@ this.ExtensionsUI = {
       // there are multiple simultaneous installs happening, see
       // bug 1329884 for a longer explanation.
       let progressNotification = target.ownerGlobal.PopupNotifications.getNotification("addon-progress", target);
       if (progressNotification) {
         progressNotification.remove();
       }
 
       let strings = this._buildStrings(info);
+      // If this is an update with no promptable permissions, just apply it
+      if (info.type == "update" && strings.msgs.length == 0) {
+        info.resolve();
+        return;
+      }
+
       this.showPermissionsPrompt(target, strings, info.icon).then(answer => {
         if (answer) {
           info.resolve();
         } else {
           info.reject();
         }
       });
     } else if (topic == "webextension-update-permissions") {
--- a/browser/modules/ReaderParent.jsm
+++ b/browser/modules/ReaderParent.jsm
@@ -83,17 +83,17 @@ var ReaderParent = {
   updateReaderButton(browser) {
     let win = browser.ownerGlobal;
     if (browser != win.gBrowser.selectedBrowser) {
       return;
     }
 
     let button = win.document.getElementById("reader-mode-button");
     let command = win.document.getElementById("View:ReaderView");
-    let key = win.document.getElementById("toggleReaderMode");
+    let key = win.document.getElementById("key_toggleReaderMode");
     if (browser.currentURI.spec.startsWith("about:reader")) {
       button.setAttribute("readeractive", true);
       button.hidden = false;
       let closeText = gStringBundle.GetStringFromName("readerView.close");
       button.setAttribute("tooltiptext", closeText);
       command.setAttribute("label", closeText);
       command.setAttribute("hidden", false);
       command.setAttribute("accesskey", gStringBundle.GetStringFromName("readerView.close.accesskey"));
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -5,17 +5,16 @@
 # This is not a complete / proper jar manifest. It is included by the
 # actual theme-specific manifests, so that shared resources need only
 # be specified once. As a result, the source file paths are relative
 # to the location of the actual manifest.
 
   skin/classic/browser/aboutNetError.css                       (../shared/aboutNetError.css)
   skin/classic/browser/blockedSite.css                         (../shared/blockedSite.css)
   skin/classic/browser/error-pages.css                         (../shared/error-pages.css)
-  skin/classic/browser/browser.inc.css                         (../shared/browser.inc.css)
 * skin/classic/browser/aboutProviderDirectory.css              (../shared/aboutProviderDirectory.css)
 * skin/classic/browser/aboutSessionRestore.css                 (../shared/aboutSessionRestore.css)
   skin/classic/browser/aboutSocialError.css                    (../shared/aboutSocialError.css)
   skin/classic/browser/aboutTabCrashed.css                     (../shared/aboutTabCrashed.css)
   skin/classic/browser/aboutWelcomeBack.css                    (../shared/aboutWelcomeBack.css)
   skin/classic/browser/content-contextmenu.svg                 (../shared/content-contextmenu.svg)
   skin/classic/browser/addons/addon-install-blocked.svg        (../shared/addons/addon-install-blocked.svg)
   skin/classic/browser/addons/addon-install-confirm.svg        (../shared/addons/addon-install-confirm.svg)
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -104,23 +104,16 @@ toolbar:-moz-lwtheme {
   margin-top: 3px;
 }
 
 #main-window[tabsintitlebar][sizemode="normal"]:not([inFullscreen])[chromehidden~="menubar"] #toolbar-menubar ~ #TabsToolbar,
 #main-window[tabsintitlebar][sizemode="normal"]:not([inFullscreen]) #toolbar-menubar[autohide="true"][inactive] ~ #TabsToolbar {
   margin-top: var(--space-above-tabbar);
 }
 
-#main-window[customize-entered][tabsintitlebar]:not([inFullscreen]) #toolbar-menubar[customizing-dragovertarget].customization-target::before,
-#main-window[customize-entered][tabsintitlebar]:not([inFullscreen]) #TabsToolbar[customizing-dragovertarget].customization-target::before,
-#main-window[customize-entered][tabsintitlebar]:not([inFullscreen]) #toolbar-menubar.customization-target:hover::before,
-#main-window[customize-entered][tabsintitlebar]:not([inFullscreen]) #TabsToolbar.customization-target:hover::before {
-  outline-color: CaptionText;
-}
-
 #navigator-toolbox {
   -moz-appearance: none;
   background-color: transparent;
   border-top: none;
 }
 
 #navigator-toolbox::after {
   content: "";
--- a/devtools/client/aboutdebugging/aboutdebugging.css
+++ b/devtools/client/aboutdebugging/aboutdebugging.css
@@ -38,24 +38,21 @@ button {
 }
 
 .main-content {
   flex: 1;
 }
 
 .panel {
   max-width: 800px;
+  margin-bottom: 35px;
 }
 
 /* Targets */
 
-.targets {
-  margin-bottom: 35px;
-}
-
 .target-list {
   margin: 0;
   padding: 0;
 }
 
 .target-container {
   margin-top: 5px;
   min-height: 34px;
--- a/devtools/client/aboutdebugging/components/addons/panel.js
+++ b/devtools/client/aboutdebugging/components/addons/panel.js
@@ -112,35 +112,49 @@ module.exports = createClass({
    */
   onDisabled() {
     this.updateAddonsList();
   },
 
   render() {
     let { client, id } = this.props;
     let { debugDisabled, extensions: targets } = this.state;
-    let name = Strings.GetStringFromName("extensions");
+    let installedName = Strings.GetStringFromName("extensions");
+    let temporaryName = Strings.GetStringFromName("temporaryExtensions");
     let targetClass = AddonTarget;
 
+    const installedTargets = targets.filter((target) => !target.temporarilyInstalled);
+    const temporaryTargets = targets.filter((target) => target.temporarilyInstalled);
+
     return dom.div({
       id: id + "-panel",
       className: "panel",
       role: "tabpanel",
       "aria-labelledby": id + "-header"
     },
     PanelHeader({
       id: id + "-header",
       name: Strings.GetStringFromName("addons")
     }),
     AddonsControls({ debugDisabled }),
+    dom.div({ id: "temporary-addons" },
+      TargetList({
+        id: "temporary-extensions",
+        name: temporaryName,
+        targets: temporaryTargets,
+        client,
+        debugDisabled,
+        targetClass,
+        sort: true
+      })),
     dom.div({ id: "addons" },
       TargetList({
         id: "extensions",
-        name,
-        targets,
+        name: installedName,
+        targets: installedTargets,
         client,
         debugDisabled,
         targetClass,
         sort: true
       })
     ));
   }
 });
--- a/devtools/client/aboutdebugging/components/addons/target.js
+++ b/devtools/client/aboutdebugging/components/addons/target.js
@@ -53,17 +53,18 @@ module.exports = createClass({
     });
   },
 
   render() {
     let { target, debugDisabled } = this.props;
     // Only temporarily installed add-ons can be reloaded.
     const canBeReloaded = target.temporarilyInstalled;
 
-    return dom.li({ className: "target-container" },
+    return dom.li(
+      { className: "target-container", "data-addon-id": target.addonID },
       dom.img({
         className: "target-icon",
         role: "presentation",
         src: target.icon
       }),
       dom.div({ className: "target" },
         dom.div({ className: "target-name", title: target.name }, target.name)
       ),
--- a/devtools/client/aboutdebugging/test/browser_addons_debug_bootstrapped.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_bootstrapped.js
@@ -28,17 +28,17 @@ add_task(function* () {
   yield waitForInitialAddonList(document);
   yield installAddon({
     document,
     path: "addons/unpacked/install.rdf",
     name: ADDON_NAME,
   });
 
   // Retrieve the DEBUG button for the addon
-  let names = [...document.querySelectorAll("#addons .target-name")];
+  let names = getInstalledAddonNames(document);
   let name = names.filter(element => element.textContent === ADDON_NAME)[0];
   ok(name, "Found the addon in the list");
   let targetElement = name.parentNode.parentNode;
   let debugBtn = targetElement.querySelector(".debug-button");
   ok(debugBtn, "Found its debug button");
 
   // Wait for a notification sent by a script evaluated the test addon via
   // the web console.
--- a/devtools/client/aboutdebugging/test/browser_addons_reload.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_reload.js
@@ -26,17 +26,17 @@ function* tearDownAddon(addon) {
   const onUninstalled = promiseAddonEvent("onUninstalled");
   addon.uninstall();
   const [uninstalledAddon] = yield onUninstalled;
   is(uninstalledAddon.id, addon.id,
      `Add-on was uninstalled: ${uninstalledAddon.id}`);
 }
 
 function getReloadButton(document, addonName) {
-  const names = [...document.querySelectorAll("#addons .target-name")];
+  const names = getInstalledAddonNames(document);
   const name = names.filter(element => element.textContent === addonName)[0];
   ok(name, `Found ${addonName} add-on in the list`);
   const targetElement = name.parentNode.parentNode;
   const reloadButton = targetElement.querySelector(".reload-button");
   info(`Found reload button for ${addonName}`);
   return reloadButton;
 }
 
@@ -152,38 +152,38 @@ add_task(function* reloadButtonRefreshes
         "id": ADDON_ID
       }
     }
   };
 
   const tempExt = new TempWebExt(ADDON_ID);
   tempExt.writeManifest(manifestBase);
 
-  const onAddonListUpdated = waitForMutation(getAddonList(document),
+  const onAddonListUpdated = waitForMutation(getTemporaryAddonList(document),
                                              { childList: true });
   const onInstalled = promiseAddonEvent("onInstalled");
   yield AddonManager.installTemporaryAddon(tempExt.sourceDir);
   const [addon] = yield onInstalled;
   info(`addon installed: ${addon.id}`);
   yield onAddonListUpdated;
 
   const newName = "Temporary web extension (updated)";
   tempExt.writeManifest(Object.assign({}, manifestBase, {name: newName}));
 
   // Wait for the add-on list to be updated with the reloaded name.
   const onReInstall = promiseAddonEvent("onInstalled");
-  const onAddonReloaded = waitForContentMutation(getAddonList(document));
+  const onAddonReloaded = waitForContentMutation(getTemporaryAddonList(document));
 
   const reloadButton = getReloadButton(document, manifestBase.name);
   reloadButton.click();
 
   yield onAddonReloaded;
   const [reloadedAddon] = yield onReInstall;
   // Make sure the name was updated correctly.
-  const allAddons = [...document.querySelectorAll("#addons .target-name")]
+  const allAddons = getInstalledAddonNames(document)
     .map(element => element.textContent);
   const nameWasUpdated = allAddons.some(name => name === newName);
   ok(nameWasUpdated, `New name appeared in reloaded add-ons: ${allAddons}`);
 
   yield tearDownAddon(reloadedAddon);
   tempExt.remove();
   yield closeAboutDebugging(tab);
 });
--- a/devtools/client/aboutdebugging/test/head.js
+++ b/devtools/client/aboutdebugging/test/head.js
@@ -85,16 +85,47 @@ function getSupportsFile(path) {
  * @return {DOMNode}                 target list or container element
  */
 function getAddonList(document) {
   return document.querySelector("#addons .target-list") ||
     document.querySelector("#addons .targets");
 }
 
 /**
+ * Depending on whether there are temporary addons installed, return either a
+ * target list element or its container.
+ * @param  {DOMDocument}  document   #temporary-addons section container document
+ * @return {DOMNode}                 target list or container element
+ */
+function getTemporaryAddonList(document) {
+  return document.querySelector("#temporary-addons .target-list") ||
+    document.querySelector("#temporary-addons .targets");
+}
+
+/**
+ * Depending on whether the addon is installed, return either the addon list
+ * element or throw an Error.
+ * @param  {DOMDocument}  document   addon section container document
+ * @return {DOMNode}                 target list
+ * @throws {Error}                   add-on not found error
+ */
+function getAddonListWithAddon(document, id) {
+  const addon = document.querySelector(`[data-addon-id="${id}"]`);
+  if (!addon) {
+    throw new Error("couldn't find add-on by id");
+  }
+  return addon.closest(".target-list");
+}
+
+function getInstalledAddonNames(document) {
+  const selector = "#addons .target-name, #temporary-addons .target-name";
+  return [...document.querySelectorAll(selector)];
+}
+
+/**
  * Depending on whether there are service workers installed, return either a
  * target list element or its container.
  * @param  {DOMDocument}  document   #service-workers section container document
  * @return {DOMNode}                 target list or container element
  */
 function getServiceWorkerList(document) {
   return document.querySelector("#service-workers .target-list") ||
     document.querySelector("#service-workers.targets");
@@ -113,17 +144,17 @@ function getTabList(document) {
 
 function* installAddon({document, path, name, isWebExtension}) {
   // Mock the file picker to select a test addon
   let MockFilePicker = SpecialPowers.MockFilePicker;
   MockFilePicker.init(window);
   let file = getSupportsFile(path);
   MockFilePicker.setFiles([file.file]);
 
-  let addonList = getAddonList(document);
+  let addonList = getTemporaryAddonList(document);
   let addonListMutation = waitForMutation(addonList, { childList: true });
 
   let onAddonInstalled;
 
   if (isWebExtension) {
     onAddonInstalled = new Promise(done => {
       Management.on("startup", function listener(event, extension) {
         if (extension.name != name) {
@@ -154,17 +185,17 @@ function* installAddon({document, path, 
   yield addonListMutation;
   let names = [...addonList.querySelectorAll(".target-name")];
   names = names.map(element => element.textContent);
   ok(names.includes(name),
     "The addon name appears in the list of addons: " + names);
 }
 
 function* uninstallAddon({document, id, name}) {
-  let addonList = getAddonList(document);
+  let addonList = getAddonListWithAddon(document, id);
   let addonListMutation = waitForMutation(addonList, { childList: true });
 
   // Now uninstall this addon
   yield new Promise(done => {
     AddonManager.getAddonByID(id, addon => {
       let listener = {
         onUninstalled: function (uninstalledAddon) {
           if (uninstalledAddon != addon) {
@@ -335,17 +366,17 @@ function* setupTestAboutDebuggingWebExte
   yield installAddon({
     document,
     path,
     name,
     isWebExtension: true,
   });
 
   // Retrieve the DEBUG button for the addon
-  let names = [...document.querySelectorAll("#addons .target-name")];
+  let names = getInstalledAddonNames(document);
   let nameEl = names.filter(element => element.textContent === name)[0];
   ok(name, "Found the addon in the list");
   let targetElement = nameEl.parentNode.parentNode;
   let debugBtn = targetElement.querySelector(".debug-button");
   ok(debugBtn, "Found its debug button");
 
   return { tab, document, debugBtn };
 }
--- a/devtools/client/inspector/toolsidebar.js
+++ b/devtools/client/inspector/toolsidebar.js
@@ -71,17 +71,16 @@ ToolSidebar.prototype = {
 
   // Rendering
 
   render: function () {
     let Tabbar = this.React.createFactory(this.browserRequire(
       "devtools/client/shared/components/tabs/tabbar"));
 
     let sidebar = Tabbar({
-      toolbox: this._toolPanel._toolbox,
       showAllTabsMenu: true,
       onSelect: this.handleSelectionChange.bind(this),
     });
 
     this._tabbar = this.ReactDOM.render(sidebar, this._tabbox);
   },
 
   /**
--- a/devtools/client/locales/en-US/aboutdebugging.properties
+++ b/devtools/client/locales/en-US/aboutdebugging.properties
@@ -56,16 +56,20 @@ moreInfo = more info
 # This string is displayed as a label of a button that allows the user to
 # load additional add-ons.
 loadTemporaryAddon = Load Temporary Add-on
 
 # LOCALIZATION NOTE (extensions):
 # This string is displayed as a header above the list of loaded add-ons.
 extensions = Extensions
 
+# LOCALIZATION NOTE (temporaryExtensions):
+# This string is displayed as a header above the list of temporarily loaded add-ons.
+temporaryExtensions = Temporary Extensions
+
 # LOCALIZATION NOTE (selectAddonFromFile2):
 # This string is displayed as the title of the file picker that appears when
 # the user clicks the 'Load Temporary Add-on' button
 selectAddonFromFile2 = Select Manifest File or Package (.xpi)
 
 # LOCALIZATION NOTE (reload):
 # This string is displayed as a label of the button that reloads a given addon.
 reload = Reload
--- a/devtools/client/netmonitor/components/monitor-panel.js
+++ b/devtools/client/netmonitor/components/monitor-panel.js
@@ -77,21 +77,23 @@ const MonitorPanel = createClass({
           true,
         );
       });
     }
   },
 
   componentWillUnmount() {
     MediaQueryList.removeListener(this.onLayoutChange);
-    let { clientWidth, clientHeight } = findDOMNode(this.refs.networkDetailsPanel) || {};
+
+    let { clientWidth, clientHeight } = findDOMNode(this.refs.endPanel) || {};
 
     if (this.state.isVerticalSpliter && clientWidth) {
       Prefs.networkDetailsWidth = clientWidth;
-    } else if (clientHeight) {
+    }
+    if (!this.state.isVerticalSpliter && clientHeight) {
       Prefs.networkDetailsHeight = clientHeight;
     }
   },
 
   onLayoutChange() {
     this.setState({
       isVerticalSpliter: MediaQueryList.matches,
     });
@@ -105,21 +107,17 @@ const MonitorPanel = createClass({
         SplitBox({
           className: "devtools-responsive-container",
           initialWidth: `${Prefs.networkDetailsWidth}px`,
           initialHeight: `${Prefs.networkDetailsHeight}px`,
           minSize: "50px",
           maxSize: "80%",
           splitterSize: "1px",
           startPanel: RequestList({ isEmpty }),
-          endPanel: networkDetailsOpen ?
-            NetworkDetailsPanel({
-              ref: "networkDetailsPanel",
-              toolbox: window.NetMonitorController._toolbox,
-            }) : null,
+          endPanel: networkDetailsOpen && NetworkDetailsPanel({ ref: "endPanel" }),
           endPanelControl: true,
           vert: this.state.isVerticalSpliter,
         }),
       )
     );
   }
 });
 
--- a/devtools/client/netmonitor/components/request-list-content.js
+++ b/devtools/client/netmonitor/components/request-list-content.js
@@ -50,20 +50,17 @@ const RequestListContent = createClass({
   },
 
   componentWillMount() {
     const { dispatch } = this.props;
     this.contextMenu = new RequestListContextMenu({
       cloneSelectedRequest: () => dispatch(Actions.cloneSelectedRequest()),
       openStatistics: (open) => dispatch(Actions.openStatistics(open)),
     });
-    this.tooltip = new HTMLTooltip(
-      window.NetMonitorController._toolbox.doc,
-      { type: "arrow" }
-     );
+    this.tooltip = new HTMLTooltip(window.parent.document, { type: "arrow" });
   },
 
   componentDidMount() {
     // Set the CSS variables for waterfall scaling
     this.setScalingStyles();
 
     // Install event handler for displaying a tooltip
     this.tooltip.startTogglingOnHover(this.refs.contentEl, this.onHover, {
--- a/devtools/client/netmonitor/components/toolbar.js
+++ b/devtools/client/netmonitor/components/toolbar.js
@@ -116,17 +116,17 @@ const Toolbar = createClass({
     return (
       span({ className: "devtools-toolbar devtools-toolbar-container" },
         span({ className: "devtools-toolbar-group" },
           button({
             className: "devtools-button devtools-clear-icon requests-list-clear-button",
             title: TOOLBAR_CLEAR,
             onClick: clearRequests,
           }),
-          div({ id: "requests-list-filter-buttons" }, buttons),
+          div({ className: "requests-list-filter-buttons" }, buttons),
         ),
         span({ className: "devtools-toolbar-group" },
           button({
             className: "devtools-button requests-list-network-summary-button",
             title: count ? text : L10N.getStr("netmonitor.toolbar.perf"),
             onClick: openStatistics,
           },
             span({ className: "summary-info-icon" }),
--- a/devtools/client/netmonitor/netmonitor-controller.js
+++ b/devtools/client/netmonitor/netmonitor-controller.js
@@ -2,32 +2,31 @@
  * 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/. */
 
 "use strict";
 
 const { TimelineFront } = require("devtools/shared/fronts/timeline");
 const { CurlUtils } = require("devtools/client/shared/curl");
 const { ACTIVITY_TYPE, EVENTS } = require("./constants");
-const { configureStore } = require("./store");
 const Actions = require("./actions/index");
 const {
   fetchHeaders,
   formDataURI,
 } = require("./utils/request-utils");
 const {
   onFirefoxConnect,
   onFirefoxDisconnect,
 } = require("./utils/client");
 const {
   getRequestById,
   getDisplayedRequestById,
 } = require("./selectors/index");
 
-const gStore = window.gStore = configureStore();
+const gStore = window.gStore;
 
 /**
  * Object defining the network monitor controller components.
  */
 var NetMonitorController = {
   /**
    * Initializes the view and connects the monitor client.
    *
@@ -307,17 +306,17 @@ var NetMonitorController = {
     return this.tabClient &&
            (this.tabClient.traits.reconfigure || !this._target.isApp);
   },
 
   /**
    * Open a given source in Debugger
    */
   viewSourceInDebugger(sourceURL, sourceLine) {
-    return this._toolbox.viewSourceInDebugger(sourceURL, sourceLine);
+    return this.toolbox.viewSourceInDebugger(sourceURL, sourceLine);
   },
 
   /**
    * Start monitoring all incoming update events about network requests and wait until
    * a complete info about all requests is received. (We wait for the timings info
    * explicitly, because that's always the last piece of information that is received.)
    *
    * This method is designed to wait for network requests that are issued during a page
@@ -824,11 +823,12 @@ NetworkEventsHandler.prototype = {
     return this.webConsoleClient.getString(stringGrip);
   }
 };
 
 /**
  * Preliminary setup for the NetMonitorController object.
  */
 NetMonitorController.NetworkEventsHandler = new NetworkEventsHandler();
+window.NetMonitorController = NetMonitorController;
 window.gNetwork = NetMonitorController.NetworkEventsHandler;
 
 exports.NetMonitorController = NetMonitorController;
--- a/devtools/client/netmonitor/netmonitor.js
+++ b/devtools/client/netmonitor/netmonitor.js
@@ -15,38 +15,37 @@ var Netmonitor = {
       window,
       commonLibRequire: toolbox.browserRequire,
     }).require;
 
     const EventEmitter = require("devtools/shared/event-emitter");
     const { createFactory } = require("devtools/client/shared/vendor/react");
     const { render } = require("devtools/client/shared/vendor/react-dom");
     const Provider = createFactory(require("devtools/client/shared/vendor/react-redux").Provider);
+    const { configureStore } = require("./store");
+    const store = window.gStore = configureStore();
+    const { NetMonitorController } = require("./netmonitor-controller");
+    NetMonitorController.toolbox = toolbox;
+    NetMonitorController._target = toolbox.target;
+    this.NetMonitorController = NetMonitorController;
 
     // Components
     const NetworkMonitor = createFactory(require("./components/network-monitor"));
 
     // Inject EventEmitter into netmonitor window.
     EventEmitter.decorate(window);
 
-    window.NetMonitorController = require("./netmonitor-controller").NetMonitorController;
-    window.NetMonitorController._toolbox = toolbox;
-    window.NetMonitorController._target = tabTarget;
-
     this.root = document.querySelector(".root");
 
-    render(Provider(
-      { store: window.gStore },
-      NetworkMonitor(),
-    ), this.root);
+    render(Provider({ store }, NetworkMonitor()), this.root);
 
-    return window.NetMonitorController.startupNetMonitor();
+    return NetMonitorController.startupNetMonitor();
   },
 
   destroy: () => {
     const require = window.windowRequire;
     const { unmountComponentAtNode } = require("devtools/client/shared/vendor/react-dom");
 
     unmountComponentAtNode(this.root);
 
-    return window.NetMonitorController.shutdownNetMonitor();
+    return this.NetMonitorController.shutdownNetMonitor();
   }
 };
--- a/devtools/client/netmonitor/reducers/timing-markers.js
+++ b/devtools/client/netmonitor/reducers/timing-markers.js
@@ -1,33 +1,35 @@
 /* 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/. */
 
 "use strict";
 
 const I = require("devtools/client/shared/vendor/immutable");
-const { ADD_TIMING_MARKER,
-        CLEAR_TIMING_MARKERS,
-        CLEAR_REQUESTS } = require("../constants");
+const {
+  ADD_TIMING_MARKER,
+  CLEAR_TIMING_MARKERS,
+  CLEAR_REQUESTS,
+} = require("../constants");
 
 const TimingMarkers = I.Record({
   firstDocumentDOMContentLoadedTimestamp: -1,
   firstDocumentLoadTimestamp: -1,
 });
 
 function addTimingMarker(state, action) {
-  if (action.marker.name == "document::DOMContentLoaded" &&
-      state.firstDocumentDOMContentLoadedTimestamp == -1) {
+  if (action.marker.name === "document::DOMContentLoaded" &&
+      state.firstDocumentDOMContentLoadedTimestamp === -1) {
     return state.set("firstDocumentDOMContentLoadedTimestamp",
                      action.marker.unixTime / 1000);
   }
 
-  if (action.marker.name == "document::Load" &&
-      state.firstDocumentLoadTimestamp == -1) {
+  if (action.marker.name === "document::Load" &&
+      state.firstDocumentLoadTimestamp === -1) {
     return state.set("firstDocumentLoadTimestamp",
                      action.marker.unixTime / 1000);
   }
 
   return state;
 }
 
 function clearTimingMarkers(state) {
--- a/devtools/client/netmonitor/request-list-context-menu.js
+++ b/devtools/client/netmonitor/request-list-context-menu.js
@@ -174,17 +174,17 @@ RequestListContextMenu.prototype = {
     menu.append(new MenuItem({
       id: "request-list-context-perf",
       label: L10N.getStr("netmonitor.context.perfTools"),
       accesskey: L10N.getStr("netmonitor.context.perfTools.accesskey"),
       visible: !!window.NetMonitorController.supportsPerfStats,
       click: () => this.openStatistics(true)
     }));
 
-    menu.popup(screenX, screenY, window.NetMonitorController._toolbox);
+    menu.popup(screenX, screenY, { doc: window.parent.document });
     return menu;
   },
 
   /**
    * Opens selected item in a new tab.
    */
   openRequestInTab() {
     let win = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
--- a/devtools/client/netmonitor/shared/components/network-details-panel.js
+++ b/devtools/client/netmonitor/shared/components/network-details-panel.js
@@ -22,30 +22,28 @@ const { div } = DOM;
 /*
  * Network details panel component
  */
 function NetworkDetailsPanel({
   activeTabId,
   cloneSelectedRequest,
   request,
   selectTab,
-  toolbox,
 }) {
   if (!request) {
     return null;
   }
 
   return (
     div({ className: "network-details-panel" },
       !request.isCustom ?
         TabboxPanel({
           activeTabId,
           request,
           selectTab,
-          toolbox,
         }) :
         CustomRequestPanel({
           cloneSelectedRequest,
           request,
         })
     )
   );
 }
@@ -53,17 +51,16 @@ function NetworkDetailsPanel({
 NetworkDetailsPanel.displayName = "NetworkDetailsPanel";
 
 NetworkDetailsPanel.propTypes = {
   activeTabId: PropTypes.string,
   cloneSelectedRequest: PropTypes.func.isRequired,
   open: PropTypes.bool,
   request: PropTypes.object,
   selectTab: PropTypes.func.isRequired,
-  toolbox: PropTypes.object.isRequired,
 };
 
 module.exports = connect(
   (state) => ({
     activeTabId: state.ui.detailsPanelSelectedTab,
     request: getSelectedRequest(state),
   }),
   (dispatch) => ({
--- a/devtools/client/netmonitor/shared/components/tabbox-panel.js
+++ b/devtools/client/netmonitor/shared/components/tabbox-panel.js
@@ -37,29 +37,27 @@ const PREVIEW_TITLE = L10N.getStr("netmo
  * Tabbox panel component
  * Display the network request details
  */
 function TabboxPanel({
   activeTabId,
   cloneSelectedRequest,
   request,
   selectTab,
-  toolbox,
 }) {
   if (!request) {
     return null;
   }
 
   return (
     Tabbar({
       activeTabId,
       onSelect: selectTab,
       renderOnlySelected: true,
       showAllTabsMenu: true,
-      toolbox,
     },
       TabPanel({
         id: "headers",
         title: HEADERS_TITLE,
       },
         HeadersPanel({ request, cloneSelectedRequest }),
       ),
       TabPanel({
@@ -106,17 +104,16 @@ function TabboxPanel({
 
 TabboxPanel.displayName = "TabboxPanel";
 
 TabboxPanel.propTypes = {
   activeTabId: PropTypes.string,
   cloneSelectedRequest: PropTypes.func.isRequired,
   request: PropTypes.object,
   selectTab: PropTypes.func.isRequired,
-  toolbox: PropTypes.object.isRequired,
 };
 
 module.exports = connect(
   (state) => ({
     activeTabId: state.ui.detailsPanelSelectedTab,
     request: getSelectedRequest(state),
   }),
   (dispatch) => ({
--- a/devtools/client/netmonitor/test/browser_net_copy_as_curl.js
+++ b/devtools/client/netmonitor/test/browser_net_copy_as_curl.js
@@ -50,19 +50,17 @@ add_task(function* () {
   yield wait;
 
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll(".request-list-item")[0]);
   EventUtils.sendMouseEvent({ type: "contextmenu" },
     document.querySelectorAll(".request-list-item")[0]);
 
   yield waitForClipboardPromise(function setup() {
-    // Context menu is appending in XUL document, we must select it from
-    // toolbox.doc
-    monitor.toolbox.doc
+    monitor.panelWin.parent.document
       .querySelector("#request-list-context-copy-as-curl").click();
   }, function validate(result) {
     if (typeof result !== "string") {
       return false;
     }
 
     // Different setups may produce the same command, but with the
     // parameters in a different order in the commandline (which is fine).
--- a/devtools/client/netmonitor/test/browser_net_copy_headers.js
+++ b/devtools/client/netmonitor/test/browser_net_copy_headers.js
@@ -36,19 +36,17 @@ add_task(function* () {
     "Accept-Encoding: gzip, deflate",
     "Connection: keep-alive",
     "Upgrade-Insecure-Requests: 1",
     "Pragma: no-cache",
     "Cache-Control: no-cache"
   ].join("\n");
 
   yield waitForClipboardPromise(function setup() {
-    // Context menu is appending in XUL document, we must select it from
-    // toolbox.doc
-    monitor.toolbox.doc
+    monitor.panelWin.parent.document
       .querySelector("#request-list-context-copy-request-headers").click();
   }, function validate(result) {
     // Sometimes, a "Cookie" header is left over from other tests. Remove it:
     result = String(result).replace(/Cookie: [^\n]+\n/, "");
     return result === EXPECTED_REQUEST_HEADERS;
   });
   info("Clipboard contains the currently selected item's request headers.");
 
@@ -61,19 +59,17 @@ add_task(function* () {
     "Server: httpd.js",
     "Date: Sun, 3 May 2015 11:11:11 GMT"
   ].join("\n");
 
   EventUtils.sendMouseEvent({ type: "contextmenu" },
     document.querySelectorAll(".request-list-item")[0]);
 
   yield waitForClipboardPromise(function setup() {
-    // Context menu is appending in XUL document, we must select it from
-    // _oolbox.doc
-    monitor.toolbox.doc
+    monitor.panelWin.parent.document
       .querySelector("#response-list-context-copy-response-headers").click();
   }, function validate(result) {
     // Fake the "Last-Modified" and "Date" headers because they will vary:
     result = String(result)
       .replace(/Last-Modified: [^\n]+ GMT/, "Last-Modified: Sun, 3 May 2015 11:11:11 GMT")
       .replace(/Date: [^\n]+ GMT/, "Date: Sun, 3 May 2015 11:11:11 GMT");
     return result === EXPECTED_RESPONSE_HEADERS;
   });
--- a/devtools/client/netmonitor/test/browser_net_copy_image_as_data_uri.js
+++ b/devtools/client/netmonitor/test/browser_net_copy_image_as_data_uri.js
@@ -20,18 +20,16 @@ add_task(function* () {
   yield wait;
 
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll(".request-list-item")[5]);
   EventUtils.sendMouseEvent({ type: "contextmenu" },
     document.querySelectorAll(".request-list-item")[5]);
 
   yield waitForClipboardPromise(function setup() {
-    // Context menu is appending in XUL document, we must select it from
-    // toolbox.doc
-    monitor.toolbox.doc
+    monitor.panelWin.parent.document
       .querySelector("#request-list-context-copy-image-as-data-uri").click();
   }, TEST_IMAGE_DATA_URI);
 
   ok(true, "Clipboard contains the currently selected image as data uri.");
 
   yield teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_copy_params.js
+++ b/devtools/client/netmonitor/test/browser_net_copy_params.js
@@ -57,51 +57,51 @@ add_task(function* () {
 
   return teardown(monitor);
 
   function testCopyUrlParamsHidden(index, hidden) {
     EventUtils.sendMouseEvent({ type: "mousedown" },
       document.querySelectorAll(".request-list-item")[index]);
     EventUtils.sendMouseEvent({ type: "contextmenu" },
       document.querySelectorAll(".request-list-item")[index]);
-    let copyUrlParamsNode = monitor.toolbox.doc
+    let copyUrlParamsNode = monitor.panelWin.parent.document
       .querySelector("#request-list-context-copy-url-params");
     is(!!copyUrlParamsNode, !hidden,
       "The \"Copy URL Parameters\" context menu item should" + (hidden ? " " : " not ") +
         "be hidden.");
   }
 
   function* testCopyUrlParams(index, queryString) {
     EventUtils.sendMouseEvent({ type: "mousedown" },
       document.querySelectorAll(".request-list-item")[index]);
     EventUtils.sendMouseEvent({ type: "contextmenu" },
       document.querySelectorAll(".request-list-item")[index]);
     yield waitForClipboardPromise(function setup() {
-      monitor.toolbox.doc
+      monitor.panelWin.parent.document
         .querySelector("#request-list-context-copy-url-params").click();
     }, queryString);
     ok(true, "The url query string copied from the selected item is correct.");
   }
 
   function testCopyPostDataHidden(index, hidden) {
     EventUtils.sendMouseEvent({ type: "mousedown" },
       document.querySelectorAll(".request-list-item")[index]);
     EventUtils.sendMouseEvent({ type: "contextmenu" },
       document.querySelectorAll(".request-list-item")[index]);
-    let copyPostDataNode = monitor.toolbox.doc
+    let copyPostDataNode = monitor.panelWin.parent.document
       .querySelector("#request-list-context-copy-post-data");
     is(!!copyPostDataNode, !hidden,
       "The \"Copy POST Data\" context menu item should" + (hidden ? " " : " not ") +
         "be hidden.");
   }
 
   function* testCopyPostData(index, postData) {
     EventUtils.sendMouseEvent({ type: "mousedown" },
       document.querySelectorAll(".request-list-item")[index]);
     EventUtils.sendMouseEvent({ type: "contextmenu" },
       document.querySelectorAll(".request-list-item")[index]);
     yield waitForClipboardPromise(function setup() {
-      monitor.toolbox.doc
+      monitor.panelWin.parent.document
         .querySelector("#request-list-context-copy-post-data").click();
     }, postData);
     ok(true, "The post data string copied from the selected item is correct.");
   }
 });
--- a/devtools/client/netmonitor/test/browser_net_copy_response.js
+++ b/devtools/client/netmonitor/test/browser_net_copy_response.js
@@ -22,16 +22,14 @@ add_task(function* () {
   yield wait;
 
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll(".request-list-item")[3]);
   EventUtils.sendMouseEvent({ type: "contextmenu" },
     document.querySelectorAll(".request-list-item")[3]);
 
   yield waitForClipboardPromise(function setup() {
-    // Context menu is appending in XUL document, we must select it from
-    // toolbox.doc
-    monitor.toolbox.doc
+    monitor.panelWin.parent.document
       .querySelector("#request-list-context-copy-response").click();
   }, EXPECTED_RESULT);
 
   yield teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_copy_svg_image_as_data_uri.js
+++ b/devtools/client/netmonitor/test/browser_net_copy_svg_image_as_data_uri.js
@@ -22,18 +22,16 @@ add_task(function* () {
   yield wait;
 
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll(".request-list-item")[0]);
   EventUtils.sendMouseEvent({ type: "contextmenu" },
     document.querySelectorAll(".request-list-item")[0]);
 
   yield waitForClipboardPromise(function setup() {
-    // Context menu is appending in XUL document, we must select it from
-    // toolbox.doc
-    monitor.toolbox.doc
+    monitor.panelWin.parent.document
       .querySelector("#request-list-context-copy-image-as-data-uri").click();
   }, function check(text) {
     return text.startsWith("data:") && !/undefined/.test(text);
   });
 
   yield teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_copy_url.js
+++ b/devtools/client/netmonitor/test/browser_net_copy_url.js
@@ -23,16 +23,14 @@ add_task(function* () {
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll(".request-list-item")[0]);
   EventUtils.sendMouseEvent({ type: "contextmenu" },
     document.querySelectorAll(".request-list-item")[0]);
 
   let requestItem = getSortedRequests(gStore.getState()).get(0);
 
   yield waitForClipboardPromise(function setup() {
-    // Context menu is appending in XUL document, we must select it from
-    // toolbox.doc
-    monitor.toolbox.doc
+    monitor.panelWin.parent.document
       .querySelector("#request-list-context-copy-url").click();
   }, requestItem.url);
 
   yield teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_image-tooltip.js
+++ b/devtools/client/netmonitor/test/browser_net_image-tooltip.js
@@ -11,17 +11,17 @@ const IMAGE_TOOLTIP_REQUESTS = 1;
  */
 add_task(function* test() {
   let { tab, monitor } = yield initNetMonitor(IMAGE_TOOLTIP_URL);
   info("Starting test... ");
 
   let { document, gStore, windowRequire, NetMonitorController } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
   let { ACTIVITY_TYPE, EVENTS } = windowRequire("devtools/client/netmonitor/constants");
-  let toolboxDoc = monitor.toolbox.doc;
+  let toolboxDoc = monitor.panelWin.parent.document;
 
   gStore.dispatch(Actions.batchEnable(false));
 
   let onEvents = waitForNetworkEvents(monitor, IMAGE_TOOLTIP_REQUESTS);
   let onThumbnail = monitor.panelWin.once(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED);
   yield performRequests();
   yield onEvents;
   yield onThumbnail;
--- a/devtools/client/netmonitor/test/browser_net_open_request_in_tab.js
+++ b/devtools/client/netmonitor/test/browser_net_open_request_in_tab.js
@@ -23,19 +23,17 @@ add_task(function* () {
   yield wait;
 
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll(".request-list-item")[0]);
   EventUtils.sendMouseEvent({ type: "contextmenu" },
     document.querySelectorAll(".request-list-item")[0]);
 
   let onTabOpen = once(gBrowser.tabContainer, "TabOpen", false);
-  // Context menu is appending in XUL document, we must select it from
-  // toolbox.doc
-  monitor.toolbox.doc
+  monitor.panelWin.parent.document
     .querySelector("#request-list-context-newtab").click();
   yield onTabOpen;
 
   ok(true, "A new tab has been opened");
 
   yield teardown(monitor);
 
   gBrowser.removeCurrentTab();
--- a/devtools/client/netmonitor/test/head.js
+++ b/devtools/client/netmonitor/test/head.js
@@ -395,17 +395,17 @@ function waitFor(subject, eventName) {
  *
  * @param string filterType
  *        The type of the filter that should be the only one checked.
  */
 function testFilterButtons(monitor, filterType) {
   let doc = monitor.panelWin.document;
   let target = doc.querySelector(".requests-list-filter-" + filterType + "-button");
   ok(target, `Filter button '${filterType}' was found`);
-  let buttons = [...doc.querySelectorAll("#requests-list-filter-buttons button")];
+  let buttons = [...doc.querySelectorAll(".requests-list-filter-buttons button")];
   ok(buttons.length > 0, "More than zero filter buttons were found");
 
   // Only target should be checked.
   let checkStatus = buttons.map(button => button == target ? 1 : 0);
   testFilterButtonsCustom(monitor, checkStatus);
 }
 
 /**
@@ -413,17 +413,17 @@ function testFilterButtons(monitor, filt
  *
  * @param array aIsChecked
  *        An array specifying if a button at given index should have a
  *        'checked' attribute. For example, if the third item of the array
  *        evaluates to true, the third button should be checked.
  */
 function testFilterButtonsCustom(monitor, isChecked) {
   let doc = monitor.panelWin.document;
-  let buttons = doc.querySelectorAll("#requests-list-filter-buttons button");
+  let buttons = doc.querySelectorAll(".requests-list-filter-buttons button");
   for (let i = 0; i < isChecked.length; i++) {
     let button = buttons[i];
     if (isChecked[i]) {
       is(button.classList.contains("checked"), true,
         "The " + button.id + " button should have a 'checked' class.");
       is(button.getAttribute("aria-pressed"), "true",
         "The " + button.id + " button should set 'aria-pressed' = true.");
     } else {
--- a/devtools/client/netmonitor/waterfall-background.js
+++ b/devtools/client/netmonitor/waterfall-background.js
@@ -23,35 +23,34 @@ const STATE_KEYS = [
   "waterfallWidth",
   "firstRequestStartedMillis",
   "timingMarkers",
 ];
 
 /**
  * Creates the background displayed on each waterfall view in this container.
  */
-function WaterfallBackground(document) {
-  this.document = document;
+function WaterfallBackground() {
   this.canvas = document.createElementNS(HTML_NS, "canvas");
   this.ctx = this.canvas.getContext("2d");
   this.prevState = {};
 }
 
 WaterfallBackground.prototype = {
   draw(state) {
     // Do a shallow compare of the previous and the new state
     const shouldUpdate = STATE_KEYS.some(key => this.prevState[key] !== state[key]);
     if (!shouldUpdate) {
       return;
     }
 
     this.prevState = state;
 
-    if (state.waterfallWidth == null || state.scale == null) {
-      this.document.mozSetImageElement("waterfall-background", null);
+    if (state.waterfallWidth === null || state.scale === null) {
+      document.mozSetImageElement("waterfall-background", null);
       return;
     }
 
     // Nuke the context.
     let canvasWidth = this.canvas.width = state.waterfallWidth;
     // Awww yeah, 1px, repeats on Y axis.
     let canvasHeight = this.canvas.height = 1;
 
@@ -73,17 +72,17 @@ WaterfallBackground.prototype = {
       scaledStep = state.scale * timingStep;
       if (scaledStep < REQUESTS_WATERFALL_BACKGROUND_TICKS_SPACING_MIN) {
         timingStep <<= 1;
         continue;
       }
       optimalTickIntervalFound = true;
     }
 
-    const isRTL = isDocumentRTL(this.document);
+    const isRTL = isDocumentRTL(document);
     const [r, g, b] = REQUESTS_WATERFALL_BACKGROUND_TICKS_COLOR_RGB;
     let alphaComponent = REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_MIN;
 
     function drawPixelAt(offset, color) {
       let position = (isRTL ? canvasWidth - offset : offset - 1) | 0;
       let [rc, gc, bc, ac] = color;
       view32bit[position] = (ac << 24) | (bc << 16) | (gc << 8) | rc;
     }
@@ -93,17 +92,17 @@ WaterfallBackground.prototype = {
       let increment = scaledStep * Math.pow(2, i);
       for (let x = 0; x < canvasWidth; x += increment) {
         drawPixelAt(x, [r, g, b, alphaComponent]);
       }
       alphaComponent += REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_ADD;
     }
 
     function drawTimestamp(timestamp, color) {
-      if (timestamp == -1) {
+      if (timestamp === -1) {
         return;
       }
 
       let delta = Math.floor((timestamp - state.firstRequestStartedMillis) * state.scale);
       drawPixelAt(delta, color);
     }
 
     drawTimestamp(state.timingMarkers.firstDocumentDOMContentLoadedTimestamp,
@@ -111,21 +110,21 @@ WaterfallBackground.prototype = {
 
     drawTimestamp(state.timingMarkers.firstDocumentLoadTimestamp,
                   REQUESTS_WATERFALL_LOAD_TICKS_COLOR_RGBA);
 
     // Flush the image data and cache the waterfall background.
     pixelArray.set(view8bit);
     this.ctx.putImageData(imageData, 0, 0);
 
-    this.document.mozSetImageElement("waterfall-background", this.canvas);
+    document.mozSetImageElement("waterfall-background", this.canvas);
   },
 
   destroy() {
-    this.document.mozSetImageElement("waterfall-background", null);
+    document.mozSetImageElement("waterfall-background", null);
   }
 };
 
 /**
  * Returns true if this is document is in RTL mode.
  * @return boolean
  */
 function isDocumentRTL(doc) {
--- a/devtools/client/responsive.html/components/global-toolbar.js
+++ b/devtools/client/responsive.html/components/global-toolbar.js
@@ -45,17 +45,17 @@ module.exports = createClass({
       onChangePixelRatio,
       onChangeTouchSimulation,
       onExit,
       onScreenshot,
     } = this.props;
 
     let touchButtonClass = "toolbar-button devtools-button";
     if (touchSimulation.enabled) {
-      touchButtonClass += " active";
+      touchButtonClass += " checked";
     }
 
     return dom.header(
       {
         id: "global-toolbar",
         className: "container",
       },
       dom.span(
deleted file mode 100644
--- a/devtools/client/responsive.html/images/close.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="#0b0b0b">
-  <path d="M6.7 8l3.6-3.6c.2-.2.2-.5 0-.7-.2-.2-.5-.2-.7 0L6 7.3 2.4 3.7c-.2-.2-.5-.2-.7 0-.2.2-.2.5 0 .7L5.3 8l-3.6 3.6c-.2.2-.2.5 0 .7.2.2.5.2.7 0L6 8.7l3.6 3.6c.2.2.5.2.7 0 .2-.2.2-.5 0-.7L6.7 8z"/>
-</svg>
--- a/devtools/client/responsive.html/images/moz.build
+++ b/devtools/client/responsive.html/images/moz.build
@@ -1,14 +1,13 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
-    'close.svg',
     'grippers.svg',
     'rotate-viewport.svg',
     'screenshot.svg',
     'select-arrow.svg',
     'touch-events.svg',
 )
--- a/devtools/client/responsive.html/index.css
+++ b/devtools/client/responsive.html/index.css
@@ -67,27 +67,29 @@ body,
  */
 
 .container {
   background-color: var(--theme-toolbar-background);
   border: 1px solid var(--theme-splitter-color);
 }
 
 .toolbar-button {
-  margin: 1px 3px;
-  width: 16px;
-  height: 16px;
-  /* Reset styles from .devtools-button */
-  min-width: initial;
-  min-height: initial;
-  align-self: center;
+  margin: 0;
+  padding: 0;
+  border: none;
+}
+
+.toolbar-button:empty:hover:not(:disabled),
+.toolbar-button:empty:-moz-any(:hover:active, .checked):not(:disabled) {
+  /* Reset background from .devtools-button */
+  background: none;
 }
 
 .toolbar-button:active::before {
-  filter: url("chrome://devtools/skin/images/filters.svg#checked-icon-state");
+  filter: var(--checked-icon-filter);
 }
 
 select {
   -moz-appearance: none;
   background-color: var(--theme-toolbar-background);
   background-image: var(--viewport-selection-arrow);
   background-position: 100% 50%;
   background-repeat: no-repeat;
@@ -150,48 +152,35 @@ select > option.divider {
   -moz-user-select: none;
 }
 
 #global-toolbar > .title {
   border-right: 1px solid var(--theme-splitter-color);
   padding: 1px 6px 0 2px;
 }
 
-#global-toolbar .toolbar-button {
-  margin: 0 0 0 5px;
-  padding: 0;
-}
-
-#global-toolbar .toolbar-button,
-#global-toolbar .toolbar-button::before {
+#global-toolbar > .toolbar-button::before {
   width: 12px;
   height: 12px;
 }
 
 #global-touch-simulation-button::before {
   background-image: url("./images/touch-events.svg");
-  margin: -6px 0 0 -6px;
-}
-
-#global-touch-simulation-button.active::before {
-  filter: url("chrome://devtools/skin/images/filters.svg#checked-icon-state");
 }
 
 #global-screenshot-button::before {
   background-image: url("./images/screenshot.svg");
-  margin: -6px 0 0 -6px;
 }
 
 #global-exit-button::before {
-  background-image: url("./images/close.svg");
-  margin: -6px 0 0 -6px;
+  background-image: url("chrome://devtools/skin/images/close.svg");
 }
 
 #global-screenshot-button:disabled {
-  filter: url("chrome://devtools/skin/images/filters.svg#checked-icon-state");
+  filter: var(--checked-icon-filter);
   opacity: 1 !important;
 }
 
 #global-network-throttling-selector {
   height: 15px;
   padding-left: 0;
   width: 103px;
 }
@@ -264,17 +253,17 @@ select > option.divider {
  */
 
 .viewport-toolbar {
   border-width: 0;
   border-bottom-width: 1px;
   display: flex;
   flex-direction: row;
   justify-content: center;
-  height: 18px;
+  height: 16px;
 }
 
 .viewport-rotate-button {
   position: absolute;
   right: 0;
 }
 
 .viewport-rotate-button::before {
@@ -474,17 +463,17 @@ select > option.divider {
   position: absolute;
   top: 5px;
   right: 2px;
   width: 12px;
   height: 12px;
 }
 
 #device-close-button::before {
-  background-image: url("./images/close.svg");
+  background-image: url("chrome://devtools/skin/images/close.svg");
   margin: -6px 0 0 -6px;
 }
 
 .device-type {
   display: flex;
   flex-direction: column;
   padding: 10px;
 }
@@ -507,23 +496,23 @@ select > option.divider {
   margin-right: 5px;
 }
 
 .device-name {
   flex: 1;
 }
 
 .device-remove-button,
-.device-remove-button::before {
+.device-remove-button:empty::before {
   width: 12px;
   height: 12px;
 }
 
-.device-remove-button::before {
-  background-image: url("./images/close.svg");
+.device-remove-button:empty::before {
+  background-image: url("chrome://devtools/skin/images/close.svg");
   margin: -6px 0 0 -6px;
 }
 
 #device-submit-button {
   background-color: var(--theme-tab-toolbar-background);
   border-width: 1px 0 0 0;
   border-top-width: 1px;
   border-top-style: solid;
--- a/devtools/client/responsive.html/test/browser/browser_touch_simulation.js
+++ b/devtools/client/responsive.html/test/browser/browser_touch_simulation.js
@@ -166,27 +166,27 @@ function* testWithMetaViewportDisabled(u
       "300ms delay between touch events and mouse events should work");
   });
 }
 
 function testTouchButton(ui) {
   let { document } = ui.toolWindow;
   let touchButton = document.querySelector("#global-touch-simulation-button");
 
-  ok(touchButton.classList.contains("active"),
+  ok(touchButton.classList.contains("checked"),
     "Touch simulation is active at end of test.");
 
   touchButton.click();
 
-  ok(!touchButton.classList.contains("active"),
+  ok(!touchButton.classList.contains("checked"),
     "Touch simulation is stopped on click.");
 
   touchButton.click();
 
-  ok(touchButton.classList.contains("active"),
+  ok(touchButton.classList.contains("checked"),
     "Touch simulation is started on click.");
 }
 
 function* waitBootstrap(ui) {
   let { store } = ui.toolWindow;
 
   yield waitUntilState(store, state => state.viewports.length == 1);
   yield waitForFrameLoad(ui, TEST_URL);
--- a/devtools/client/responsive.html/test/browser/head.js
+++ b/devtools/client/responsive.html/test/browser/head.js
@@ -366,17 +366,17 @@ function waitForClientClose(ui) {
 
 function* testTouchEventsOverride(ui, expected) {
   let { document } = ui.toolWindow;
   let touchButton = document.querySelector("#global-touch-simulation-button");
 
   let flag = yield ui.emulationFront.getTouchEventsOverride();
   is(flag === Ci.nsIDocShell.TOUCHEVENTS_OVERRIDE_ENABLED, expected,
     `Touch events override should be ${expected ? "enabled" : "disabled"}`);
-  is(touchButton.classList.contains("active"), expected,
+  is(touchButton.classList.contains("checked"), expected,
     `Touch simulation button should be ${expected ? "" : "in"}active.`);
 }
 
 function testViewportDeviceSelectLabel(ui, expected) {
   info("Test viewport's device select label");
 
   let select = ui.toolWindow.document.querySelector(".viewport-device-selector");
   is(select.selectedOptions[0].textContent, expected,
--- a/devtools/client/shared/components/h-split-box.js
+++ b/devtools/client/shared/components/h-split-box.js
@@ -102,17 +102,19 @@ module.exports = createClass({
   _onMouseMove(event) {
     if (!this.state.mouseDown) {
       return;
     }
 
     const rect = this.refs.box.getBoundingClientRect();
     const { left, right } = rect;
     const width = right - left;
-    const relative = event.clientX - left;
+    const direction = this.refs.box.ownerDocument.dir;
+    const relative = direction == "rtl" ? right - event.clientX
+                                        : event.clientX - left;
     this.props.onResize(relative / width);
 
     event.preventDefault();
   },
 
   render() {
     /* eslint-disable no-shadow */
     const { start, end, startWidth, minStartWidth, minEndWidth } = this.props;
--- a/devtools/client/shared/components/tabs/tabbar.js
+++ b/devtools/client/shared/components/tabs/tabbar.js
@@ -1,14 +1,16 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=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/. */
 
+/* eslint-env browser */
+
 "use strict";
 
 const { DOM, createClass, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
 const Tabs = createFactory(require("devtools/client/shared/components/tabs/tabs").Tabs);
 
 const Menu = require("devtools/client/framework/menu");
 const MenuItem = require("devtools/client/framework/menu-item");
 
@@ -21,17 +23,16 @@ const { div } = DOM;
 let Tabbar = createClass({
   displayName: "Tabbar",
 
   propTypes: {
     children: PropTypes.array,
     onSelect: PropTypes.func,
     showAllTabsMenu: PropTypes.bool,
     activeTabId: PropTypes.string,
-    toolbox: PropTypes.object,
     renderOnlySelected: PropTypes.bool,
   },
 
   getDefaultProps: function () {
     return {
       showAllTabsMenu: false,
     };
   },
@@ -190,17 +191,18 @@ let Tabbar = createClass({
     // Show a drop down menu with frames.
     // XXX Missing menu API for specifying target (anchor)
     // and relative position to it. See also:
     // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/Method/openPopup
     // https://bugzilla.mozilla.org/show_bug.cgi?id=1274551
     let rect = target.getBoundingClientRect();
     let screenX = target.ownerDocument.defaultView.mozInnerScreenX;
     let screenY = target.ownerDocument.defaultView.mozInnerScreenY;
-    menu.popup(rect.left + screenX, rect.bottom + screenY, this.props.toolbox);
+    menu.popup(rect.left + screenX, rect.bottom + screenY,
+      { doc: window.parent.document });
 
     return menu;
   },
 
   // Rendering
 
   renderTab: function (tab) {
     if (typeof tab.panel === "function") {
--- a/devtools/client/shared/developer-toolbar.js
+++ b/devtools/client/shared/developer-toolbar.js
@@ -145,16 +145,26 @@ loader.lazyGetter(this, "isMac", functio
  * @param chromeWindow The browser window to which this toolbar is attached
  */
 function DeveloperToolbar(chromeWindow) {
   this._chromeWindow = chromeWindow;
 
   // Will be setup when show() is called
   this.target = null;
 
+  // The `_showPromise` will be set once `show` is called the first time, and resolved
+  // when the toolbar is shown. Since it will be set to `null` only when `hide` method
+  // is called, multiple calls to `show` method will returns this promise instead of
+  // process again all the initialization.
+  this._showPromise = null;
+  // The `_hidePromise` will be set once `hide` is called, and resolved when the method
+  // has finished. Once the toolbar is hidden, both `_showPromise` and `_hidePromise`
+  // will be set to `null`.
+  this._hidePromise = null;
+
   this._doc = chromeWindow.document;
 
   this._telemetry = new Telemetry();
   this._errorsCount = {};
   this._warningsCount = {};
   this._errorListeners = {};
 
   this._onToolboxReady = this._onToolboxReady.bind(this);
@@ -283,23 +293,45 @@ DeveloperToolbar.prototype.toggle = func
   if (this.visible) {
     return this.hide().catch(console.error);
   }
   return this.show(true).catch(console.error);
 };
 
 /**
  * Called from browser.xul in response to menu-click or keyboard shortcut to
- * toggle the toolbar
+ * toggle the toolbar.
+ * The method returns a promise that would be resolved once focused; if the toolbar is not
+ * visible yet it will be automatically shown.
  */
 DeveloperToolbar.prototype.focus = function () {
   if (this.visible) {
-    this._input.focus();
-    return promise.resolve();
+    // If the toolbar was just inserted, the <textbox> may still have
+    // its binding in process of being applied and not be focusable yet
+    let waitForBinding = defer();
+
+    let checkBinding = () => {
+      // Bail out if the toolbar has been destroyed in the meantime
+      if (!this._input) {
+        waitForBinding.reject();
+        return;
+      }
+      // mInputField is a xbl field of <xul:textbox>
+      if (typeof this._input.mInputField != "undefined") {
+        this._input.focus();
+        waitForBinding.resolve();
+      } else {
+        this._input.ownerDocument.defaultView.setTimeout(checkBinding, 50);
+      }
+    };
+    checkBinding();
+
+    return waitForBinding.promise;
   }
+
   return this.show(true);
 };
 
 /**
  * Called from browser.xul in response to menu-click or keyboard shortcut to
  * toggle the toolbar
  */
 DeveloperToolbar.prototype.focusToggle = function () {
@@ -325,17 +357,22 @@ DeveloperToolbar.prototype.focusToggle =
  * same as this.DeveloperToolbar when in browser.js context.
  */
 DeveloperToolbar.introShownThisSession = false;
 
 /**
  * Show the developer toolbar
  */
 DeveloperToolbar.prototype.show = function (focus) {
-  if (this._showPromise != null) {
+  // if `_showPromise` is set, just returns it instead of process all the initialization
+  // again; ensuring we're focusing the element too if `focus` argument is set to `true`.
+  if (this._showPromise !== null) {
+    if (focus) {
+      return this.focus();
+    }
     return this._showPromise;
   }
 
   this._showPromise = Task.spawn((function* () {
     // hide() is async, so ensure we don't need to wait for hide() to
     // finish.  We unconditionally yield here, even if _hidePromise is
     // null, so that the spawn call returns a promise before starting
     // to do any real work.
@@ -425,73 +462,62 @@ DeveloperToolbar.prototype.show = functi
     gDevTools.on("toolbox-ready", this._onToolboxReady);
     gDevTools.on("toolbox-destroyed", this._onToolboxDestroyed);
 
     this._initErrorsCount(tabbrowser.selectedTab);
 
     this._element.hidden = false;
 
     if (focus) {
-      // If the toolbar was just inserted, the <textbox> may still have
-      // its binding in process of being applied and not be focusable yet
-      let waitForBinding = () => {
-        // Bail out if the toolbar has been destroyed in the meantime
-        if (!this._input) {
-          return;
-        }
-        // mInputField is a xbl field of <xul:textbox>
-        if (typeof this._input.mInputField != "undefined") {
-          this._input.focus();
-          this._notify(NOTIFICATIONS.SHOW);
-        } else {
-          this._input.ownerDocument.defaultView.setTimeout(waitForBinding, 50);
-        }
-      };
-      waitForBinding();
-    } else {
-      this._notify(NOTIFICATIONS.SHOW);
+      yield this.focus();
     }
+    this._notify(NOTIFICATIONS.SHOW);
 
     if (!DeveloperToolbar.introShownThisSession) {
       let intro = require("gcli/ui/intro");
       intro.maybeShowIntro(this.requisition.commandOutputManager,
                            this.requisition.conversionContext,
                            this.outputPanel);
       DeveloperToolbar.introShownThisSession = true;
     }
-
-    this._showPromise = null;
   }).bind(this));
 
   return this._showPromise;
 };
 
 /**
  * Hide the developer toolbar.
  */
 DeveloperToolbar.prototype.hide = function () {
-  // If we're already in the process of hiding, just use the other promise
-  if (this._hidePromise != null) {
+  // If we're already in the process of hiding, just returns the promise
+  if (this._hidePromise !== null) {
     return this._hidePromise;
   }
 
+  // If `_showPromise` is `null`, it means `show` method was never called, so just
+  // returns a resolved promise.
+  if (this._showPromise === null) {
+    return promise.resolve();
+  }
+
   // show() is async, so ensure we don't need to wait for show() to finish
-  let waitPromise = this._showPromise || promise.resolve();
-
-  this._hidePromise = waitPromise.then(() => {
+  this._hidePromise = this._showPromise.then(() => {
     this._element.hidden = true;
 
     Services.prefs.setBoolPref("devtools.toolbar.visible", false);
 
     this._doc.getElementById("menu_devToolbar").setAttribute("checked", "false");
     this.destroy();
 
     this._telemetry.toolClosed("developertoolbar");
     this._notify(NOTIFICATIONS.HIDE);
 
+    // The developer toolbar is now closed, is neither shown or in process of hiding,
+    // so we're set to `null` both `_showPromise` and `_hidePromise`.
+    this._showPromise = null;
     this._hidePromise = null;
   });
 
   return this._hidePromise;
 };
 
 /**
  * Initialize the listeners needed for tracking the number of errors for a given
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -7701,27 +7701,16 @@ nsDocShell::EndPageLoad(nsIWebProgress* 
   //
   if (url && NS_FAILED(aStatus)) {
     if (aStatus == NS_ERROR_FILE_NOT_FOUND ||
         aStatus == NS_ERROR_FILE_ACCESS_DENIED ||
         aStatus == NS_ERROR_CORRUPTED_CONTENT ||
         aStatus == NS_ERROR_INVALID_CONTENT_ENCODING) {
       DisplayLoadError(aStatus, url, nullptr, aChannel);
       return NS_OK;
-    } else if (aStatus == NS_ERROR_INVALID_SIGNATURE) {
-      // NS_ERROR_INVALID_SIGNATURE indicates a content-signature error.
-      // This currently only happens in case a remote about page fails.
-      // We have to load a fallback in this case.
-      // XXX: We always load about blank here, firefox has to overwrite this if
-      // it wants to display something else.
-      return LoadURI(u"about:blank",            // URI string
-                     nsIChannel::LOAD_NORMAL,   // Load flags
-                     nullptr,                   // Referring URI
-                     nullptr,                   // Post data stream
-                     nullptr);                  // Headers stream
     }
 
     // Handle iframe document not loading error because source was
     // a tracking URL. We make a note of this iframe node by including
     // it in a dedicated array of blocked tracking nodes under its parent
     // document. (document of parent window of blocked document)
     if (isTopFrame == false && aStatus == NS_ERROR_TRACKING_URI) {
       // frameElement is our nsIContent to be annotated
@@ -9745,37 +9734,16 @@ nsDocShell::CreatePrincipalFromReferrer(
   attrs.Inherit(mOriginAttributes);
   nsCOMPtr<nsIPrincipal> prin =
     BasePrincipal::CreateCodebasePrincipal(aReferrer, attrs);
   prin.forget(aResult);
 
   return *aResult ? NS_OK : NS_ERROR_FAILURE;
 }
 
-bool
-nsDocShell::IsAboutNewtab(nsIURI* aURI)
-{
-  if (!aURI) {
-    return false;
-  }
-  bool isAbout;
-  if (NS_WARN_IF(NS_FAILED(aURI->SchemeIs("about", &isAbout)))) {
-    return false;
-  }
-  if (!isAbout) {
-    return false;
-  }
-
-  nsAutoCString module;
-  if (NS_WARN_IF(NS_FAILED(NS_GetAboutModuleName(aURI, module)))) {
-    return false;
-  }
-  return module.Equals("newtab");
-}
-
 NS_IMETHODIMP
 nsDocShell::InternalLoad(nsIURI* aURI,
                          nsIURI* aOriginalURI,
                          bool aLoadReplace,
                          nsIURI* aReferrer,
                          uint32_t aReferrerPolicy,
                          nsIPrincipal* aTriggeringPrincipal,
                          nsIPrincipal* aPrincipalToInherit,
@@ -10601,26 +10569,18 @@ nsDocShell::InternalLoad(nsIURI* aURI,
   if (mTiming && timeBeforeUnload) {
     mTiming->NotifyUnloadAccepted(mCurrentURI);
   }
 
   // Check if the webbrowser chrome wants the load to proceed; this can be
   // used to cancel attempts to load URIs in the wrong process.
   nsCOMPtr<nsIWebBrowserChrome3> browserChrome3 = do_GetInterface(mTreeOwner);
   if (browserChrome3) {
-    // In case this is a remote newtab load, set aURI to aOriginalURI (newtab).
-    // This ensures that the verifySignedContent flag is set on loadInfo in
-    // DoURILoad.
-    nsIURI* uriForShouldLoadCheck = aURI;
-    if (IsAboutNewtab(aOriginalURI)) {
-      uriForShouldLoadCheck = aOriginalURI;
-    }
     bool shouldLoad;
-    rv = browserChrome3->ShouldLoadURI(this, uriForShouldLoadCheck, aReferrer,
-                                       aTriggeringPrincipal, &shouldLoad);
+    rv = browserChrome3->ShouldLoadURI(this, aURI, aReferrer, aTriggeringPrincipal, &shouldLoad);
     if (NS_SUCCEEDED(rv) && !shouldLoad) {
       return NS_OK;
     }
   }
 
   if (browserChrome3 && aCheckForPrerender) {
     nsCOMPtr<nsIRunnable> ev =
       new InternalLoadEvent(this, aURI, aOriginalURI, aLoadReplace,
@@ -11277,25 +11237,16 @@ nsDocShell::DoURILoad(nsIURI* aURI,
     if (aHeadersData) {
       rv = AddHeadersToChannel(aHeadersData, httpChannel);
     }
     // Set the referrer explicitly
     if (aReferrerURI && aSendReferrer) {
       // Referrer is currenly only set for link clicks here.
       httpChannel->SetReferrerWithPolicy(aReferrerURI, aReferrerPolicy);
     }
-    // set Content-Signature enforcing bit if aOriginalURI == about:newtab
-    if (aOriginalURI && httpChannel) {
-      if (IsAboutNewtab(aOriginalURI)) {
-        nsCOMPtr<nsILoadInfo> loadInfo = httpChannel->GetLoadInfo();
-        if (loadInfo) {
-          loadInfo->SetVerifySignedContent(true);
-        }
-      }
-    }
   }
 
   nsCOMPtr<nsIScriptChannel> scriptChannel = do_QueryInterface(channel);
   if (scriptChannel) {
     // Allow execution against our context if the principals match
     scriptChannel->SetExecutionPolicy(nsIScriptChannel::EXECUTE_NORMAL);
   }
 
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -733,19 +733,16 @@ protected:
 
   nsIChannel* GetCurrentDocChannel();
 
   bool ShouldBlockLoadingForBackButton();
 
   // Convenience method for getting our parent docshell. Can return null
   already_AddRefed<nsDocShell> GetParentDocshell();
 
-  // Check if aURI is about:newtab.
-  bool IsAboutNewtab(nsIURI* aURI);
-
 protected:
   nsresult GetCurScrollPos(int32_t aScrollOrientation, int32_t* aCurPos);
   nsresult SetCurScrollPosEx(int32_t aCurHorizontalPos,
                              int32_t aCurVerticalPos);
 
   // Override the parent setter from nsDocLoader
   virtual nsresult SetDocLoaderParent(nsDocLoader* aLoader) override;
 
--- a/dom/animation/KeyframeEffectReadOnly.cpp
+++ b/dom/animation/KeyframeEffectReadOnly.cpp
@@ -532,18 +532,17 @@ KeyframeEffectReadOnly::ComposeStyle(
   mCurrentIterationOnLastCompose = computedTiming.mCurrentIteration;
 
   // If the progress is null, we don't have fill data for the current
   // time so we shouldn't animate.
   if (computedTiming.mProgress.IsNull()) {
     return;
   }
 
-  nsPresContext* presContext = GetPresContext();
-  bool isServoBackend = presContext && presContext->StyleSet()->IsServo();
+  bool isServoBackend = mDocument->IsStyledByServo();
 
   for (size_t propIdx = 0, propEnd = mProperties.Length();
        propIdx != propEnd; ++propIdx)
   {
     const AnimationProperty& prop = mProperties[propIdx];
 
     MOZ_ASSERT(prop.mSegments[0].mFromKey == 0.0, "incorrect first from key");
     MOZ_ASSERT(prop.mSegments[prop.mSegments.Length() - 1].mToKey == 1.0,
@@ -1613,17 +1612,17 @@ CreateStyleContextForAnimationValue(nsCS
 
   return styleContext.forget();
 }
 
 void
 KeyframeEffectReadOnly::CalculateCumulativeChangeHint(
   nsStyleContext *aStyleContext)
 {
-  if (aStyleContext->PresContext()->StyleSet()->IsServo()) {
+  if (mDocument->IsStyledByServo()) {
     // FIXME (bug 1303235): Do this for Servo too
     return;
   }
   mCumulativeChangeHint = nsChangeHint(0);
 
   for (const AnimationProperty& property : mProperties) {
     for (const AnimationPropertySegment& segment : property.mSegments) {
       // In case composite operation is not 'replace', we can't throttle
@@ -1687,18 +1686,17 @@ bool
 KeyframeEffectReadOnly::CanIgnoreIfNotVisible() const
 {
   if (!AnimationUtils::IsOffscreenThrottlingEnabled()) {
     return false;
   }
 
   // FIXME (bug 1303235): We don't calculate mCumulativeChangeHint for
   // the Servo backend yet
-  nsPresContext* presContext = GetPresContext();
-  if (!presContext || presContext->StyleSet()->IsServo()) {
+  if (mDocument->IsStyledByServo()) {
     return false;
   }
 
   // FIXME: For further sophisticated optimization we need to check
   // change hint on the segment corresponding to computedTiming.progress.
   return NS_IsHintSubset(
     mCumulativeChangeHint, nsChangeHint_Hints_CanIgnoreIfNotVisible);
 }
--- a/dom/audiochannel/AudioChannelService.cpp
+++ b/dom/audiochannel/AudioChannelService.cpp
@@ -198,23 +198,16 @@ AudioChannelService::CreateServiceIfNeed
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!gAudioChannelService) {
     gAudioChannelService = new AudioChannelService();
   }
 }
 
-/* static */ bool
-AudioChannelService::IsServiceStarted()
-{
-  // The service would start when the first AudioChannelAgent is created.
-  return !!gAudioChannelService;
-}
-
 /* static */ already_AddRefed<AudioChannelService>
 AudioChannelService::GetOrCreate()
 {
   if (sXPCOMShuttingDown) {
     return nullptr;
   }
 
   CreateServiceIfNeeded();
--- a/dom/audiochannel/AudioChannelService.h
+++ b/dom/audiochannel/AudioChannelService.h
@@ -101,18 +101,16 @@ public:
   static already_AddRefed<AudioChannelService> Get();
 
   static bool IsAudioChannelMutedByDefault();
 
   static PRLogModuleInfo* GetAudioChannelLog();
 
   static bool IsEnableAudioCompeting();
 
-  static bool IsServiceStarted();
-
   /**
    * Any audio channel agent that starts playing should register itself to
    * this service, sharing the AudioChannel.
    */
   void RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
                                  AudibleState aAudible);
 
   /**
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -42,16 +42,17 @@
 #include "mozilla/dom/FlyWebService.h"
 #include "mozilla/dom/Permissions.h"
 #include "mozilla/dom/Presentation.h"
 #include "mozilla/dom/ServiceWorkerContainer.h"
 #include "mozilla/dom/StorageManager.h"
 #include "mozilla/dom/TCPSocket.h"
 #include "mozilla/dom/URLSearchParams.h"
 #include "mozilla/dom/VRDisplay.h"
+#include "mozilla/dom/VRServiceTest.h"
 #include "mozilla/dom/WebAuthentication.h"
 #include "mozilla/dom/workers/RuntimeService.h"
 #include "mozilla/Hal.h"
 #include "nsISiteSpecificUserAgent.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/SSE.h"
 #include "mozilla/StaticPtr.h"
 #include "Connection.h"
@@ -217,16 +218,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServiceWorkerContainer)
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeySystemAccessManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDeviceStorageAreaListener)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPresentation)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGamepadServiceTest)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVRGetDisplaysPromises)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVRServiceTest)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(Navigator)
 
 void
 Navigator::Invalidate()
 {
   // Don't clear mWindow here so we know we've got a non-null mWindow
@@ -308,16 +310,21 @@ Navigator::Invalidate()
   }
 
   if (mGamepadServiceTest) {
     mGamepadServiceTest->Shutdown();
     mGamepadServiceTest = nullptr;
   }
 
   mVRGetDisplaysPromises.Clear();
+
+  if (mVRServiceTest) {
+    mVRServiceTest->Shutdown();
+    mVRServiceTest = nullptr;
+  }
 }
 
 //*****************************************************************************
 //    Navigator::nsIDOMNavigator
 //*****************************************************************************
 
 void
 Navigator::GetUserAgent(nsAString& aUserAgent, CallerType aCallerType,
@@ -1699,16 +1706,25 @@ Navigator::NotifyVRDisplaysUpdated()
 }
 
 void
 Navigator::NotifyActiveVRDisplaysChanged()
 {
   NavigatorBinding::ClearCachedActiveVRDisplaysValue(this);
 }
 
+VRServiceTest*
+Navigator::RequestVRServiceTest()
+{
+  if (!mVRServiceTest) {
+    mVRServiceTest = VRServiceTest::CreateTestService(mWindow);
+  }
+  return mVRServiceTest;
+}
+
 //*****************************************************************************
 //    Navigator::nsIMozNavigatorNetwork
 //*****************************************************************************
 
 NS_IMETHODIMP
 Navigator::GetProperties(nsINetworkProperties** aProperties)
 {
   ErrorResult rv;
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -74,16 +74,17 @@ namespace network {
 class Connection;
 } // namespace network
 
 class PowerManager;
 class DeviceStorageAreaListener;
 class Presentation;
 class LegacyMozTCPSocket;
 class VRDisplay;
+class VRServiceTest;
 class StorageManager;
 
 namespace time {
 class TimeManager;
 } // namespace time
 
 namespace system {
 #ifdef MOZ_AUDIO_CHANNEL_MANAGER
@@ -212,16 +213,17 @@ public:
   already_AddRefed<LegacyMozTCPSocket> MozTCPSocket();
   network::Connection* GetConnection(ErrorResult& aRv);
   MediaDevices* GetMediaDevices(ErrorResult& aRv);
 
   void GetGamepads(nsTArray<RefPtr<Gamepad> >& aGamepads, ErrorResult& aRv);
   GamepadServiceTest* RequestGamepadServiceTest();
   already_AddRefed<Promise> GetVRDisplays(ErrorResult& aRv);
   void GetActiveVRDisplays(nsTArray<RefPtr<VRDisplay>>& aDisplays) const;
+  VRServiceTest* RequestVRServiceTest();
 #ifdef MOZ_TIME_MANAGER
   time::TimeManager* GetMozTime(ErrorResult& aRv);
 #endif // MOZ_TIME_MANAGER
 #ifdef MOZ_AUDIO_CHANNEL_MANAGER
   system::AudioChannelManager* GetMozAudioChannelManager(ErrorResult& aRv);
 #endif // MOZ_AUDIO_CHANNEL_MANAGER
 
   Presentation* GetPresentation(ErrorResult& aRv);
@@ -324,16 +326,17 @@ private:
   nsTArray<nsWeakPtr> mDeviceStorageStores;
   RefPtr<time::TimeManager> mTimeManager;
   RefPtr<ServiceWorkerContainer> mServiceWorkerContainer;
   nsCOMPtr<nsPIDOMWindowInner> mWindow;
   RefPtr<DeviceStorageAreaListener> mDeviceStorageAreaListener;
   RefPtr<Presentation> mPresentation;
   RefPtr<GamepadServiceTest> mGamepadServiceTest;
   nsTArray<RefPtr<Promise> > mVRGetDisplaysPromises;
+  RefPtr<VRServiceTest> mVRServiceTest;
   nsTArray<uint32_t> mRequestedVibrationPattern;
   RefPtr<StorageManager> mStorageManager;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_Navigator_h
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -10270,16 +10270,32 @@ nsDocument::AddResponsiveContent(nsICont
 void
 nsDocument::RemoveResponsiveContent(nsIContent* aContent)
 {
   MOZ_ASSERT(aContent);
   mResponsiveContent.RemoveEntry(aContent);
 }
 
 void
+nsDocument::AddMediaContent(nsIContent* aContent)
+{
+  MOZ_ASSERT(aContent);
+  MOZ_ASSERT(aContent->IsHTMLElement(nsGkAtoms::video) ||
+             aContent->IsHTMLElement(nsGkAtoms::audio));
+  mMediaContent.PutEntry(aContent);
+}
+
+void
+nsDocument::RemoveMediaContent(nsIContent* aContent)
+{
+  MOZ_ASSERT(aContent);
+  mMediaContent.RemoveEntry(aContent);
+}
+
+void
 nsDocument::NotifyMediaFeatureValuesChanged()
 {
   for (auto iter = mResponsiveContent.ConstIter(); !iter.Done();
        iter.Next()) {
     nsCOMPtr<nsIContent> content = iter.Get()->GetKey();
     if (content->IsHTMLElement(nsGkAtoms::img)) {
       auto* imageElement = static_cast<HTMLImageElement*>(content.get());
       imageElement->MediaFeatureValuesChanged();
@@ -12070,16 +12086,20 @@ nsDocument::MaybeActiveMediaComponents()
   if (mEverInForeground) {
     return;
   }
 
   if (!mWindow) {
     return;
   }
 
+  if (mMediaContent.IsEmpty()) {
+    return;
+  }
+
   mEverInForeground = true;
   GetWindow()->MaybeActiveMediaComponents();
 }
 
 NS_IMETHODIMP
 nsDocument::GetHidden(bool* aHidden)
 {
   *aHidden = Hidden();
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -1006,16 +1006,23 @@ public:
   virtual nsresult AddResponsiveContent(nsIContent* aContent) override;
   // Removes an element from mResponsiveContent when the element is
   // removed from the tree.
   virtual void RemoveResponsiveContent(nsIContent* aContent) override;
   // Notifies any responsive content added by AddResponsiveContent upon media
   // features values changing.
   virtual void NotifyMediaFeatureValuesChanged() override;
 
+  // Adds an element to mMediaContent when the element is added to the tree.
+  virtual void AddMediaContent(nsIContent* aContent) override;
+
+  // Removes an element from mMediaContent when the element is removed from
+  // the tree.
+  virtual void RemoveMediaContent(nsIContent* aContent) override;
+
   virtual nsresult GetStateObject(nsIVariant** aResult) override;
 
   virtual nsDOMNavigationTiming* GetNavigationTiming() const override;
   virtual nsresult SetNavigationTiming(nsDOMNavigationTiming* aTiming) override;
 
   virtual Element* FindImageMap(const nsAString& aNormalizedMapName) override;
 
   virtual nsTArray<Element*> GetFullscreenStack() const override;
@@ -1553,16 +1560,19 @@ private:
   // state so we can provide useful assertions to consumers of ForgetLink and
   // AddStyleRelevantLink.
   bool mStyledLinksCleared;
 #endif
 
   // A set of responsive images keyed by address pointer.
   nsTHashtable< nsPtrHashKey<nsIContent> > mResponsiveContent;
 
+  // A set of media elements keyed by address pointer.
+  nsTHashtable<nsPtrHashKey<nsIContent>> mMediaContent;
+
   // Member to store out last-selected stylesheet set.
   nsString mLastStyleSheetSet;
 
   nsTArray<RefPtr<nsFrameLoader> > mInitializableFrameLoaders;
   nsTArray<nsCOMPtr<nsIRunnable> > mFrameLoaderFinalizers;
   RefPtr<nsRunnableMethod<nsDocument> > mFrameLoaderRunner;
 
   nsCOMPtr<nsIRunnable> mMaybeEndOutermostXBLUpdateRunner;
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -4236,18 +4236,17 @@ nsPIDOMWindowOuter::MaybeActiveMediaComp
   }
 
   nsCOMPtr<nsIDocument> doc = inner->GetExtantDoc();
   if (!doc) {
     return;
   }
 
   if (!doc->Hidden() &&
-      mMediaSuspend == nsISuspendedTypes::SUSPENDED_BLOCK &&
-      AudioChannelService::IsServiceStarted()) {
+      mMediaSuspend == nsISuspendedTypes::SUSPENDED_BLOCK) {
     SetMediaSuspend(nsISuspendedTypes::NONE_SUSPENDED);
   }
 }
 
 SuspendTypes
 nsPIDOMWindowOuter::GetMediaSuspend() const
 {
   if (IsInnerWindow()) {
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -2428,16 +2428,19 @@ public:
   virtual nsresult AddPlugin(nsIObjectLoadingContent* aPlugin) = 0;
   virtual void RemovePlugin(nsIObjectLoadingContent* aPlugin) = 0;
   virtual void GetPlugins(nsTArray<nsIObjectLoadingContent*>& aPlugins) = 0;
 
   virtual nsresult AddResponsiveContent(nsIContent* aContent) = 0;
   virtual void RemoveResponsiveContent(nsIContent* aContent) = 0;
   virtual void NotifyMediaFeatureValuesChanged() = 0;
 
+  virtual void AddMediaContent(nsIContent* aContent) = 0;
+  virtual void RemoveMediaContent(nsIContent* aContent) = 0;
+
   virtual nsresult GetStateObject(nsIVariant** aResult) = 0;
 
   virtual nsDOMNavigationTiming* GetNavigationTiming() const = 0;
 
   virtual nsresult SetNavigationTiming(nsDOMNavigationTiming* aTiming) = 0;
 
   virtual Element* FindImageMap(const nsAString& aNormalizedMapName) = 0;
 
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -993,17 +993,19 @@ private:
             mSuspended == nsISuspendedTypes::SUSPENDED_BLOCK);
   }
 
   AudibleState
   IsOwnerAudible() const
   {
     // Muted or the volume should not be ~0
     if (mOwner->mMuted || (std::fabs(mOwner->Volume()) <= 1e-7)) {
-      return AudioChannelService::AudibleState::eNotAudible;
+      return mOwner->HasAudio() ?
+        AudioChannelService::AudibleState::eMaybeAudible :
+        AudioChannelService::AudibleState::eNotAudible;
     }
 
     // No audio track.
     if (!mOwner->HasAudio()) {
       return AudioChannelService::AudibleState::eNotAudible;
     }
 
     // Might be audible but not yet.
@@ -4274,16 +4276,17 @@ nsresult HTMLMediaElement::BindToTree(ns
 
   if (aDocument) {
     mAutoplayEnabled =
       IsAutoplayEnabled() && (!aDocument || !aDocument->IsStaticDocument()) &&
       !IsEditable();
     // The preload action depends on the value of the autoplay attribute.
     // It's value may have changed, so update it.
     UpdatePreloadAction();
+    aDocument->AddMediaContent(this);
   }
 
   if (mDecoder) {
     // When the MediaElement is binding to tree, the dormant status is
     // aligned to document's hidden status.
     mDecoder->NotifyOwnerActivityChanged(!IsHidden());
   }
 
@@ -4509,16 +4512,19 @@ HTMLMediaElement::ReportTelemetry()
     }
   }
 }
 
 void HTMLMediaElement::UnbindFromTree(bool aDeep,
                                       bool aNullParent)
 {
   mUnboundFromTree = true;
+  if (OwnerDoc()) {
+    OwnerDoc()->RemoveMediaContent(this);
+  }
 
   nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
 
   if (mDecoder) {
     MOZ_ASSERT(IsHidden());
     mDecoder->NotifyOwnerActivityChanged(false);
   }
 
--- a/dom/media/TextTrackCue.cpp
+++ b/dom/media/TextTrackCue.cpp
@@ -128,28 +128,20 @@ TextTrackCue::GetCueAsHTML()
     ClearOnShutdown(&sParserWrapper);
   }
 
   nsPIDOMWindowInner* window = mDocument->GetInnerWindow();
   if (!window) {
     return mDocument->CreateDocumentFragment();
   }
 
-  nsCOMPtr<nsIDOMHTMLElement> div;
+  nsCOMPtr<nsIDOMDocumentFragment> frag;
   sParserWrapper->ConvertCueToDOMTree(window, this,
-                                      getter_AddRefs(div));
-  nsCOMPtr<nsINode> divNode = do_QueryInterface(div);
-  if (!divNode) {
-    return mDocument->CreateDocumentFragment();
-  }
-  RefPtr<DocumentFragment> docFrag = mDocument->CreateDocumentFragment();
-  IgnoredErrorResult rv;
-  docFrag->AppendChild(*divNode, rv);
-
-  return docFrag.forget();
+                                      getter_AddRefs(frag));
+  return frag.forget().downcast<DocumentFragment>();
 }
 
 void
 TextTrackCue::SetTrackElement(HTMLTrackElement* aTrackElement)
 {
   mTrackElement = aTrackElement;
 }
 
--- a/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.cpp
+++ b/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.cpp
@@ -1,29 +1,28 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WidevineVideoDecoder.h"
 
-#include "mp4_demuxer/AnnexB.h"
 #include "WidevineUtils.h"
 #include "WidevineVideoFrame.h"
 #include "mozilla/Move.h"
+#include <inttypes.h>
 
 using namespace cdm;
 
 namespace mozilla {
 
 WidevineVideoDecoder::WidevineVideoDecoder(GMPVideoHost* aVideoHost,
                                            RefPtr<CDMWrapper> aCDMWrapper)
   : mVideoHost(aVideoHost)
   , mCDMWrapper(Move(aCDMWrapper))
-  , mExtraData(new MediaByteBuffer())
   , mSentInput(false)
   , mCodecType(kGMPVideoCodecInvalid)
   , mReturnOutputCallDepth(0)
   , mDrainPending(false)
   , mResetInProgress(false)
 {
   // Expect to start with a CDM wrapper, will release it in DecodingComplete().
   MOZ_ASSERT(mCDMWrapper);
@@ -75,26 +74,29 @@ WidevineVideoDecoder::InitDecode(const G
     config.codec = VideoDecoderConfig::kCodecVp9;
     config.profile = VideoDecoderConfig::kProfileNotNeeded;
   } else {
     mCallback->Error(GMPInvalidArgErr);
     return;
   }
   config.format = kYv12;
   config.coded_size = Size(aCodecSettings.mWidth, aCodecSettings.mHeight);
-  mExtraData->AppendElements(aCodecSpecific + 1, aCodecSpecificLength);
-  config.extra_data = mExtraData->Elements();
-  config.extra_data_size = mExtraData->Length();
+  nsTArray<uint8_t> extraData;
+  if (aCodecSpecificLength > 0) {
+    // The first byte is the WebRTC packetization mode, which can be ignored.
+    extraData.AppendElements(aCodecSpecific + 1, aCodecSpecificLength - 1);
+    config.extra_data = extraData.Elements();
+    config.extra_data_size = extraData.Length();
+  }
   Status rv = CDM()->InitializeVideoDecoder(config);
   if (rv != kSuccess) {
     mCallback->Error(ToGMPErr(rv));
     return;
   }
   CDM_LOG("WidevineVideoDecoder::InitDecode() rv=%d", rv);
-  mAnnexB = mp4_demuxer::AnnexB::ConvertExtraDataToAnnexB(mExtraData);
 }
 
 void
 WidevineVideoDecoder::Decode(GMPVideoEncodedFrame* aInputFrame,
                              bool aMissingFrames,
                              const uint8_t* aCodecSpecificInfo,
                              uint32_t aCodecSpecificInfoLength,
                              int64_t aRenderTimeMs)
@@ -105,45 +107,22 @@ WidevineVideoDecoder::Decode(GMPVideoEnc
   // may be some latency, i.e. we may need to input (say) 30 frames before
   // we receive output. So we need to store the durations of the frames input,
   // and retrieve them on output.
   mFrameDurations[aInputFrame->TimeStamp()] = aInputFrame->Duration();
 
   mSentInput = true;
   InputBuffer sample;
 
-  RefPtr<MediaRawData> raw(
-    new MediaRawData(aInputFrame->Buffer(), aInputFrame->Size()));
-  if (aInputFrame->Size() && !raw->Data()) {
-    // OOM.
-    mCallback->Error(GMPAllocErr);
-    return;
-  }
-  raw->mExtraData = mExtraData;
-  raw->mKeyframe = (aInputFrame->FrameType() == kGMPKeyFrame);
-  if (mCodecType == kGMPVideoCodecH264) {
-    // Convert input from AVCC, which GMPAPI passes in, to AnnexB, which
-    // Chromium uses internally.
-    mp4_demuxer::AnnexB::ConvertSampleToAnnexB(raw);
-  }
-
   const GMPEncryptedBufferMetadata* crypto = aInputFrame->GetDecryptionData();
   nsTArray<SubsampleEntry> subsamples;
-  InitInputBuffer(crypto, aInputFrame->TimeStamp(), raw->Data(), raw->Size(),
+  InitInputBuffer(crypto, aInputFrame->TimeStamp(),
+                  aInputFrame->Buffer(), aInputFrame->Size(),
                   sample, subsamples);
 
-  // For keyframes, ConvertSampleToAnnexB will stick the AnnexB extra data
-  // at the start of the input. So we need to account for that as clear data
-  // in the subsamples.
-  if (raw->mKeyframe
-      && !subsamples.IsEmpty()
-      && mCodecType == kGMPVideoCodecH264) {
-    subsamples[0].clear_bytes += mAnnexB->Length();
-  }
-
   WidevineVideoFrame frame;
   Status rv = CDM()->DecryptAndDecodeFrame(sample, &frame);
   CDM_LOG("WidevineVideoDecoder::Decode(timestamp=%" PRId64 ") rv=%d",
           sample.timestamp, rv);
 
   // Destroy frame, so that the shmem is now free to be used to return
   // output to the Gecko process.
   aInputFrame->Destroy();
--- a/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.h
+++ b/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.h
@@ -54,18 +54,16 @@ private:
     return mCDMWrapper->GetCDM();
   }
 
   bool ReturnOutput(WidevineVideoFrame& aFrame);
   void CompleteReset();
 
   GMPVideoHost* mVideoHost;
   RefPtr<CDMWrapper> mCDMWrapper;
-  RefPtr<MediaByteBuffer> mExtraData;
-  RefPtr<MediaByteBuffer> mAnnexB;
   GMPVideoDecoderCallback* mCallback = nullptr;
   std::map<uint64_t, uint64_t> mFrameDurations;
   bool mSentInput;
   GMPVideoCodecType mCodecType;
   // Frames waiting on allocation
   std::deque<WidevineVideoFrame> mFrameAllocationQueue;
   // Number of calls of ReturnOutput currently in progress.
   int32_t mReturnOutputCallDepth;
--- a/dom/media/platforms/agnostic/BlankDecoderModule.cpp
+++ b/dom/media/platforms/agnostic/BlankDecoderModule.cpp
@@ -141,27 +141,27 @@ public:
     buffer.mPlanes[0].mStride = mFrameWidth;
     buffer.mPlanes[0].mHeight = mFrameHeight;
     buffer.mPlanes[0].mWidth = mFrameWidth;
     buffer.mPlanes[0].mOffset = 0;
     buffer.mPlanes[0].mSkip = 0;
 
     // Cb plane.
     buffer.mPlanes[1].mData = frame.get() + sizeY;
-    buffer.mPlanes[1].mStride = mFrameWidth / 2;
-    buffer.mPlanes[1].mHeight = mFrameHeight / 2;
-    buffer.mPlanes[1].mWidth = mFrameWidth / 2;
+    buffer.mPlanes[1].mStride = (mFrameWidth + 1) / 2;
+    buffer.mPlanes[1].mHeight = (mFrameHeight + 1) / 2;
+    buffer.mPlanes[1].mWidth = (mFrameWidth + 1) / 2;
     buffer.mPlanes[1].mOffset = 0;
     buffer.mPlanes[1].mSkip = 0;
 
     // Cr plane.
     buffer.mPlanes[2].mData = frame.get() + sizeY;
-    buffer.mPlanes[2].mStride = mFrameWidth / 2;
-    buffer.mPlanes[2].mHeight = mFrameHeight / 2;
-    buffer.mPlanes[2].mWidth = mFrameWidth / 2;
+    buffer.mPlanes[2].mStride = (mFrameWidth + 1) / 2;
+    buffer.mPlanes[2].mHeight = (mFrameHeight + 1) / 2;
+    buffer.mPlanes[2].mWidth = (mFrameWidth + 1) / 2;
     buffer.mPlanes[2].mOffset = 0;
     buffer.mPlanes[2].mSkip = 0;
 
     // Set to color white.
     memset(buffer.mPlanes[0].mData, 255, sizeY);
     memset(buffer.mPlanes[1].mData, 128, sizeCbCr);
 
     return VideoData::CreateAndCopyData(mInfo,
--- a/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp
+++ b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp
@@ -6,16 +6,17 @@
 
 #include "GMPVideoDecoder.h"
 #include "GMPDecoderModule.h"
 #include "GMPVideoHost.h"
 #include "MediaData.h"
 #include "VPXDecoder.h"
 #include "mozilla/EndianUtils.h"
 #include "prsystem.h"
+#include "mp4_demuxer/AnnexB.h"
 
 namespace mozilla {
 
 #if defined(DEBUG)
 static bool IsOnGMPThread()
 {
   nsCOMPtr<mozIGeckoMediaPluginService> mps =
     do_GetService("@mozilla.org/gecko-media-plugin-service;1");
@@ -226,26 +227,31 @@ GMPVideoDecoder::GMPInitDone(GMPVideoDec
 
   if (mInitPromise.IsEmpty()) {
     // GMP must have been shutdown while we were waiting for Init operation
     // to complete.
     aGMP->Close();
     return;
   }
 
+  bool isOpenH264 = aGMP->GetDisplayName().EqualsLiteral("gmpopenh264");
+
   GMPVideoCodec codec;
   memset(&codec, 0, sizeof(codec));
 
   codec.mGMPApiVersion = kGMPVersion33;
   nsTArray<uint8_t> codecSpecific;
   if (MP4Decoder::IsH264(mConfig.mMimeType)) {
     codec.mCodecType = kGMPVideoCodecH264;
     codecSpecific.AppendElement(0); // mPacketizationMode.
     codecSpecific.AppendElements(mConfig.mExtraData->Elements(),
                                  mConfig.mExtraData->Length());
+    // OpenH264 expects pseudo-AVCC, but others must be passed
+    // AnnexB for H264.
+    mConvertToAnnexB = !isOpenH264;
   } else if (VPXDecoder::IsVP8(mConfig.mMimeType)) {
     codec.mCodecType = kGMPVideoCodecVP8;
   } else if (VPXDecoder::IsVP9(mConfig.mMimeType)) {
     codec.mCodecType = kGMPVideoCodecVP9;
   } else {
     // Unrecognized mime type
     aGMP->Close();
     mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
@@ -270,17 +276,17 @@ GMPVideoDecoder::GMPInitDone(GMPVideoDec
   // GMP implementations have interpreted the meaning of GMP_BufferLength32
   // differently.  The OpenH264 GMP expects GMP_BufferLength32 to behave as
   // specified in the GMP API, where each buffer is prefixed by a 32-bit
   // host-endian buffer length that includes the size of the buffer length
   // field.  Other existing GMPs currently expect GMP_BufferLength32 (when
   // combined with kGMPVideoCodecH264) to mean "like AVCC but restricted to
   // 4-byte NAL lengths" (i.e. buffer lengths are specified in big-endian
   // and do not include the length of the buffer length field.
-  mConvertNALUnitLengths = mGMP->GetDisplayName().EqualsLiteral("gmpopenh264");
+  mConvertNALUnitLengths = isOpenH264;
 
   mInitPromise.Resolve(TrackInfo::kVideoTrack, __func__);
 }
 
 RefPtr<MediaDataDecoder::InitPromise>
 GMPVideoDecoder::Init()
 {
   MOZ_ASSERT(IsOnGMPThread());
--- a/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h
+++ b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h
@@ -39,17 +39,18 @@ public:
   RefPtr<FlushPromise> Flush() override;
   RefPtr<ShutdownPromise> Shutdown() override;
   const char* GetDescriptionName() const override
   {
     return "GMP video decoder";
   }
   ConversionRequired NeedsConversion() const override
   {
-    return ConversionRequired::kNeedAVCC;
+    return mConvertToAnnexB ? ConversionRequired::kNeedAnnexB
+                            : ConversionRequired::kNeedAVCC;
   }
 
   // GMPVideoDecoderCallbackProxy
   // All those methods are called on the GMP thread.
   void Decoded(GMPVideoi420Frame* aDecodedFrame) override;
   void ReceivedDecodedReferenceFrame(const uint64_t aPictureId) override;
   void ReceivedDecodedFrame(const uint64_t aPictureId) override;
   void InputDataExhausted() override;
@@ -95,13 +96,14 @@ private:
 
   int64_t mLastStreamOffset = 0;
   RefPtr<layers::ImageContainer> mImageContainer;
 
   MozPromiseHolder<DecodePromise> mDecodePromise;
   MozPromiseHolder<DecodePromise> mDrainPromise;
   MozPromiseHolder<FlushPromise> mFlushPromise;
   DecodedData mDecodedData;
+  bool mConvertToAnnexB = false;
 };
 
 } // namespace mozilla
 
 #endif // GMPVideoDecoder_h_
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -269,8 +269,10 @@ skip-if = (android_version == '18') # an
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_remoteRollback.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_remoteReofferRollback.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_selftest.html]
 # Bug 1227781: Crash with bogus TURN server.
 [test_peerConnection_bug1227781.html]
+[test_peerConnection_stats.html]
+skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_peerConnection_stats.html
@@ -0,0 +1,397 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+  createHTML({
+    bug: "1337525",
+    title: "webRtc Stats composition and sanity"
+  });
+var statsExpectedByType = {
+  "inbound-rtp": {
+    expected: ["id", "timestamp", "type", "ssrc", "isRemote", "mediaType",
+      "packetsReceived", "packetsLost", "bytesReceived", "jitter",],
+    optional: ["mozRtt", "remoteId",],
+    videoOnly: ["discardedPackets", "framerateStdDev", "framerateMean",
+      "bitrateMean", "bitrateStdDev",],
+    unimplemented: ["mediaTrackId", "transportId", "codecId", "framesDecoded",
+      "packetsDiscarded", "associateStatsId", "firCount", "pliCount",
+      "nackCount", "sliCount", "qpSum", "packetsRepaired", "fractionLost",
+      "burstPacketsLost", "burstLossCount", "burstDiscardCount",
+      "gapDiscardRate", "gapLossRate",],
+  },
+  "outbound-rtp": {
+    expected: ["id", "timestamp", "type", "ssrc", "isRemote", "mediaType",
+      "packetsSent", "bytesSent", "remoteId",],
+    optional: ["remoteId",],
+    videoOnly: ["droppedFrames", "bitrateMean", "bitrateStdDev",
+      "framerateMean", "framerateStdDev",],
+    unimplemented: ["mediaTrackId", "transportId", "codecId",
+      "framesEncoded", "firCount", "pliCount", "nackCount", "sliCount",
+      "qpSum", "roundTripTime", "targetBitrate",],
+  },
+  "codec": { skip: true },
+  "peer-connection": { skip: true },
+  "data-channel": { skip: true },
+  "track": { skip: true },
+  "transport": { skip: true },
+  "candidate-pair": { skip : true },
+  "local-candidate": { skip: true },
+  "remote-candidate": { skip: true },
+  "certificate": { skip: true },
+};
+["in", "out"].forEach(pre => {
+  let s = statsExpectedByType[pre + "bound-rtp"];
+  s.optional = [...s.optional, ...s.videoOnly];
+});
+
+//
+//  Checks that the fields in a report conform to the expectations in
+// statExpectedByType
+//
+var checkExpectedFields = report => report.forEach(stat => {
+  let expectations = statsExpectedByType[stat.type];
+  ok(expectations, "Stats type " + stat.type + " was expected");
+  // If the type is not expected or if it is flagged for skipping continue to
+  // the next
+  if (!expectations || expectations.skip) {
+    return;
+  }
+  // Check that all required fields exist
+  expectations.expected.forEach(field => {
+    ok(field in stat, "Expected stat field " + stat.type + "." + field
+      + " exists");
+  });
+  // Check that each field is either expected or optional
+  let allowed = [...expectations.expected, ...expectations.optional];
+  Object.keys(stat).forEach(field => {
+    ok(allowed.includes(field), "Stat field " + stat.type + "." + field
+      + " is allowed");
+  });
+
+  //
+  // Ensure that unimplemented fields are not implemented
+  //   note: if a field is implemented it should be moved to expected or
+  //   optional.
+  //
+  expectations.unimplemented.forEach(field => {
+    ok(!Object.keys(stat).includes(field), "Unimplemented field " + stat.type
+      + "." + field + " does not exist.");
+  });
+});
+
+var pedanticChecks = report => {
+  report.forEach((statObj, mapKey) => {
+    let tested = {};
+    // Record what fields get tested.
+    // To access a field foo without marking it as tested use stat.inner.foo
+    let stat = new Proxy(statObj, {
+      get(stat, key) {
+        if (key == "inner") return stat;
+        tested[key] = true;
+        return stat[key];
+      }
+    });
+
+    let expectations = statsExpectedByType[stat.type];
+
+    if (expectations.skip) {
+      return;
+    }
+
+    // All stats share the following attributes inherited from RTCStats
+    is(stat.id, mapKey, stat.type + ".id is the same as the report key.");
+
+    // timestamp
+    ok(stat.timestamp >= 0, stat.type + ".timestamp is not less than 0");
+
+    //
+    // RTCStreamStats attributes with common behavior
+    //
+    // inbound-rtp and outbound-rtp inherit from RTCStreamStats
+    if (["inbound-rtp", "outbound-rtp"].includes(stat.type)) {
+      //
+      // Common RTCStreamStats fields
+      //
+
+      // SSRC
+      ok(stat.ssrc, stat.type + ".ssrc has a value");
+
+      // isRemote
+      ok(stat.isRemote !== undefined, stat.type + ".isRemote exists.");
+
+      // mediaType
+      ok(["audio", "video"].includes(stat.mediaType),
+        stat.type + ".mediaType is 'audio' or 'video'");
+
+      // remote id
+      if (stat.remoteId) {
+        ok(report.has(stat.remoteId), "remoteId exists in report.");
+        is(report.get(stat.remoteId).ssrc, stat.ssrc,
+          "remote ssrc and local ssrc match.");
+        is(report.get(stat.remoteId).remoteId, stat.id,
+          "remote object has local object as it's own remote object.");
+      }
+
+    }
+
+    if (stat.type == "inbound-rtp") {
+      //
+      // Required fields
+      //
+
+      // packetsReceived
+      ok(stat.packetsReceived >= 0
+        && stat.packetsReceived < 10 ** 5,
+        stat.type + ".packetsReceived is a sane number for a short test. value="
+        + stat.packetsReceived);
+
+      // bytesReceived
+      ok(stat.bytesReceived >= 0
+        && stat.bytesReceived < 10 ** 9, // Not a magic number, just a guess
+        stat.type + ".bytesReceived is a sane number for a short test. value="
+        + stat.bytesReceived);
+
+      // packetsLost
+      ok(stat.packetsLost < 100,
+        stat.type + ".packetsLost is a sane number for a short test. value="
+        + stat.packetsLost);
+
+      // jitter
+      ok(stat.jitter < 10, // This should be much lower, TODO: Bug 1330575
+        stat.type + ".jitter is sane number for a local only test. value="
+        + stat.jitter);
+
+      // packetsDiscarded
+      // special exception for, TODO: Bug 1335967
+      // if (!stat.inner.isRemote && stat.discardedPackets !== undefined) {
+      //   ok(stat.packetsDiscarded < 100, stat.type
+      //     + ".packetsDiscarded is a sane number for a short test. value="
+      //     + stat.packetsDiscarded);
+      // }
+      // if (stat.packetsDiscarded !== undefined) {
+      //   ok(!stat.inner.isRemote,
+      //     stat.type + ".packetsDiscarded is only set when isRemote is "
+      //     + "false");
+      // }
+
+      //
+      // Optional fields
+      //
+
+      // mozRtt
+      if (stat.inner.isRemote) {
+        ok(stat.mozRtt >= 0, stat.type + ".mozRtt is sane.");
+      } else {
+        is(stat.mozRtt, undefined, stat.type
+          + ".mozRtt is only set when isRemote is true");
+      }
+
+      //
+      // Local video only stats
+      //
+      if (stat.inner.isRemote || stat.inner.mediaType != "video") {
+        expectations.videoOnly.forEach(field => {
+          if (stat.inner.isRemote) {
+            ok(stat[field] === undefined, stat.type + " does not have field "
+              + field + " when isRemote is true");
+          } else { // mediaType != video
+            ok(stat[field] === undefined, stat.type + " does not have field "
+              + field + " when mediaType is not 'video'");
+          }
+        });
+      } else {
+        expectations.videoOnly.forEach(field => {
+          ok(stat[field] !== undefined, stat.type + " has field " + field
+            + " when mediaType is video");
+        });
+        // discardedPackets
+        ok(stat.discardedPackets < 100, stat.type
+          + ".discardedPackets is a sane number for a short test. value="
+          + stat.discardedPackets);
+
+        // bitrateMean
+        // special exception, TODO: Bug 1341533
+        if (stat.bitrateMean !== undefined) {
+          // TODO: uncomment when Bug 1341533 lands
+          // ok(stat.bitrateMean >= 0 && stat.bitrateMean < 2 ** 25,
+          //   stat.type + ".bitrateMean is sane. value="
+          //   + stat.bitrateMean);
+        }
+
+        // bitrateStdDev
+        // special exception, TODO Bug 1341533
+        if (stat.bitrateStdDev !== undefined) {
+          // TODO: uncomment when Bug 1341533 lands
+          // ok(stat.bitrateStdDev >= 0 && stat.bitrateStdDev < 2 ** 25,
+          //   stat.type + ".bitrateStdDev is sane. value="
+          //   + stat.bitrateStdDev);
+        }
+
+        // framerateMean
+        // special exception, TODO: Bug 1341533
+        if (stat.framerateMean !== undefined) {
+          // TODO: uncomment when Bug 1341533 lands
+          // ok(stat.framerateMean >= 0 && stat.framerateMean < 120,
+          //   stat.type + ".framerateMean is sane. value="
+          //   + stat.framerateMean);
+        }
+
+        // framerateStdDev
+        // special exception, TODO: Bug 1341533
+        if (stat.framerateStdDev !== undefined) {
+          // TODO: uncomment when Bug 1341533 lands
+          // ok(stat.framerateStdDev >= 0 && stat.framerateStdDev < 120,
+          //   stat.type + ".framerateStdDev is sane. value="
+          //   + stat.framerateStdDev);
+        }
+      }
+    } else if (stat.type == "outbound-rtp") {
+      //
+      // Required fields
+      //
+
+      // packetsSent
+      ok(stat.packetsSent > 0 && stat.packetsSent < 10000,
+        stat.type + ".packetsSent is a sane number for a short test. value="
+        + stat.packetsSent);
+
+      // bytesSent
+      ok(stat.bytesSent, stat.type + ".bytesSent has a value."
+        + " Value not expected to be sane, bug 1339104. value="
+        + stat.bytesSent);
+
+      //
+      // Optional fields
+      //
+
+      //
+      // Local video only stats
+      //
+      if (stat.inner.isRemote || stat.inner.mediaType != "video") {
+        expectations.videoOnly.forEach(field => {
+          if (stat.inner.isRemote) {
+            ok(stat[field] === undefined, stat.type + " does not have field "
+              + field + " when isRemote is true");
+          } else { // mediaType != video
+            ok(stat[field] === undefined, stat.type + " does not have field "
+              + field + " when mediaType is not 'video'");
+          }
+        });
+      } else {
+        expectations.videoOnly.forEach(field => {
+          ok(stat[field] !== undefined, stat.type + " has field " + field
+            + " when mediaType is video");
+        });
+
+        // bitrateMean
+        if (stat.bitrateMean !== undefined) {
+          // TODO: uncomment when Bug 1341533 lands
+          // ok(stat.bitrateMean >= 0 && stat.bitrateMean < 2 ** 25,
+          //   stat.type + ".bitrateMean is sane. value="
+          //   + stat.bitrateMean);
+        }
+
+        // bitrateStdDev
+        if (stat.bitrateStdDev !== undefined) {
+          // TODO: uncomment when Bug 1341533 lands
+          // ok(stat.bitrateStdDev >= 0 && stat.bitrateStdDev < 2 ** 25,
+          //   stat.type + ".bitrateStdDev is sane. value="
+          //   + stat.bitrateStdDev);
+        }
+
+        // framerateMean
+        if (stat.framerateMean !== undefined) {
+          // TODO: uncomment when Bug 1341533 lands
+          // ok(stat.framerateMean >= 0 && stat.framerateMean < 120,
+          //   stat.type + ".framerateMean is sane. value="
+          //   + stat.framerateMean);
+        }
+
+        // framerateStdDev
+        if (stat.framerateStdDev !== undefined) {
+          // TODO: uncomment when Bug 1341533 lands
+          // ok(stat.framerateStdDev >= 0 && stat.framerateStdDev < 120,
+          //   stat.type + ".framerateStdDev is sane. value="
+          //   + stat.framerateStdDev);
+        }
+
+        // droppedFrames
+        ok(stat.droppedFrames >= 0,
+          stat.type + ".droppedFrames is not negative. value="
+          + stat.droppedFrames);
+      }
+    }
+
+    //
+    // Ensure everything was tested
+    //
+    [...expectations.expected, ...expectations.optional].forEach(field => {
+      ok(Object.keys(tested).includes(field), stat.type + "." + field
+        + " was tested.");
+    });
+  });
+}
+
+// This MUST be run after PC_*_WAIT_FOR_MEDIA_FLOW to ensure that we have RTP
+// before checking for RTCP.
+var waitForRtcp = async pc => {
+  // Ensures that RTCP is present
+  let ensureRtcp = async () => pc.getStats().then(stats => {
+    for (let [k, v] of stats) {
+      if (v.type.endsWith("bound-rtp") && !v.remoteId) {
+        throw new Error(v.id + " is missing remoteId: "
+          + JSON.stringify(v));
+      }
+    }
+    return stats;
+  });
+
+  const waitPeriod = 500;
+  for (let totalTime = 10000; totalTime > 0; totalTime -= waitPeriod) {
+    try {
+      return await ensureRtcp();
+    } catch (e) {
+      info(e);
+      await wait(waitPeriod);
+    }
+  }
+  throw new Error("Waiting for RTCP timed out after at least " + totalTime
+    + "ms");
+}
+
+var PC_LOCAL_TEST_LOCAL_STATS = test => {
+  return waitForRtcp(test.pcLocal).then(stats => {
+    checkExpectedFields(stats);
+    pedanticChecks(stats);
+  });
+}
+
+var PC_REMOTE_TEST_REMOTE_STATS = test => {
+  return waitForRtcp(test.pcRemote).then(stats => {
+    checkExpectedFields(stats);
+    pedanticChecks(stats);
+  });
+}
+
+var test;
+runNetworkTest(function (options) {
+  test = new PeerConnectionTest(options);
+
+  test.chain.insertAfter("PC_LOCAL_WAIT_FOR_MEDIA_FLOW",
+    [PC_LOCAL_TEST_LOCAL_STATS]);
+
+  test.chain.insertAfter("PC_REMOTE_WAIT_FOR_MEDIA_FLOW",
+    [PC_REMOTE_TEST_REMOTE_STATS]);
+
+  test.setMediaConstraints([{audio: true}, {video: true}],
+                           [{audio: true}, {video: true}]);
+  test.run();
+});
+</script>
+</pre>
+</body>
+</html>
--- a/dom/media/webvtt/nsIWebVTTParserWrapper.idl
+++ b/dom/media/webvtt/nsIWebVTTParserWrapper.idl
@@ -1,15 +1,15 @@
 /* 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 "nsISupports.idl"
 
-interface nsIDOMHTMLElement;
+interface nsIDOMDocumentFragment;
 interface nsIWebVTTListener;
 interface mozIDOMWindow;
 interface nsIVariant;
 
 /**
  * Interface for a wrapper of a JS WebVTT parser (vtt.js).
  */
 [scriptable, uuid(8dfe016e-1701-4618-9f5e-9a6154e853f0)]
@@ -55,18 +55,18 @@ interface nsIWebVTTParserWrapper : nsISu
    * Convert the text content of a WebVTT cue to a document fragment so that
    * we can display it on the page.
    *
    * @param window A window object with which the document fragment will be
    *               created.
    * @param cue    The cue whose content will be converted to a document
    *               fragment.
    */
-  nsIDOMHTMLElement convertCueToDOMTree(in mozIDOMWindow window,
-                                        in nsISupports cue);
+  nsIDOMDocumentFragment convertCueToDOMTree(in mozIDOMWindow window,
+                                             in nsISupports cue);
 
 
   /**
    * Compute the display state of the VTTCues in cues along with any VTTRegions
    * that they might be in. First, it computes the positioning and styling of
    * the cues and regions passed and converts them into a DOM tree rooted at
    * a containing HTMLDivElement. It then adjusts those computed divs for
    * overlap avoidance using the dimensions of 'overlay'. Finally, it adds the
--- a/dom/media/webvtt/vtt.jsm
+++ b/dom/media/webvtt/vtt.jsm
@@ -298,17 +298,17 @@ Cu.import('resource://gre/modules/Servic
     lang: "lang"
   };
 
   var NEEDS_PARENT = {
     rt: "ruby"
   };
 
   // Parse content into a document fragment.
-  function parseContent(window, input) {
+  function parseContent(window, input, bReturnFrag) {
     function nextToken() {
       // Check for end-of-string.
       if (!input) {
         return null;
       }
 
       // Consume 'n' characters from the input.
       function consume(result) {
@@ -342,24 +342,54 @@ Cu.import('resource://gre/modules/Servic
     function createElement(type, annotation) {
       var tagName = TAG_NAME[type];
       if (!tagName) {
         return null;
       }
       var element = window.document.createElement(tagName);
       element.localName = tagName;
       var name = TAG_ANNOTATION[type];
-      if (name && annotation) {
-        element[name] = annotation.trim();
+      if (name) {
+        element[name] = annotation ? annotation.trim() : "";
       }
       return element;
     }
 
-    var rootDiv = window.document.createElement("div"),
-        current = rootDiv,
+    // https://w3c.github.io/webvtt/#webvtt-timestamp-object
+    // Return hhhhh:mm:ss.fff
+    function normalizedTimeStamp(secondsWithFrag) {
+      var totalsec = parseInt(secondsWithFrag, 10);
+      var hours = Math.floor(totalsec / 3600);
+      var minutes = Math.floor(totalsec % 3600 / 60);
+      var seconds = Math.floor(totalsec % 60);
+      if (hours < 10) {
+        hours = "0" + hours;
+      }
+      if (minutes < 10) {
+        minutes = "0" + minutes;
+      }
+      if (seconds < 10) {
+        seconds = "0" + seconds;
+      }
+      var f = secondsWithFrag.toString().split(".");
+      if (f[1]) {
+        f = f[1].slice(0, 3).padEnd(3, "0");
+      } else {
+        f = "000";
+      }
+      return hours + ':' + minutes + ':' + seconds + '.' + f;
+    }
+
+    var root;
+    if (bReturnFrag) {
+      root = window.document.createDocumentFragment();
+    } else {
+      root = window.document.createElement("div");
+    }
+    var current = root,
         t,
         tagStack = [];
 
     while ((t = nextToken()) !== null) {
       if (t[0] === '<') {
         if (t[1] === "/") {
           // If the closing tag matches, move back up to the parent node.
           if (tagStack.length &&
@@ -369,17 +399,17 @@ Cu.import('resource://gre/modules/Servic
           }
           // Otherwise just ignore the end tag.
           continue;
         }
         var ts = collectTimeStamp(t.substr(1, t.length - 2));
         var node;
         if (ts) {
           // Timestamps are lead nodes as well.
-          node = window.document.createProcessingInstruction("timestamp", ts);
+          node = window.document.createProcessingInstruction("timestamp", normalizedTimeStamp(ts));
           current.appendChild(node);
           continue;
         }
         var m = t.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/);
         // If we can't parse the tag, skip to the next tag.
         if (!m) {
           continue;
         }
@@ -404,17 +434,17 @@ Cu.import('resource://gre/modules/Servic
         current = node;
         continue;
       }
 
       // Text nodes are leaf nodes.
       current.appendChild(window.document.createTextNode(unescape(t)));
     }
 
-    return rootDiv;
+    return root;
   }
 
   function StyleBox() {
   }
 
   // Apply styles to a div. If there is no div passed then it defaults to the
   // div on 'this'.
   StyleBox.prototype.applyStyles = function(styles, div) {
@@ -443,17 +473,17 @@ Cu.import('resource://gre/modules/Servic
       backgroundColor = "rgb(0, 0, 0)";
     }
 
     StyleBox.call(this);
     this.cue = cue;
 
     // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will
     // have inline positioning and will function as the cue background box.
-    this.cueDiv = parseContent(window, cue.text);
+    this.cueDiv = parseContent(window, cue.text, false);
     var styles = {
       color: color,
       backgroundColor: backgroundColor,
       position: "relative",
       left: 0,
       right: 0,
       top: 0,
       bottom: 0,
@@ -832,20 +862,20 @@ Cu.import('resource://gre/modules/Servic
           throw new Error("Error - expected string data.");
         }
         return decodeURIComponent(encodeURIComponent(data));
       }
     };
   };
 
   WebVTT.convertCueToDOMTree = function(window, cuetext) {
-    if (!window || !cuetext) {
+    if (!window) {
       return null;
     }
-    return parseContent(window, cuetext);
+    return parseContent(window, cuetext, true);
   };
 
   var FONT_SIZE_PERCENT = 0.05;
   var FONT_STYLE = "sans-serif";
   var CUE_BACKGROUND_PADDING = "1.5%";
 
   // Runs the processing model over the cues and regions passed to it.
   // @param overlay A block level element (usually a div) that the computed cues
--- a/dom/security/test/moz.build
+++ b/dom/security/test/moz.build
@@ -23,12 +23,11 @@ MOCHITEST_MANIFESTS += [
     'sri/mochitest.ini',
 ]
 
 MOCHITEST_CHROME_MANIFESTS += [
     'general/chrome.ini',
 ]
 
 BROWSER_CHROME_MANIFESTS += [
-    'contentverifier/browser.ini',
     'csp/browser.ini',
     'hsts/browser.ini',
 ]
--- a/dom/vr/VRDisplay.cpp
+++ b/dom/vr/VRDisplay.cpp
@@ -748,16 +748,33 @@ VRFrameData::Update(const VRFrameInfo& a
 
 void
 VRFrameInfo::Update(const gfx::VRDisplayInfo& aInfo,
                     const gfx::VRHMDSensorState& aState,
                     float aDepthNear,
                     float aDepthFar)
 {
   mVRState = aState;
+  if (mTimeStampOffset == 0.0f) {
+    /**
+     * A mTimeStampOffset value of 0.0f indicates that this is the first iteration
+     * and an offset has not yet been set.
+     *
+     * Generate a value for mTimeStampOffset such that if aState.timestamp is
+     * monotonically increasing, aState.timestamp + mTimeStampOffset will never
+     * be a negative number and will start at a pseudo-random offset
+     * between 1000.0f and 11000.0f seconds.
+     *
+     * We use a pseudo random offset rather than 0.0f just to discourage users
+     * from making the assumption that the timestamp returned in the WebVR API
+     * has a base of 0, which is not necessarily true in all UA's.
+     */
+    mTimeStampOffset = float(rand()) / RAND_MAX * 10000.0f + 1000.0f - aState.timestamp;
+  }
+  mVRState.timestamp = aState.timestamp + mTimeStampOffset;
 
   gfx::Quaternion qt;
   if (mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_Orientation) {
     qt.x = mVRState.orientation[0];
     qt.y = mVRState.orientation[1];
     qt.z = mVRState.orientation[2];
     qt.w = mVRState.orientation[3];
   }
@@ -785,16 +802,17 @@ VRFrameInfo::Update(const gfx::VRDisplay
 
   const gfx::VRFieldOfView leftFOV = aInfo.mEyeFOV[gfx::VRDisplayInfo::Eye_Left];
   mLeftProjection = leftFOV.ConstructProjectionMatrix(aDepthNear, aDepthFar, true);
   const gfx::VRFieldOfView rightFOV = aInfo.mEyeFOV[gfx::VRDisplayInfo::Eye_Right];
   mRightProjection = rightFOV.ConstructProjectionMatrix(aDepthNear, aDepthFar, true);
 }
 
 VRFrameInfo::VRFrameInfo()
+ : mTimeStampOffset(0.0f)
 {
   mVRState.Clear();
 }
 
 bool
 VRFrameInfo::IsDirty()
 {
   return mVRState.timestamp == 0;
--- a/dom/vr/VRDisplay.h
+++ b/dom/vr/VRDisplay.h
@@ -145,16 +145,23 @@ struct VRFrameInfo
   bool IsDirty();
 
   gfx::VRHMDSensorState mVRState;
   gfx::Matrix4x4 mLeftProjection;
   gfx::Matrix4x4 mLeftView;
   gfx::Matrix4x4 mRightProjection;
   gfx::Matrix4x4 mRightView;
 
+  /**
+   * In order to avoid leaking information related to the duration of
+   * the user's VR session, we re-base timestamps.
+   * mTimeStampOffset is added to the actual timestamp returned by the
+   * underlying VR platform API when returned through WebVR API's.
+   */
+  double mTimeStampOffset;
 };
 
 class VRFrameData final : public nsWrapperCache
 {
 public:
   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(VRFrameData)
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(VRFrameData)
 
new file mode 100644
--- /dev/null
+++ b/dom/vr/VRServiceTest.cpp
@@ -0,0 +1,348 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/VRServiceTest.h"
+#include "mozilla/dom/VRServiceTestBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(VRMockDisplay)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(VRMockDisplay,
+                                                  DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(VRMockDisplay,
+                                                DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(VRMockDisplay)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(VRMockDisplay, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(VRMockDisplay, DOMEventTargetHelper)
+
+VRMockDisplay::VRMockDisplay(const nsCString& aID, uint32_t aDeviceID)
+ : mDeviceID(aDeviceID)
+{
+  mDisplayInfo.mDisplayName = aID;
+  mDisplayInfo.mType = VRDeviceType::Puppet;
+  mDisplayInfo.mIsConnected = true;
+  mDisplayInfo.mIsMounted = false;
+  mDisplayInfo.mCapabilityFlags = VRDisplayCapabilityFlags::Cap_None |
+                                  VRDisplayCapabilityFlags::Cap_Orientation |
+                                  VRDisplayCapabilityFlags::Cap_AngularAcceleration |
+                                  VRDisplayCapabilityFlags::Cap_Position |
+                                  VRDisplayCapabilityFlags::Cap_LinearAcceleration |
+                                  VRDisplayCapabilityFlags::Cap_External |
+                                  VRDisplayCapabilityFlags::Cap_Present |
+                                  VRDisplayCapabilityFlags::Cap_StageParameters |
+                                  VRDisplayCapabilityFlags::Cap_MountDetection;
+}
+
+JSObject*
+VRMockDisplay::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return VRMockDisplayBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void VRMockDisplay::SetEyeResolution(unsigned long aRenderWidth, unsigned long aRenderHeight)
+{
+  mDisplayInfo.mEyeResolution.width = aRenderWidth;
+  mDisplayInfo.mEyeResolution.height = aRenderHeight;
+}
+
+void
+VRMockDisplay::SetEyeParameter(VREye aEye, double aOffsetX, double aOffsetY,
+                               double aOffsetZ, double aUpDegree, double aRightDegree,
+                               double aDownDegree, double aLeftDegree)
+{
+  uint32_t eye = static_cast<uint32_t>(aEye);
+  mDisplayInfo.mEyeFOV[eye] = gfx ::VRFieldOfView(aUpDegree, aRightDegree,
+                                                  aRightDegree, aLeftDegree);
+  mDisplayInfo.mEyeTranslation[eye].x = aOffsetX;
+  mDisplayInfo.mEyeTranslation[eye].y = aOffsetY;
+  mDisplayInfo.mEyeTranslation[eye].z = aOffsetZ;
+}
+
+void
+VRMockDisplay::SetPose(const Nullable<Float32Array>& aPosition,
+                       const Nullable<Float32Array>& aLinearVelocity,
+                       const Nullable<Float32Array>& aLinearAcceleration,
+                       const Nullable<Float32Array>& aOrientation,
+                       const Nullable<Float32Array>& aAngularVelocity,
+                       const Nullable<Float32Array>& aAngularAcceleration)
+{
+  mSensorState.flags = VRDisplayCapabilityFlags::Cap_Orientation |
+                       VRDisplayCapabilityFlags::Cap_Position |
+                       VRDisplayCapabilityFlags::Cap_AngularAcceleration |
+                       VRDisplayCapabilityFlags::Cap_LinearAcceleration |
+                       VRDisplayCapabilityFlags::Cap_External |
+                       VRDisplayCapabilityFlags::Cap_MountDetection |
+                       VRDisplayCapabilityFlags::Cap_Present;
+
+  if (!aOrientation.IsNull()) {
+    const Float32Array& value = aOrientation.Value();
+    value.ComputeLengthAndData();
+    MOZ_ASSERT(value.Length() == 4);
+    mSensorState.orientation[0] = value.Data()[0];
+    mSensorState.orientation[1] = value.Data()[1];
+    mSensorState.orientation[2] = value.Data()[2];
+    mSensorState.orientation[3] = value.Data()[3];
+  }
+  if (!aAngularVelocity.IsNull()) {
+    const Float32Array& value = aAngularVelocity.Value();
+    value.ComputeLengthAndData();
+    MOZ_ASSERT(value.Length() == 3);
+    mSensorState.angularVelocity[0] = value.Data()[0];
+    mSensorState.angularVelocity[1] = value.Data()[1];
+    mSensorState.angularVelocity[2] = value.Data()[2];
+  }
+  if (!aAngularAcceleration.IsNull()) {
+    const Float32Array& value = aAngularAcceleration.Value();
+    value.ComputeLengthAndData();
+    MOZ_ASSERT(value.Length() == 3);
+    mSensorState.angularAcceleration[0] = value.Data()[0];
+    mSensorState.angularAcceleration[1] = value.Data()[1];
+    mSensorState.angularAcceleration[2] = value.Data()[2];
+  }
+  if (!aPosition.IsNull()) {
+    const Float32Array& value = aPosition.Value();
+    value.ComputeLengthAndData();
+    MOZ_ASSERT(value.Length() == 3);
+    mSensorState.position[0] = value.Data()[0];
+    mSensorState.position[1] = value.Data()[1];
+    mSensorState.position[2] = value.Data()[2];
+  }
+  if (!aLinearVelocity.IsNull()) {
+    const Float32Array& value = aLinearVelocity.Value();
+    value.ComputeLengthAndData();
+    MOZ_ASSERT(value.Length() == 3);
+    mSensorState.linearVelocity[0] = value.Data()[0];
+    mSensorState.linearVelocity[1] = value.Data()[1];
+    mSensorState.linearVelocity[2] = value.Data()[2];
+  }
+  if (!aLinearAcceleration.IsNull()) {
+    const Float32Array& value = aLinearAcceleration.Value();
+    value.ComputeLengthAndData();
+    MOZ_ASSERT(value.Length() == 3);
+    mSensorState.linearAcceleration[0] = value.Data()[0];
+    mSensorState.linearAcceleration[1] = value.Data()[1];
+    mSensorState.linearAcceleration[2] = value.Data()[2];
+  }
+}
+
+void
+VRMockDisplay::Update()
+{
+  gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
+
+  vm->SendSetSensorStateToMockDisplay(mDeviceID, mSensorState);
+  vm->SendSetDisplayInfoToMockDisplay(mDeviceID, mDisplayInfo);
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(VRMockController)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(VRMockController,
+                                                  DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(VRMockController,
+                                                DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(VRMockController)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(VRMockController, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(VRMockController, DOMEventTargetHelper)
+
+VRMockController::VRMockController(const nsCString& aID, uint32_t aDeviceID)
+ : mID(aID), mDeviceID(aDeviceID)
+{
+}
+
+JSObject*
+VRMockController::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return VRMockControllerBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+VRMockController::NewButtonEvent(unsigned long aButton, bool aPressed)
+{
+  gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
+  vm->SendNewButtonEventToMockController(mDeviceID, aButton, aPressed);
+}
+
+void
+VRMockController::NewAxisMoveEvent(unsigned long aAxis, double aValue)
+{
+  gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
+  vm->SendNewAxisMoveEventToMockController(mDeviceID, aAxis, aValue);
+}
+
+void
+VRMockController::NewPoseMove(const Nullable<Float32Array>& aPosition,
+                              const Nullable<Float32Array>& aLinearVelocity,
+                              const Nullable<Float32Array>& aLinearAcceleration,
+                              const Nullable<Float32Array>& aOrientation,
+                              const Nullable<Float32Array>& aAngularVelocity,
+                              const Nullable<Float32Array>& aAngularAcceleration)
+{
+  gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
+  GamepadPoseState poseState;
+
+  poseState.flags = GamepadCapabilityFlags::Cap_Orientation |
+                    GamepadCapabilityFlags::Cap_Position |
+                    GamepadCapabilityFlags::Cap_AngularAcceleration |
+                    GamepadCapabilityFlags::Cap_LinearAcceleration;
+  if (!aOrientation.IsNull()) {
+    const Float32Array& value = aOrientation.Value();
+    value.ComputeLengthAndData();
+    MOZ_ASSERT(value.Length() == 4);
+    poseState.orientation[0] = value.Data()[0];
+    poseState.orientation[1] = value.Data()[1];
+    poseState.orientation[2] = value.Data()[2];
+    poseState.orientation[3] = value.Data()[3];
+  }
+  if (!aPosition.IsNull()) {
+    const Float32Array& value = aPosition.Value();
+    value.ComputeLengthAndData();
+    MOZ_ASSERT(value.Length() == 3);
+    poseState.position[0] = value.Data()[0];
+    poseState.position[1] = value.Data()[1];
+    poseState.position[2] = value.Data()[2];
+  }
+  if (!aAngularVelocity.IsNull()) {
+    const Float32Array& value = aAngularVelocity.Value();
+    value.ComputeLengthAndData();
+    MOZ_ASSERT(value.Length() == 3);
+    poseState.angularVelocity[0] = value.Data()[0];
+    poseState.angularVelocity[1] = value.Data()[1];
+    poseState.angularVelocity[2] = value.Data()[2];
+  }
+  if (!aAngularAcceleration.IsNull()) {
+    const Float32Array& value = aAngularAcceleration.Value();
+    value.ComputeLengthAndData();
+    MOZ_ASSERT(value.Length() == 3);
+    poseState.angularAcceleration[0] = value.Data()[0];
+    poseState.angularAcceleration[1] = value.Data()[1];
+    poseState.angularAcceleration[2] = value.Data()[2];
+  }
+  if (!aLinearVelocity.IsNull()) {
+    const Float32Array& value = aLinearVelocity.Value();
+    value.ComputeLengthAndData();
+    MOZ_ASSERT(value.Length() == 3);
+    poseState.linearVelocity[0] = value.Data()[0];
+    poseState.linearVelocity[1] = value.Data()[1];
+    poseState.linearVelocity[2] = value.Data()[2];
+  }
+  if (!aLinearAcceleration.IsNull()) {
+    const Float32Array& value = aLinearAcceleration.Value();
+    value.ComputeLengthAndData();
+    MOZ_ASSERT(value.Length() == 3);
+    poseState.linearAcceleration[0] = value.Data()[0];
+    poseState.linearAcceleration[1] = value.Data()[1];
+    poseState.linearAcceleration[2] = value.Data()[2];
+  }
+  vm->SendNewPoseMoveToMockController(mDeviceID, poseState);
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(VRServiceTest)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(VRServiceTest,
+                                                  DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(VRServiceTest,
+                                                DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(VRServiceTest)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(VRServiceTest, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(VRServiceTest, DOMEventTargetHelper)
+
+
+JSObject*
+VRServiceTest::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return VRServiceTestBinding::Wrap(aCx, this, aGivenProto);
+}
+
+// static
+already_AddRefed<VRServiceTest>
+VRServiceTest::CreateTestService(nsPIDOMWindowInner* aWindow)
+{
+  MOZ_ASSERT(aWindow);
+  RefPtr<VRServiceTest> service = new VRServiceTest(aWindow);
+  return service.forget();
+}
+
+VRServiceTest::VRServiceTest(nsPIDOMWindowInner* aWindow)
+  : mWindow(aWindow),
+    mShuttingDown(false)
+{
+  gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
+  vm->SendCreateVRTestSystem();
+}
+
+VRServiceTest::~VRServiceTest()
+{}
+
+void
+VRServiceTest::Shutdown()
+{
+  MOZ_ASSERT(!mShuttingDown);
+  mShuttingDown = true;
+  mWindow = nullptr;
+}
+
+already_AddRefed<Promise>
+VRServiceTest::AttachVRDisplay(const nsAString& aID, ErrorResult& aRv)
+{
+  if (mShuttingDown) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
+
+  RefPtr<Promise> p = Promise::Create(go, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
+  vm->CreateVRServiceTestDisplay(nsCString(ToNewUTF8String(aID)), p);
+
+  return p.forget();
+}
+
+already_AddRefed<Promise>
+VRServiceTest::AttachVRController(const nsAString& aID, ErrorResult& aRv)
+{
+  if (mShuttingDown) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
+
+  RefPtr<Promise> p = Promise::Create(go, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
+  vm->CreateVRServiceTestController(nsCString(ToNewUTF8String(aID)), p);
+
+  return p.forget();
+}
+
+} // namespace dom
+} // namespace mozilla
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/vr/VRServiceTest.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_VRServiceTest_h_
+#define mozilla_dom_VRServiceTest_h_
+
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/VRServiceTestBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+class VRMockDisplay final : public DOMEventTargetHelper
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(VRMockDisplay, DOMEventTargetHelper)
+
+  VRMockDisplay(const nsCString& aID, uint32_t aDeviceID);
+  void SetEyeParameter(VREye aEye, double aOffsetX, double aOffsetY, double aOffsetZ,
+                       double aUpDegree, double aRightDegree,
+                       double aDownDegree, double aLeftDegree);
+  void SetEyeResolution(unsigned long aRenderWidth, unsigned long aRenderHeight);
+  void SetPose(const Nullable<Float32Array>& aPosition, const Nullable<Float32Array>& aLinearVelocity,
+               const Nullable<Float32Array>& aLinearAcceleration, const Nullable<Float32Array>& aOrientation,
+               const Nullable<Float32Array>& aAngularVelocity, const Nullable<Float32Array>& aAngularAcceleration);
+  void Update();
+  virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+private:
+  ~VRMockDisplay() = default;
+
+  uint32_t mDeviceID;
+  gfx::VRDisplayInfo mDisplayInfo;
+  gfx::VRHMDSensorState mSensorState;
+};
+
+class VRMockController : public DOMEventTargetHelper
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(VRMockController, DOMEventTargetHelper)
+
+  VRMockController(const nsCString& aID, uint32_t aDeviceID);
+  void NewButtonEvent(unsigned long aButton, bool aPressed);
+  void NewAxisMoveEvent(unsigned long aAxis, double aValue);
+  void NewPoseMove(const Nullable<Float32Array>& aPosition, const Nullable<Float32Array>& aLinearVelocity,
+                   const Nullable<Float32Array>& aLinearAcceleration, const Nullable<Float32Array>& aOrientation,
+                   const Nullable<Float32Array>& aAngularVelocity, const Nullable<Float32Array>& aAngularAcceleration);
+  virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+private:
+  ~VRMockController() = default;
+
+  nsCString mID;
+  uint32_t mDeviceID;
+};
+
+class VRServiceTest final : public DOMEventTargetHelper
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(VRServiceTest, DOMEventTargetHelper)
+
+  already_AddRefed<Promise> AttachVRDisplay(const nsAString& aID, ErrorResult& aRv);
+  already_AddRefed<Promise> AttachVRController(const nsAString& aID, ErrorResult& aRv);
+  void Shutdown();
+
+  static already_AddRefed<VRServiceTest> CreateTestService(nsPIDOMWindowInner* aWindow);
+  virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+private:
+  explicit VRServiceTest(nsPIDOMWindowInner* aWindow);
+  ~VRServiceTest();
+
+  nsCOMPtr<nsPIDOMWindowInner> mWindow;
+  bool mShuttingDown;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_VRServiceTest_h_
\ No newline at end of file
--- a/dom/vr/moz.build
+++ b/dom/vr/moz.build
@@ -6,22 +6,26 @@
 
 with Files("**"):
     BUG_COMPONENT = ("Core", "DOM")
 
 EXPORTS.mozilla.dom += [
     'VRDisplay.h',
     'VRDisplayEvent.h',
     'VREventObserver.h',
+    'VRServiceTest.h'
     ]
 
 UNIFIED_SOURCES = [
     'VRDisplay.cpp',
     'VRDisplayEvent.cpp',
     'VREventObserver.cpp',
+    'VRServiceTest.cpp'
     ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 LOCAL_INCLUDES += [
     '/dom/base'
 ]
+
+MOCHITEST_MANIFESTS += ['test/mochitest.ini']
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/vr/test/VRSimulationDriver.js
@@ -0,0 +1,45 @@
+
+var VRServiceTest;
+
+var VRSimulationDriver = (function() {
+"use strict";
+
+var AttachWebVRDisplay = function() {
+  return VRServiceTest.attachVRDisplay("VRDisplayTest");
+};
+
+var SetVRDisplayPose = function(vrDisplay, position,
+                                linearVelocity, linearAcceleration,
+                                orientation, angularVelocity,
+                                angularAcceleration) {
+  vrDisplay.setPose(position, linearVelocity, linearAcceleration,
+                    orientation, angularVelocity, angularAcceleration);
+};
+
+var SetEyeResolution = function(width, height) {
+  vrDisplay.setEyeResolution(width, height);
+}
+
+var SetEyeParameter = function(vrDisplay, eye, offsetX, offsetY, offsetZ,
+                               upDegree, rightDegree, downDegree, leftDegree) {
+  vrDisplay.setEyeParameter(eye, offsetX, offsetY, offsetZ, upDegree, rightDegree,
+                            downDegree, leftDegree);
+}
+
+var UpdateVRDisplay = function(vrDisplay) {
+  vrDisplay.update();
+}
+
+var API = {
+  AttachWebVRDisplay: AttachWebVRDisplay,
+  SetVRDisplayPose: SetVRDisplayPose,
+  SetEyeResolution: SetEyeResolution,
+  SetEyeParameter: SetEyeParameter,
+  UpdateVRDisplay: UpdateVRDisplay,
+
+  none: false
+};
+
+return API;
+
+}());
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/vr/test/WebVRHelpers.js
@@ -0,0 +1,21 @@
+
+var WebVRHelpers = (function() {
+"use strict";
+
+var RequestPresentOnVRDisplay = function(vrDisplay, vrLayers, callback) {
+  if (callback) {
+    callback();
+  }
+
+  return vrDisplay.requestPresent(vrLayers);
+};
+
+var API = {
+  RequestPresentOnVRDisplay: RequestPresentOnVRDisplay,
+
+  none: false
+};
+
+return API;
+
+}());
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/vr/test/mochitest.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+support-files =
+  VRSimulationDriver.js
+  requestPresent.js
+  runVRTest.js
+  WebVRHelpers.js
+
+[test_vrDisplay_requestPresent.html]
+skip-if = true
new file mode 100644
--- /dev/null
+++ b/dom/vr/test/requestPresent.js
@@ -0,0 +1,34 @@
+// requestPresent.js
+//
+// This file provides helpers for testing VRDisplay requestPresent.
+
+function setupVRDisplay(test) {
+  assert_equals(typeof (navigator.getVRDisplays), "function", "'navigator.getVRDisplays()' must be defined.");
+  return VRSimulationDriver.AttachWebVRDisplay().then(() => {
+    return navigator.getVRDisplays();
+  }).then((displays) => {
+    assert_equals(displays.length, 1, "displays.length must be one after attach.");
+    vrDisplay = displays[0];
+    return validateNewVRDisplay(test, vrDisplay);
+  });
+}
+
+// Validate the settings off a freshly created VRDisplay (prior to calling
+// requestPresent).
+function validateNewVRDisplay(test, display) {
+  assert_true(display.capabilities.canPresent, "display.capabilities.canPresent must always be true for HMDs.");
+  assert_equals(display.capabilities.maxLayers, 1, "display.capabilities.maxLayers must always be 1 when display.capabilities.canPresent is true for current spec revision.");
+  assert_false(display.isPresenting, "display.isPresenting must be false before calling requestPresent.");
+  assert_equals(display.getLayers().length, 0, "display.getLayers() should have no layers if not presenting.");
+  var promise = display.exitPresent();
+  return promise_rejects(test, null, promise);
+}
+
+// Validate the settings off a VRDisplay after requestPresent promise is
+// rejected or after exitPresent is fulfilled.
+function validateDisplayNotPresenting(test, display) {
+  assert_false(display.isPresenting, "display.isPresenting must be false if requestPresent is rejected or after exitPresent is fulfilled.");
+  assert_equals(display.getLayers().length, 0, "display.getLayers() should have no layers if requestPresent is rejected  or after exitPresent is fulfilled.");
+  var promise = display.exitPresent();
+  return promise_rejects(test, null, promise);
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/vr/test/runVRTest.js
@@ -0,0 +1,9 @@
+function runVRTest(callback) {
+  SpecialPowers.pushPrefEnv({"set" : [["dom.vr.enabled", true],
+                                      ["dom.vr.puppet.enabled", true],
+                                      ["dom.vr.test.enabled", true]]},
+  () => {
+    VRServiceTest = navigator.requestVRServiceTest();
+    callback();
+  });
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/vr/test/test_vrDisplay_requestPresent.html
@@ -0,0 +1,130 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>VRDisplay RequestPresent</title>
+    <meta name="timeout" content="long"/>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="VRSimulationDriver.js"></script>
+    <script src="WebVRHelpers.js"></script>
+    <script src="requestPresent.js"></script>
+    <script src="runVRTest.js"></script>
+</head>
+<body id="body">
+    <canvas id="webglCanvas"></canvas>
+    <div id="testDiv"></div>
+    <script>
+        "use strict";
+        var vrDisplay;
+        var canvas = document.getElementById('webglCanvas');
+        var div = document.getElementById('testDiv');
+        function startTest() {
+          promise_test((test) => {
+            return setupVRDisplay(test).then(() => {
+              return promise_rejects(test, null, WebVRHelpers.RequestPresentOnVRDisplay(vrDisplay, [{}]));
+            }).then(() => {
+              return validateDisplayNotPresenting(test, vrDisplay);
+            });
+          }, "WebVR requestPresent rejected with empty frames");
+
+          promise_test((test) => {
+            return setupVRDisplay(test).then(() => {
+              return promise_rejects(test, null, WebVRHelpers.RequestPresentOnVRDisplay(vrDisplay, [{ source: canvas, leftBounds: [0.0, 0.0] }]));
+            }).then(() => {
+              return validateDisplayNotPresenting(test, vrDisplay);
+            });
+          }, "WebVR requestPresent rejected with incorrect bounds (bounds arrays must be 0 or 4 long)");
+
+          promise_test((test) => {
+            return setupVRDisplay(test).then(() => {
+              return promise_rejects(test, null, WebVRHelpers.RequestPresentOnVRDisplay(vrDisplay, [{ source: div }]));
+            }).then(() => {
+              return validateDisplayNotPresenting(test, vrDisplay);
+            });
+          }, "WebVR requestPresent rejected with invalid source (must be canvas element)");
+
+          promise_test((test) => {
+            return setupVRDisplay(test).then(() => {
+              return promise_rejects(test, null, WebVRHelpers.RequestPresentOnVRDisplay(vrDisplay, [{ source: canvas, leftBounds: [div] }]));
+            }).then(() => {
+              return validateDisplayNotPresenting(test, vrDisplay);
+            });
+          }, "WebVR requestPresent rejected with invalid bounds data type (must be able to convert to float)");
+
+          const invalidBounds = [
+            [2.0, 0.0, 0.0, 0.0],
+            [0.0, 2.0, 0.0, 0.0],
+            [0.0, 0.0, 2.0, 0.0],
+            [0.0, 0.0, 0.0, 2.0],
+            [-1.0, 0.0, 0.0, 0.0],
+            [0.0, -1.0, 0.0, 0.0],
+            [0.0, 0.0, -1.0, 0.0],
+            [0.0, 0.0, 0.0, -1.0]];
+
+          invalidBounds.forEach((bound) => {
+            promise_test((test) => {
+              return setupVRDisplay(test).then(() => {
+                return promise_rejects(test, null, WebVRHelpers.RequestPresentOnVRDisplay(vrDisplay, [{ source: canvas, leftBounds: bound }]));
+              }).then(() => {
+                return validateDisplayNotPresenting(test, vrDisplay);
+              });
+            }, "WebVR requestPresent rejected with bounds in invalid range: [" + bound + "]");
+          });
+
+          promise_test((test) => {
+            return setupVRDisplay(test).then(() => {
+              var promise = vrDisplay.requestPresent({ source: canvas });
+              return promise_rejects(test, null, promise);
+            }).then(() => {
+              return validateDisplayNotPresenting(test, vrDisplay);
+            });
+          }, "WebVR requestPresent rejected without user initiated action");
+
+          promise_test((test) => {
+            return setupVRDisplay(test).then(() => {
+              return promise_rejects(test, null, WebVRHelpers.RequestPresentOnVRDisplay(vrDisplay, [{ source: canvas }, { source: canvas }]));
+            }).then(() => {
+              return validateDisplayNotPresenting(test, vrDisplay);
+            });
+          }, "WebVR requestPresent rejected with more frames than max layers");
+
+          promise_test((test) => {
+            return setupVRDisplay(test).then(() => {
+              function requestAgain() {
+                // Callback for immediate requestPresent call for further testing.
+                // Cache this promise on global object since it seems to be the only object
+                // in scope across calls.
+                window.promiseSecond = vrDisplay.requestPresent([{ source: canvas }]);
+              }
+              return WebVRHelpers.RequestPresentOnVRDisplay(vrDisplay, [{ source: canvas }], requestAgain);
+            }).then(() => {
+              // First promise succeeded
+              assert_true(vrDisplay.isPresenting, "First promise should successfully fulfill");
+              // Now, validate that the subsequent requestPresent was rejected
+              return promise_rejects(test, null, window.promiseSecond);
+            }).then(() => {
+              delete window.promiseSecond;
+              assert_true(vrDisplay.isPresenting, "Should still be presenting after rejected second promise");
+              return vrDisplay.exitPresent();
+            });
+          }, "WebVR requestPresent fails while another one is in progress");
+
+          promise_test((test) => {
+            return setupVRDisplay(test).then(() => {
+              return WebVRHelpers.RequestPresentOnVRDisplay(vrDisplay, [{ source: canvas }]);
+            }).then(() => {
+              assert_true(vrDisplay.isPresenting, "vrDisplay.isPresenting must be true if requestPresent is fulfilled.");
+              assert_equals(vrDisplay.getLayers().length, 1, "vrDisplay.getLayers() should return one layer.");
+                return vrDisplay.exitPresent();
+            }).then(() => {
+              assert_false(vrDisplay.isPresenting, "vrDisplay.isPresenting must be false if exitPresent is fulfilled.");
+              // exitPresent() should reject since we are no longer presenting.
+              return promise_rejects(test, null, vrDisplay.exitPresent());
+            });
+          }, "WebVR requestPresent fulfilled");
+        }
+
+        runVRTest(startTest);
+    </script>
+</body>
+</html>
\ No newline at end of file
--- a/dom/webidl/Navigator.webidl
+++ b/dom/webidl/Navigator.webidl
@@ -284,16 +284,20 @@ partial interface Navigator {
 
 partial interface Navigator {
   [Throws, Pref="dom.vr.enabled"]
   Promise<sequence<VRDisplay>> getVRDisplays();
   // TODO: Use FrozenArray once available. (Bug 1236777)
   [Frozen, Cached, Pure, Pref="dom.vr.enabled"]
   readonly attribute sequence<VRDisplay> activeVRDisplays;
 };
+partial interface Navigator {
+  [Pref="dom.vr.test.enabled"]
+  VRServiceTest requestVRServiceTest();
+};
 
 #ifdef MOZ_TIME_MANAGER
 // nsIDOMMozNavigatorTime
 partial interface Navigator {
   [Throws, ChromeOnly, UnsafeInPrerendering]
   readonly attribute MozTimeManager mozTime;
 };
 #endif // MOZ_TIME_MANAGER
new file mode 100644
--- /dev/null
+++ b/dom/webidl/VRServiceTest.webidl
@@ -0,0 +1,38 @@
+/* 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/.
+ *
+ * This WebIDL is just for WebVR testing.
+ */
+
+[Pref="dom.vr.test.enabled",
+ HeaderFile="mozilla/dom/VRServiceTest.h"]
+interface VRMockDisplay {
+  void setEyeResolution(unsigned long aRenderWidth, unsigned long aRenderHeight);
+  void setEyeParameter(VREye eye, double offsetX, double offsetY, double offsetZ,
+                       double upDegree, double rightDegree,
+                       double downDegree, double leftDegree);
+  void setPose(Float32Array? position, Float32Array? linearVelocity,
+               Float32Array? linearAcceleration, Float32Array? orientation,
+               Float32Array? angularVelocity, Float32Array? angularAcceleration);
+  void update();
+};
+
+[Pref="dom.vr.test.enabled",
+ HeaderFile="mozilla/dom/VRServiceTest.h"]
+interface VRMockController {
+  void newButtonEvent(unsigned long button, boolean pressed);
+  void newAxisMoveEvent(unsigned long axis, double value);
+  void newPoseMove(Float32Array? position, Float32Array? linearVelocity,
+                   Float32Array? linearAcceleration, Float32Array? orientation,
+                   Float32Array? angularVelocity, Float32Array? angularAcceleration);
+};
+
+[Pref="dom.vr.test.enabled",
+ HeaderFile="mozilla/dom/VRServiceTest.h"]
+interface VRServiceTest {
+  [Throws, NewObject]
+  Promise<VRMockDisplay> attachVRDisplay(DOMString id);
+  [Throws, NewObject]
+  Promise<VRMockController> attachVRController(DOMString id);
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -930,16 +930,17 @@ WEBIDL_FILES = [
     'URLSearchParams.webidl',
     'ValidityState.webidl',
     'VideoPlaybackQuality.webidl',
     'VideoStreamTrack.webidl',
     'VideoTrack.webidl',
     'VideoTrackList.webidl',
     'VRDisplay.webidl',
     'VRDisplayEvent.webidl',
+    'VRServiceTest.webidl',
     'VTTCue.webidl',
     'VTTRegion.webidl',
     'WaveShaperNode.webidl',
     'WebAuthentication.webidl',
     'WebComponents.webidl',
     'WebGL2RenderingContext.webidl',
     'WebGLRenderingContext.webidl',
     'WebKitCSSMatrix.webidl',
--- a/gfx/ipc/GfxMessageUtils.h
+++ b/gfx/ipc/GfxMessageUtils.h
@@ -1322,17 +1322,17 @@ struct ParamTraits<mozilla::Array<T, Len
   static void Write(Message* aMsg, const paramType& aParam) {
     for (size_t i = 0; i < Length; i++) {
       WriteParam(aMsg, aParam[i]);
     }
   }
 
   static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) {
     for (size_t i = 0; i < Length; i++) {
-      if (!ReadParam(aMsg, aIter, &aResult[i])) {
+      if (!ReadParam<T>(aMsg, aIter, &aResult->operator[](i))) {
         return false;
       }
     }
     return true;
   }
 };
 
 template <>
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -320,17 +320,17 @@ private:
 
   DECL_GFX_PREF(Live, "dom.ipc.plugins.asyncdrawing.enabled",  PluginAsyncDrawingEnabled, bool, false);
   DECL_GFX_PREF(Live, "dom.meta-viewport.enabled",             MetaViewportEnabled, bool, false);
   DECL_GFX_PREF(Once, "dom.vr.enabled",                        VREnabled, bool, false);
   DECL_GFX_PREF(Once, "dom.vr.oculus.enabled",                 VROculusEnabled, bool, true);
   DECL_GFX_PREF(Once, "dom.vr.openvr.enabled",                 VROpenVREnabled, bool, false);
   DECL_GFX_PREF(Once, "dom.vr.osvr.enabled",                   VROSVREnabled, bool, false);
   DECL_GFX_PREF(Live, "dom.vr.poseprediction.enabled",         VRPosePredictionEnabled, bool, false);
-  DECL_GFX_PREF(Once, "dom.vr.puppet.enabled",                 VRPuppetEnabled, bool, false);
+  DECL_GFX_PREF(Live, "dom.vr.puppet.enabled",                 VRPuppetEnabled, bool, false);
   DECL_GFX_PREF(Live, "dom.w3c_pointer_events.enabled",        PointerEventsEnabled, bool, false);
   DECL_GFX_PREF(Live, "dom.w3c_touch_events.enabled",          TouchEventsEnabled, int32_t, 0);
 
   DECL_GFX_PREF(Live, "general.smoothScroll",                  SmoothScrollEnabled, bool, true);
   DECL_GFX_PREF(Live, "general.smoothScroll.currentVelocityWeighting",
                 SmoothScrollCurrentVelocityWeighting, float, 0.25);
   DECL_GFX_PREF(Live, "general.smoothScroll.durationToIntervalRatio",
                 SmoothScrollDurationToIntervalRatio, int32_t, 200);
--- a/gfx/vr/VRManager.cpp
+++ b/gfx/vr/VRManager.cpp
@@ -43,16 +43,17 @@ VRManager::ManagerInit()
   if (sVRManagerSingleton == nullptr) {
     sVRManagerSingleton = new VRManager();
     ClearOnShutdown(&sVRManagerSingleton);
   }
 }
 
 VRManager::VRManager()
   : mInitialized(false)
+  , mVRTestSystemCreated(false)
 {
   MOZ_COUNT_CTOR(VRManager);
   MOZ_ASSERT(sVRManagerSingleton == nullptr);
 
   RefPtr<VRSystemManager> mgr;
 
   /**
    * We must add the VRDisplayManager's to mManagers in a careful order to
@@ -84,20 +85,16 @@ VRManager::VRManager()
   }
 
   // OSVR is cross platform compatible
   mgr = VRSystemManagerOSVR::Create();
   if (mgr) {
       mManagers.AppendElement(mgr);
   }
 #endif
-  mgr = VRSystemManagerPuppet::Create();
-  if (mgr) {
-    mManagers.AppendElement(mgr);
-  }
   // Enable gamepad extensions while VR is enabled.
   // Preference only can be set at the Parent process.
   if (XRE_IsParentProcess() && gfxPrefs::VREnabled()) {
     Preferences::SetBool("dom.gamepad.extensions.enabled", true);
   }
 }
 
 VRManager::~VRManager()
@@ -384,16 +381,31 @@ void
 VRManager::RemoveControllers()
 {
   for (uint32_t i = 0; i < mManagers.Length(); ++i) {
     mManagers[i]->RemoveControllers();
   }
   mVRControllers.Clear();
 }
 
+void
+VRManager::CreateVRTestSystem()
+{
+  if (mVRTestSystemCreated) {
+    return;
+  }
+
+  RefPtr<VRSystemManager> mgr = VRSystemManagerPuppet::Create();
+  if (mgr) {
+    mgr->Init();
+    mManagers.AppendElement(mgr);
+    mVRTestSystemCreated = true;
+  }
+}
+
 template<class T>
 void
 VRManager::NotifyGamepadChange(const T& aInfo)
 {
   dom::GamepadChangeEvent e(aInfo);
 
   for (auto iter = mVRManagerParents.Iter(); !iter.Done(); iter.Next()) {
     Unused << iter.Get()->GetKey()->SendGamepadUpdate(e);
--- a/gfx/vr/VRManager.h
+++ b/gfx/vr/VRManager.h
@@ -43,16 +43,17 @@ public:
   RefPtr<gfx::VRDisplayHost> GetDisplay(const uint32_t& aDisplayID);
   void GetVRDisplayInfo(nsTArray<VRDisplayInfo>& aDisplayInfo);
 
   void SubmitFrame(VRLayerParent* aLayer, layers::PTextureParent* aTexture,
                    const gfx::Rect& aLeftEyeRect,
                    const gfx::Rect& aRightEyeRect);
   RefPtr<gfx::VRControllerHost> GetController(const uint32_t& aControllerID);
   void GetVRControllerInfo(nsTArray<VRControllerInfo>& aControllerInfo);
+  void CreateVRTestSystem();
 
 protected:
   VRManager();
   ~VRManager();
 
 private:
   RefPtr<layers::TextureHost> mLastFrame;
 
@@ -72,14 +73,15 @@ private:
   VRDisplayHostHashMap mVRDisplays;
 
   typedef nsRefPtrHashtable<nsUint32HashKey, gfx::VRControllerHost> VRControllerHostHashMap;
   VRControllerHostHashMap mVRControllers;
 
   Atomic<bool> mInitialized;
 
   TimeStamp mLastRefreshTime;
+  bool mVRTestSystemCreated;
 };
 
 } // namespace gfx
 } // namespace mozilla
 
 #endif // GFX_VR_MANAGER_H
--- a/gfx/vr/gfxVROpenVR.cpp
+++ b/gfx/vr/gfxVROpenVR.cpp
@@ -292,17 +292,25 @@ VRDisplayOpenVR::GetSensorState(double t
   PollEvents();
 
   ::vr::TrackedDevicePose_t poses[::vr::k_unMaxTrackedDeviceCount];
   // Note: We *must* call WaitGetPoses in order for any rendering to happen at all
   mVRCompositor->WaitGetPoses(poses, ::vr::k_unMaxTrackedDeviceCount, nullptr, 0);
 
   VRHMDSensorState result;
   result.Clear();
-  result.timestamp = PR_Now();
+
+  ::vr::Compositor_FrameTiming timing;
+  timing.m_nSize = sizeof(::vr::Compositor_FrameTiming);
+  if (mVRCompositor->GetFrameTiming(&timing)) {
+    result.timestamp = timing.m_flSystemTimeInSeconds;
+  } else {
+    // This should not happen, but log it just in case
+    NS_WARNING("OpenVR - IVRCompositor::GetFrameTiming failed");
+  }
 
   if (poses[::vr::k_unTrackedDeviceIndex_Hmd].bDeviceIsConnected &&
       poses[::vr::k_unTrackedDeviceIndex_Hmd].bPoseIsValid &&
       poses[::vr::k_unTrackedDeviceIndex_Hmd].eTrackingResult == ::vr::TrackingResult_Running_OK)
   {
     const ::vr::TrackedDevicePose_t& pose = poses[::vr::k_unTrackedDeviceIndex_Hmd];
 
     gfx::Matrix4x4 m;
--- a/gfx/vr/gfxVRPuppet.cpp
+++ b/gfx/vr/gfxVRPuppet.cpp
@@ -309,18 +309,16 @@ VRControllerPuppet::SetAxisMove(uint32_t
 
 VRSystemManagerPuppet::VRSystemManagerPuppet()
 {
 }
 
 /*static*/ already_AddRefed<VRSystemManagerPuppet>
 VRSystemManagerPuppet::Create()
 {
-  MOZ_ASSERT(NS_IsMainThread());
-
   if (!gfxPrefs::VREnabled() || !gfxPrefs::VRPuppetEnabled()) {
     return nullptr;
   }
 
   RefPtr<VRSystemManagerPuppet> manager = new VRSystemManagerPuppet();
   return manager.forget();
 }
 
--- a/gfx/vr/ipc/PVRManager.ipdl
+++ b/gfx/vr/ipc/PVRManager.ipdl
@@ -57,29 +57,43 @@ parent:
   sync GetImmediateSensorState(uint32_t aDisplayID) returns(VRHMDSensorState aState);
   sync SetHaveEventListener(bool aHaveEventListener);
 
   async ControllerListenerAdded();
   async ControllerListenerRemoved();
   // GetControllers synchronously returns the VR controllers that have already been
   // enumerated by RefreshVRControllers() but does not enumerate new ones.
   sync GetControllers() returns(VRControllerInfo[] aControllerInfo);
+  async CreateVRTestSystem();
+  async CreateVRServiceTestDisplay(nsCString aID, uint32_t aPromiseID);
+  async CreateVRServiceTestController(nsCString aID, uint32_t aPromiseID);
+  async SetDisplayInfoToMockDisplay(uint32_t aDeviceID, VRDisplayInfo aDisplayInfo);
+  async SetSensorStateToMockDisplay(uint32_t aDeviceID, VRHMDSensorState aSensorState);
+  async NewButtonEventToMockController(uint32_t aDeviceID, long aButton,
+                                       bool aPressed);
+  async NewAxisMoveEventToMockController(uint32_t aDeviceID, long aAxis,
+                                         double aValue);
+  async NewPoseMoveToMockController(uint32_t aDeviceID, GamepadPoseState aPose);
 
 child:
 
   async ParentAsyncMessages(AsyncParentMessageData[] aMessages);
 
   // Notify children of updated VR display enumeration and details.  This will
   // be sent to all children when the parent receives RefreshDisplays, even
   // if no changes have been detected.  This ensures that Promises exposed
   // through DOM calls are always resolved.
   async UpdateDisplayInfo(VRDisplayInfo[] aDisplayUpdates);
 
   async NotifyVSync();
   async NotifyVRVSync(uint32_t aDisplayID);
   async GamepadUpdate(GamepadChangeEvent aGamepadEvent);
+  async ReplyCreateVRServiceTestDisplay(nsCString aID, uint32_t aPromiseID,
+                                        uint32_t aDeviceID);
+  async ReplyCreateVRServiceTestController(nsCString aID, uint32_t aPromiseID,
+                                           uint32_t aDeviceID);
 
   async __delete__();
 
 };
 
 } // gfx
 } // mozilla
--- a/gfx/vr/ipc/VRManagerChild.cpp
+++ b/gfx/vr/ipc/VRManagerChild.cpp
@@ -13,16 +13,17 @@
 #include "mozilla/layers/CompositorThread.h" // for CompositorThread
 #include "mozilla/dom/Navigator.h"
 #include "mozilla/dom/VREventObserver.h"
 #include "mozilla/dom/WindowBinding.h" // for FrameRequestCallback
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/layers/TextureClient.h"
 #include "nsContentUtils.h"
 #include "mozilla/dom/GamepadManager.h"
+#include "mozilla/dom/VRServiceTest.h"
 
 using layers::TextureClient;
 
 namespace {
 const nsTArray<RefPtr<dom::VREventObserver>>::index_type kNoIndex =
   nsTArray<RefPtr<dom::VREventObserver> >::NoIndex;
 } // namespace
 
@@ -38,16 +39,17 @@ void ReleaseVRManagerParentSingleton() {
 
 VRManagerChild::VRManagerChild()
   : TextureForwarder()
   , mDisplaysInitialized(false)
   , mInputFrameID(-1)
   , mMessageLoop(MessageLoop::current())
   , mFrameRequestCallbackCounter(0)
   , mBackend(layers::LayersBackend::LAYERS_NONE)
+  , mPromiseID(0)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   mStartTimeStamp = TimeStamp::Now();
 }
 
 VRManagerChild::~VRManagerChild()
 {
@@ -309,16 +311,32 @@ VRManagerChild::RefreshVRDisplaysWithCal
 {
   bool success = SendRefreshDisplays();
   if (success) {
     mNavigatorCallbacks.AppendElement(aWindowId);
   }
   return success;
 }
 
+void
+VRManagerChild::CreateVRServiceTestDisplay(const nsCString& aID, dom::Promise* aPromise)
+{
+  SendCreateVRServiceTestDisplay(aID, mPromiseID);
+  mPromiseList.Put(mPromiseID, aPromise);
+  ++mPromiseID;
+}
+
+void
+VRManagerChild::CreateVRServiceTestController(const nsCString& aID, dom::Promise* aPromise)
+{
+  SendCreateVRServiceTestController(aID, mPromiseID);
+  mPromiseList.Put(mPromiseID, aPromise);
+  ++mPromiseID;
+}
+
 int
 VRManagerChild::GetInputFrameID()
 {
   return mInputFrameID;
 }
 
 mozilla::ipc::IPCResult
 VRManagerChild::RecvParentAsyncMessages(InfallibleTArray<AsyncParentMessageData>&& aMessages)
@@ -486,16 +504,46 @@ VRManagerChild::RecvGamepadUpdate(const 
   RefPtr<GamepadManager> gamepadManager(GamepadManager::GetService());
   if (gamepadManager) {
     gamepadManager->Update(aGamepadEvent);
   }
 
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult
+VRManagerChild::RecvReplyCreateVRServiceTestDisplay(const nsCString& aID,
+                                                    const uint32_t& aPromiseID,
+                                                    const uint32_t& aDeviceID)
+{
+  RefPtr<dom::Promise> p;
+  if (!mPromiseList.Get(aPromiseID, getter_AddRefs(p))) {
+    MOZ_CRASH("We should always have a promise.");
+  }
+
+  p->MaybeResolve(new VRMockDisplay(aID, aDeviceID));
+  mPromiseList.Remove(aPromiseID);
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+VRManagerChild::RecvReplyCreateVRServiceTestController(const nsCString& aID,
+                                                       const uint32_t& aPromiseID,
+                                                       const uint32_t& aDeviceID)
+{
+  RefPtr<dom::Promise> p;
+  if (!mPromiseList.Get(aPromiseID, getter_AddRefs(p))) {
+    MOZ_CRASH("We should always have a promise.");
+  }
+
+  p->MaybeResolve(new VRMockController(aID, aDeviceID));
+  mPromiseList.Remove(aPromiseID);
+  return IPC_OK();
+}
+
 void
 VRManagerChild::RunFrameRequestCallbacks()
 {
   TimeStamp nowTime = TimeStamp::Now();
   mozilla::TimeDuration duration = nowTime - mStartTimeStamp;
   DOMHighResTimeStamp timeStamp = duration.ToMilliseconds();
 
 
--- a/gfx/vr/ipc/VRManagerChild.h
+++ b/gfx/vr/ipc/VRManagerChild.h
@@ -44,16 +44,18 @@ public:
   // Indicate that an observer wants to receive VR events.
   void AddListener(dom::VREventObserver* aObserver);
   // Indicate that an observer should no longer receive VR events.
   void RemoveListener(dom::VREventObserver* aObserver);
 
   int GetInputFrameID();
   bool GetVRDisplays(nsTArray<RefPtr<VRDisplayClient> >& aDisplays);
   bool RefreshVRDisplaysWithCallback(uint64_t aWindowId);
+  void CreateVRServiceTestDisplay(const nsCString& aID, dom::Promise* aPromise);
+  void CreateVRServiceTestController(const nsCString& aID, dom::Promise* aPromise);
 
   static void InitSameProcess();
   static void InitWithGPUProcess(Endpoint<PVRManagerChild>&& aEndpoint);
   static bool InitForContent(Endpoint<PVRManagerChild>&& aEndpoint);
   static bool ReinitForContent(Endpoint<PVRManagerChild>&& aEndpoint);
   static void ShutDown();
 
   static bool IsCreated();
@@ -112,16 +114,22 @@ protected:
 
   virtual mozilla::ipc::IPCResult RecvUpdateDisplayInfo(nsTArray<VRDisplayInfo>&& aDisplayUpdates) override;
 
   virtual mozilla::ipc::IPCResult RecvParentAsyncMessages(InfallibleTArray<AsyncParentMessageData>&& aMessages) override;
 
   virtual mozilla::ipc::IPCResult RecvNotifyVSync() override;
   virtual mozilla::ipc::IPCResult RecvNotifyVRVSync(const uint32_t& aDisplayID) override;
   virtual mozilla::ipc::IPCResult RecvGamepadUpdate(const GamepadChangeEvent& aGamepadEvent) override;
+  virtual mozilla::ipc::IPCResult RecvReplyCreateVRServiceTestDisplay(const nsCString& aID,
+                                                                      const uint32_t& aPromiseID,
+                                                                      const uint32_t& aDeviceID) override;
+  virtual mozilla::ipc::IPCResult RecvReplyCreateVRServiceTestController(const nsCString& aID,
+                                                                         const uint32_t& aPromiseID,
+                                                                         const uint32_t& aDeviceID) override;
 
   // ShmemAllocator
 
   virtual bool AllocShmem(size_t aSize,
                           ipc::SharedMemory::SharedMemoryType aType,
                           ipc::Shmem* aShmem) override;
 
   virtual bool AllocUnsafeShmem(size_t aSize,
@@ -172,16 +180,18 @@ private:
   /**
   * Hold TextureClients refs until end of their usages on host side.
   * It defer calling of TextureClient recycle callback.
   */
   nsDataHashtable<nsUint64HashKey, RefPtr<layers::TextureClient> > mTexturesWaitingRecycled;
 
   layers::LayersBackend mBackend;
   RefPtr<layers::SyncObject> mSyncObject;
+  uint32_t mPromiseID;
+  nsRefPtrHashtable<nsUint32HashKey, dom::Promise> mPromiseList;
 
   DISALLOW_COPY_AND_ASSIGN(VRManagerChild);
 };
 
 } // namespace mozilla
 } // namespace gfx
 
 #endif // MOZILLA_GFX_VR_VRMANAGERCHILD_H
--- a/gfx/vr/ipc/VRManagerParent.cpp
+++ b/gfx/vr/ipc/VRManagerParent.cpp
@@ -9,23 +9,26 @@
 #include "ipc/VRLayerParent.h"
 #include "mozilla/gfx/PVRManagerParent.h"
 #include "mozilla/ipc/ProtocolTypes.h"
 #include "mozilla/ipc/ProtocolUtils.h"       // for IToplevelProtocol
 #include "mozilla/TimeStamp.h"               // for TimeStamp
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/Unused.h"
 #include "VRManager.h"
+#include "gfxVRPuppet.h"
 
 namespace mozilla {
 using namespace layers;
 namespace gfx {
 
 VRManagerParent::VRManagerParent(ProcessId aChildProcessId, bool aIsContentChild)
   : HostIPCAllocator()
+  , mDisplayTestID(0)
+  , mControllerTestID(0)
   , mHaveEventListener(false)
   , mHaveControllerListener(false)
   , mIsContentChild(aIsContentChild)
 {
   MOZ_COUNT_CTOR(VRManagerParent);
   MOZ_ASSERT(NS_IsMainThread());
 
   SetOtherProcessId(aChildProcessId);
@@ -321,16 +324,147 @@ VRManagerParent::RecvControllerListenerR
 mozilla::ipc::IPCResult
 VRManagerParent::RecvGetControllers(nsTArray<VRControllerInfo> *aControllers)
 {
   VRManager* vm = VRManager::Get();
   vm->GetVRControllerInfo(*aControllers);
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult
+VRManagerParent::RecvCreateVRTestSystem()
+{
+  VRManager* vm = VRManager::Get();
+  vm->CreateVRTestSystem();
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+VRManagerParent::RecvCreateVRServiceTestDisplay(const nsCString& aID, const uint32_t& aPromiseID)
+{
+  nsTArray<VRDisplayInfo> displayInfoArray;
+  impl::VRDisplayPuppet* displayPuppet = nullptr;
+  VRManager* vm = VRManager::Get();
+  vm->RefreshVRDisplays();
+
+  // Get VRDisplayPuppet from VRManager
+  vm->GetVRDisplayInfo(displayInfoArray);
+  for (auto& displayInfo : displayInfoArray) {
+    if (displayInfo.GetType() == VRDeviceType::Puppet) {
+        displayPuppet = static_cast<impl::VRDisplayPuppet*>(
+                        vm->GetDisplay(displayInfo.GetDisplayID()).get());
+        break;
+    }
+  }
+
+  MOZ_ASSERT(displayPuppet);
+  MOZ_ASSERT(!mDisplayTestID); // We have only one display in VRSystemManagerPuppet.
+
+  if (!mVRDisplayTests.Get(mDisplayTestID, nullptr)) {
+    mVRDisplayTests.Put(mDisplayTestID, displayPuppet);
+  }
+
+  if (SendReplyCreateVRServiceTestDisplay(aID, aPromiseID, mDisplayTestID)) {
+    return IPC_OK();
+  }
+
+  return IPC_FAIL(this, "SendReplyCreateVRServiceTestController fail");
+}
+
+mozilla::ipc::IPCResult
+VRManagerParent::RecvCreateVRServiceTestController(const nsCString& aID, const uint32_t& aPromiseID)
+{
+  uint32_t controllerIdx = 0;
+  nsTArray<VRControllerInfo> controllerInfoArray;
+  impl::VRControllerPuppet* controllerPuppet = nullptr;
+  VRManager* vm = VRManager::Get();
+
+  // Get VRControllerPuppet from VRManager
+  vm->GetVRControllerInfo(controllerInfoArray);
+  for (auto& controllerInfo : controllerInfoArray) {
+    if (controllerInfo.GetType() == VRDeviceType::Puppet) {
+      if (controllerIdx == mControllerTestID) {
+        controllerPuppet = static_cast<impl::VRControllerPuppet*>(
+                           vm->GetController(controllerInfo.GetControllerID()).get());
+        break;
+      }
+      ++controllerIdx;
+    }
+  }
+
+  MOZ_ASSERT(controllerPuppet);
+  MOZ_ASSERT(mControllerTestID < 2); // We have only two controllers in VRSystemManagerPuppet.
+
+  if (!mVRControllerTests.Get(mControllerTestID, nullptr)) {
+    mVRControllerTests.Put(mControllerTestID, controllerPuppet);
+  }
+
+  if (SendReplyCreateVRServiceTestController(aID, aPromiseID, mControllerTestID)) {
+    ++mControllerTestID;
+    return IPC_OK();
+  }
+
+  return IPC_FAIL(this, "SendReplyCreateVRServiceTestController fail");
+}
+
+mozilla::ipc::IPCResult
+VRManagerParent::RecvSetDisplayInfoToMockDisplay(const uint32_t& aDeviceID,
+                                                 const VRDisplayInfo& aDisplayInfo)
+{
+  RefPtr<impl::VRDisplayPuppet> displayPuppet;
+  MOZ_ASSERT(mVRDisplayTests.Get(mDisplayTestID,
+                                 getter_AddRefs(displayPuppet)));
+  displayPuppet->SetDisplayInfo(aDisplayInfo);
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+VRManagerParent::RecvSetSensorStateToMockDisplay(const uint32_t& aDeviceID,
+                                                 const VRHMDSensorState& aSensorState)
+{
+  RefPtr<impl::VRDisplayPuppet> displayPuppet;
+  MOZ_ASSERT(mVRDisplayTests.Get(mControllerTestID,
+                                 getter_AddRefs(displayPuppet)));
+  displayPuppet->SetSensorState(aSensorState);
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+VRManagerParent::RecvNewButtonEventToMockController(const uint32_t& aDeviceID, const long& aButton,
+                                                    const bool& aPressed)
+{
+  RefPtr<impl::VRControllerPuppet> controllerPuppet;
+  MOZ_ASSERT(mVRControllerTests.Get(mControllerTestID,
+                                    getter_AddRefs(controllerPuppet)));
+  controllerPuppet->SetButtonPressState(aButton, aPressed);
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+VRManagerParent::RecvNewAxisMoveEventToMockController(const uint32_t& aDeviceID, const long& aAxis,
+                                                      const double& aValue)
+{
+  RefPtr<impl::VRControllerPuppet> controllerPuppet;
+  MOZ_ASSERT(mVRControllerTests.Get(mControllerTestID,
+                                    getter_AddRefs(controllerPuppet)));
+  controllerPuppet->SetAxisMoveState(aAxis, aValue);
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+VRManagerParent::RecvNewPoseMoveToMockController(const uint32_t& aDeviceID,
+                                                 const GamepadPoseState& pose)
+{
+  RefPtr<impl::VRControllerPuppet> controllerPuppet;
+  MOZ_ASSERT(mVRControllerTests.Get(mControllerTestID,
+                                    getter_AddRefs(controllerPuppet)));
+  controllerPuppet->SetPoseMoveState(pose);
+  return IPC_OK();
+}
+
 bool
 VRManagerParent::SendGamepadUpdate(const GamepadChangeEvent& aGamepadEvent)
 {
   // GamepadManager only exists at the content process
   // or the same process in non-e10s mode.
   if (mIsContentChild || IsSameProcess()) {
     return PVRManagerParent::SendGamepadUpdate(aGamepadEvent);
   } else {
--- a/gfx/vr/ipc/VRManagerParent.h
+++ b/gfx/vr/ipc/VRManagerParent.h
@@ -17,16 +17,21 @@
 #include "gfxVR.h"                        // for VRFieldOfView
 
 namespace mozilla {
 using namespace layers;
 namespace gfx {
 
 class VRManager;
 
+namespace impl {
+class VRDisplayPuppet;
+class VRControllerPuppet;
+} // namespace impl
+
 class VRManagerParent final : public PVRManagerParent
                             , public HostIPCAllocator
                             , public ShmemAllocator
 {
 public:
   explicit VRManagerParent(ProcessId aChildProcessId, bool aIsContentChild);
 
   static VRManagerParent* CreateSameProcess();
@@ -84,16 +89,28 @@ protected:
   virtual mozilla::ipc::IPCResult RecvGetDisplays(nsTArray<VRDisplayInfo> *aDisplays) override;
   virtual mozilla::ipc::IPCResult RecvResetSensor(const uint32_t& aDisplayID) override;
   virtual mozilla::ipc::IPCResult RecvGetSensorState(const uint32_t& aDisplayID, VRHMDSensorState* aState) override;
   virtual mozilla::ipc::IPCResult RecvGetImmediateSensorState(const uint32_t& aDisplayID, VRHMDSensorState* aState) override;
   virtual mozilla::ipc::IPCResult RecvSetHaveEventListener(const bool& aHaveEventListener) override;
   virtual mozilla::ipc::IPCResult RecvControllerListenerAdded() override;
   virtual mozilla::ipc::IPCResult RecvControllerListenerRemoved() override;
   virtual mozilla::ipc::IPCResult RecvGetControllers(nsTArray<VRControllerInfo> *aControllers) override;
+  virtual mozilla::ipc::IPCResult RecvCreateVRTestSystem() override;
+  virtual mozilla::ipc::IPCResult RecvCreateVRServiceTestDisplay(const nsCString& aID, const uint32_t& aPromiseID) override;
+  virtual mozilla::ipc::IPCResult RecvCreateVRServiceTestController(const nsCString& aID, const uint32_t& aPromiseID) override;
+  virtual mozilla::ipc::IPCResult RecvSetDisplayInfoToMockDisplay(const uint32_t& aDeviceID,
+                                                                  const VRDisplayInfo& aDisplayInfo) override;
+  virtual mozilla::ipc::IPCResult RecvSetSensorStateToMockDisplay(const uint32_t& aDeviceID,
+                                                                  const VRHMDSensorState& aSensorState) override;
+  virtual mozilla::ipc::IPCResult RecvNewButtonEventToMockController(const uint32_t& aDeviceID, const long& aButton,
+                                                                     const bool& aPressed) override;
+  virtual mozilla::ipc::IPCResult RecvNewAxisMoveEventToMockController(const uint32_t& aDeviceID, const long& aAxis,
+                                                                       const double& aValue) override;
+  virtual mozilla::ipc::IPCResult RecvNewPoseMoveToMockController(const uint32_t& aDeviceID, const GamepadPoseState& pose) override;
 
 private:
   void RegisterWithManager();
   void UnregisterFromManager();
 
   void Bind(Endpoint<PVRManagerParent>&& aEndpoint);
 
   static void RegisterVRManagerInCompositorThread(VRManagerParent* aVRManager);
@@ -104,16 +121,20 @@ private:
   // deferred destruction of ourselves.
   RefPtr<VRManagerParent> mSelfRef;
 
   // Keep the compositor thread alive, until we have destroyed ourselves.
   RefPtr<layers::CompositorThreadHolder> mCompositorThreadHolder;
 
   // Keep the VRManager alive, until we have destroyed ourselves.
   RefPtr<VRManager> mVRManagerHolder;
+  nsRefPtrHashtable<nsUint32HashKey, impl::VRDisplayPuppet> mVRDisplayTests;
+  nsRefPtrHashtable<nsUint32HashKey, impl::VRControllerPuppet> mVRControllerTests;
+  uint32_t mDisplayTestID;
+  uint32_t mControllerTestID;
   bool mHaveEventListener;
   bool mHaveControllerListener;
   bool mIsContentChild;
 };
 
 } // namespace mozilla
 } // namespace gfx
 
--- a/intl/locale/DateTimeFormat.cpp
+++ b/intl/locale/DateTimeFormat.cpp
@@ -2,49 +2,36 @@
  *
  * 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 "DateTimeFormat.h"
 #include "nsCOMPtr.h"
 #include "nsIServiceManager.h"
-#include "nsILocaleService.h"
+#include "mozilla/intl/LocaleService.h"
 #include "unicode/udatpg.h"
 
 namespace mozilla {
 
 nsCString* DateTimeFormat::mLocale = nullptr;
 
 /*static*/ nsresult
 DateTimeFormat::Initialize()
 {
-  nsAutoString localeStr;
-  nsresult rv = NS_OK;
-
-  if (!mLocale) {
-    mLocale = new nsCString();
-  } else if (!mLocale->IsEmpty()) {
+  if (mLocale) {
     return NS_OK;
   }
 
-  nsCOMPtr<nsILocaleService> localeService =
-           do_GetService(NS_LOCALESERVICE_CONTRACTID, &rv);
-  if (NS_SUCCEEDED(rv)) {
-    nsCOMPtr<nsILocale> appLocale;
-    rv = localeService->GetApplicationLocale(getter_AddRefs(appLocale));
-    if (NS_SUCCEEDED(rv)) {
-      rv = appLocale->GetCategory(NS_LITERAL_STRING("NSILOCALE_TIME"), localeStr);
-      if (NS_SUCCEEDED(rv) && !localeStr.IsEmpty()) {
-        *mLocale = NS_LossyConvertUTF16toASCII(localeStr); // cache locale name
-      }
-    }
-  }
+  mLocale = new nsCString();
+  nsAutoCString locale;
+  intl::LocaleService::GetInstance()->GetAppLocale(locale);
+  mLocale->Assign(locale);
 
-  return rv;
+  return NS_OK;
 }
 
 // performs a locale sensitive date formatting operation on the time_t parameter
 /*static*/ nsresult
 DateTimeFormat::FormatTime(const nsDateFormatSelector aDateFormatSelector,
                            const nsTimeFormatSelector aTimeFormatSelector,
                            const time_t aTimetTime,
                            nsAString& aStringOut)
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -2577,25 +2577,16 @@ HelperThreadCount(JSContext* cx, unsigne
     if (CanUseExtraThreads())
         args.rval().setInt32(HelperThreadState().threadCount);
     else
         args.rval().setInt32(0);
 #endif
     return true;
 }
 
-static bool
-TimesAccessed(JSContext* cx, unsigned argc, Value* vp)
-{
-    static int32_t accessed = 0;
-    CallArgs args = CallArgsFromVp(argc, vp);
-    args.rval().setInt32(++accessed);
-    return true;
-}
-
 #ifdef JS_TRACE_LOGGING
 static bool
 EnableTraceLogger(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     TraceLoggerThread* logger = TraceLoggerForCurrentThread(cx);
     if (!TraceLoggerEnable(logger, cx))
         return false;
@@ -4838,33 +4829,25 @@ static const JSFunctionSpecWithHelp Fuzz
 
     JS_FN_HELP("getErrorNotes", GetErrorNotes, 1, 0,
 "getErrorNotes(error)",
 "  Returns an array of error notes."),
 
     JS_FS_HELP_END
 };
 
-static const JSPropertySpec TestingProperties[] = {
-    JS_PSG("timesAccessed", TimesAccessed, 0),
-    JS_PS_END
-};
-
 bool
 js::DefineTestingFunctions(JSContext* cx, HandleObject obj, bool fuzzingSafe_,
                            bool disableOOMFunctions_)
 {
     fuzzingSafe = fuzzingSafe_;
     if (EnvVarIsDefined("MOZ_FUZZING_SAFE"))
         fuzzingSafe = true;
 
     disableOOMFunctions = disableOOMFunctions_;
 
-    if (!JS_DefineProperties(cx, obj, TestingProperties))
-        return false;
-
     if (!fuzzingSafe) {
         if (!JS_DefineFunctionsWithHelp(cx, obj, FuzzingUnsafeTestingFunctions))
             return false;
     }
 
     return JS_DefineFunctionsWithHelp(cx, obj, TestingFunctions);
 }
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -300,27 +300,16 @@ class TryFinallyControl : public Bytecod
         emittingSubroutine_ = true;
     }
 
     bool emittingSubroutine() const {
         return emittingSubroutine_;
     }
 };
 
-static bool
-ScopeKindIsInBody(ScopeKind kind)
-{
-    return kind == ScopeKind::Lexical ||
-           kind == ScopeKind::SimpleCatch ||
-           kind == ScopeKind::Catch ||
-           kind == ScopeKind::With ||
-           kind == ScopeKind::FunctionBodyVar ||
-           kind == ScopeKind::ParameterExpressionVar;
-}
-
 static inline void
 MarkAllBindingsClosedOver(LexicalScope::Data& data)
 {
     BindingName* names = data.names;
     for (uint32_t i = 0; i < data.length; i++)
         names[i] = BindingName(names[i].name(), true);
 }
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/bug1342483-1.js
@@ -0,0 +1,6 @@
+// |jit-test| error: ReferenceError
+for (var i = 0; i < 10; ++i) {}
+for (var i = 0; i < 3; i++) {
+    throw eval(raisesException);
+    function ff() {}
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/bug1342483-2.js
@@ -0,0 +1,17 @@
+// |jit-test| error: () => g
+function f() {
+    // Block Scope
+    {
+        // Lexical capture creates environment
+        function g() {}
+        var h = [() => g];
+
+        // OSR Re-Entry Point
+        for (;;) { break; }
+
+        // Type Invalidation + Throw
+        throw h[0];
+    }
+}
+
+f();
--- a/js/src/jit/BaselineBailouts.cpp
+++ b/js/src/jit/BaselineBailouts.cpp
@@ -732,16 +732,31 @@ InitFromBailout(JSContext* cx, HandleScr
             if (fun && fun->needsFunctionEnvironmentObjects()) {
                 MOZ_ASSERT(fun->nonLazyScript()->initialEnvironmentShape());
                 MOZ_ASSERT(!fun->needsExtraBodyVarEnvironment());
                 flags |= BaselineFrame::HAS_INITIAL_ENV;
             }
         } else {
             MOZ_ASSERT(v.isUndefined() || v.isMagic(JS_OPTIMIZED_OUT));
 
+#ifdef DEBUG
+            // The |envChain| slot must not be optimized out if the currently
+            // active scope requires any EnvironmentObjects beyond what is
+            // available at body scope. This checks that scope chain does not
+            // require any such EnvironmentObjects.
+            // See also: |CompileInfo::isObservableFrameSlot|
+            jsbytecode* pc = script->offsetToPC(iter.pcOffset());
+            Scope* scopeIter = script->innermostScope(pc);
+            while (scopeIter != script->bodyScope()) {
+                MOZ_ASSERT(scopeIter);
+                MOZ_ASSERT(!scopeIter->hasEnvironment());
+                scopeIter = scopeIter->enclosing();
+            }
+#endif
+
             // Get env chain from function or script.
             if (fun) {
                 // If pcOffset == 0, we may have to push a new call object, so
                 // we leave envChain nullptr and enter baseline code before
                 // the prologue.
                 if (!IsPrologueBailout(iter, excInfo))
                     envChain = fun->environment();
             } else if (script->module()) {
--- a/js/src/jit/CompileInfo.h
+++ b/js/src/jit/CompileInfo.h
@@ -235,22 +235,27 @@ class CompileInfo
                     continue;
                 BindingLocation loc = bi.location();
                 if (loc.kind() == BindingLocation::Kind::Frame) {
                     thisSlotForDerivedClassConstructor_ = mozilla::Some(localSlot(loc.slot()));
                     break;
                 }
             }
         }
+
+        // If the script uses an environment in body, the environment chain
+        // will need to be observable.
+        needsBodyEnvironmentObject_ = script->needsBodyEnvironment();
     }
 
     explicit CompileInfo(unsigned nlocals)
       : script_(nullptr), fun_(nullptr), osrPc_(nullptr),
         analysisMode_(Analysis_None), scriptNeedsArgsObj_(false),
-        mayReadFrameArgsDirectly_(false), inlineScriptTree_(nullptr)
+        mayReadFrameArgsDirectly_(false), inlineScriptTree_(nullptr),
+        needsBodyEnvironmentObject_(false)
     {
         nimplicit_ = 0;
         nargs_ = 0;
         nlocals_ = nlocals;
         nstack_ = 1;  /* For FunctionCompiler::pushPhiInput/popPhiOutput */
         nslots_ = nlocals_ + nstack_;
     }
 
@@ -429,31 +434,40 @@ class CompileInfo
     AnalysisMode analysisMode() const {
         return analysisMode_;
     }
 
     bool isAnalysis() const {
         return analysisMode_ != Analysis_None;
     }
 
+    bool needsBodyEnvironmentObject() const {
+        return needsBodyEnvironmentObject_;
+    }
+
     // Returns true if a slot can be observed out-side the current frame while
     // the frame is active on the stack.  This implies that these definitions
     // would have to be executed and that they cannot be removed even if they
     // are unused.
     bool isObservableSlot(uint32_t slot) const {
         if (isObservableFrameSlot(slot))
             return true;
 
         if (isObservableArgumentSlot(slot))
             return true;
 
         return false;
     }
 
     bool isObservableFrameSlot(uint32_t slot) const {
+        // The |envChain| value must be preserved if environments are added
+        // after the prologue.
+        if (needsBodyEnvironmentObject() && slot == environmentChainSlot())
+            return true;
+
         if (!funMaybeLazy())
             return false;
 
         // The |this| value must always be observable.
         if (slot == thisSlot())
             return true;
 
         // The |this| frame slot in derived class constructors should never be
@@ -490,19 +504,21 @@ class CompileInfo
 
         return false;
     }
 
     // Returns true if a slot can be recovered before or during a bailout.  A
     // definition which can be observed and recovered, implies that this
     // definition can be optimized away as long as we can compute its values.
     bool isRecoverableOperand(uint32_t slot) const {
-        // If this script is not a function, then none of the slots are
-        // observable.  If it this |slot| is not observable, thus we can always
-        // recover it.
+        // The |envChain| value cannot be recovered if environments can be
+        // added in body (after the prologue).
+        if (needsBodyEnvironmentObject() && slot == environmentChainSlot())
+            return false;
+
         if (!funMaybeLazy())
             return true;
 
         // The |this| and the |envChain| values can be recovered.
         if (slot == thisSlot() || slot == environmentChainSlot())
             return true;
 
         if (isObservableFrameSlot(slot))
@@ -542,14 +558,18 @@ class CompileInfo
 
     // Record the state of previous bailouts in order to prevent compiling the
     // same function identically the next time.
     bool hadOverflowBailout_;
 
     bool mayReadFrameArgsDirectly_;
 
     InlineScriptTree* inlineScriptTree_;
+
+    // Whether a script needs environments within its body. This informs us
+    // that the environment chain is not easy to reconstruct.
+    bool needsBodyEnvironmentObject_;
 };
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_CompileInfo_h */
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -1632,16 +1632,25 @@ class JSScript : public js::gc::TenuredC
         for (uint32_t i = 0; i < scopes()->length; i++) {
             js::Scope* scope = getScope(i);
             if (scope->kind() == js::ScopeKind::FunctionBodyVar)
                 return &scope->as<js::VarScope>();
         }
         MOZ_CRASH("Function extra body var scope not found");
     }
 
+    bool needsBodyEnvironment() const {
+        for (uint32_t i = 0; i < scopes()->length; i++) {
+            js::Scope* scope = getScope(i);
+            if (ScopeKindIsInBody(scope->kind()) && scope->hasEnvironment())
+                return true;
+        }
+        return false;
+    }
+
     inline js::LexicalScope* maybeNamedLambdaScope() const;
 
     js::Scope* enclosingScope() const {
         return outermostScope()->enclosing();
     }
 
   private:
     bool makeTypes(JSContext* cx);
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -7457,16 +7457,30 @@ ShellBuildId(JS::BuildIdCharVector* buil
 
 static const JS::AsmJSCacheOps asmJSCacheOps = {
     ShellOpenAsmJSCacheEntryForRead,
     ShellCloseAsmJSCacheEntryForRead,
     ShellOpenAsmJSCacheEntryForWrite,
     ShellCloseAsmJSCacheEntryForWrite
 };
 
+static bool
+TimesAccessed(JSContext* cx, unsigned argc, Value* vp)
+{
+    static int32_t accessed = 0;
+    CallArgs args = CallArgsFromVp(argc, vp);
+    args.rval().setInt32(++accessed);
+    return true;
+}
+
+static const JSPropertySpec TestingProperties[] = {
+    JS_PSG("timesAccessed", TimesAccessed, 0),
+    JS_PS_END
+};
+
 static JSObject*
 NewGlobalObject(JSContext* cx, JS::CompartmentOptions& options,
                 JSPrincipals* principals)
 {
     RootedObject glob(cx, JS_NewGlobalObject(cx, &global_class, principals,
                                              JS::DontFireOnNewGlobalHook, options));
     if (!glob)
         return nullptr;
@@ -7498,16 +7512,18 @@ NewGlobalObject(JSContext* cx, JS::Compa
             return nullptr;
         if (!JS_DefineFunctionsWithHelp(cx, glob, shell_functions) ||
             !JS_DefineProfilingFunctions(cx, glob))
         {
             return nullptr;
         }
         if (!js::DefineTestingFunctions(cx, glob, fuzzingSafe, disableOOMFunctions))
             return nullptr;
+        if (!JS_DefineProperties(cx, glob, TestingProperties))
+            return nullptr;
 
         if (!fuzzingSafe) {
             if (!JS_DefineFunctionsWithHelp(cx, glob, fuzzing_unsafe_functions))
                 return nullptr;
             if (!DefineConsole(cx, glob))
                 return nullptr;
         }
 
--- a/js/src/vm/Scope.h
+++ b/js/src/vm/Scope.h
@@ -78,16 +78,27 @@ enum class ScopeKind : uint8_t
 };
 
 static inline bool
 ScopeKindIsCatch(ScopeKind kind)
 {
     return kind == ScopeKind::SimpleCatch || kind == ScopeKind::Catch;
 }
 
+static inline bool
+ScopeKindIsInBody(ScopeKind kind)
+{
+    return kind == ScopeKind::Lexical ||
+           kind == ScopeKind::SimpleCatch ||
+           kind == ScopeKind::Catch ||
+           kind == ScopeKind::With ||
+           kind == ScopeKind::FunctionBodyVar ||
+           kind == ScopeKind::ParameterExpressionVar;
+}
+
 const char* BindingKindString(BindingKind kind);
 const char* ScopeKindString(ScopeKind kind);
 
 class BindingName
 {
     // A JSAtom* with its low bit used as a tag for whether it is closed over
     // (i.e., exists in the environment shape).
     uintptr_t bits_;
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -1385,17 +1385,52 @@ RestyleManager::ProcessRestyledFrames(ns
   for (const nsStyleChangeData& data : aChangeList) {
     if (data.mFrame) {
       propTable->Set(data.mFrame, ChangeListProperty(), true);
     }
   }
 
   bool didUpdateCursor = false;
 
-  for (const nsStyleChangeData& data : aChangeList) {
+  for (size_t i = 0; i < aChangeList.Length(); ++i) {
+
+    // Collect and coalesce adjacent siblings for lazy frame construction.
+    // Eventually it would be even better to make RecreateFramesForContent
+    // accept a range and coalesce all adjacent reconstructs (bug 1344139).
+    size_t lazyRangeStart = i;
+    while (i < aChangeList.Length() &&
+           aChangeList[i].mContent &&
+           aChangeList[i].mContent->HasFlag(NODE_NEEDS_FRAME) &&
+           (i == lazyRangeStart ||
+            aChangeList[i - 1].mContent->GetNextSibling() == aChangeList[i].mContent))
+    {
+      MOZ_ASSERT(aChangeList[i].mHint & nsChangeHint_ReconstructFrame);
+      MOZ_ASSERT(!aChangeList[i].mFrame);
+      ++i;
+    }
+    if (i != lazyRangeStart) {
+      nsIContent* start = aChangeList[lazyRangeStart].mContent;
+      nsIContent* end = aChangeList[i-1].mContent->GetNextSibling();
+      nsIContent* container = start->GetParent();
+      MOZ_ASSERT(container);
+      if (!end) {
+        frameConstructor->ContentAppended(container, start, false);
+      } else {
+        frameConstructor->ContentRangeInserted(container, start, end, nullptr, false);
+      }
+    }
+    for (size_t j = lazyRangeStart; j < i; ++j) {
+      MOZ_ASSERT_IF(aChangeList[j].mContent->GetPrimaryFrame(),
+                    !aChangeList[j].mContent->HasFlag(NODE_NEEDS_FRAME));
+    }
+    if (i == aChangeList.Length()) {
+      break;
+    }
+
+    const nsStyleChangeData& data = aChangeList[i];
     nsIFrame* frame = data.mFrame;
     nsIContent* content = data.mContent;
     nsChangeHint hint = data.mHint;
     bool didReflowThisFrame = false;
 
     NS_ASSERTION(!(hint & nsChangeHint_AllReflowHints) ||
                  (hint & nsChangeHint_NeedReflow),
                  "Reflow hint bits set without actually asking for a reflow");
@@ -1710,17 +1745,17 @@ RestyleManager::AnimationsWithDestroyedF
     mRestyleManager->PresContext()->TransitionManager();
   for (nsIContent* content : aArray) {
     if (content->GetPrimaryFrame()) {
       continue;
     }
     dom::Element* element = content->AsElement();
 
     animationManager->StopAnimationsForElement(element, aPseudoType);
-    transitionManager->StopTransitionsForElement(element, aPseudoType);
+    transitionManager->StopAnimationsForElement(element, aPseudoType);
 
     // All other animations should keep running but not running on the
     // *compositor* at this point.
     EffectSet* effectSet = EffectSet::GetEffectSet(element, aPseudoType);
     if (effectSet) {
       for (KeyframeEffectReadOnly* effect : *effectSet) {
         effect->ResetIsRunningOnCompositor();
       }
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -122,63 +122,46 @@ ClearRestyleStateFromSubtree(Element* aE
       if (n->IsElement()) {
         ClearRestyleStateFromSubtree(n->AsElement());
       }
     }
   }
 
   Unused << Servo_TakeChangeHint(aElement);
   aElement->UnsetHasDirtyDescendantsForServo();
-}
-
-static void
-UpdateStyleContextForTableWrapper(nsIFrame* aTableWrapper,
-                                  nsStyleContext* aTableStyleContext,
-                                  ServoStyleSet* aStyleSet)
-{
-  MOZ_ASSERT(aTableWrapper->GetType() == nsGkAtoms::tableWrapperFrame);
-  MOZ_ASSERT(aTableWrapper->StyleContext()->GetPseudo() ==
-             nsCSSAnonBoxes::tableWrapper);
-  RefPtr<nsStyleContext> newContext =
-    aStyleSet->ResolveAnonymousBoxStyle(nsCSSAnonBoxes::tableWrapper,
-                                        aTableStyleContext);
-  aTableWrapper->SetStyleContext(newContext);
+  aElement->UnsetFlags(NODE_DESCENDANTS_NEED_FRAMES);
 }
 
 void
-ServoRestyleManager::RecreateStyleContexts(Element* aElement,
-                                           nsStyleContext* aParentContext,
-                                           ServoStyleSet* aStyleSet,
-                                           nsStyleChangeList& aChangeListToProcess)
+ServoRestyleManager::ProcessPostTraversal(Element* aElement,
+                                          nsStyleContext* aParentContext,
+                                          ServoStyleSet* aStyleSet,
+                                          nsStyleChangeList& aChangeList)
 {
   nsIFrame* styleFrame = nsLayoutUtils::GetStyleFrame(aElement);
-  bool isTable = styleFrame != aElement->GetPrimaryFrame();
+
+  // Grab the change hint from Servo.
+  nsChangeHint changeHint = Servo_TakeChangeHint(aElement);
 
-  nsChangeHint changeHint = Servo_TakeChangeHint(aElement);
+  // Handle lazy frame construction by posting a reconstruct for any lazily-
+  // constructed roots.
+  if (aElement->HasFlag(NODE_NEEDS_FRAME)) {
+    changeHint |= nsChangeHint_ReconstructFrame;
+    // The only time the primary frame is non-null is when image maps do hacky
+    // SetPrimaryFrame calls.
+    MOZ_ASSERT_IF(styleFrame, styleFrame->GetType() == nsGkAtoms::imageFrame);
+    styleFrame = nullptr;
+  }
+
   // Although we shouldn't generate non-ReconstructFrame hints for elements with
   // no frames, we can still get them here if they were explicitly posted by
   // PostRestyleEvent, such as a RepaintFrame hint when a :link changes to be
   // :visited.  Skip processing these hints if there is no frame.
   if ((styleFrame || (changeHint & nsChangeHint_ReconstructFrame)) && changeHint) {
-    if (isTable) {
-      // This part is a bit annoying: when isTable, that means the style frame
-      // is actually a _child_ of the primary frame.  In that situation, we want
-      // to go ahead and append the changeHint for the _parent_ but also append
-      // all the parts of it not handled for descendants for the _child_.
-      MOZ_ASSERT(styleFrame, "Or else GetPrimaryFrame() would be null too");
-      MOZ_ASSERT(styleFrame->GetParent() == aElement->GetPrimaryFrame(),
-                 "How did that happen?");
-      aChangeListToProcess.AppendChange(aElement->GetPrimaryFrame(), aElement,
-                                        changeHint);
-      // We may be able to do better here.
-      // https://bugzilla.mozilla.org/show_bug.cgi?id=1340717 tracks that.
-      aChangeListToProcess.AppendChange(styleFrame, aElement, changeHint);
-    } else {
-      aChangeListToProcess.AppendChange(styleFrame, aElement, changeHint);
-    }
+    aChangeList.AppendChange(styleFrame, aElement, changeHint);
   }
 
   // If our change hint is reconstruct, we delegate to the frame constructor,
   // which consumes the new style and expects the old style to be on the frame.
   //
   // XXXbholley: We should teach the frame constructor how to clear the dirty
   // descendants bit to avoid the traversal here.
   if (changeHint & nsChangeHint_ReconstructFrame) {
@@ -230,34 +213,33 @@ ServoRestyleManager::RecreateStyleContex
       aStyleSet->GetContext(computedValues.forget(), aParentContext, nullptr,
                             CSSPseudoElementType::NotPseudo, aElement);
 
     newContext->EnsureStructsForServo(oldStyleContext);
 
     // XXX This could not always work as expected: there are kinds of content
     // with the first split and the last sharing style, but others not. We
     // should handle those properly.
+    // XXXbz I think the UpdateStyleOfOwnedAnonBoxes call below handles _that_
+    // right, but not other cases where we happen to have different styles on
+    // different continuations... (e.g. first-line).
     for (nsIFrame* f = styleFrame; f;
          f = GetNextContinuationWithSameStyle(f, oldStyleContext)) {
       f->SetStyleContext(newContext);
     }
 
-    if (isTable) {
-      nsIFrame* primaryFrame = aElement->GetPrimaryFrame();
-      MOZ_ASSERT(primaryFrame->StyleContext()->GetPseudo() ==
-                   nsCSSAnonBoxes::tableWrapper,
-                 "What sort of frame is this?");
-      UpdateStyleContextForTableWrapper(primaryFrame, newContext, aStyleSet);
-    }
-
     if (MOZ_UNLIKELY(displayContentsNode)) {
       MOZ_ASSERT(!styleFrame);
       displayContentsNode->mStyle = newContext;
     }
 
+    if (styleFrame) {
+      styleFrame->UpdateStyleOfOwnedAnonBoxes(*aStyleSet, aChangeList, changeHint);
+    }
+
     // Update pseudo-elements state if appropriate.
     const static CSSPseudoElementType pseudosToRestyle[] = {
       CSSPseudoElementType::before,
       CSSPseudoElementType::after,
     };
 
     for (CSSPseudoElementType pseudoType : pseudosToRestyle) {
       nsIAtom* pseudoTag = nsCSSPseudoElements::GetPseudoAtom(pseudoType);
@@ -283,41 +265,52 @@ ServoRestyleManager::RecreateStyleContex
                        "How? This node is created at FC time!");
             n->GetPrimaryFrame()->SetStyleContext(childContext);
           }
         }
       }
     }
   }
 
-  bool traverseElementChildren = aElement->HasDirtyDescendantsForServo();
-  bool traverseTextChildren = recreateContext;
+  bool descendantsNeedFrames = aElement->HasFlag(NODE_DESCENDANTS_NEED_FRAMES);
+  bool traverseElementChildren =
+    aElement->HasDirtyDescendantsForServo() || descendantsNeedFrames;
+  bool traverseTextChildren = recreateContext || descendantsNeedFrames;
   if (traverseElementChildren || traverseTextChildren) {
     nsStyleContext* upToDateContext =
       recreateContext ? newContext : oldStyleContext;
 
     StyleChildrenIterator it(aElement);
     for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
       if (traverseElementChildren && n->IsElement()) {
-        RecreateStyleContexts(n->AsElement(), upToDateContext,
-                              aStyleSet, aChangeListToProcess);
+        ProcessPostTraversal(n->AsElement(), upToDateContext,
+                             aStyleSet, aChangeList);
       } else if (traverseTextChildren && n->IsNodeOfType(nsINode::eTEXT)) {
-        RecreateStyleContextsForText(n, upToDateContext, aStyleSet);
+        ProcessPostTraversalForText(n, upToDateContext, aStyleSet, aChangeList);
       }
     }
   }
 
   aElement->UnsetHasDirtyDescendantsForServo();
+  aElement->UnsetFlags(NODE_DESCENDANTS_NEED_FRAMES);
 }
 
 void
-ServoRestyleManager::RecreateStyleContextsForText(nsIContent* aTextNode,
-                                                  nsStyleContext* aParentContext,
-                                                  ServoStyleSet* aStyleSet)
+ServoRestyleManager::ProcessPostTraversalForText(nsIContent* aTextNode,
+                                                 nsStyleContext* aParentContext,
+                                                 ServoStyleSet* aStyleSet,
+                                                 nsStyleChangeList& aChangeList)
 {
+  // Handle lazy frame construction.
+  if (aTextNode->HasFlag(NODE_NEEDS_FRAME)) {
+    aChangeList.AppendChange(nullptr, aTextNode, nsChangeHint_ReconstructFrame);
+    return;
+  }
+
+  // Handle restyle.
   nsIFrame* primaryFrame = aTextNode->GetPrimaryFrame();
   if (primaryFrame) {
     RefPtr<nsStyleContext> oldStyleContext = primaryFrame->StyleContext();
     RefPtr<nsStyleContext> newContext =
       aStyleSet->ResolveStyleForText(aTextNode, aParentContext);
 
     for (nsIFrame* f = primaryFrame; f;
          f = GetNextContinuationWithSameStyle(f, oldStyleContext)) {
@@ -385,34 +378,24 @@ ServoRestyleManager::ProcessPendingResty
   PresContext()->RefreshDriver()->MostRecentRefresh();
 
 
   // Perform the Servo traversal, and the post-traversal if required. We do this
   // in a loop because certain rare paths in the frame constructor (like
   // uninstalling XBL bindings) can trigger additional style validations.
   mInStyleRefresh = true;
   while (styleSet->StyleDocument()) {
-
     PresContext()->EffectCompositor()->ClearElementsToRestyle();
 
-    // First do any queued-up frame creation. (see bugs 827239 and 997506).
-    //
-    // XXXEmilio I'm calling this to avoid random behavior changes, since we
-    // delay frame construction after styling we should re-check once our
-    // model is more stable whether we can skip this call.
-    //
-    // Note this has to be *after* restyling, because otherwise frame
-    // construction will find unstyled nodes, and that's not funny.
-    PresContext()->FrameConstructor()->CreateNeededFrames();
-
-    // Recreate style contexts and queue up change hints.
+    // Recreate style contexts, and queue up change hints (which also handle
+    // lazy frame construction).
     nsStyleChangeList currentChanges;
     DocumentStyleRootIterator iter(doc);
     while (Element* root = iter.GetNextStyleRoot()) {
-      RecreateStyleContexts(root, nullptr, styleSet, currentChanges);
+      ProcessPostTraversal(root, nullptr, styleSet, currentChanges);
     }
 
     // Process the change hints.
     //
     // Unfortunately, the frame constructor can generate new change hints while
     // processing existing ones. We redirect those into a secondary queue and
     // iterate until there's nothing left.
     ReentrantChangeList newChanges;
--- a/layout/base/ServoRestyleManager.h
+++ b/layout/base/ServoRestyleManager.h
@@ -95,27 +95,27 @@ public:
 protected:
   ~ServoRestyleManager() override
   {
     MOZ_ASSERT(!mReentrantChanges);
   }
 
 private:
   /**
-   * Traverses a tree of content that Servo has just restyled, recreating style
-   * contexts for their frames with the new style data.
+   * Performs post-Servo-traversal processing on this element and its descendants.
    */
-  void RecreateStyleContexts(Element* aElement,
-                             nsStyleContext* aParentContext,
-                             ServoStyleSet* aStyleSet,
-                             nsStyleChangeList& aChangeList);
+  void ProcessPostTraversal(Element* aElement,
+                            nsStyleContext* aParentContext,
+                            ServoStyleSet* aStyleSet,
+                            nsStyleChangeList& aChangeList);
 
-  void RecreateStyleContextsForText(nsIContent* aTextNode,
-                                    nsStyleContext* aParentContext,
-                                    ServoStyleSet* aStyleSet);
+  void ProcessPostTraversalForText(nsIContent* aTextNode,
+                                   nsStyleContext* aParentContext,
+                                   ServoStyleSet* aStyleSet,
+                                   nsStyleChangeList& aChangeList);
 
   inline ServoStyleSet* StyleSet() const
   {
     MOZ_ASSERT(PresContext()->StyleSet()->IsServo(),
                "ServoRestyleManager should only be used with a Servo-flavored "
                "style backend");
     return PresContext()->StyleSet()->AsServo();
   }
--- a/layout/base/crashtests/crashtests.list
+++ b/layout/base/crashtests/crashtests.list
@@ -284,17 +284,17 @@ load 468491-1.html
 load 468546-1.xhtml
 load 468555-1.xhtml
 load 468563-1.html
 load 468578-1.xhtml
 # These three didn't actually crash without the resizing that the
 # browser does when setting up print preview, but adding them anyway.
 load 468645-1.xhtml
 load 468645-2.xhtml
-asserts-if(stylo,1) load 468645-3.xhtml # bug 1337696
+load 468645-3.xhtml
 load 469861-1.xhtml
 load 469861-2.xhtml
 load 470851-1.xhtml
 load 471594-1.xhtml
 asserts-if(Android&&!asyncPan,1-2) load 473042.xhtml # bug 1034369 (may also cause a few assertions to be registered on the next test)
 asserts(0-5) load 474075.html # bug 847368
 load 477333-1.xhtml
 load 477731-1.html
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -803,31 +803,33 @@ public:
   // yet.  The mCreatingExtraFrames == true mode is meant to be used for
   // construction of random "extra" frames for elements via normal frame
   // construction APIs (e.g. replication of things across pages in paginated
   // mode).
   bool                      mCreatingExtraFrames;
 
   nsCOMArray<nsIContent>    mGeneratedTextNodesWithInitializer;
 
-  TreeMatchContext          mTreeMatchContext;
+  TreeMatchContext&         mTreeMatchContext;
 
   // Constructor
   // Use the passed-in history state.
   nsFrameConstructorState(
     nsIPresShell* aPresShell,
+    TreeMatchContext& aTreeMatchContext,
     nsContainerFrame* aFixedContainingBlock,
     nsContainerFrame* aAbsoluteContainingBlock,
     nsContainerFrame* aFloatContainingBlock,
     already_AddRefed<nsILayoutHistoryState> aHistoryState);
   // Get the history state from the pres context's pres shell.
-  nsFrameConstructorState(nsIPresShell*          aPresShell,
-                          nsContainerFrame*      aFixedContainingBlock,
-                          nsContainerFrame*      aAbsoluteContainingBlock,
-                          nsContainerFrame*      aFloatContainingBlock);
+  nsFrameConstructorState(nsIPresShell* aPresShell,
+                          TreeMatchContext& aTreeMatchContext,
+                          nsContainerFrame* aFixedContainingBlock,
+                          nsContainerFrame* aAbsoluteContainingBlock,
+                          nsContainerFrame* aFloatContainingBlock);
 
   ~nsFrameConstructorState();
 
   // Function to push the existing absolute containing block state and
   // create a new scope. Code that uses this function should get matching
   // logic in GetAbsoluteContainingBlock.
   // Also makes aNewAbsoluteContainingBlock the containing block for
   // fixed-pos elements if necessary.
@@ -970,16 +972,17 @@ protected:
   // AddToAttachedQueue on all of them, in order.
   LinkedList<PendingBinding> mPendingBindings;
 
   PendingBinding* mCurrentPendingBindingInsertionPoint;
 };
 
 nsFrameConstructorState::nsFrameConstructorState(
   nsIPresShell* aPresShell,
+  TreeMatchContext& aTreeMatchContext,
   nsContainerFrame* aFixedContainingBlock,
   nsContainerFrame* aAbsoluteContainingBlock,
   nsContainerFrame* aFloatContainingBlock,
   already_AddRefed<nsILayoutHistoryState> aHistoryState)
   : mPresContext(aPresShell->GetPresContext()),
     mPresShell(aPresShell),
     mFrameManager(aPresShell->FrameManager()),
 #ifdef MOZ_XUL
@@ -996,34 +999,36 @@ nsFrameConstructorState::nsFrameConstruc
     mFrameState(aHistoryState),
     mAdditionalStateBits(nsFrameState(0)),
     // If the fixed-pos containing block is equal to the abs-pos containing
     // block, use the abs-pos containing block's abs-pos list for fixed-pos
     // frames.
     mFixedPosIsAbsPos(aFixedContainingBlock == aAbsoluteContainingBlock),
     mHavePendingPopupgroup(false),
     mCreatingExtraFrames(false),
-    mTreeMatchContext(true, nsRuleWalker::eRelevantLinkUnvisited,
-                      aPresShell->GetDocument()),
+    mTreeMatchContext(aTreeMatchContext),
     mCurrentPendingBindingInsertionPoint(nullptr)
 {
 #ifdef MOZ_XUL
   nsIRootBox* rootBox = nsIRootBox::GetRootBox(aPresShell);
   if (rootBox) {
     mPopupItems.containingBlock = rootBox->GetPopupSetFrame();
   }
 #endif
   MOZ_COUNT_CTOR(nsFrameConstructorState);
 }
 
 nsFrameConstructorState::nsFrameConstructorState(nsIPresShell* aPresShell,
+                                                 TreeMatchContext& aTreeMatchContext,
                                                  nsContainerFrame* aFixedContainingBlock,
                                                  nsContainerFrame* aAbsoluteContainingBlock,
                                                  nsContainerFrame* aFloatContainingBlock)
-  : nsFrameConstructorState(aPresShell, aFixedContainingBlock,
+  : nsFrameConstructorState(aPresShell,
+                            aTreeMatchContext,
+                            aFixedContainingBlock,
                             aAbsoluteContainingBlock,
                             aFloatContainingBlock,
                             aPresShell->GetDocument()->GetLayoutHistoryState())
 {
 }
 
 nsFrameConstructorState::~nsFrameConstructorState()
 {
@@ -2097,16 +2102,17 @@ nsCSSFrameConstructor::ConstructTable(ns
   // Create the inner table frame
   nsContainerFrame* innerFrame;
   if (kNameSpaceID_MathML == nameSpaceID)
     innerFrame = NS_NewMathMLmtableFrame(mPresShell, styleContext);
   else
     innerFrame = NS_NewTableFrame(mPresShell, styleContext);
 
   InitAndRestoreFrame(aState, content, newFrame, innerFrame);
+  innerFrame->AddStateBits(NS_FRAME_OWNS_ANON_BOXES);
 
   // Put the newly created frames into the right child list
   SetInitialSingleChild(newFrame, innerFrame);
 
   aState.AddChild(newFrame, aFrameItems, content, styleContext, aParentFrame);
 
   if (!mRootElementFrame) {
     // The frame we're constructing will be the root element frame.
@@ -2287,16 +2293,17 @@ nsCSSFrameConstructor::ConstructTableCel
     // Warning: If you change this and add a wrapper frame around table cell
     // frames, make sure Bug 368554 doesn't regress!
     // See IsInAutoWidthTableCellForQuirk() in nsImageFrame.cpp.
     newFrame = NS_NewTableCellFrame(mPresShell, styleContext, tableFrame);
   }
 
   // Initialize the table cell frame
   InitAndRestoreFrame(aState, content, aParentFrame, newFrame);
+  newFrame->AddStateBits(NS_FRAME_OWNS_ANON_BOXES);
 
   // Resolve pseudo style and initialize the body cell frame
   RefPtr<nsStyleContext> innerPseudoStyle;
   innerPseudoStyle = mPresShell->StyleSet()->
     ResolveAnonymousBoxStyle(nsCSSAnonBoxes::cellContent, styleContext);
 
   // Create a block frame that will format the cell's content
   bool isBlock;
@@ -2413,23 +2420,25 @@ nsCSSFrameConstructor::ConstructDocEleme
   if (nsPresContext* presContext = mPresShell->GetPresContext()) {
     propagatedScrollFrom = presContext->UpdateViewportScrollbarStylesOverride();
   }
 
   SetUpDocElementContainingBlock(aDocElement);
 
   NS_ASSERTION(mDocElementContainingBlock, "Should have parent by now");
 
+  TreeMatchContext matchContext(mDocument, TreeMatchContext::ForFrameConstruction);
+  // Initialize the ancestor filter with null for now; we'll push
+  // aDocElement once we finish resolving style for it.
+  matchContext.InitAncestors(nullptr);
   nsFrameConstructorState state(mPresShell,
+                                matchContext,
                                 GetAbsoluteContainingBlock(mDocElementContainingBlock, FIXED_POS),
                                 nullptr,
                                 nullptr, do_AddRef(aFrameState));
-  // Initialize the ancestor filter with null for now; we'll push
-  // aDocElement once we finish resolving style for it.
-  state.mTreeMatchContext.InitAncestors(nullptr);
 
   // XXXbz why, exactly?
   if (!mTempFrameTreeState)
     state.mPresShell->CaptureHistoryState(getter_AddRefs(mTempFrameTreeState));
 
   // Make sure that we'll handle restyles for this document element in
   // the future.  We need this, because the document element might
   // have stale restyle bits from a previous frame constructor for
@@ -2860,17 +2869,18 @@ nsCSSFrameConstructor::SetUpDocElementCo
   // propagated
   NS_ASSERTION(!isScrollable || !isXUL,
                "XUL documents should never be scrollable - see above");
 
   nsContainerFrame* newFrame = rootFrame;
   RefPtr<nsStyleContext> rootPseudoStyle;
   // we must create a state because if the scrollbars are GFX it needs the
   // state to build the scrollbar frames.
-  nsFrameConstructorState state(mPresShell, nullptr, nullptr, nullptr);
+  TreeMatchContext matchContext(mDocument, TreeMatchContext::ForFrameConstruction);
+  nsFrameConstructorState state(mPresShell, matchContext, nullptr, nullptr, nullptr);
 
   // Start off with the viewport as parent; we'll adjust it as needed.
   nsContainerFrame* parentFrame = viewportFrame;
 
   StyleSetHandle styleSet = mPresShell->StyleSet();
   // If paginated, make sure we don't put scrollbars in
   if (!isScrollable) {
     rootPseudoStyle = styleSet->ResolveAnonymousBoxStyle(rootPseudo,
@@ -4575,16 +4585,17 @@ nsCSSFrameConstructor::BeginBuildingScro
     }
 
     FrameConstructionItemList items;
     AddFCItemsForAnonymousContent(aState, gfxScrollFrame, scrollNAC, items);
     ConstructFramesFromItemList(aState, items, gfxScrollFrame, anonymousItems);
   }
 
   aNewFrame = gfxScrollFrame;
+  gfxScrollFrame->AddStateBits(NS_FRAME_OWNS_ANON_BOXES);
 
   // we used the style that was passed in. So resolve another one.
   StyleSetHandle styleSet = mPresShell->StyleSet();
   RefPtr<nsStyleContext> scrolledChildStyle =
     styleSet->ResolveAnonymousBoxStyle(aScrolledPseudo, contentStyle);
 
   if (gfxScrollFrame) {
      gfxScrollFrame->SetInitialChildList(kPrincipalList, anonymousItems);
@@ -7129,22 +7140,27 @@ nsCSSFrameConstructor::MaybeConstructLaz
     }
   }
 
   RestyleManager()->PostRestyleEventForLazyConstruction();
   return true;
 }
 
 void
-nsCSSFrameConstructor::CreateNeededFrames(nsIContent* aContent)
+nsCSSFrameConstructor::CreateNeededFrames(
+    nsIContent* aContent,
+    TreeMatchContext& aTreeMatchContext)
 {
   NS_ASSERTION(!aContent->HasFlag(NODE_NEEDS_FRAME),
     "shouldn't get here with a content node that has needs frame bit set");
   NS_ASSERTION(aContent->HasFlag(NODE_DESCENDANTS_NEED_FRAMES),
     "should only get here with a content node that has descendants needing frames");
+  MOZ_ASSERT(aTreeMatchContext.mAncestorFilter.HasFilter(),
+             "The whole point of having the tree match context is optimizing "
+             "the ancestor filter usage!");
 
   aContent->UnsetFlags(NODE_DESCENDANTS_NEED_FRAMES);
 
   // We could either descend first (on nodes that don't have NODE_NEEDS_FRAME
   // set) or issue content notifications for our kids first. In absence of
   // anything definitive either way we'll go with the latter.
 
   // It might be better to use GetChildArray and scan it completely first and
@@ -7170,44 +7186,59 @@ nsCSSFrameConstructor::CreateNeededFrame
         inRun = true;
         firstChildInRun = child;
       }
     } else {
       if (inRun) {
         inRun = false;
         // generate a ContentRangeInserted for [startOfRun,i)
         ContentRangeInserted(aContent, firstChildInRun, child, nullptr,
-                             false);
-      }
-    }
-  }
+                             false, &aTreeMatchContext);
+      }
+    }
+  }
+
   if (inRun) {
-    ContentAppended(aContent, firstChildInRun, false);
+    ContentAppended(aContent, firstChildInRun, false, &aTreeMatchContext);
   }
 
   // Now descend.
   FlattenedChildIterator iter(aContent);
   for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
     if (child->HasFlag(NODE_DESCENDANTS_NEED_FRAMES)) {
-      CreateNeededFrames(child);
+      TreeMatchContext::AutoAncestorPusher insertionPointPusher(
+          aTreeMatchContext);
+
+      // Handle stuff like xbl:children.
+      if (child->GetParent() != aContent && child->GetParent()->IsElement()) {
+        insertionPointPusher.PushAncestorAndStyleScope(
+            child->GetParent()->AsElement());
+      }
+
+      TreeMatchContext::AutoAncestorPusher pusher(aTreeMatchContext);
+      pusher.PushAncestorAndStyleScope(child);
+
+      CreateNeededFrames(child, aTreeMatchContext);
     }
   }
 }
 
 void nsCSSFrameConstructor::CreateNeededFrames()
 {
   NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
                "Someone forgot a script blocker");
 
   Element* rootElement = mDocument->GetRootElement();
   NS_ASSERTION(!rootElement || !rootElement->HasFlag(NODE_NEEDS_FRAME),
     "root element should not have frame created lazily");
   if (rootElement && rootElement->HasFlag(NODE_DESCENDANTS_NEED_FRAMES)) {
     BeginUpdate();
-    CreateNeededFrames(rootElement);
+    TreeMatchContext treeMatchContext(mDocument, TreeMatchContext::ForFrameConstruction);
+    treeMatchContext.InitAncestors(rootElement);
+    CreateNeededFrames(rootElement, treeMatchContext);
     EndUpdate();
   }
 }
 
 void
 nsCSSFrameConstructor::IssueSingleInsertNofications(nsIContent* aContainer,
                                                     nsIContent* aStartChild,
                                                     nsIContent* aEndChild,
@@ -7313,20 +7344,23 @@ nsCSSFrameConstructor::MaybeRecreateForF
         return true;
       }
     }
   }
   return false;
 }
 
 nsresult
-nsCSSFrameConstructor::ContentAppended(nsIContent*     aContainer,
-                                       nsIContent*     aFirstNewContent,
-                                       bool            aAllowLazyConstruction)
-{
+nsCSSFrameConstructor::ContentAppended(nsIContent* aContainer,
+                                       nsIContent* aFirstNewContent,
+                                       bool aAllowLazyConstruction,
+                                       TreeMatchContext* aProvidedTreeMatchContext)
+{
+  MOZ_ASSERT_IF(aProvidedTreeMatchContext, !aAllowLazyConstruction);
+
   AUTO_LAYOUT_PHASE_ENTRY_POINT(mPresShell->GetPresContext(), FrameC);
   NS_PRECONDITION(mUpdateCount != 0,
                   "Should be in an update while creating frames");
 
 #ifdef DEBUG
   if (gNoisyContentUpdates) {
     printf("nsCSSFrameConstructor::ContentAppended container=%p "
            "first-child=%p lazy=%d\n",
@@ -7476,21 +7510,30 @@ nsCSSFrameConstructor::ContentAppended(n
 
   // Deal with possible :after generated content on the parent
   nsIFrame* parentAfterFrame;
   parentFrame =
     ::AdjustAppendParentForAfterContent(this, insertion.mContainer, parentFrame,
                                         aFirstNewContent, &parentAfterFrame);
 
   // Create some new frames
+  //
+  // We use the provided tree match context, or create a new one on the fly
+  // otherwise.
+  Maybe<TreeMatchContext> matchContext;
+  if (!aProvidedTreeMatchContext) {
+    matchContext.emplace(mDocument, TreeMatchContext::ForFrameConstruction);
+    matchContext->InitAncestors(aContainer->AsElement());
+  }
   nsFrameConstructorState state(mPresShell,
+                                aProvidedTreeMatchContext
+                                  ? *aProvidedTreeMatchContext : *matchContext,
                                 GetAbsoluteContainingBlock(parentFrame, FIXED_POS),
                                 GetAbsoluteContainingBlock(parentFrame, ABS_POS),
                                 GetFloatContainingBlock(parentFrame));
-  state.mTreeMatchContext.InitAncestors(aContainer->AsElement());
 
   // See if the containing block has :first-letter style applied.
   bool haveFirstLetterStyle = false, haveFirstLineStyle = false;
   nsContainerFrame* containingBlock = state.mFloatedItems.containingBlock;
   if (containingBlock) {
     haveFirstLetterStyle = HasFirstLetterStyle(containingBlock);
     haveFirstLineStyle =
       ShouldHaveFirstLineStyle(containingBlock->GetContent(),
@@ -7697,21 +7740,22 @@ nsCSSFrameConstructor::ContentInserted(n
 // IsValidSibling (the only place GetInsertionPrevSibling might look at the
 // passed in node itself) needs to resolve style on the node we record this and
 // return that this range needs to be split up and inserted separately. Table
 // captions need extra attention as we need to determine where to insert them
 // in the caption list, while skipping any nodes in the range being inserted
 // (because when we treat the caption frames the other nodes have had their
 // frames constructed but not yet inserted into the frame tree).
 nsresult
-nsCSSFrameConstructor::ContentRangeInserted(nsIContent*            aContainer,
-                                            nsIContent*            aStartChild,
-                                            nsIContent*            aEndChild,
+nsCSSFrameConstructor::ContentRangeInserted(nsIContent* aContainer,
+                                            nsIContent* aStartChild,
+                                            nsIContent* aEndChild,
                                             nsILayoutHistoryState* aFrameState,
-                                            bool                   aAllowLazyConstruction)
+                                            bool aAllowLazyConstruction,
+                                            TreeMatchContext* aProvidedTreeMatchContext)
 {
   AUTO_LAYOUT_PHASE_ENTRY_POINT(mPresShell->GetPresContext(), FrameC);
   NS_PRECONDITION(mUpdateCount != 0,
                   "Should be in an update while creating frames");
 
   NS_PRECONDITION(aStartChild, "must always pass a child");
 
   // XXXldb Do we need to re-resolve style to handle the CSS2 + combinator and
@@ -7970,24 +8014,28 @@ nsCSSFrameConstructor::ContentRangeInser
   if (insertion.mParentFrame->IsFrameOfType(nsIFrame::eMathML)) {
     LAYOUT_PHASE_TEMP_EXIT();
     nsresult rv = RecreateFramesForContent(insertion.mParentFrame->GetContent(), false,
                                            REMOVE_FOR_RECONSTRUCTION, nullptr);
     LAYOUT_PHASE_TEMP_REENTER();
     return rv;
   }
 
+  Maybe<TreeMatchContext> matchContext;
+  if (!aProvidedTreeMatchContext) {
+    matchContext.emplace(mDocument, TreeMatchContext::ForFrameConstruction);
+    matchContext->InitAncestors(aContainer ? aContainer->AsElement() : nullptr);
+  }
   nsFrameConstructorState state(mPresShell,
+                                aProvidedTreeMatchContext
+                                  ? *aProvidedTreeMatchContext : *matchContext,
                                 GetAbsoluteContainingBlock(insertion.mParentFrame, FIXED_POS),
                                 GetAbsoluteContainingBlock(insertion.mParentFrame, ABS_POS),
                                 GetFloatContainingBlock(insertion.mParentFrame),
                                 do_AddRef(aFrameState));
-  state.mTreeMatchContext.InitAncestors(aContainer ?
-                                          aContainer->AsElement() :
-                                          nullptr);
 
   // Recover state for the containing block - we need to know if
   // it has :first-letter or :first-line style applied to it. The
   // reason we care is that the internal structure in these cases
   // is not the normal structure and requires custom updating
   // logic.
   nsContainerFrame* containingBlock = state.mFloatedItems.containingBlock;
   bool haveFirstLetterStyle = false;
@@ -8909,17 +8957,20 @@ nsCSSFrameConstructor::CreateContinuingT
     nsIFrame* rgNextInFlow = rowGroupFrame->GetNextInFlow();
     if (rgNextInFlow) {
       rowGroupFrame->SetRepeatable(false);
     }
     else if (rowGroupFrame->IsRepeatable()) {
       // Replicate the header/footer frame.
       nsTableRowGroupFrame*   headerFooterFrame;
       nsFrameItems            childItems;
+
+      TreeMatchContext matchContext(mDocument, TreeMatchContext::ForFrameConstruction);
       nsFrameConstructorState state(mPresShell,
+                                    matchContext,
                                     GetAbsoluteContainingBlock(newFrame, FIXED_POS),
                                     GetAbsoluteContainingBlock(newFrame, ABS_POS),
                                     nullptr);
       state.mCreatingExtraFrames = true;
 
       nsStyleContext* const headerFooterStyleContext = rowGroupFrame->StyleContext();
       headerFooterFrame = static_cast<nsTableRowGroupFrame*>
                                      (NS_NewTableRowGroupFrame(aPresShell, headerFooterStyleContext));
@@ -9183,17 +9234,20 @@ nsCSSFrameConstructor::ReplicateFixedFra
   if (!firstFixed) {
     return NS_OK;
   }
 
   // Don't allow abs-pos descendants of the fixed content to escape the content.
   // This should not normally be possible (because fixed-pos elements should
   // be absolute containers) but fixed-pos tables currently aren't abs-pos
   // containers.
-  nsFrameConstructorState state(mPresShell, aParentFrame,
+  TreeMatchContext matchContext(mDocument, TreeMatchContext::ForFrameConstruction);
+  nsFrameConstructorState state(mPresShell,
+                                matchContext,
+                                aParentFrame,
                                 nullptr,
                                 mRootElementFrame);
   state.mCreatingExtraFrames = true;
 
   // We can't use an ancestor filter here, because we're not going to
   // be usefully recurring down the tree.  This means that other
   // places in frame construction can't assume a filter is
   // initialized!
@@ -11483,17 +11537,20 @@ nsCSSFrameConstructor::CreateLetterFrame
     // frame.
     // XXXbz it would be really nice to destroy the old frame _first_,
     // then create the new one, so we could avoid this hack.
     aTextContent->SetPrimaryFrame(nullptr);
     nsIFrame* textFrame = NS_NewTextFrame(mPresShell, textSC);
 
     NS_ASSERTION(aBlockContinuation == GetFloatContainingBlock(aParentFrame),
                  "Containing block is confused");
+    TreeMatchContext matchContext(mDocument,
+                                  TreeMatchContext::ForFrameConstruction);
     nsFrameConstructorState state(mPresShell,
+                                  matchContext,
                                   GetAbsoluteContainingBlock(aParentFrame, FIXED_POS),
                                   GetAbsoluteContainingBlock(aParentFrame, ABS_POS),
                                   aBlockContinuation);
 
     // Create the right type of first-letter frame
     const nsStyleDisplay* display = sc->StyleDisplay();
     if (display->IsFloatingStyle() && !aParentFrame->IsSVGText()) {
       // Make a floating first-letter frame
@@ -11869,17 +11926,20 @@ nsCSSFrameConstructor::CreateListBoxCont
                                             bool                   aIsAppend)
 {
 #ifdef MOZ_XUL
   nsresult rv = NS_OK;
 
   // Construct a new frame
   if (nullptr != aParentFrame) {
     nsFrameItems            frameItems;
-    nsFrameConstructorState state(mPresShell, GetAbsoluteContainingBlock(aParentFrame, FIXED_POS),
+    TreeMatchContext matchContext(mDocument, TreeMatchContext::ForFrameConstruction);
+    nsFrameConstructorState state(mPresShell,
+                                  matchContext,
+                                  GetAbsoluteContainingBlock(aParentFrame, FIXED_POS),
                                   GetAbsoluteContainingBlock(aParentFrame, ABS_POS),
                                   GetFloatContainingBlock(aParentFrame),
                                   do_AddRef(mTempFrameTreeState.get()));
 
     // If we ever initialize the ancestor filter on |state|, make sure
     // to push the right parent!
 
     RefPtr<nsStyleContext> styleContext;
@@ -12124,16 +12184,17 @@ nsCSSFrameConstructor::ConstructInline(n
   // has to be chopped into several pieces, as described above.
 
   // Grab the first inline's kids
   nsFrameList firstInlineKids = childItems.ExtractHead(firstBlockEnumerator);
   newFrame->SetInitialChildList(kPrincipalList, firstInlineKids);
 
   aFrameItems.AddChild(newFrame);
 
+  newFrame->AddStateBits(NS_FRAME_OWNS_ANON_BOXES);
   CreateIBSiblings(aState, newFrame, positioned, childItems, aFrameItems);
 
   return newFrame;
 }
 
 void
 nsCSSFrameConstructor::CreateIBSiblings(nsFrameConstructorState& aState,
                                         nsContainerFrame* aInitialInline,
@@ -12760,17 +12821,18 @@ nsCSSFrameConstructor::ReframeContaining
 nsresult
 nsCSSFrameConstructor::GenerateChildFrames(nsContainerFrame* aFrame)
 {
   {
     nsAutoScriptBlocker scriptBlocker;
     BeginUpdate();
 
     nsFrameItems childItems;
-    nsFrameConstructorState state(mPresShell, nullptr, nullptr, nullptr);
+    TreeMatchContext matchContext(mDocument, TreeMatchContext::ForFrameConstruction);
+    nsFrameConstructorState state(mPresShell, matchContext, nullptr, nullptr, nullptr);
     // We don't have a parent frame with a pending binding constructor here,
     // so no need to worry about ordering of the kids' constructors with it.
     // Pass null for the PendingBinding.
     ProcessChildren(state, aFrame->GetContent(), aFrame->StyleContext(),
                     aFrame, false, childItems, false,
                     nullptr);
 
     aFrame->SetInitialChildList(kPrincipalList, childItems);
--- a/layout/base/nsCSSFrameConstructor.h
+++ b/layout/base/nsCSSFrameConstructor.h
@@ -82,17 +82,18 @@ public:
 
   // Create frames for content nodes that are marked as needing frames. This
   // should be called before ProcessPendingRestyles.
   // Note: It's the caller's responsibility to make sure to wrap a
   // CreateNeededFrames call in a view update batch and a script blocker.
   void CreateNeededFrames();
 
 private:
-  void CreateNeededFrames(nsIContent* aContent);
+  void CreateNeededFrames(nsIContent* aContent,
+                          TreeMatchContext& aTreeMatchContext);
 
   enum Operation {
     CONTENTAPPEND,
     CONTENTINSERT
   };
 
   // aChild is the child being inserted for inserts, and the first
   // child being appended for appends.
@@ -197,38 +198,50 @@ public:
    * quicker by processing the content tree from top down the next time we call
    * CreateNeededFrames. (We do clear the bits when BindToTree is called on any
    * nsIContent; so any nodes added to the document will not have any lazy bits
    * set.)
    */
 
   // If aAllowLazyConstruction is true then frame construction of the new
   // children can be done lazily.
+  //
+  // When constructing frames lazily, we can keep the tree match context in a
+  // much easier way than nsFrameConstructorState, and thus, we're allowed to
+  // provide a TreeMatchContext to avoid calling InitAncestors repeatedly deep
+  // in the DOM.
   nsresult ContentAppended(nsIContent* aContainer,
                            nsIContent* aFirstNewContent,
-                           bool        aAllowLazyConstruction);
+                           bool aAllowLazyConstruction,
+                           TreeMatchContext* aProvidedTreeMatchContext = nullptr);
 
   // If aAllowLazyConstruction is true then frame construction of the new child
   // can be done lazily.
-  nsresult ContentInserted(nsIContent*            aContainer,
-                           nsIContent*            aChild,
+  nsresult ContentInserted(nsIContent* aContainer,
+                           nsIContent* aChild,
                            nsILayoutHistoryState* aFrameState,
-                           bool                   aAllowLazyConstruction);
+                           bool aAllowLazyConstruction);
 
   // Like ContentInserted but handles inserting the children of aContainer in
   // the range [aStartChild, aEndChild).  aStartChild must be non-null.
   // aEndChild may be null to indicate the range includes all kids after
-  // aStartChild.  If aAllowLazyConstruction is true then frame construction of
+  // aStartChild.
+  //
+  // If aAllowLazyConstruction is true then frame construction of
   // the new children can be done lazily. It is only allowed to be true when
   // inserting a single node.
-  nsresult ContentRangeInserted(nsIContent*            aContainer,
-                                nsIContent*            aStartChild,
-                                nsIContent*            aEndChild,
+  //
+  // See ContentAppended to see why we allow passing an already initialized
+  // TreeMatchContext.
+  nsresult ContentRangeInserted(nsIContent* aContainer,
+                                nsIContent* aStartChild,
+                                nsIContent* aEndChild,
                                 nsILayoutHistoryState* aFrameState,
-                                bool                   aAllowLazyConstruction);
+                                bool aAllowLazyConstruction,
+                                TreeMatchContext* aProvidedTreeMatchContext = nullptr);
 
   enum RemoveFlags {
     REMOVE_CONTENT, REMOVE_FOR_RECONSTRUCTION, REMOVE_DESTROY_FRAMES };
   /**
    * Recreate or destroy frames for aChild in aContainer.
    * aFlags == REMOVE_CONTENT means aChild has been removed from the document.
    * aFlags == REMOVE_FOR_RECONSTRUCTION means the caller will reconstruct the
    *   frames later.
--- a/layout/base/nsStyleChangeList.cpp
+++ b/layout/base/nsStyleChangeList.cpp
@@ -24,19 +24,24 @@ nsStyleChangeList::AppendChange(nsIFrame
              "and should be stripped out");
   MOZ_ASSERT(aContent || !(aHint & nsChangeHint_ReconstructFrame),
              "must have content");
   // XXXbz we should make this take Element instead of nsIContent
   MOZ_ASSERT(!aContent || aContent->IsElement() ||
              // display:contents elements posts the changes for their children:
              (aFrame && aContent->GetParent() &&
              aFrame->PresContext()->FrameManager()->
-               GetDisplayContentsStyleFor(aContent->GetParent())),
+               GetDisplayContentsStyleFor(aContent->GetParent())) ||
+             (aContent->IsNodeOfType(nsINode::eTEXT) &&
+              aContent->IsStyledByServo() &&
+              aContent->HasFlag(NODE_NEEDS_FRAME) &&
+              aHint & nsChangeHint_ReconstructFrame),
              "Shouldn't be trying to restyle non-elements directly, "
-             "except if it's a display:contents child");
+             "except if it's a display:contents child or a text node "
+             "doing lazy frame construction");
   MOZ_ASSERT(!(aHint & nsChangeHint_AllReflowHints) ||
              (aHint & nsChangeHint_NeedReflow),
              "Reflow hint bits set without actually asking for a reflow");
 
   // Filter out all other changes for same content
   if (!IsEmpty() && (aHint & nsChangeHint_ReconstructFrame)) {
     if (aContent) {
       // NOTE: This is captured by reference to please static analysis.
--- a/layout/base/nsStyleChangeList.h
+++ b/layout/base/nsStyleChangeList.h
@@ -31,15 +31,17 @@ class nsStyleChangeList : private AutoTA
   typedef AutoTArray<nsStyleChangeData, 10> base_type;
   nsStyleChangeList(const nsStyleChangeList&) = delete;
 
 public:
   using base_type::begin;
   using base_type::end;
   using base_type::IsEmpty;
   using base_type::Clear;
+  using base_type::Length;
+  using base_type::operator[];
 
   nsStyleChangeList() { MOZ_COUNT_CTOR(nsStyleChangeList); }
   ~nsStyleChangeList() { MOZ_COUNT_DTOR(nsStyleChangeList); }
   void AppendChange(nsIFrame* aFrame, nsIContent* aContent, nsChangeHint aHint);
 };
 
 #endif /* nsStyleChangeList_h___ */
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -84,26 +84,28 @@
 
 #include "gfxContext.h"
 #include "nsRenderingContext.h"
 #include "nsAbsoluteContainingBlock.h"
 #include "StickyScrollContainer.h"
 #include "nsFontInflationData.h"
 #include "nsRegion.h"
 #include "nsIFrameInlines.h"
+#include "nsStyleChangeList.h"
 
 #include "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/EffectCompositor.h"
 #include "mozilla/EffectSet.h"
 #include "mozilla/EventListenerManager.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/LookAndFeel.h"
 #include "mozilla/MouseEvents.h"
+#include "mozilla/ServoStyleSet.h"
 #include "mozilla/css/ImageLoader.h"
 #include "mozilla/gfx/Tools.h"
 #include "nsPrintfCString.h"
 #include "ActiveLayerTracker.h"
 
 #include "nsITheme.h"
 #include "nsThemeConstants.h"
 
@@ -10045,16 +10047,63 @@ nsFrame::BoxReflow(nsBoxLayoutState&    
 nsBoxLayoutMetrics*
 nsFrame::BoxMetrics() const
 {
   nsBoxLayoutMetrics* metrics = Properties().Get(BoxMetricsProperty());
   NS_ASSERTION(metrics, "A box layout method was called but InitBoxMetrics was never called");
   return metrics;
 }
 
+void
+nsFrame::UpdateStyleOfChildAnonBox(nsIFrame* aChildFrame,
+                                   ServoStyleSet& aStyleSet,
+                                   nsStyleChangeList& aChangeList,
+                                   nsChangeHint aHintForThisFrame)
+{
+  MOZ_ASSERT(aChildFrame->GetParent() == this,
+             "This should only be used for children!");
+  MOZ_ASSERT(aChildFrame->GetContent() == GetContent(),
+             "What content node is it a frame for?");
+
+  // We could force the caller to pass in the pseudo, since some callers know it
+  // statically...  But this API is a bit nicer.
+  nsIAtom* pseudo = aChildFrame->StyleContext()->GetPseudo();
+  MOZ_ASSERT(nsCSSAnonBoxes::IsAnonBox(pseudo), "Child is not an anon box?");
+
+  // Anon boxes inherit from their parent; that's us.
+  RefPtr<nsStyleContext> newContext =
+    aStyleSet.ResolveAnonymousBoxStyle(pseudo, StyleContext());
+
+  // Figure out whether we have an actual change.  It's important that we do
+  // this, for several reasons:
+  //
+  // 1) Even if all the child's changes are due to properties it inherits from
+  //    us, it's possible that no one ever asked us for those style structs and
+  //    hence changes to them aren't reflected in aHintForThisFrame at all.
+  // 2) Extensions can add/remove stylesheets that change the styles of
+  //    anonymous boxed directly.
+  uint32_t equalStructs, samePointerStructs; // Not used, actually.
+  nsChangeHint childHint = aChildFrame->StyleContext()->CalcStyleDifference(
+    newContext,
+    NS_HintsNotHandledForDescendantsIn(aHintForThisFrame),
+    &equalStructs,
+    &samePointerStructs);
+  if (childHint) {
+    aChangeList.AppendChange(aChildFrame, aChildFrame->GetContent(), childHint);
+  }
+
+  for (nsIFrame* kid = aChildFrame; kid; kid = kid->GetNextContinuation()) {
+    kid->SetStyleContext(newContext);
+  }
+
+  // Now that we've updated the style on aChildFrame, check whether it itself
+  // has anon boxes to deal with.
+  aChildFrame->UpdateStyleOfOwnedAnonBoxes(aStyleSet, aChangeList, childHint);
+}
+
 /* static */ void
 nsIFrame::AddInPopupStateBitToDescendants(nsIFrame* aFrame)
 {
   if (!aFrame->HasAnyStateBits(NS_FRAME_IN_POPUP) &&
       aFrame->TrackingVisibility()) {
     // Assume all frames in popups are visible.
     aFrame->IncApproximateVisibleCount();
   }
@@ -10259,16 +10308,26 @@ IsFrameScrolledOutOfView(nsIFrame *aFram
 }
 
 bool
 nsIFrame::IsScrolledOutOfView()
 {
   return IsFrameScrolledOutOfView(this);
 }
 
+/* virtual */
+void
+nsIFrame::DoUpdateStyleOfOwnedAnonBoxes(ServoStyleSet& aStyleSet,
+                                      nsStyleChangeList& aChangeList,
+                                      nsChangeHint aHintForThisFrame)
+{
+  MOZ_ASSERT(!(GetStateBits() & NS_FRAME_OWNS_ANON_BOXES));
+  MOZ_ASSERT(false, "Why did this get called?");
+}
+
 nsIFrame::CaretPosition::CaretPosition()
   : mContentOffset(0)
 {
 }
 
 nsIFrame::CaretPosition::~CaretPosition()
 {
 }
--- a/layout/generic/nsFrame.h
+++ b/layout/generic/nsFrame.h
@@ -683,16 +683,23 @@ protected:
   void GetBoxName(nsAutoString& aName) override;
 #endif
 
   nsBoxLayoutMetrics* BoxMetrics() const;
 
   // Fire DOM event. If no aContent argument use frame's mContent.
   void FireDOMEvent(const nsAString& aDOMEventName, nsIContent *aContent = nullptr);
 
+  // A helper for implementing UpdateStyleOfOwnedAnonBoxes for the specific case
+  // of the owned anon box being a child of this frame.
+  void UpdateStyleOfChildAnonBox(nsIFrame* aChildFrame,
+                                 mozilla::ServoStyleSet& aStyleSet,
+                                 nsStyleChangeList& aChangeList,
+                                 nsChangeHint aHintForThisFrame);
+
 private:
   void BoxReflow(nsBoxLayoutState& aState,
                  nsPresContext*    aPresContext,
                  ReflowOutput&     aDesiredSize,
                  nsRenderingContext* aRenderingContext,
                  nscoord aX,
                  nscoord aY,
                  nscoord aWidth,
--- a/layout/generic/nsFrameStateBits.h
+++ b/layout/generic/nsFrameStateBits.h
@@ -261,16 +261,20 @@ FRAME_STATE_BIT(Generic, 52, NS_FRAME_HA
 
 // Frame is not displayed directly due to it being, or being under, an SVG
 // <defs> element or an SVG resource element (<mask>, <pattern>, etc.)
 FRAME_STATE_BIT(Generic, 53, NS_FRAME_IS_NONDISPLAY)
 
 // Frame has a LayerActivityProperty property
 FRAME_STATE_BIT(Generic, 54, NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY)
 
+// Frame owns anonymous boxes whose style contexts it will need to update during
+// a stylo tree traversal.
+FRAME_STATE_BIT(Generic, 55, NS_FRAME_OWNS_ANON_BOXES)
+
 // Set for all descendants of MathML sub/supscript elements (other than the
 // base frame) to indicate that the SSTY font feature should be used.
 FRAME_STATE_BIT(Generic, 58, NS_FRAME_MATHML_SCRIPT_DESCENDANT)
 
 // This state bit is set on frames within token MathML elements if the
 // token represents an <mi> tag whose inner HTML consists of a single
 // non-whitespace character to allow special rendering behaviour.
 FRAME_STATE_BIT(Generic, 59, NS_FRAME_IS_IN_SINGLE_CHAR_MI)
--- a/layout/generic/nsGfxScrollFrame.h
+++ b/layout/generic/nsGfxScrollFrame.h
@@ -1032,16 +1032,24 @@ public:
   virtual bool DragScroll(mozilla::WidgetEvent* aEvent) override {
     return mHelper.DragScroll(aEvent);
   }
 
   virtual void AsyncScrollbarDragRejected() override {
     return mHelper.AsyncScrollbarDragRejected();
   }
 
+  // Update the style on our scrolled frame.
+  virtual void DoUpdateStyleOfOwnedAnonBoxes(mozilla::ServoStyleSet& aStyleSet,
+                                             nsStyleChangeList& aChangeList,
+                                             nsChangeHint aHintForThisFrame) override {
+    UpdateStyleOfChildAnonBox(mHelper.GetScrolledFrame(), aStyleSet,
+                              aChangeList, aHintForThisFrame);
+  }
+
 #ifdef DEBUG_FRAME_DUMP
   virtual nsresult GetFrameName(nsAString& aResult) const override;
 #endif
 
 #ifdef ACCESSIBILITY
   virtual mozilla::a11y::AccType AccessibleType() override;
 #endif
 
@@ -1466,16 +1474,23 @@ public:
   virtual bool DragScroll(mozilla::WidgetEvent* aEvent) override {
     return mHelper.DragScroll(aEvent);
   }
 
   virtual void AsyncScrollbarDragRejected() override {
     return mHelper.AsyncScrollbarDragRejected();
   }
 
+  virtual void DoUpdateStyleOfOwnedAnonBoxes(mozilla::ServoStyleSet& aStyleSet,
+                                             nsStyleChangeList& aChangeList,
+                                             nsChangeHint aHintForThisFrame) override {
+    UpdateStyleOfChildAnonBox(mHelper.GetScrolledFrame(), aStyleSet,
+                              aChangeList, aHintForThisFrame);
+  }
+
 #ifdef DEBUG_FRAME_DUMP
   virtual nsresult GetFrameName(nsAString& aResult) const override;
 #endif
 
 protected:
   nsXULScrollFrame(nsStyleContext* aContext, bool aIsRoot,
                    bool aClipAllDescendants);
 
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -34,16 +34,17 @@
 #include "mozilla/Reflo