Merge inbound to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Wed, 10 May 2017 16:48:03 -0700
changeset 357663 d8762cb967423618ff0a488f14745f60964e5c49
parent 357623 3df2494ade45b92728a1e0e4c0ca257b162ff94c (current diff)
parent 357662 c0318c8fa15e76f6eaab2df21235b196f64187b2 (diff)
child 357664 7a335120af24874f46eeb9d25a6e8056ab14a954
child 357770 8bc6932d46a586881b5f5d35000cd1eb0252a3d0
push id42436
push userkwierso@gmail.com
push dateThu, 11 May 2017 00:29:29 +0000
treeherderautoland@7a335120af24 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone55.0a1
first release with
nightly linux32
d8762cb96742 / 55.0a1 / 20170511100157 / files
nightly linux64
d8762cb96742 / 55.0a1 / 20170511100157 / files
nightly mac
d8762cb96742 / 55.0a1 / 20170511030301 / files
nightly win32
d8762cb96742 / 55.0a1 / 20170511030301 / files
nightly win64
d8762cb96742 / 55.0a1 / 20170511030301 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to central, a=merge MozReview-Commit-ID: DfRZi0gKjit
browser/base/content/browser-sync.js
browser/components/customizableui/CustomizableWidgets.jsm
browser/components/customizableui/content/panelUI.js
docshell/base/nsDocShell.cpp
dom/base/nsContentUtils.cpp
dom/html/HTMLInputElement.cpp
dom/html/HTMLInputElement.h
dom/html/moz.build
layout/reftests/svg/reftest.list
toolkit/moz.configure
--- a/.cron.yml
+++ b/.cron.yml
@@ -13,17 +13,17 @@ jobs:
           - mozilla-central
           - mozilla-aurora
           - date
       when:
           by-project:
             # Match buildbot starts for now
             date: [{hour: 15, minute: 0}]
             mozilla-central: [{hour: 10, minute: 0}]
-            mozilla-aurora: [{hour: 7, minute: 45}]  # Buildbot uses minute 40
+            mozilla-aurora: [] # bug 1358976
             # No default
 
     - name: nightly-desktop-osx
       job:
           type: decision-task
           treeherder-symbol: Nd-OSX
           triggered-by: nightly
           target-tasks-method: nightly_macosx
--- a/browser/base/content/abouthome/aboutHome.css
+++ b/browser/base/content/abouthome/aboutHome.css
@@ -400,24 +400,22 @@ body[narrow] #restorePreviousSession::be
   #defaultSnippet1 {
     background-image: url("chrome://browser/content/abouthome/snippet1@2x.png");
   }
 
   #defaultSnippet2 {
     background-image: url("chrome://browser/content/abouthome/snippet2@2x.png");
   }
 
-  .launchButton::before,
-  #aboutMozilla::before {
+  .launchButton::before {
     transform: scale(.5);
     transform-origin: 0 0;
   }
 
-  .launchButton:dir(rtl)::before,
-  #aboutMozilla:dir(rtl)::before {
+  .launchButton:dir(rtl)::before {
     transform: scale(.5) translateX(32px);
   }
 
   #downloads::before {
     content: url("chrome://browser/content/abouthome/downloads@2x.png");
   }
 
   #bookmarks::before {
--- a/browser/base/content/browser-sync.js
+++ b/browser/base/content/browser-sync.js
@@ -463,16 +463,19 @@ var gSync = {
   /* After we are initialized we perform a once-only check for the sync
      button being in "customize purgatory" and if so, move it to the panel.
      This is done primarily for profiles created before SyncedTabs landed,
      where the button defaulted to being in that purgatory.
      We use a preference to ensure we only do it once, so people can still
      customize it away and have it stick.
   */
   maybeMoveSyncedTabsButton() {
+    if (gPhotonStructure) {
+      return;
+    }
     const prefName = "browser.migrated-sync-button";
     let migrated = Services.prefs.getBoolPref(prefName, false);
     if (migrated) {
       return;
     }
     if (!CustomizableUI.getPlacementOfWidget("sync-button")) {
       CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
     }
--- a/browser/base/content/test/siteIdentity/browser.ini
+++ b/browser/base/content/test/siteIdentity/browser.ini
@@ -85,8 +85,13 @@ support-files =
 tags = mcb
 support-files =
   test_no_mcb_on_http_site_img.html
   test_no_mcb_on_http_site_img.css
   test_no_mcb_on_http_site_font.html
   test_no_mcb_on_http_site_font.css
   test_no_mcb_on_http_site_font2.html
   test_no_mcb_on_http_site_font2.css
+[browser_no_mcb_for_loopback.js]
+tags = mcb
+support-files =
+  ../general/moz.png
+  test_no_mcb_for_loopback.html
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/siteIdentity/browser_no_mcb_for_loopback.js
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// The test loads a HTTPS web page with active content from HTTP loopback URLs
+// and makes sure that the mixed content flags on the docshell are not set.
+//
+// Note that the URLs referenced within the test page intentionally use the
+// unassigned port 8 because we don't want to actually load anything, we just
+// want to check that the URLs are not blocked.
+
+const TEST_URL = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "https://example.com") + "test_no_mcb_for_loopback.html";
+
+const LOOPBACK_PNG_URL = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "http://127.0.0.1:8888") + "moz.png";
+
+const PREF_BLOCK_DISPLAY = "security.mixed_content.block_display_content";
+const PREF_BLOCK_ACTIVE = "security.mixed_content.block_active_content";
+
+registerCleanupFunction(function() {
+  Services.prefs.clearUserPref(PREF_BLOCK_DISPLAY);
+  Services.prefs.clearUserPref(PREF_BLOCK_ACTIVE);
+  gBrowser.removeCurrentTab();
+});
+
+add_task(function* allowLoopbackMixedContent() {
+  Services.prefs.setBoolPref(PREF_BLOCK_DISPLAY, true);
+  Services.prefs.setBoolPref(PREF_BLOCK_ACTIVE, true);
+
+  const tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+  const browser = gBrowser.getBrowserForTab(tab);
+
+  yield ContentTask.spawn(browser, null, function() {
+    is(docShell.hasMixedDisplayContentBlocked, false, "hasMixedDisplayContentBlocked not set");
+    is(docShell.hasMixedActiveContentBlocked, false, "hasMixedActiveContentBlocked not set");
+  });
+
+  // Check that loopback content served from the cache is not blocked.
+  yield ContentTask.spawn(browser, LOOPBACK_PNG_URL, function* (loopbackPNGUrl) {
+    const doc = content.document;
+    const img = doc.createElement("img");
+    const promiseImgLoaded = ContentTaskUtils.waitForEvent(img, "load", false);
+    img.src = loopbackPNGUrl;
+    Assert.ok(!img.complete, "loopback image not yet loaded");
+    doc.body.appendChild(img);
+    yield promiseImgLoaded;
+
+    const cachedImg = doc.createElement("img");
+    cachedImg.src = img.src;
+    Assert.ok(cachedImg.complete, "loopback image loaded from cache");
+  });
+
+  assertMixedContentBlockingState(browser, {
+    activeBlocked: false,
+    activeLoaded: false,
+    passiveLoaded: false,
+  });
+});
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/siteIdentity/test_no_mcb_for_loopback.html
@@ -0,0 +1,50 @@
+<!-- See browser_no_mcb_for_localhost.js -->
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset="utf8">
+    <title>Bug 903966</title>
+  </head>
+
+  <style>
+    @font-face {
+      font-family: "Font-IPv4";
+      src: url("http://127.0.0.1:8/test.ttf");
+    }
+
+    @font-face {
+      font-family: "Font-IPv6";
+      src: url("http://[::1]:8/test.ttf");
+    }
+
+    #ip-v4 {
+      font-family: "Font-IPv4"
+    }
+
+    #ip-v6 {
+      font-family: "Font-IPv6"
+    }
+  </style>
+
+  <body>
+    <div id="ip-v4">test</div>
+    <div id="ip-v6">test</div>
+
+    <img src="http://127.0.0.1:8/test.png">
+    <img src="http://[::1]:8/test.png">
+
+    <iframe src="http://127.0.0.1:8/test.html"></iframe>
+    <iframe src="http://[::1]:8/test.html"></iframe>
+  </body>
+
+  <script src="http://127.0.0.1:8/test.js"></script>
+  <script src="http://[::1]:8/test.js"></script>
+
+  <link href="http://127.0.0.1:8/test.css" rel="stylesheet"></link>
+  <link href="http://[::1]:8/test.css" rel="stylesheet"></link>
+
+  <script>
+    fetch("http://127.0.0.1:8");
+    fetch("http://[::1]:8");
+  </script>
+</html>
--- a/browser/components/customizableui/CustomizableUI.jsm
+++ b/browser/components/customizableui/CustomizableUI.jsm
@@ -24,16 +24,21 @@ XPCOMUtils.defineLazyGetter(this, "gWidg
   return Services.strings.createBundle(kUrl);
 });
 XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils",
   "resource://gre/modules/ShortcutUtils.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "gELS",
   "@mozilla.org/eventlistenerservice;1", "nsIEventListenerService");
 XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
                                   "resource://gre/modules/LightweightThemeManager.jsm");
+XPCOMUtils.defineLazyPreferenceGetter(this, "gPhotonStructure", "browser.photon.structure.enabled", false,
+  (pref, oldValue, newValue) => {
+    CustomizableUIInternal._updateAreasForPhoton();
+    CustomizableUIInternal.notifyListeners("onPhotonChanged");
+  });
 
 const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 const kSpecialWidgetPfx = "customizableui-special-";
 
 const kPrefCustomizationState        = "browser.uiCustomization.state";
 const kPrefCustomizationAutoAdd      = "browser.uiCustomization.autoAdd";
 const kPrefCustomizationDebug        = "browser.uiCustomization.debug";
@@ -149,16 +154,18 @@ var gSingleWrapperCache = new WeakMap();
 var gListeners = new Set();
 
 var gUIStateBeforeReset = {
   uiCustomizationState: null,
   drawInTitlebar: null,
   currentTheme: null,
 };
 
+var gDefaultPanelPlacements = null;
+
 XPCOMUtils.defineLazyGetter(this, "log", () => {
   let scope = {};
   Cu.import("resource://gre/modules/Console.jsm", scope);
   let debug = Services.prefs.getBoolPref(kPrefCustomizationDebug, false);
   let consoleOptions = {
     maxLogLevel: debug ? "all" : "log",
     prefix: "CustomizableUI",
   };
@@ -221,29 +228,18 @@ var CustomizableUIInternal = {
     }
 
     if (AppConstants.NIGHTLY_BUILD) {
       if (Services.prefs.getBoolPref("extensions.webcompat-reporter.enabled")) {
         panelPlacements.push("webcompat-reporter-button");
       }
     }
 
-    this.registerArea(CustomizableUI.AREA_PANEL, {
-      anchor: "PanelUI-menu-button",
-      type: CustomizableUI.TYPE_MENU_PANEL,
-      defaultPlacements: panelPlacements
-    }, true);
-    PanelWideWidgetTracker.init();
-
-    if (Services.prefs.getBoolPref("browser.photon.structure.enabled")) {
-      this.registerArea(CustomizableUI.AREA_FIXED_OVERFLOW_PANEL, {
-        type: CustomizableUI.TYPE_MENU_PANEL,
-        defaultPlacements: [],
-      }, true);
-    }
+    gDefaultPanelPlacements = panelPlacements;
+    this._updateAreasForPhoton();
 
     let navbarPlacements = [
       "urlbar-container",
       "search-container",
       "bookmarks-menu-button",
       "downloads-button",
       "home-button",
     ];
@@ -300,16 +296,44 @@ var CustomizableUIInternal = {
     this.registerArea(CustomizableUI.AREA_ADDONBAR, {
       type: CustomizableUI.TYPE_TOOLBAR,
       legacy: true,
       defaultPlacements: ["addonbar-closebutton", "status-bar"],
       defaultCollapsed: false,
     }, true);
   },
 
+  _updateAreasForPhoton() {
+    if (gPhotonStructure) {
+      if (gAreas.has(CustomizableUI.AREA_PANEL)) {
+        this.unregisterArea(CustomizableUI.AREA_PANEL, true);
+      }
+      this.registerArea(CustomizableUI.AREA_FIXED_OVERFLOW_PANEL, {
+        type: CustomizableUI.TYPE_MENU_PANEL,
+        defaultPlacements: [],
+      }, true);
+    } else {
+      if (gAreas.has(CustomizableUI.AREA_FIXED_OVERFLOW_PANEL)) {
+        this.unregisterArea(CustomizableUI.AREA_FIXED_OVERFLOW_PANEL, true);
+      }
+      // In tests we destroy some widgets. Those should be removed from the
+      // default placements when re-registering the panel.
+      let placementsToUse = Array.from(gDefaultPanelPlacements);
+      if (!gPalette.has("e10s-button") && placementsToUse.includes("e10s-button")) {
+        placementsToUse.splice(placementsToUse.indexOf("e10s-button"), 1);
+      }
+      this.registerArea(CustomizableUI.AREA_PANEL, {
+        anchor: "PanelUI-menu-button",
+        type: CustomizableUI.TYPE_MENU_PANEL,
+        defaultPlacements: placementsToUse,
+      }, true);
+      PanelWideWidgetTracker.init();
+    }
+  },
+
   get _builtinToolbars() {
     let toolbars = new Set([
       CustomizableUI.AREA_NAVBAR,
       CustomizableUI.AREA_BOOKMARKS,
       CustomizableUI.AREA_TABSTRIP,
       CustomizableUI.AREA_ADDONBAR,
     ]);
     if (AppConstants.platform != "macosx") {
@@ -3722,17 +3746,17 @@ this.CustomizableUI = {
    *         palette, undefined otherwise.
    */
   getPlaceForItem(aElement) {
     let place;
     let node = aElement;
     while (node && !place) {
       if (node.localName == "toolbar")
         place = "toolbar";
-      else if (node.id == CustomizableUI.AREA_PANEL)
+      else if (node.id == CustomizableUI.AREA_PANEL || node.id == CustomizableUI.AREA_FIXED_OVERFLOW_PANEL)
         place = "panel";
       else if (node.id == "customization-palette")
         place = "palette";
 
       node = node.parentNode;
     }
     return place;
   },
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -745,17 +745,28 @@ const CustomizableWidgets = [
           CustomizableUI.removeListener(listener);
         },
 
         onWidgetDrag: (aWidgetId, aArea) => {
           if (aWidgetId != this.id)
             return;
           aArea = aArea || this.currentArea;
           updateCombinedWidgetStyle(node, aArea, true);
-        }
+        },
+
+        // Hack. This can go away when the old menu panel goes away (post photon).
+        // We need it right now for the case where we re-register the old-style
+        // main menu panel if photon is disabled at runtime, and we automatically
+        // put the widgets in there, so they get the right style in the panel.
+        onAreaNodeRegistered(aArea, aContainer) {
+          if (aContainer.ownerDocument == node.ownerDocument &&
+              aArea == CustomizableUI.AREA_PANEL) {
+            updateCombinedWidgetStyle(node, aArea, true);
+          }
+        },
       };
       CustomizableUI.addListener(listener);
 
       return node;
     }
   }, {
     id: "edit-controls",
     type: "custom",
@@ -841,17 +852,28 @@ const CustomizableWidgets = [
           CustomizableUI.removeListener(listener);
         },
 
         onWidgetDrag: (aWidgetId, aArea) => {
           if (aWidgetId != this.id)
             return;
           aArea = aArea || this.currentArea;
           updateCombinedWidgetStyle(node, aArea);
-        }
+        },
+
+        // Hack. This can go away when the old menu panel goes away (post photon).
+        // We need it right now for the case where we re-register the old-style
+        // main menu panel if photon is disabled at runtime, and we automatically
+        // put the widgets in there, so they get the right style in the panel.
+        onAreaNodeRegistered(aArea, aContainer) {
+          if (aContainer.ownerDocument == node.ownerDocument &&
+              aArea == CustomizableUI.AREA_PANEL) {
+            updateCombinedWidgetStyle(node, aArea);
+          }
+        },
       };
       CustomizableUI.addListener(listener);
 
       return node;
     }
   },
   {
     id: "feed-button",
--- a/browser/components/customizableui/CustomizeMode.jsm
+++ b/browser/components/customizableui/CustomizeMode.jsm
@@ -33,16 +33,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource:///modules/DragPositionManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry",
                                   "resource:///modules/BrowserUITelemetry.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
                                   "resource://gre/modules/LightweightThemeManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
                                   "resource:///modules/sessionstore/SessionStore.jsm");
 
+XPCOMUtils.defineLazyPreferenceGetter(this, "gPhotonStructure",
+  "browser.photon.structure.enabled", false);
+
 let gDebug;
 XPCOMUtils.defineLazyGetter(this, "log", () => {
   let scope = {};
   Cu.import("resource://gre/modules/Console.jsm", scope);
   gDebug = Services.prefs.getBoolPref(kPrefCustomizationDebug, false);
   let consoleOptions = {
     maxLogLevel: gDebug ? "all" : "log",
     prefix: "CustomizeMode",
@@ -273,49 +276,60 @@ CustomizeMode.prototype = {
 
       // Add a keypress listener to the document so that we can quickly exit
       // customization mode when pressing ESC.
       document.addEventListener("keypress", this);
 
       // Same goes for the menu button - if we're customizing, a click on the
       // menu button means a quick exit from customization mode.
       window.PanelUI.hide();
-      window.PanelUI.menuButton.addEventListener("command", this);
-      window.PanelUI.menuButton.open = true;
-      window.PanelUI.beginBatchUpdate();
 
-      // The menu panel is lazy, and registers itself when the popup shows. We
-      // need to force the menu panel to register itself, or else customization
-      // is really not going to work. We pass "true" to ensureReady to
-      // indicate that we're handling calling startBatchUpdate and
-      // endBatchUpdate.
-      if (!window.PanelUI.isReady) {
-        yield window.PanelUI.ensureReady(true);
-      }
+      let panelHolder = document.getElementById("customization-panelHolder");
+      if (gPhotonStructure) {
+        panelHolder.appendChild(window.PanelUI.overflowFixedList);
+        window.PanelUI.overflowFixedList.setAttribute("customizing", true);
+        window.PanelUI.menuButton.disabled = true;
+      } else {
+        window.PanelUI.menuButton.addEventListener("command", this);
+        window.PanelUI.menuButton.open = true;
+        window.PanelUI.beginBatchUpdate();
 
-      // Hide the palette before starting the transition for increased perf.
-      this.visiblePalette.hidden = true;
-      this.visiblePalette.removeAttribute("showing");
+        // The menu panel is lazy, and registers itself when the popup shows. We
+        // need to force the menu panel to register itself, or else customization
+        // is really not going to work. We pass "true" to ensureReady to
+        // indicate that we're handling calling startBatchUpdate and
+        // endBatchUpdate.
+        if (!window.PanelUI.isReady) {
+          yield window.PanelUI.ensureReady(true);
+        }
 
-      // Disable the button-text fade-out mask
-      // during the transition for increased perf.
-      let panelContents = window.PanelUI.contents;
-      panelContents.setAttribute("customize-transitioning", "true");
+        // Hide the palette before starting the transition for increased perf.
+        this.visiblePalette.hidden = true;
+        this.visiblePalette.removeAttribute("showing");
+
+        // Disable the button-text fade-out mask
+        // during the transition for increased perf.
+        window.PanelUI.contents.setAttribute("customize-transitioning", "true");
 
-      // Move the mainView in the panel to the holder so that we can see it
-      // while customizing.
-      let mainView = window.PanelUI.mainView;
-      let panelHolder = document.getElementById("customization-panelHolder");
-      panelHolder.appendChild(mainView);
+        // Move the mainView in the panel to the holder so that we can see it
+        // while customizing.
+        let mainView = window.PanelUI.mainView;
+        panelHolder.appendChild(mainView);
 
-      let customizeButton = document.getElementById("PanelUI-customize");
-      customizeButton.setAttribute("enterLabel", customizeButton.getAttribute("label"));
-      customizeButton.setAttribute("label", customizeButton.getAttribute("exitLabel"));
-      customizeButton.setAttribute("enterTooltiptext", customizeButton.getAttribute("tooltiptext"));
-      customizeButton.setAttribute("tooltiptext", customizeButton.getAttribute("exitTooltiptext"));
+        let customizeButton = document.getElementById("PanelUI-customize");
+        customizeButton.setAttribute("enterLabel", customizeButton.getAttribute("label"));
+        customizeButton.setAttribute("label", customizeButton.getAttribute("exitLabel"));
+        customizeButton.setAttribute("enterTooltiptext", customizeButton.getAttribute("tooltiptext"));
+        customizeButton.setAttribute("tooltiptext", customizeButton.getAttribute("exitTooltiptext"));
+
+        this._mainViewContext = mainView.getAttribute("context");
+        if (this._mainViewContext) {
+          mainView.removeAttribute("context");
+        }
+      }
 
       this._transitioning = true;
 
       let customizer = document.getElementById("customization-container");
       customizer.parentNode.selectedPanel = customizer;
       customizer.hidden = false;
 
       this._wrapToolbarItemSync(CustomizableUI.AREA_TABSTRIP);
@@ -326,41 +340,42 @@ CustomizeMode.prototype = {
 
       yield this._doTransition(true);
 
       Services.obs.addObserver(this, "lightweight-theme-window-updated");
 
       // Let everybody in this window know that we're about to customize.
       CustomizableUI.dispatchToolboxEvent("customizationstarting", {}, window);
 
-      this._mainViewContext = mainView.getAttribute("context");
-      if (this._mainViewContext) {
-        mainView.removeAttribute("context");
+      if (!gPhotonStructure) {
+        this._showPanelCustomizationPlaceholders();
       }
 
-      this._showPanelCustomizationPlaceholders();
-
       yield this._wrapToolbarItems();
       this.populatePalette();
 
       this._addDragHandlers(this.visiblePalette);
 
       window.gNavToolbox.addEventListener("toolbarvisibilitychange", this);
 
-      document.getElementById("PanelUI-help").setAttribute("disabled", true);
-      document.getElementById("PanelUI-quit").setAttribute("disabled", true);
+      if (!gPhotonStructure) {
+        document.getElementById("PanelUI-help").setAttribute("disabled", true);
+        document.getElementById("PanelUI-quit").setAttribute("disabled", true);
+      }
 
       this._updateResetButton();
       this._updateUndoResetButton();
 
       this._skipSourceNodeCheck = Services.prefs.getPrefType(kSkipSourceNodePref) == Ci.nsIPrefBranch.PREF_BOOL &&
                                   Services.prefs.getBoolPref(kSkipSourceNodePref);
 
       CustomizableUI.addListener(this);
-      window.PanelUI.endBatchUpdate();
+      if (!gPhotonStructure) {
+        window.PanelUI.endBatchUpdate();
+      }
       this._customizing = true;
       this._transitioning = false;
 
       // Show the palette now that the transition has finished.
       this.visiblePalette.hidden = false;
       window.setTimeout(() => {
         // Force layout reflow to ensure the animation runs,
         // and make it async so it doesn't affect the timing.
@@ -368,22 +383,26 @@ CustomizeMode.prototype = {
         this.visiblePalette.setAttribute("showing", "true");
       }, 0);
       this._updateEmptyPaletteNotice();
 
       this._updateLWThemeButtonIcon();
       this.maybeShowTip(panelHolder);
 
       this._handler.isEnteringCustomizeMode = false;
-      panelContents.removeAttribute("customize-transitioning");
+      if (!gPhotonStructure) {
+        window.PanelUI.contents.removeAttribute("customize-transitioning");
+      }
 
       CustomizableUI.dispatchToolboxEvent("customizationready", {}, window);
       this._enableOutlinesTimeout = window.setTimeout(() => {
         this.document.getElementById("nav-bar").setAttribute("showoutline", "true");
-        this.panelUIContents.setAttribute("showoutline", "true");
+        if (!gPhotonStructure) {
+          this.panelUIContents.setAttribute("showoutline", "true");
+        }
         delete this._enableOutlinesTimeout;
       }, 0);
 
       if (!this._wantToBeInCustomizeMode) {
         this.exit();
       }
     }.bind(this)).then(null, e => {
       log.error("Error entering customize mode", e);
@@ -426,35 +445,39 @@ CustomizeMode.prototype = {
       this.panelUIContents.removeAttribute("showoutline");
     }
 
     this._removeExtraToolbarsIfEmpty();
 
     CustomizableUI.removeListener(this);
 
     this.document.removeEventListener("keypress", this);
-    this.window.PanelUI.menuButton.removeEventListener("command", this);
-    this.window.PanelUI.menuButton.open = false;
+    if (!gPhotonStructure) {
+      this.window.PanelUI.menuButton.removeEventListener("command", this);
+      this.window.PanelUI.menuButton.open = false;
 
-    this.window.PanelUI.beginBatchUpdate();
+      this.window.PanelUI.beginBatchUpdate();
 
-    this._removePanelCustomizationPlaceholders();
+      this._removePanelCustomizationPlaceholders();
+    }
 
     let window = this.window;
     let document = this.document;
 
     // Hide the palette before starting the transition for increased perf.
     this.visiblePalette.hidden = true;
     this.visiblePalette.removeAttribute("showing");
     this.paletteEmptyNotice.hidden = true;
 
-    // Disable the button-text fade-out mask
-    // during the transition for increased perf.
-    let panelContents = window.PanelUI.contents;
-    panelContents.setAttribute("customize-transitioning", "true");
+    if (!gPhotonStructure) {
+      // Disable the button-text fade-out mask
+      // during the transition for increased perf.
+      let panelContents = window.PanelUI.contents;
+      panelContents.setAttribute("customize-transitioning", "true");
+    }
 
     // Disable the reset and undo reset buttons while transitioning:
     let resetButton = this.document.getElementById("customization-reset-button");
     let undoResetButton = this.document.getElementById("customization-undo-reset-button");
     undoResetButton.hidden = resetButton.disabled = true;
 
     this._transitioning = true;
 
@@ -495,62 +518,72 @@ CustomizeMode.prototype = {
 
       // And drop all area references.
       this.areas.clear();
 
       // Let everybody in this window know that we're starting to
       // exit customization mode.
       CustomizableUI.dispatchToolboxEvent("customizationending", {}, window);
 
-      window.PanelUI.setMainView(window.PanelUI.mainView);
       window.PanelUI.menuButton.disabled = false;
+      if (gPhotonStructure) {
+        let overflowContainer = document.getElementById("widget-overflow-scroller");
+        overflowContainer.insertBefore(window.PanelUI.overflowFixedList, overflowContainer.firstElementChild);
+      } else {
+        window.PanelUI.setMainView(window.PanelUI.mainView);
 
-      let customizeButton = document.getElementById("PanelUI-customize");
-      customizeButton.setAttribute("exitLabel", customizeButton.getAttribute("label"));
-      customizeButton.setAttribute("label", customizeButton.getAttribute("enterLabel"));
-      customizeButton.setAttribute("exitTooltiptext", customizeButton.getAttribute("tooltiptext"));
-      customizeButton.setAttribute("tooltiptext", customizeButton.getAttribute("enterTooltiptext"));
+        let customizeButton = document.getElementById("PanelUI-customize");
+        customizeButton.setAttribute("exitLabel", customizeButton.getAttribute("label"));
+        customizeButton.setAttribute("label", customizeButton.getAttribute("enterLabel"));
+        customizeButton.setAttribute("exitTooltiptext", customizeButton.getAttribute("tooltiptext"));
+        customizeButton.setAttribute("tooltiptext", customizeButton.getAttribute("enterTooltiptext"));
 
-      // We have to use setAttribute/removeAttribute here instead of the
-      // property because the XBL property will be set later, and right
-      // now we'd be setting an expando, which breaks the XBL property.
-      document.getElementById("PanelUI-help").removeAttribute("disabled");
-      document.getElementById("PanelUI-quit").removeAttribute("disabled");
-
-      panelContents.removeAttribute("customize-transitioning");
+        // We have to use setAttribute/removeAttribute here instead of the
+        // property because the XBL property will be set later, and right
+        // now we'd be setting an expando, which breaks the XBL property.
+        document.getElementById("PanelUI-help").removeAttribute("disabled");
+        document.getElementById("PanelUI-quit").removeAttribute("disabled");
+        this.panelUIContents.removeAttribute("customize-transitioning");
+      }
 
       // We need to set this._customizing to false before removing the tab
       // or the TabSelect event handler will think that we are exiting
       // customization mode for a second time.
       this._customizing = false;
 
-      let mainView = window.PanelUI.mainView;
-      if (this._mainViewContext) {
-        mainView.setAttribute("context", this._mainViewContext);
+      if (!gPhotonStructure) {
+        let mainView = window.PanelUI.mainView;
+        if (this._mainViewContext) {
+          mainView.setAttribute("context", this._mainViewContext);
+        }
       }
 
       let customizableToolbars = document.querySelectorAll("toolbar[customizable=true]:not([autohide=true])");
       for (let toolbar of customizableToolbars)
         toolbar.removeAttribute("customizing");
 
-      this.window.PanelUI.endBatchUpdate();
+      if (!gPhotonStructure) {
+        this.window.PanelUI.endBatchUpdate();
+      }
       delete this._lastLightweightTheme;
       this._changed = false;
       this._transitioning = false;
       this._handler.isExitingCustomizeMode = false;
       CustomizableUI.dispatchToolboxEvent("aftercustomization", {}, window);
       CustomizableUI.notifyEndCustomizing(window);
 
       if (this._wantToBeInCustomizeMode) {
         this.enter();
       }
     }.bind(this)).then(null, e => {
       log.error("Error exiting customize mode", e);
-      // We should ensure this has been called, and calling it again doesn't hurt:
-      window.PanelUI.endBatchUpdate();
+      if (!gPhotonStructure) {
+        // We should ensure this has been called, and calling it again doesn't hurt:
+        window.PanelUI.endBatchUpdate();
+      }
       this._handler.isExitingCustomizeMode = false;
     });
   },
 
   /**
    * The customize mode transition has 4 phases when entering:
    * 1) Pre-customization mode
    *    This is the starting phase of the browser.
@@ -1523,16 +1556,30 @@ CustomizeMode.prototype = {
   toggleTitlebar(aShouldShowTitlebar) {
     if (!AppConstants.CAN_DRAW_IN_TITLEBAR) {
       return;
     }
     // Drawing in the titlebar means not showing the titlebar, hence the negation:
     Services.prefs.setBoolPref(kDrawInTitlebarPref, !aShouldShowTitlebar);
   },
 
+  get _dwu() {
+    if (!this.__dwu) {
+      this.__dwu = this.window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+    }
+    return this.__dwu;
+  },
+
+  get _dir() {
+    if (!this.__dir) {
+      this.__dir = this.window.getComputedStyle(this.document.documentElement).direction;
+    }
+    return this.__dir;
+  },
+
   _onDragStart(aEvent) {
     __dumpDragData(aEvent);
     let item = aEvent.target;
     while (item && item.localName != "toolbarpaletteitem") {
       if (item.localName == "toolbar") {
         return;
       }
       item = item.parentNode;
@@ -1543,22 +1590,21 @@ CustomizeMode.prototype = {
     let isRemovable = placeForItem == "palette" ||
                       CustomizableUI.isWidgetRemovable(draggedItem);
     if (item.classList.contains(kPlaceholderClass) || !isRemovable) {
       return;
     }
 
     let dt = aEvent.dataTransfer;
     let documentId = aEvent.target.ownerDocument.documentElement.id;
-    let isInToolbar = placeForItem == "toolbar";
 
     dt.mozSetDataAt(kDragDataTypePrefix + documentId, draggedItem.id, 0);
     dt.effectAllowed = "move";
 
-    let itemRect = draggedItem.getBoundingClientRect();
+    let itemRect = this._dwu.getBoundsWithoutFlushing(draggedItem);
     let itemCenter = {x: itemRect.left + itemRect.width / 2,
                       y: itemRect.top + itemRect.height / 2};
     this._dragOffset = {x: aEvent.clientX - itemCenter.x,
                         y: aEvent.clientY - itemCenter.y};
 
     gDraggingInToolbars = new Set();
 
     // Hack needed so that the dragimage will still show the
@@ -1566,21 +1612,23 @@ CustomizeMode.prototype = {
     this._initializeDragAfterMove = () => {
       // For automated tests, we sometimes start exiting customization mode
       // before this fires, which leaves us with placeholders inserted after
       // we've exited. So we need to check that we are indeed customizing.
       if (this._customizing && !this._transitioning) {
         item.hidden = true;
         this._showPanelCustomizationPlaceholders();
         DragPositionManager.start(this.window);
+        let canUsePrevSibling = placeForItem == "toolbar" ||
+                                (placeForItem == "panel" && gPhotonStructure);
         if (item.nextSibling) {
-          this._setDragActive(item.nextSibling, "before", draggedItem.id, isInToolbar);
+          this._setDragActive(item.nextSibling, "before", draggedItem.id, placeForItem);
           this._dragOverItem = item.nextSibling;
-        } else if (isInToolbar && item.previousSibling) {
-          this._setDragActive(item.previousSibling, "after", draggedItem.id, isInToolbar);
+        } else if (canUsePrevSibling && item.previousSibling) {
+          this._setDragActive(item.previousSibling, "after", draggedItem.id, placeForItem);
           this._dragOverItem = item.previousSibling;
         }
       }
       this._initializeDragAfterMove = null;
       this.window.clearTimeout(this._dragInitializeTimeout);
     };
     this._dragInitializeTimeout = this.window.setTimeout(this._initializeDragAfterMove, 0);
   },
@@ -1619,65 +1667,76 @@ CustomizeMode.prototype = {
     }
 
     // Do nothing if the widget is not allowed to move to the target area.
     if (targetArea.id != kPaletteId &&
         !CustomizableUI.canWidgetMoveToArea(draggedItemId, targetArea.id)) {
       return;
     }
 
-    let targetIsToolbar = CustomizableUI.getAreaType(targetArea.id) == "toolbar";
-    let targetNode = this._getDragOverNode(aEvent, targetArea, targetIsToolbar, draggedItemId);
+    let targetAreaType = CustomizableUI.getAreaType(targetArea.id);
+    let targetNode = this._getDragOverNode(aEvent, targetArea, targetAreaType, draggedItemId);
 
     // We need to determine the place that the widget is being dropped in
     // the target.
     let dragOverItem, dragValue;
     if (targetNode == targetArea.customizationTarget) {
       // We'll assume if the user is dragging directly over the target, that
       // they're attempting to append a child to that target.
-      dragOverItem = (targetIsToolbar ? this._findVisiblePreviousSiblingNode(targetNode.lastChild) :
-                                        targetNode.lastChild) || targetNode;
+      dragOverItem = (targetAreaType == "toolbar"
+                        ? this._findVisiblePreviousSiblingNode(targetNode.lastChild)
+                        : targetNode.lastChild) ||
+                     targetNode;
       dragValue = "after";
     } else {
       let targetParent = targetNode.parentNode;
       let position = Array.indexOf(targetParent.children, targetNode);
       if (position == -1) {
-        dragOverItem = targetIsToolbar ? this._findVisiblePreviousSiblingNode(targetNode.lastChild) :
-                                         targetParent.lastChild;
+        dragOverItem = (targetAreaType == "toolbar"
+                          ? this._findVisiblePreviousSiblingNode(targetNode.lastChild)
+                          : targetNode.lastChild);
         dragValue = "after";
       } else {
         dragOverItem = targetParent.children[position];
-        if (!targetIsToolbar) {
-          dragValue = "before";
-        } else {
+        if (targetAreaType == "toolbar") {
           // Check if the aDraggedItem is hovered past the first half of dragOverItem
-          let window = dragOverItem.ownerGlobal;
-          let direction = window.getComputedStyle(dragOverItem).direction;
-          let itemRect = dragOverItem.getBoundingClientRect();
+          let itemRect = this._dwu.getBoundsWithoutFlushing(dragOverItem);
           let dropTargetCenter = itemRect.left + (itemRect.width / 2);
           let existingDir = dragOverItem.getAttribute("dragover");
-          if ((existingDir == "before") == (direction == "ltr")) {
-            dropTargetCenter += (parseInt(dragOverItem.style.borderLeftWidth) || 0) / 2;
+          if (existingDir == "before") {
+            dropTargetCenter += (parseInt(dragOverItem.style.borderInlineStartWidth) || 0) / 2;
           } else {
-            dropTargetCenter -= (parseInt(dragOverItem.style.borderRightWidth) || 0) / 2;
+            dropTargetCenter -= (parseInt(dragOverItem.style.borderInlineEndWidth) || 0) / 2;
           }
-          let before = direction == "ltr" ? aEvent.clientX < dropTargetCenter : aEvent.clientX > dropTargetCenter;
+          let before = this._dir == "ltr" ? aEvent.clientX < dropTargetCenter : aEvent.clientX > dropTargetCenter;
           dragValue = before ? "before" : "after";
+        } else if (targetAreaType == "menu-panel" && gPhotonStructure) {
+          let itemRect = this._dwu.getBoundsWithoutFlushing(dragOverItem);
+          let dropTargetCenter = itemRect.top + (itemRect.height / 2);
+          let existingDir = dragOverItem.getAttribute("dragover");
+          if (existingDir == "before") {
+            dropTargetCenter += (parseInt(dragOverItem.style.borderBlockStartWidth) || 0) / 2;
+          } else {
+            dropTargetCenter -= (parseInt(dragOverItem.style.borderBlockEndWidth) || 0) / 2;
+          }
+          dragValue = aEvent.clientY < dropTargetCenter ? "before" : "after";
+        } else {
+          dragValue = "before";
         }
       }
     }
 
     if (this._dragOverItem && dragOverItem != this._dragOverItem) {
       this._cancelDragActive(this._dragOverItem, dragOverItem);
     }
 
     if (dragOverItem != this._dragOverItem || dragValue != dragOverItem.getAttribute("dragover")) {
       if (dragOverItem != targetArea.customizationTarget) {
-        this._setDragActive(dragOverItem, dragValue, draggedItemId, targetIsToolbar);
-      } else if (targetIsToolbar) {
+        this._setDragActive(dragOverItem, dragValue, draggedItemId, targetAreaType);
+      } else if (targetAreaType == "toolbar") {
         this._updateToolbarCustomizationOutline(this.window, targetArea);
       }
       this._dragOverItem = dragOverItem;
     }
 
     aEvent.preventDefault();
     aEvent.stopPropagation();
   },
@@ -1936,80 +1995,87 @@ CustomizeMode.prototype = {
        prevent content->chrome privilege escalations. */
     let mozSourceNode = aEvent.dataTransfer.mozSourceNode;
     // mozSourceNode is null in the dragStart event handler or if
     // the drag event originated in an external application.
     return !mozSourceNode ||
            mozSourceNode.ownerGlobal != this.window;
   },
 
-  _setDragActive(aItem, aValue, aDraggedItemId, aInToolbar) {
+  _setDragActive(aItem, aValue, aDraggedItemId, aAreaType) {
     if (!aItem) {
       return;
     }
 
+    // getPlaceForItem and getAreaType return different things. Hack-around
+    // rather than having to update every. single. consumer. (and break add-ons)
+    if (aAreaType == "panel") {
+      aAreaType = "menu-panel";
+    }
     if (aItem.getAttribute("dragover") != aValue) {
       aItem.setAttribute("dragover", aValue);
 
       let window = aItem.ownerGlobal;
       let draggedItem = window.document.getElementById(aDraggedItemId);
-      if (!aInToolbar) {
+      if (aAreaType == "palette" || (aAreaType == "menu-panel" && !gPhotonStructure)) {
         this._setGridDragActive(aItem, draggedItem, aValue);
       } else {
         let targetArea = this._getCustomizableParent(aItem);
         this._updateToolbarCustomizationOutline(window, targetArea);
         let makeSpaceImmediately = false;
         if (!gDraggingInToolbars.has(targetArea.id)) {
           gDraggingInToolbars.add(targetArea.id);
           let draggedWrapper = this.document.getElementById("wrapper-" + aDraggedItemId);
           let originArea = this._getCustomizableParent(draggedWrapper);
           makeSpaceImmediately = originArea == targetArea;
         }
-        // Calculate width of the item when it'd be dropped in this position
-        let width = this._getDragItemSize(aItem, draggedItem).width;
-        let direction = window.getComputedStyle(aItem).direction;
+        let propertyToMeasure = aAreaType == "toolbar" ? "width" : "height";
+        // Calculate width/height of the item when it'd be dropped in this position.
+        let borderWidth = this._getDragItemSize(aItem, draggedItem)[propertyToMeasure];
+        let layoutSide = aAreaType == "toolbar" ? "Inline" : "Block";
         let prop, otherProp;
-        // If we're inserting before in ltr, or after in rtl:
-        if ((aValue == "before") == (direction == "ltr")) {
-          prop = "borderLeftWidth";
-          otherProp = "border-right-width";
+        if (aValue == "before") {
+          prop = "border" + layoutSide + "StartWidth";
+          otherProp = "border-" + layoutSide.toLowerCase() + "-end-width";
         } else {
-          // otherwise:
-          prop = "borderRightWidth";
-          otherProp = "border-left-width";
+          prop = "border" + layoutSide + "EndWidth";
+          otherProp = "border-" + layoutSide.toLowerCase() + "-start-width";
         }
         if (makeSpaceImmediately) {
           aItem.setAttribute("notransition", "true");
         }
-        aItem.style[prop] = width + "px";
+        aItem.style[prop] = borderWidth + "px";
         aItem.style.removeProperty(otherProp);
         if (makeSpaceImmediately) {
           // Force a layout flush:
           aItem.getBoundingClientRect();
           aItem.removeAttribute("notransition");
         }
       }
     }
   },
   _cancelDragActive(aItem, aNextItem, aNoTransition) {
     this._updateToolbarCustomizationOutline(aItem.ownerGlobal);
     let currentArea = this._getCustomizableParent(aItem);
     if (!currentArea) {
       return;
     }
-    let isToolbar = CustomizableUI.getAreaType(currentArea.id) == "toolbar";
-    if (isToolbar) {
+    let areaType = CustomizableUI.getAreaType(currentArea.id);
+    let needPositionManager = !areaType || (areaType == "menu-panel" && !gPhotonStructure);
+    if (!needPositionManager) {
       if (aNoTransition) {
         aItem.setAttribute("notransition", "true");
       }
       aItem.removeAttribute("dragover");
-      // Remove both property values in the case that the end padding
+      // Remove all property values in the case that the end padding
       // had been set.
-      aItem.style.removeProperty("border-left-width");
-      aItem.style.removeProperty("border-right-width");
+      aItem.style.removeProperty("border-inline-start-width");
+      aItem.style.removeProperty("border-inline-end-width");
+      aItem.style.removeProperty("border-block-start-width");
+      aItem.style.removeProperty("border-block-end-width");
       if (aNoTransition) {
         // Force a layout flush:
         aItem.getBoundingClientRect();
         aItem.removeAttribute("notransition");
       }
     } else {
       aItem.removeAttribute("dragover");
       if (aNextItem) {
@@ -2103,17 +2169,17 @@ CustomizeMode.prototype = {
       if (areas.indexOf(aElement.id) != -1) {
         return aElement;
       }
       aElement = aElement.parentNode;
     }
     return null;
   },
 
-  _getDragOverNode(aEvent, aAreaElement, aInToolbar, aDraggedItemId) {
+  _getDragOverNode(aEvent, aAreaElement, aAreaType, aDraggedItemId) {
     let expectedParent = aAreaElement.customizationTarget || aAreaElement;
     // Our tests are stupid. Cope:
     if (!aEvent.clientX && !aEvent.clientY) {
       return aEvent.target;
     }
     // Offset the drag event's position with the offset to the center of
     // the thing we're dragging
     let dragX = aEvent.clientX - this._dragOffset.x;
@@ -2128,17 +2194,17 @@ CustomizeMode.prototype = {
     if (boundsContainer == this.panelUIContents) {
       boundsContainer = boundsContainer.parentNode;
     }
     let bounds = boundsContainer.getBoundingClientRect();
     dragX = Math.min(bounds.right, Math.max(dragX, bounds.left));
     dragY = Math.min(bounds.bottom, Math.max(dragY, bounds.top));
 
     let targetNode;
-    if (aInToolbar) {
+    if (aAreaType == "toolbar" || (aAreaType == "menu-panel" && gPhotonStructure)) {
       targetNode = aAreaElement.ownerDocument.elementFromPoint(dragX, dragY);
       while (targetNode && targetNode.parentNode != expectedParent) {
         targetNode = targetNode.parentNode;
       }
     } else {
       let positionManager = DragPositionManager.getManagerForArea(aAreaElement);
       // Make it relative to the container:
       dragX -= bounds.left;
--- a/browser/components/customizableui/DragPositionManager.jsm
+++ b/browser/components/customizableui/DragPositionManager.jsm
@@ -1,15 +1,19 @@
 /* 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";
 
 Components.utils.import("resource:///modules/CustomizableUI.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyPreferenceGetter(this, "gPhotonStructure",
+  "browser.photon.structure.enabled", false);
 
 var gManagers = new WeakMap();
 
 const kPaletteId = "customization-palette";
 const kPlaceholderClass = "panel-customization-placeholder";
 
 this.EXPORTED_SYMBOLS = ["DragPositionManager"];
 
@@ -374,17 +378,17 @@ AreaPositionManager.prototype = {
       rv = rv[aDirection + "Sibling"];
     } while (rv && rv.getAttribute("hidden") == "true")
     return rv;
   }
 }
 
 var DragPositionManager = {
   start(aWindow) {
-    let areas = [CustomizableUI.AREA_PANEL];
+    let areas = gPhotonStructure ? [] : [CustomizableUI.AREA_PANEL];
     areas = areas.map((area) => CustomizableUI.getCustomizeTargetForArea(area, aWindow));
     areas.push(aWindow.document.getElementById(kPaletteId));
     for (let areaNode of areas) {
       let positionManager = gManagers.get(areaNode);
       if (positionManager) {
         positionManager.update(areaNode);
       } else {
         gManagers.set(areaNode, new AreaPositionManager(areaNode));
--- a/browser/components/customizableui/content/panelUI.js
+++ b/browser/components/customizableui/content/panelUI.js
@@ -40,28 +40,17 @@ const PanelUI = {
       addonNotificationContainer: gPhotonStructure ? "appMenu-addon-banners" : "PanelUI-footer-addons",
 
       overflowFixedList: gPhotonStructure ? "widget-overflow-fixed-list" : "",
     };
   },
 
   _initialized: false,
   init() {
-    for (let [k, v] of Object.entries(this.kElements)) {
-      if (!v) {
-        continue;
-      }
-      // Need to do fresh let-bindings per iteration
-      let getKey = k;
-      let id = v;
-      this.__defineGetter__(getKey, function() {
-        delete this[getKey];
-        return this[getKey] = document.getElementById(id);
-      });
-    }
+    this._initElements();
 
     this.notifications = [];
     this.menuButton.addEventListener("mousedown", this);
     this.menuButton.addEventListener("keypress", this);
     this._overlayScrollListenerBoundFn = this._overlayScrollListener.bind(this);
 
     Services.obs.addObserver(this, "fullscreen-nav-toolbox");
     Services.obs.addObserver(this, "panelUI-notification-main-action");
@@ -70,23 +59,53 @@ const PanelUI = {
     window.addEventListener("fullscreen", this);
     window.matchMedia("(-moz-overlay-scrollbars)").addListener(this._overlayScrollListenerBoundFn);
     CustomizableUI.addListener(this);
 
     for (let event of this.kEvents) {
       this.notificationPanel.addEventListener(event, this);
     }
 
+    this._initPhotonPanel();
+
+    this._initialized = true;
+  },
+
+  reinit() {
+    this._removeEventListeners();
+    // If the Photon pref changes, we need to re-init our element references.
+    this._initElements();
+    this._initPhotonPanel();
+    delete this._readyPromise;
+    this._isReady = false;
+  },
+
+  // We do this sync on init because in order to have the overflow button show up
+  // we need to know whether anything is in the permanent panel area.
+  _initPhotonPanel() {
     if (gPhotonStructure) {
       this.overflowFixedList.hidden = false;
       this.overflowFixedList.nextSibling.hidden = false;
       CustomizableUI.registerMenuPanel(this.overflowFixedList, CustomizableUI.AREA_FIXED_OVERFLOW_PANEL);
     }
+  },
 
-    this._initialized = true;
+  _initElements() {
+    for (let [k, v] of Object.entries(this.kElements)) {
+      if (!v) {
+        continue;
+      }
+      // Need to do fresh let-bindings per iteration
+      let getKey = k;
+      let id = v;
+      this.__defineGetter__(getKey, function() {
+        delete this[getKey];
+        return this[getKey] = document.getElementById(id);
+      });
+    }
   },
 
   _eventListenersAdded: false,
   _ensureEventListenersAdded() {
     if (this._eventListenersAdded)
       return;
     this._addEventListeners();
   },
@@ -95,28 +114,35 @@ const PanelUI = {
     for (let event of this.kEvents) {
       this.panel.addEventListener(event, this);
     }
 
     this.helpView.addEventListener("ViewShowing", this._onHelpViewShow);
     this._eventListenersAdded = true;
   },
 
-  uninit() {
+  _removeEventListeners() {
     for (let event of this.kEvents) {
       this.panel.removeEventListener(event, this);
+    }
+    this.helpView.removeEventListener("ViewShowing", this._onHelpViewShow);
+    this._eventListenersAdded = false;
+  },
+
+  uninit() {
+    this._removeEventListeners();
+    for (let event of this.kEvents) {
       this.notificationPanel.removeEventListener(event, this);
     }
 
     Services.obs.removeObserver(this, "fullscreen-nav-toolbox");
     Services.obs.removeObserver(this, "panelUI-notification-main-action");
     Services.obs.removeObserver(this, "panelUI-notification-dismissed");
 
     window.removeEventListener("fullscreen", this);
-    this.helpView.removeEventListener("ViewShowing", this._onHelpViewShow);
     this.menuButton.removeEventListener("mousedown", this);
     this.menuButton.removeEventListener("keypress", this);
     window.matchMedia("(-moz-overlay-scrollbars)").removeListener(this._overlayScrollListenerBoundFn);
     CustomizableUI.removeListener(this);
     this._overlayScrollListenerBoundFn = null;
   },
 
   /**
@@ -361,16 +387,23 @@ const PanelUI = {
    *        mode will handle calling beginBatchUpdate and endBatchUpdate.
    *
    * @return a Promise that resolves once the panel is ready to roll.
    */
   ensureReady(aCustomizing = false) {
     if (this._readyPromise) {
       return this._readyPromise;
     }
+    this._ensureEventListenersAdded();
+    if (gPhotonStructure) {
+      this.panel.hidden = false;
+      this._readyPromise = Promise.resolve();
+      this._isReady = true;
+      return this._readyPromise;
+    }
     this._readyPromise = Task.spawn(function*() {
       if (!this._initialized) {
         yield new Promise(resolve => {
           let delayedStartupObserver = (aSubject, aTopic, aData) => {
             if (aSubject == window) {
               Services.obs.removeObserver(delayedStartupObserver, "browser-delayed-startup-finished");
               resolve();
             }
@@ -554,16 +587,20 @@ const PanelUI = {
   disableSingleSubviewPanelAnimations() {
     this._disableAnimations = true;
   },
 
   enableSingleSubviewPanelAnimations() {
     this._disableAnimations = false;
   },
 
+  onPhotonChanged() {
+    this.reinit();
+  },
+
   onWidgetAfterDOMChange(aNode, aNextNode, aContainer, aWasRemoval) {
     if (aContainer != this.contents) {
       return;
     }
     if (aWasRemoval) {
       aNode.removeAttribute("auto-hyphens");
     }
   },
--- a/browser/components/customizableui/test/browser_1003588_no_specials_in_panel.js
+++ b/browser/components/customizableui/test/browser_1003588_no_specials_in_panel.js
@@ -15,17 +15,17 @@ function simulateItemDragAndEnd(aToDrag,
     EventUtils.sendDragEvent({ type: "dragend", dataTransfer },
                              aToDrag.parentNode);
   } finally {
     ds.endDragSession(true);
   }
 }
 
 add_task(function* checkNoAddingToPanel() {
-  let area = CustomizableUI.AREA_PANEL;
+  let area = gPhotonStructure ? CustomizableUI.AREA_FIXED_OVERFLOW_PANEL : CustomizableUI.AREA_PANEL;
   let previousPlacements = getAreaWidgetIds(area);
   CustomizableUI.addWidgetToArea("separator", area);
   CustomizableUI.addWidgetToArea("spring", area);
   CustomizableUI.addWidgetToArea("spacer", area);
   assertAreaPlacements(area, previousPlacements);
 
   let oldNumberOfItems = previousPlacements.length;
   if (getAreaWidgetIds(area).length != oldNumberOfItems) {
@@ -56,16 +56,17 @@ add_task(function* checkAddingToToolbar(
   let oldNumberOfItems = previousPlacements.length;
   if (getAreaWidgetIds(area).length != oldNumberOfItems) {
     CustomizableUI.reset();
   }
 });
 
 
 add_task(function* checkDragging() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   let startArea = CustomizableUI.AREA_NAVBAR;
   let targetArea = CustomizableUI.AREA_PANEL;
   let startingToolbarPlacements = getAreaWidgetIds(startArea);
   let startingTargetPlacements = getAreaWidgetIds(targetArea);
 
   CustomizableUI.addWidgetToArea("separator", startArea);
   CustomizableUI.addWidgetToArea("spring", startArea);
   CustomizableUI.addWidgetToArea("spacer", startArea);
--- a/browser/components/customizableui/test/browser_1008559_anchor_undo_restore.js
+++ b/browser/components/customizableui/test/browser_1008559_anchor_undo_restore.js
@@ -5,16 +5,17 @@
 
 const kAnchorAttribute = "cui-anchorid";
 
 /**
  * Check that anchor gets set correctly when moving an item from the panel to the toolbar
  * using 'undo'
  */
 add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   yield startCustomizing();
   let button = document.getElementById("history-panelmenu");
   is(button.getAttribute(kAnchorAttribute), "PanelUI-menu-button",
      "Button (" + button.id + ") starts out with correct anchor");
 
   let navbar = document.getElementById("nav-bar").customizationTarget;
   simulateItemDrag(button, navbar);
   is(CustomizableUI.getPlacementOfWidget(button.id).area, "nav-bar",
--- a/browser/components/customizableui/test/browser_1087303_button_fullscreen.js
+++ b/browser/components/customizableui/test/browser_1087303_button_fullscreen.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/. */
 
 "use strict";
 
 add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   info("Check fullscreen button existence and functionality");
 
   yield PanelUI.show();
 
   let fullscreenButton = document.getElementById("fullscreen-button");
   ok(fullscreenButton, "Fullscreen button appears in Panel Menu");
 
   let fullscreenPromise = promiseFullscreenChange();
--- a/browser/components/customizableui/test/browser_1087303_button_preferences.js
+++ b/browser/components/customizableui/test/browser_1087303_button_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/. */
 
 "use strict";
 
 var newTab = null;
 
 add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   info("Check preferences button existence and functionality");
 
   yield PanelUI.show();
   info("Menu panel was opened");
 
   let preferencesButton = document.getElementById("preferences-button");
   ok(preferencesButton, "Preferences button exists in Panel Menu");
   preferencesButton.click();
--- a/browser/components/customizableui/test/browser_876926_customize_mode_wrapping.js
+++ b/browser/components/customizableui/test/browser_876926_customize_mode_wrapping.js
@@ -150,16 +150,17 @@ function createXULButtonForWindow(win) {
 function removeXULButtonForWindow(win) {
   win.gNavToolbox.palette.querySelector(`#${kXULWidgetId}`).remove();
 }
 
 var otherWin;
 
 // Moving widgets in two windows, one with customize mode and one without, should work.
 add_task(function* MoveWidgetsInTwoWindows() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   yield startCustomizing();
   otherWin = yield openAndLoadWindow(null, true);
   yield otherWin.PanelUI.ensureReady();
   // Create the XUL button to use in the test in both windows.
   createXULButtonForWindow(window);
   createXULButtonForWindow(otherWin);
   ok(CustomizableUI.inDefaultState, "Should start in default state");
 
--- a/browser/components/customizableui/test/browser_876944_customize_mode_create_destroy.js
+++ b/browser/components/customizableui/test/browser_876944_customize_mode_create_destroy.js
@@ -4,16 +4,17 @@
 
 "use strict";
 
 const kTestWidget1 = "test-customize-mode-create-destroy1";
 const kTestWidget2 = "test-customize-mode-create-destroy2";
 
 // Creating and destroying a widget should correctly wrap/unwrap stuff
 add_task(function* testWrapUnwrap() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   yield startCustomizing();
   CustomizableUI.createWidget({id: kTestWidget1, label: "Pretty label", tooltiptext: "Pretty tooltip"});
   let elem = document.getElementById(kTestWidget1);
   let wrapper = document.getElementById("wrapper-" + kTestWidget1);
   ok(elem, "There should be an item");
   ok(wrapper, "There should be a wrapper");
   is(wrapper.firstChild.id, kTestWidget1, "Wrapper should have test widget");
   is(wrapper.parentNode.id, "customization-palette", "Wrapper should be in palette");
--- a/browser/components/customizableui/test/browser_878452_drag_to_panel.js
+++ b/browser/components/customizableui/test/browser_878452_drag_to_panel.js
@@ -1,16 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 // Dragging an item from the palette to another button in the panel should work.
 add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   yield startCustomizing();
   let btn = document.getElementById("feed-button");
   let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
 
   let lastButtonIndex = placements.length - 1;
   let lastButton = placements[lastButtonIndex];
   let placementsAfterInsert = placements.slice(0, lastButtonIndex).concat(["feed-button", lastButton]);
   let lastButtonNode = document.getElementById(lastButton);
--- a/browser/components/customizableui/test/browser_880164_customization_context_menus.js
+++ b/browser/components/customizableui/test/browser_880164_customization_context_menus.js
@@ -6,16 +6,17 @@
 
 requestLongerTimeout(2);
 
 const isOSX = (Services.appinfo.OS === "Darwin");
 
 // Right-click on the home button should
 // show a context menu with options to move it.
 add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   let contextMenu = document.getElementById("toolbar-context-menu");
   let shownPromise = popupShown(contextMenu);
   let homeButton = document.getElementById("home-button");
   EventUtils.synthesizeMouse(homeButton, 2, 2, {type: "contextmenu", button: 2 });
   yield shownPromise;
 
   let expectedEntries = [
     [".customize-context-moveToPanel", true],
--- a/browser/components/customizableui/test/browser_880382_drag_wide_widgets_in_panel.js
+++ b/browser/components/customizableui/test/browser_880382_drag_wide_widgets_in_panel.js
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 requestLongerTimeout(5);
 
 // Dragging the zoom controls to be before the print button should not move any controls.
 add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   yield startCustomizing();
   let zoomControls = document.getElementById("zoom-controls");
   let printButton = document.getElementById("print-button");
   let placementsAfterMove = ["edit-controls",
                              "new-window-button",
                              "privatebrowsing-button",
                              "save-page-button",
                              "zoom-controls",
--- a/browser/components/customizableui/test/browser_884402_customize_from_overflow.js
+++ b/browser/components/customizableui/test/browser_884402_customize_from_overflow.js
@@ -8,16 +8,17 @@ var originalWindowWidth;
 registerCleanupFunction(function() {
   overflowPanel.removeAttribute("animate");
   window.resizeTo(originalWindowWidth, window.outerHeight);
 });
 
 // Right-click on an item within the overflow panel should
 // show a context menu with options to move it.
 add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
 
   overflowPanel.setAttribute("animate", "false");
 
   originalWindowWidth = window.outerWidth;
   let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
   ok(!navbar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar.");
   window.resizeTo(400, window.outerHeight);
 
--- a/browser/components/customizableui/test/browser_885052_customize_mode_observers_disabed.js
+++ b/browser/components/customizableui/test/browser_885052_customize_mode_observers_disabed.js
@@ -6,16 +6,17 @@
 
 function isFullscreenSizeMode() {
   let sizemode = document.documentElement.getAttribute("sizemode");
   return sizemode == "fullscreen";
 }
 
 // Observers should be disabled when in customization mode.
 add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   // Open and close the panel to make sure that the
   // area is generated before getting a child of the area.
   let shownPanelPromise = promisePanelShown(window);
   PanelUI.toggle({type: "command"});
   yield shownPanelPromise;
   let hiddenPanelPromise = promisePanelHidden(window);
   PanelUI.toggle({type: "command"});
   yield hiddenPanelPromise;
--- a/browser/components/customizableui/test/browser_890140_orphaned_placeholders.js
+++ b/browser/components/customizableui/test/browser_890140_orphaned_placeholders.js
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 requestLongerTimeout(2);
 
 // One orphaned item should have two placeholders next to it.
 add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   yield startCustomizing();
 
   if (isInDevEdition()) {
     CustomizableUI.addWidgetToArea("developer-button", CustomizableUI.AREA_PANEL);
     ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
   }
   if (!isInDevEdition()) {
     ok(CustomizableUI.inDefaultState, "Should be in default state.");
--- a/browser/components/customizableui/test/browser_901207_searchbar_in_panel.js
+++ b/browser/components/customizableui/test/browser_901207_searchbar_in_panel.js
@@ -11,16 +11,17 @@ function* waitForSearchBarFocus() {
   yield waitForCondition(function() {
     logActiveElement();
     return document.activeElement === searchbar.textbox.inputField;
   });
 }
 
 // Ctrl+K should open the menu panel and focus the search bar if the search bar is in the panel.
 add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   let searchbar = document.getElementById("searchbar");
   gCustomizeMode.addToPanel(searchbar);
   let placement = CustomizableUI.getPlacementOfWidget("search-container");
   is(placement.area, CustomizableUI.AREA_PANEL, "Should be in panel");
 
   let shownPanelPromise = promisePanelShown(window);
   sendWebSearchKeyCommand();
   yield shownPanelPromise;
--- a/browser/components/customizableui/test/browser_914863_disabled_help_quit_buttons.js
+++ b/browser/components/customizableui/test/browser_914863_disabled_help_quit_buttons.js
@@ -1,14 +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/. */
 
 // Entering then exiting customization mode should reenable the Help and Exit buttons.
 add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   yield startCustomizing();
   let helpButton = document.getElementById("PanelUI-help");
   let quitButton = document.getElementById("PanelUI-quit");
   ok(helpButton.getAttribute("disabled") == "true", "Help button should be disabled while in customization mode.");
   ok(quitButton.getAttribute("disabled") == "true", "Quit button should be disabled while in customization mode.");
   yield endCustomizing();
 
   ok(!helpButton.hasAttribute("disabled"), "Help button should not be disabled.");
--- a/browser/components/customizableui/test/browser_938995_indefaultstate_nonremovable.js
+++ b/browser/components/customizableui/test/browser_938995_indefaultstate_nonremovable.js
@@ -2,17 +2,19 @@
  * 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 kWidgetId = "test-non-removable-widget";
 
 // Adding non-removable items to a toolbar or the panel shouldn't change inDefaultState
-add_task(function() {
+add_task(async function() {
+  await SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
+  await PanelUI.ensureReady();
   ok(CustomizableUI.inDefaultState, "Should start in default state");
 
   let button = createDummyXULButton(kWidgetId, "Test non-removable inDefaultState handling");
   CustomizableUI.addWidgetToArea(kWidgetId, CustomizableUI.AREA_NAVBAR);
   button.setAttribute("removable", "false");
   ok(CustomizableUI.inDefaultState, "Should still be in default state after navbar addition");
   button.remove();
 
--- a/browser/components/customizableui/test/browser_940307_panel_click_closure_handling.js
+++ b/browser/components/customizableui/test/browser_940307_panel_click_closure_handling.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/. */
 
 "use strict";
 
 var button, menuButton;
 /* Clicking a button should close the panel */
 add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   button = document.createElement("toolbarbutton");
   button.id = "browser_940307_button";
   button.setAttribute("label", "Button");
   PanelUI.contents.appendChild(button);
   yield PanelUI.show();
   let hiddenAgain = promisePanelHidden(window);
   EventUtils.synthesizeMouseAtCenter(button, {});
   yield hiddenAgain;
--- a/browser/components/customizableui/test/browser_947914_button_addons.js
+++ b/browser/components/customizableui/test/browser_947914_button_addons.js
@@ -3,16 +3,17 @@
   * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 var initialLocation = gBrowser.currentURI.spec;
 var newTab = null;
 
 add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   info("Check addons button existence and functionality");
 
   yield PanelUI.show();
   info("Menu panel was opened");
 
   let addonsButton = document.getElementById("add-ons-button");
   ok(addonsButton, "Add-ons button exists in Panel Menu");
   addonsButton.click();
--- a/browser/components/customizableui/test/browser_947914_button_copy.js
+++ b/browser/components/customizableui/test/browser_947914_button_copy.js
@@ -3,16 +3,17 @@
   * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 var initialLocation = gBrowser.currentURI.spec;
 var globalClipboard;
 
 add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   yield BrowserTestUtils.withNewTab({gBrowser, url: "about:blank"}, function*() {
     info("Check copy button existence and functionality");
 
     let testText = "copy text test";
 
     gURLBar.focus();
     info("The URL bar was focused");
     yield PanelUI.show();
--- a/browser/components/customizableui/test/browser_947914_button_cut.js
+++ b/browser/components/customizableui/test/browser_947914_button_cut.js
@@ -3,16 +3,17 @@
   * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 var initialLocation = gBrowser.currentURI.spec;
 var globalClipboard;
 
 add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   yield BrowserTestUtils.withNewTab({gBrowser, url: "about:blank"}, function*() {
     info("Check cut button existence and functionality");
 
     let testText = "cut text test";
 
     gURLBar.focus();
     yield PanelUI.show();
     info("Menu panel was opened");
--- a/browser/components/customizableui/test/browser_947914_button_find.js
+++ b/browser/components/customizableui/test/browser_947914_button_find.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/. */
 
 "use strict";
 
 add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   info("Check find button existence and functionality");
 
   yield PanelUI.show();
   info("Menu panel was opened");
 
   let findButton = document.getElementById("find-button");
   ok(findButton, "Find button exists in Panel Menu");
 
--- a/browser/components/customizableui/test/browser_947914_button_history.js
+++ b/browser/components/customizableui/test/browser_947914_button_history.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/. */
 
 "use strict";
 
 add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   info("Check history button existence and functionality");
 
   yield PanelUI.show();
   info("Menu panel was opened");
 
   let historyButton = document.getElementById("history-panelmenu");
   ok(historyButton, "History button appears in Panel Menu");
 
--- a/browser/components/customizableui/test/browser_947914_button_newPrivateWindow.js
+++ b/browser/components/customizableui/test/browser_947914_button_newPrivateWindow.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/. */
 
 "use strict";
 
 add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   info("Check private browsing button existence and functionality");
 
   yield PanelUI.show();
   info("Menu panel was opened");
 
   let windowWasHandled = false;
   let privateWindow = null;
 
--- a/browser/components/customizableui/test/browser_947914_button_newWindow.js
+++ b/browser/components/customizableui/test/browser_947914_button_newWindow.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/. */
 
 "use strict";
 
 add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   info("Check new window button existence and functionality");
   yield PanelUI.show();
   info("Menu panel was opened");
 
   let windowWasHandled = false;
   let newWindow = null;
 
   let observerWindowOpened = {
--- a/browser/components/customizableui/test/browser_947914_button_paste.js
+++ b/browser/components/customizableui/test/browser_947914_button_paste.js
@@ -3,16 +3,17 @@
   * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 var initialLocation = gBrowser.currentURI.spec;
 var globalClipboard;
 
 add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   yield BrowserTestUtils.withNewTab({gBrowser, url: "about:blank"}, function*() {
     info("Check paste button existence and functionality");
 
     let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
     globalClipboard = Services.clipboard.kGlobalClipboard;
 
     yield PanelUI.show();
     info("Menu panel was opened");
--- a/browser/components/customizableui/test/browser_947914_button_print.js
+++ b/browser/components/customizableui/test/browser_947914_button_print.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/. */
 
 "use strict";
 
 const isOSX = (Services.appinfo.OS === "Darwin");
 
 add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   yield BrowserTestUtils.withNewTab({
     gBrowser,
     url: "http://example.com/",
   }, function* () {
     info("Check print button existence and functionality");
 
     yield PanelUI.show();
     info("Menu panel was opened");
--- a/browser/components/customizableui/test/browser_947914_button_savePage.js
+++ b/browser/components/customizableui/test/browser_947914_button_savePage.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/. */
 
 "use strict";
 
 add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   info("Check save page button existence");
 
   yield PanelUI.show();
   info("Menu panel was opened");
 
   let savePageButton = document.getElementById("save-page-button");
   ok(savePageButton, "Save Page button exists in Panel Menu");
 
--- a/browser/components/customizableui/test/browser_947914_button_zoomIn.js
+++ b/browser/components/customizableui/test/browser_947914_button_zoomIn.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/. */
 
 "use strict";
 
 var initialPageZoom = ZoomManager.zoom;
 
 add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   info("Check zoom in button existence and functionality");
 
   is(initialPageZoom, 1, "Initial zoom factor should be 1");
 
   yield PanelUI.show();
   info("Menu panel was opened");
 
   let zoomInButton = document.getElementById("zoom-in-button");
--- a/browser/components/customizableui/test/browser_947914_button_zoomOut.js
+++ b/browser/components/customizableui/test/browser_947914_button_zoomOut.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/. */
 
 "use strict";
 
 var initialPageZoom = ZoomManager.zoom;
 
 add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   info("Check zoom out button existence and functionality");
 
   is(initialPageZoom, 1, "Initial zoom factor should be 1");
 
   yield PanelUI.show();
   info("Menu panel was opened");
 
   let zoomOutButton = document.getElementById("zoom-out-button");
--- a/browser/components/customizableui/test/browser_947914_button_zoomReset.js
+++ b/browser/components/customizableui/test/browser_947914_button_zoomReset.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/. */
 
 "use strict";
 
 var initialPageZoom = ZoomManager.zoom;
 
 add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   info("Check zoom reset button existence and functionality");
 
   is(initialPageZoom, 1, "Page zoom reset correctly");
   ZoomManager.zoom = 0.5;
   yield PanelUI.show();
   info("Menu panel was opened");
 
   let zoomResetButton = document.getElementById("zoom-reset-button");
--- a/browser/components/customizableui/test/browser_962884_opt_in_disable_hyphens.js
+++ b/browser/components/customizableui/test/browser_962884_opt_in_disable_hyphens.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/. */
 
 "use strict";
 
 add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   const kNormalLabel = "Character Encoding";
   CustomizableUI.addWidgetToArea("characterencoding-button", CustomizableUI.AREA_NAVBAR);
   let characterEncoding = document.getElementById("characterencoding-button");
   const kOriginalLabel = characterEncoding.getAttribute("label");
   characterEncoding.setAttribute("label", "\u00ad" + kNormalLabel);
   CustomizableUI.addWidgetToArea("characterencoding-button", CustomizableUI.AREA_PANEL);
 
   yield PanelUI.show();
--- a/browser/components/customizableui/test/browser_967000_button_charEncoding.js
+++ b/browser/components/customizableui/test/browser_967000_button_charEncoding.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/. */
 
 "use strict";
 
 const TEST_PAGE = "http://mochi.test:8888/browser/browser/components/customizableui/test/support/test_967000_charEncoding_page.html";
 
 add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   info("Check Character Encoding button functionality");
 
   // add the Character Encoding button to the panel
   CustomizableUI.addWidgetToArea("characterencoding-button",
                                   CustomizableUI.AREA_PANEL);
 
   // check the button's functionality
   yield PanelUI.show();
--- a/browser/components/customizableui/test/browser_967000_button_feeds.js
+++ b/browser/components/customizableui/test/browser_967000_button_feeds.js
@@ -6,16 +6,17 @@
 
 const TEST_PAGE = "http://mochi.test:8888/browser/browser/components/customizableui/test/support/feeds_test_page.html";
 const TEST_FEED = "http://mochi.test:8888/browser/browser/components/customizableui/test/support/test-feed.xml"
 
 var newTab = null;
 var initialLocation = gBrowser.currentURI.spec;
 
 add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   info("Check Subscribe button functionality");
 
   // add the Subscribe button to the panel
   CustomizableUI.addWidgetToArea("feed-button",
                                   CustomizableUI.AREA_PANEL);
 
   // check the button's functionality
   yield PanelUI.show();
--- a/browser/components/customizableui/test/browser_968447_bookmarks_toolbar_items_in_panel.js
+++ b/browser/components/customizableui/test/browser_968447_bookmarks_toolbar_items_in_panel.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/. */
 
 "use strict";
 
 // Bug 968447 - The Bookmarks Toolbar Items doesn't appear as a
 // normal menu panel button in new windows.
 add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   const buttonId = "bookmarks-toolbar-placeholder";
   yield startCustomizing();
   CustomizableUI.addWidgetToArea("personal-bookmarks", CustomizableUI.AREA_PANEL);
   yield endCustomizing();
 
   yield PanelUI.show();
 
   let bookmarksToolbarPlaceholder = document.getElementById(buttonId);
--- a/browser/components/customizableui/test/browser_969661_character_encoding_navbar_disabled.js
+++ b/browser/components/customizableui/test/browser_969661_character_encoding_navbar_disabled.js
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 
 // Adding the character encoding menu to the panel, exiting customize mode,
 // and moving it to the nav-bar should have it enabled, not disabled.
 add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   yield startCustomizing();
   CustomizableUI.addWidgetToArea("characterencoding-button", "PanelUI-contents");
   yield endCustomizing();
   yield PanelUI.show();
   let panelHiddenPromise = promisePanelHidden(window);
   PanelUI.hide();
   yield panelHiddenPromise;
   CustomizableUI.addWidgetToArea("characterencoding-button", "nav-bar");
--- a/browser/components/customizableui/test/browser_970511_undo_restore_default.js
+++ b/browser/components/customizableui/test/browser_970511_undo_restore_default.js
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 requestLongerTimeout(2);
 
 // Restoring default should reset theme and show an "undo" option which undoes the restoring operation.
 add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   let homeButtonId = "home-button";
   CustomizableUI.removeWidgetFromArea(homeButtonId);
   yield startCustomizing();
   ok(!CustomizableUI.inDefaultState, "Not in default state to begin with");
   is(CustomizableUI.getPlacementOfWidget(homeButtonId), null, "Home button is in palette");
   let undoResetButton = document.getElementById("customization-undo-reset-button");
   is(undoResetButton.hidden, true, "The undo button is hidden before reset");
 
--- a/browser/components/customizableui/test/browser_972267_customizationchange_events.js
+++ b/browser/components/customizableui/test/browser_972267_customizationchange_events.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/. */
 
 "use strict";
 
 // Create a new window, then move the home button to the menu and check both windows have
 // customizationchange events fire on the toolbox:
 add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   let newWindow = yield openAndLoadWindow();
   let otherToolbox = newWindow.gNavToolbox;
 
   let handlerCalledCount = 0;
   let handler = (ev) => {
     handlerCalledCount++;
   };
 
--- a/browser/components/customizableui/test/browser_973641_button_addon.js
+++ b/browser/components/customizableui/test/browser_973641_button_addon.js
@@ -3,16 +3,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const kButton = "test_button_for_addon";
 var initialLocation = gBrowser.currentURI.spec;
 
 add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
+
   info("Check addon button functionality");
 
   // create mocked addon button on the navigation bar
   let widgetSpec = {
     id: kButton,
     type: "button",
     onClick() {
       gBrowser.selectedTab = gBrowser.addTab("about:addons");
--- a/browser/components/customizableui/test/browser_981305_separator_insertion.js
+++ b/browser/components/customizableui/test/browser_981305_separator_insertion.js
@@ -57,16 +57,20 @@ function checkSeparatorInsertion(menuId,
     yield panelHiddenPromise;
 
     if (changedPlacement) {
       CustomizableUI.reset();
     }
   };
 }
 
+add_task(async function() {
+  await SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
+});
+
 add_task(checkSeparatorInsertion("menuWebDeveloperPopup", "developer-button", "PanelUI-developerItems"));
 add_task(checkSeparatorInsertion("viewSidebarMenu", "sidebar-button", "PanelUI-sidebarItems"));
 
 registerCleanupFunction(function() {
   for (let el of tempElements) {
     el.remove();
   }
   tempElements = null;
--- a/browser/components/customizableui/test/browser_981418-widget-onbeforecreated-handler.js
+++ b/browser/components/customizableui/test/browser_981418-widget-onbeforecreated-handler.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/. */
 
 "use strict";
 const kWidgetId = "test-981418-widget-onbeforecreated";
 
 // Should be able to add broken view widget
 add_task(function* testAddOnBeforeCreatedWidget() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   let viewShownDeferred = Promise.defer();
   let onBeforeCreatedCalled = false;
   let widgetSpec = {
     id: kWidgetId,
     type: "view",
     viewId: kWidgetId + "idontexistyet",
     onBeforeCreated(doc) {
       let view = doc.createElement("panelview");
--- a/browser/components/customizableui/test/browser_984455_bookmarks_items_reparenting.js
+++ b/browser/components/customizableui/test/browser_984455_bookmarks_items_reparenting.js
@@ -185,16 +185,17 @@ function checkNotOverflowing(aID) {
      "Item with ID " + aID + " should not have overflowedItem attribute");
 }
 
 /**
  * Test that overflowing the bookmarks menu button doesn't break the
  * context menus for the Unsorted and Bookmarks Toolbar menu items.
  */
 add_task(function* testOverflowingBookmarksButtonContextMenu() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   ok(!gNavBar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar.");
   ok(CustomizableUI.inDefaultState, "Should start in default state.");
 
   // Open the Unsorted and Bookmarks Toolbar context menus and ensure
   // that they have views attached.
   yield checkSpecialContextMenus();
 
   yield overflowEverything();
--- a/browser/components/customizableui/test/browser_987640_charEncoding.js
+++ b/browser/components/customizableui/test/browser_987640_charEncoding.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/. */
 
 "use strict";
 
 const TEST_PAGE = "http://mochi.test:8888/browser/browser/components/customizableui/test/support/test_967000_charEncoding_page.html";
 
 add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   info("Check Character Encoding panel functionality");
 
   // add the Character Encoding button to the panel
   CustomizableUI.addWidgetToArea("characterencoding-button",
                                   CustomizableUI.AREA_PANEL);
 
   let newTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_PAGE, true, true);
 
--- a/browser/components/customizableui/test/browser_989751_subviewbutton_class.js
+++ b/browser/components/customizableui/test/browser_989751_subviewbutton_class.js
@@ -47,16 +47,19 @@ function checkSubviewButtonClass(menuId,
     PanelUI.hide();
     yield panelHiddenPromise;
 
     if (changedPlacement) {
       CustomizableUI.reset();
     }
   };
 }
+add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
+});
 
 add_task(checkSubviewButtonClass("menuWebDeveloperPopup", "developer-button", "PanelUI-developerItems"));
 add_task(checkSubviewButtonClass("viewSidebarMenu", "sidebar-button", "PanelUI-sidebarItems"));
 
 registerCleanupFunction(function() {
   tempElement.classList.remove(kCustomClass)
   tempElement = null;
 });
--- a/browser/components/customizableui/test/browser_customizemode_contextmenu_menubuttonstate.js
+++ b/browser/components/customizableui/test/browser_customizemode_contextmenu_menubuttonstate.js
@@ -1,11 +1,12 @@
 "use strict";
 
 add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   ok(!PanelUI.menuButton.hasAttribute("open"), "Menu button should not be 'pressed' outside customize mode");
   yield startCustomizing();
 
   is(PanelUI.menuButton.getAttribute("open"), "true", "Menu button should be 'pressed' when in customize mode");
 
   let contextMenu = document.getElementById("customizationPanelItemContextMenu");
   let shownPromise = popupShown(contextMenu);
   let newWindowButton = document.getElementById("wrapper-new-window-button");
--- a/browser/components/customizableui/test/browser_remote_tabs_button.js
+++ b/browser/components/customizableui/test/browser_remote_tabs_button.js
@@ -10,16 +10,17 @@ const service = syncService.Service;
 Components.utils.import("resource://services-sync/UIState.jsm");
 
 let getState;
 let originalSync;
 let syncWasCalled = false;
 
 // TODO: This test should probably be re-written, we don't really test much here.
 add_task(async function testSyncRemoteTabsButtonFunctionality() {
+  await SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   info("Test the Sync Remote Tabs button in the PanelUI");
   storeInitialValues();
   mockFunctions();
 
   // Force UI update.
   Services.obs.notifyObservers(null, UIState.ON_UPDATE);
 
   // add the sync remote tabs button to the panel
--- a/browser/components/customizableui/test/browser_synced_tabs_menu.js
+++ b/browser/components/customizableui/test/browser_synced_tabs_menu.js
@@ -34,16 +34,17 @@ let mockedInternal = {
   get isConfiguredToSyncTabs() { return true; },
   getTabClients() { return Promise.resolve([]); },
   syncTabs() { return Promise.resolve(); },
   hasSyncedThisSession: false,
 };
 
 
 add_task(function* setup() {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.photon.structure.enabled", false]]});
   let oldInternal = SyncedTabs._internal;
   SyncedTabs._internal = mockedInternal;
 
   // This test hacks some observer states to simulate a user being signed
   // in to Sync - restore them when the test completes.
   let initialObserverStates = {};
   for (let id of ["sync-reauth-state", "sync-setup-state", "sync-syncnow-state"]) {
     initialObserverStates[id] = document.getElementById(id).hidden;
--- a/browser/components/preferences/in-content/tests/browser_performance.js
+++ b/browser/components/preferences/in-content/tests/browser_performance.js
@@ -1,13 +1,18 @@
-SpecialPowers.pushPrefEnv({set: [
-  ["browser.preferences.defaultPerformanceSettings.enabled", true],
-  ["dom.ipc.processCount", 4],
-  ["layers.acceleration.disabled", false],
-]});
+const DEFAULT_HW_ACCEL_PREF = Services.prefs.getDefaultBranch(null).getBoolPref("layers.acceleration.disabled");
+const DEFAULT_PROCESS_COUNT = Services.prefs.getDefaultBranch(null).getIntPref("dom.ipc.processCount");
+
+add_task(function*() {
+  yield SpecialPowers.pushPrefEnv({set: [
+    ["layers.acceleration.disabled", DEFAULT_HW_ACCEL_PREF],
+    ["dom.ipc.processCount", DEFAULT_PROCESS_COUNT],
+    ["browser.preferences.defaultPerformanceSettings.enabled", true],
+  ]});
+});
 
 add_task(function*() {
   let prefs = yield openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
   is(prefs.selectedPane, "paneGeneral", "General pane was selected");
 
   let doc = gBrowser.contentDocument;
   let useRecommendedPerformanceSettings = doc.querySelector("#useRecommendedPerformanceSettings");
 
@@ -20,43 +25,46 @@ add_task(function*() {
   let performanceSettings = doc.querySelector("#performanceSettings");
   is(performanceSettings.hidden, false, "performance settings section is shown");
 
   is(Services.prefs.getBoolPref("browser.preferences.defaultPerformanceSettings.enabled"), false,
      "pref value should be false after clicking on checkbox");
   ok(!useRecommendedPerformanceSettings.checked, "checkbox should not be checked after clicking on checkbox");
 
   let allowHWAccel = doc.querySelector("#allowHWAccel");
-  is(Services.prefs.getBoolPref("layers.acceleration.disabled"), false,
-    "pref value should be false before clicking on checkbox");
-  ok(allowHWAccel.checked, "checkbox should be checked");
+  let allowHWAccelPref = Services.prefs.getBoolPref("layers.acceleration.disabled");
+  is(allowHWAccelPref, DEFAULT_HW_ACCEL_PREF,
+    "pref value should be the default value before clicking on checkbox");
+  is(allowHWAccel.checked, !DEFAULT_HW_ACCEL_PREF, "checkbox should show the invert of the default value");
 
   let contentProcessCount = doc.querySelector("#contentProcessCount");
-  is(Services.prefs.getIntPref("dom.ipc.processCount"), 4, "default pref value should be default value");
-  is(contentProcessCount.selectedItem.value, 4, "selected item should be the default one");
+  is(Services.prefs.getIntPref("dom.ipc.processCount"), DEFAULT_PROCESS_COUNT, "default pref value should be default value");
+  is(contentProcessCount.selectedItem.value, DEFAULT_PROCESS_COUNT, "selected item should be the default one");
 
   allowHWAccel.click();
-  is(Services.prefs.getBoolPref("layers.acceleration.disabled"), true,
-    "pref value should be true after clicking on checkbox");
-  ok(!allowHWAccel.checked, "checkbox should not be checked");
+  allowHWAccelPref = Services.prefs.getBoolPref("layers.acceleration.disabled");
+  is(allowHWAccelPref, !DEFAULT_HW_ACCEL_PREF,
+    "pref value should be opposite of the default value after clicking on checkbox");
+  is(allowHWAccel.checked, !allowHWAccelPref, "checkbox should show the invert of the current value");
 
   contentProcessCount.value = 7;
   contentProcessCount.doCommand();
   is(Services.prefs.getIntPref("dom.ipc.processCount"), 7, "pref value should be 7");
   is(contentProcessCount.selectedItem.value, 7, "selected item should be 7");
 
   allowHWAccel.click();
-  is(Services.prefs.getBoolPref("layers.acceleration.disabled"), false,
-    "pref value should be false after clicking on checkbox");
-  ok(allowHWAccel.checked, "checkbox should not be checked");
+  allowHWAccelPref = Services.prefs.getBoolPref("layers.acceleration.disabled");
+  is(allowHWAccelPref, DEFAULT_HW_ACCEL_PREF,
+    "pref value should be the default value after clicking on checkbox");
+  is(allowHWAccel.checked, !allowHWAccelPref, "checkbox should show the invert of the current value");
 
-  contentProcessCount.value = 4;
+  contentProcessCount.value = DEFAULT_PROCESS_COUNT;
   contentProcessCount.doCommand();
-  is(Services.prefs.getIntPref("dom.ipc.processCount"), 4, "pref value should be default value");
-  is(contentProcessCount.selectedItem.value, 4, "selected item should be default one");
+  is(Services.prefs.getIntPref("dom.ipc.processCount"), DEFAULT_PROCESS_COUNT, "pref value should be default value");
+  is(contentProcessCount.selectedItem.value, DEFAULT_PROCESS_COUNT, "selected item should be default one");
 
   is(performanceSettings.hidden, false, "performance settings section should be still shown");
 
   Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", true);
   yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
 
 add_task(function*() {
--- a/browser/themes/shared/customizableui/customizeMode.inc.css
+++ b/browser/themes/shared/customizableui/customizeMode.inc.css
@@ -247,16 +247,17 @@
                     url("chrome://browser/skin/customizableui/background-noise-toolbar.png"),
                     linear-gradient(to bottom, #3e86ce, #3878ba);
   background-position: center top, left center, left top, left top, left top;
   background-repeat: no-repeat, no-repeat, repeat, repeat, no-repeat;
   background-size: auto 12px, 12px 100%, auto, auto, auto;
   background-attachment: scroll, scroll, fixed, fixed, scroll;
 }
 
+#widget-overflow-fixed-list > toolbarpaletteitem[place="panel"],
 toolbarpaletteitem[place="toolbar"] {
   transition: border-width 250ms ease-in-out;
 }
 
 toolbarpaletteitem[mousedown] {
   cursor: -moz-grabbing;
 }
 
@@ -274,16 +275,17 @@ toolbarpaletteitem[place="panel"] {
 #customization-palette[showing="true"] {
   opacity: 1;
 }
 
 toolbarpaletteitem toolbarbutton[disabled] {
   color: inherit !important;
 }
 
+#widget-overflow-fixed-list > toolbarpaletteitem[notransition][place="panel"],
 toolbarpaletteitem[notransition].panel-customization-placeholder,
 toolbarpaletteitem[notransition][place="toolbar"],
 toolbarpaletteitem[notransition][place="palette"],
 toolbarpaletteitem[notransition][place="panel"] {
   transition: none;
 }
 
 toolbarpaletteitem > toolbarbutton > .toolbarbutton-icon,
@@ -305,16 +307,20 @@ toolbarpaletteitem[mousedown] > toolbarb
 
 /* Override the toolkit styling for items being dragged over. */
 toolbarpaletteitem[place="toolbar"] {
   border-left-width: 0;
   border-right-width: 0;
   margin-right: 0;
   margin-left: 0;
 }
+#widget-overflow-fixed-list > toolbarpaletteitem[place="panel"] {
+  border-top: 0px solid transparent;
+  border-bottom: 0px solid transparent;
+}
 
 #customization-palette:not([hidden]) {
   margin-bottom: 25px;
 }
 
 toolbarpaletteitem[place="palette"]:-moz-focusring,
 toolbarpaletteitem[place="panel"]:-moz-focusring,
 toolbarpaletteitem[place="toolbar"]:-moz-focusring {
--- a/build/moz.configure/init.configure
+++ b/build/moz.configure/init.configure
@@ -712,21 +712,25 @@ def milestone(build_env, _):
         is_nightly = True
     elif 'a' not in milestone:
         is_release_or_beta = True
 
     return namespace(version=milestone,
                      is_nightly=is_nightly,
                      is_release_or_beta=is_release_or_beta)
 
+@depends(milestone)
+def is_nightly(milestone):
+    return milestone.is_nightly
+
 set_config('GRE_MILESTONE', delayed_getattr(milestone, 'version'))
-set_config('NIGHTLY_BUILD', delayed_getattr(milestone, 'is_nightly'))
-set_define('NIGHTLY_BUILD', delayed_getattr(milestone, 'is_nightly'))
+set_config('NIGHTLY_BUILD', is_nightly)
+set_define('NIGHTLY_BUILD', is_nightly)
 add_old_configure_assignment('NIGHTLY_BUILD',
-                             delayed_getattr(milestone, 'is_nightly'))
+                             is_nightly)
 set_config('RELEASE_OR_BETA', delayed_getattr(milestone, 'is_release_or_beta'))
 set_define('RELEASE_OR_BETA', delayed_getattr(milestone, 'is_release_or_beta'))
 add_old_configure_assignment('RELEASE_OR_BETA',
                              delayed_getattr(milestone, 'is_release_or_beta'))
 
 # The app update channel is 'default' when not supplied. The value is used in
 # the application's confvars.sh (and is made available to a project specific
 # moz.configure).
--- a/devtools/client/jsonview/converter-child.js
+++ b/devtools/client/jsonview/converter-child.js
@@ -91,19 +91,38 @@ Converter.prototype = {
     this.data = "";
     this.uri = request.QueryInterface(Ci.nsIChannel).URI.spec;
 
     // Sets the charset if it is available. (For documents loaded from the
     // filesystem, this is not set.)
     this.charset =
       request.QueryInterface(Ci.nsIChannel).contentCharset || "UTF-8";
 
-    // Let "save as" save the original JSON, not the viewer
+    // Let "save as" save the original JSON, not the viewer.
+    // To save with the proper extension we need the original content type,
+    // which has been replaced by application/vnd.mozilla.json.view
+    let originalType;
+    if (request instanceof Ci.nsIHttpChannel) {
+      try {
+        originalType = request.getResponseHeader("Content-Type");
+      } catch (err) {
+        // Handled below
+      }
+    } else {
+      let match = this.uri.match(/^data:(.*?)[,;]/);
+      if (match) {
+        originalType = match[1];
+      }
+    }
+    const JSON_TYPES = ["application/json", "application/manifest+json"];
+    if (!JSON_TYPES.includes(originalType)) {
+      originalType = JSON_TYPES[0];
+    }
     request.QueryInterface(Ci.nsIWritablePropertyBag);
-    request.setProperty("contentType", "application/json");
+    request.setProperty("contentType", originalType);
 
     this.channel = request;
     this.channel.contentType = "text/html";
     this.channel.contentCharset = "UTF-8";
     // Because content might still have a reference to this window,
     // force setting it to a null principal to avoid it being same-
     // origin with (other) content.
     this.channel.loadInfo.resetPrincipalToInheritToNullPrincipal();
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -5691,16 +5691,21 @@ nsDocShell::LoadPage(nsISupports* aPageD
     newSpec.Append(spec);
 
     rv = NS_NewURI(getter_AddRefs(newUri), newSpec);
     if (NS_FAILED(rv)) {
       return rv;
     }
     shEntry->SetURI(newUri);
     shEntry->SetOriginalURI(nullptr);
+    // shEntry's current triggering principal is whoever loaded that page initially.
+    // But now we're doing another load of the page, via an API that is only exposed
+    // to system code.  The triggering principal for this load should be the system
+    // principal.
+    shEntry->SetTriggeringPrincipal(nsContentUtils::GetSystemPrincipal());
   }
 
   rv = LoadHistoryEntry(shEntry, LOAD_HISTORY);
   return rv;
 }
 
 NS_IMETHODIMP
 nsDocShell::GetCurrentDescriptor(nsISupports** aPageDescriptor)
--- a/docshell/test/browser/browser.ini
+++ b/docshell/test/browser/browser.ini
@@ -41,16 +41,17 @@ support-files =
   test-form_sjis.html
   timelineMarkers-04.html
   browser_timelineMarkers-frame-02.js
   browser_timelineMarkers-frame-03.js
   browser_timelineMarkers-frame-04.js
   browser_timelineMarkers-frame-05.js
   head.js
   frame-head.js
+  file_click_link_within_view_source.html
 
 [browser_bug1206879.js]
 [browser_bug1309900_crossProcessHistoryNavigation.js]
 [browser_bug1347823.js]
 [browser_bug134911.js]
 [browser_bug234628-1.js]
 [browser_bug234628-10.js]
 [browser_bug234628-11.js]
@@ -95,8 +96,9 @@ skip-if = true # Bug 1220415
 [browser_ua_emulation.js]
 [browser_grouped_shistory_dead_navigate.js]
 skip-if = !e10s
 [browser_grouped_shistory_crossproc.js]
 skip-if = !e10s
 [browser_grouped_shistory_bfcache_cleaning.js]
 skip-if = !e10s
 [browser_history_triggeringprincipal_viewsource.js]
+[browser_click_link_within_view_source.js]
new file mode 100644
--- /dev/null
+++ b/docshell/test/browser/browser_click_link_within_view_source.js
@@ -0,0 +1,60 @@
+"use strict";
+
+/**
+ * Test for Bug 1359204
+ *
+ * Loading a local file, then view-source on that file. Make sure that
+ * clicking a link within that view-source page is not blocked by security checks.
+ */
+
+add_task(function* test_click_link_within_view_source() {
+  let TEST_FILE = "file_click_link_within_view_source.html";
+  let TEST_FILE_URI = getChromeDir(getResolvedURI(gTestPath));
+  TEST_FILE_URI.append(TEST_FILE);
+  TEST_FILE_URI = Services.io.newFileURI(TEST_FILE_URI).spec;
+
+  let DUMMY_FILE = "dummy_page.html";
+  let DUMMY_FILE_URI = getChromeDir(getResolvedURI(gTestPath));
+  DUMMY_FILE_URI.append(DUMMY_FILE);
+  DUMMY_FILE_URI = Services.io.newFileURI(DUMMY_FILE_URI).spec;
+
+  yield BrowserTestUtils.withNewTab(TEST_FILE_URI, function*(aBrowser) {
+    let tabSpec = gBrowser.selectedBrowser.currentURI.spec;
+    info("loading: " + tabSpec);
+    ok(tabSpec.startsWith("file://") && tabSpec.endsWith(TEST_FILE),
+       "sanity check to make sure html loaded");
+
+    info("click view-source of html");
+    let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser);
+    document.getElementById("View:PageSource").doCommand();
+
+    let tab = yield tabPromise;
+    tabSpec = gBrowser.selectedBrowser.currentURI.spec;
+    info("loading: " + tabSpec);
+    ok(tabSpec.startsWith("view-source:file://") && tabSpec.endsWith(TEST_FILE),
+       "loading view-source of html succeeded");
+
+    info("click testlink within view-source page");
+    let loadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, url => url.endsWith("dummy_page.html"));
+    yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function*() {
+      if (content.document.readyState != "complete") {
+        yield ContentTaskUtils.waitForEvent(content.document, "readystatechange", false, () =>
+          content.document.readyState == "complete");
+      }
+      // document.getElementById() does not work on a view-source page, hence we use document.links
+      let linksOnPage = content.document.links;
+      is (linksOnPage.length, 1, "sanity check: make sure only one link is present on page");
+      let myLink = content.document.links[0];
+      myLink.click();
+    });
+
+    yield loadPromise;
+
+    tabSpec = gBrowser.selectedBrowser.currentURI.spec;
+    info("loading: " + tabSpec);
+    ok(tabSpec.startsWith("view-source:file://") && tabSpec.endsWith(DUMMY_FILE),
+       "loading view-source of html succeeded");
+
+    yield BrowserTestUtils.removeTab(tab);
+  });
+});
new file mode 100644
--- /dev/null
+++ b/docshell/test/browser/file_click_link_within_view_source.html
@@ -0,0 +1,6 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+  <body>
+  <a id="testlink" href="dummy_page.html">clickme</a>
+  </body>
+</html>
--- a/dom/base/AutocompleteFieldList.h
+++ b/dom/base/AutocompleteFieldList.h
@@ -33,28 +33,75 @@
 #define DEFINED_AUTOCOMPLETE_FIELD_CONTACT_HINT
 #endif
 
 #ifndef AUTOCOMPLETE_CATEGORY
 #define AUTOCOMPLETE_CATEGORY(name_, value_)
 #define DEFINED_AUTOCOMPLETE_CATEGORY
 #endif
 
+#ifndef AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME
+#define AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(name_, value_)
+#define DEFINED_AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME
+#endif
+
+#ifndef AUTOCOMPLETE_UNSUPPORTED_FIELD_CONTACT_HINT
+#define AUTOCOMPLETE_UNSUPPORTED_FIELD_CONTACT_HINT(name_, value_)
+#define DEFINED_AUTOCOMPLETE_UNSUPPORTED_FIELD_CONTACT_HINT
+#endif
+
+//-----------------------------------------------------
+// Unsupported list
+
+// Unsupported field names
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(HONORIFIX_PREFIX, "honorifix-prefix")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(HONORIFIX_SUFFIX, "honorifix-suffix")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(NICKNAME, "nickname")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(ORGANIZATION_TITLE, "organization-title")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(USERNAME, "username")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(NEW_PASSWORD, "new-password")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(CURRENT_PASSWORD, "current-password")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(ADDRESS_LEVEL4, "address-level4")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(ADDRESS_LEVEL3, "address-level3")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(CC_GIVEN_NAME, "cc-given-name")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(CC_ADDITIONAL_NAME, "cc-additional-name")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(CC_FAMILY_NAME, "cc-family-name")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(CC_CSC, "cc-csc")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(TRANSACTION_CURRENCY, "transaction-currency")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(TRANSACTION_AMOUNT, "transaction-amount")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(LANGUAGE, "language")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(BDAY, "bday")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(BDAY_DAY, "bday-day")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(BDAY_MONTH, "bday-month")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(BDAY_YEAR, "bday-year")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(SEX, "sex")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(URL, "url")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(PHOTO, "photo")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(TEL_EXTENSION, "tel-extension")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(IMPP, "impp")
+
+// Unsupported contact types
+AUTOCOMPLETE_UNSUPPORTED_FIELD_CONTACT_HINT(FAX, "fax")
+AUTOCOMPLETE_UNSUPPORTED_FIELD_CONTACT_HINT(PAGER, "pager")
+
+//-----------------------------------------------------
+// Valid list
+
 AUTOCOMPLETE_FIELD_NAME(OFF, "off")
 AUTOCOMPLETE_FIELD_NAME(ON, "on")
 
 // Name types
 AUTOCOMPLETE_FIELD_NAME(NAME, "name")
-// AUTOCOMPLETE_FIELD_NAME(HONORIFIX_PREFIX, "honorifix-prefix")
+AUTOCOMPLETE_FIELD_NAME(HONORIFIX_PREFIX, "honorifix-prefix")
 AUTOCOMPLETE_FIELD_NAME(GIVEN_NAME, "given-name")
 AUTOCOMPLETE_FIELD_NAME(ADDITIONAL_NAME, "additional-name")
 AUTOCOMPLETE_FIELD_NAME(FAMILY_NAME, "family-name")
-// AUTOCOMPLETE_FIELD_NAME(HONORIFIX_SUFFIX, "honorifix-suffix")
-// AUTOCOMPLETE_FIELD_NAME(NICKNAME, "nickname")
-// AUTOCOMPLETE_FIELD_NAME(ORGANIZATION_TITLE, "organization-title")
+AUTOCOMPLETE_FIELD_NAME(HONORIFIX_SUFFIX, "honorifix-suffix")
+AUTOCOMPLETE_FIELD_NAME(NICKNAME, "nickname")
+AUTOCOMPLETE_FIELD_NAME(ORGANIZATION_TITLE, "organization-title")
 
 // Login types
 AUTOCOMPLETE_FIELD_NAME(USERNAME, "username")
 AUTOCOMPLETE_FIELD_NAME(NEW_PASSWORD, "new-password")
 AUTOCOMPLETE_FIELD_NAME(CURRENT_PASSWORD, "current-password")
 
 // Address types
 AUTOCOMPLETE_FIELD_NAME(ORGANIZATION, "organization")
@@ -66,60 +113,75 @@ AUTOCOMPLETE_FIELD_NAME(ADDRESS_LEVEL4, 
 AUTOCOMPLETE_FIELD_NAME(ADDRESS_LEVEL3, "address-level3")
 AUTOCOMPLETE_FIELD_NAME(ADDRESS_LEVEL2, "address-level2")
 AUTOCOMPLETE_FIELD_NAME(ADDRESS_LEVEL1, "address-level1")
 AUTOCOMPLETE_FIELD_NAME(COUNTRY, "country")
 AUTOCOMPLETE_FIELD_NAME(COUNTRY_NAME, "country-name")
 AUTOCOMPLETE_FIELD_NAME(POSTAL_CODE, "postal-code")
 
 // Credit Card types
-// AUTOCOMPLETE_FIELD_NAME(CC_NAME, "cc-name")
-// AUTOCOMPLETE_FIELD_NAME(CC_GIVEN_NAME, "cc-given-name")
-// AUTOCOMPLETE_FIELD_NAME(CC_ADDITIONAL_NAME, "cc-additional-name")
-// AUTOCOMPLETE_FIELD_NAME(CC_FAMILY_NAME, "cc-family-name")
-// AUTOCOMPLETE_FIELD_NAME(CC_NUMBER, "cc-number")
-// AUTOCOMPLETE_FIELD_NAME(CC_EXP, "cc-exp")
-// AUTOCOMPLETE_FIELD_NAME(CC_EXP_MONTH, "cc-exp-month")
-// AUTOCOMPLETE_FIELD_NAME(CC_EXP_YEAR, "cc-exp-year")
-// AUTOCOMPLETE_FIELD_NAME(CC_CSC, "cc-csc")
-// AUTOCOMPLETE_FIELD_NAME(CC_TYPE, "cc-type")
+AUTOCOMPLETE_FIELD_NAME(CC_NAME, "cc-name")
+AUTOCOMPLETE_FIELD_NAME(CC_GIVEN_NAME, "cc-given-name")
+AUTOCOMPLETE_FIELD_NAME(CC_ADDITIONAL_NAME, "cc-additional-name")
+AUTOCOMPLETE_FIELD_NAME(CC_FAMILY_NAME, "cc-family-name")
+AUTOCOMPLETE_FIELD_NAME(CC_NUMBER, "cc-number")
+AUTOCOMPLETE_FIELD_NAME(CC_EXP, "cc-exp")
+AUTOCOMPLETE_FIELD_NAME(CC_EXP_MONTH, "cc-exp-month")
+AUTOCOMPLETE_FIELD_NAME(CC_EXP_YEAR, "cc-exp-year")
+AUTOCOMPLETE_FIELD_NAME(CC_CSC, "cc-csc")
+AUTOCOMPLETE_FIELD_NAME(CC_TYPE, "cc-type")
+
+// Transaction types
+AUTOCOMPLETE_FIELD_NAME(TRANSACTION_CURRENCY, "transaction-currency")
+AUTOCOMPLETE_FIELD_NAME(TRANSACTION_AMOUNT, "transaction-amount")
 
 // Additional field types
-// AUTOCOMPLETE_FIELD_NAME(LANGUAGE, "language")
-// AUTOCOMPLETE_FIELD_NAME(BDAY, "bday")
-// AUTOCOMPLETE_FIELD_NAME(BDAY_DAY, "bday-day")
-// AUTOCOMPLETE_FIELD_NAME(BDAY_MONTH, "bday-month")
-// AUTOCOMPLETE_FIELD_NAME(BDAY_YEAR, "bday-year")
-// AUTOCOMPLETE_FIELD_NAME(SEX, "sex")
-// AUTOCOMPLETE_FIELD_NAME(URL, "url")
-// AUTOCOMPLETE_FIELD_NAME(PHOTO, "photo")
+AUTOCOMPLETE_FIELD_NAME(LANGUAGE, "language")
+AUTOCOMPLETE_FIELD_NAME(BDAY, "bday")
+AUTOCOMPLETE_FIELD_NAME(BDAY_DAY, "bday-day")
+AUTOCOMPLETE_FIELD_NAME(BDAY_MONTH, "bday-month")
+AUTOCOMPLETE_FIELD_NAME(BDAY_YEAR, "bday-year")
+AUTOCOMPLETE_FIELD_NAME(SEX, "sex")
+AUTOCOMPLETE_FIELD_NAME(URL, "url")
+AUTOCOMPLETE_FIELD_NAME(PHOTO, "photo")
 
 // Contact category types
 AUTOCOMPLETE_CONTACT_FIELD_NAME(TEL, "tel")
 AUTOCOMPLETE_CONTACT_FIELD_NAME(TEL_COUNTRY_CODE, "tel-country-code")
 AUTOCOMPLETE_CONTACT_FIELD_NAME(TEL_NATIONAL, "tel-national")
 AUTOCOMPLETE_CONTACT_FIELD_NAME(TEL_AREA_CODE, "tel-area-code")
 AUTOCOMPLETE_CONTACT_FIELD_NAME(TEL_LOCAL, "tel-local")
 AUTOCOMPLETE_CONTACT_FIELD_NAME(TEL_LOCAL_PREFIX, "tel-local-prefix")
 AUTOCOMPLETE_CONTACT_FIELD_NAME(TEL_LOCAL_SUFFIX, "tel-local-suffix")
 AUTOCOMPLETE_CONTACT_FIELD_NAME(TEL_EXTENSION, "tel-extension")
 AUTOCOMPLETE_CONTACT_FIELD_NAME(EMAIL, "email")
-// AUTOCOMPLETE_CONTACT_FIELD_NAME(IMPP, "impp")
+AUTOCOMPLETE_CONTACT_FIELD_NAME(IMPP, "impp")
 
 AUTOCOMPLETE_FIELD_HINT(SHIPPING, "shipping")
 AUTOCOMPLETE_FIELD_HINT(BILLING, "billing")
 
 AUTOCOMPLETE_FIELD_CONTACT_HINT(HOME, "home")
 AUTOCOMPLETE_FIELD_CONTACT_HINT(WORK, "work")
 AUTOCOMPLETE_FIELD_CONTACT_HINT(MOBILE, "mobile")
 AUTOCOMPLETE_FIELD_CONTACT_HINT(FAX, "fax")
-// AUTOCOMPLETE_FIELD_CONTACT_HINT(PAGER, "pager")
+AUTOCOMPLETE_FIELD_CONTACT_HINT(PAGER, "pager")
 
 AUTOCOMPLETE_CATEGORY(NORMAL, "normal")
 AUTOCOMPLETE_CATEGORY(CONTACT, "contact")
+//-----------------------------------------------------
+
+#ifdef DEFINED_AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME
+#undef AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME
+#undef DEFINED_AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME
+#endif
+
+#ifdef DEFINED_AUTOCOMPLETE_UNSUPPORTED_FIELD_CONTACT_HINT
+#undef AUTOCOMPLETE_UNSUPPORTED_FIELD_CONTACT_HINT
+#undef DEFINED_AUTOCOMPLETE_UNSUPPORTED_FIELD_CONTACT_HINT
+#endif
 
 #ifdef DEFINED_AUTOCOMPLETE_FIELD_NAME
 #undef AUTOCOMPLETE_FIELD_NAME
 #undef DEFINED_AUTOCOMPLETE_FIELD_NAME
 #endif
 
 #ifdef DEFINED_AUTOCOMPLETE_CONTACT_FIELD_NAME
 #undef AUTOCOMPLETE_CONTACT_FIELD_NAME
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -320,16 +320,32 @@ bool nsContentUtils::sFragmentParsingAct
 bool nsContentUtils::sDOMWindowDumpEnabled;
 #endif
 
 bool nsContentUtils::sDoNotTrackEnabled = false;
 
 mozilla::LazyLogModule nsContentUtils::sDOMDumpLog("Dump");
 
 // Subset of http://www.whatwg.org/specs/web-apps/current-work/#autofill-field-name
+enum AutocompleteUnsupportedFieldName : uint8_t
+{
+  #define AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(name_, value_) \
+    eAutocompleteUnsupportedFieldName_##name_,
+  #include "AutocompleteFieldList.h"
+  #undef AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME
+};
+
+enum AutocompleteUnsupportFieldContactHint : uint8_t
+{
+  #define AUTOCOMPLETE_UNSUPPORTED_FIELD_CONTACT_HINT(name_, value_) \
+    eAutocompleteUnsupportedFieldContactHint_##name_,
+  #include "AutocompleteFieldList.h"
+  #undef AUTOCOMPLETE_UNSUPPORTED_FIELD_CONTACT_HINT
+};
+
 enum AutocompleteFieldName : uint8_t
 {
   #define AUTOCOMPLETE_FIELD_NAME(name_, value_) \
     eAutocompleteFieldName_##name_,
   #define AUTOCOMPLETE_CONTACT_FIELD_NAME(name_, value_) \
     AUTOCOMPLETE_FIELD_NAME(name_, value_)
   #include "AutocompleteFieldList.h"
   #undef AUTOCOMPLETE_FIELD_NAME
@@ -354,16 +370,33 @@ enum AutocompleteFieldContactHint : uint
 
 enum AutocompleteCategory
 {
   #define AUTOCOMPLETE_CATEGORY(name_, value_) eAutocompleteCategory_##name_,
   #include "AutocompleteFieldList.h"
   #undef AUTOCOMPLETE_CATEGORY
 };
 
+static const nsAttrValue::EnumTable kAutocompleteUnsupportedFieldNameTable[] = {
+  #define AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME(name_, value_) \
+    { value_, eAutocompleteUnsupportedFieldName_##name_ },
+  #include "AutocompleteFieldList.h"
+  #undef AUTOCOMPLETE_UNSUPPORTED_FIELD_NAME
+  { nullptr, 0 }
+};
+
+static const nsAttrValue::EnumTable kAutocompleteUnsupportedContactFieldHintTable[] = {
+  #define AUTOCOMPLETE_UNSUPPORTED_FIELD_CONTACT_HINT(name_, value_) \
+    { value_, eAutocompleteUnsupportedFieldContactHint_##name_ },
+  #include "AutocompleteFieldList.h"
+  #undef AUTOCOMPLETE_UNSUPPORTED_FIELD_CONTACT_HINT
+  { nullptr, 0 }
+};
+
+
 static const nsAttrValue::EnumTable kAutocompleteFieldNameTable[] = {
   #define AUTOCOMPLETE_FIELD_NAME(name_, value_) \
     { value_, eAutocompleteFieldName_##name_ },
   #include "AutocompleteFieldList.h"
   #undef AUTOCOMPLETE_FIELD_NAME
   { nullptr, 0 }
 };
 
@@ -995,78 +1028,92 @@ nsContentUtils::SerializeAutocompleteAtt
   }
 
   return state;
 }
 
 nsContentUtils::AutocompleteAttrState
 nsContentUtils::SerializeAutocompleteAttribute(const nsAttrValue* aAttr,
                                                mozilla::dom::AutocompleteInfo& aInfo,
-                                               AutocompleteAttrState aCachedState)
+                                               AutocompleteAttrState aCachedState,
+                                               bool aGrantAllValidValue)
 {
   if (!aAttr ||
       aCachedState == nsContentUtils::eAutocompleteAttrState_Invalid) {
     return aCachedState;
   }
 
-  return InternalSerializeAutocompleteAttribute(aAttr, aInfo);
+  return InternalSerializeAutocompleteAttribute(aAttr, aInfo, aGrantAllValidValue);
 }
 
 /**
  * Helper to validate the @autocomplete tokens.
  *
  * @return {AutocompleteAttrState} The state of the attribute (invalid/valid).
  */
 nsContentUtils::AutocompleteAttrState
 nsContentUtils::InternalSerializeAutocompleteAttribute(const nsAttrValue* aAttrVal,
-                                                       mozilla::dom::AutocompleteInfo& aInfo)
+                                                       mozilla::dom::AutocompleteInfo& aInfo,
+                                                       bool aGrantAllValidValue)
 {
   // No sandbox attribute so we are done
   if (!aAttrVal) {
     return eAutocompleteAttrState_Invalid;
   }
 
   uint32_t numTokens = aAttrVal->GetAtomCount();
   if (!numTokens) {
     return eAutocompleteAttrState_Invalid;
   }
 
   uint32_t index = numTokens - 1;
   nsString tokenString = nsDependentAtomString(aAttrVal->AtomAt(index));
   AutocompleteCategory category;
   nsAttrValue enumValue;
 
+  bool unsupported = false;
+  if (!aGrantAllValidValue) {
+    unsupported = enumValue.ParseEnumValue(tokenString,
+                                           kAutocompleteUnsupportedFieldNameTable,
+                                           false);
+    if (unsupported) {
+      return eAutocompleteAttrState_Invalid;
+    }
+  }
+
   nsAutoString str;
   bool result = enumValue.ParseEnumValue(tokenString, kAutocompleteFieldNameTable, false);
   if (result) {
     // Off/Automatic/Normal categories.
     if (enumValue.Equals(NS_LITERAL_STRING("off"), eIgnoreCase) ||
         enumValue.Equals(NS_LITERAL_STRING("on"), eIgnoreCase)) {
       if (numTokens > 1) {
         return eAutocompleteAttrState_Invalid;
       }
       enumValue.ToString(str);
       ASCIIToLower(str);
       aInfo.mFieldName.Assign(str);
       return eAutocompleteAttrState_Valid;
     }
 
-    // Only allow on/off if experimental @autocomplete values aren't enabled.
-    if (!sIsExperimentalAutocompleteEnabled) {
+    // Only allow on/off if experimental @autocomplete values aren't enabled
+    // and it doesn't grant all valid values.
+    if (!sIsExperimentalAutocompleteEnabled && !aGrantAllValidValue) {
       return eAutocompleteAttrState_Invalid;
     }
 
     // Normal category
     if (numTokens > 3) {
       return eAutocompleteAttrState_Invalid;
     }
     category = eAutocompleteCategory_NORMAL;
   } else { // Check if the last token is of the contact category instead.
-    // Only allow on/off if experimental @autocomplete values aren't enabled.
-    if (!sIsExperimentalAutocompleteEnabled) {
+    // Only allow on/off if experimental @autocomplete values aren't enabled
+    // and it doesn't grant all valid values.
+    if (!sIsExperimentalAutocompleteEnabled && !aGrantAllValidValue) {
       return eAutocompleteAttrState_Invalid;
     }
 
     result = enumValue.ParseEnumValue(tokenString, kAutocompleteContactFieldNameTable, false);
     if (!result || numTokens > 4) {
       return eAutocompleteAttrState_Invalid;
     }
 
@@ -1081,16 +1128,26 @@ nsContentUtils::InternalSerializeAutocom
   if (numTokens == 1) {
     return eAutocompleteAttrState_Valid;
   }
 
   --index;
   tokenString = nsDependentAtomString(aAttrVal->AtomAt(index));
 
   if (category == eAutocompleteCategory_CONTACT) {
+    if (!aGrantAllValidValue) {
+      unsupported = enumValue.ParseEnumValue(tokenString,
+                                             kAutocompleteUnsupportedContactFieldHintTable,
+                                             false);
+      if (unsupported) {
+        return eAutocompleteAttrState_Invalid;
+      }
+    }
+
+
     nsAttrValue contactFieldHint;
     result = contactFieldHint.ParseEnumValue(tokenString, kAutocompleteContactFieldHintTable, false);
     if (result) {
       nsAutoString contactFieldHintString;
       contactFieldHint.ToString(contactFieldHintString);
       ASCIIToLower(contactFieldHintString);
       aInfo.mContactType.Assign(contactFieldHintString);
       if (index == 0) {
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -2407,17 +2407,18 @@ public:
    * autocomplete attribute.
    *
    * @return whether aAttr was valid and can be cached.
    */
   static AutocompleteAttrState
   SerializeAutocompleteAttribute(const nsAttrValue* aAttr,
                                  mozilla::dom::AutocompleteInfo& aInfo,
                                  AutocompleteAttrState aCachedState =
-                                   eAutocompleteAttrState_Unknown);
+                                   eAutocompleteAttrState_Unknown,
+                                 bool aGrantAllValidValue = false);
 
   /**
    * This will parse aSource, to extract the value of the pseudo attribute
    * with the name specified in aName. See
    * http://www.w3.org/TR/xml-stylesheet/#NT-StyleSheetPI for the specification
    * which is used to parse aSource.
    *
    * @param aSource the string to parse
@@ -2953,17 +2954,18 @@ private:
                               int32_t aNamespaceID,
                               nsIAtom* aAtom, void* aData);
   static void DestroyClassNameArray(void* aData);
   static void* AllocClassMatchingInfo(nsINode* aRootNode,
                                       const nsString* aClasses);
 
   // Fills in aInfo with the tokens from the supplied autocomplete attribute.
   static AutocompleteAttrState InternalSerializeAutocompleteAttribute(const nsAttrValue* aAttrVal,
-                                                                      mozilla::dom::AutocompleteInfo& aInfo);
+                                                                      mozilla::dom::AutocompleteInfo& aInfo,
+                                                                      bool aGrantAllValidValue = false);
 
   static bool CallOnAllRemoteChildren(nsIMessageBroadcaster* aManager,
                                       CallOnRemoteChildFunction aCallback,
                                       void* aArg);
 
   /**
    * Gets the current cookie lifetime policy and cookie behavior for a given
    * principal by checking with preferences and the permission manager.
--- a/dom/cache/CacheOpChild.cpp
+++ b/dom/cache/CacheOpChild.cpp
@@ -70,17 +70,22 @@ CacheOpChild::CacheOpChild(CacheWorkerHo
   , mParent(aParent)
   , mPromise(aPromise)
 {
   MOZ_DIAGNOSTIC_ASSERT(mGlobal);
   MOZ_DIAGNOSTIC_ASSERT(mParent);
   MOZ_DIAGNOSTIC_ASSERT(mPromise);
 
   MOZ_ASSERT_IF(!NS_IsMainThread(), aWorkerHolder);
-  SetWorkerHolder(aWorkerHolder);
+
+  RefPtr<CacheWorkerHolder> workerHolder =
+    CacheWorkerHolder::PreferBehavior(aWorkerHolder,
+                                      CacheWorkerHolder::PreventIdleShutdownStart);
+
+  SetWorkerHolder(workerHolder);
 }
 
 CacheOpChild::~CacheOpChild()
 {
   NS_ASSERT_OWNINGTHREAD(CacheOpChild);
   MOZ_DIAGNOSTIC_ASSERT(!mPromise);
 }
 
@@ -161,17 +166,21 @@ CacheOpChild::Recv__delete__(const Error
       MOZ_DIAGNOSTIC_ASSERT(actor);
       if (!actor) {
         ErrorResult status;
         status.ThrowTypeError<MSG_CACHE_OPEN_FAILED>();
         mPromise->MaybeReject(status);
         break;
       }
 
-      actor->SetWorkerHolder(GetWorkerHolder());
+      RefPtr<CacheWorkerHolder> workerHolder =
+        CacheWorkerHolder::PreferBehavior(GetWorkerHolder(),
+                                          CacheWorkerHolder::AllowIdleShutdownStart);
+
+      actor->SetWorkerHolder(workerHolder);
       RefPtr<Cache> cache = new Cache(mGlobal, actor);
       mPromise->MaybeResolve(cache);
       break;
     }
     case CacheOpResult::TStorageDeleteResult:
     {
       mPromise->MaybeResolve(aResult.get_StorageDeleteResult().success());
       break;
--- a/dom/cache/CacheStorage.cpp
+++ b/dom/cache/CacheStorage.cpp
@@ -190,17 +190,18 @@ CacheStorage::CreateOnWorker(Namespace a
 
   if (aWorkerPrivate->GetOriginAttributes().mPrivateBrowsingId > 0) {
     NS_WARNING("CacheStorage not supported during private browsing.");
     RefPtr<CacheStorage> ref = new CacheStorage(NS_ERROR_DOM_SECURITY_ERR);
     return ref.forget();
   }
 
   RefPtr<CacheWorkerHolder> workerHolder =
-    CacheWorkerHolder::Create(aWorkerPrivate);
+    CacheWorkerHolder::Create(aWorkerPrivate,
+                              CacheWorkerHolder::AllowIdleShutdownStart);
   if (!workerHolder) {
     NS_WARNING("Worker thread is shutting down.");
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   const PrincipalInfo& principalInfo = aWorkerPrivate->GetPrincipalInfo();
 
--- a/dom/cache/CacheWorkerHolder.cpp
+++ b/dom/cache/CacheWorkerHolder.cpp
@@ -14,28 +14,50 @@ namespace dom {
 namespace cache {
 
 using mozilla::dom::workers::Terminating;
 using mozilla::dom::workers::Status;
 using mozilla::dom::workers::WorkerPrivate;
 
 // static
 already_AddRefed<CacheWorkerHolder>
-CacheWorkerHolder::Create(WorkerPrivate* aWorkerPrivate)
+CacheWorkerHolder::Create(WorkerPrivate* aWorkerPrivate, Behavior aBehavior)
 {
   MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate);
 
-  RefPtr<CacheWorkerHolder> workerHolder = new CacheWorkerHolder();
+  RefPtr<CacheWorkerHolder> workerHolder = new CacheWorkerHolder(aBehavior);
   if (NS_WARN_IF(!workerHolder->HoldWorker(aWorkerPrivate, Terminating))) {
     return nullptr;
   }
 
   return workerHolder.forget();
 }
 
+// static
+already_AddRefed<CacheWorkerHolder>
+CacheWorkerHolder::PreferBehavior(CacheWorkerHolder* aCurrentHolder,
+                                  Behavior aBehavior)
+{
+  if (!aCurrentHolder) {
+    return nullptr;
+  }
+
+  RefPtr<CacheWorkerHolder> orig = aCurrentHolder;
+  if (orig->GetBehavior() == aBehavior) {
+    return orig.forget();
+  }
+
+  RefPtr<CacheWorkerHolder> replace = Create(orig->mWorkerPrivate, aBehavior);
+  if (!replace) {
+    return orig.forget();
+  }
+
+  return replace.forget();
+}
+
 void
 CacheWorkerHolder::AddActor(ActorChild* aActor)
 {
   NS_ASSERT_OWNINGTHREAD(CacheWorkerHolder);
   MOZ_DIAGNOSTIC_ASSERT(aActor);
   MOZ_ASSERT(!mActorList.Contains(aActor));
 
   mActorList.AppendElement(aActor);
@@ -88,18 +110,19 @@ CacheWorkerHolder::Notify(Status aStatus
   for (uint32_t i = 0; i < mActorList.Length(); ++i) {
     MOZ_DIAGNOSTIC_ASSERT(mActorList[i]);
     mActorList[i]->StartDestroy();
   }
 
   return true;
 }
 
-CacheWorkerHolder::CacheWorkerHolder()
-  : mNotified(false)
+CacheWorkerHolder::CacheWorkerHolder(Behavior aBehavior)
+  : WorkerHolder(aBehavior)
+  , mNotified(false)
 {
 }
 
 CacheWorkerHolder::~CacheWorkerHolder()
 {
   NS_ASSERT_OWNINGTHREAD(CacheWorkerHolder);
   MOZ_DIAGNOSTIC_ASSERT(mActorList.IsEmpty());
 }
--- a/dom/cache/CacheWorkerHolder.h
+++ b/dom/cache/CacheWorkerHolder.h
@@ -21,28 +21,32 @@ namespace dom {
 namespace cache {
 
 class ActorChild;
 
 class CacheWorkerHolder final : public workers::WorkerHolder
 {
 public:
   static already_AddRefed<CacheWorkerHolder>
-  Create(workers::WorkerPrivate* aWorkerPrivate);
+  Create(workers::WorkerPrivate* aWorkerPrivate,
+         Behavior aBehavior);
+
+  static already_AddRefed<CacheWorkerHolder>
+  PreferBehavior(CacheWorkerHolder* aCurrentHolder, Behavior aBehavior);
 
   void AddActor(ActorChild* aActor);
   void RemoveActor(ActorChild* aActor);
 
   bool Notified() const;
 
   // WorkerHolder methods
   virtual bool Notify(workers::Status aStatus) override;
 
 private:
-  CacheWorkerHolder();
+  explicit CacheWorkerHolder(Behavior aBehavior);
   ~CacheWorkerHolder();
 
   nsTArray<ActorChild*> mActorList;
   bool mNotified;
 
 public:
   NS_INLINE_DECL_REFCOUNTING(mozilla::dom::cache::CacheWorkerHolder)
 };
new file mode 100644
--- /dev/null
+++ b/dom/cache/test/mochitest/idle_worker.js
@@ -0,0 +1,9 @@
+// Touch CacheStorage, but then idle and do nothing.
+const name = 'idle_worker_cache';
+var cache;
+self.caches.open(name).then(c => {
+  cache = c;
+  return self.caches.delete(name);
+}).then(_ => {
+  postMessage('LOADED');
+});
--- a/dom/cache/test/mochitest/mochitest.ini
+++ b/dom/cache/test/mochitest/mochitest.ini
@@ -19,16 +19,17 @@ support-files =
   test_cache_put.js
   test_cache_requestCache.js
   test_cache_delete.js
   test_cache_put_reorder.js
   test_cache_redirect.js
   test_cache_https.js
   large_url_list.js
   empty.html
+  idle_worker.js
 
 [test_cache.html]
 [test_cache_add.html]
 [test_cache_match_request.html]
 [test_cache_matchAll_request.html]
 [test_cache_overwrite.html]
 [test_cache_match_vary.html]
 [test_caches.html]
@@ -41,8 +42,10 @@ support-files =
 [test_cache_redirect.html]
 [test_cache_restart.html]
 [test_cache_shrink.html]
 [test_cache_orphaned_cache.html]
 [test_cache_orphaned_body.html]
 scheme=https
 [test_cache_untrusted.html]
 [test_chrome_constructor.html]
+[test_cache_worker_gc.html]
+scheme=https
new file mode 100644
--- /dev/null
+++ b/dom/cache/test/mochitest/test_cache_worker_gc.html
@@ -0,0 +1,46 @@
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test CacheStorage does not prevent worker GC</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <script type="text/javascript" src="driver.js"></script>
+</head>
+<body>
+<iframe id="frame"></iframe>
+<script class="testbody" type="text/javascript">
+function startWorker() {
+  return new Promise(resolve => {
+    var w = new Worker('idle_worker.js');
+    w.addEventListener('message', function onMessage(evt) {
+      if (evt.data === 'LOADED') {
+        w.removeEventListener('message', onMessage);
+        resolve(w);
+      }
+    });
+  });
+}
+
+function gc() {
+  return new Promise(resolve => {
+    SpecialPowers.exactGC(resolve);
+  });
+}
+
+var weakWorker;
+
+SimpleTest.waitForExplicitFinish();
+startWorker().then(w => {
+  weakWorker = SpecialPowers.Cu.getWeakReference(w);
+  ok(weakWorker, 'worker supports weak reference');
+}).then(_ => {
+  return gc();
+}).then(_ => {
+  ok(!weakWorker.get(), 'worker weak reference should be garbage collected');
+  SimpleTest.finish();
+});
+</script>
+</body>
+</html>
--- a/dom/html/HTMLFormElement.cpp
+++ b/dom/html/HTMLFormElement.cpp
@@ -9,16 +9,17 @@
 #include "jsapi.h"
 #include "mozilla/ContentEvents.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/dom/AutocompleteErrorEvent.h"
 #include "mozilla/dom/nsCSPUtils.h"
 #include "mozilla/dom/nsCSPContext.h"
+#include "mozilla/dom/nsMixedContentBlocker.h"
 #include "mozilla/dom/HTMLFormControlsCollection.h"
 #include "mozilla/dom/HTMLFormElementBinding.h"
 #include "mozilla/Move.h"
 #include "nsIHTMLDocument.h"
 #include "nsGkAtoms.h"
 #include "nsStyleConsts.h"
 #include "nsPresContext.h"
 #include "nsIDocument.h"
@@ -902,16 +903,20 @@ HTMLFormElement::DoSecureToInsecureSubmi
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   if (!formIsHTTPS || actionIsHTTPS || actionIsJS) {
     return NS_OK;
   }
 
+  if (nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL(aActionURL)) {
+    return NS_OK;
+  }
+
   nsCOMPtr<nsPIDOMWindowOuter> window = OwnerDoc()->GetWindow();
   if (!window) {
     return NS_ERROR_FAILURE;
   }
   nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
   if (!docShell) {
     return NS_ERROR_FAILURE;
   }
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -1133,16 +1133,17 @@ HTMLInputElement::Shutdown()
 //
 // construction, destruction
 //
 
 HTMLInputElement::HTMLInputElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
                                    FromParser aFromParser, FromClone aFromClone)
   : nsGenericHTMLFormElementWithState(aNodeInfo, kInputDefaultType->value)
   , mAutocompleteAttrState(nsContentUtils::eAutocompleteAttrState_Unknown)
+  , mAutocompleteInfoState(nsContentUtils::eAutocompleteAttrState_Unknown)
   , mDisabledChanged(false)
   , mValueChanged(false)
   , mLastValueChangeWasInteractive(false)
   , mCheckedChanged(false)
   , mChecked(false)
   , mHandlingSelectEvent(false)
   , mShouldInitChecked(false)
   , mDoneCreating(aFromParser == NOT_FROM_PARSER &&
@@ -1508,18 +1509,19 @@ HTMLInputElement::AfterSetAttr(int32_t a
         GetNonFileValueInternal(value);
         nsNumberControlFrame* numberControlFrame =
           do_QueryFrame(GetPrimaryFrame());
         if (numberControlFrame) {
           numberControlFrame->SetValueOfAnonTextControl(value);
         }
       }
     } else if (aName == nsGkAtoms::autocomplete) {
-      // Clear the cached @autocomplete attribute state.
+      // Clear the cached @autocomplete attribute and autocompleteInfo state.
       mAutocompleteAttrState = nsContentUtils::eAutocompleteAttrState_Unknown;
+      mAutocompleteInfoState = nsContentUtils::eAutocompleteAttrState_Unknown;
     }
   }
 
   return nsGenericHTMLFormElementWithState::AfterSetAttr(aNameSpaceID, aName,
                                                          aValue, aNotify);
 }
 
 void
@@ -1588,17 +1590,17 @@ NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLInpu
 
 NS_IMETHODIMP
 HTMLInputElement::GetAutocomplete(nsAString& aValue)
 {
   if (!DoesAutocompleteApply()) {
     return NS_OK;
   }
 
-  aValue.Truncate(0);
+  aValue.Truncate();
   const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
 
   mAutocompleteAttrState =
     nsContentUtils::SerializeAutocompleteAttribute(attributeVal, aValue,
                                                    mAutocompleteAttrState);
   return NS_OK;
 }
 
@@ -1612,19 +1614,20 @@ void
 HTMLInputElement::GetAutocompleteInfo(Nullable<AutocompleteInfo>& aInfo)
 {
   if (!DoesAutocompleteApply()) {
     aInfo.SetNull();
     return;
   }
 
   const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
-  mAutocompleteAttrState =
+  mAutocompleteInfoState =
     nsContentUtils::SerializeAutocompleteAttribute(attributeVal, aInfo.SetValue(),
-                                                   mAutocompleteAttrState);
+                                                   mAutocompleteInfoState,
+                                                   true);
 }
 
 int32_t
 HTMLInputElement::TabIndexDefault()
 {
   return 0;
 }
 
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -1589,16 +1589,17 @@ protected:
   // Maximum valid month is 275760-09.
   static const double kMaximumMonthInMaximumYear;
   // Long years in a ISO calendar have 53 weeks in them.
   static const double kMaximumWeekInYear;
   // Milliseconds in a day.
   static const double kMsPerDay;
 
   nsContentUtils::AutocompleteAttrState mAutocompleteAttrState;
+  nsContentUtils::AutocompleteAttrState mAutocompleteInfoState;
   bool                     mDisabledChanged     : 1;
   bool                     mValueChanged        : 1;
   bool                     mLastValueChangeWasInteractive : 1;
   bool                     mCheckedChanged      : 1;
   bool                     mChecked             : 1;
   bool                     mHandlingSelectEvent : 1;
   bool                     mShouldInitChecked   : 1;
   bool                     mDoneCreating        : 1;
--- a/dom/html/HTMLSelectElement.cpp
+++ b/dom/html/HTMLSelectElement.cpp
@@ -115,16 +115,17 @@ SafeOptionListMutation::~SafeOptionListM
 // construction, destruction
 
 
 HTMLSelectElement::HTMLSelectElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
                                      FromParser aFromParser)
   : nsGenericHTMLFormElementWithState(aNodeInfo, NS_FORM_SELECT),
     mOptions(new HTMLOptionsCollection(this)),
     mAutocompleteAttrState(nsContentUtils::eAutocompleteAttrState_Unknown),
+    mAutocompleteInfoState(nsContentUtils::eAutocompleteAttrState_Unknown),
     mIsDoneAddingChildren(!aFromParser),
     mDisabledChanged(false),
     mMutating(false),
     mInhibitStateRestoration(!!(aFromParser & FROM_PARSER_FRAGMENT)),
     mSelectionHasChanged(false),
     mDefaultSelectionSet(false),
     mCanShowInvalidUI(true),
     mCanShowValidUI(true),
@@ -202,19 +203,20 @@ HTMLSelectElement::GetAutocomplete(DOMSt
     nsContentUtils::SerializeAutocompleteAttribute(attributeVal, aValue,
                                                    mAutocompleteAttrState);
 }
 
 void
 HTMLSelectElement::GetAutocompleteInfo(AutocompleteInfo& aInfo)
 {
   const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
-  mAutocompleteAttrState =
+  mAutocompleteInfoState =
     nsContentUtils::SerializeAutocompleteAttribute(attributeVal, aInfo,
-                                                   mAutocompleteAttrState);
+                                                   mAutocompleteInfoState,
+                                                   true);
 }
 
 NS_IMETHODIMP
 HTMLSelectElement::GetForm(nsIDOMHTMLFormElement** aForm)
 {
   return nsGenericHTMLFormElementWithState::GetForm(aForm);
 }
 
@@ -1313,18 +1315,19 @@ HTMLSelectElement::AfterSetAttr(int32_t 
                                 const nsAttrValue* aValue, bool aNotify)
 {
   if (aNameSpaceID == kNameSpaceID_None) {
     if (aName == nsGkAtoms::disabled) {
       UpdateBarredFromConstraintValidation();
     } else if (aName == nsGkAtoms::required) {
       UpdateValueMissingValidityState();
     } else if (aName == nsGkAtoms::autocomplete) {
-      // Clear the cached @autocomplete attribute state
+      // Clear the cached @autocomplete attribute and autocompleteInfo state.
       mAutocompleteAttrState = nsContentUtils::eAutocompleteAttrState_Unknown;
+      mAutocompleteInfoState = nsContentUtils::eAutocompleteAttrState_Unknown;
     }
   }
 
   return nsGenericHTMLFormElementWithState::AfterSetAttr(aNameSpaceID, aName,
                                                          aValue, aNotify);
 }
 
 nsresult
--- a/dom/html/HTMLSelectElement.h
+++ b/dom/html/HTMLSelectElement.h
@@ -605,16 +605,17 @@ protected:
     }
 
     return mSelectionHasChanged;
   }
 
   /** The options[] array */
   RefPtr<HTMLOptionsCollection> mOptions;
   nsContentUtils::AutocompleteAttrState mAutocompleteAttrState;
+  nsContentUtils::AutocompleteAttrState mAutocompleteInfoState;
   /** false if the parser is in the middle of adding children. */
   bool            mIsDoneAddingChildren;
   /** true if our disabled state has changed from the default **/
   bool            mDisabledChanged;
   /** true if child nodes are being added or removed.
    *  Used by SafeOptionListMutation.
    */
   bool            mMutating;
--- a/dom/html/moz.build
+++ b/dom/html/moz.build
@@ -229,17 +229,18 @@ EXTRA_COMPONENTS += [
 include('/ipc/chromium/chromium-config.mozbuild')
 
 LOCAL_INCLUDES += [
     '/caps',
     '/docshell/base',
     '/dom/base',
     '/dom/canvas',
     '/dom/html/input',
-    '/dom/media/',
+    '/dom/media',
+    '/dom/security',
     '/dom/xbl',
     '/dom/xul',
     '/editor/txmgr',
     '/image',
     '/layout/forms',
     '/layout/generic',
     '/layout/style',
     '/layout/tables',
--- a/dom/html/test/forms/test_autocompleteinfo.html
+++ b/dom/html/test/forms/test_autocompleteinfo.html
@@ -1,16 +1,17 @@
 <!DOCTYPE html>
 <html>
 <!--
-Test getAutocompleteInfo() on <input>
+Test getAutocompleteInfo() on <input> and <select>
 -->
 <head>
   <title>Test for getAutocompleteInfo()</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
 </head>
 
 <body>
 <p id="display"></p>
 <div id="content" style="display: none">
   <form>
     <input id="input"/>
@@ -18,126 +19,153 @@ Test getAutocompleteInfo() on <input>
   </form>
 </div>
 <pre id="test">
 <script>
 "use strict";
 
 var values = [
   // Missing or empty attribute
-  [undefined, {}],
-  ["", {}],
+  [undefined, {}, ""],
+  ["", {}, ""],
 
   // One token
-  ["on", {fieldName: "on" }],
-  ["On", {fieldName: "on" }],
-  ["off", {fieldName: "off" } ],
-  ["username", {fieldName: "username" }],
-  [" username ", {fieldName: "username" }],
-  ["foobar", {}],
-  ["section-blue", {}],
+  ["on", {fieldName: "on" }, "on"],
+  ["On", {fieldName: "on" }, "on"],
+  ["off", {fieldName: "off" }, "off" ],
+  ["name", {fieldName: "name" }, "name"],
+  [" name ", {fieldName: "name" }, "name"],
+  ["username", {fieldName: "username"}, ""],
+  [" username ", {fieldName: "username"}, ""],
+  ["cc-csc", {fieldName: "cc-csc"}, ""],
+  ["language", {fieldName: "language"}, ""],
+  ["tel-extension", {fieldName: "tel-extension"}, ""],
+  ["foobar", {}, ""],
+  ["section-blue", {}, ""],
 
   // Two tokens
-  ["on off", {}],
-  ["off on", {}],
-  ["username tel", {}],
-  ["tel username ", {}],
-  [" username tel ", {}],
-  ["tel mobile", {}],
-  ["tel shipping", {}],
-  ["shipping tel", {addressType: "shipping", fieldName: "tel"}],
-  ["shipPING tel", {addressType: "shipping", fieldName: "tel"}],
-  ["mobile tel", {contactType: "mobile", fieldName: "tel"}],
-  ["  MoBiLe  TeL  ", {contactType: "mobile", fieldName: "tel"}],
-  ["XXX tel", {}],
-  ["XXX username", {}],
-  ["name section-blue", {}],
-  ["scetion-blue cc-name", {}],
-  ["section-blue name", {section: "section-blue", fieldName: "name"}],
-  ["section-blue tel", {section: "section-blue", fieldName: "tel"}],
+  ["on off", {}, ""],
+  ["off on", {}, ""],
+  ["username tel", {}, ""],
+  ["tel username ", {}, ""],
+  [" username tel ", {}, ""],
+  ["tel mobile", {}, ""],
+  ["tel shipping", {}, ""],
+  ["shipping tel", {addressType: "shipping", fieldName: "tel"}, "shipping tel"],
+  ["shipPING tel", {addressType: "shipping", fieldName: "tel"}, "shipping tel"],
+  ["mobile tel", {contactType: "mobile", fieldName: "tel"}, "mobile tel"],
+  ["  MoBiLe  TeL  ", {contactType: "mobile", fieldName: "tel"}, "mobile tel"],
+  ["pager impp", {contactType: "pager", fieldName: "impp"}, ""],
+  ["fax tel-extension", {contactType: "fax", fieldName: "tel-extension"}, ""],
+  ["XXX tel", {}, ""],
+  ["XXX username", {}, ""],
+  ["name section-blue", {}, ""],
+  ["scetion-blue cc-name", {}, ""],
+  ["pager language", {}, ""],
+  ["fax url", {}, ""],
+  ["section-blue name", {section: "section-blue", fieldName: "name"}, "section-blue name"],
+  ["section-blue tel", {section: "section-blue", fieldName: "tel"}, "section-blue tel"],
 
   // Three tokens
-  ["billing invalid tel", {}],
-  ["___ mobile tel", {}],
-  ["mobile foo tel", {}],
-  ["mobile tel foo", {}],
-  ["tel mobile billing", {}],
-  ["billing mobile tel", {addressType: "billing", contactType: "mobile", fieldName: "tel"}],
-  ["  BILLing   MoBiLE   tEl  ", {addressType: "billing", contactType: "mobile", fieldName: "tel"}],
-  ["billing home tel", {addressType: "billing", contactType: "home", fieldName: "tel"}],
-  ["home section-blue tel", {}],
-  ["setion-blue work email", {}],
-  ["section-blue home address-level2", {}],
-  ["section-blue shipping name", {section: "section-blue", addressType: "shipping", fieldName: "name"}],
-  ["section-blue mobile tel", {section: "section-blue", contactType: "mobile", fieldName: "tel"}],
+  ["billing invalid tel", {}, ""],
+  ["___ mobile tel", {}, ""],
+  ["mobile foo tel", {}, ""],
+  ["mobile tel foo", {}, ""],
+  ["tel mobile billing", {}, ""],
+  ["billing mobile tel", {addressType: "billing", contactType: "mobile", fieldName: "tel"}, "billing mobile tel"],
+  ["  BILLing   MoBiLE   tEl  ", {addressType: "billing", contactType: "mobile", fieldName: "tel"}, "billing mobile tel"],
+  ["billing home tel", {addressType: "billing", contactType: "home", fieldName: "tel"}, "billing home tel"],
+  ["home section-blue tel", {}, ""],
+  ["setion-blue work email", {}, ""],
+  ["section-blue home address-level2", {}, ""],
+  ["section-blue shipping name", {section: "section-blue", addressType: "shipping", fieldName: "name"}, "section-blue shipping name"],
+  ["section-blue mobile tel", {section: "section-blue", contactType: "mobile", fieldName: "tel"}, "section-blue mobile tel"],
 
   // Four tokens
-  ["billing billing mobile tel", {}],
-  ["name section-blue shipping home", {}],
-  ["secti shipping work address-line1", {}],
-  ["section-blue shipping home name", {}],
-  ["section-blue shipping mobile tel", {section: "section-blue", addressType: "shipping", contactType: "mobile", fieldName: "tel"}],
+  ["billing billing mobile tel", {}, ""],
+  ["name section-blue shipping home", {}, ""],
+  ["secti shipping work address-line1", {}, ""],
+  ["section-blue shipping home name", {}, ""],
+  ["section-blue shipping mobile tel", {section: "section-blue", addressType: "shipping", contactType: "mobile", fieldName: "tel"}, "section-blue shipping mobile tel"],
 
   // Five tokens (invalid)
-  ["billing billing billing mobile tel", {}],
-  ["section-blue section-blue billing mobile tel", {}],
+  ["billing billing billing mobile tel", {}, ""],
+  ["section-blue section-blue billing mobile tel", {}, ""],
 ];
 
+var autocompleteInfoFieldIds = ["input", "select"];
 var autocompleteEnabledTypes = ["hidden", "text", "search", "url", "tel",
                                 "email", "password", "date", "time", "number",
                                 "range", "color"];
 var autocompleteDisabledTypes = ["reset", "submit", "image", "button", "radio",
                                  "checkbox", "file"];
 
-function start() {
+function testInputTypes() {
   let field = document.getElementById("input");
-  testAutocompleteInfoValue(field, "input");
 
   for (var type of autocompleteEnabledTypes) {
     testAutocomplete(field, type, true);
   }
 
   for (var type of autocompleteDisabledTypes) {
     testAutocomplete(field, type, false);
   }
 
-  field = document.getElementById("select");
-  testAutocompleteInfoValue(field, "select");
-
-  SimpleTest.finish();
+  // Clear input type attribute.
+  field.removeAttribute("type");
 }
 
-function testAutocompleteInfoValue(aField, aFieldId) {
-  for (var test of values) {
-    if (typeof(test[0]) === "undefined")
-      aField.removeAttribute("autocomplete");
-    else
-      aField.setAttribute("autocomplete", test[0]);
+function testAutocompleteInfoValue(aEnabled) {
+  for (var fieldId of autocompleteInfoFieldIds) {
+    let field = document.getElementById(fieldId);
 
-    var info = aField.getAutocompleteInfo();
+    for (var test of values) {
+      if (typeof(test[0]) === "undefined")
+        field.removeAttribute("autocomplete");
+      else
+        field.setAttribute("autocomplete", test[0]);
 
-    is(info.section, "section" in test[1] ? test[1].section : "",
-       "Checking autocompleteInfo.section for " + aFieldId + ": " + test[0]);
-    is(info.addressType, "addressType" in test[1] ? test[1].addressType : "",
-       "Checking autocompleteInfo.addressType for " + aFieldId + ": " + test[0]);
-    is(info.contactType, "contactType" in test[1] ? test[1].contactType : "",
-       "Checking autocompleteInfo.contactType for " + aFieldId + ": " + test[0]);
-    is(info.fieldName, "fieldName" in test[1] ? test[1].fieldName : "",
-       "Checking autocompleteInfo.fieldName for " + aFieldId + ": " + test[0]);
+      var info = field.getAutocompleteInfo();
+      if (aEnabled) {
+        // We need to consider if getAutocompleteInfo() is valid,
+        // but @autocomplete is invalid case, because @autocomplete
+        // has smaller set of values.
+        is(field.autocomplete, test[2], "Checking @autocomplete of: " + test[0]);
+      }
+
+      is(info.section, "section" in test[1] ? test[1].section : "",
+        "Checking autocompleteInfo.section for " + field + ": " + test[0]);
+      is(info.addressType, "addressType" in test[1] ? test[1].addressType : "",
+        "Checking autocompleteInfo.addressType for " + field + ": " + test[0]);
+      is(info.contactType, "contactType" in test[1] ? test[1].contactType : "",
+        "Checking autocompleteInfo.contactType for " + field + ": " + test[0]);
+      is(info.fieldName, "fieldName" in test[1] ? test[1].fieldName : "",
+        "Checking autocompleteInfo.fieldName for " + field + ": " + test[0]);
+    }
   }
 }
 
 function testAutocomplete(aField, aType, aEnabled) {
   aField.type = aType;
   if (aEnabled) {
     ok(aField.getAutocompleteInfo() !== null, "getAutocompleteInfo shouldn't return null");
   } else {
     is(aField.getAutocompleteInfo(), null, "getAutocompleteInfo should return null");
   }
 }
 
-SimpleTest.waitForExplicitFinish();
-SpecialPowers.pushPrefEnv({"set": [["dom.forms.autocomplete.experimental", true]]}, start);
+// getAutocompleteInfo() should be able to parse all tokens as defined
+// in the spec regardless of whether dom.forms.autocomplete.experimental pref
+// is on or off.
+add_task(function* testAutocompletePreferenceEnabled() {
+  yield SpecialPowers.pushPrefEnv({"set": [["dom.forms.autocomplete.experimental", true]]}, testInputTypes);
+  testAutocompleteInfoValue(true);
+});
+
+add_task(function* testAutocompletePreferenceDisabled() {
+  yield SpecialPowers.pushPrefEnv({"set": [["dom.forms.autocomplete.experimental", false]]}, testInputTypes);
+  testAutocompleteInfoValue(false);
+});
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/html/test/forms/test_input_autocomplete.html
+++ b/dom/html/test/forms/test_input_autocomplete.html
@@ -17,37 +17,46 @@ var values = [
   [undefined, ""],
   ["", ""],
 
   // One token
   ["on", "on"],
   ["On", "on"],
   ["off", "off"],
   ["OFF", "off"],
-  ["username", "username"],
-  [" username ", "username"],
+  ["name", "name"],
+  [" name ", "name"],
+  ["username", ""],
+  [" username ", ""],
+  ["cc-csc", ""],
+  ["language", ""],
+  ["tel-extension", ""],
   ["foobar", ""],
   ["section-blue", ""],
 
   // Two tokens
   ["on off", ""],
   ["off on", ""],
   ["username tel", ""],
   ["tel username ", ""],
   [" username tel ", ""],
   ["tel mobile", ""],
   ["tel shipping", ""],
   ["shipping tel", "shipping tel"],
   ["shipPING tel", "shipping tel"],
   ["mobile tel", "mobile tel"],
   ["  MoBiLe  TeL  ", "mobile tel"],
+  ["pager impp", ""],
+  ["fax tel-extension", ""],
   ["XXX tel", ""],
   ["XXX username", ""],
   ["name section-blue", ""],
   ["scetion-blue cc-name", ""],
+  ["pager language", ""],
+  ["fax url", ""],
   ["section-blue name", "section-blue name"],
   ["section-blue tel", "section-blue tel"],
 
   // Three tokens
   ["billing invalid tel", ""],
   ["___ mobile tel", ""],
   ["mobile foo tel", ""],
   ["mobile tel foo", ""],
--- a/dom/network/TCPSocket.cpp
+++ b/dom/network/TCPSocket.cpp
@@ -997,17 +997,17 @@ TCPSocket::CreateInputStreamPump()
 {
   if (!mSocketInputStream) {
     return NS_ERROR_NOT_AVAILABLE;
   }
   nsresult rv;
   mInputStreamPump = do_CreateInstance("@mozilla.org/network/input-stream-pump;1", &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  rv = mInputStreamPump->Init(mSocketInputStream, -1, -1, 0, 0, false);
+  rv = mInputStreamPump->Init(mSocketInputStream, -1, -1, 0, 0, false, nullptr);
   NS_ENSURE_SUCCESS(rv, rv);
 
   uint64_t suspendCount = mSuspendCount;
   while (suspendCount--) {
     mInputStreamPump->Suspend();
   }
 
   rv = mInputStreamPump->AsyncRead(this, nullptr);
--- a/dom/presentation/PresentationTCPSessionTransport.cpp
+++ b/dom/presentation/PresentationTCPSessionTransport.cpp
@@ -277,17 +277,17 @@ PresentationTCPSessionTransport::CreateI
   }
 
   nsresult rv;
   mInputStreamPump = do_CreateInstance(NS_INPUTSTREAMPUMP_CONTRACTID, &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  rv = mInputStreamPump->Init(mSocketInputStream, -1, -1, 0, 0, false);
+  rv = mInputStreamPump->Init(mSocketInputStream, -1, -1, 0, 0, false, nullptr);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = mInputStreamPump->AsyncRead(this, nullptr);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
--- a/dom/security/nsContentSecurityManager.cpp
+++ b/dom/security/nsContentSecurityManager.cpp
@@ -5,18 +5,16 @@
 #include "nsILoadInfo.h"
 #include "nsContentUtils.h"
 #include "nsCORSListenerProxy.h"
 #include "nsIStreamListener.h"
 #include "nsIDocument.h"
 #include "nsMixedContentBlocker.h"
 #include "nsCDefaultURIFixup.h"
 #include "nsIURIFixup.h"
-#include "nsINestedURI.h"
-
 #include "mozilla/dom/Element.h"
 
 NS_IMPL_ISUPPORTS(nsContentSecurityManager,
                   nsIContentSecurityManager,
                   nsIChannelEventSink)
 
 static nsresult
 ValidateSecurityFlags(nsILoadInfo* aLoadInfo)
@@ -553,22 +551,16 @@ nsContentSecurityManager::CheckChannel(n
   nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsContentPolicyType contentPolicyType =
     loadInfo->GetExternalContentPolicyType();
 
   if (contentPolicyType == nsIContentPolicy::TYPE_DOCUMENT ||
       contentPolicyType == nsIContentPolicy::TYPE_SUBDOCUMENT) {
-    // query the nested URI for security checks like in the case of view-source
-    nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(uri);
-    if (nestedURI) {
-      nestedURI->GetInnerURI(getter_AddRefs(uri));
-    }
-
     // TYPE_DOCUMENT and TYPE_SUBDOCUMENT loads might potentially
     // be wyciwyg:// channels. Let's fix up the URI so we can
     // perform proper security checks.
     nsCOMPtr<nsIURIFixup> urifixup(do_GetService(NS_URIFIXUP_CONTRACTID, &rv));
     if (NS_SUCCEEDED(rv) && urifixup) {
       nsCOMPtr<nsIURI> fixedURI;
       rv = urifixup->CreateExposableURI(uri, getter_AddRefs(fixedURI));
       if (NS_SUCCEEDED(rv)) {
--- a/dom/security/nsMixedContentBlocker.cpp
+++ b/dom/security/nsMixedContentBlocker.cpp
@@ -422,16 +422,28 @@ nsMixedContentBlocker::ShouldLoad(uint32
                            aRequestingContext,
                            aMimeGuess,
                            aExtra,
                            aRequestPrincipal,
                            aDecision);
   return rv;
 }
 
+bool
+nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL(nsIURI* aURL) {
+  nsAutoCString host;
+  nsresult rv = aURL->GetHost(host);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  // We could also allow 'localhost' (if we can guarantee that it resolves
+  // to a loopback address), but Chrome doesn't support it as of writing. For
+  // web compat, lets only allow what Chrome allows.
+  return host.Equals("127.0.0.1") || host.Equals("::1");
+}
+
 /* Static version of ShouldLoad() that contains all the Mixed Content Blocker
  * logic.  Called from non-static ShouldLoad().
  */
 nsresult
 nsMixedContentBlocker::ShouldLoad(bool aHadInsecureImageRedirect,
                                   uint32_t aContentType,
                                   nsIURI* aContentLocation,
                                   nsIURI* aRequestingLocation,
@@ -724,30 +736,39 @@ nsMixedContentBlocker::ShouldLoad(bool a
     rv = innerContentLocation->SchemeIs("https", &isHttpsScheme);
     NS_ENSURE_SUCCESS(rv, rv);
     MOZ_ASSERT(!isHttpsScheme);
 #endif
     *aDecision = REJECT_REQUEST;
     return NS_OK;
   }
 
+  bool isHttpScheme = false;
+  rv = innerContentLocation->SchemeIs("http", &isHttpScheme);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Loopback origins are not considered mixed content even over HTTP. See:
+  // https://w3c.github.io/webappsec-mixed-content/#should-block-fetch
+  if (isHttpScheme &&
+      IsPotentiallyTrustworthyLoopbackURL(innerContentLocation)) {
+    *aDecision = ACCEPT;
+    return NS_OK;
+  }
+
   // The page might have set the CSP directive 'upgrade-insecure-requests'. In such
   // a case allow the http: load to succeed with the promise that the channel will
   // get upgraded to https before fetching any data from the netwerk.
   // Please see: nsHttpChannel::Connect()
   //
   // Please note that the CSP directive 'upgrade-insecure-requests' only applies to
   // http: and ws: (for websockets). Websockets are not subject to mixed content
   // blocking since insecure websockets are not allowed within secure pages. Hence,
   // we only have to check against http: here. Skip mixed content blocking if the
   // subresource load uses http: and the CSP directive 'upgrade-insecure-requests'
   // is present on the page.
-  bool isHttpScheme = false;
-  rv = innerContentLocation->SchemeIs("http", &isHttpScheme);
-  NS_ENSURE_SUCCESS(rv, rv);
   nsIDocument* document = docShell->GetDocument();
   MOZ_ASSERT(document, "Expected a document");
   if (isHttpScheme && document->GetUpgradeInsecureRequests(isPreload)) {
     *aDecision = ACCEPT;
     return NS_OK;
   }
 
   // The page might have set the CSP directive 'block-all-mixed-content' which
--- a/dom/security/nsMixedContentBlocker.h
+++ b/dom/security/nsMixedContentBlocker.h
@@ -40,16 +40,20 @@ private:
 
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSICONTENTPOLICY
   NS_DECL_NSICHANNELEVENTSINK
 
   nsMixedContentBlocker();
 
+  // See:
+  // https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy
+  static bool IsPotentiallyTrustworthyLoopbackURL(nsIURI* aURL);
+
   /* Static version of ShouldLoad() that contains all the Mixed Content Blocker
    * logic.  Called from non-static ShouldLoad().
    * Called directly from imageLib when an insecure redirect exists in a cached
    * image load.
    * @param aHadInsecureImageRedirect
    *        boolean flag indicating that an insecure redirect through http
    *        occured when this image was initially loaded and cached.
    * Remaining parameters are from nsIContentPolicy::ShouldLoad().
--- a/dom/security/test/hsts/browser_hsts-priming_no-ip-address.js
+++ b/dom/security/test/hsts/browser_hsts-priming_no-ip-address.js
@@ -9,17 +9,17 @@
 //jscs:disable
 add_task(function*() {
   //jscs:enable
   Observer.add_observers(Services);
   registerCleanupFunction(do_cleanup);
 
   // add the top-level server
   test_servers['localhost-ip'] = {
-    host: '127.0.0.1',
+    host: '127.0.0.2',
     response: true,
     id: 'localhost-ip',
   };
   test_settings.block_active.result['localhost-ip'] = 'blocked';
 
   let which = "block_active";
 
   SetupPrefTestEnvironment(which);
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -1051,28 +1051,30 @@ class WorkerJSRuntime final : public moz
 {
 public:
   // The heap size passed here doesn't matter, we will change it later in the
   // call to JS_SetGCParameter inside InitJSContextForWorker.
   explicit WorkerJSRuntime(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
     : CycleCollectedJSRuntime(aCx)
     , mWorkerPrivate(aWorkerPrivate)
   {
+    MOZ_COUNT_CTOR_INHERITED(WorkerJSRuntime, CycleCollectedJSRuntime);
     MOZ_ASSERT(aWorkerPrivate);
   }
 
   void Shutdown(JSContext* cx) override
   {
     // The CC is shut down, and the superclass destructor will GC, so make sure
     // we don't try to CC again.
     mWorkerPrivate = nullptr;
   }
 
   ~WorkerJSRuntime()
   {
+    MOZ_COUNT_DTOR_INHERITED(WorkerJSRuntime, CycleCollectedJSRuntime);
   }
 
   virtual void
   PrepareForForgetSkippable() override
   {
   }
 
   virtual void
@@ -1115,21 +1117,23 @@ private:
 class MOZ_STACK_CLASS WorkerJSContext final : public mozilla::CycleCollectedJSContext
 {
 public:
   // The heap size passed here doesn't matter, we will change it later in the
   // call to JS_SetGCParameter inside InitJSContextForWorker.
   explicit WorkerJSContext(WorkerPrivate* aWorkerPrivate)
     : mWorkerPrivate(aWorkerPrivate)
   {
+    MOZ_COUNT_CTOR_INHERITED(WorkerJSContext, CycleCollectedJSContext);
     MOZ_ASSERT(aWorkerPrivate);
   }
 
   ~WorkerJSContext()
   {
+    MOZ_COUNT_DTOR_INHERITED(WorkerJSContext, CycleCollectedJSContext);
     JSContext* cx = MaybeContext();
     if (!cx) {
       return;   // Initialize() must have failed
     }
 
     delete static_cast<WorkerThreadContextPrivate*>(JS_GetContextPrivate(cx));
     JS_SetContextPrivate(cx, nullptr);
 
--- a/dom/workers/ServiceWorkerScriptCache.cpp
+++ b/dom/workers/ServiceWorkerScriptCache.cpp
@@ -25,16 +25,17 @@
 #include "nsContentUtils.h"
 #include "nsNetUtil.h"
 #include "ServiceWorkerManager.h"
 #include "Workers.h"
 #include "nsStringStream.h"
 
 using mozilla::dom::cache::Cache;
 using mozilla::dom::cache::CacheStorage;
+using mozilla::ipc::PrincipalInfo;
 
 BEGIN_WORKERS_NAMESPACE
 
 namespace serviceWorkerScriptCache {
 
 namespace {
 
 // XXX A sandbox nsIGlobalObject does not preserve its reflector, so |aSandbox|
@@ -74,603 +75,658 @@ CreateCacheStorage(JSContext* aCx, nsIPr
   return CacheStorage::CreateOnMainThread(cache::CHROME_ONLY_NAMESPACE,
                                           sandboxGlobalObject, aPrincipal,
                                           false /* private browsing */,
                                           true /* force trusted origin */,
                                           aRv);
 }
 
 class CompareManager;
+class CompareCache;
 
-// This class downloads a URL from the network and then it calls
-// NetworkFinished() in the CompareManager.
+// This class downloads a URL from the network, compare the downloaded script
+// with an existing cache if provided, and report to CompareManager via calling
+// ComparisonFinished().
 class CompareNetwork final : public nsIStreamLoaderObserver,
                              public nsIRequestObserver
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSISTREAMLOADEROBSERVER
   NS_DECL_NSIREQUESTOBSERVER
 
-  explicit CompareNetwork(CompareManager* aManager)
+  CompareNetwork(CompareManager* aManager,
+                 nsIPrincipal* aPrincipal,
+                 const nsAString& aURL,
+                 bool aIsMainScript,
+                 nsILoadGroup* aLoadGroup,
+                 Cache* const aCache)
     : mManager(aManager)
+    , mIsMainScript(true)
+    , mInternalHeaders(new InternalHeaders())
+    , mState(WaitingForInitialization)
+    , mNetworkResult(NS_OK)
+    , mCacheResult(NS_OK)
+    , mLoadFlags(nsIChannel::LOAD_BYPASS_SERVICE_WORKER)
   {
     MOZ_ASSERT(aManager);
     AssertIsOnMainThread();
+
+    Initialize(aPrincipal, aURL, aIsMainScript, aLoadGroup, aCache);
   }
 
-  nsresult
-  Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL, nsILoadGroup* aLoadGroup);
-
   void
-  Abort()
-  {
-    AssertIsOnMainThread();
+  Abort();
 
-    MOZ_ASSERT(mChannel);
-    mChannel->Cancel(NS_BINDING_ABORTED);
-    mChannel = nullptr;
-  }
+  void
+  NetworkFinished(nsresult aRv);
+
+  void
+  CacheFinished(nsresult aRv);
 
   const nsString& Buffer() const
   {
     AssertIsOnMainThread();
     return mBuffer;
   }
 
+  const nsString&
+  URL() const
+  {
+    AssertIsOnMainThread();
+    return mURL;
+  }
+
+  const ChannelInfo&
+  GetChannelInfo() const
+  {
+    return mChannelInfo;
+  }
+
+  already_AddRefed<InternalHeaders>
+  GetInternalHeaders() const
+  {
+    RefPtr<InternalHeaders> internalHeaders = mInternalHeaders;
+    return internalHeaders.forget();
+  }
+
+  UniquePtr<PrincipalInfo>
+  TakePrincipalInfo()
+  {
+    return Move(mPrincipalInfo);
+  }
+
+  bool
+  Succeeded() const
+  {
+    return NS_SUCCEEDED(mNetworkResult);
+  }
+
 private:
   ~CompareNetwork()
   {
     AssertIsOnMainThread();
+    MOZ_ASSERT(!mCC);
   }
 
+  void
+  Initialize(nsIPrincipal* aPrincipal,
+             const nsAString& aURL,
+             bool aIsMainScript,
+             nsILoadGroup* aLoadGroup,
+             Cache* const aCache);
+
+  void
+  Finished();
+
+  nsresult
+  SetPrincipalInfo(nsIChannel* aChannel);
+
   RefPtr<CompareManager> mManager;
+  RefPtr<CompareCache> mCC;
   nsCOMPtr<nsIChannel> mChannel;
   nsString mBuffer;
+
+  nsString mURL;
+  bool mIsMainScript;
+  ChannelInfo mChannelInfo;
+  RefPtr<InternalHeaders> mInternalHeaders;
+  UniquePtr<PrincipalInfo> mPrincipalInfo;
+
+  enum {
+    WaitingForInitialization,
+    WaitingForBothFinished,
+    WaitingForNetworkFinished,
+    WaitingForCacheFinished,
+    Redundant
+  } mState;
+
+  nsresult mNetworkResult;
+  nsresult mCacheResult;
+
+  nsCString mMaxScope;
+  nsLoadFlags mLoadFlags;
 };
 
 NS_IMPL_ISUPPORTS(CompareNetwork, nsIStreamLoaderObserver,
                   nsIRequestObserver)
 
 // This class gets a cached Response from the CacheStorage and then it calls
 // CacheFinished() in the CompareManager.
 class CompareCache final : public PromiseNativeHandler
                          , public nsIStreamLoaderObserver
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSISTREAMLOADEROBSERVER
 
-  explicit CompareCache(CompareManager* aManager)
-    : mManager(aManager)
-    , mState(WaitingForCache)
-    , mAborted(false)
+  CompareCache(CompareNetwork* aCN,
+               const nsAString& aURL,
+               Cache* const aCache)
+    : mCN(aCN)
+    , mState(WaitingForInitialization)
+    , mInCache(false)
   {
-    MOZ_ASSERT(aManager);
-    AssertIsOnMainThread();
-  }
-
-  nsresult
-  Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL,
-             const nsAString& aCacheName);
-
-  void
-  Abort()
-  {
+    MOZ_ASSERT(aCN);
     AssertIsOnMainThread();
 
-    MOZ_ASSERT(!mAborted);
-    mAborted = true;
-
-    if (mPump) {
-      mPump->Cancel(NS_BINDING_ABORTED);
-      mPump = nullptr;
-    }
+    Initialize(aURL, aCache);
   }
 
-  // This class manages 2 promises: 1 is to retrieve cache object, and 2 is for
-  // the value from the cache. For this reason we have mState to know what
-  // reject/resolve callback we are handling.
+  void
+  Abort();
 
   virtual void
-  ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
-  {
-    AssertIsOnMainThread();
-
-    if (mAborted) {
-      return;
-    }
-
-    if (mState == WaitingForCache) {
-      ManageCacheResult(aCx, aValue);
-      return;
-    }
-
-    MOZ_ASSERT(mState == WaitingForValue);
-    ManageValueResult(aCx, aValue);
-  }
+  ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
 
   virtual void
   RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
 
   const nsString& Buffer() const
   {
     AssertIsOnMainThread();
     return mBuffer;
   }
 
   const nsString& URL() const
   {
     AssertIsOnMainThread();
     return mURL;
   }
 
+  bool
+  InCache()
+  {
+    return mInCache;
+  }
+
 private:
   ~CompareCache()
   {
     AssertIsOnMainThread();
   }
 
   void
-  ManageCacheResult(JSContext* aCx, JS::Handle<JS::Value> aValue);
+  Initialize(const nsAString& aURL, Cache* const aCache);
+
+  void
+  Finished(nsresult aStatus, bool aInCache = false);
 
   void
   ManageValueResult(JSContext* aCx, JS::Handle<JS::Value> aValue);
 
-  RefPtr<CompareManager> mManager;
+  RefPtr<CompareNetwork> mCN;
   nsCOMPtr<nsIInputStreamPump> mPump;
 
   nsString mURL;
   nsString mBuffer;
 
   enum {
-    WaitingForCache,
-    WaitingForValue
+    WaitingForInitialization,
+    WaitingForValue,
+    Redundant
   } mState;
 
-  bool mAborted;
+  bool mInCache;
 };
 
 NS_IMPL_ISUPPORTS(CompareCache, nsIStreamLoaderObserver)
 
 class CompareManager final : public PromiseNativeHandler
 {
 public:
   NS_DECL_ISUPPORTS
 
-  explicit CompareManager(ServiceWorkerRegistrationInfo* aRegistration,
-                          CompareCallback* aCallback)
+  CompareManager(ServiceWorkerRegistrationInfo* aRegistration,
+                 CompareCallback* aCallback,
+                 nsIPrincipal* aPrincipal,
+                 const nsAString& aURL,
+                 const nsAString& aCacheName,
+                 nsILoadGroup* aLoadGroup)
     : mRegistration(aRegistration)
     , mCallback(aCallback)
-    , mInternalHeaders(new InternalHeaders())
-    , mState(WaitingForOpen)
-    , mNetworkFinished(false)
-    , mCacheFinished(false)
-    , mInCache(false)
+    , mState(WaitingForInitialization)
+    , mPendingCount(0)
+    , mAreScriptsEqual(true)
+    , mLoadFlags(nsIChannel::LOAD_BYPASS_SERVICE_WORKER)
   {
     AssertIsOnMainThread();
     MOZ_ASSERT(aRegistration);
-  }
 
-  nsresult
-  Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL,
-             const nsAString& aCacheName, nsILoadGroup* aLoadGroup)
-  {
-    AssertIsOnMainThread();
-    MOZ_ASSERT(aPrincipal);
-
-    mURL = aURL;
-
-    // Always create a CacheStorage since we want to write the network entry to
-    // the cache even if there isn't an existing one.
-    AutoJSAPI jsapi;
-    jsapi.Init();
-    ErrorResult result;
-    mSandbox.init(jsapi.cx());
-    mCacheStorage = CreateCacheStorage(jsapi.cx(), aPrincipal, result, &mSandbox);
-    if (NS_WARN_IF(result.Failed())) {
-      MOZ_ASSERT(!result.IsErrorWithMessage());
-      Cleanup();
-      return result.StealNSResult();
-    }
-
-    mCN = new CompareNetwork(this);
-    nsresult rv = mCN->Initialize(aPrincipal, aURL, aLoadGroup);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      Cleanup();
-      return rv;
-    }
-
-    if (!aCacheName.IsEmpty()) {
-      mCC = new CompareCache(this);
-      rv = mCC->Initialize(aPrincipal, aURL, aCacheName);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        mCN->Abort();
-        Cleanup();
-        return rv;
-      }
-    }
-
-    return NS_OK;
+    Initialize(aPrincipal, aURL, aCacheName, aLoadGroup);
   }
 
   const nsString&
   URL() const
   {
     AssertIsOnMainThread();
     return mURL;
   }
 
-  void
-  SetMaxScope(const nsACString& aMaxScope)
-  {
-    MOZ_ASSERT(!mNetworkFinished);
-    mMaxScope = aMaxScope;
-  }
-
   already_AddRefed<ServiceWorkerRegistrationInfo>
   GetRegistration()
   {
     RefPtr<ServiceWorkerRegistrationInfo> copy = mRegistration.get();
     return copy.forget();
   }
 
   void
-  SaveLoadFlags(nsLoadFlags aLoadFlags)
-  {
-    mCallback->SaveLoadFlags(aLoadFlags);
-  }
-
-  void
-  NetworkFinished(nsresult aStatus)
-  {
-    AssertIsOnMainThread();
-
-    mNetworkFinished = true;
-
-    if (NS_WARN_IF(NS_FAILED(aStatus))) {
-      if (mCC) {
-        mCC->Abort();
-      }
-
-      ComparisonFinished(aStatus, false);
-      return;
-    }
-
-    MaybeCompare();
-  }
-
-  void
-  CacheFinished(nsresult aStatus, bool aInCache)
-  {
-    AssertIsOnMainThread();
-
-    mCacheFinished = true;
-    mInCache = aInCache;
-
-    if (NS_WARN_IF(NS_FAILED(aStatus))) {
-      if (mCN) {
-        mCN->Abort();
-      }
-
-      ComparisonFinished(aStatus, false);
-      return;
-    }
-
-    MaybeCompare();
-  }
+  ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
 
   void
-  MaybeCompare()
-  {
-    AssertIsOnMainThread();
-
-    if (!mNetworkFinished || (mCC && !mCacheFinished)) {
-      return;
-    }
-
-    if (!mCC || !mInCache) {
-      ComparisonFinished(NS_OK, false);
-      return;
-    }
-
-    ComparisonFinished(NS_OK, mCC->Buffer().Equals(mCN->Buffer()));
-  }
-
-  // This class manages 2 promises: 1 is to retrieve Cache object, and 2 is to
-  // Put the value in the cache. For this reason we have mState to know what
-  // callback we are handling.
-  void
-  ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
-  {
-    AssertIsOnMainThread();
-    MOZ_ASSERT(mCallback);
-
-    if (mState == WaitingForOpen) {
-      if (NS_WARN_IF(!aValue.isObject())) {
-        Fail(NS_ERROR_FAILURE);
-        return;
-      }
-
-      JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
-      if (NS_WARN_IF(!obj)) {
-        Fail(NS_ERROR_FAILURE);
-        return;
-      }
-
-      Cache* cache = nullptr;
-      nsresult rv = UNWRAP_OBJECT(Cache, obj, cache);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        Fail(rv);
-        return;
-      }
-
-      // Just to be safe.
-      RefPtr<Cache> kungfuDeathGrip = cache;
-      WriteToCache(cache);
-      return;
-    }
-
-    MOZ_ASSERT(mState == WaitingForPut);
-    mCallback->ComparisonResult(NS_OK, false /* aIsEqual */,
-                                mNewCacheName, mMaxScope);
-    Cleanup();
-  }
-
-  void
-  RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
-  {
-    AssertIsOnMainThread();
-    if (mState == WaitingForOpen) {
-      NS_WARNING("Could not open cache.");
-    } else {
-      NS_WARNING("Could not write to cache.");
-    }
-    Fail(NS_ERROR_FAILURE);
-  }
+  RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
 
   CacheStorage*
   CacheStorage_()
   {
     AssertIsOnMainThread();
     MOZ_ASSERT(mCacheStorage);
     return mCacheStorage;
   }
 
-  nsresult
-  OnStartRequest(nsIChannel* aChannel)
+  void
+  ComparisonFinished(nsresult aStatus)
   {
-    nsresult rv = SetPrincipalInfo(aChannel);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    mChannelInfo.InitFromChannel(aChannel);
-
-    mInternalHeaders->FillResponseHeaders(aChannel);
-
-    return NS_OK;
+    MOZ_ASSERT(NS_FAILED(aStatus));
+    ComparisonFinished(aStatus,
+                       false /* aIsEqual */,
+                       false /* aIsMainScript */,
+                       EmptyCString(),
+                       nsIRequest::LOAD_NORMAL);
   }
 
-  nsresult
-  SetPrincipalInfo(nsIChannel* aChannel)
+  void
+  ComparisonFinished(nsresult aStatus,
+                     bool aIsEqual,
+                     bool aIsMainScript,
+                     const nsACString& aMaxScope,
+                     nsLoadFlags aLoadFlags)
   {
-    nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
-    NS_ASSERTION(ssm, "Should never be null!");
+    AssertIsOnMainThread();
 
-    nsCOMPtr<nsIPrincipal> channelPrincipal;
-    nsresult rv = ssm->GetChannelResultPrincipal(aChannel, getter_AddRefs(channelPrincipal));
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
+    if (NS_WARN_IF(NS_FAILED(aStatus))) {
+      NotifyComparisonResult(aStatus);
+      return;
     }
 
-    UniquePtr<mozilla::ipc::PrincipalInfo> principalInfo(new mozilla::ipc::PrincipalInfo());
-    rv = PrincipalToPrincipalInfo(channelPrincipal, principalInfo.get());
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
+    mAreScriptsEqual = mAreScriptsEqual && aIsEqual;
+
+    if (aIsMainScript) {
+      mMaxScope = aMaxScope;
+      mLoadFlags = aLoadFlags;
+    }
+
+    // Check whether all CompareNetworks finished their jobs.
+    MOZ_DIAGNOSTIC_ASSERT(mPendingCount > 0);
+    if (--mPendingCount) {
+      return;
     }
 
-    mPrincipalInfo = Move(principalInfo);
-    return NS_OK;
+    if (mAreScriptsEqual) {
+      NotifyComparisonResult(aStatus,
+                             true /* aSameScripts */,
+                             EmptyString(),
+                             mMaxScope,
+                             mLoadFlags);
+      return;
+    }
+
+    // Write to Cache so ScriptLoader reads succeed.
+    mState = WaitingForOpen;
+    WriteNetworkBufferToNewCache();
   }
 
 private:
   ~CompareManager()
   {
     AssertIsOnMainThread();
-    MOZ_ASSERT(!mCC);
-    MOZ_ASSERT(!mCN);
+    MOZ_ASSERT(mCNs.Length() == 0);
   }
 
   void
-  Fail(nsresult aStatus)
+  Initialize(nsIPrincipal* aPrincipal,
+             const nsAString& aURL,
+             const nsAString& aCacheName,
+             nsILoadGroup* aLoadGroup);
+
+
+  // Only used for notifying falsy results.
+  void
+  NotifyComparisonResult(nsresult aStatus);
+
+  void
+  NotifyComparisonResult(nsresult aStatus,
+                         bool aSameScripts,
+                         const nsAString& aNewCacheName,
+                         const nsACString& aMaxScope,
+                         nsLoadFlags aLoadFlags);
+
+  void
+  FetchScript(const nsAString& aURL,
+              bool aIsMainScript,
+              Cache* const aCache)
   {
-    AssertIsOnMainThread();
-    mCallback->ComparisonResult(aStatus, false /* aIsEqual */,
-                                EmptyString(), EmptyCString());
-    Cleanup();
+    RefPtr<CompareNetwork> cn = new CompareNetwork(this,
+                                                   mPrincipal,
+                                                   aURL,
+                                                   aIsMainScript,
+                                                   mLoadGroup,
+                                                   aCache);
+    mCNs.AppendElement(cn.forget());
+    mPendingCount += 1;
+  }
+
+  void
+  ManageOldCache(JSContext* aCx, JS::Handle<JS::Value> aValue)
+  {
+    MOZ_ASSERT(mState == WaitingForExistingOpen);
+
+    if (NS_WARN_IF(!aValue.isObject())) {
+      NotifyComparisonResult(NS_ERROR_FAILURE);
+      return;
+    }
+
+    MOZ_ASSERT(!mOldCache);
+    JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
+    if (NS_WARN_IF(!obj) ||
+        NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Cache, obj, mOldCache)))) {
+      NotifyComparisonResult(NS_ERROR_FAILURE);
+      return;
+    }
+
+    Optional<RequestOrUSVString> request;
+    CacheQueryOptions options;
+    ErrorResult error;
+    RefPtr<Promise> promise = mOldCache->Keys(request, options, error);
+    if (NS_WARN_IF(error.Failed())) {
+      NotifyComparisonResult(error.StealNSResult());
+      return;
+    }
+
+    mState = WaitingForExistingKeys;
+    promise->AppendNativeHandler(this);
+    return;
   }
 
   void
-  Cleanup()
+  ManageOldKeys(JSContext* aCx, JS::Handle<JS::Value> aValue)
   {
-    AssertIsOnMainThread();
-    MOZ_ASSERT(mCallback);
-    mCallback = nullptr;
-    mCN = nullptr;
-    mCC = nullptr;
+    MOZ_ASSERT(mState == WaitingForExistingKeys);
+
+    if (NS_WARN_IF(!aValue.isObject())) {
+      NotifyComparisonResult(NS_ERROR_FAILURE);
+      return;
+    }
+
+    JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
+    if (NS_WARN_IF(!obj)) {
+      NotifyComparisonResult(NS_ERROR_FAILURE);
+      return;
+    }
+
+    uint32_t len = 0;
+    if (!JS_GetArrayLength(aCx, obj, &len)) {
+      NotifyComparisonResult(NS_ERROR_FAILURE);
+      return;
+    }
+
+    // Fetch the new scripts.
+    MOZ_ASSERT(mPendingCount == 0);
+
+    mState = WaitingForScriptOrComparisonResult;
+    for (uint32_t i = 0; i < len; ++i) {
+      JS::Rooted<JS::Value> val(aCx);
+      if (NS_WARN_IF(!JS_GetElement(aCx, obj, i, &val)) ||
+          NS_WARN_IF(!val.isObject())) {
+        NotifyComparisonResult(NS_ERROR_FAILURE);
+        return;
+      }
+
+      Request* request;
+      JS::Rooted<JSObject*> requestObj(aCx, &val.toObject());
+      if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Request,
+                requestObj,
+                request)))) {
+        NotifyComparisonResult(NS_ERROR_FAILURE);
+        continue;
+      };
+
+      nsString URL;
+      request->GetUrl(URL);
+      FetchScript(URL, mURL == URL /* aIsMainScript */, mOldCache);
+    }
+
+    return;
   }
 
   void
-  ComparisonFinished(nsresult aStatus, bool aIsEqual)
+  ManageNewCache(JSContext* aCx, JS::Handle<JS::Value> aValue)
   {
-    AssertIsOnMainThread();
-    MOZ_ASSERT(mCallback);
+    MOZ_ASSERT(mState == WaitingForOpen);
 
-    if (NS_WARN_IF(NS_FAILED(aStatus))) {
-      Fail(aStatus);
+    if (NS_WARN_IF(!aValue.isObject())) {
+      NotifyComparisonResult(NS_ERROR_FAILURE);
+      return;
+    }
+
+    JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
+    if (NS_WARN_IF(!obj)) {
+      NotifyComparisonResult(NS_ERROR_FAILURE);
       return;
     }
 
-    if (aIsEqual) {
-      mCallback->ComparisonResult(aStatus, aIsEqual, EmptyString(), mMaxScope);
-      Cleanup();
+    Cache* cache = nullptr;
+    nsresult rv = UNWRAP_OBJECT(Cache, obj, cache);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      NotifyComparisonResult(rv);
       return;
     }
 
-    // Write to Cache so ScriptLoader reads succeed.
-    WriteNetworkBufferToNewCache();
+    // Just to be safe.
+    RefPtr<Cache> kungfuDeathGrip = cache;
+    mState = WaitingForPut;
+
+    MOZ_ASSERT(mPendingCount == 0);
+    for (uint32_t i = 0; i < mCNs.Length(); ++i) {
+      WriteToCache(cache, mCNs[i]);
+    }
+    return;
   }
 
   void
   WriteNetworkBufferToNewCache()
   {
     AssertIsOnMainThread();
-    MOZ_ASSERT(mCN);
+    MOZ_ASSERT(mCNs.Length() != 0);
     MOZ_ASSERT(mCacheStorage);
     MOZ_ASSERT(mNewCacheName.IsEmpty());
 
     ErrorResult result;
     result = serviceWorkerScriptCache::GenerateCacheName(mNewCacheName);
     if (NS_WARN_IF(result.Failed())) {
       MOZ_ASSERT(!result.IsErrorWithMessage());
-      Fail(result.StealNSResult());
+      NotifyComparisonResult(result.StealNSResult());
       return;
     }
 
     RefPtr<Promise> cacheOpenPromise = mCacheStorage->Open(mNewCacheName, result);
     if (NS_WARN_IF(result.Failed())) {
       MOZ_ASSERT(!result.IsErrorWithMessage());
-      Fail(result.StealNSResult());
+      NotifyComparisonResult(result.StealNSResult());
       return;
     }
 
     cacheOpenPromise->AppendNativeHandler(this);
   }
 
   void
-  WriteToCache(Cache* aCache)
+  WriteToCache(Cache* aCache, CompareNetwork* aCN)
   {
     AssertIsOnMainThread();
     MOZ_ASSERT(aCache);
-    MOZ_ASSERT(mState == WaitingForOpen);
+    MOZ_ASSERT(aCN);
+    MOZ_ASSERT(mState == WaitingForPut);
+
+    if (!aCN->Succeeded()) {
+      return;
+    }
 
     ErrorResult result;
     nsCOMPtr<nsIInputStream> body;
     result = NS_NewCStringInputStream(getter_AddRefs(body),
-                                      NS_ConvertUTF16toUTF8(mCN->Buffer()));
+                                      NS_ConvertUTF16toUTF8(aCN->Buffer()));
     if (NS_WARN_IF(result.Failed())) {
       MOZ_ASSERT(!result.IsErrorWithMessage());
-      Fail(result.StealNSResult());
+      NotifyComparisonResult(result.StealNSResult());
       return;
     }
 
     RefPtr<InternalResponse> ir =
       new InternalResponse(200, NS_LITERAL_CSTRING("OK"));
-    ir->SetBody(body, mCN->Buffer().Length());
+    ir->SetBody(body, aCN->Buffer().Length());
 
-    ir->InitChannelInfo(mChannelInfo);
-    if (mPrincipalInfo) {
-      ir->SetPrincipalInfo(Move(mPrincipalInfo));
+    ir->InitChannelInfo(aCN->GetChannelInfo());
+    UniquePtr<PrincipalInfo> principalInfo = aCN->TakePrincipalInfo();
+    if (principalInfo) {
+      ir->SetPrincipalInfo(Move(principalInfo));
     }
 
     IgnoredErrorResult ignored;
-    ir->Headers()->Fill(*mInternalHeaders, ignored);
+    RefPtr<InternalHeaders> internalHeaders = aCN->GetInternalHeaders();
+    ir->Headers()->Fill(*(internalHeaders.get()), ignored);
 
     RefPtr<Response> response = new Response(aCache->GetGlobalObject(), ir);
 
     RequestOrUSVString request;
-    request.SetAsUSVString().Rebind(URL().Data(), URL().Length());
+    request.SetAsUSVString().Rebind(aCN->URL().Data(), URL().Length());
 
     // For now we have to wait until the Put Promise is fulfilled before we can
     // continue since Cache does not yet support starting a read that is being
     // written to.
     RefPtr<Promise> cachePromise = aCache->Put(request, *response, result);
     if (NS_WARN_IF(result.Failed())) {
       MOZ_ASSERT(!result.IsErrorWithMessage());
-      Fail(result.StealNSResult());
+      NotifyComparisonResult(result.StealNSResult());
       return;
     }
 
-    mState = WaitingForPut;
+    mPendingCount += 1;
     cachePromise->AppendNativeHandler(this);
   }
 
   RefPtr<ServiceWorkerRegistrationInfo> mRegistration;
   RefPtr<CompareCallback> mCallback;
   JS::PersistentRooted<JSObject*> mSandbox;
   RefPtr<CacheStorage> mCacheStorage;
 
-  RefPtr<CompareNetwork> mCN;
-  RefPtr<CompareCache> mCC;
+  nsTArray<RefPtr<CompareNetwork>> mCNs;
 
   nsString mURL;
+  RefPtr<nsIPrincipal> mPrincipal;
+  RefPtr<nsILoadGroup> mLoadGroup;
+
+  // Used for the old cache where saves the old source scripts.
+  nsString mOldCacheName;
+  RefPtr<Cache> mOldCache;
+
   // Only used if the network script has changed and needs to be cached.
   nsString mNewCacheName;
 
-  ChannelInfo mChannelInfo;
-  RefPtr<InternalHeaders> mInternalHeaders;
+  enum {
+    WaitingForInitialization,
+    WaitingForExistingOpen,
+    WaitingForExistingKeys,
+    WaitingForScriptOrComparisonResult,
+    WaitingForOpen,
+    WaitingForPut,
+    Redundant
+  } mState;
 
-  UniquePtr<mozilla::ipc::PrincipalInfo> mPrincipalInfo;
+  uint32_t mPendingCount;
+  bool mAreScriptsEqual;
 
   nsCString mMaxScope;
-
-  enum {
-    WaitingForOpen,
-    WaitingForPut
-  } mState;
-
-  bool mNetworkFinished;
-  bool mCacheFinished;
-  bool mInCache;
+  nsLoadFlags mLoadFlags;
 };
 
 NS_IMPL_ISUPPORTS0(CompareManager)
 
-nsresult
-CompareNetwork::Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL, nsILoadGroup* aLoadGroup)
+void
+CompareNetwork::Initialize(nsIPrincipal* aPrincipal,
+                           const nsAString& aURL,
+                           bool aIsMainScript,
+                           nsILoadGroup* aLoadGroup,
+                           Cache* const aCache)
 {
+  AssertIsOnMainThread();
   MOZ_ASSERT(aPrincipal);
-  AssertIsOnMainThread();
+  MOZ_ASSERT(mState == WaitingForInitialization);
+
+  // RAII error notifier
+  nsresult rv = NS_ERROR_FAILURE;
+  auto guard = MakeScopeExit([&] {
+      NetworkFinished(rv);
+  });
+
+  mURL = aURL;
 
   nsCOMPtr<nsIURI> uri;
-  nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, nullptr, nullptr);
+  rv = NS_NewURI(getter_AddRefs(uri), aURL, nullptr, nullptr);
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
+    return;
   }
 
+  mIsMainScript = aIsMainScript;
+
   nsCOMPtr<nsILoadGroup> loadGroup;
   rv = NS_NewLoadGroup(getter_AddRefs(loadGroup), aPrincipal);
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
+    return;
   }
 
-  nsLoadFlags flags = nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
+  // Update mLoadFlags, which is passed to script loaders later.
   RefPtr<ServiceWorkerRegistrationInfo> registration =
     mManager->GetRegistration();
-  flags |= registration->GetLoadFlags();
+  mLoadFlags |= registration->GetLoadFlags();
   if (registration->IsLastUpdateCheckTimeOverOneDay()) {
-    flags |= nsIRequest::LOAD_BYPASS_CACHE;
+    mLoadFlags |= nsIRequest::LOAD_BYPASS_CACHE;
   }
 
-  // Save the load flags for propagating to ServiceWorkerInfo.
-  mManager->SaveLoadFlags(flags);
-
   // Note that because there is no "serviceworker" RequestContext type, we can
   // use the TYPE_INTERNAL_SCRIPT content policy types when loading a service
   // worker.
   rv = NS_NewChannel(getter_AddRefs(mChannel),
                      uri, aPrincipal,
                      nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED,
                      nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER,
                      loadGroup,
                      nullptr, // aCallbacks
-                     flags);
+                     mLoadFlags);
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
+    return;
   }
 
   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
   if (httpChannel) {
     // Spec says no redirects allowed for SW scripts.
     rv = httpChannel->SetRedirectionLimit(0);
     MOZ_ASSERT(NS_SUCCEEDED(rv));
 
@@ -678,25 +734,122 @@ CompareNetwork::Initialize(nsIPrincipal*
                                        NS_LITERAL_CSTRING("script"),
                                        /* merge */ false);
     MOZ_ASSERT(NS_SUCCEEDED(rv));
   }
 
   nsCOMPtr<nsIStreamLoader> loader;
   rv = NS_NewStreamLoader(getter_AddRefs(loader), this, this);
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
+    return;
   }
 
   rv = mChannel->AsyncOpen2(loader);
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
+    return;
+  }
+
+  // If we don't have an existing cache to compare with.
+  if (!aCache) {
+    mState = WaitingForNetworkFinished;
+
+    guard.release();
+    return;
+  }
+
+  mCC = new CompareCache(this, aURL, aCache);
+  mState = WaitingForBothFinished;
+
+  guard.release();
+}
+
+void
+CompareNetwork::Finished()
+{
+  MOZ_ASSERT(mState != Redundant);
+  mState = Redundant;
+
+  bool same = true;
+  nsresult rv = NS_OK;
+
+  // mNetworkResult is prior to mCacheResult, since it's needed for reporting
+  // various error to the web contenet.
+  if (NS_FAILED(mNetworkResult)) {
+    // An imported script could become offline, since it might no longer be
+    // needed by the new importing script. In that case, the importing script
+    // must be different, and thus, it's okay to report same script found here.
+    rv = mIsMainScript ? mNetworkResult : NS_OK;
+    same = true;
+  } else if (mCC && NS_FAILED(mCacheResult)) {
+    rv = mCacheResult;
+  } else { // Both passed.
+    same = mCC &&
+           mCC->InCache() &&
+           mCC->Buffer().Equals(mBuffer);
   }
 
-  return NS_OK;
+  mManager->ComparisonFinished(rv, same, mIsMainScript, mMaxScope, mLoadFlags);
+
+  mCC = nullptr;
+}
+
+void
+CompareNetwork::NetworkFinished(nsresult aRv)
+{
+  mNetworkResult = aRv;
+
+  if (mState == WaitingForBothFinished) {
+    mState = WaitingForCacheFinished;
+    return;
+  }
+
+  if (mState == WaitingForInitialization ||
+      mState == WaitingForNetworkFinished) {
+    Finished();
+    return;
+  }
+
+  MOZ_CRASH("Unacceptable state.");
+}
+
+void
+CompareNetwork::CacheFinished(nsresult aRv)
+{
+  mCacheResult = aRv;
+
+  if (mState == WaitingForBothFinished) {
+    mState = WaitingForNetworkFinished;
+    return;
+  }
+
+  if (mState == WaitingForCacheFinished) {
+    Finished();
+    return;
+  }
+
+  MOZ_CRASH("Unacceptable state.");
+}
+
+void
+CompareNetwork::Abort()
+{
+  AssertIsOnMainThread();
+
+  if (mState != Redundant) {
+    mState = Redundant;
+
+    MOZ_ASSERT(mChannel);
+    mChannel->Cancel(NS_BINDING_ABORTED);
+    mChannel = nullptr;
+
+    if (mCC) {
+      mCC->Abort();
+      mCC = nullptr;
+    }
+  }
 }
 
 NS_IMETHODIMP
 CompareNetwork::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
 {
   AssertIsOnMainThread();
 
   // If no channel, Abort() has been called.
@@ -704,21 +857,49 @@ CompareNetwork::OnStartRequest(nsIReques
     return NS_OK;
   }
 
 #ifdef DEBUG
   nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
   MOZ_ASSERT(channel == mChannel);
 #endif
 
-  nsresult rv = mManager->OnStartRequest(mChannel);
+  MOZ_ASSERT(!mChannelInfo.IsInitialized());
+  mChannelInfo.InitFromChannel(mChannel);
+
+  nsresult rv = SetPrincipalInfo(mChannel);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  mInternalHeaders->FillResponseHeaders(mChannel);
+
+  return NS_OK;
+}
+
+nsresult
+CompareNetwork::SetPrincipalInfo(nsIChannel* aChannel)
+{
+  nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+  NS_ASSERTION(ssm, "Should never be null!");
+
+  nsCOMPtr<nsIPrincipal> channelPrincipal;
+  nsresult rv = ssm->GetChannelResultPrincipal(aChannel, getter_AddRefs(channelPrincipal));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  UniquePtr<PrincipalInfo> principalInfo = MakeUnique<PrincipalInfo>();
+  rv = PrincipalToPrincipalInfo(channelPrincipal, principalInfo.get());
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  mPrincipalInfo = Move(principalInfo);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 CompareNetwork::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext,
                               nsresult aStatusCode)
 {
   // Nothing to do here!
@@ -734,64 +915,64 @@ CompareNetwork::OnStreamComplete(nsIStre
 
   // If no channel, Abort() has been called.
   if (!mChannel) {
     return NS_OK;
   }
 
   if (NS_WARN_IF(NS_FAILED(aStatus))) {
     if (aStatus == NS_ERROR_REDIRECT_LOOP) {
-      mManager->NetworkFinished(NS_ERROR_DOM_SECURITY_ERR);
+      NetworkFinished(NS_ERROR_DOM_SECURITY_ERR);
     } else {
-      mManager->NetworkFinished(aStatus);
+      NetworkFinished(aStatus);
     }
     return NS_OK;
   }
 
   nsCOMPtr<nsIRequest> request;
   nsresult rv = aLoader->GetRequest(getter_AddRefs(request));
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    mManager->NetworkFinished(rv);
+    NetworkFinished(rv);
     return NS_OK;
   }
 
   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request);
   MOZ_ASSERT(httpChannel, "How come we don't have an HTTP channel?");
 
   bool requestSucceeded;
   rv = httpChannel->GetRequestSucceeded(&requestSucceeded);
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    mManager->NetworkFinished(rv);
+    NetworkFinished(rv);
     return NS_OK;
   }
 
   if (NS_WARN_IF(!requestSucceeded)) {
     // Get the stringified numeric status code, not statusText which could be
     // something misleading like OK for a 404.
     uint32_t status = 0;
     Unused << httpChannel->GetResponseStatus(&status); // don't care if this fails, use 0.
     nsAutoString statusAsText;
     statusAsText.AppendInt(status);
 
     RefPtr<ServiceWorkerRegistrationInfo> registration = mManager->GetRegistration();
     ServiceWorkerManager::LocalizeAndReportToAllClients(
       registration->mScope, "ServiceWorkerRegisterNetworkError",
       nsTArray<nsString> { NS_ConvertUTF8toUTF16(registration->mScope),
         statusAsText, mManager->URL() });
-    mManager->NetworkFinished(NS_ERROR_FAILURE);
+    NetworkFinished(NS_ERROR_FAILURE);
     return NS_OK;
   }
 
-  nsAutoCString maxScope;
-  // Note: we explicitly don't check for the return value here, because the
-  // absence of the header is not an error condition.
-  Unused << httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Service-Worker-Allowed"),
-                                           maxScope);
-
-  mManager->SetMaxScope(maxScope);
+  if (mIsMainScript) {
+    // Note: we explicitly don't check for the return value here, because the
+    // absence of the header is not an error condition.
+    Unused << httpChannel->GetResponseHeader(
+        NS_LITERAL_CSTRING("Service-Worker-Allowed"),
+        mMaxScope);
+  }
 
   bool isFromCache = false;
   nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(httpChannel));
   if (cacheChannel) {
     cacheChannel->IsFromCache(&isFromCache);
   }
 
   // [9.2 Update]4.13, If response's cache state is not "local",
@@ -804,218 +985,393 @@ CompareNetwork::OnStreamComplete(nsIStre
 
   nsAutoCString mimeType;
   rv = httpChannel->GetContentType(mimeType);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     // We should only end up here if !mResponseHead in the channel.  If headers
     // were received but no content type was specified, we'll be given
     // UNKNOWN_CONTENT_TYPE "application/x-unknown-content-type" and so fall
     // into the next case with its better error message.
-    mManager->NetworkFinished(NS_ERROR_DOM_SECURITY_ERR);
+    NetworkFinished(NS_ERROR_DOM_SECURITY_ERR);
     return rv;
   }
 
   if (!mimeType.LowerCaseEqualsLiteral("text/javascript") &&
       !mimeType.LowerCaseEqualsLiteral("application/x-javascript") &&
       !mimeType.LowerCaseEqualsLiteral("application/javascript")) {
     RefPtr<ServiceWorkerRegistrationInfo> registration = mManager->GetRegistration();
     ServiceWorkerManager::LocalizeAndReportToAllClients(
       registration->mScope, "ServiceWorkerRegisterMimeTypeError",
       nsTArray<nsString> { NS_ConvertUTF8toUTF16(registration->mScope),
         NS_ConvertUTF8toUTF16(mimeType), mManager->URL() });
-    mManager->NetworkFinished(NS_ERROR_DOM_SECURITY_ERR);
+    NetworkFinished(NS_ERROR_DOM_SECURITY_ERR);
     return rv;
   }
 
   char16_t* buffer = nullptr;
   size_t len = 0;
 
   rv = ScriptLoader::ConvertToUTF16(httpChannel, aString, aLen,
                                     NS_LITERAL_STRING("UTF-8"), nullptr,
                                     buffer, len);
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    mManager->NetworkFinished(rv);
+    NetworkFinished(rv);
     return rv;
   }
 
   mBuffer.Adopt(buffer, len);
 
-  mManager->NetworkFinished(NS_OK);
+  NetworkFinished(NS_OK);
   return NS_OK;
 }
 
-nsresult
-CompareCache::Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL,
-                         const nsAString& aCacheName)
+void
+CompareCache::Initialize(const nsAString& aURL,
+                         Cache* const aCache)
 {
-  MOZ_ASSERT(aPrincipal);
+  MOZ_ASSERT(aCache);
+  MOZ_ASSERT(mState == WaitingForInitialization);
   AssertIsOnMainThread();
 
+  // RAII error notifier
+  nsresult rv = NS_ERROR_FAILURE;
+  auto guard = MakeScopeExit([&] {
+    Finished(rv);
+  });
+
   mURL = aURL;
 
-  ErrorResult rv;
-
-  RefPtr<Promise> promise = mManager->CacheStorage_()->Open(aCacheName, rv);
-  if (NS_WARN_IF(rv.Failed())) {
-    MOZ_ASSERT(!rv.IsErrorWithMessage());
-    return rv.StealNSResult();
+  RequestOrUSVString request;
+  request.SetAsUSVString().Rebind(mURL.Data(), mURL.Length());
+  ErrorResult error;
+  CacheQueryOptions params;
+  RefPtr<Promise> promise = aCache->Match(request, params, error);
+  if (NS_WARN_IF(error.Failed())) {
+    rv = error.StealNSResult();
+    return;
   }
 
+  mState = WaitingForValue;
   promise->AppendNativeHandler(this);
-  return NS_OK;
+
+  guard.release();
+}
+
+void
+CompareCache::Finished(nsresult aStatus, bool aInCache)
+{
+  if (mState != Redundant) {
+    mState = Redundant;
+    mInCache = aInCache;
+    mCN->CacheFinished(aStatus);
+  }
+}
+
+void
+CompareCache::Abort()
+{
+  AssertIsOnMainThread();
+
+  if (mState != Redundant) {
+    mState = Redundant;
+
+    if (mPump) {
+      mPump->Cancel(NS_BINDING_ABORTED);
+      mPump = nullptr;
+    }
+
+    mCN = nullptr;
+  }
 }
 
 NS_IMETHODIMP
 CompareCache::OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext,
                                nsresult aStatus, uint32_t aLen,
                                const uint8_t* aString)
 {
   AssertIsOnMainThread();
 
-  if (mAborted) {
+  if (mState == Redundant) {
     return aStatus;
   }
 
   if (NS_WARN_IF(NS_FAILED(aStatus))) {
-    mManager->CacheFinished(aStatus, false);
+    Finished(aStatus, false);
     return aStatus;
   }
 
   char16_t* buffer = nullptr;
   size_t len = 0;
 
   nsresult rv = ScriptLoader::ConvertToUTF16(nullptr, aString, aLen,
                                              NS_LITERAL_STRING("UTF-8"),
                                              nullptr, buffer, len);
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    mManager->CacheFinished(rv, false);
+    Finished(rv, false);
     return rv;
   }
 
   mBuffer.Adopt(buffer, len);
 
-  mManager->CacheFinished(NS_OK, true);
+  Finished(NS_OK, true);
   return NS_OK;
 }
 
+// This class manages only 1 promise: For the value from the cache.
+void
+CompareCache::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
+{
+  AssertIsOnMainThread();
+
+  if (mState == Redundant) {
+    return;
+  }
+
+  MOZ_ASSERT(mState == WaitingForValue);
+  ManageValueResult(aCx, aValue);
+}
+
 void
 CompareCache::RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
 {
   AssertIsOnMainThread();
 
-  if (mAborted) {
-    return;
-  }
-
-  mManager->CacheFinished(NS_ERROR_FAILURE, false);
-}
-
-void
-CompareCache::ManageCacheResult(JSContext* aCx, JS::Handle<JS::Value> aValue)
-{
-  AssertIsOnMainThread();
-
-  if (NS_WARN_IF(!aValue.isObject())) {
-    mManager->CacheFinished(NS_ERROR_FAILURE, false);
+  if (mState == Redundant) {
     return;
   }
 
-  JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
-  if (NS_WARN_IF(!obj)) {
-    mManager->CacheFinished(NS_ERROR_FAILURE, false);
-    return;
-  }
-
-  Cache* cache = nullptr;
-  nsresult rv = UNWRAP_OBJECT(Cache, obj, cache);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    mManager->CacheFinished(rv, false);
-    return;
-  }
-
-  RequestOrUSVString request;
-  request.SetAsUSVString().Rebind(mURL.Data(), mURL.Length());
-  ErrorResult error;
-  CacheQueryOptions params;
-  RefPtr<Promise> promise = cache->Match(request, params, error);
-  if (NS_WARN_IF(error.Failed())) {
-    mManager->CacheFinished(error.StealNSResult(), false);
-    return;
-  }
-
-  promise->AppendNativeHandler(this);
-  mState = WaitingForValue;
+  Finished(NS_ERROR_FAILURE, false);
 }
 
 void
 CompareCache::ManageValueResult(JSContext* aCx, JS::Handle<JS::Value> aValue)
 {
   AssertIsOnMainThread();
 
   // The cache returns undefined if the object is not stored.
   if (aValue.isUndefined()) {
-    mManager->CacheFinished(NS_OK, false);
+    Finished(NS_OK, false);
     return;
   }
 
   MOZ_ASSERT(aValue.isObject());
 
   JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
   if (NS_WARN_IF(!obj)) {
-    mManager->CacheFinished(NS_ERROR_FAILURE, false);
+    Finished(NS_ERROR_FAILURE, false);
     return;
   }
 
   Response* response = nullptr;
   nsresult rv = UNWRAP_OBJECT(Response, obj, response);
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    mManager->CacheFinished(rv, false);
+    Finished(rv, false);
     return;
   }
 
   MOZ_ASSERT(response->Ok());
 
   nsCOMPtr<nsIInputStream> inputStream;
   response->GetBody(getter_AddRefs(inputStream));
   MOZ_ASSERT(inputStream);
 
   MOZ_ASSERT(!mPump);
   rv = NS_NewInputStreamPump(getter_AddRefs(mPump), inputStream);
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    mManager->CacheFinished(rv, false);
+    Finished(rv, false);
     return;
   }
 
   nsCOMPtr<nsIStreamLoader> loader;
   rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    mManager->CacheFinished(rv, false);
+    Finished(rv, false);
     return;
   }
 
   rv = mPump->AsyncRead(loader, nullptr);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     mPump = nullptr;
-    mManager->CacheFinished(rv, false);
+    Finished(rv, false);
     return;
   }
 
   nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(mPump);
   if (rr) {
     nsCOMPtr<nsIEventTarget> sts =
       do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
     rv = rr->RetargetDeliveryTo(sts);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       mPump = nullptr;
-      mManager->CacheFinished(rv, false);
+      Finished(rv, false);
       return;
     }
   }
 }
 
+void
+CompareManager::Initialize(nsIPrincipal* aPrincipal,
+                           const nsAString& aURL,
+                           const nsAString& aCacheName,
+                           nsILoadGroup* aLoadGroup)
+{
+  AssertIsOnMainThread();
+  MOZ_ASSERT(aPrincipal);
+  MOZ_ASSERT(mState == WaitingForInitialization);
+  MOZ_ASSERT(mPendingCount == 0);
+
+  // RAII error notifier
+  nsresult rv = NS_ERROR_FAILURE;
+  auto guard = MakeScopeExit([&] {
+      NotifyComparisonResult(rv);
+  });
+
+  mURL = aURL;
+  mPrincipal = aPrincipal;
+  mLoadGroup = aLoadGroup;
+  mOldCacheName = aCacheName;
+
+  // Always create a CacheStorage since we want to write the network entry to
+  // the cache even if there isn't an existing one.
+  AutoJSAPI jsapi;
+  jsapi.Init();
+  ErrorResult result;
+  mSandbox.init(jsapi.cx());
+  mCacheStorage = CreateCacheStorage(jsapi.cx(), aPrincipal, result, &mSandbox);
+  if (NS_WARN_IF(result.Failed())) {
+    MOZ_ASSERT(!result.IsErrorWithMessage());
+    rv = result.StealNSResult();
+    return;
+  }
+
+  // Open the cache saving the old source scripts.
+  if (!mOldCacheName.IsEmpty()) {
+    RefPtr<Promise> promise = mCacheStorage->Open(mOldCacheName, result);
+    if (NS_WARN_IF(result.Failed())) {
+      MOZ_ASSERT(!result.IsErrorWithMessage());
+      rv = result.StealNSResult();
+      return;
+    }
+
+    mState = WaitingForExistingOpen;
+    promise->AppendNativeHandler(this);
+
+    guard.release();
+    return;
+  }
+
+  // Go fetch the script directly without comparison.
+  mState = WaitingForScriptOrComparisonResult;
+  FetchScript(mURL, true /* aIsMainScript */, nullptr);
+
+  guard.release();
+}
+
+// This class manages 4 promises if needed:
+// 1. Retrieve the Cache object by a given CacheName of OldCache.
+// 2. Retrieve the URLs saved in OldCache.
+// 3. Retrieve the Cache object of the NewCache for the newly created SW.
+// 4. Put the value in the cache.
+// For this reason we have mState to know what callback we are handling.
+void
+CompareManager::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
+{
+  AssertIsOnMainThread();
+  MOZ_ASSERT(mCallback);
+
+  if (mState == WaitingForExistingOpen) {
+    ManageOldCache(aCx, aValue);
+    return;
+  }
+
+  if (mState == WaitingForExistingKeys) {
+    ManageOldKeys(aCx, aValue);
+    return;
+  }
+
+  if (mState == WaitingForOpen) {
+    ManageNewCache(aCx, aValue);
+    return;
+  }
+
+  MOZ_ASSERT(mState == WaitingForPut);
+  MOZ_DIAGNOSTIC_ASSERT(mPendingCount > 0);
+  if (--mPendingCount) {
+    return;
+  }
+
+  NotifyComparisonResult(NS_OK,
+                         false /* aSameScripts */,
+                         mNewCacheName,
+                         mMaxScope,
+                         mLoadFlags);
+}
+
+void
+CompareManager::RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
+{
+  AssertIsOnMainThread();
+  if (mState == WaitingForExistingKeys) {
+    NS_WARNING("Could not get the existing URLs.");
+  } else if (mState == WaitingForExistingKeys){
+    NS_WARNING("Could not get the existing URLs.");
+  } else if (mState == WaitingForOpen) {
+    NS_WARNING("Could not open cache.");
+  } else {
+    NS_WARNING("Could not write to cache.");
+  }
+  NotifyComparisonResult(NS_ERROR_FAILURE);
+}
+
+void
+CompareManager::NotifyComparisonResult(nsresult aState)
+{
+  MOZ_ASSERT(NS_FAILED(aState));
+  NotifyComparisonResult(aState,
+                         false /* aSameScripts*/,
+                         EmptyString(),
+                         EmptyCString(),
+                         nsIRequest::LOAD_NORMAL);
+}
+
+void
+CompareManager::NotifyComparisonResult(nsresult aStatus,
+                                       bool aSameScripts,
+                                       const nsAString& aNewCacheName,
+                                       const nsACString& aMaxScope,
+                                       nsLoadFlags aLoadFlags)
+{
+  AssertIsOnMainThread();
+
+  // If mState is Redundant, the comparison result is sent out and this
+  // CompareManager cleaned up before. Thus, there is nothing left to do here.
+  if (mState == Redundant) {
+    return;
+  }
+
+  // Mark this CompareManager is Redundant.
+  mState = Redundant;
+
+  // Send the result to mCallBack.
+  MOZ_ASSERT(mCallback);
+  mCallback->ComparisonResult(aStatus,
+                              aSameScripts,
+                              aNewCacheName,
+                              aMaxScope,
+                              aLoadFlags);
+  mCallback = nullptr;
+
+  // Abort and release CompareNetworks.
+  MOZ_ASSERT(mCNs.Length());
+  for (uint32_t i = 0; i < mCNs.Length(); ++i) {
+    mCNs[i]->Abort();
+  }
+  mCNs.Clear();
+}
+
 } // namespace
 
 nsresult
 PurgeCache(nsIPrincipal* aPrincipal, const nsAString& aCacheName)
 {
   AssertIsOnMainThread();
   MOZ_ASSERT(aPrincipal);
 
@@ -1063,33 +1419,31 @@ GenerateCacheName(nsAString& aName)
   id.ToProvidedString(chars);
 
   // NSID_LENGTH counts the null terminator.
   aName.AssignASCII(chars, NSID_LENGTH - 1);
 
   return NS_OK;
 }
 
-nsresult
+void
 Compare(ServiceWorkerRegistrationInfo* aRegistration,
         nsIPrincipal* aPrincipal, const nsAString& aCacheName,
         const nsAString& aURL, CompareCallback* aCallback,
         nsILoadGroup* aLoadGroup)
 {
   AssertIsOnMainThread();
   MOZ_ASSERT(aRegistration);
   MOZ_ASSERT(aPrincipal);
   MOZ_ASSERT(!aURL.IsEmpty());
   MOZ_ASSERT(aCallback);
 
-  RefPtr<CompareManager> cm = new CompareManager(aRegistration, aCallback);
-
-  nsresult rv = cm->Initialize(aPrincipal, aURL, aCacheName, aLoadGroup);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  return NS_OK;
+  RefPtr<CompareManager> cm = new CompareManager(aRegistration,
+                                                 aCallback,
+                                                 aPrincipal,
+                                                 aURL,
+                                                 aCacheName,
+                                                 aLoadGroup);
 }
 
 } // namespace serviceWorkerScriptCache
 
 END_WORKERS_NAMESPACE
--- a/dom/workers/ServiceWorkerScriptCache.h
+++ b/dom/workers/ServiceWorkerScriptCache.h
@@ -34,31 +34,23 @@ public:
    * On success, if the cached result and network result matched,
    * aInCacheAndEqual will be true and no new cache name is passed, otherwise
    * use the new cache name to load the ServiceWorker.
    */
   virtual void
   ComparisonResult(nsresult aStatus,
                    bool aInCacheAndEqual,
                    const nsAString& aNewCacheName,
-                   const nsACString& aMaxScope) = 0;
-
-  /*
-   * Right before fetching the main script from the network, we check whether
-   * the script expiration timer has expired. Via this method, we can save the
-   * result of the check, and propogate it to the new ServiceWorkerInfo if there
-   * is one, so the imported scripts can be affected by the result as well.
-   */
-  virtual void
-  SaveLoadFlags(nsLoadFlags aLoadFlags) = 0;
+                   const nsACString& aMaxScope,
+                   nsLoadFlags aLoadFlags) = 0;
 
   NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
 };
 
-nsresult
+void
 Compare(ServiceWorkerRegistrationInfo* aRegistration,
         nsIPrincipal* aPrincipal, const nsAString& aCacheName,
         const nsAString& aURL, CompareCallback* aCallback, nsILoadGroup* aLoadGroup);
 
 } // namespace serviceWorkerScriptCache
 
 } // namespace workers
 } // namespace dom
--- a/dom/workers/ServiceWorkerUpdateJob.cpp
+++ b/dom/workers/ServiceWorkerUpdateJob.cpp
@@ -93,25 +93,24 @@ public:
   {
     MOZ_ASSERT(mJob);
   }
 
   virtual void
   ComparisonResult(nsresult aStatus,
                    bool aInCacheAndEqual,
                    const nsAString& aNewCacheName,
-                   const nsACString& aMaxScope) override
+                   const nsACString& aMaxScope,
+                   nsLoadFlags aLoadFlags) override
   {
-    mJob->ComparisonResult(aStatus, aInCacheAndEqual, aNewCacheName, aMaxScope);
-  }
-
-  virtual void
-  SaveLoadFlags(nsLoadFlags aLoadFlags) override
-  {
-    mJob->SetLoadFlags(aLoadFlags);
+    mJob->ComparisonResult(aStatus,
+                           aInCacheAndEqual,
+                           aNewCacheName,
+                           aMaxScope,
+                           aLoadFlags);
   }
 
   NS_INLINE_DECL_REFCOUNTING(ServiceWorkerUpdateJob::CompareCallback, override)
 };
 
 class ServiceWorkerUpdateJob::ContinueUpdateRunnable final : public LifeCycleEventCallback
 {
   nsMainThreadPtrHandle<ServiceWorkerUpdateJob> mJob;
@@ -313,43 +312,33 @@ ServiceWorkerUpdateJob::Update()
   // If the script has not changed, we need to perform a byte-for-byte
   // comparison.
   if (workerInfo && workerInfo->ScriptSpec().Equals(mScriptSpec)) {
     cacheName = workerInfo->CacheName();
   }
 
   RefPtr<CompareCallback> callback = new CompareCallback(this);
 
-  nsresult rv =
-    serviceWorkerScriptCache::Compare(mRegistration, mPrincipal, cacheName,
-                                      NS_ConvertUTF8toUTF16(mScriptSpec),
-                                      callback, mLoadGroup);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    FailUpdateJob(rv);
-    return;
-  }
+  serviceWorkerScriptCache::Compare(mRegistration, mPrincipal, cacheName,
+                                    NS_ConvertUTF8toUTF16(mScriptSpec),
+                                    callback, mLoadGroup);
 }
 
 nsLoadFlags
 ServiceWorkerUpdateJob::GetLoadFlags() const
 {
   return mLoadFlags;
 }
 
 void
-ServiceWorkerUpdateJob::SetLoadFlags(nsLoadFlags aLoadFlags)
-{
-  mLoadFlags = aLoadFlags;
-}
-
-void
 ServiceWorkerUpdateJob::ComparisonResult(nsresult aStatus,
                                          bool aInCacheAndEqual,
                                          const nsAString& aNewCacheName,
-                                         const nsACString& aMaxScope)
+                                         const nsACString& aMaxScope,
+                                         nsLoadFlags aLoadFlags)
 {
   AssertIsOnMainThread();
 
   RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
   if (NS_WARN_IF(Canceled() || !swm)) {
     FailUpdateJob(NS_ERROR_DOM_ABORT_ERR);
     return;
   }
@@ -434,17 +423,17 @@ ServiceWorkerUpdateJob::ComparisonResult
 
   // Begin step 7 of the Update algorithm to evaluate the new script.
 
   RefPtr<ServiceWorkerInfo> sw =
     new ServiceWorkerInfo(mRegistration->mPrincipal,
                           mRegistration->mScope,
                           mScriptSpec,
                           aNewCacheName,
-                          mLoadFlags);
+                          aLoadFlags);
 
   mRegistration->SetEvaluating(sw);
 
   nsMainThreadPtrHandle<ServiceWorkerUpdateJob> handle(
       new nsMainThreadPtrHolder<ServiceWorkerUpdateJob>(this));
   RefPtr<LifeCycleEventCallback> callback = new ContinueUpdateRunnable(handle);
 
   ServiceWorkerPrivate* workerPrivate = sw->WorkerPrivate();
--- a/dom/workers/ServiceWorkerUpdateJob.h
+++ b/dom/workers/ServiceWorkerUpdateJob.h
@@ -68,31 +68,29 @@ protected:
   // AsyncExecute() to complete the job.  The Update() method will always call
   // Finish().  This method corresponds to the spec Update algorithm.
   void
   Update();
 
   nsLoadFlags
   GetLoadFlags() const;
 
-  void
-  SetLoadFlags(nsLoadFlags aLoadFlags);
-
 private:
   class CompareCallback;
   class ContinueUpdateRunnable;
   class ContinueInstallRunnable;
 
   // Utility method called after a script is loaded and compared to
   // our current cached script.
   void
   ComparisonResult(nsresult aStatus,
                    bool aInCacheAndEqual,
                    const nsAString& aNewCacheName,
-                   const nsACString& aMaxScope);
+                   const nsACString& aMaxScope,
+                   nsLoadFlags aLoadFlags);
 
   // Utility method called after evaluating the worker script.
   void
   ContinueUpdateAfterScriptEval(bool aScriptEvaluationResult);
 
   // Utility method corresponding to the spec Install algorithm.
   void
   Install(ServiceWorkerManager* aSWM);
--- a/dom/workers/WorkerHolder.cpp
+++ b/dom/workers/WorkerHolder.cpp
@@ -4,18 +4,19 @@
  * 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 "WorkerHolder.h"
 #include "WorkerPrivate.h"
 
 BEGIN_WORKERS_NAMESPACE
 
-WorkerHolder::WorkerHolder()
+WorkerHolder::WorkerHolder(Behavior aBehavior)
   : mWorkerPrivate(nullptr)
+  , mBehavior(aBehavior)
 {
 }
 
 WorkerHolder::~WorkerHolder()
 {
   NS_ASSERT_OWNINGTHREAD(WorkerHolder);
   ReleaseWorkerInternal();
   MOZ_ASSERT(mWorkerPrivate == nullptr);
@@ -40,16 +41,22 @@ void
 WorkerHolder::ReleaseWorker()
 {
   NS_ASSERT_OWNINGTHREAD(WorkerHolder);
   MOZ_ASSERT(mWorkerPrivate);
 
   ReleaseWorkerInternal();
 }
 
+WorkerHolder::Behavior
+WorkerHolder::GetBehavior() const
+{
+  return mBehavior;
+}
+
 void
 WorkerHolder::ReleaseWorkerInternal()
 {
   NS_ASSERT_OWNINGTHREAD(WorkerHolder);
 
   if (mWorkerPrivate) {
     mWorkerPrivate->AssertIsOnWorkerThread();
     mWorkerPrivate->RemoveHolder(this);
--- a/dom/workers/WorkerHolder.h
+++ b/dom/workers/WorkerHolder.h
@@ -69,28 +69,37 @@ enum Status
   Dead
 };
 
 class WorkerHolder
 {
 public:
   NS_DECL_OWNINGTHREAD
 
-  WorkerHolder();
+  enum Behavior {
+    AllowIdleShutdownStart,
+    PreventIdleShutdownStart,
+  };
+
+  explicit WorkerHolder(Behavior aBehavior = PreventIdleShutdownStart);
   virtual ~WorkerHolder();
 
   bool HoldWorker(WorkerPrivate* aWorkerPrivate, Status aFailStatus);
   void ReleaseWorker();
 
   virtual bool Notify(Status aStatus) = 0;
 
+  Behavior GetBehavior() const;
+
 protected:
   void ReleaseWorkerInternal();
 
   WorkerPrivate* MOZ_NON_OWNING_REF mWorkerPrivate;
 
 private:
   void AssertIsOwningThread() const;
+
+  const Behavior mBehavior;
 };
 
 END_WORKERS_NAMESPACE
 
 #endif /* mozilla_dom_workers_WorkerHolder_h */
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -4424,16 +4424,17 @@ WorkerPrivate::WorkerPrivate(WorkerPriva
                              WorkerLoadInfo& aLoadInfo)
   : WorkerPrivateParent<WorkerPrivate>(aParent, aScriptURL,
                                        aIsChromeWorker, aWorkerType,
                                        aWorkerName, aLoadInfo)
   , mDebuggerRegistered(false)
   , mDebugger(nullptr)
   , mJSContext(nullptr)
   , mPRThread(nullptr)
+  , mNumHoldersPreventingShutdownStart(0)
   , mDebuggerEventLoopLevel(0)
   , mMainThreadEventTarget(do_GetMainThread())
   , mWorkerControlEventTarget(new WorkerControlEventTarget(this))
   , mErrorHandlerRecursionCount(0)
   , mNextTimeoutId(1)
   , mStatus(Pending)
   , mFrozen(false)
   , mTimerRunning(false)
@@ -5642,34 +5643,40 @@ WorkerPrivate::AddHolder(WorkerHolder* a
 
     if (mStatus >= aFailStatus) {
       return false;
     }
   }
 
   MOZ_ASSERT(!mHolders.Contains(aHolder), "Already know about this one!");
 
-  if (mHolders.IsEmpty() && !ModifyBusyCountFromWorker(true)) {
-    return false;
+  if (aHolder->GetBehavior() == WorkerHolder::PreventIdleShutdownStart) {
+    if (!mNumHoldersPreventingShutdownStart && !ModifyBusyCountFromWorker(true)) {
+      return false;
+    }
+    mNumHoldersPreventingShutdownStart += 1;
   }
 
   mHolders.AppendElement(aHolder);
   return true;
 }
 
 void
 WorkerPrivate::RemoveHolder(WorkerHolder* aHolder)
 {
   AssertIsOnWorkerThread();
 
   MOZ_ASSERT(mHolders.Contains(aHolder), "Didn't know about this one!");
   mHolders.RemoveElement(aHolder);
 
-  if (mHolders.IsEmpty() && !ModifyBusyCountFromWorker(false)) {
-    NS_WARNING("Failed to modify busy count!");
+  if (aHolder->GetBehavior() == WorkerHolder::PreventIdleShutdownStart) {
+    mNumHoldersPreventingShutdownStart -= 1;
+    if (!mNumHoldersPreventingShutdownStart && !ModifyBusyCountFromWorker(false)) {
+      NS_WARNING("Failed to modify busy count!");
+    }
   }
 }
 
 void
 WorkerPrivate::NotifyHolders(JSContext* aCx, Status aStatus)
 {
   AssertIsOnWorkerThread();
   MOZ_ASSERT(!JS_IsExceptionPending(aCx));
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -991,16 +991,17 @@ class WorkerPrivate : public WorkerPriva
   RefPtr<WorkerThread> mThread;
   PRThread* mPRThread;
 
   // Things touched on worker thread only.
   RefPtr<WorkerGlobalScope> mScope;
   RefPtr<WorkerDebuggerGlobalScope> mDebuggerScope;
   nsTArray<ParentType*> mChildWorkers;
   nsTObserverArray<WorkerHolder*> mHolders;
+  uint32_t mNumHoldersPreventingShutdownStart;
   nsTArray<nsAutoPtr<TimeoutInfo>> mTimeouts;
   uint32_t mDebuggerEventLoopLevel;
   RefPtr<ThrottledEventQueue> mMainThreadThrottledEventQueue;
   nsCOMPtr<nsIEventTarget> mMainThreadEventTarget;
   RefPtr<WorkerControlEventTarget> mWorkerControlEventTarget;
 
   struct SyncLoopInfo
   {
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/bug1290951_worker_imported.sjs
@@ -0,0 +1,23 @@
+/* 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";
+
+function handleRequest(request, response) {
+  let count = getState("count");
+  if (count === "") {
+    count = "1";
+  }
+
+  // If this is the first request, return the first source.
+  if (count === "1") {
+    response.setHeader("Content-Type", "application/javascript");
+    response.write("// Imported.");
+    setState("count", "2");
+  }
+  // For all subsequent requests, return the second source.
+  else {
+    response.setStatusLine(request.httpVersion, 404, "Not found");
+  }
+}
+
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/bug1290951_worker_main.sjs
@@ -0,0 +1,33 @@
+/* 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 WORKER_1 = `
+  importScripts("bug1290951_worker_imported.sjs");
+`;
+
+const WORKER_2 = `
+  // Remove importScripts(...)" for testing purpose.
+`;
+
+function handleRequest(request, response) {
+  let count = getState("count");
+  if (count === "") {
+    count = "1";
+  }
+
+  // This header is necessary for making this script able to be loaded.
+  response.setHeader("Content-Type", "application/javascript");
+
+  // If this is the first request, return the first source.
+  if (count === "1") {
+    response.write(WORKER_1);
+    setState("count", "2");
+  }
+  // For all subsequent requests, return the second source.
+  else {
+    response.write(WORKER_2);
+  }
+}
+
--- a/dom/workers/test/serviceworkers/mochitest.ini
+++ b/dom/workers/test/serviceworkers/mochitest.ini
@@ -210,18 +210,21 @@ support-files =
   hello.html
   create_another_sharedWorker.html
   sharedWorker_fetch.js
   async_waituntil_worker.js
   lazy_worker.js
   nofetch_handler_worker.js
   service_worker.js
   service_worker_client.html
+  bug1290951_worker_main.sjs
+  bug1290951_worker_imported.sjs
 
 [test_bug1151916.html]
+[test_bug1290951.html]
 [test_bug1240436.html]
 [test_claim.html]
 [test_claim_oninstall.html]
 [test_controller.html]
 [test_cross_origin_url_after_redirect.html]
 [test_csp_upgrade-insecure_intercept.html]
 [test_empty_serviceworker.html]
 [test_error_reporting.html]
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_bug1290951.html
@@ -0,0 +1,68 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Bug 1290951 - Test update after a new verion of mainscipt that doesn't need an imported script anymore.</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+<!--
+  If the principal is not set, accessing self.caches in the worker will crash.
+-->
+</head>
+<body>
+<p id="display"></p>
+<div id="content"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+  // We have the iframe register for its own scope so that this page is not
+  // holding any references when we GC.
+  function register() {
+    return Promise.resolve()
+      .then(_ => navigator.serviceWorker.register("http://mochi.test:8888/" +
+                                                  "tests/dom/workers/test/" +
+                                                  "serviceworkers/" +
+                                                  "bug1290951_worker_main.sjs"))
+      .then(r => ok(r, "Should be a registration."));
+  }
+
+  function update() {
+    return Promise.resolve()
+      .then(_ => navigator.serviceWorker.getRegistration())
+      .then(r => {
+        return new Promise(aResolve => {
+          r.addEventListener("updatefound", aResolve);
+          r.update();
+        });
+      });
+  }
+
+  function unregister() {
+    return Promise.resolve()
+      .then(_ => navigator.serviceWorker.getRegistration())
+      .then(r => r.unregister())
+  }
+
+  function runTest() {
+    register()
+      .then(register)
+      .then(update)
+      .then(unregister)
+
+      .catch(e => ok(false, "Some test failed with error " + e))
+      .then(SimpleTest.finish);
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  SpecialPowers.pushPrefEnv({"set": [
+    ["dom.serviceWorkers.enabled", true],
+    ["dom.serviceWorkers.testing.enabled", true]
+  ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
+
--- a/gfx/layers/LayerTreeInvalidation.cpp
+++ b/gfx/layers/LayerTreeInvalidation.cpp
@@ -177,24 +177,22 @@ struct LayerPropertiesBase : public Laye
   }
 
 protected:
   LayerPropertiesBase(const LayerPropertiesBase& a) = delete;
   LayerPropertiesBase& operator=(const LayerPropertiesBase& a) = delete;
 
 public:
   nsIntRegion ComputeDifferences(Layer* aRoot,
-                                 NotifySubDocInvalidationFunc aCallback,
-                                 bool* aGeometryChanged) override;
+                                 NotifySubDocInvalidationFunc aCallback) override;
 
   void MoveBy(const IntPoint& aOffset) override;
 
   nsIntRegion ComputeChange(const char* aPrefix,
-                            NotifySubDocInvalidationFunc aCallback,
-                            bool& aGeometryChanged)
+                            NotifySubDocInvalidationFunc aCallback)
   {
     // Bug 1251615: This canary is sometimes hit. We're still not sure why.
     mCanary.Check();
     bool transformChanged = !mTransform.FuzzyEqual(GetTransformForInvalidation(mLayer)) ||
                              mLayer->GetPostXScale() != mPostXScale ||
                              mLayer->GetPostYScale() != mPostYScale;
     const Maybe<ParentLayerIntRect>& otherClip = mLayer->GetLocalClipRect();
     nsIntRegion result;
@@ -211,49 +209,47 @@ public:
 
     Layer* otherMask = mLayer->GetMaskLayer();
     if ((mMaskLayer ? mMaskLayer->mLayer : nullptr) != otherMask ||
         ancestorMaskChanged ||
         (mUseClipRect != !!otherClip) ||
         mLayer->GetLocalOpacity() != mOpacity ||
         transformChanged)
     {
-      aGeometryChanged = true;
       result = OldTransformedBounds();
       LTI_DUMP(result, "oldtransform");
       LTI_DUMP(NewTransformedBounds(), "newtransform");
       AddRegion(result, NewTransformedBounds());
 
       // We can't bail out early because we need to update mChildrenChanged.
     }
 
-    nsIntRegion internal = ComputeChangeInternal(aPrefix, aCallback, aGeometryChanged);
+    nsIntRegion internal = ComputeChangeInternal(aPrefix, aCallback);
     LTI_DUMP(internal, "internal");
     AddRegion(result, internal);
     LTI_DUMP(mLayer->GetInvalidRegion().GetRegion(), "invalid");
     AddTransformedRegion(result, mLayer->GetInvalidRegion().GetRegion(), mTransform);
 
     if (mMaskLayer && otherMask) {
-      nsIntRegion mask = mMaskLayer->ComputeChange(aPrefix, aCallback, aGeometryChanged);
+      nsIntRegion mask = mMaskLayer->ComputeChange(aPrefix, aCallback);
       LTI_DUMP(mask, "mask");
       AddTransformedRegion(result, mask, mTransform);
     }
 
     for (size_t i = 0;
          i < std::min(mAncestorMaskLayers.Length(), mLayer->GetAncestorMaskLayerCount());
          i++)
     {
-      nsIntRegion mask = mAncestorMaskLayers[i]->ComputeChange(aPrefix, aCallback, aGeometryChanged);
+      nsIntRegion mask = mAncestorMaskLayers[i]->ComputeChange(aPrefix, aCallback);
       LTI_DUMP(mask, "ancestormask");
       AddTransformedRegion(result, mask, mTransform);
     }
 
     if (mUseClipRect && otherClip) {
       if (!mClipRect.IsEqualInterior(*otherClip)) {
-        aGeometryChanged = true;
         nsIntRegion tmp;
         tmp.Xor(mClipRect.ToUnknownRect(), otherClip->ToUnknownRect());
         LTI_DUMP(tmp, "clip");
         AddRegion(result, tmp);
       }
     }
 
     mLayer->ClearInvalidRect();
@@ -273,18 +269,17 @@ public:
   }
 
   virtual IntRect OldTransformedBounds()
   {
     return TransformRect(mVisibleRegion.ToUnknownRegion().GetBounds(), mTransform);
   }
 
   virtual nsIntRegion ComputeChangeInternal(const char* aPrefix,
-                                            NotifySubDocInvalidationFunc aCallback,
-                                            bool& aGeometryChanged)
+                                            NotifySubDocInvalidationFunc aCallback)
   {
     return IntRect();
   }
 
   RefPtr<Layer> mLayer;
   UniquePtr<LayerPropertiesBase> mMaskLayer;
   nsTArray<UniquePtr<LayerPropertiesBase>> mAncestorMaskLayers;
   nsIntRegion mVisibleRegion;
@@ -311,32 +306,30 @@ struct ContainerLayerProperties : public
   }
 
 protected:
   ContainerLayerProperties(const ContainerLayerProperties& a) = delete;
   ContainerLayerProperties& operator=(const ContainerLayerProperties& a) = delete;
 
 public:
   nsIntRegion ComputeChangeInternal(const char *aPrefix,
-                                    NotifySubDocInvalidationFunc aCallback,
-                                    bool& aGeometryChanged) override
+                                    NotifySubDocInvalidationFunc aCallback) override
   {
     // Make sure we got our virtual call right
     mSubtypeCanary.Check();
     ContainerLayer* container = mLayer->AsContainerLayer();
     nsIntRegion invalidOfLayer; // Invalid regions of this layer.
     nsIntRegion result;         // Invliad regions for children only.
 
     container->CheckCanary();
 
     bool childrenChanged = false;
 
     if (mPreXScale != container->GetPreXScale() ||
         mPreYScale != container->GetPreYScale()) {
-      aGeometryChanged = true;
       invalidOfLayer = OldTransformedBounds();
       AddRegion(invalidOfLayer, NewTransformedBounds());
       childrenChanged = true;
 
       // Can't bail out early, we need to update the child container layers
     }
 
     // A low frame rate is especially visible to users when scrolling, so we
@@ -370,17 +363,17 @@ public:
               LTI_DUMP(mChildren[j]->OldTransformedBounds(), "reordered child");
               AddRegion(result, mChildren[j]->OldTransformedBounds());
               childrenChanged |= true;
             }
             if (childsOldIndex >= mChildren.Length()) {
               MOZ_CRASH("Out of bounds");
             }
             // Invalidate any regions of the child that have changed:
-            nsIntRegion region = mChildren[childsOldIndex]->ComputeChange(LTI_DEEPER(aPrefix), aCallback, aGeometryChanged);
+            nsIntRegion region = mChildren[childsOldIndex]->ComputeChange(LTI_DEEPER(aPrefix), aCallback);
             i = childsOldIndex + 1;
             if (!region.IsEmpty()) {
               LTI_LOG("%s%p: child %p produced %s\n", aPrefix, mLayer.get(),
                 mChildren[childsOldIndex]->mLayer.get(), Stringify(region).c_str());
               AddRegion(result, region);
               childrenChanged |= true;
             }
           } else {
@@ -393,17 +386,16 @@ public:
           // |child| is new
           invalidateChildsCurrentArea = true;
         }
       } else {
         // |child| is new, or was reordered to a higher index
         invalidateChildsCurrentArea = true;
       }
       if (invalidateChildsCurrentArea) {
-        aGeometryChanged = true;
         LTI_DUMP(child->GetLocalVisibleRegion().ToUnknownRegion(), "invalidateChidlsCurrentArea");
         AddTransformedRegion(result, child->GetLocalVisibleRegion().ToUnknownRegion(),
                              GetTransformForInvalidation(child));
         if (aCallback) {
           NotifySubdocumentInvalidation(child, aCallback);
         } else {
           ClearInvalidations(child);
         }
@@ -480,23 +472,21 @@ struct ColorLayerProperties : public Lay
   { }
 
 protected:
   ColorLayerProperties(const ColorLayerProperties& a) = delete;
   ColorLayerProperties& operator=(const ColorLayerProperties& a) = delete;
 
 public:
   nsIntRegion ComputeChangeInternal(const char* aPrefix,
-                                    NotifySubDocInvalidationFunc aCallback,
-                                    bool& aGeometryChanged) override
+                                    NotifySubDocInvalidationFunc aCallback) override
   {
     ColorLayer* color = static_cast<ColorLayer*>(mLayer.get());
 
     if (mColor != color->GetColor()) {
-      aGeometryChanged = true;
       LTI_DUMP(NewTransformedBounds(), "color");
       return NewTransformedBounds();
     }
 
     nsIntRegion boundsDiff;
     boundsDiff.Xor(mBounds, color->GetBounds());
     LTI_DUMP(boundsDiff, "colorbounds");
 
@@ -521,33 +511,30 @@ struct BorderLayerProperties : public La
   { }
 
 protected:
   BorderLayerProperties(const BorderLayerProperties& a) = delete;
   BorderLayerProperties& operator=(const BorderLayerProperties& a) = delete;
 
 public:
   nsIntRegion ComputeChangeInternal(const char* aPrefix,
-                                    NotifySubDocInvalidationFunc aCallback,
-                                    bool& aGeometryChanged) override
+                                    NotifySubDocInvalidationFunc aCallback) override
   {
     BorderLayer* border = static_cast<BorderLayer*>(mLayer.get());
 
     if (!border->GetLocalVisibleRegion().ToUnknownRegion().IsEqual(mVisibleRegion)) {
-      aGeometryChanged = true;
       IntRect result = NewTransformedBounds();
       result = result.Union(OldTransformedBounds());
       return result;
     }
 
     if (!PodEqual(&mColors[0], &border->GetColors()[0], 4) ||
         !PodEqual(&mWidths[0], &border->GetWidths()[0], 4) ||
         !PodEqual(&mCorners[0], &border->GetCorners()[0], 4) ||
         !mRect.IsEqualEdges(border->GetRect())) {
-      aGeometryChanged = true;
       LTI_DUMP(NewTransformedBounds(), "bounds");
       return NewTransformedBounds();
     }
 
     return nsIntRegion();
   }
 
   BorderColors mColors;
@@ -566,32 +553,29 @@ struct TextLayerProperties : public Laye
   { }
 
 protected:
   TextLayerProperties(const TextLayerProperties& a) = delete;
   TextLayerProperties& operator=(const TextLayerProperties& a) = delete;
 
 public:
   nsIntRegion ComputeChangeInternal(const char* aPrefix,
-                                    NotifySubDocInvalidationFunc aCallback,
-                                    bool& aGeometryChanged) override
+                                    NotifySubDocInvalidationFunc aCallback) override
   {
     TextLayer* text = static_cast<TextLayer*>(mLayer.get());
 
     if (!text->GetLocalVisibleRegion().ToUnknownRegion().IsEqual(mVisibleRegion)) {
-      aGeometryChanged = true;
       IntRect result = NewTransformedBounds();
       result = result.Union(OldTransformedBounds());
       return result;
     }
 
     if (!mBounds.IsEqualEdges(text->GetBounds()) ||
         mGlyphs != text->GetGlyphs() ||
         mFont != text->GetScaledFont()) {
-      aGeometryChanged = true;
       LTI_DUMP(NewTransformedBounds(), "bounds");
       return NewTransformedBounds();
     }
 
     return nsIntRegion();
   }
 
   gfx::IntRect mBounds;
@@ -623,38 +607,35 @@ struct ImageLayerProperties : public Lay
   {
     if (mImageHost) {
       mLastProducerID = mImageHost->GetLastProducerID();
       mLastFrameID = mImageHost->GetLastFrameID();
     }
   }
 
   nsIntRegion ComputeChangeInternal(const char* aPrefix,
-                                    NotifySubDocInvalidationFunc aCallback,
-                                    bool& aGeometryChanged) override
+                                    NotifySubDocInvalidationFunc aCallback) override
   {
     ImageLayer* imageLayer = static_cast<ImageLayer*>(mLayer.get());
 
     if (!imageLayer->GetLocalVisibleRegion().ToUnknownRegion().IsEqual(mVisibleRegion)) {
-      aGeometryChanged = true;
       IntRect result = NewTransformedBounds();
       result = result.Union(OldTransformedBounds());
       return result;
     }
 
     ImageContainer* container = imageLayer->GetContainer();
     ImageHost* host = GetImageHost(imageLayer);
     if (mContainer != container ||
         mSamplingFilter != imageLayer->GetSamplingFilter() ||
         mScaleToSize != imageLayer->GetScaleToSize() ||
         mScaleMode != imageLayer->GetScaleMode() ||
         host != mImageHost ||
         (host && host->GetProducerID() != mLastProducerID) ||
         (host && host->GetFrameID() != mLastFrameID)) {
-      aGeometryChanged = true;
 
       if (mIsMask) {
         // Mask layers have an empty visible region, so we have to
         // use the image size instead.
         IntSize size;
         if (container) {
           size = container->GetCurrentSize();
         }
@@ -687,25 +668,22 @@ struct CanvasLayerProperties : public La
   explicit CanvasLayerProperties(CanvasLayer* aCanvas)
     : LayerPropertiesBase(aCanvas)
     , mImageHost(GetImageHost(aCanvas))
   {
     mFrameID = mImageHost ? mImageHost->GetFrameID() : -1;
   }
 
   nsIntRegion ComputeChangeInternal(const char* aPrefix,
-                                    NotifySubDocInvalidationFunc aCallback,
-                                    bool& aGeometryChanged) override
+                                    NotifySubDocInvalidationFunc aCallback) override
   {
     CanvasLayer* canvasLayer = static_cast<CanvasLayer*>(mLayer.get());
 
     ImageHost* host = GetImageHost(canvasLayer);
     if (host && host->GetFrameID() != mFrameID) {
-      aGeometryChanged = true;
-
       LTI_DUMP(NewTransformedBounds(), "frameId");
       return NewTransformedBounds();
     }
 
     return IntRect();
   }
 
   RefPtr<ImageHost> mImageHost;
@@ -769,40 +747,32 @@ LayerProperties::ClearInvalidations(Laye
             ClearInvalidations(layer->GetAncestorMaskLayerAt(i));
           }
 
         }
       );
 }
 
 nsIntRegion
-LayerPropertiesBase::ComputeDifferences(Layer* aRoot, NotifySubDocInvalidationFunc aCallback,
-                                        bool* aGeometryChanged = nullptr)
+LayerPropertiesBase::ComputeDifferences(Layer* aRoot, NotifySubDocInvalidationFunc aCallback)
 {
   NS_ASSERTION(aRoot, "Must have a layer tree to compare against!");
   if (mLayer != aRoot) {
     if (aCallback) {
       NotifySubdocumentInvalidation(aRoot, aCallback);
     } else {
       ClearInvalidations(aRoot);
     }
     IntRect result = TransformRect(
       aRoot->GetLocalVisibleRegion().ToUnknownRegion().GetBounds(),
       aRoot->GetLocalTransform());
     result = result.Union(OldTransformedBounds());
-    if (aGeometryChanged != nullptr) {
-      *aGeometryChanged = true;
-    }
     return result;
   }
-  bool geometryChanged = (aGeometryChanged != nullptr) ? *aGeometryChanged : false;
-  nsIntRegion invalid = ComputeChange("  ", aCallback, geometryChanged);
-  if (aGeometryChanged != nullptr) {
-    *aGeometryChanged = geometryChanged;
-  }
+  nsIntRegion invalid = ComputeChange("  ", aCallback);
   return invalid;
 }
 
 void
 LayerPropertiesBase::MoveBy(const IntPoint& aOffset)
 {
   mTransform.PostTranslate(aOffset.x, aOffset.y, 0);
 }
--- a/gfx/layers/LayerTreeInvalidation.h
+++ b/gfx/layers/LayerTreeInvalidation.h
@@ -60,18 +60,17 @@ public:
    * tree and generates the changed rectangle.
    *
    * @param aRoot Root layer of the layer tree to compare against.
    * @param aCallback If specified, callback to call when ContainerLayers
    * are invalidated.
    * @return Painted area changed by the layer tree changes.
    */
   virtual nsIntRegion ComputeDifferences(Layer* aRoot,
-                                         NotifySubDocInvalidationFunc aCallback,
-                                         bool* aGeometryChanged = nullptr) = 0;
+                                         NotifySubDocInvalidationFunc aCallback) = 0;
 
   virtual void MoveBy(const gfx::IntPoint& aOffset) = 0;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif /* GFX_LAYER_TREE_INVALIDATON_H */
--- a/gfx/layers/composite/LayerManagerComposite.cpp
+++ b/gfx/layers/composite/LayerManagerComposite.cpp
@@ -142,17 +142,16 @@ HostLayerManager::RecordUpdateTime(float
  * LayerManagerComposite
  */
 LayerManagerComposite::LayerManagerComposite(Compositor* aCompositor)
 : mUnusedApzTransformWarning(false)
 , mDisabledApzWarning(false)
 , mCompositor(aCompositor)
 , mInTransaction(false)
 , mIsCompositorReady(false)
-, mGeometryChanged(true)
 #if defined(MOZ_WIDGET_ANDROID)
 , mScreenPixelsTarget(nullptr)
 #endif // defined(MOZ_WIDGET_ANDROID)
 {
   mTextRenderer = new TextRenderer();
   mDiagnostics = MakeUnique<Diagnostics>();
   MOZ_ASSERT(aCompositor);
 
@@ -430,19 +429,16 @@ LayerManagerComposite::EndTransaction(co
   // ComputeEffectiveTransforms (so the correct video frame size is picked) and
   // also to compute invalid regions properly.
   SetCompositionTime(aTimeStamp);
 
   if (mRoot && !(aFlags & END_NO_IMMEDIATE_REDRAW)) {
     MOZ_ASSERT(!aTimeStamp.IsNull());
     UpdateAndRender();
     mCompositor->FlushPendingNotifyNotUsed();
-  } else {
-    // Modified the layer tree.
-    mGeometryChanged = true;
   }
 
   mCompositor->ClearTargetContext();
   mTarget = nullptr;
 
 #ifdef MOZ_LAYERS_HAVE_LOG
   Log();
   MOZ_LAYERS_LOG(("]----- EndTransaction"));
@@ -463,17 +459,17 @@ LayerManagerComposite::UpdateAndRender()
     // Effective transforms are needed by ComputeDifferences().
     mRoot->ComputeEffectiveTransforms(gfx::Matrix4x4());
     didEffectiveTransforms = true;
 
     // We need to compute layer tree differences even if we're not going to
     // immediately use the resulting damage area, since ComputeDifferences
     // is also responsible for invalidates intermediate surfaces in
     // ContainerLayers.
-    nsIntRegion changed = mClonedLayerTreeProperties->ComputeDifferences(mRoot, nullptr, &mGeometryChanged);
+    nsIntRegion changed = mClonedLayerTreeProperties->ComputeDifferences(mRoot, nullptr);
 
     if (mTarget) {
       // Since we're composing to an external target, we're not going to use
       // the damage region from layers changes - we want to composite
       // everything in the target bounds. Instead we accumulate the layers
       // damage region for the next window composite.
       mInvalidRegion.Or(mInvalidRegion, changed);
     } else {
@@ -511,17 +507,16 @@ LayerManagerComposite::UpdateAndRender()
     // so we don't need to pass any global transform here.
     mRoot->ComputeEffectiveTransforms(gfx::Matrix4x4());
   }
 
   Render(invalid, opaque);
 #if defined(MOZ_WIDGET_ANDROID)
   RenderToPresentationSurface();
 #endif
-  mGeometryChanged = false;
   mWindowOverlayChanged = false;
 
   // Update cached layer tree information.
   mClonedLayerTreeProperties = LayerProperties::CloneFrom(GetRoot());
 }
 
 already_AddRefed<DrawTarget>
 LayerManagerComposite::CreateOptimalMaskDrawTarget(const IntSize &aSize)
--- a/gfx/layers/composite/LayerManagerComposite.h
+++ b/gfx/layers/composite/LayerManagerComposite.h
@@ -480,17 +480,16 @@ private:
                            CSSIntRegion> VisibleRegions;
   VisibleRegions mVisibleRegions;
 
   bool mInTransaction;
   bool mIsCompositorReady;
 
   RefPtr<CompositingRenderTarget> mTwoPassTmpTarget;
   RefPtr<TextRenderer> mTextRenderer;
-  bool mGeometryChanged;
 
 #ifdef USE_SKIA
   /**
    * Render paint and composite times above the frame.
    */
   void DrawPaintTimes(Compositor* aCompositor);
   RefPtr<PaintCounter> mPaintCounter;
 #endif
--- a/gfx/layers/composite/TextureHost.cpp
+++ b/gfx/layers/composite/TextureHost.cpp
@@ -427,22 +427,20 @@ TextureHost::Updated(const nsIntRegion* 
 {
     LayerScope::ContentChanged(this);
     UpdatedInternal(aRegion);
 }
 
 TextureSource::TextureSource()
 : mCompositableCount(0)
 {
-    MOZ_COUNT_CTOR(TextureSource);
 }
 
 TextureSource::~TextureSource()
 {
-    MOZ_COUNT_DTOR(TextureSource);
 }
 
 const char*
 TextureSource::Name() const
 {
   MOZ_CRASH("GFX: TextureSource without class name");
   return "TextureSource";
 }
--- a/gfx/layers/d3d11/TextureD3D11.cpp
+++ b/gfx/layers/d3d11/TextureD3D11.cpp
@@ -164,32 +164,29 @@ DataTextureSourceD3D11::DataTextureSourc
   : mDevice(aDevice)
   , mFormat(aFormat)
   , mFlags(aFlags)
   , mCurrentTile(0)
   , mIsTiled(false)
   , mIterating(false)
   , mAllowTextureUploads(true)
 {
-  MOZ_COUNT_CTOR(DataTextureSourceD3D11);
 }
 
 DataTextureSourceD3D11::DataTextureSourceD3D11(ID3D11Device* aDevice,
                                                SurfaceFormat aFormat,
                                                ID3D11Texture2D* aTexture)
 : mDevice(aDevice)
 , mFormat(aFormat)
 , mFlags(TextureFlags::NO_FLAGS)
 , mCurrentTile(0)
 , mIsTiled(false)
 , mIterating(false)
 , mAllowTextureUploads(false)
 {
-  MOZ_COUNT_CTOR(DataTextureSourceD3D11);
-
   mTexture = aTexture;
   D3D11_TEXTURE2D_DESC desc;
   aTexture->GetDesc(&desc);
 
   mSize = IntSize(desc.Width, desc.Height);
 }
 
 DataTextureSourceD3D11::DataTextureSourceD3D11(gfx::SurfaceFormat aFormat, TextureSourceProvider* aProvider, ID3D11Texture2D* aTexture)
@@ -199,17 +196,16 @@ DataTextureSourceD3D11::DataTextureSourc
 
 DataTextureSourceD3D11::DataTextureSourceD3D11(gfx::SurfaceFormat aFormat, TextureSourceProvider* aProvider, TextureFlags aFlags)
  : DataTextureSourceD3D11(aProvider->GetD3D11Device(), aFormat, aFlags)
 {
 }
 
 DataTextureSourceD3D11::~DataTextureSourceD3D11()
 {
-  MOZ_COUNT_DTOR(DataTextureSourceD3D11);
 }
 
 
 template<typename T> // ID3D10Texture2D or ID3D11Texture2D
 static bool LockD3DTexture(T* aTexture)
 {
   MOZ_ASSERT(aTexture);
   RefPtr<IDXGIKeyedMutex> mutex;
--- a/gfx/thebes/gfxFT2Utils.cpp
+++ b/gfx/thebes/gfxFT2Utils.cpp
@@ -5,16 +5,20 @@
 
 #include "gfxFT2FontBase.h"
 #include "gfxFT2Utils.h"
 #include "mozilla/Likely.h"
 #include FT_TRUETYPE_TAGS_H
 #include FT_TRUETYPE_TABLES_H
 #include <algorithm>
 
+#ifndef FT_FACE_FLAG_COLOR
+#define FT_FACE_FLAG_COLOR ( 1L << 14 )
+#endif
+
 #ifdef HAVE_FONTCONFIG_FCFREETYPE_H
 #include <fontconfig/fcfreetype.h>
 #endif
 
 #include "prlink.h"
 
 // aScale is intended for a 16.16 x/y_scale of an FT_Size_Metrics
 static inline FT_Long
@@ -48,17 +52,17 @@ gfxFT2LockedFace::GetMetrics(gfxFont::Me
                              uint32_t* aSpaceGlyph)
 {
     NS_PRECONDITION(aMetrics != nullptr, "aMetrics must not be NULL");
     NS_PRECONDITION(aSpaceGlyph != nullptr, "aSpaceGlyph must not be NULL");
 
     if (MOZ_UNLIKELY(!mFace)) {
         // No face.  This unfortunate situation might happen if the font
         // file is (re)moved at the wrong time.
-        const gfxFloat emHeight = mGfxFont->GetStyle()->size;
+        const gfxFloat emHeight = mGfxFont->GetAdjustedSize();
         aMetrics->emHeight = emHeight;
         aMetrics->maxAscent = aMetrics->emAscent = 0.8 * emHeight;
         aMetrics->maxDescent = aMetrics->emDescent = 0.2 * emHeight;
         aMetrics->maxHeight = emHeight;
         aMetrics->internalLeading = 0.0;
         aMetrics->externalLeading = 0.2 * emHeight;
         const gfxFloat spaceWidth = 0.5 * emHeight;
         aMetrics->spaceWidth = spaceWidth;
@@ -75,16 +79,21 @@ gfxFT2LockedFace::GetMetrics(gfxFont::Me
         aMetrics->strikeoutSize = underlineSize;
 
         *aSpaceGlyph = 0;
         return;
     }
 
     const FT_Size_Metrics& ftMetrics = mFace->size->metrics;
 
+    aMetrics->maxAscent = FLOAT_FROM_26_6(ftMetrics.ascender);
+    aMetrics->maxDescent = -FLOAT_FROM_26_6(ftMetrics.descender);
+    aMetrics->maxAdvance = FLOAT_FROM_26_6(ftMetrics.max_advance);
+    gfxFloat lineHeight = FLOAT_FROM_26_6(ftMetrics.height);
+
     gfxFloat emHeight;
     // Scale for vertical design metric conversion: pixels per design unit.
     // If this remains at 0.0, we can't use metrics from OS/2 etc.
     gfxFloat yScale = 0.0;
     if (FT_IS_SCALABLE(mFace)) {
         // Prefer FT_Size_Metrics::x_scale to x_ppem as x_ppem does not
         // have subpixel accuracy.
         //
@@ -98,29 +107,37 @@ gfxFT2LockedFace::GetMetrics(gfxFont::Me
         emHeight = ftMetrics.y_ppem;
         // FT_Face doc says units_per_EM and a bunch of following fields
         // are "only relevant to scalable outlines". If it's an sfnt,
         // we can get units_per_EM from the 'head' table instead; otherwise,
         // we don't have a unitsPerEm value so we can't compute/use yScale.
         const TT_Header* head =
             static_cast<TT_Header*>(FT_Get_Sfnt_Table(mFace, ft_sfnt_head));
         if (head) {
+            // Bug 1267909 - Even if the font is not explicitly scalable,
+            // if the face has color bitmaps, it should be treated as scalable
+            // and scaled to the desired size. Metrics based on y_ppem need
+            // to be rescaled for the adjusted size. This makes metrics agree
+            // with the scales we pass to Cairo for Fontconfig fonts.
+            if (mFace->face_flags & FT_FACE_FLAG_COLOR) {
+                emHeight = mGfxFont->GetAdjustedSize();
+                gfxFloat adjustScale = emHeight / ftMetrics.y_ppem;
+                aMetrics->maxAscent *= adjustScale;
+                aMetrics->maxDescent *= adjustScale;
+                aMetrics->maxAdvance *= adjustScale;
+                lineHeight *= adjustScale;
+            }
             gfxFloat emUnit = head->Units_Per_EM;
             yScale = emHeight / emUnit;
         }
     }
 
     TT_OS2 *os2 =
         static_cast<TT_OS2*>(FT_Get_Sfnt_Table(mFace, ft_sfnt_os2));
 
-    aMetrics->maxAscent = FLOAT_FROM_26_6(ftMetrics.ascender);
-    aMetrics->maxDescent = -FLOAT_FROM_26_6(ftMetrics.descender);
-    aMetrics->maxAdvance = FLOAT_FROM_26_6(ftMetrics.max_advance);
-
-    gfxFloat lineHeight;
     if (os2 && os2->sTypoAscender && yScale > 0.0) {
         aMetrics->emAscent = os2->sTypoAscender * yScale;
         aMetrics->emDescent = -os2->sTypoDescender * yScale;
         FT_Short typoHeight =
             os2->sTypoAscender - os2->sTypoDescender + os2->sTypoLineGap;
         lineHeight = typoHeight * yScale;
 
         // If the OS/2 fsSelection USE_TYPO_METRICS bit is set,
@@ -137,17 +154,16 @@ gfxFT2LockedFace::GetMetrics(gfxFont::Me
             aMetrics->maxAscent =
                 std::max(aMetrics->maxAscent, NS_round(aMetrics->emAscent));
             aMetrics->maxDescent =
                 std::max(aMetrics->maxDescent, NS_round(aMetrics->emDescent));
         }
     } else {
         aMetrics->emAscent = aMetrics->maxAscent;
         aMetrics->emDescent = aMetrics->maxDescent;
-        lineHeight = FLOAT_FROM_26_6(ftMetrics.height);
     }
 
     cairo_text_extents_t extents;
     *aSpaceGlyph = GetCharExtents(' ', &extents);
     if (*aSpaceGlyph) {
         aMetrics->spaceWidth = extents.x_advance;
     } else {
         aMetrics->spaceWidth = aMetrics->maxAdvance; // guess
--- a/image/decoders/icon/mac/nsIconChannelCocoa.mm
+++ b/image/decoders/icon/mac/nsIconChannelCocoa.mm
@@ -232,17 +232,20 @@ nsIconChannel::AsyncOpen(nsIStreamListen
   nsCOMPtr<nsIInputStream> inStream;
   nsresult rv = MakeInputStream(getter_AddRefs(inStream), true);
   if (NS_FAILED(rv)) {
       mCallbacks = nullptr;
       return rv;
   }
 
   // Init our stream pump
-  rv = mPump->Init(inStream, int64_t(-1), int64_t(-1), 0, 0, false);
+  nsCOMPtr<nsIEventTarget> target =
+      nsContentUtils::GetEventTargetByLoadInfo(mLoadInfo,
+                                               mozilla::TaskCategory::Other);
+  rv = mPump->Init(inStream, int64_t(-1), int64_t(-1), 0, 0, false, target);
   if (NS_FAILED(rv)) {
       mCallbacks = nullptr;
       return rv;
   }
 
   rv = mPump->AsyncRead(this, ctxt);
   if (NS_SUCCEEDED(rv)) {
     // Store our real listener
--- a/image/decoders/icon/win/nsIconChannel.cpp
+++ b/image/decoders/icon/win/nsIconChannel.cpp
@@ -247,17 +247,20 @@ nsIconChannel::AsyncOpen(nsIStreamListen
   nsCOMPtr<nsIInputStream> inStream;
   nsresult rv = MakeInputStream(getter_AddRefs(inStream), true);
   if (NS_FAILED(rv)) {
     mCallbacks = nullptr;
     return rv;
   }
 
   // Init our streampump
-  rv = mPump->Init(inStream, int64_t(-1), int64_t(-1), 0, 0, false);
+  nsCOMPtr<nsIEventTarget> target =
+    nsContentUtils::GetEventTargetByLoadInfo(mLoadInfo,
+                                             mozilla::TaskCategory::Other);
+  rv = mPump->Init(inStream, int64_t(-1), int64_t(-1), 0, 0, false, target);
   if (NS_FAILED(rv)) {
     mCallbacks = nullptr;
     return rv;
   }
 
   rv = mPump->AsyncRead(this, ctxt);
   if (NS_SUCCEEDED(rv)) {
     // Store our real listener
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -9382,17 +9382,17 @@ Parser<ParseHandler, CharT>::newRegExp()
 {
     MOZ_ASSERT(!options().selfHostingMode);
     // Create the regexp even when doing a syntax parse, to check the regexp's syntax.
     const char16_t* chars = tokenStream.getTokenbuf().begin();
     size_t length = tokenStream.getTokenbuf().length();
     RegExpFlag flags = tokenStream.currentToken().regExpFlags();
 
     Rooted<RegExpObject*> reobj(context);
-    reobj = RegExpObject::create(context, chars, length, flags, &tokenStream, alloc);
+    reobj = RegExpObject::create(context, chars, length, flags, nullptr, &tokenStream, alloc);
     if (!reobj)
         return null();
 
     return handler.newRegExp(reobj, pos(), *this);
 }
 
 template <template <typename CharT> class ParseHandler, typename CharT>
 void
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -6090,28 +6090,28 @@ JS_PUBLIC_API(JSObject*)
 JS_NewRegExpObject(JSContext* cx, const char* bytes, size_t length, unsigned flags)
 {
     AssertHeapIsIdle();
     CHECK_REQUEST(cx);
     ScopedJSFreePtr<char16_t> chars(InflateString(cx, bytes, &length));
     if (!chars)
         return nullptr;
 
-    RegExpObject* reobj = RegExpObject::create(cx, chars, length,
-                                               RegExpFlag(flags), nullptr, cx->tempLifoAlloc());
+    RegExpObject* reobj = RegExpObject::create(cx, chars, length, RegExpFlag(flags),
+                                               nullptr, nullptr, cx->tempLifoAlloc());
     return reobj;
 }
 
 JS_PUBLIC_API(JSObject*)
 JS_NewUCRegExpObject(JSContext* cx, const char16_t* chars, size_t length, unsigned flags)
 {
     AssertHeapIsIdle();
     CHECK_REQUEST(cx);
-    return RegExpObject::create(cx, chars, length,
-                                RegExpFlag(flags), nullptr, cx->tempLifoAlloc());
+    return RegExpObject::create(cx, chars, length, RegExpFlag(flags),
+                                nullptr, nullptr, cx->tempLifoAlloc());
 }
 
 JS_PUBLIC_API(bool)
 JS_SetRegExpInput(JSContext* cx, HandleObject obj, HandleString input)
 {
     AssertHeapIsIdle();
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, input);
--- a/js/src/vm/RegExpObject.cpp
+++ b/js/src/vm/RegExpObject.cpp
@@ -233,34 +233,39 @@ const Class RegExpObject::protoClass_ = 
     js_Object_str,
     JSCLASS_HAS_CACHED_PROTO(JSProto_RegExp),
     JS_NULL_CLASS_OPS,
     &RegExpObjectClassSpec
 };
 
 RegExpObject*
 RegExpObject::create(JSContext* cx, const char16_t* chars, size_t length, RegExpFlag flags,
-                     TokenStream* tokenStream, LifoAlloc& alloc)
+                     const ReadOnlyCompileOptions* options, TokenStream* tokenStream,
+                     LifoAlloc& alloc)
 {
     RootedAtom source(cx, AtomizeChars(cx, chars, length));
     if (!source)
         return nullptr;
 
-    return create(cx, source, flags, tokenStream, alloc);
+    return create(cx, source, flags, options, tokenStream, alloc);
 }
 
 RegExpObject*
 RegExpObject::create(JSContext* cx, HandleAtom source, RegExpFlag flags,
-                     TokenStream* tokenStream, LifoAlloc& alloc)
+                     const ReadOnlyCompileOptions* options, TokenStream* tokenStream,
+                     LifoAlloc& alloc)
 {
     Maybe<CompileOptions> dummyOptions;
+    if (!tokenStream && !options) {
+        dummyOptions.emplace(cx);
+        options = dummyOptions.ptr();
+    }
     Maybe<TokenStream> dummyTokenStream;
     if (!tokenStream) {
-        dummyOptions.emplace(cx);
-        dummyTokenStream.emplace(cx, *dummyOptions,
+        dummyTokenStream.emplace(cx, *options,
                                    (const char16_t*) nullptr, 0,
                                    (frontend::StrictModeGetter*) nullptr);
         tokenStream = dummyTokenStream.ptr();
     }
 
     if (!irregexp::ParsePatternSyntax(*tokenStream, alloc, source, flags & UnicodeFlag))
         return nullptr;
 
@@ -1452,18 +1457,21 @@ js::XDRScriptRegExpObject(XDRState<mode>
         RegExpObject& reobj = *objp;
         source = reobj.getSource();
         flagsword = reobj.getFlags();
     }
     if (!XDRAtom(xdr, &source) || !xdr->codeUint32(&flagsword))
         return false;
     if (mode == XDR_DECODE) {
         RegExpFlag flags = RegExpFlag(flagsword);
-        RegExpObject* reobj = RegExpObject::create(xdr->cx(), source, flags, nullptr,
-                                                   xdr->lifoAlloc());
+        const ReadOnlyCompileOptions* options = nullptr;
+        if (xdr->hasOptions())
+            options = &xdr->options();
+        RegExpObject* reobj = RegExpObject::create(xdr->cx(), source, flags,
+                                                   options, nullptr, xdr->lifoAlloc());
         if (!reobj)
             return false;
 
         objp.set(reobj);
     }
     return true;
 }
 
@@ -1476,17 +1484,18 @@ js::XDRScriptRegExpObject(XDRState<XDR_D
 JSObject*
 js::CloneScriptRegExpObject(JSContext* cx, RegExpObject& reobj)
 {
     /* NB: Keep this in sync with XDRScriptRegExpObject. */
 
     RootedAtom source(cx, reobj.getSource());
     cx->markAtom(source);
 
-    return RegExpObject::create(cx, source, reobj.getFlags(), nullptr, cx->tempLifoAlloc());
+    return RegExpObject::create(cx, source, reobj.getFlags(),
+                                nullptr, nullptr, cx->tempLifoAlloc());
 }
 
 JS_FRIEND_API(bool)
 js::RegExpToSharedNonInline(JSContext* cx, HandleObject obj, MutableHandleRegExpShared shared)
 {
     return RegExpToShared(cx, obj, shared);
 }
 
--- a/js/src/vm/RegExpObject.h
+++ b/js/src/vm/RegExpObject.h
@@ -69,21 +69,21 @@ class RegExpObject : public NativeObject
     static const Class protoClass_;
 
     // The maximum number of pairs a MatchResult can have, without having to
     // allocate a bigger MatchResult.
     static const size_t MaxPairCount = 14;
 
     static RegExpObject*
     create(JSContext* cx, const char16_t* chars, size_t length, RegExpFlag flags,
-           frontend::TokenStream* ts, LifoAlloc& alloc);
+           const ReadOnlyCompileOptions* options, frontend::TokenStream* ts, LifoAlloc& alloc);
 
     static RegExpObject*
     create(JSContext* cx, HandleAtom atom, RegExpFlag flags,
-           frontend::TokenStream* ts, LifoAlloc& alloc);
+           const ReadOnlyCompileOptions* options, frontend::TokenStream* ts, LifoAlloc& alloc);
 
     /*
      * Compute the initial shape to associate with fresh RegExp objects,
      * encoding their initial properties. Return the shape after
      * changing |obj|'s last property to it.
      */
     static Shape*
     assignInitialShape(JSContext* cx, Handle<RegExpObject*> obj);
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -3056,17 +3056,18 @@ CloneObject(JSContext* cx, HandleNativeO
         if (clone && hasName) {
             clone->as<JSFunction>().setExtendedSlot(LAZY_FUNCTION_NAME_SLOT,
                                                     StringValue(selfHostedFunction->explicitName()));
         }
     } else if (selfHostedObject->is<RegExpObject>()) {
         RegExpObject& reobj = selfHostedObject->as<RegExpObject>();
         RootedAtom source(cx, reobj.getSource());
         MOZ_ASSERT(source->isPermanentAtom());
-        clone = RegExpObject::create(cx, source, reobj.getFlags(), nullptr, cx->tempLifoAlloc());
+        clone = RegExpObject::create(cx, source, reobj.getFlags(),
+                                     nullptr, nullptr, cx->tempLifoAlloc());
     } else if (selfHostedObject->is<DateObject>()) {
         clone = JS::NewDateObject(cx, selfHostedObject->as<DateObject>().clippedTime());
     } else if (selfHostedObject->is<BooleanObject>()) {
         clone = BooleanObject::create(cx, selfHostedObject->as<BooleanObject>().unbox());
     } else if (selfHostedObject->is<NumberObject>()) {
         clone = NumberObject::create(cx, selfHostedObject->as<NumberObject>().unbox());
     } else if (selfHostedObject->is<StringObject>()) {
         JSString* selfHostedString = selfHostedObject->as<StringObject>().unbox();
--- a/js/src/vm/StructuredClone.cpp
+++ b/js/src/vm/StructuredClone.cpp
@@ -2121,17 +2121,17 @@ JSStructuredCloneReader::startRead(Mutab
         JSString* str = readString(stringData);
         if (!str)
             return false;
 
         RootedAtom atom(context(), AtomizeString(context(), str));
         if (!atom)
             return false;
 
-        RegExpObject* reobj = RegExpObject::create(context(), atom, flags, nullptr,
+        RegExpObject* reobj = RegExpObject::create(context(), atom, flags, nullptr, nullptr,
                                                    context()->tempLifoAlloc());
         if (!reobj)
             return false;
         vp.setObject(*reobj);
         break;
       }
 
       case SCTAG_ARRAY_OBJECT:
--- a/js/xpconnect/src/XPCJSContext.cpp
+++ b/js/xpconnect/src/XPCJSContext.cpp
@@ -576,16 +576,17 @@ XPCJSContext::InterruptCallback(JSContex
     if (response == nsGlobalWindow::AlwaysContinueSlowScript)
         Preferences::SetInt(prefName, 0);
 
     return true;
 }
 
 XPCJSContext::~XPCJSContext()
 {
+    MOZ_COUNT_DTOR_INHERITED(XPCJSContext, CycleCollectedJSContext);
     // Elsewhere we abort immediately if XPCJSContext initialization fails.
     // Therefore the context must be non-null.
     MOZ_ASSERT(MaybeContext());
 
     js::SetActivityCallback(Context(), nullptr, nullptr);
 
     // Clear any pending exception.  It might be an XPCWrappedJS, and if we try
     // to destroy it later we will crash.
@@ -615,16 +616,17 @@ XPCJSContext::XPCJSContext()
    mAutoRoots(nullptr),
    mResolveName(JSID_VOID),
    mResolvingWrapper(nullptr),
    mWatchdogManager(new WatchdogManager(this)),
    mSlowScriptSecondHalf(false),
    mTimeoutAccumulated(false),
    mPendingResult(NS_OK)
 {
+    MOZ_COUNT_CTOR_INHERITED(XPCJSContext, CycleCollectedJSContext);
     MOZ_RELEASE_ASSERT(!gTlsContext.get());
     gTlsContext.set(this);
 }
 
 /* static */ XPCJSContext*
 XPCJSContext::Get()
 {
     return gTlsContext.get();
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -1216,16 +1216,17 @@ XPCJSRuntime::Shutdown(JSContext* cx)
     Preferences::UnregisterCallback(ReloadPrefsCallback, "fuzzing.enabled");
 #endif
 
     CycleCollectedJSRuntime::Shutdown(cx);
 }
 
 XPCJSRuntime::~XPCJSRuntime()
 {
+    MOZ_COUNT_DTOR_INHERITED(XPCJSRuntime, CycleCollectedJSRuntime);
 }
 
 // If |*anonymizeID| is non-zero and this is a user compartment, the name will
 // be anonymized.
 static void
 GetCompartmentName(JSCompartment* c, nsCString& name, int* anonymizeID,
                    bool replaceSlashes)
 {
@@ -2947,16 +2948,17 @@ XPCJSRuntime::XPCJSRuntime(JSContext* aC
    mGCIsRunning(false),
    mNativesToReleaseArray(),
    mDoingFinalization(false),
    mVariantRoots(nullptr),
    mWrappedJSRoots(nullptr),
    mObjectHolderRoots(nullptr),
    mAsyncSnowWhiteFreer(new AsyncFreeSnowWhite())
 {
+    MOZ_COUNT_CTOR_INHERITED(XPCJSRuntime, CycleCollectedJSRuntime);
 }
 
 /* static */
 XPCJSRuntime*
 XPCJSRuntime::Get()
 {
     return nsXPConnect::GetRuntimeInstance();
 }
--- a/mfbt/Atomics.h
+++ b/mfbt/Atomics.h
@@ -16,40 +16,19 @@
 #ifndef mozilla_Atomics_h
 #define mozilla_Atomics_h
 
 #include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Compiler.h"
 #include "mozilla/TypeTraits.h"
 
-#include <stdint.h>
+#include <atomic>
 
-/*
- * Our minimum deployment target on clang/OS X is OS X 10.6, whose SDK
- * does not have <atomic>.  So be sure to check for <atomic> support
- * along with C++0x support.
- */
-#if defined(_MSC_VER)
-#  define MOZ_HAVE_CXX11_ATOMICS
-#elif defined(__clang__) || defined(__GNUC__)
-   /*
-    * Clang doesn't like <atomic> from libstdc++ before 4.7 due to the
-    * loose typing of the atomic builtins. GCC 4.5 and 4.6 lacks inline
-    * definitions for unspecialized std::atomic and causes linking errors.
-    * Therefore, we require at least 4.7.0 for using libstdc++.
-    *
-    * libc++ <atomic> is only functional with clang.
-    */
-#  if MOZ_USING_LIBSTDCXX && MOZ_LIBSTDCXX_VERSION_AT_LEAST(4, 7, 0)
-#    define MOZ_HAVE_CXX11_ATOMICS
-#  elif MOZ_USING_LIBCXX && defined(__clang__)
-#    define MOZ_HAVE_CXX11_ATOMICS
-#  endif
-#endif
+#include <stdint.h>
 
 namespace mozilla {
 
 /**
  * An enum of memory ordering possibilities for atomics.
  *
  * Memory ordering is the observable state of distinct values in memory.
  * (It's a separate concept from atomicity, which concerns whether an
@@ -157,24 +136,16 @@ enum MemoryOrdering {
    * SequentiallyConsistent ordering, even if they shouldn't, when
    * writing code, atomic or otherwise.  SequentiallyConsistent is also
    * the ordering of choice when designing lockless data structures.  If
    * you don't know what order to use, use this one.
    */
   SequentiallyConsistent,
 };
 
-} // namespace mozilla
-
-// Build up the underlying intrinsics.
-#ifdef MOZ_HAVE_CXX11_ATOMICS
-
-#  include <atomic>
-
-namespace mozilla {
 namespace detail {
 
 /*
  * We provide CompareExchangeFailureOrder to work around a bug in some
  * versions of GCC's <atomic> header.  See bug 898491.
  */
 template<MemoryOrdering Order> struct AtomicOrderConstraints;
 
@@ -321,224 +292,16 @@ struct AtomicIntrinsics<T*, Order>
 };
 
 template<typename T>
 struct ToStorageTypeArgument
 {
   static constexpr T convert (T aT) { return aT; }
 };
 
-} // namespace detail
-} // namespace mozilla
-
-#elif defined(__GNUC__)
-
-namespace mozilla {
-namespace detail {
-
-/*
- * The __sync_* family of intrinsics is documented here:
- *
- * http://gcc.gnu.org/onlinedocs/gcc-4.6.4/gcc/Atomic-Builtins.html
- *
- * While these intrinsics are deprecated in favor of the newer __atomic_*
- * family of intrincs:
- *
- * http://gcc.gnu.org/onlinedocs/gcc-4.7.3/gcc/_005f_005fatomic-Builtins.html
- *
- * any GCC version that supports the __atomic_* intrinsics will also support
- * the <atomic> header and so will be handled above.  We provide a version of
- * atomics using the __sync_* intrinsics to support older versions of GCC.
- *
- * All __sync_* intrinsics that we use below act as full memory barriers, for
- * both compiler and hardware reordering, except for __sync_lock_test_and_set,
- * which is a only an acquire barrier.  When we call __sync_lock_test_and_set,
- * we add a barrier above it as appropriate.
- */
-
-template<MemoryOrdering Order> struct Barrier;
-
-/*
- * Some processors (in particular, x86) don't require quite so many calls to
- * __sync_sychronize as our specializations of Barrier produce.  If
- * performance turns out to be an issue, defining these specializations
- * on a per-processor basis would be a good first tuning step.
- */
-
-template<>
-struct Barrier<Relaxed>
-{
-  static void beforeLoad() {}
-  static void afterLoad() {}
-  static void beforeStore() {}
-  static void afterStore() {}
-};
-
-template<>
-struct Barrier<ReleaseAcquire>
-{
-  static void beforeLoad() {}
-  static void afterLoad() { __sync_synchronize(); }
-  static void beforeStore() { __sync_synchronize(); }
-  static void afterStore() {}
-};
-
-template<>
-struct Barrier<SequentiallyConsistent>
-{
-  static void beforeLoad() { __sync_synchronize(); }
-  static void afterLoad() { __sync_synchronize(); }
-  static void beforeStore() { __sync_synchronize(); }
-  static void afterStore() { __sync_synchronize(); }
-};
-
-template<typename T, bool TIsEnum = IsEnum<T>::value>
-struct AtomicStorageType
-{
-  // For non-enums, just use the type directly.
-  typedef T Type;
-};
-
-template<typename T>
-struct AtomicStorageType<T, true>
-  : Conditional<sizeof(T) == 4, uint32_t, uint64_t>
-{
-  static_assert(sizeof(T) == 4 || sizeof(T) == 8,
-                "wrong type computed in conditional above");
-};
-
-template<typename T, MemoryOrdering Order>
-struct IntrinsicMemoryOps
-{
-  typedef typename AtomicStorageType<T>::Type ValueType;
-
-  static T load(const ValueType& aPtr)
-  {
-    Barrier<Order>::beforeLoad();
-    T val = T(aPtr);
-    Barrier<Order>::afterLoad();
-    return val;
-  }
-
-  static void store(ValueType& aPtr, T aVal)
-  {
-    Barrier<Order>::beforeStore();
-    aPtr = ValueType(aVal);
-    Barrier<Order>::afterStore();
-  }
-
-  static T exchange(ValueType& aPtr, T aVal)
-  {
-    // __sync_lock_test_and_set is only an acquire barrier; loads and stores
-    // can't be moved up from after to before it, but they can be moved down
-    // from before to after it.  We may want a stricter ordering, so we need
-    // an explicit barrier.
-    Barrier<Order>::beforeStore();
-    return T(__sync_lock_test_and_set(&aPtr, ValueType(aVal)));
-  }
-
-  static bool compareExchange(ValueType& aPtr, T aOldVal, T aNewVal)
-  {
-    return __sync_bool_compare_and_swap(&aPtr, ValueType(aOldVal), ValueType(aNewVal));
-  }
-};
-
-template<typename T, MemoryOrdering Order>
-struct IntrinsicAddSub
-  : public IntrinsicMemoryOps<T, Order>
-{
-  typedef IntrinsicMemoryOps<T, Order> Base;
-  typedef typename Base::ValueType ValueType;
-
-  static T add(ValueType& aPtr, T aVal)
-  {
-    return T(__sync_fetch_and_add(&aPtr, ValueType(aVal)));
-  }
-
-  static T sub(ValueType& aPtr, T aVal)
-  {
-    return T(__sync_fetch_and_sub(&aPtr, ValueType(aVal)));
-  }
-};
-
-template<typename T, MemoryOrdering Order>
-struct IntrinsicAddSub<T*, Order>
-  : public IntrinsicMemoryOps<T*, Order>
-{
-  typedef IntrinsicMemoryOps<T*, Order> Base;
-  typedef typename Base::ValueType ValueType;
-
-  /*
-   * The reinterpret_casts are needed so that
-   * __sync_fetch_and_{add,sub} will properly type-check.
-   *
-   * Also, these functions do not provide standard semantics for
-   * pointer types, so we need to adjust the addend.
-   */
-  static ValueType add(ValueType& aPtr, ptrdiff_t aVal)
-  {
-    ValueType amount = reinterpret_cast<ValueType>(aVal * sizeof(T));
-    return __sync_fetch_and_add(&aPtr, amount);
-  }
-
-  static ValueType sub(ValueType& aPtr, ptrdiff_t aVal)
-  {
-    ValueType amount = reinterpret_cast<ValueType>(aVal * sizeof(T));
-    return __sync_fetch_and_sub(&aPtr, amount);
-  }
-};
-
-template<typename T, MemoryOrdering Order>
-struct IntrinsicIncDec : public IntrinsicAddSub<T, Order>
-{
-  typedef IntrinsicAddSub<T, Order> Base;
-  typedef typename Base::ValueType ValueType;
-
-  static T inc(ValueType& aPtr) { return Base::add(aPtr, 1); }
-  static T dec(ValueType& aPtr) { return Base::sub(aPtr, 1); }
-};
-
-template<typename T, MemoryOrdering Order>
-struct AtomicIntrinsics : public IntrinsicIncDec<T, Order>
-{
-  static T or_( T& aPtr, T aVal) { return __sync_fetch_and_or(&aPtr, aVal); }
-  static T xor_(T& aPtr, T aVal) { return __sync_fetch_and_xor(&aPtr, aVal); }
-  static T and_(T& aPtr, T aVal) { return __sync_fetch_and_and(&aPtr, aVal); }
-};
-
-template<typename T, MemoryOrdering Order>
-struct AtomicIntrinsics<T*, Order> : public IntrinsicIncDec<T*, Order>
-{
-};
-
-template<typename T, bool TIsEnum = IsEnum<T>::value>
-struct ToStorageTypeArgument
-{
-  typedef typename AtomicStorageType<T>::Type ResultType;
-
-  static constexpr ResultType convert (T aT) { return ResultType(aT); }
-};
-
-template<typename T>
-struct ToStorageTypeArgument<T, false>
-{
-  static constexpr T convert (T aT) { return aT; }
-};
-
-} // namespace detail
-} // namespace mozilla
-
-#else
-# error "Atomic compiler intrinsics are not supported on your platform"
-#endif
-
-namespace mozilla {
-
-namespace detail {
-
 template<typename T, MemoryOrdering Order>
 class AtomicBase
 {
   static_assert(sizeof(T) == 4 || sizeof(T) == 8,
                 "mozilla/Atomics.h only supports 32-bit and 64-bit types");
 
 protected:
   typedef typename detail::AtomicIntrinsics<T, Order> Intrinsics;
--- a/netwerk/base/nsBaseChannel.cpp
+++ b/netwerk/base/nsBaseChannel.cpp
@@ -259,19 +259,21 @@ nsBaseChannel::BeginPumpingData()
       return rv;
   }
 
   // By assigning mPump, we flag this channel as pending (see Pending).  It's
   // important that the pending flag is set when we call into the stream (the
   // call to AsyncRead results in the stream's AsyncWait method being called)
   // and especially when we call into the loadgroup.  Our caller takes care to
   // release mPump if we return an error.
- 
+
+  nsCOMPtr<nsIEventTarget> target =
+    nsContentUtils::GetEventTargetByLoadInfo(mLoadInfo, TaskCategory::Other);
   rv = nsInputStreamPump::Create(getter_AddRefs(mPump), stream, -1, -1, 0, 0,
-                                 true);
+                                 true, target);
   if (NS_SUCCEEDED(rv)) {
     mPumpingData = true;
     mRequest = mPump;
     rv = mPump->AsyncRead(this, nullptr);
   }
 
   return rv;
 }
--- a/netwerk/base/nsIInputStreamPump.idl
+++ b/netwerk/base/nsIInputStreamPump.idl
@@ -1,14 +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 "nsIRequest.idl"
 
+interface nsIEventTarget;
 interface nsIInputStream;
 interface nsIStreamListener;
 
 /**
  * nsIInputStreamPump
  *
  * This interface provides a means to configure and use a input stream pump
  * instance.  The input stream pump will asynchronously read from an input
@@ -46,23 +47,26 @@ interface nsIInputStreamPump : nsIReques
      *        specifies the segment size for the stream transport's buffer.
      *        pass 0 to specify the default value.
      * @param aSegmentCount
      *        if the stream transport service is used, then this parameter
      *        specifies the segment count for the stream transport's buffer.
      *        pass 0 to specify the default value.
      * @param aCloseWhenDone
      *        if true, the input stream will be closed after it has been read.
+     * @param aMainThreadTarget
+     *        a labeled main therad event target.
      */
     void init(in nsIInputStream aStream,
               in long long      aStreamPos,
               in long long      aStreamLen,
               in unsigned long  aSegmentSize,
               in unsigned long  aSegmentCount,
-              in boolean        aCloseWhenDone);
+              in boolean        aCloseWhenDone,
+              [optional] in nsIEventTarget aMainThreadTarget);
 
     /**
      * asyncRead causes the input stream to be read in chunks and delivered
      * asynchronously to the listener via OnDataAvailable.
      *
      * @param aListener
      *        receives notifications.
      * @param aListenerContext
--- a/netwerk/base/nsInputStreamPump.cpp
+++ b/netwerk/base/nsInputStreamPump.cpp
@@ -54,23 +54,24 @@ nsInputStreamPump::~nsInputStreamPump()
 
 nsresult
 nsInputStreamPump::Create(nsInputStreamPump  **result,
                           nsIInputStream      *stream,
                           int64_t              streamPos,
                           int64_t              streamLen,
                           uint32_t             segsize,
                           uint32_t             segcount,
-                          bool                 closeWhenDone)
+                          bool                 closeWhenDone,
+                          nsIEventTarget      *mainThreadTarget)
 {
     nsresult rv = NS_ERROR_OUT_OF_MEMORY;
     RefPtr<nsInputStreamPump> pump = new nsInputStreamPump();
     if (pump) {
         rv = pump->Init(stream, streamPos, streamLen,
-                        segsize, segcount, closeWhenDone);
+                        segsize, segcount, closeWhenDone, mainThreadTarget);
         if (NS_SUCCEEDED(rv)) {
             pump.forget(result);
         }
     }
     return rv;
 }
 
 struct PeekData {
@@ -125,17 +126,19 @@ nsInputStreamPump::EnsureWaiting()
     mMonitor.AssertCurrentThreadIn();
 
     // no need to worry about multiple threads... an input stream pump lives
     // on only one thread at a time.
     MOZ_ASSERT(mAsyncStream);
     if (!mWaitingForInputStreamReady && !mProcessingCallbacks) {
         // Ensure OnStateStop is called on the main thread.
         if (mState == STATE_STOP) {
-            nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+            nsCOMPtr<nsIEventTarget> mainThread = mLabeledMainThreadTarget
+                ? mLabeledMainThreadTarget
+                : do_GetMainThread();
             if (mTargetThread != mainThread) {
                 mTargetThread = do_QueryInterface(mainThread);
             }
         }
         MOZ_ASSERT(mTargetThread);
         nsresult rv = mAsyncStream->AsyncWait(this, 0, 0, mTargetThread);
         if (NS_FAILED(rv)) {
             NS_ERROR("AsyncWait failed");
@@ -291,27 +294,28 @@ nsInputStreamPump::SetLoadGroup(nsILoadG
 //-----------------------------------------------------------------------------
 // nsInputStreamPump::nsIInputStreamPump implementation
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 nsInputStreamPump::Init(nsIInputStream *stream,
                         int64_t streamPos, int64_t streamLen,
                         uint32_t segsize, uint32_t segcount,
-                        bool closeWhenDone)
+                        bool closeWhenDone, nsIEventTarget *mainThreadTarget)
 {
     NS_ENSURE_TRUE(mState == STATE_IDLE, NS_ERROR_IN_PROGRESS);
 
     mStreamOffset = uint64_t(streamPos);
     if (int64_t(streamLen) >= int64_t(0))
         mStreamLength = uint64_t(streamLen);
     mStream = stream;
     mSegSize = segsize;
     mSegCount = segcount;
     mCloseWhenDone = closeWhenDone;
+    mLabeledMainThreadTarget = mainThreadTarget;
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsInputStreamPump::AsyncRead(nsIStreamListener *listener, nsISupports *ctxt)
 {
     ReentrantMonitorAutoEnter mon(mMonitor);
@@ -371,17 +375,21 @@ nsInputStreamPump::AsyncRead(nsIStreamLi
     mStream = nullptr;
 
     // mStreamOffset now holds the number of bytes currently read.  we use this
     // to enforce the mStreamLength restriction.
     mStreamOffset = 0;
 
     // grab event queue (we must do this here by contract, since all notifications
     // must go to the thread which called AsyncRead)
-    mTargetThread = do_GetCurrentThread();
+    if (NS_IsMainThread() && mLabeledMainThreadTarget) {
+        mTargetThread = mLabeledMainThreadTarget;
+    } else {
+        mTargetThread = do_GetCurrentThread();
+    }
     NS_ENSURE_STATE(mTargetThread);
 
     rv = EnsureWaiting();
     if (NS_FAILED(rv)) return rv;
 
     if (mLoadGroup)
         mLoadGroup->AddRequest(this, nullptr);
 
--- a/netwerk/base/nsInputStreamPump.h
+++ b/netwerk/base/nsInputStreamPump.h
@@ -35,17 +35,18 @@ public:
 
     static nsresult
                       Create(nsInputStreamPump  **result,
                              nsIInputStream      *stream,
                              int64_t              streamPos = -1,
                              int64_t              streamLen = -1,
                              uint32_t             segsize = 0,
                              uint32_t             segcount = 0,
-                             bool                 closeWhenDone = false);
+                             bool                 closeWhenDone = false,
+                             nsIEventTarget      *mainThreadTarget = nullptr);
 
     typedef void (*PeekSegmentFun)(void *closure, const uint8_t *buf,
                                    uint32_t bufLen);
     /**
      * Peek into the first chunk of data that's in the stream. Note that this
      * method will not call the callback when there is no data in the stream.
      * The callback will be called at most once.
      *
@@ -77,16 +78,17 @@ protected:
     uint32_t OnStateStop();
     nsresult CreateBufferedStreamIfNeeded();
 
     uint32_t                      mState;
     nsCOMPtr<nsILoadGroup>        mLoadGroup;
     nsCOMPtr<nsIStreamListener>   mListener;
     nsCOMPtr<nsISupports>         mListenerContext;
     nsCOMPtr<nsIEventTarget>      mTargetThread;
+    nsCOMPtr<nsIEventTarget>      mLabeledMainThreadTarget;
     nsCOMPtr<nsIInputStream>      mStream;
     nsCOMPtr<nsIAsyncInputStream> mAsyncStream;
     nsCOMPtr<nsIInputStream>      mBufferedStream;
     uint64_t                      mStreamOffset;
     uint64_t                      mStreamLength;
     uint32_t                      mSegSize;
     uint32_t                      mSegCount;
     nsresult                      mStatus;
--- a/netwerk/base/nsNetUtil.cpp
+++ b/netwerk/base/nsNetUtil.cpp
@@ -713,24 +713,25 @@ NS_NewInputStreamChannel(nsIChannel     
 
 nsresult
 NS_NewInputStreamPump(nsIInputStreamPump **result,
                       nsIInputStream      *stream,
                       int64_t              streamPos /* = int64_t(-1) */,
                       int64_t              streamLen /* = int64_t(-1) */,
                       uint32_t             segsize /* = 0 */,
                       uint32_t             segcount /* = 0 */,
-                      bool                 closeWhenDone /* = false */)
+                      bool                 closeWhenDone /* = false */,
+                      nsIEventTarget      *mainThreadTarget /* = nullptr */)
 {
     nsresult rv;
     nsCOMPtr<nsIInputStreamPump> pump =
         do_CreateInstance(NS_INPUTSTREAMPUMP_CONTRACTID, &rv);
     if (NS_SUCCEEDED(rv)) {
         rv = pump->Init(stream, streamPos, streamLen,
-                        segsize, segcount, closeWhenDone);
+                        segsize, segcount, closeWhenDone, mainThreadTarget);
         if (NS_SUCCEEDED(rv)) {
             *result = nullptr;
             pump.swap(*result);
         }
     }
     return rv;
 }
 
--- a/netwerk/base/nsNetUtil.h
+++ b/netwerk/base/nsNetUtil.h
@@ -282,17 +282,18 @@ nsresult NS_NewInputStreamChannel(nsICha
                                   bool                aIsSrcdocChannel = false);
 
 nsresult NS_NewInputStreamPump(nsIInputStreamPump **result,
                                nsIInputStream      *stream,
                                int64_t              streamPos = int64_t(-1),
                                int64_t              streamLen = int64_t(-1),
                                uint32_t             segsize = 0,
                                uint32_t             segcount = 0,
-                               bool                 closeWhenDone = false);
+                               bool                 closeWhenDone = false,
+                               nsIEventTarget      *mainThreadTarget = nullptr);
 
 // NOTE: you will need to specify whether or not your streams are buffered
 // (i.e., do they implement ReadSegments/WriteSegments).  the default
 // assumption of TRUE for both streams might not be right for you!
 nsresult NS_NewAsyncStreamCopier(nsIAsyncStreamCopier **result,
                                  nsIInputStream        *source,
                                  nsIOutputStream       *sink,
                                  nsIEventTarget        *target,
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -3246,19 +3246,23 @@ HttpChannelChild::OverrideWithSynthesize
   uint64_t available;
   nsresult rv = aSynthesizedInput->Available(&available);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     mSynthesizedStreamLength = -1;
   } else {
     mSynthesizedStreamLength = int64_t(available);
   }
 
+  nsCOMPtr<nsIEventTarget> neckoTarget = GetNeckoTarget();
+  MOZ_ASSERT(neckoTarget);
+
   rv = nsInputStreamPump::Create(getter_AddRefs(mSynthesizedResponsePump),
                                  aSynthesizedInput,
-                                 int64_t(-1), int64_t(-1), 0, 0, true);
+                                 int64_t(-1), int64_t(-1), 0, 0, true,
+                                 neckoTarget);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     aSynthesizedInput->Close();
     return;
   }
 
   rv = mSynthesizedResponsePump->AsyncRead(aStreamListener, nullptr);
   NS_ENSURE_SUCCESS_VOID(rv);
 
--- a/netwerk/protocol/wyciwyg/nsWyciwygProtocolHandler.cpp
+++ b/netwerk/protocol/wyciwyg/nsWyciwygProtocolHandler.cpp
@@ -87,16 +87,21 @@ nsWyciwygProtocolHandler::NewChannel2(ns
 
   NS_ENSURE_ARG_POINTER(url);
   nsresult rv;
 
   nsCOMPtr<nsIWyciwygChannel> channel;
   if (IsNeckoChild()) {
     NS_ENSURE_TRUE(gNeckoChild != nullptr, NS_ERROR_FAILURE);
 
+    ContentChild* cc = static_cast<ContentChild*>(gNeckoChild->Manager());
+    if (cc->IsShuttingDown()) {
+      return NS_ERROR_FAILURE;
+    }
+
     WyciwygChannelChild *wcc = static_cast<WyciwygChannelChild *>(
                                  gNeckoChild->SendPWyciwygChannelConstructor());
     if (!wcc)
       return NS_ERROR_OUT_OF_MEMORY;
 
     channel = wcc;
     rv = wcc->Init(url);
     if (NS_FAILED(rv))
--- a/nsprpub/TAG-INFO
+++ b/nsprpub/TAG-INFO
@@ -1,1 +1,1 @@
-NSPR_4_15_BETA1
+NSPR_4_15_BETA2
--- a/nsprpub/config/prdepend.h
+++ b/nsprpub/config/prdepend.h
@@ -5,8 +5,9 @@
 
 /*
  * A dummy header file that is a dependency for all the object files.
  * Used to force a full recompilation of NSPR in Mozilla's Tinderbox
  * depend builds.  See comments in rules.mk.
  */
 
 #error "Do not include this header file."
+
--- a/nsprpub/pr/include/prtypes.h
+++ b/nsprpub/pr/include/prtypes.h
@@ -226,17 +226,17 @@ PR_BEGIN_EXTERN_C
 **
 ** NOTE: NSPR can't use <stdint.h> because C99 requires C++ code to define
 ** __STDC_LIMIT_MACROS and __STDC_CONSTANT_MACROS to make all the macros
 ** defined in <stdint.h> available. This strange requirement is gone in
 ** C11. When most platforms ignore this C99 requirement, NSPR will be able
 ** to use <stdint.h>. A patch to do that is in NSPR bug 634793.
 */
 
-#if defined(__APPLE__) || defined(__ANDROID__) || defined(__OpenBSD__)
+#if defined(__APPLE__) || defined(__OpenBSD__)
 #define PR_ALTERNATE_INT64_TYPEDEF
 #endif
 
 /************************************************************************
 ** TYPES:       PRUint8
 **              PRInt8
 ** DESCRIPTION:
 **  The int8 types are known to be 8 bits each. There is no type that
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -61976,16 +61976,26 @@
      {}
     ]
    ],
    "service-workers/service-worker/resources/blank.html": [
     [
      {}
     ]
    ],
+   "service-workers/service-worker/resources/bytecheck-worker-imported-script.py": [
+    [
+     {}
+    ]
+   ],
+   "service-workers/service-worker/resources/bytecheck-worker.py": [
+    [
+     {}
+    ]
+   ],
    "service-workers/service-worker/resources/claim-with-redirect-iframe.html": [
     [
      {}
     ]
    ],
    "service-workers/service-worker/resources/claim-worker.js": [
     [
      {}
@@ -122841,16 +122851,22 @@
     ]
    ],
    "service-workers/service-worker/update-after-oneday.https.html": [
     [
      "/service-workers/service-worker/update-after-oneday.https.html",
      {}
     ]
    ],
+   "service-workers/service-worker/update-bytecheck.https.html": [
+    [
+     "/service-workers/service-worker/update-bytecheck.https.html",
+     {}
+    ]
+   ],
    "service-workers/service-worker/update-recovery.https.html": [
     [
      "/service-workers/service-worker/update-recovery.https.html",
      {}
     ]
    ],
    "service-workers/service-worker/update.https.html": [
     [
@@ -206404,16 +206420,24 @@
   "service-workers/service-worker/resources/appcache-ordering.manifest": [
    "963e8436d3c2571fb971f05aaa4710f5d3dd7abe",
    "support"
   ],
   "service-workers/service-worker/resources/blank.html": [
    "0ddb4f1cf84729ed673295719ec58a3e5d600a12",
    "support"
   ],
+  "service-workers/service-worker/resources/bytecheck-worker-imported-script.py": [
+   "772d029d4efbe22f62f3473d4afe9e501a792571",
+   "support"
+  ],
+  "service-workers/service-worker/resources/bytecheck-worker.py": [
+   "2693790af1dcd812bc3741db7fa355e23eef0e01",
+   "support"
+  ],
   "service-workers/service-worker/resources/claim-with-redirect-iframe.html": [
    "fdc472f4e9a591f0b471174b2aa1783107731f49",
    "support"
   ],
   "service-workers/service-worker/resources/claim-worker.js": [
    "e779a28c42928ff10219073171c1216c6623b4d4",
    "support"
   ],
@@ -207072,16 +207096,20 @@
   "service-workers/service-worker/update-after-navigation-fetch-event.https.html": [
    "14d79d1008193c96f0eadaf3e47ef321a429b905",
    "testharness"
   ],
   "service-workers/service-worker/update-after-oneday.https.html": [
    "7c8c6c3edca83d54f1838eccf3afb0b1223c7a44",
    "testharness"
   ],
+  "service-workers/service-worker/update-bytecheck.https.html": [
+   "6562348b198124822297c6b622c3e63870427672",
+   "testharness"
+  ],
   "service-workers/service-worker/update-recovery.https.html": [
    "aac5705d6844e4a33200418504adb57053a45be2",
    "testharness"
   ],
   "service-workers/service-worker/update.https.html": [
    "73c129838eda3c18365f384b8b0a5c32b5e9cf6d",
    "testharness"
   ],
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/bytecheck-worker-imported-script.py
@@ -0,0 +1,19 @@
+import time
+
+def main(request, response):
+    headers = [('Content-Type', 'application/javascript'),
+               ('Cache-Control', 'max-age=0')]
+
+    imported_content_type = ''
+    if 'imported' in request.GET:
+        imported_content_type = request.GET['imported']
+
+    imported_content = 'default'
+    if imported_content_type == 'time':
+        imported_content = time.time()
+
+    body = '''
+    // %s
+    ''' % (imported_content)
+
+    return headers, body
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/resources/bytecheck-worker.py
@@ -0,0 +1,28 @@
+import time
+
+def main(request, response):
+    headers = [('Content-Type', 'application/javascript'),
+               ('Cache-Control', 'max-age=0')]
+
+    main_content_type = ''
+    if 'main' in request.GET:
+        main_content_type = request.GET['main']
+
+    main_content = 'default'
+    if main_content_type == 'time':
+        main_content = time.time()
+
+    imported_request_type = ''
+    if 'imported' in request.GET:
+        imported_request_type = request.GET['imported']
+
+    imported_request = ''
+    if imported_request_type == 'time':
+        imported_request = '?imported=time';
+
+    body = '''
+    // %s
+    importScripts('bytecheck-worker-imported-script.py%s');
+    ''' % (main_content, imported_request)
+
+    return headers, body
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/service-workers/service-worker/update-bytecheck.https.html
@@ -0,0 +1,127 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<script src="/resources/testharness.js"></script>
+<script src="resources/testharness-helpers.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script>
+
+promise_test(function(t) {
+    var script = 'resources/bytecheck-worker.py' +
+                 '?main=default' +
+                 '&imported=default';
+    var scope = 'resources/blank.html';
+
+    var swr, sw;
+    return Promise.resolve()
+        .then(() => service_worker_unregister_and_register(t, script, scope))
+        .then(registration => swr = registration)
+
+        .then(() => wait_for_update(t, swr))
+        .then(worker => sw = worker)
+
+        .then(() => wait_for_state(t, sw, 'activated'))
+        .then(() => assert_array_equals([swr.active,
+                                         swr.waiting,
+                                         swr.installing],
+                                        [sw,
+                                         null,
+                                         null]))
+
+        .then(() => swr.update())
+        .then(() => assert_array_equals([swr.active,
+                                         swr.waiting,
+                                         swr.installing],
+                                        [sw,
+                                         null,
+                                         null]))
+
+        .then(() => service_worker_unregister_and_done(t, scope));
+}, "Test with (main: same, imported: same)");
+
+promise_test(function(t) {
+    var script = 'resources/bytecheck-worker.py' +
+                 '?main=default' +
+                 '&imported=time';
+    var scope = 'resources/blank.html';
+
+    var swr, sw;
+    return Promise.resolve()
+        .then(() => service_worker_unregister_and_register(t, script, scope))
+        .then(registration => swr = registration)
+
+        .then(() => wait_for_update(t, swr))
+        .then(worker => sw = worker)
+
+        .then(() => wait_for_state(t, sw, 'activated'))
+        .then(() => assert_array_equals([swr.active,
+                                         swr.waiting,
+                                         swr.installing],
+                                        [sw,
+                                         null,
+                                         null]))
+
+        .then(() => swr.update())
+        .then(() => wait_for_update(t, swr))
+
+        .then(() => service_worker_unregister_and_done(t, scope));
+}, "Test with (main: same, imported: different)");
+
+promise_test(function(t) {
+    var script = 'resources/bytecheck-worker.py' +
+                 '?main=time' +
+                 '&imported=default';
+    var scope = 'resources/blank.html';
+
+    var swr, sw;
+    return Promise.resolve()
+        .then(() => service_worker_unregister_and_register(t, script, scope))
+        .then(registration => swr = registration)
+
+        .then(() => wait_for_update(t, swr))
+        .then(worker => sw = worker)
+
+        .then(() => wait_for_state(t, sw, 'activated'))
+        .then(() => assert_array_equals([swr.active,
+                                         swr.waiting,
+                                         swr.installing],
+                                        [sw,
+                                         null,
+                                         null]))
+
+        .then(() => swr.update())
+        .then(() => wait_for_update(t, swr))
+
+        .then(() => service_worker_unregister_and_done(t, scope));
+}, "Test with (main: different, imported: same)");
+
+promise_test(function(t) {
+    var script = 'resources/bytecheck-worker.py' +
+                 '?main=time' +
+                 '&imported=time';
+    var scope = 'resources/blank.html';
+
+    var swr, sw;
+    return Promise.resolve()
+        .then(() => service_worker_unregister_and_register(t, script, scope))
+        .then(registration => swr = registration)
+
+        .then(() => wait_for_update(t, swr))
+        .then(worker => sw = worker)
+
+        .then(() => wait_for_state(t, sw, 'activated'))
+        .then(() => assert_array_equals([swr.active,
+                                         swr.waiting,
+                                         swr.installing],
+                                        [sw,
+                                         null,
+                                         null]))
+
+        .then(() => swr.update())
+        .then(() => wait_for_update(t, swr))
+
+        .then(() => service_worker_unregister_and_done(t, scope));
+}, "Test with (main: different, imported: different)");
+
+</script>
--- a/toolkit/components/places/nsAnnoProtocolHandler.cpp
+++ b/toolkit/components/places/nsAnnoProtocolHandler.cpp
@@ -127,36 +127,38 @@ public:
     NS_ENSURE_TRUE(mListener, NS_ERROR_UNEXPECTED);
 
     nsresult rv;
     // Ensure we'll break possible cycles with the listener.
     auto cleanup = MakeScopeExit([&] () {
       mListener = nullptr;
     });
 
+    nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
+    nsCOMPtr<nsIEventTarget> target =
+      nsContentUtils::GetEventTargetByLoadInfo(loadInfo, TaskCategory::Other);
     if (!mData.IsEmpty()) {
       nsCOMPtr<nsIInputStream> stream;
       rv = NS_NewCStringInputStream(getter_AddRefs(stream), mData);
       MOZ_ASSERT(NS_SUCCEEDED(rv));
       if (NS_SUCCEEDED(rv)) {
         RefPtr<nsInputStreamPump> pump;
         rv = nsInputStreamPump::Create(getter_AddRefs(pump), stream, -1, -1, 0, 0,
-                                      true);
+                                       true, target);
         MOZ_ASSERT(NS_SUCCEEDED(rv));
         if (NS_SUCCEEDED(rv)) {
           return pump->AsyncRead(mListener, nullptr);
         }
       }
     }
 
     // Fallback to the default favicon.
     // we should pass the loadInfo of the original channel along
     // to the new channel. Note that mChannel can not be null,
     // constructor checks that.
-    nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
     nsCOMPtr<nsIChannel> newChannel;
     rv = GetDefaultIcon(loadInfo, getter_AddRefs(newChannel));
     if (NS_FAILED(rv)) {
       mListener->OnStartRequest(mChannel, nullptr);
       mListener->OnStopRequest(mChannel, nullptr, rv);
       return rv;
     }
     return newChannel->AsyncOpen2(mListener);
--- a/toolkit/moz.configure
+++ b/toolkit/moz.configure
@@ -533,16 +533,40 @@ project_flag('MOZ_SERVICES_HEALTHREPORT'
 
 project_flag('MOZ_SERVICES_SYNC',
              help='Build Sync Services if required')
 
 project_flag('MOZ_ANDROID_HISTORY',
              help='Enable Android History instead of Places',
              set_as_define=True)
 
+option(env='MOZ_PHOTON_ANIMATIONS',
+       help='Enable Photon UI animations',
+       default=is_nightly)
+
+@depends('MOZ_PHOTON_ANIMATIONS')
+def photon_animations(value):
+    if value:
+        return bool(value)
+
+set_config('MOZ_PHOTON_ANIMATIONS', photon_animations)
+set_define('MOZ_PHOTON_ANIMATIONS', photon_animations)
+
+option(env='MOZ_PHOTON_THEME',
+       help='Enable Photon theme',
+       default=is_nightly)
+
+@depends('MOZ_PHOTON_THEME')
+def photon_theme(value):
+    if value:
+        return bool(value)
+
+set_config('MOZ_PHOTON_THEME', photon_theme)
+set_define('MOZ_PHOTON_THEME', photon_theme)
+
 @depends('MOZ_PLACES', 'MOZ_ANDROID_HISTORY')
 def check_places_and_android_history(places, android_history):
     if places and android_history:
         die('Cannot use MOZ_ANDROID_HISTORY alongside MOZ_PLACES.')
 
 # Permissions system
 # ==============================================================
 option(name='--disable-permissions',
--- a/widget/android/AndroidUiThread.cpp
+++ b/widget/android/AndroidUiThread.cpp
@@ -15,16 +15,17 @@
 
 using namespace mozilla;
 
 namespace {
 
 class AndroidUiThread;
 
 StaticRefPtr<AndroidUiThread> sThread;
+static bool sThreadDestroyed;
 static MessageLoop* sMessageLoop;
 static Atomic<Monitor*> sMessageLoopAccessMonitor;
 
 /*
  * The AndroidUiThread is derived from nsThread so that nsIRunnable objects that get
  * dispatched may be intercepted. Only nsIRunnable objects that need to be synchronously
  * executed are passed into the nsThread to be queued. All other nsIRunnable object
  * are immediately dispatched to the Android UI thread via the AndroidBridge.
@@ -42,16 +43,24 @@ class AndroidUiThread : public nsThread
 public:
   NS_DECL_ISUPPORTS_INHERITED
   AndroidUiThread() : nsThread(nsThread::NOT_MAIN_THREAD, 0)
   {}
 
   nsresult Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags) override;
   nsresult DelayedDispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aDelayMs) override;
 
+  static int64_t RunDelayedTasksIfValid() {
+    if (!AndroidBridge::Bridge() ||
+        sThreadDestroyed) {
+      return -1;
+    }
+    return AndroidBridge::Bridge()->RunDelayedUiThreadTasks();
+  }
+
 private:
   ~AndroidUiThread()
   {}
 };
 
 NS_IMPL_ISUPPORTS_INHERITED0(AndroidUiThread, nsThread)
 
 NS_IMETHODIMP
@@ -113,16 +122,17 @@ ThreadObserver::AfterProcessNextEvent(ns
 }
 
 class CreateOnUiThread : public Runnable {
 public:
   CreateOnUiThread()
   {}
 
   NS_IMETHOD Run() override {
+    MOZ_ASSERT(!sThreadDestroyed);
     MOZ_ASSERT(sMessageLoopAccessMonitor);
     MonitorAutoLock lock(*sMessageLoopAccessMonitor);
     sThread = new AndroidUiThread();
     sThread->InitCurrentThread();
     sThread->SetObserver(new ThreadObserver());
     sMessageLoop = new MessageLoop(MessageLoop::TYPE_MOZILLA_ANDROID_UI, sThread.get());
     lock.NotifyAll();
     return NS_OK;
@@ -130,25 +140,27 @@ public:
 };
 
 class DestroyOnUiThread : public Runnable {
 public:
   DestroyOnUiThread() : mDestroyed(false)
   {}
 
   NS_IMETHOD Run() override {
+    MOZ_ASSERT(!sThreadDestroyed);
     MOZ_ASSERT(sMessageLoopAccessMonitor);
     MonitorAutoLock lock(*sMessageLoopAccessMonitor);
 
     delete sMessageLoop;
     sMessageLoop = nullptr;
     MOZ_ASSERT(sThread);
     nsThreadManager::get().UnregisterCurrentThread(*sThread);
     sThread = nullptr;
     mDestroyed = true;
+    sThreadDestroyed = true;
     lock.NotifyAll();
     return NS_OK;
   }
 
   void WaitForDestruction()
   {
     MOZ_ASSERT(sMessageLoopAccessMonitor);
     MonitorAutoLock lock(*sMessageLoopAccessMonitor);
@@ -166,16 +178,17 @@ private:
 namespace mozilla {
 
 void
 CreateAndroidUiThread()
 {
   MOZ_ASSERT(!sThread);
   MOZ_ASSERT(!sMessageLoopAccessMonitor);
   sMessageLoopAccessMonitor = new Monitor("AndroidUiThreadMessageLoopAccessMonitor");
+  sThreadDestroyed = false;
   RefPtr<CreateOnUiThread> runnable = new CreateOnUiThread;
   AndroidBridge::Bridge()->PostTaskToUiThread(do_AddRef(runnable), 0);
 }
 
 void
 DestroyAndroidUiThread()
 {
   MOZ_ASSERT(sThread);
--- a/widget/android/nsAppShell.cpp
+++ b/widget/android/nsAppShell.cpp
@@ -234,21 +234,17 @@ public:
                 category.get(),
                 nullptr, // aOrigin
                 category.get(),
                 aData ? aData->ToString().get() : nullptr);
     }
 
     static int64_t RunUiThreadCallback()
     {
-        if (!AndroidBridge::Bridge()) {
-            return -1;
-        }
-
-        return AndroidBridge::Bridge()->RunDelayedUiThreadTasks();
+        return AndroidUiThread::RunDelayedTasksIfValid();
     }
 };
 
 int32_t GeckoThreadSupport::sPauseCount;
 
 
 class GeckoAppShellSupport final
     : public java::GeckoAppShell::Natives<GeckoAppShellSupport>
--- a/widget/windows/nsWindow.cpp
+++ b/widget/windows/nsWindow.cpp
@@ -8366,8 +8366,14 @@ DWORD ChildWindow::WindowStyle()
 
 void
 nsWindow::GetCompositorWidgetInitData(mozilla::widget::CompositorWidgetInitData* aInitData)
 {
   aInitData->hWnd() = reinterpret_cast<uintptr_t>(mWnd);
   aInitData->widgetKey() = reinterpret_cast<uintptr_t>(static_cast<nsIWidget*>(this));
   aInitData->transparencyMode() = mTransparencyMode;
 }
+
+bool
+nsWindow::SynchronouslyRepaintOnResize()
+{
+  return !gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled();
+}
--- a/widget/windows/nsWindow.h
+++ b/widget/windows/nsWindow.h
@@ -311,19 +311,17 @@ public:
   virtual void DefaultProcOfPluginEvent(
                  const mozilla::WidgetPluginEvent& aEvent) override;
   virtual nsresult OnWindowedPluginKeyEvent(
                      const mozilla::NativeEventData& aKeyEventData,
                      nsIKeyEventInPluginCallback* aCallback) override;
 
   void GetCompositorWidgetInitData(mozilla::widget::CompositorWidgetInitData* aInitData) override;
   bool IsTouchWindow() const { return mTouchWindow; }
-  bool SynchronouslyRepaintOnResize() override {
-    return false;
-  }
+  bool SynchronouslyRepaintOnResize() override;
 
 protected:
   virtual ~nsWindow();
 
   virtual void WindowUsesOMTC() override;
   virtual void RegisterTouchWindow() override;
 
   // A magic number to identify the FAKETRACKPOINTSCROLLABLE window created
--- a/xpcom/base/CycleCollectedJSContext.cpp
+++ b/xpcom/base/CycleCollectedJSContext.cpp
@@ -57,23 +57,25 @@ namespace mozilla {
 
 CycleCollectedJSContext::CycleCollectedJSContext()
   : mIsPrimaryContext(true)
   , mRuntime(nullptr)
   , mJSContext(nullptr)
   , mDoingStableStates(false)
   , mDisableMicroTaskCheckpoint(false)
 {
+  MOZ_COUNT_CTOR(CycleCollectedJSContext);
   nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
   mOwningThread = thread.forget().downcast<nsThread>().take();
   MOZ_RELEASE_ASSERT(mOwningThread);
 }
 
 CycleCollectedJSContext::~CycleCollectedJSContext()
 {
+  MOZ_COUNT_DTOR(CycleCollectedJSContext);
   // If the allocation failed, here we are.
   if (!mJSContext) {
     return;
   }
 
   mRuntime->RemoveContext(this);
 
   if (mIsPrimaryContext) {
--- a/xpcom/base/CycleCollectedJSRuntime.cpp
+++ b/xpcom/base/CycleCollectedJSRuntime.cpp
@@ -506,16 +506,17 @@ CycleCollectedJSRuntime::CycleCollectedJ
   , mJSZoneCycleCollectorGlobal(sJSZoneCycleCollectorGlobal)
   , mJSRuntime(JS_GetRuntime(aCx))
   , mPrevGCSliceCallback(nullptr)
   , mPrevGCNurseryCollectionCallback(nullptr)
   , mJSHolders(256)
   , mOutOfMemoryState(OOMState::OK)
   , mLargeAllocationFailureState(OOMState::OK)
 {
+  MOZ_COUNT_CTOR(CycleCollectedJSRuntime);
   MOZ_ASSERT(aCx);
   MOZ_ASSERT(mJSRuntime);
 
   if (!JS_AddExtraGCRootsTracer(aCx, TraceBlackJS, this)) {
     MOZ_CRASH("JS_AddExtraGCRootsTracer failed");
   }
   JS_SetGrayGCRootsTracer(aCx, TraceGrayJS, this);
   JS_SetGCCallback(aCx, GCCallback, this);
@@ -556,16 +557,17 @@ void
 CycleCollectedJSRuntime::Shutdown(JSContext* cx)
 {
   JS_RemoveExtraGCRootsTracer(cx, TraceBlackJS, this);
   JS_RemoveExtraGCRootsTracer(cx, TraceGrayJS, this);
 }
 
 CycleCollectedJSRuntime::~CycleCollectedJSRuntime()
 {
+  MOZ_COUNT_DTOR(CycleCollectedJSRuntime);
   MOZ_ASSERT(!mDeferredFinalizerTable.Count());
 }
 
 void
 CycleCollectedJSRuntime::AddContext(CycleCollectedJSContext* aContext)
 {
   mContexts.insertBack(aContext);
 }