Merge inbound to mozilla-central. a=merge
authorBogdan Tara <btara@mozilla.com>
Thu, 19 Apr 2018 01:36:28 +0300
changeset 467890 a0c804993efc599a95e97bea39fa1528fd0195d8
parent 467864 e4c4c815992454d67e947beb0c18aa5202c26579 (current diff)
parent 467889 fb5f1d08489f41235639568b5c53ab65deb752ef (diff)
child 467923 ddbc9ec4e94c36f837c7be493d46a51f92733672
child 467964 c8b6fba2ae947808a8965b2e1254487dbe1b7d48
push id9165
push userasasaki@mozilla.com
push dateThu, 26 Apr 2018 21:04:54 +0000
treeherdermozilla-beta@064c3804de2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone61.0a1
first release with
nightly linux32
a0c804993efc / 61.0a1 / 20180418230818 / files
nightly linux64
a0c804993efc / 61.0a1 / 20180418230818 / files
nightly mac
a0c804993efc / 61.0a1 / 20180418230818 / files
nightly win32
a0c804993efc / 61.0a1 / 20180418230818 / files
nightly win64
a0c804993efc / 61.0a1 / 20180418230818 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
browser/extensions/presentation/bootstrap.js
browser/extensions/presentation/content/PresentationDevicePrompt.jsm
browser/extensions/presentation/install.rdf.in
browser/extensions/presentation/jar.mn
browser/extensions/presentation/locale/en-US/presentation.properties
browser/extensions/presentation/locale/jar.mn
browser/extensions/presentation/locale/moz.build
browser/extensions/presentation/moz.build
browser/extensions/presentation/skin/shared/link.svg
taskcluster/docker/beet-mover/Dockerfile
taskcluster/docker/beet-mover/requirements.txt
testing/mozharness/configs/beetmover/en_us_build.yml.tmpl
testing/mozharness/configs/beetmover/en_us_repackage.yml.tmpl
testing/mozharness/configs/beetmover/en_us_repackage_signing.yml.tmpl
testing/mozharness/configs/beetmover/en_us_signing.yml.tmpl
testing/mozharness/configs/beetmover/l10n_changesets.tmpl
testing/mozharness/configs/beetmover/partials.yml.tmpl
testing/mozharness/configs/beetmover/recompressed_completes.yml.tmpl
testing/mozharness/configs/beetmover/repacks.yml.tmpl
testing/mozharness/configs/beetmover/snap.yml.tmpl
testing/mozharness/configs/beetmover/snap_checksums.yml.tmpl
testing/mozharness/configs/beetmover/source.yml.tmpl
testing/mozharness/configs/beetmover/source_checksums.yml.tmpl
testing/mozharness/configs/beetmover/win32_to_win64_partials.yml.tmpl
testing/mozharness/configs/releases/postrelease_fennec_beta.py
testing/mozharness/configs/releases/postrelease_fennec_release.py
testing/mozharness/configs/releases/postrelease_firefox_beta.py
testing/mozharness/configs/releases/postrelease_firefox_esr52.py
testing/mozharness/configs/releases/postrelease_firefox_release.py
testing/mozharness/scripts/release/beet_mover.py
testing/mozharness/scripts/release/postrelease_bouncer_aliases.py
testing/mozharness/scripts/release/postrelease_mark_as_shipped.py
testing/mozharness/scripts/release/postrelease_version_bump.py
testing/web-platform/meta/css/css-scoping/shadow-cascade-order-001.html.ini
--- a/browser/base/content/test/sanitize/SiteDataTestUtils.jsm
+++ b/browser/base/content/test/sanitize/SiteDataTestUtils.jsm
@@ -1,66 +1,121 @@
 "use strict";
 
 var EXPORTED_SYMBOLS = [
   "SiteDataTestUtils",
 ];
 
+ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
+ChromeUtils.import("resource://testing-common/ContentTask.jsm");
+ChromeUtils.import("resource://testing-common/BrowserTestUtils.jsm");
+
 const {Sanitizer} = ChromeUtils.import("resource:///modules/Sanitizer.jsm", {});
 
+XPCOMUtils.defineLazyServiceGetter(this, "swm",
+                                   "@mozilla.org/serviceworkers/manager;1",
+                                   "nsIServiceWorkerManager");
+
 /**
  * This module assists with tasks around testing functionality that shows
  * or clears site data.
  *
  * Please note that you will have to clean up changes made manually, for
  * example using SiteDataTestUtils.clear().
  */
 var SiteDataTestUtils = {
 
   /**
    * Adds a new entry to a dummy indexedDB database for the specified origin.
    *
    * @param {String} origin - the origin of the site to add test data for
+   * @param {String} name [optional] - the entry key
+   * @param {String} value [optional] - the entry value
    *
    * @returns a Promise that resolves when the data was added successfully.
    */
-  addToIndexedDB(origin) {
+  addToIndexedDB(origin, key = "foo", value = "bar") {
     return new Promise(resolve => {
       let uri = Services.io.newURI(origin);
       let principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
       let request = indexedDB.openForPrincipal(principal, "TestDatabase", 1);
       request.onupgradeneeded = function(e) {
         let db = e.target.result;
         db.createObjectStore("TestStore", { keyPath: "id" });
       };
       request.onsuccess = function(e) {
         let db = e.target.result;
         let tx = db.transaction("TestStore", "readwrite");
         let store = tx.objectStore("TestStore");
         tx.oncomplete = resolve;
-        store.put({ id: performance.now().toString(), description: "IndexedDB Test"});
+        store.put({ id: key, description: value});
       };
     });
   },
 
   /**
    * Adds a new cookie for the specified origin, with the specified contents.
    * The cookie will be valid for one day.
    *
-   * @param {String} name - the cookie name
-   * @param {String} value - the cookie value
+   * @param {String} origin - the origin of the site to add test data for
+   * @param {String} name [optional] - the cookie name
+   * @param {String} value [optional] - the cookie value
    */
-  addToCookies(origin, name, value) {
+  addToCookies(origin, name = "foo", value = "bar") {
     let uri = Services.io.newURI(origin);
     Services.cookies.add(uri.host, uri.pathQueryRef, name, value,
       false, false, false, Date.now() + 24000 * 60 * 60);
   },
 
   /**
+   * Adds a new serviceworker with the specified path. Note that this
+   * method will open a new tab at the domain of the SW path to that effect.
+   *
+   * @param {String} path - the path to the service worker to add.
+   *
+   * @returns a Promise that resolves when the service worker was registered
+   */
+  addServiceWorker(path) {
+    let uri = Services.io.newURI(path);
+    // Register a dummy ServiceWorker.
+    return BrowserTestUtils.withNewTab(uri.prePath, async function(browser) {
+      return ContentTask.spawn(browser, {path}, async ({path: p}) => {
+        let r = await content.navigator.serviceWorker.register(p);
+        return new Promise(resolve => {
+          let worker = r.installing;
+          worker.addEventListener("statechange", () => {
+            if (worker.state === "installed") {
+              resolve();
+            }
+          });
+        });
+      });
+    });
+  },
+
+  /**
+   * Checks whether the specified origin has registered ServiceWorkers.
+   *
+   * @param {String} origin - the origin of the site to check
+   *
+   * @returns {Boolean} whether or not the site has ServiceWorkers.
+   */
+  hasServiceWorkers(origin) {
+    let serviceWorkers = swm.getAllRegistrations();
+    for (let i = 0; i < serviceWorkers.length; i++) {
+      let sw = serviceWorkers.queryElementAt(i, Ci.nsIServiceWorkerRegistrationInfo);
+      if (sw.principal.origin == origin) {
+        return true;
+      }
+    }
+    return false;
+  },
+
+  /**
    * Gets the current quota usage for the specified origin.
    *
    * @returns a Promise that resolves to an integer with the total
    *          amount of disk usage by a given origin.
    */
   getQuotaUsage(origin) {
     return new Promise(resolve => {
       let uri = Services.io.newURI(origin);
--- a/browser/base/content/test/sanitize/browser.ini
+++ b/browser/base/content/test/sanitize/browser.ini
@@ -1,11 +1,13 @@
 [DEFAULT]
 support-files=
   head.js
+  dummy.js
   dummy_page.html
 
 [browser_purgehistory_clears_sh.js]
 [browser_sanitize-formhistory.js]
+[browser_sanitize-offlineData.js]
 [browser_sanitize-passwordDisabledHosts.js]
 [browser_sanitize-sitepermissions.js]
 [browser_sanitize-timespans.js]
 [browser_sanitizeDialog.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/sanitize/browser_sanitize-offlineData.js
@@ -0,0 +1,165 @@
+// Bug 380852 - Delete permission manager entries in Clear Recent History
+
+ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+const {Sanitizer} = ChromeUtils.import("resource:///modules/Sanitizer.jsm", {});
+const {SiteDataTestUtils} = ChromeUtils.import("resource://testing-common/SiteDataTestUtils.jsm", {});
+const {PromiseTestUtils} = ChromeUtils.import("resource://testing-common/PromiseTestUtils.jsm", {});
+
+XPCOMUtils.defineLazyServiceGetter(this, "sas",
+                                   "@mozilla.org/storage/activity-service;1",
+                                   "nsIStorageActivityService");
+XPCOMUtils.defineLazyServiceGetter(this, "swm",
+                                   "@mozilla.org/serviceworkers/manager;1",
+                                   "nsIServiceWorkerManager");
+
+const oneHour = 3600000000;
+const fiveHours = oneHour * 5;
+
+const itemsToClear = [ "cookies", "offlineApps" ];
+
+function hasIndexedDB(origin) {
+  return new Promise(resolve => {
+    let hasData = true;
+    let uri = Services.io.newURI(origin);
+    let principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
+    let request = indexedDB.openForPrincipal(principal, "TestDatabase", 1);
+    request.onupgradeneeded = function(e) {
+      hasData = false;
+    };
+    request.onsuccess = function(e) {
+      resolve(hasData);
+    };
+  });
+}
+
+function waitForUnregister(host) {
+  return new Promise(resolve => {
+    let listener = {
+      onUnregister: registration => {
+        if (registration.principal.URI.host != host) {
+          return;
+        }
+        let swm = Cc["@mozilla.org/serviceworkers/manager;1"]
+                    .getService(Ci.nsIServiceWorkerManager);
+        swm.removeListener(listener);
+        resolve(registration);
+      }
+    };
+    swm.addListener(listener);
+  });
+}
+
+async function createData(host) {
+  let origin = "https://" + host;
+  let dummySWURL = getRootDirectory(gTestPath).replace("chrome://mochitests/content", origin) + "dummy.js";
+
+  await SiteDataTestUtils.addToIndexedDB(origin);
+  await SiteDataTestUtils.addServiceWorker(dummySWURL);
+}
+
+function moveOriginInTime(principals, endDate, host) {
+  for (let i = 0; i < principals.length; ++i) {
+    let principal = principals.queryElementAt(i, Ci.nsIPrincipal);
+    if (principal.URI.host == host) {
+      sas.moveOriginInTime(principal, endDate - fiveHours);
+      return true;
+    }
+  }
+  return false;
+}
+
+add_task(async function testWithRange() {
+  // We have intermittent occurrences of NS_ERROR_ABORT being
+  // thrown at closing database instances when using Santizer.sanitize().
+  // This does not seem to impact cleanup, since our tests run fine anyway.
+  PromiseTestUtils.whitelistRejectionsGlobally(/NS_ERROR_ABORT/);
+
+  await SpecialPowers.pushPrefEnv({"set": [
+    ["dom.serviceWorkers.enabled", true],
+    ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.testing.enabled", true]
+  ]});
+
+  // The service may have picked up activity from prior tests in this run.
+  // Clear it.
+  sas.testOnlyReset();
+
+  let endDate = Date.now() * 1000;
+  let principals = sas.getActiveOrigins(endDate - oneHour, endDate);
+  is(principals.length, 0, "starting from clear activity state");
+
+  info("sanitize: " + itemsToClear.join(", "));
+  await Sanitizer.sanitize(itemsToClear, {ignoreTimespan: false});
+
+  await createData("example.org");
+  await createData("example.com");
+
+  endDate = Date.now() * 1000;
+  principals = sas.getActiveOrigins(endDate - oneHour, endDate);
+  ok(!!principals, "We have an active origin.");
+  ok(principals.length >= 2, "We have an active origin.");
+
+  let found = 0;
+  for (let i = 0; i < principals.length; ++i) {
+    let principal = principals.queryElementAt(i, Ci.nsIPrincipal);
+    if (principal.URI.host == "example.org" ||
+        principal.URI.host == "example.com") {
+      found++;
+    }
+  }
+
+  is(found, 2, "Our origins are active.");
+
+  ok(await hasIndexedDB("https://example.org"),
+    "We have indexedDB data for example.org");
+  ok(SiteDataTestUtils.hasServiceWorkers("https://example.org"),
+    "We have serviceWorker data for example.org");
+
+  ok(await hasIndexedDB("https://example.com"),
+    "We have indexedDB data for example.com");
+  ok(SiteDataTestUtils.hasServiceWorkers("https://example.com"),
+    "We have serviceWorker data for example.com");
+
+  // Let's move example.com in the past.
+  ok(moveOriginInTime(principals, endDate, "example.com"), "Operation completed!");
+
+  let p = waitForUnregister("example.org");
+
+  // Clear it
+  info("sanitize: " + itemsToClear.join(", "));
+  await Sanitizer.sanitize(itemsToClear, {ignoreTimespan: false});
+  await p;
+
+  ok(!(await hasIndexedDB("https://example.org")),
+    "We don't have indexedDB data for example.org");
+  ok(!SiteDataTestUtils.hasServiceWorkers("https://example.org"),
+    "We don't have serviceWorker data for example.org");
+
+  ok(await hasIndexedDB("https://example.com"),
+    "We still have indexedDB data for example.com");
+  ok(SiteDataTestUtils.hasServiceWorkers("https://example.com"),
+    "We still have serviceWorker data for example.com");
+
+  // We have to move example.com in the past because how we check IDB triggers
+  // a storage activity.
+  ok(moveOriginInTime(principals, endDate, "example.com"), "Operation completed!");
+
+  // Let's call the clean up again.
+  info("sanitize again to ensure clearing doesn't expand the activity scope");
+  await Sanitizer.sanitize(itemsToClear, {ignoreTimespan: false});
+
+  ok(await hasIndexedDB("https://example.com"),
+    "We still have indexedDB data for example.com");
+  ok(SiteDataTestUtils.hasServiceWorkers("https://example.com"),
+    "We still have serviceWorker data for example.com");
+
+  ok(!(await hasIndexedDB("https://example.org")),
+    "We don't have indexedDB data for example.org");
+  ok(!SiteDataTestUtils.hasServiceWorkers("https://example.org"),
+    "We don't have serviceWorker data for example.org");
+
+  sas.testOnlyReset();
+
+  // Clean up.
+  await Sanitizer.sanitize(itemsToClear);
+});
new file mode 100644
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -807,17 +807,17 @@ if (Services.prefs.getBoolPref("privacy.
     type: "view",
     viewId: "PanelUI-panicView",
 
     forgetButtonCalled(aEvent) {
       let doc = aEvent.target.ownerDocument;
       let group = doc.getElementById("PanelUI-panic-timeSpan");
       BrowserUITelemetry.countPanicEvent(group.selectedItem.id);
       let itemsToClear = [
-        "cookies", "history", "openWindows", "formdata", "sessions", "cache", "downloads"
+        "cookies", "history", "openWindows", "formdata", "sessions", "cache", "downloads", "offlineApps"
       ];
       let newWindowPrivateState = PrivateBrowsingUtils.isWindowPrivate(doc.defaultView) ?
                                   "private" : "non-private";
       let promise = Sanitizer.sanitize(itemsToClear, {
         ignoreTimespan: false,
         range: Sanitizer.getClearRange(+group.value),
         privateStateForNewWindow: newWindowPrivateState,
       });
--- a/browser/components/extensions/parent/ext-browsingData.js
+++ b/browser/components/extensions/parent/ext-browsingData.js
@@ -8,16 +8,18 @@ ChromeUtils.defineModuleGetter(this, "Pl
 ChromeUtils.defineModuleGetter(this, "Preferences",
                                "resource://gre/modules/Preferences.jsm");
 ChromeUtils.defineModuleGetter(this, "Sanitizer",
                                "resource:///modules/Sanitizer.jsm");
 ChromeUtils.defineModuleGetter(this, "Services",
                                "resource://gre/modules/Services.jsm");
 ChromeUtils.defineModuleGetter(this, "setTimeout",
                                "resource://gre/modules/Timer.jsm");
+ChromeUtils.defineModuleGetter(this, "ServiceWorkerCleanUp",
+                               "resource://gre/modules/ServiceWorkerCleanUp.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "serviceWorkerManager",
                                    "@mozilla.org/serviceworkers/manager;1",
                                    "nsIServiceWorkerManager");
 XPCOMUtils.defineLazyServiceGetter(this, "quotaManagerService",
                                    "@mozilla.org/dom/quota-manager-service;1",
                                    "nsIQuotaManagerService");
 
@@ -140,32 +142,16 @@ const clearPasswords = async function(op
     loginManager.removeAllLogins();
   }
 };
 
 const clearPluginData = options => {
   return Sanitizer.items.pluginData.clear(makeRange(options));
 };
 
-const clearServiceWorkers = async function() {
-  // Clearing service workers does not support timestamps.
-  let yieldCounter = 0;
-
-  // Iterate through the service workers and remove them.
-  let serviceWorkers = serviceWorkerManager.getAllRegistrations();
-  for (let i = 0; i < serviceWorkers.length; i++) {
-    let sw = serviceWorkers.queryElementAt(i, Ci.nsIServiceWorkerRegistrationInfo);
-    let host = sw.principal.URI.host;
-    serviceWorkerManager.removeAndPropagate(host);
-    if (++yieldCounter % YIELD_PERIOD == 0) {
-      await new Promise(resolve => setTimeout(resolve, 0)); // Don't block the main thread too long.
-    }
-  }
-};
-
 const doRemoval = (options, dataToRemove, extension) => {
   if (options.originTypes &&
       (options.originTypes.protectedWeb || options.originTypes.extension)) {
     return Promise.reject(
       {message: "Firefox does not support protectedWeb or extension as originTypes."});
   }
 
   let removalPromises = [];
@@ -196,17 +182,17 @@ const doRemoval = (options, dataToRemove
           break;
         case "passwords":
           removalPromises.push(clearPasswords(options));
           break;
         case "pluginData":
           removalPromises.push(clearPluginData(options));
           break;
         case "serviceWorkers":
-          removalPromises.push(clearServiceWorkers());
+          removalPromises.push(ServiceWorkerCleanUp.removeAll());
           break;
         default:
           invalidDataTypes.push(dataType);
       }
     }
   }
   if (extension && invalidDataTypes.length) {
     extension.logger.warn(
--- a/browser/extensions/moz.build
+++ b/browser/extensions/moz.build
@@ -12,22 +12,16 @@ DIRS += [
     'onboarding',
     'pdfjs',
     'pocket',
     'screenshots',
     'webcompat',
     'webcompat-reporter'
 ]
 
-# Only include the following system add-ons if building Aurora or Nightly
-if not CONFIG['RELEASE_OR_BETA']:
-    DIRS += [
-        'presentation',
-    ]
-
 # Only include mortar system add-ons if we locally enable it
 if CONFIG['MOZ_MORTAR']:
     DIRS += [
         'mortar',
     ]
 
 # Add ASan reporter system add-on if requested
 if CONFIG['MOZ_ASAN_REPORTER']:
deleted file mode 100644
--- a/browser/extensions/presentation/bootstrap.js
+++ /dev/null
@@ -1,86 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-const Cm = Components.manager;
-
-ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-
-const PRESENTATION_DEVICE_PROMPT_PATH =
-  "chrome://presentation/content/PresentationDevicePrompt.jsm";
-
-function log(aMsg) {
-  // dump("@ Presentation: " + aMsg + "\n");
-}
-
-function install(aData, aReason) {
-}
-
-function uninstall(aData, aReason) {
-}
-
-function startup(aData, aReason) {
-  log("startup");
-  Presentation.init();
-}
-
-function shutdown(aData, aReason) {
-  log("shutdown");
-  Presentation.uninit();
-}
-
-// Register/unregister a constructor as a factory.
-function Factory() {}
-Factory.prototype = {
-  register(targetConstructor) {
-    let proto = targetConstructor.prototype;
-    this._classID = proto.classID;
-
-    let factory = XPCOMUtils._getFactory(targetConstructor);
-    this._factory = factory;
-
-    let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
-    registrar.registerFactory(proto.classID, proto.classDescription,
-                              proto.contractID, factory);
-  },
-
-  unregister() {
-    let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
-    registrar.unregisterFactory(this._classID, this._factory);
-    this._factory = null;
-    this._classID = null;
-  },
-};
-
-var Presentation = {
-  // PUBLIC APIs
-  init() {
-    log("init");
-    // Register PresentationDevicePrompt into a XPCOM component.
-    let {PresentationDevicePrompt} = ChromeUtils.import(PRESENTATION_DEVICE_PROMPT_PATH, {});
-    this.PresentationDevicePrompt = PresentationDevicePrompt;
-    this._register();
-  },
-
-  uninit() {
-    log("uninit");
-    // Unregister PresentationDevicePrompt XPCOM component.
-    this._unregister();
-    delete this.PresentationDevicePrompt;
-    Cu.unload(PRESENTATION_DEVICE_PROMPT_PATH);
-  },
-
-  // PRIVATE APIs
-  _register() {
-    log("_register");
-    this._devicePromptFactory = new Factory();
-    this._devicePromptFactory.register(this.PresentationDevicePrompt);
-  },
-
-  _unregister() {
-    log("_unregister");
-    this._devicePromptFactory.unregister();
-    delete this._devicePromptFactory;
-  },
-};
deleted file mode 100644
--- a/browser/extensions/presentation/content/PresentationDevicePrompt.jsm
+++ /dev/null
@@ -1,251 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-/*
- * This is the implementation of nsIPresentationDevicePrompt XPCOM.
- * It will be registered into a XPCOM component by Presentation.jsm.
- *
- * This component will prompt a device selection UI for users to choose which
- * devices they want to connect, when PresentationRequest is started.
- */
-
-"use strict";
-
-var EXPORTED_SYMBOLS = ["PresentationDevicePrompt"];
-
-ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-ChromeUtils.import("resource://gre/modules/Services.jsm");
-
-// An string bundle for localization.
-XPCOMUtils.defineLazyGetter(this, "Strings", function() {
-  return Services.strings.createBundle("chrome://presentation/locale/presentation.properties");
-});
-// To generate a device selection prompt.
-ChromeUtils.defineModuleGetter(this, "PermissionUI",
-                                     "resource:///modules/PermissionUI.jsm");
-/*
- * Utils
- */
-function log(aMsg) {
-  // Prefix is useful to grep log.
-  // dump("@ PresentationDevicePrompt: " + aMsg + "\n");
-}
-
-function GetString(aName) {
-  return Strings.GetStringFromName(aName);
-}
-
-/*
- * Device Selection UI
- */
-const kNotificationId = "presentation-device-selection";
-const kNotificationPopupIcon = "chrome://presentation-shared/skin/link.svg";
-
-// There is no dependancy between kNotificationId and kNotificationAnchorId,
-// so it's NOT necessary to name them by same prefix
-// (e.g., presentation-device-selection-notification-icon).
-const kNotificationAnchorId = "presentation-device-notification-icon";
-const kNotificationAnchorIcon = "chrome://presentation-shared/skin/link.svg";
-
-// This will insert our own popupnotification content with the device list
-// into the displayed popupnotification element.
-// PopupNotifications.jsm will automatically generate a popupnotification
-// element whose id is <notification id> + "-notification" and show it,
-// so kPopupNotificationId must be kNotificationId + "-notification".
-// Read more detail in PopupNotifications._refreshPanel.
-const kPopupNotificationId = kNotificationId + "-notification";
-
-function PresentationPermissionPrompt(aRequest, aDevices) {
-  this.request = aRequest;
-  this._isResponded = false;
-  this._devices = aDevices;
-}
-
-PresentationPermissionPrompt.prototype = {
-  __proto__: PermissionUI.PermissionPromptForRequestPrototype,
-  // PUBLIC APIs
-  get browser() {
-    return this.request.chromeEventHandler;
-  },
-  get principal() {
-    return this.request.principal;
-  },
-  get popupOptions() {
-    return {
-      removeOnDismissal: true,
-      popupIconURL: kNotificationPopupIcon, // Icon shown on prompt content
-      eventCallback: (aTopic, aNewBrowser) => {
-        log("eventCallback: " + aTopic);
-        let handler = {
-          // dismissed: () => { // Won't be fired if removeOnDismissal is true.
-          //   log("Dismissed by user. Cancel the request.");
-          // },
-          removed: () => {
-            log("Prompt is removed.");
-            if (!this._isResponded) {
-              log("Dismissed by user. Cancel the request.");
-              this.request.cancel(Cr.NS_ERROR_NOT_AVAILABLE);
-            }
-          },
-          showing: () => {
-            log("Prompt is showing.");
-            // We cannot insert the device list at "showing" phase because
-            // the popupnotification content whose id is kPopupNotificationId
-            // is not generated yet.
-          },
-          shown: () => {
-            log("Prompt is shown.");
-            // Insert device selection list into popupnotification element.
-            this._createPopupContent();
-          },
-        };
-
-        // Call the handler for Notification events.
-        handler[aTopic]();
-      },
-    };
-  },
-  get notificationID() {
-    return kNotificationId;
-  },
-  get anchorID() {
-    let chromeDoc = this.browser.ownerDocument;
-    let anchor = chromeDoc.getElementById(kNotificationAnchorId);
-    if (!anchor) {
-      let notificationPopupBox =
-        chromeDoc.getElementById("notification-popup-box");
-      // Icon shown on URL bar
-      let notificationIcon = chromeDoc.createElement("image");
-      notificationIcon.id = kNotificationAnchorId;
-      notificationIcon.setAttribute("src", kNotificationAnchorIcon);
-      notificationIcon.classList.add("notification-anchor-icon");
-      notificationIcon.setAttribute("role", "button");
-      notificationIcon.setAttribute("tooltiptext",
-                                    GetString("presentation.urlbar.tooltiptext"));
-      notificationIcon.style.setProperty("-moz-context-properties", "fill");
-      notificationIcon.style.fill = "currentcolor";
-      notificationIcon.style.opacity = "0.4";
-      notificationPopupBox.appendChild(notificationIcon);
-    }
-
-    return kNotificationAnchorId;
-  },
-  get message() {
-    return GetString("presentation.message", this._domainName);
-  },
-  get promptActions() {
-    return [{
-      label: GetString("presentation.deviceprompt.select.label"),
-      accessKey: GetString("presentation.deviceprompt.select.accessKey"),
-      callback: () => {
-        log("Select");
-        this._isResponded = true;
-        if (!this._listbox || !this._devices.length) {
-          log("No device can be selected!");
-          this.request.cancel(Cr.NS_ERROR_NOT_AVAILABLE);
-          return;
-        }
-        let device = this._devices[this._listbox.selectedIndex];
-        this.request.select(device);
-        log("device: " + device.name + "(" + device.id + ") is selected!");
-      },
-    }, {
-      label: GetString("presentation.deviceprompt.cancel.label"),
-      accessKey: GetString("presentation.deviceprompt.cancel.accessKey"),
-      callback: () => {
-        log("Cancel selection.");
-        this._isResponded = true;
-        this.request.cancel(Cr.NS_ERROR_NOT_AVAILABLE);
-      },
-      dismiss: true,
-    }];
-  },
-  // PRIVATE APIs
-  get _domainName() {
-    if (this.principal.URI instanceof Ci.nsIFileURL) {
-      return this.principal.URI.pathQueryRef.split("/")[1];
-    }
-    return this.principal.URI.hostPort;
-  },
-  _createPopupContent() {
-    log("_createPopupContent");
-
-    if (!this._devices.length) {
-      log("No available devices can be listed!");
-      return;
-    }
-
-    let chromeDoc = this.browser.ownerDocument;
-
-    let popupnotification = chromeDoc.getElementById(kPopupNotificationId);
-    if (!popupnotification) {
-      log("No available popupnotification element to be inserted!");
-      return;
-    }
-
-    let popupnotificationcontent =
-      chromeDoc.createElement("popupnotificationcontent");
-
-    this._listbox = chromeDoc.createElement("richlistbox");
-    this._listbox.setAttribute("flex", "1");
-    this._devices.forEach((device) => {
-      let listitem = chromeDoc.createElement("richlistitem");
-      let label = chromeDoc.createElement("label");
-      label.setAttribute("value", device.name);
-      listitem.appendChild(label);
-      this._listbox.appendChild(listitem);
-    });
-
-    popupnotificationcontent.appendChild(this._listbox);
-    popupnotification.appendChild(popupnotificationcontent);
-  },
-};
-
-
-/*
- * nsIPresentationDevicePrompt
- */
-// For XPCOM registration
-const PRESENTATIONDEVICEPROMPT_CONTRACTID = "@mozilla.org/presentation-device/prompt;1";
-const PRESENTATIONDEVICEPROMPT_CID        = Components.ID("{388bd149-c919-4a43-b646-d7ec57877689}");
-
-function PresentationDevicePrompt() {}
-
-PresentationDevicePrompt.prototype = {
-  // properties required for XPCOM registration:
-  classID: PRESENTATIONDEVICEPROMPT_CID,
-  classDescription: "Presentation API Device Prompt",
-  contractID: PRESENTATIONDEVICEPROMPT_CONTRACTID,
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDevicePrompt]),
-
-  // This will be fired when window.PresentationRequest(URL).start() is called.
-  promptDeviceSelection(aRequest) {
-    log("promptDeviceSelection");
-
-    // Cancel request if no available device.
-    let devices = this._loadDevices();
-    if (!devices.length) {
-      log("No available device.");
-      aRequest.cancel(Cr.NS_ERROR_NOT_AVAILABLE);
-      return;
-    }
-
-    // Show the prompt to users.
-    let promptUI = new PresentationPermissionPrompt(aRequest, devices);
-    promptUI.prompt();
-  },
-  _loadDevices() {
-    let deviceManager = Cc["@mozilla.org/presentation-device/manager;1"]
-                        .getService(Ci.nsIPresentationDeviceManager);
-    let devices = deviceManager.getAvailableDevices().QueryInterface(Ci.nsIArray);
-    let list = [];
-    for (let i = 0; i < devices.length; i++) {
-      let device = devices.queryElementAt(i, Ci.nsIPresentationDevice);
-      list.push(device);
-    }
-
-    return list;
-  },
-};
deleted file mode 100644
--- a/browser/extensions/presentation/install.rdf.in
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-#filter substitution
-
-<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
-
-  <Description about="urn:mozilla:install-manifest">
-    <em:id>presentation@mozilla.org</em:id>
-    <em:version>1.0.0</em:version>
-    <em:type>2</em:type>
-    <em:bootstrap>true</em:bootstrap>
-    <em:multiprocessCompatible>true</em:multiprocessCompatible>
-
-    <!-- Target Application this theme can install into,
-        with minimum and maximum supported versions. -->
-    <em:targetApplication>
-      <Description>
-        <!-- Firefox GUID -->
-        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
-        <em:minVersion>@MOZ_APP_VERSION@</em:minVersion>
-        <em:maxVersion>@MOZ_APP_MAXVERSION@</em:maxVersion>
-      </Description>
-    </em:targetApplication>
-
-    <!-- Front End MetaData -->
-    <em:name>Presentation</em:name>
-    <em:description>Discover nearby devices in the browser</em:description>
-  </Description>
-</RDF>
deleted file mode 100644
--- a/browser/extensions/presentation/jar.mn
+++ /dev/null
@@ -1,5 +0,0 @@
-[features/presentation@mozilla.org] chrome.jar:
-% content presentation %content/
-  content/  (content/*)
-% skin presentation-shared classic/1.0 %skin/shared/
-  skin/  (skin/*)
deleted file mode 100644
--- a/browser/extensions/presentation/locale/en-US/presentation.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-presentation.message=Select one device to send the content.
-presentation.urlbar.tooltiptext=View the device-selection request
-presentation.deviceprompt.select.label=Send
-presentation.deviceprompt.select.accessKey=S
-presentation.deviceprompt.cancel.label=Cancel
-presentation.deviceprompt.cancel.accessKey=C
deleted file mode 100644
--- a/browser/extensions/presentation/locale/jar.mn
+++ /dev/null
@@ -1,8 +0,0 @@
-#filter substitution
-# 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/.
-
-[features/presentation@mozilla.org] @AB_CD@.jar:
-% locale presentation @AB_CD@ %locale/@AB_CD@/
-  locale/@AB_CD@/                    (en-US/*)
deleted file mode 100644
--- a/browser/extensions/presentation/locale/moz.build
+++ /dev/null
@@ -1,7 +0,0 @@
-# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-JAR_MANIFESTS += ['jar.mn']
deleted file mode 100644
--- a/browser/extensions/presentation/moz.build
+++ /dev/null
@@ -1,17 +0,0 @@
-with Files("**"):
-    BUG_COMPONENT = ("Firefox", "Device Permissions")
-
-DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
-DEFINES['MOZ_APP_MAXVERSION'] = CONFIG['MOZ_APP_MAXVERSION']
-
-DIRS += ['locale']
-
-FINAL_TARGET_FILES.features['presentation@mozilla.org'] += [
-  'bootstrap.js'
-]
-
-FINAL_TARGET_PP_FILES.features['presentation@mozilla.org'] += [
-  'install.rdf.in'
-]
-
-JAR_MANIFESTS += ['jar.mn']
deleted file mode 100644
--- a/browser/extensions/presentation/skin/shared/link.svg
+++ /dev/null
@@ -1,11 +0,0 @@
-<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
-     width="32px" height="32px" viewBox="0 0 32 32">
-  <path fill="context-fill gray" d="M15.246,17.992c0,1.064,0.862,1.926,1.926,1.926c1.064,0,1.926-0.862,1.926-1.926c0-1.064-0.862-1.926-1.926-1.926C16.108,16.066,15.246,16.929,15.246,17.992z"/>
-  <path fill="context-fill gray" d="M17.135,13.964c1.948,0,3.566,1.397,3.917,3.244l1.779-1.35c-0.849-2.276-3.023-3.904-5.595-3.904c-2.669,0-4.904,1.758-5.677,4.171l1.647,1.205C13.509,15.424,15.145,13.964,17.135,13.964z"/>
-  <path fill="context-fill gray" d="M16.968,9.895c3.293,0,6.117,1.995,7.338,4.841l1.603-1.216c-1.628-3.3-4.994-5.591-8.923-5.591c-3.942,0-7.319,2.305-8.94,5.624l1.594,1.166C10.865,11.883,13.682,9.895,16.968,9.895z"/>
-  <path fill="context-fill gray" d="M29.962,2.034H4.011c-1.102,0-1.996,0.894-1.996,1.996v10.008h4.042V5.937c0-1.102,0.894-1.996,1.996-1.996h17.973c1.103,0,1.996,0.894,1.996,1.996v16.075c0,1.103-0.894,1.996-1.996,1.996H12.089v1.918h17.873c1.102,0,1.996-0.894,1.996-1.996V4.03C31.958,2.927,31.064,2.034,29.962,2.034z"/>
-  <path fill="context-fill gray" fill-rule="evenodd"
-        d="M8.756,16.025H1.833c-0.737,0-1.729,0.598-1.729,1.335v11.271c0,0.738,0.596,1.335,1.334,1.335h7.318c0.738,0,1.336-0.597,1.336-1.335V17.36C10.092,16.623,9.494,16.025,8.756,16.025z
-           M8.113,27.472c0,0.299-0.243,0.541-0.541,0.541H2.599c-0.298,0-0.541-0.242-0.541-0.541v-8.949c0-0.299,0.243-0.541,0.541-0.541h4.973c0.298,0,0.541,0.242,0.541,0.541V27.472z"/>
-</svg>
-
--- a/browser/locales/Makefile.in
+++ b/browser/locales/Makefile.in
@@ -70,19 +70,16 @@ libs-%:
 	@$(MAKE) -C ../../toolkit/locales libs-$* XPI_ROOT_APPID='$(XPI_ROOT_APPID)'
 	@$(MAKE) -C ../../services/sync/locales AB_CD=$* XPI_NAME=locale-$*
 	@$(MAKE) -C ../../extensions/spellcheck/locales AB_CD=$* XPI_NAME=locale-$*
 ifneq (,$(wildcard ../extensions/formautofill/locales))
 	@$(MAKE) -C ../extensions/formautofill/locales AB_CD=$* XPI_NAME=locale-$*
 endif
 	@$(MAKE) -C ../extensions/onboarding/locales AB_CD=$* XPI_NAME=locale-$*
 	@$(MAKE) -C ../extensions/pocket/locale AB_CD=$* XPI_NAME=locale-$*
-ifndef RELEASE_OR_BETA
-	@$(MAKE) -C ../extensions/presentation/locale AB_CD=$* XPI_NAME=locale-$*
-endif
 	@$(MAKE) -C ../extensions/webcompat-reporter/locales AB_CD=$* XPI_NAME=locale-$*
 	@$(MAKE) -C ../../devtools/client/locales AB_CD=$* XPI_NAME=locale-$* XPI_ROOT_APPID='$(XPI_ROOT_APPID)'
 	@$(MAKE) -C ../../devtools/startup/locales AB_CD=$* XPI_NAME=locale-$* XPI_ROOT_APPID='$(XPI_ROOT_APPID)'
 	@$(MAKE) -B searchplugins AB_CD=$* XPI_NAME=locale-$*
 	@$(MAKE) libs AB_CD=$* XPI_NAME=locale-$* PREF_DIR=$(PREF_DIR)
 	@$(MAKE) multilocale.txt-$* AB_CD=$* XPI_NAME=locale-$*
 	@$(MAKE) -C $(DEPTH)/$(MOZ_BRANDING_DIRECTORY)/locales AB_CD=$* XPI_NAME=locale-$*
 
@@ -92,19 +89,16 @@ chrome-%:
 	$(if $(filter en-US,$(AB_CD)),, @$(MAKE) merge-$*)
 	@$(MAKE) -C ../../toolkit/locales chrome-$*
 	@$(MAKE) -C ../../services/sync/locales chrome AB_CD=$*
 	@$(MAKE) -C ../../extensions/spellcheck/locales chrome AB_CD=$*
 ifneq (,$(wildcard ../extensions/formautofill/locales))
 	@$(MAKE) -C ../extensions/formautofill/locales chrome AB_CD=$*
 endif
 	@$(MAKE) -C ../extensions/pocket/locale chrome AB_CD=$*
-ifndef RELEASE_OR_BETA
-	@$(MAKE) -C ../extensions/presentation/locale chrome AB_CD=$*
-endif
 	@$(MAKE) -C ../../devtools/client/locales chrome AB_CD=$*
 	@$(MAKE) -C ../../devtools/startup/locales chrome AB_CD=$*
 	@$(MAKE) chrome AB_CD=$*
 	@$(MAKE) -C $(DEPTH)/$(MOZ_BRANDING_DIRECTORY)/locales chrome AB_CD=$*
 	@$(MAKE) -C ../extensions/webcompat-reporter/locales chrome AB_CD=$*
 
 package-win32-installer: $(SUBMAKEFILES)
 	$(MAKE) -C ../installer/windows CONFIG_DIR=l10ngen ZIP_IN='$(ZIP_OUT)' installer
--- a/browser/modules/Sanitizer.jsm
+++ b/browser/modules/Sanitizer.jsm
@@ -11,21 +11,23 @@ ChromeUtils.import("resource://gre/modul
 XPCOMUtils.defineLazyModuleGetters(this, {
   AppConstants: "resource://gre/modules/AppConstants.jsm",
   PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
   FormHistory: "resource://gre/modules/FormHistory.jsm",
   Downloads: "resource://gre/modules/Downloads.jsm",
   DownloadsCommon: "resource:///modules/DownloadsCommon.jsm",
   TelemetryStopwatch: "resource://gre/modules/TelemetryStopwatch.jsm",
   setTimeout: "resource://gre/modules/Timer.jsm",
+  ServiceWorkerCleanUp: "resource://gre/modules/ServiceWorkerCleanUp.jsm",
+  OfflineAppCacheHelper: "resource:///modules/offlineAppCache.jsm",
 });
 
-XPCOMUtils.defineLazyServiceGetter(this, "serviceWorkerManager",
-                                   "@mozilla.org/serviceworkers/manager;1",
-                                   "nsIServiceWorkerManager");
+XPCOMUtils.defineLazyServiceGetter(this, "sas",
+                                   "@mozilla.org/storage/activity-service;1",
+                                   "nsIStorageActivityService");
 XPCOMUtils.defineLazyServiceGetter(this, "quotaManagerService",
                                    "@mozilla.org/dom/quota-manager-service;1",
                                    "nsIQuotaManagerService");
 
 // Used as unique id for pending sanitizations.
 var gPendingSanitizationSerial = 0;
 
 /**
@@ -359,47 +361,58 @@ var Sanitizer = {
         if (seenException) {
           throw seenException;
         }
       },
     },
 
     offlineApps: {
       async clear(range) {
-        // AppCache
-        ChromeUtils.import("resource:///modules/offlineAppCache.jsm");
-        // This doesn't wait for the cleanup to be complete.
+        // AppCache: this doesn't wait for the cleanup to be complete.
         OfflineAppCacheHelper.clear();
 
+        if (range) {
+          let principals = sas.getActiveOrigins(range[0], range[1])
+                              .QueryInterface(Ci.nsIArray);
+
+          let promises = [];
+
+          for (let i = 0; i < principals.length; ++i) {
+            let principal = principals.queryElementAt(i, Ci.nsIPrincipal);
+
+            if (principal.URI.scheme != "http" &&
+                principal.URI.scheme != "https" &&
+                principal.URI.scheme != "file") {
+              continue;
+            }
+
+            // LocalStorage
+            Services.obs.notifyObservers(null, "browser:purge-domain-data", principal.URI.host);
+
+            // ServiceWorkers
+            await ServiceWorkerCleanUp.removeFromPrincipal(principal);
+
+            // QuotaManager
+            promises.push(new Promise(r => {
+              let req = quotaManagerService.clearStoragesForPrincipal(principal, null, false);
+              req.callback = () => { r(); };
+            }));
+          }
+
+          return Promise.all(promises);
+        }
+
         // LocalStorage
         Services.obs.notifyObservers(null, "extension:purge-localStorage");
 
         // ServiceWorkers
-        let promises = [];
-        let serviceWorkers = serviceWorkerManager.getAllRegistrations();
-        for (let i = 0; i < serviceWorkers.length; i++) {
-          let sw = serviceWorkers.queryElementAt(i, Ci.nsIServiceWorkerRegistrationInfo);
-
-          promises.push(new Promise(resolve => {
-            let unregisterCallback = {
-              unregisterSucceeded: () => { resolve(true); },
-              // We don't care about failures.
-              unregisterFailed: () => { resolve(true); },
-              QueryInterface: XPCOMUtils.generateQI(
-                [Ci.nsIServiceWorkerUnregisterCallback])
-            };
-
-            serviceWorkerManager.propagateUnregister(sw.principal, unregisterCallback, sw.scope);
-          }));
-        }
-
-        await Promise.all(promises);
+        await ServiceWorkerCleanUp.removeAll();
 
         // QuotaManager
-        promises = [];
+        let promises = [];
         await new Promise(resolve => {
           quotaManagerService.getUsage(request => {
             if (request.resultCode != Cr.NS_OK) {
               // We are probably shutting down. We don't want to propagate the
               // error, rejecting the promise.
               resolve();
               return;
             }
--- a/browser/modules/SiteDataManager.jsm
+++ b/browser/modules/SiteDataManager.jsm
@@ -1,18 +1,17 @@
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 ChromeUtils.defineModuleGetter(this, "OfflineAppCacheHelper",
                                "resource:///modules/offlineAppCache.jsm");
-XPCOMUtils.defineLazyServiceGetter(this, "serviceWorkerManager",
-                                   "@mozilla.org/serviceworkers/manager;1",
-                                   "nsIServiceWorkerManager");
+ChromeUtils.defineModuleGetter(this, "ServiceWorkerCleanUp",
+                               "resource://gre/modules/ServiceWorkerCleanUp.jsm");
 
 var EXPORTED_SYMBOLS = [
   "SiteDataManager"
 ];
 
 XPCOMUtils.defineLazyGetter(this, "gStringBundle", function() {
   return Services.strings.createBundle("chrome://browser/locale/siteData.properties");
 });
@@ -25,17 +24,18 @@ var SiteDataManager = {
 
   _qms: Services.qms,
 
   _appCache: Cc["@mozilla.org/network/application-cache-service;1"].getService(Ci.nsIApplicationCacheService),
 
   // A Map of sites and their disk usage according to Quota Manager and appcache
   // Key is host (group sites based on host across scheme, port, origin atttributes).
   // Value is one object holding:
-  //   - principals: instances of nsIPrincipal.
+  //   - principals: instances of nsIPrincipal (only when the site has
+  //     quota storage or AppCache).
   //   - persisted: the persistent-storage status.
   //   - quotaUsage: the usage of indexedDB and localStorage.
   //   - appCacheList: an array of app cache; instances of nsIApplicationCache
   _sites: new Map(),
 
   _getCacheSizeObserver: null,
 
   _getCacheSizePromise: null,
@@ -310,76 +310,47 @@ var SiteDataManager = {
   _removeCookies(site) {
     for (let cookie of site.cookies) {
       Services.cookies.remove(
         cookie.host, cookie.name, cookie.path, false, cookie.originAttributes);
     }
     site.cookies = [];
   },
 
-  _unregisterServiceWorker(serviceWorker) {
-    return new Promise(resolve => {
-      let unregisterCallback = {
-        unregisterSucceeded: resolve,
-        unregisterFailed: resolve, // We don't care about failures.
-        QueryInterface: XPCOMUtils.generateQI([Ci.nsIServiceWorkerUnregisterCallback])
-      };
-      serviceWorkerManager.propagateUnregister(serviceWorker.principal, unregisterCallback, serviceWorker.scope);
-    });
-  },
-
-  _removeServiceWorkersForSites(sites) {
-    let promises = [];
-    let serviceWorkers = serviceWorkerManager.getAllRegistrations();
-    for (let i = 0; i < serviceWorkers.length; i++) {
-      let sw = serviceWorkers.queryElementAt(i, Ci.nsIServiceWorkerRegistrationInfo);
-      // Sites are grouped and removed by host so we unregister service workers by the same host as well
-      if (sites.has(sw.principal.URI.host)) {
-        promises.push(this._unregisterServiceWorker(sw));
-      }
-    }
-    return Promise.all(promises);
-  },
-
   /**
    * Removes all site data for the specified list of hosts.
    *
    * @param {Array} a list of hosts to match for removal.
    * @returns a Promise that resolves when data is removed and the site data
    *          manager has been updated.
    */
   async remove(hosts) {
     // Make sure we have up-to-date information.
     await this._getQuotaUsage();
     this._updateAppCache();
 
     let unknownHost = "";
-    let targetSites = new Map();
+    let promises = [];
     for (let host of hosts) {
       let site = this._sites.get(host);
       if (site) {
+        // Clear localstorage.
+        Services.obs.notifyObservers(null, "browser:purge-domain-data", host);
         this._removePermission(site);
         this._removeAppCache(site);
         this._removeCookies(site);
-        Services.obs.notifyObservers(null, "browser:purge-domain-data", host);
-        targetSites.set(host, site);
+        promises.push(ServiceWorkerCleanUp.removeFromHost(host));
+        promises.push(this._removeQuotaUsage(site));
       } else {
         unknownHost = host;
         break;
       }
     }
 
-    if (targetSites.size > 0) {
-      await this._removeServiceWorkersForSites(targetSites);
-      let promises = [];
-      for (let [, site] of targetSites) {
-        promises.push(this._removeQuotaUsage(site));
-      }
-      await Promise.all(promises);
-    }
+    await Promise.all(promises);
 
     if (unknownHost) {
       throw `SiteDataManager: removing unknown site of ${unknownHost}`;
     }
 
     return this.updateSites();
   },
 
@@ -433,47 +404,44 @@ var SiteDataManager = {
   removeCache() {
     Services.cache2.clear();
   },
 
   /**
    * Clears all site data, which currently means
    *   - Cookies
    *   - AppCache
+   *   - LocalStorage
    *   - ServiceWorkers
    *   - Quota Managed Storage
    *   - persistent-storage permissions
    *
    * @returns a Promise that resolves with the cache size on disk in bytes
    */
   async removeSiteData() {
+    // LocalStorage
+    Services.obs.notifyObservers(null, "extension:purge-localStorage");
+
     Services.cookies.removeAll();
     OfflineAppCacheHelper.clear();
 
-    // Iterate through the service workers and remove them.
-    let promises = [];
-    let serviceWorkers = serviceWorkerManager.getAllRegistrations();
-    for (let i = 0; i < serviceWorkers.length; i++) {
-      let sw = serviceWorkers.queryElementAt(i, Ci.nsIServiceWorkerRegistrationInfo);
-      promises.push(this._unregisterServiceWorker(sw));
-    }
-    await Promise.all(promises);
+    await ServiceWorkerCleanUp.removeAll();
 
     // Refresh sites using quota usage again.
     // This is for the case:
     //   1. User goes to the about:preferences Site Data section.
     //   2. With the about:preferences opened, user visits another website.
     //   3. The website saves to quota usage, like indexedDB.
     //   4. User goes back to the Site Data section and commands to clear all site data.
     // For this case, we should refresh the site list so not to miss the website in the step 3.
     // We don't do "Clear All" on the quota manager like the cookie, appcache, http cache above
     // because that would clear browser data as well too,
     // see https://bugzilla.mozilla.org/show_bug.cgi?id=1312361#c9
     this._sites.clear();
     await this._getQuotaUsage();
-    promises = [];
+    let promises = [];
     for (let site of this._sites.values()) {
       this._removePermission(site);
       promises.push(this._removeQuotaUsage(site));
     }
     return Promise.all(promises).then(() => this.updateSites());
   },
 };
--- a/devtools/client/commandline/test/browser_cmd_addon.js
+++ b/devtools/client/commandline/test/browser_cmd_addon.js
@@ -31,17 +31,17 @@ function* spawnTest() {
         hints:                      "",
         markup: "VVVVVVVVVVVVVVVVVVVV",
         status: "VALID"
       },
       exec: {
         output: [/The following/, /Mochitest/, /Special Powers/],
         notinoutput: [
           /Web Compat/, /Pocket/, /Multi-process staged rollout/,
-          /Form Autofill/, /Application Update Service Helper/, /Presentation/,
+          /Form Autofill/, /Application Update Service Helper/,
           /Shield Recipe Client/]
       }
     },
     {
       setup: "addon list locale",
       check: {
         input:  "addon list locale",
         hints:                   "",
--- a/devtools/client/debugger/new/README.mozilla
+++ b/devtools/client/debugger/new/README.mozilla
@@ -1,13 +1,13 @@
 This is the debugger.html project output.
 See https://github.com/devtools-html/debugger.html
 
-Version 39.0
+Version 40.0
 
-Comparison: https://github.com/devtools-html/debugger.html/compare/release-38...release-39
+Comparison: https://github.com/devtools-html/debugger.html/compare/release-39...release-40
 
 Packages:
 - babel-plugin-transform-es2015-modules-commonjs @6.26.0
 - babel-preset-react @6.24.1
 - react @16.2.0
 - react-dom @16.2.0
 - webpack @3.11.0
--- a/devtools/client/debugger/new/debugger.css
+++ b/devtools/client/debugger/new/debugger.css
@@ -910,16 +910,17 @@ html[dir="rtl"] .tree-node img.arrow {
   width: 14px;
   height: 14px;
   border: 1px solid transparent;
   border-radius: 2px;
   display: flex;
   flex-direction: row;
   justify-content: center;
   align-items: center;
+  padding: 0;
 }
 
 .close-btn .close {
   mask: url("chrome://devtools/skin/images/debugger/close.svg") no-repeat;
   mask-size: 100%;
   background-color: var(--theme-comment);
   width: 8px;
   height: 8px;
@@ -931,24 +932,33 @@ html[dir="rtl"] .tree-node img.arrow {
 .close-btn:hover img.close {
   background-color: white;
 }
 
 .close-btn:hover {
   background-color: var(--theme-selection-background);
 }
 
+.close-btn:focus {
+  background-color: var(--theme-selection-background);
+}
+
+.close-btn:focus img.close {
+  background-color: white;
+}
+
 .close-btn.big {
   width: 16px;
-  height: 16px;
-}
-
-.close-btn.big .close {
-  width: 9px;
-  height: 9px;
+  height: 18px;
+}
+
+img.close::before {
+  width: 100%;
+  height: 100%;
+  padding: 6px;
 }
 /* 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/>. */
 
 .search-field {
   width: calc(100% - 1px);
   height: 27px;
--- a/devtools/client/debugger/new/debugger.js
+++ b/devtools/client/debugger/new/debugger.js
@@ -3230,17 +3230,17 @@ function createPendingBreakpoint(bp) {
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
-exports.replaceOriginalVariableName = exports.getPausePoints = exports.getFramework = exports.mapOriginalExpression = exports.hasSyntaxError = exports.clearSources = exports.setSource = exports.hasSource = exports.isInvalidPauseLocation = exports.getNextStep = exports.clearASTs = exports.clearScopes = exports.clearSymbols = exports.findOutOfScopeLocations = exports.getScopes = exports.getSymbols = exports.getClosestExpression = exports.stopParserWorker = exports.startParserWorker = undefined;
+exports.replaceOriginalVariableName = exports.getPausePoints = exports.getFramework = exports.mapOriginalExpression = exports.hasSyntaxError = exports.clearSources = exports.setSource = exports.hasSource = exports.getNextStep = exports.clearASTs = exports.clearScopes = exports.clearSymbols = exports.findOutOfScopeLocations = exports.getScopes = exports.getSymbols = exports.getClosestExpression = exports.stopParserWorker = exports.startParserWorker = undefined;
 
 var _devtoolsUtils = __webpack_require__(1363);
 
 const { WorkerDispatcher } = _devtoolsUtils.workerUtils; /* This Source Code Form is subject to the terms of the Mozilla Public
                                                           * License, v. 2.0. If a copy of the MPL was not distributed with this
                                                           * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
 
 const dispatcher = new WorkerDispatcher();
@@ -3250,17 +3250,16 @@ const stopParserWorker = exports.stopPar
 const getClosestExpression = exports.getClosestExpression = dispatcher.task("getClosestExpression");
 const getSymbols = exports.getSymbols = dispatcher.task("getSymbols");
 const getScopes = exports.getScopes = dispatcher.task("getScopes");
 const findOutOfScopeLocations = exports.findOutOfScopeLocations = dispatcher.task("findOutOfScopeLocations");
 const clearSymbols = exports.clearSymbols = dispatcher.task("clearSymbols");
 const clearScopes = exports.clearScopes = dispatcher.task("clearScopes");
 const clearASTs = exports.clearASTs = dispatcher.task("clearASTs");
 const getNextStep = exports.getNextStep = dispatcher.task("getNextStep");
-const isInvalidPauseLocation = exports.isInvalidPauseLocation = dispatcher.task("isInvalidPauseLocation");
 const hasSource = exports.hasSource = dispatcher.task("hasSource");
 const setSource = exports.setSource = dispatcher.task("setSource");
 const clearSources = exports.clearSources = dispatcher.task("clearSources");
 const hasSyntaxError = exports.hasSyntaxError = dispatcher.task("hasSyntaxError");
 const mapOriginalExpression = exports.mapOriginalExpression = dispatcher.task("mapOriginalExpression");
 const getFramework = exports.getFramework = dispatcher.task("getFramework");
 const getPausePoints = exports.getPausePoints = dispatcher.task("getPausePoints");
 const replaceOriginalVariableName = exports.replaceOriginalVariableName = dispatcher.task("replaceOriginalVariableName");
@@ -4189,17 +4188,17 @@ var _react2 = _interopRequireDefault(_re
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
 
 function CloseButton({ handleClick, buttonClass, tooltip }) {
   return _react2.default.createElement(
-    "div",
+    "button",
     {
       className: buttonClass ? `close-btn ${buttonClass}` : "close-btn",
       onClick: handleClick,
       title: tooltip
     },
     _react2.default.createElement("img", { className: "close" })
   );
 }
@@ -4880,16 +4879,17 @@ var _extends = Object.assign || function
 
 exports.initialASTState = initialASTState;
 exports.getSymbols = getSymbols;
 exports.hasSymbols = hasSymbols;
 exports.isSymbolsLoading = isSymbolsLoading;
 exports.isEmptyLineInSource = isEmptyLineInSource;
 exports.getEmptyLines = getEmptyLines;
 exports.getPausePoints = getPausePoints;
+exports.getPausePoint = getPausePoint;
 exports.hasPausePoints = hasPausePoints;
 exports.getOutOfScopeLocations = getOutOfScopeLocations;
 exports.getPreview = getPreview;
 exports.getSourceMetaData = getSourceMetaData;
 exports.hasSourceMetaData = hasSourceMetaData;
 exports.getInScopeLines = getInScopeLines;
 exports.isLineInScope = isLineInScope;
 
@@ -5033,16 +5033,31 @@ function getEmptyLines(state, source) {
 
   return state.ast.emptyLines.get(source.id);
 }
 
 function getPausePoints(state, sourceId) {
   return state.ast.pausePoints.get(sourceId);
 }
 
+function getPausePoint(state, location) {
+  if (!location) {
+    return;
+  }
+
+  const { column, line, sourceId } = location;
+  const pausePoints = getPausePoints(state, sourceId);
+  if (!pausePoints) {
+    return;
+  }
+
+  const linePoints = pausePoints[line];
+  return linePoints && linePoints[column];
+}
+
 function hasPausePoints(state, sourceId) {
   const pausePoints = getPausePoints(state, sourceId);
   return !!pausePoints;
 }
 
 function getOutOfScopeLocations(state) {
   return state.ast.get("outOfScopeLocations");
 }
@@ -15401,17 +15416,16 @@ const svg = {
   close: __webpack_require__(352),
   coffeescript: __webpack_require__(2250),
   dojo: __webpack_require__(806),
   domain: __webpack_require__(353),
   extension: __webpack_require__(3632),
   file: __webpack_require__(354),
   folder: __webpack_require__(355),
   globe: __webpack_require__(356),
-  help: __webpack_require__(3633),
   home: __webpack_require__(3604),
   javascript: __webpack_require__(2251),
   jquery: __webpack_require__(999),
   underscore: __webpack_require__(1117),
   lodash: __webpack_require__(1118),
   ember: __webpack_require__(1119),
   vuejs: __webpack_require__(1174),
   "magnifying-glass": __webpack_require__(357),
@@ -23480,17 +23494,17 @@ class Breakpoints extends _react.Compone
   renderBreakpoints() {
     const { breakpoints } = this.props;
     if (breakpoints.size == 0) {
       return;
     }
 
     const groupedBreakpoints = (0, _lodash.groupBy)((0, _lodash.sortBy)([...breakpoints.valueSeq()], bp => bp.location.line), bp => getBreakpointFilename(bp.source));
 
-    return [...Object.keys(groupedBreakpoints).map(filename => {
+    return [...Object.keys(groupedBreakpoints).sort().map(filename => {
       return [_react2.default.createElement(
         "div",
         { className: "breakpoint-heading", title: filename, key: filename },
         filename
       ), ...groupedBreakpoints[filename].filter(bp => !bp.hidden && bp.text).map((bp, i) => this.renderBreakpoint(bp))];
     })];
   }
 
@@ -24449,22 +24463,26 @@ class Accordion extends _react.Component
     this.renderContainer = (item, i) => {
       const { opened } = item;
 
       return _react2.default.createElement(
         "div",
         { className: item.className, key: i },
         _react2.default.createElement(
           "div",
-          { className: "_header", onClick: () => this.handleHeaderClick(i) },
+          {
+            className: "_header",
+            tabIndex: "0",
+            onClick: () => this.handleHeaderClick(i)
+          },
           _react2.default.createElement(_Svg2.default, { name: "arrow", className: opened ? "expanded" : "" }),
           item.header,
           item.buttons ? _react2.default.createElement(
             "div",
-            { className: "header-buttons" },
+            { className: "header-buttons", tabIndex: "-1" },
             item.buttons
           ) : null
         ),
         opened && _react2.default.createElement(
           "div",
           { className: "_content" },
           (0, _react.cloneElement)(item.component, item.componentProps || {})
         )
@@ -27176,17 +27194,17 @@ function paused(pauseInfo) {
 
     if (rootFrame) {
       const mappedFrame = await (0, _mapFrames.updateFrameLocation)(rootFrame, sourceMaps);
       const source = await getOriginalSourceForFrame(getState(), mappedFrame);
 
       // Ensure that the original file has loaded if there is one.
       await dispatch((0, _loadSourceText.loadSourceText)(source));
 
-      if (await (0, _pause.shouldStep)(mappedFrame, getState(), sourceMaps)) {
+      if ((0, _pause.shouldStep)(mappedFrame, getState(), sourceMaps)) {
         dispatch((0, _commands.command)("stepOver"));
         return;
       }
     }
 
     dispatch({
       type: "PAUSED",
       why,
@@ -34872,20 +34890,20 @@ async function findGeneratedBindingFromP
   }
 
   return null;
 }
 
 function filterApplicableBindings(bindings, mapped) {
   // Any binding overlapping a part of the mapping range.
   return (0, _filtering.filterSortedArray)(bindings, binding => {
-    if (positionCmp(binding.loc.end, mapped.start) < 0) {
+    if (positionCmp(binding.loc.end, mapped.start) <= 0) {
       return -1;
     }
-    if (positionCmp(binding.loc.start, mapped.end) > 0) {
+    if (positionCmp(binding.loc.start, mapped.end) >= 0) {
       return 1;
     }
 
     return 0;
   });
 }
 
 /**
@@ -34938,24 +34956,58 @@ async function findGeneratedImportRefere
 /**
  * Given a mapped range over the generated source and the name of the imported
  * value that is referenced, attempt to resolve a binding descriptor for
  * the import's value.
  */
 async function findGeneratedImportDeclaration(generatedAstBindings, mapped) {
   const bindings = filterApplicableBindings(generatedAstBindings, mapped);
 
-  return bindings.reduce(async (acc, val) => {
-    const accVal = await acc;
-    if (accVal) {
-      return accVal;
-    }
-
-    return await mapImportDeclarationToDescriptor(val, mapped);
-  }, null);
+  let result = null;
+
+  for (const binding of bindings) {
+    if (binding.loc.type !== "decl") {
+      continue;
+    }
+
+    const namespaceDesc = await binding.desc();
+    if (isPrimitiveValue(namespaceDesc)) {
+      continue;
+    }
+    if (!isObjectValue(namespaceDesc)) {
+      // We want to handle cases like
+      //
+      //   var _mod = require(...);
+      //   var _mod2 = _interopRequire(_mod);
+      //
+      // where "_mod" is optimized out because it is only referenced once. To
+      // allow that, we track the optimized-out value as a possible result,
+      // but allow later binding values to overwrite the result.
+      result = {
+        name: binding.name,
+        desc: namespaceDesc,
+        expression: binding.name
+      };
+      continue;
+    }
+
+    const desc = await readDescriptorProperty(namespaceDesc, mapped.importName);
+    const expression = `${binding.name}.${mapped.importName}`;
+
+    if (desc) {
+      result = {
+        name: binding.name,
+        desc,
+        expression
+      };
+      break;
+    }
+  }
+
+  return result;
 }
 
 /**
  * Given a generated binding, and a range over the generated code, statically
  * check if the given binding matches the range.
  */
 async function mapBindingReferenceToDescriptor(binding, mapped, isFirst) {
   // Allow the mapping to point anywhere within the generated binding
@@ -34975,50 +35027,16 @@ async function mapBindingReferenceToDesc
     };
   }
 
   return null;
 }
 
 /**
  * Given an generated binding, and a range over the generated code, statically
- * resolve the module namespace object and attempt to access the imported
- * property on the namespace.
- *
- * This is mostly hard-coded to work for Babel 6's imports.
- */
-async function mapImportDeclarationToDescriptor(binding, mapped) {
-  // When trying to map an actual import declaration binding, we can try
-  // to map it back to the namespace object in the original code.
-  if (!mappingContains(mapped, binding.loc)) {
-    return null;
-  }
-
-  const desc = await readDescriptorProperty((await binding.desc()), mapped.importName,
-  // If the value was optimized out or otherwise unavailable, we skip it
-  // entirely because there is a good chance that this means that this
-  // isn't the right binding. This allows us to catch cases like
-  //
-  //   var _mod = require(...);
-  //   var _mod2 = _interopRequire(_mod);
-  //
-  // where "_mod" is optimized out because it is only referenced once, and
-  // we want to continue searching to try to find "_mod2".
-  true);
-  const expression = `${binding.name}.${mapped.importName}`;
-
-  return desc ? {
-    name: binding.name,
-    desc,
-    expression
-  } : null;
-}
-
-/**
- * Given an generated binding, and a range over the generated code, statically
  * evaluate accessed properties within the mapped range to resolve the actual
  * imported value.
  */
 async function mapImportReferenceToDescriptor(binding, mapped) {
   if (mapped.type !== "ref") {
     return null;
   }
 
@@ -35081,42 +35099,42 @@ async function mapImportReferenceToDescr
 
   return desc ? {
     name: binding.name,
     desc,
     expression
   } : null;
 }
 
-async function readDescriptorProperty(desc, property, requireValidObject = false) {
+function isPrimitiveValue(desc) {
+  return desc && (!desc.value || typeof desc.value !== "object");
+}
+function isObjectValue(desc) {
+  return desc && !isPrimitiveValue(desc) && desc.value.type === "object" &&
+  // Note: The check for `.type` might already cover the optimizedOut case
+  // but not 100% sure, so just being cautious.
+  !desc.value.optimizedOut;
+}
+
+async function readDescriptorProperty(desc, property) {
   if (!desc) {
     return null;
   }
 
   if (typeof desc.value !== "object" || !desc.value) {
-    if (requireValidObject) {
-      return null;
-    }
-
     // If accessing a property on a primitive type, just return 'undefined'
     // as the value.
     return {
       value: {
         type: "undefined"
       }
     };
   }
 
-  // Note: The check for `.type` might already cover the optimizedOut case
-  // but not 100% sure, so just being cautious.
-  if (desc.value.type !== "object" || desc.value.optimizedOut) {
-    if (requireValidObject) {
-      return null;
-    }
-
+  if (!isObjectValue(desc)) {
     // If we got a non-primitive descriptor but it isn't an object, then
     // it's definitely not the namespace and it is probably an error.
     return desc;
   }
 
   const objectClient = (0, _firefox.createObjectClient)(desc.value);
   return (await objectClient.getProperty(property)).descriptor;
 }
@@ -35147,17 +35165,17 @@ function positionCmp(p1, p2) {
 async function getGeneratedLocationRange(pos, source, type, sourceMaps) {
   const endPosition = await sourceMaps.getGeneratedLocation(pos.end, source);
   const startPosition = await sourceMaps.getGeneratedLocation(pos.start, source);
   const ranges = await sourceMaps.getGeneratedRanges(pos.start, source);
   if (ranges.length === 0) {
     return null;
   }
 
-  // If the stand and end positions collapse into eachother, it means that
+  // If the start and end positions collapse into eachother, it means that
   // the range in the original content didn't _start_ at the start position.
   // Since this likely means that the range doesn't logically apply to this
   // binding location, we skip it.
   if (positionCmp(startPosition, endPosition) === 0) {
     return null;
   }
 
   const start = {
@@ -35425,42 +35443,47 @@ Object.defineProperty(exports, "__esModu
 exports.shouldStep = shouldStep;
 
 var _lodash = __webpack_require__(2);
 
 var _devtoolsSourceMap = __webpack_require__(1360);
 
 var _selectors = __webpack_require__(3590);
 
-var _parser = __webpack_require__(1365);
-
-/* 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/>. */
-
-async function shouldStep(rootFrame, state, sourceMaps) {
-  if (!rootFrame) {
-    return false;
-  }
-
+function getFrameLocation(source, frame) {
+  if (!frame) {
+    return null;
+  }
+
+  return (0, _devtoolsSourceMap.isOriginalId)(source.id) ? frame.location : frame.generatedLocation;
+} /* This Source Code Form is subject to the terms of the Mozilla Public
+   * License, v. 2.0. If a copy of the MPL was not distributed with this
+   * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
+
+function shouldStep(rootFrame, state, sourceMaps) {
   const selectedSource = (0, _selectors.getSelectedSource)(state);
   const previousFrameInfo = (0, _selectors.getPreviousPauseFrameLocation)(state);
 
-  let previousFrameLoc;
-  let currentFrameLoc;
-
-  if (selectedSource && (0, _devtoolsSourceMap.isOriginalId)(selectedSource.get("id"))) {
-    currentFrameLoc = rootFrame.location;
-    previousFrameLoc = previousFrameInfo && previousFrameInfo.location;
-  } else {
-    currentFrameLoc = rootFrame.generatedLocation;
-    previousFrameLoc = previousFrameInfo && previousFrameInfo.generatedLocation;
-  }
-
-  return (0, _devtoolsSourceMap.isOriginalId)(currentFrameLoc.sourceId) && (previousFrameLoc && (0, _lodash.isEqual)(previousFrameLoc, currentFrameLoc) || (await (0, _parser.isInvalidPauseLocation)(currentFrameLoc)));
+  if (!rootFrame || !selectedSource) {
+    return false;
+  }
+
+  const previousFrameLoc = getFrameLocation(selectedSource, previousFrameInfo);
+  const frameLoc = getFrameLocation(selectedSource, rootFrame);
+
+  const sameLocation = previousFrameLoc && (0, _lodash.isEqual)(previousFrameLoc, frameLoc);
+  const pausePoint = (0, _selectors.getPausePoint)(state, frameLoc);
+  const invalidPauseLocation = pausePoint && !pausePoint.step;
+
+  // We always want to pause in generated locations
+  if (!frameLoc || (0, _devtoolsSourceMap.isGeneratedId)(frameLoc.sourceId)) {
+    return false;
+  }
+
+  return sameLocation || invalidPauseLocation;
 }
 
 /***/ }),
 
 /***/ 247:
 /***/ (function(module, exports) {
 
 module.exports = "<!-- This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. --><svg viewBox=\"0 0 256 272\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" preserveAspectRatio=\"xMidYMid\"><g><path d=\"M0.0996108949,45.522179 L125.908171,0.697276265 L255.103502,44.7252918 L234.185214,211.175097 L125.908171,271.140856 L19.3245136,211.971984 L0.0996108949,45.522179 Z\" fill=\"#E23237\"></path><path d=\"M255.103502,44.7252918 L125.908171,0.697276265 L125.908171,271.140856 L234.185214,211.274708 L255.103502,44.7252918 L255.103502,44.7252918 Z\" fill=\"#B52E31\"></path><path d=\"M126.107393,32.27393 L126.107393,32.27393 L47.7136187,206.692607 L76.9992218,206.194553 L92.7377432,166.848249 L126.207004,166.848249 L126.306615,166.848249 L163.063035,166.848249 L180.29572,206.692607 L208.286381,207.190661 L126.107393,32.27393 L126.107393,32.27393 Z M126.306615,88.155642 L152.803113,143.5393 L127.402335,143.5393 L126.107393,143.5393 L102.997665,143.5393 L126.306615,88.155642 L126.306615,88.155642 Z\" fill=\"#FFFFFF\"></path></g></svg>"
@@ -38933,18 +38956,18 @@ function insertStrtAt(string, index, new
   const end = string.slice(index);
   return `${start}${newString}${end}`;
 } /* This Source Code Form is subject to the terms of the Mozilla Public
    * License, v. 2.0. If a copy of the MPL was not distributed with this
    * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
 
 function convertToList(pausePoints) {
   const list = [];
-  for (let line in pausePoints) {
-    for (let column in pausePoints[line]) {
+  for (const line in pausePoints) {
+    for (const column in pausePoints[line]) {
       const point = pausePoints[line][column];
       list.push({
         location: { line: parseInt(line, 10), column: parseInt(column, 10) },
         types: point
       });
     }
   }
   return list;
@@ -39183,23 +39206,16 @@ module.exports = "<!-- This Source Code 
 
 /***/ 3632:
 /***/ (function(module, exports) {
 
 module.exports = "<!-- This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. --><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\"><path fill=\"context-fill\" d=\"M14.5 8c-.971 0-1 1-1.75 1a.765.765 0 0 1-.75-.75V5a1 1 0 0 0-1-1H7.75A.765.765 0 0 1 7 3.25c0-.75 1-.779 1-1.75C8 .635 7.1 0 6 0S4 .635 4 1.5c0 .971 1 1 1 1.75a.765.765 0 0 1-.75.75H1a1 1 0 0 0-1 1v2.25A.765.765 0 0 0 .75 8c.75 0 .779-1 1.75-1C3.365 7 4 7.9 4 9s-.635 2-1.5 2c-.971 0-1-1-1.75-1a.765.765 0 0 0-.75.75V15a1 1 0 0 0 1 1h3.25a.765.765 0 0 0 .75-.75c0-.75-1-.779-1-1.75 0-.865.9-1.5 2-1.5s2 .635 2 1.5c0 .971-1 1-1 1.75a.765.765 0 0 0 .75.75H11a1 1 0 0 0 1-1v-3.25a.765.765 0 0 1 .75-.75c.75 0 .779 1 1.75 1 .865 0 1.5-.9 1.5-2s-.635-2-1.5-2z\"></path></svg>"
 
 /***/ }),
 
-/***/ 3633:
-/***/ (function(module, exports) {
-
-module.exports = "<!-- This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. --><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\"><path fill=\"context-fill\" d=\"M8 1a7 7 0 1 0 7 7 7.008 7.008 0 0 0-7-7zm0 13a6 6 0 1 1 6-6 6.007 6.007 0 0 1-6 6zM8 3.125A2.7 2.7 0 0 0 5.125 6a.875.875 0 0 0 1.75 0c0-1 .6-1.125 1.125-1.125a1.105 1.105 0 0 1 1.13.744.894.894 0 0 1-.53 1.016A2.738 2.738 0 0 0 7.125 9v.337a.875.875 0 0 0 1.75 0v-.37a1.041 1.041 0 0 1 .609-.824A2.637 2.637 0 0 0 10.82 5.16 2.838 2.838 0 0 0 8 3.125zm0 7.625A1.25 1.25 0 1 0 9.25 12 1.25 1.25 0 0 0 8 10.75z\"></path></svg>"
-
-/***/ }),
-
 /***/ 3634:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
--- a/devtools/client/debugger/new/parser-worker.js
+++ b/devtools/client/debugger/new/parser-worker.js
@@ -2202,45 +2202,40 @@ var _findOutOfScopeLocations = __webpack
 var _findOutOfScopeLocations2 = _interopRequireDefault(_findOutOfScopeLocations);
 
 var _steps = __webpack_require__(1625);
 
 var _validate = __webpack_require__(1629);
 
 var _frameworks = __webpack_require__(1703);
 
-var _pauseLocation = __webpack_require__(2422);
-
 var _pausePoints = __webpack_require__(3612);
 
 var _mapOriginalExpression = __webpack_require__(3613);
 
 var _mapOriginalExpression2 = _interopRequireDefault(_mapOriginalExpression);
 
 var _devtoolsUtils = __webpack_require__(1363);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
-
-const { workerHandler } = _devtoolsUtils.workerUtils;
+const { workerHandler } = _devtoolsUtils.workerUtils; /* 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/>. */
 
 self.onmessage = workerHandler({
   findOutOfScopeLocations: _findOutOfScopeLocations2.default,
   getSymbols: _getSymbols.getSymbols,
   getScopes: _getScopes2.default,
   clearSymbols: _getSymbols.clearSymbols,
   clearScopes: _getScopes.clearScopes,
   clearASTs: _ast.clearASTs,
   hasSource: _sources.hasSource,
   setSource: _sources.setSource,
   clearSources: _sources.clearSources,
-  isInvalidPauseLocation: _pauseLocation.isInvalidPauseLocation,
   getNextStep: _steps.getNextStep,
   hasSyntaxError: _validate.hasSyntaxError,
   getFramework: _frameworks.getFramework,
   getPausePoints: _pausePoints.getPausePoints,
   mapOriginalExpression: _mapOriginalExpression2.default
 });
 
 /***/ }),
@@ -19980,86 +19975,16 @@ function stripModuleScope(rootScope) {
   rootLexicalScope.children = moduleScope.children;
   rootLexicalScope.children.forEach(child => {
     child.parent = rootLexicalScope;
   });
 }
 
 /***/ }),
 
-/***/ 2422:
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-Object.defineProperty(exports, "__esModule", {
-  value: true
-});
-exports.isInvalidPauseLocation = isInvalidPauseLocation;
-
-var _types = __webpack_require__(2268);
-
-var t = _interopRequireWildcard(_types);
-
-var _ast = __webpack_require__(1375);
-
-function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
-
-const STOP = {};
-
-function isInvalidPauseLocation(location) {
-  const state = {
-    invalid: false,
-    location
-  };
-
-  try {
-    (0, _ast.traverseAst)(location.sourceId, { enter: invalidLocationVisitor }, state);
-  } catch (e) {
-    if (e !== STOP) {
-      throw e;
-    }
-  }
-
-  return state.invalid;
-}
-
-function invalidLocationVisitor(node, ancestors, state) {
-  const { location } = state;
-
-  if (node.loc.end.line < location.line) {
-    return;
-  }
-  if (node.loc.start.line > location.line) {
-    throw STOP;
-  }
-
-  if (location.line === node.loc.start.line && location.column >= node.loc.start.column && t.isFunction(node) && !t.isArrowFunctionExpression(node) && (location.line < node.body.loc.start.line || location.line === node.body.loc.start.line && location.column <= node.body.loc.start.column)) {
-    // Disallow pausing _inside_ in function arguments to avoid pausing inside
-    // of destructuring and other logic.
-    state.invalid = true;
-    throw STOP;
-  }
-
-  if (location.line === node.loc.start.line && location.column === node.loc.start.column && t.isBlockStatement(node)) {
-    // Disallow pausing directly before the opening curly of a block statement.
-    // Babel occasionally maps statements with unknown original positions to
-    // this location.
-    state.invalid = true;
-    throw STOP;
-  }
-}
-
-/***/ }),
-
 /***/ 248:
 /***/ (function(module, exports, __webpack_require__) {
 
 (function(){
   var crypt = __webpack_require__(249),
       utf8 = __webpack_require__(250).utf8,
       isBuffer = __webpack_require__(251),
       bin = __webpack_require__(250).bin,
@@ -21410,21 +21335,23 @@ var t = _interopRequireWildcard(_types);
 var _isEqual = __webpack_require__(1127);
 
 var _isEqual2 = _interopRequireDefault(_isEqual);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
 
-const isControlFlow = node => t.isForStatement(node) || t.isWhileStatement(node) || t.isIfStatement(node) || t.isSwitchCase(node) || t.isSwitchStatement(node); /* This Source Code Form is subject to the terms of the Mozilla Public
-                                                                                                                                                                 * License, v. 2.0. If a copy of the MPL was not distributed with this
-                                                                                                                                                                 * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
-
-const isAssignment = node => t.isVariableDeclarator(node) || t.isAssignmentExpression(node);
+const isForStatement = node => t.isForStatement(node) || t.isForOfStatement(node); /* This Source Code Form is subject to the terms of the Mozilla Public
+                                                                                    * License, v. 2.0. If a copy of the MPL was not distributed with this
+                                                                                    * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
+
+const isControlFlow = node => isForStatement(node) || t.isWhileStatement(node) || t.isIfStatement(node) || t.isSwitchCase(node) || t.isSwitchStatement(node);
+
+const isAssignment = node => t.isVariableDeclarator(node) || t.isAssignmentExpression(node) || t.isAssignmentPattern(node);
 
 const isImport = node => t.isImport(node) || t.isImportDeclaration(node);
 const isReturn = node => t.isReturnStatement(node);
 const isCall = node => t.isCallExpression(node) || t.isJSXElement(node);
 
 const inStepExpression = parent => t.isArrayExpression(parent) || t.isObjectProperty(parent) || t.isCallExpression(parent) || t.isJSXElement(parent);
 
 const inExpression = (parent, grandParent) => inStepExpression(parent) || t.isJSXAttribute(grandParent) || t.isTemplateLiteral(parent);
@@ -21444,37 +21371,48 @@ function onEnter(node, ancestors, state)
   const grandParent = ancestors[ancestors.length - 2];
   const startLocation = node.loc.start;
 
   if (isImport(node) || t.isClassDeclaration(node) || isExport(node) || t.isDebuggerStatement(node)) {
     return addStopPoint(state, startLocation);
   }
 
   if (isControlFlow(node)) {
-    addEmptyPoint(state, startLocation);
+    if (isForStatement(node)) {
+      addStopPoint(state, startLocation);
+    } else {
+      addEmptyPoint(state, startLocation);
+    }
 
     const test = node.test || node.discriminant;
     if (test) {
       addStopPoint(state, test.loc.start);
     }
     return;
   }
 
+  if (t.isBlockStatement(node)) {
+    return addEmptyPoint(state, startLocation);
+  }
+
   if (isReturn(node)) {
     // We do not want to pause at the return and the call e.g. return foo()
     if (isCall(node.argument)) {
       return addEmptyPoint(state, startLocation);
     }
     return addStopPoint(state, startLocation);
   }
 
   if (isAssignment(node)) {
     // We only want to pause at literal assignments `var a = foo()`
     const value = node.right || node.init;
-    if (!isCall(value)) {
+
+    if (isCall(value) || t.isFunction(parentNode)) {
+      return addEmptyPoint(state, startLocation);
+    } else {
       return addStopPoint(state, startLocation);
     }
   }
 
   if (isCall(node)) {
     let location = startLocation;
 
     // When functions are chained, we want to use the property location
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-babel-preview.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-babel-preview.js
@@ -1,201 +1,187 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests for preview through Babel's compile output.
 requestLongerTimeout(3);
 
-function getCoordsFromPosition(cm, { line, ch }) {
-  return cm.charCoords({ line: ~~line, ch: ~~ch });
-}
-
-async function assertPreviews(dbg, previews) {
-  for (const { line, column, expression, result, fields } of previews) {
-    hoverAtPos(dbg, { line, ch: column });
-
-    if (fields && result) {
-      throw new Error("Invalid test fixture");
-    }
-
-    if (fields) {
-      for (const [field, value] of fields) {
-        await assertPreviewPopup(dbg, { expression, field, value });
-      }
-    } else {
-      await assertPreviewTextValue(dbg, { expression, text: result });
-    }
-
-    // Move to column 0 after to make sure that the preview created by this
-    // test does not affect later attempts to hover and preview.
-    hoverAtPos(dbg, { line: line - 1, ch: 0 });
-  }
-}
-
 async function breakpointPreviews(dbg, fixture, { line, column }, previews) {
   const filename = `fixtures/${fixture}/input.js`;
   const fnName = fixture.replace(/-([a-z])/g, (s, c) => c.toUpperCase());
 
+  log(`Starting ${fixture} tests`);
+
   await invokeWithBreakpoint(dbg, fnName, filename, { line, column }, async () => {
     await assertPreviews(dbg, previews);
   });
 
   ok(true, `Ran tests for ${fixture} at line ${line} column ${column}`);
 }
 
-add_task(async function() {
-  await pushPref("devtools.debugger.features.map-scopes", true);
-
-  const dbg = await initDebugger("doc-babel.html");
-
-  await breakpointPreviews(dbg, "for-of", { line: 5, column: 4 }, [
+function testForOf(dbg) {
+  return breakpointPreviews(dbg, "for-of", { line: 5, column: 4 }, [
     {
       line: 5,
       column: 7,
       expression: "doThing",
       result: "doThing(arg)",
     },
     {
       line: 5,
-      column: 12,
+      column: 13,
       expression: "x",
       result: "1",
     },
     {
       line: 8,
       column: 16,
       expression: "doThing",
       result: "doThing(arg)",
     },
   ]);
+}
 
-  await breakpointPreviews(dbg, "shadowed-vars", { line: 18, column: 6 }, [
-    // These aren't what the user would expect, but we test them anyway since
-    // they reflect what this actually returns. These shadowed bindings read
-    // the binding closest to the current frame's scope even though their
-    // actual value is different.
-    {
-      line: 2,
-      column: 9,
-      expression: "aVar",
-      result: '"var3"',
-    },
-    {
-      line: 3,
-      column: 9,
-      expression: "_aLet2;",
-      result: '"let3"',
-    },
-    {
-      line: 4,
-      column: 11,
-      expression: "_aConst2;",
-      result: '"const3"',
-    },
-    {
-      line: 10,
-      column: 11,
-      expression: "aVar",
-      result: '"var3"',
-    },
-    {
-      line: 11,
-      column: 11,
-      expression: "_aLet2;",
-      result: '"let3"',
-    },
-    {
-      line: 12,
-      column: 13,
-      expression: "_aConst2;",
-      result: '"const3"',
-    },
+function testShadowing(dbg) {
+  return breakpointPreviews(dbg, "shadowed-vars", { line: 18, column: 6 }, [
+      // These aren't what the user would expect, but we test them anyway since
+      // they reflect what this actually returns. These shadowed bindings read
+      // the binding closest to the current frame's scope even though their
+      // actual value is different.
+      {
+        line: 2,
+        column: 9,
+        expression: "aVar",
+        result: '"var3"',
+      },
+      {
+        line: 3,
+        column: 9,
+        expression: "_aLet2;",
+        result: '"let3"',
+      },
+      {
+        line: 4,
+        column: 11,
+        expression: "_aConst2;",
+        result: '"const3"',
+      },
+      {
+        line: 10,
+        column: 11,
+        expression: "aVar",
+        result: '"var3"',
+      },
+      {
+        line: 11,
+        column: 11,
+        expression: "_aLet2;",
+        result: '"let3"',
+      },
+      {
+        line: 12,
+        column: 13,
+        expression: "_aConst2;",
+        result: '"const3"',
+      },
 
-    // These actually result in the values the user would expect.
-    {
-      line: 14,
-      column: 13,
-      expression: "aVar",
-      result: '"var3"',
-    },
-    {
-      line: 15,
-      column: 13,
-      expression: "_aLet2;",
-      result: '"let3"',
-    },
-    {
-      line: 16,
-      column: 13,
-      expression: "_aConst2;",
-      result: '"const3"',
-    },
-  ]);
+      // These actually result in the values the user would expect.
+      {
+        line: 14,
+        column: 13,
+        expression: "aVar",
+        result: '"var3"',
+      },
+      {
+        line: 15,
+        column: 13,
+        expression: "_aLet2;",
+        result: '"let3"',
+      },
+      {
+        line: 16,
+        column: 13,
+        expression: "_aConst2;",
+        result: '"const3"',
+      },
+    ]);
+}
 
-  await breakpointPreviews(dbg, "imported-bindings", { line: 20, column: 2 }, [
-    {
-      line: 22,
-      column: 16,
-      expression: "_mod2.default;",
-      result: '"a-default"',
-    },
-    {
-      line: 23,
-      column: 16,
-      expression: "_mod4.original;",
-      result: '"an-original"',
-    },
-    {
-      line: 24,
-      column: 16,
-      expression: "_mod3.aNamed;",
-      result: '"a-named"',
-    },
-    {
-      line: 25,
-      column: 16,
-      expression: "_mod4.original;",
-      result: '"an-original"',
-    },
-    {
-      line: 26,
-      column: 16,
-      expression: "aNamespace",
-      fields: [
-        ['aNamed', 'a-named'],
-        ['default', 'a-default'],
-      ],
-    },
-    {
-      line: 31,
-      column: 20,
-      expression: "_mod7.default;",
-      result: '"a-default2"',
-    },
-    {
-      line: 32,
-      column: 20,
-      expression: "_mod9.original;",
-      result: '"an-original2"',
-    },
-    {
-      line: 33,
-      column: 20,
-      expression: "_mod8.aNamed2;",
-      result: '"a-named2"',
-    },
-    {
-      line: 34,
-      column: 20,
-      expression: "_mod9.original;",
-      result: '"an-original2"',
-    },
-    {
-      line: 35,
-      column: 20,
-      expression: "aNamespace2",
-      fields: [
-        ['aNamed', 'a-named2'],
-        ['default', 'a-default2'],
-      ],
-    },
-  ]);
+function testImportedBindings(dbg) {
+  return breakpointPreviews(dbg, "imported-bindings", { line: 20, column: 2 }, [
+   {
+     line: 22,
+     column: 16,
+     expression: "_mod2.default;",
+     result: '"a-default"',
+   },
+   {
+     line: 23,
+     column: 16,
+     expression: "_mod4.original;",
+     result: '"an-original"',
+   },
+   {
+     line: 24,
+     column: 16,
+     expression: "_mod3.aNamed;",
+     result: '"a-named"',
+   },
+   {
+     line: 25,
+     column: 16,
+     expression: "_mod4.original;",
+     result: '"an-original"',
+   },
+   {
+     line: 26,
+     column: 16,
+     expression: "aNamespace",
+     fields: [
+       ['aNamed', 'a-named'],
+       ['default', 'a-default'],
+     ],
+   },
+   {
+     line: 31,
+     column: 20,
+     expression: "_mod7.default;",
+     result: '"a-default2"',
+   },
+   {
+     line: 32,
+     column: 20,
+     expression: "_mod9.original;",
+     result: '"an-original2"',
+   },
+   {
+     line: 33,
+     column: 20,
+     expression: "_mod8.aNamed2;",
+     result: '"a-named2"',
+   },
+   {
+     line: 34,
+     column: 20,
+     expression: "_mod9.original;",
+     result: '"an-original2"',
+   },
+   {
+     line: 35,
+     column: 20,
+     expression: "aNamespace2",
+     fields: [
+       ['aNamed', 'a-named2'],
+       ['default', 'a-default2'],
+     ],
+   },
+ ]);
+}
+
+
+add_task(async function() {
+  await pushPref("devtools.debugger.features.map-scopes", true);
+  const dbg = await initDebugger("doc-babel.html");
+
+  await testForOf(dbg)
+  await testShadowing(dbg)
+  await testImportedBindings(dbg)
 });
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-babel-scopes.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-babel-scopes.js
@@ -98,17 +98,17 @@ add_task(async function() {
   // No '<this>' binding here because Babel does not currently general
   // the current mappings for 'this' bindings.
   await breakpointScopes(
     dbg,
     "this-arguments-bindings",
     { line: 8, column: 6 },
     [
       "arrow",
-      ["argArrow", "(optimized away)"],
+      ["argArrow", "(unmapped)"],
       "Block",
       "arrow()",
       "fn",
       ["arg", '"arg-value"'],
       ["arguments", "Arguments"],
       "root",
       "fn()",
       "Module",
@@ -215,19 +215,23 @@ add_task(async function() {
     "fn()",
     ["val", "undefined"],
     "root",
     ["callback", "(optimized away)"],
     ["fn", "(optimized away)"],
     ["val", "(optimized away)"],
     "Module",
 
-    // This value is currently unmapped because import declarations don't map
-    // very well and ones at the end of the file map especially badly.
-    ["aDefault", "(unmapped)"],
+    // This value is currently optimized away, which isn't 100% accurate.
+    // Because import declarations is the last thing in the file, our current
+    // logic doesn't cover _both_ 'var' statements that it generates,
+    // making us use the first, optimized-out binding. Given that imports
+    // are almost never the last thing in a file though, this is probably not
+    // a huge deal for now.
+    ["aDefault", "(optimized away)"],
     ["root", "(optimized away)"],
     ["val", "(optimized away)"],
   ]);
 
   await breakpointScopes(dbg, "non-modules", { line: 7, column: 2 }, []);
 
   await breakpointScopes(dbg, "flowtype-bindings", { line: 8, column: 2 }, [
     "Module",
--- a/devtools/client/debugger/new/test/mochitest/head.js
+++ b/devtools/client/debugger/new/test/mochitest/head.js
@@ -168,16 +168,17 @@ function waitForThreadEvents(dbg, eventN
  * @param {Function} predicate
  * @return {Promise}
  * @static
  */
 function waitForState(dbg, predicate, msg) {
   return new Promise(resolve => {
     info(`Waiting for state change: ${msg || ""}`);
     if (predicate(dbg.store.getState())) {
+      info(`Finished waiting for state change: ${msg || ""}`);
       return resolve();
     }
 
     const unsubscribe = dbg.store.subscribe(() => {
       const result = predicate(dbg.store.getState());
       if (result) {
         info(`Finished waiting for state change: ${msg || ""}`);
         unsubscribe();
@@ -1255,18 +1256,19 @@ async function assertPreviewTooltip(dbg,
 
 async function assertPreviewPopup(dbg, { field, value, expression }) {
   const previewEl = await waitForElement(dbg, "popup");
   const preview = dbg.selectors.getPreview(dbg.getState());
 
   const properties =
     preview.result.preview.ownProperties || preview.result.preview.items;
   const property = properties[field];
+  const propertyValue = property.value || property
 
-  is(`${property.value || property}`, value, "Preview.result");
+  is(`${propertyValue}`, value, "Preview.result");
   is(preview.updating, false, "Preview.updating");
   is(preview.expression, expression, "Preview.expression");
 }
 
 async function assertPreviews(dbg, previews) {
   for (const { line, column, expression, result, fields } of previews) {
     hoverAtPos(dbg, { line, ch: column - 1 });
 
@@ -1277,19 +1279,17 @@ async function assertPreviews(dbg, previ
     if (fields) {
       for (const [field, value] of fields) {
         await assertPreviewPopup(dbg, { expression, field, value });
       }
     } else {
       await assertPreviewTextValue(dbg, { expression, text: result });
     }
 
-    // Move to column 0 after to make sure that the preview created by this
-    // test does not affect later attempts to hover and preview.
-    hoverAtPos(dbg, { line: line, ch: 0 });
+    dbg.actions.clearPreview();
   }
 }
 
 // NOTE: still experimental, the screenshots might not be exactly correct
 async function takeScreenshot(dbg) {
   let canvas = dbg.win.document.createElementNS(
     "http://www.w3.org/1999/xhtml",
     "html:canvas"
--- a/devtools/server/actors/highlighters/shapes.js
+++ b/devtools/server/actors/highlighters/shapes.js
@@ -76,16 +76,17 @@ class ShapesHighlighter extends AutoRefr
 
     this.referenceBox = "border";
     this.useStrokeBox = false;
     this.geometryBox = "";
     this.hoveredPoint = null;
     this.fillRule = "";
     this.numInsetPoints = 0;
     this.transformMode = false;
+    this.viewport = {};
 
     this.markup = new CanvasFrameAnonymousContentHelper(this.highlighterEnv,
       this._buildMarkup.bind(this));
     this.onPageHide = this.onPageHide.bind(this);
 
     let { pageListenerTarget } = this.highlighterEnv;
     DOM_EVENTS.forEach(event => pageListenerTarget.addEventListener(event, this));
     pageListenerTarget.addEventListener("pagehide", this.onPageHide);
@@ -428,16 +429,37 @@ class ShapesHighlighter extends AutoRefr
     // remove existing cursor definitions in the style
     style = style.replace(/cursor:.*?;/g, "");
     style = style.replace(/pointer-events:.*?;/g, "");
     let pointerEvents = cursorType === "auto" ? "none" : "auto";
     container.setAttribute("style",
       `${style}pointer-events:${pointerEvents};cursor:${cursorType};`);
   }
 
+  /**
+   * Set the absolute pixel offsets which define the current viewport in relation to
+   * the full page size.
+   *
+   * If a padding value is given, inset the viewport by this value. This is used to define
+   * a virtual viewport which ensures some element remains visible even when at the edges
+   * of the actual viewport.
+   *
+   * @param {Number} padding
+   *        Optional. Amount by which to inset the viewport in all directions.
+   */
+  setViewport(padding = 0) {
+    const { pageXOffset, pageYOffset, innerWidth, innerHeight } =
+      this.currentNode.ownerGlobal;
+    const left = pageXOffset + padding;
+    const right = innerWidth + pageXOffset - padding;
+    const top = pageYOffset + padding;
+    const bottom = innerHeight + pageYOffset - padding;
+    this.viewport = { left, right, top, bottom, padding };
+  }
+
   handleEvent(event, id) {
     // No event handling if the highlighter is hidden
     if (this.areShapesHidden()) {
       return;
     }
 
     let { target, type, pageX, pageY } = event;
 
@@ -476,31 +498,51 @@ class ShapesHighlighter extends AutoRefr
           this._handleCircleClick(pageX, pageY);
         } else if (this.shapeType === "ellipse") {
           this._handleEllipseClick(pageX, pageY);
         } else if (this.shapeType === "inset") {
           this._handleInsetClick(pageX, pageY);
         }
         event.stopPropagation();
         event.preventDefault();
+
+        // Calculate constraints for a virtual viewport which ensures that a dragged
+        // marker remains visible even at the edges of the actual viewport.
+        this.setViewport(BASE_MARKER_SIZE);
         break;
       case "mouseup":
         if (this[_dragging]) {
           this[_dragging] = null;
           this._handleMarkerHover(this.hoveredPoint);
         }
         break;
       case "mousemove":
         if (!this[_dragging]) {
           this._handleMouseMoveNotDragging(pageX, pageY);
           return;
         }
         event.stopPropagation();
         event.preventDefault();
 
+        // Set constraints for mouse position to ensure dragged marker stays in viewport.
+        const { left, right, top, bottom, padding } = this.viewport;
+        const { x, y } = this[_dragging];
+
+        // If marker was within viewport at mousedown, clamp its changes to the viewport.
+        // If marker was outside, do not clamp and allow dragging outside of the viewport.
+        // The latter applies to shapes in iframes which exceed the iframe viewport,
+        // but their markers are visible in the viewport of the iframe's parent.
+        if (x > left - padding && x < right + padding) {
+          pageX = Math.min(Math.max(left, pageX), right);
+        }
+
+        if (y > top - padding && y < bottom + padding) {
+          pageY = Math.min(Math.max(top, pageY), bottom);
+        }
+
         let { point } = this[_dragging];
         if (this.transformMode) {
           this._handleTransformMove(pageX, pageY);
         } else if (this.shapeType === "polygon") {
           this._handlePolygonMove(pageX, pageY);
         } else if (this.shapeType === "circle") {
           this._handleCircleMove(point, pageX, pageY);
         } else if (this.shapeType === "ellipse") {
--- a/dom/interfaces/base/nsIServiceWorkerManager.idl
+++ b/dom/interfaces/base/nsIServiceWorkerManager.idl
@@ -139,27 +139,16 @@ interface nsIServiceWorkerManager : nsIS
                   in DOMString aScope);
 
   nsIServiceWorkerRegistrationInfo getRegistrationByPrincipal(in nsIPrincipal aPrincipal,
                                                               in DOMString aScope);
 
   [notxpcom, nostdcall] bool StartControlling(in const_ClientInfoRef aClientInfo,
                                               in const_ServiceWorkerDescriptorRef aServiceWorker);
 
-  /*
-   * Clears ServiceWorker registrations from memory and disk for the specified
-   * host.
-   * - All ServiceWorker instances change their state to redundant.
-   * - Existing ServiceWorker instances handling fetches will keep running.
-   * - All documents will immediately stop being controlled.
-   * - Unregister jobs will be queued for all registrations.
-   *   This eventually results in the registration being deleted from disk too.
-   */
-  void removeAndPropagate(in AUTF8String aHost);
-
   // Testing
   DOMString getScopeForUrl(in nsIPrincipal aPrincipal, in DOMString aPath);
 
   // It returns an array of nsIServiceWorkerRegistrationInfos.
   nsIArray getAllRegistrations();
 
   // It calls softUpdate() for each child process.
   [implicit_jscontext] void propagateSoftUpdate(in jsval aOriginAttributes,
--- a/dom/interfaces/storage/moz.build
+++ b/dom/interfaces/storage/moz.build
@@ -5,11 +5,12 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 with Files("**"):
     BUG_COMPONENT = ("Core", "DOM")
 
 XPIDL_SOURCES += [
     'nsIDOMStorage.idl',
     'nsIDOMStorageManager.idl',
+    'nsIStorageActivityService.idl',
 ]
 
 XPIDL_MODULE = 'dom_storage'
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/storage/nsIStorageActivityService.idl
@@ -0,0 +1,42 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "domstubs.idl"
+
+interface nsIArray;
+interface nsIPrincipal;
+
+/**
+ * nsIStorageActivityService is a service that can be used to know which
+ * origins have been active in a time range. This information can be used to
+ * implement "Clear Recent History" or similar features.
+ *
+ * If you are implementing a new Storage component, you should use
+ * QuotaManager. But if you don't do it, remember to call
+ * StorageActivityService methods in order to inform this service about
+ * 'writing' operations executed by origins.
+ */
+[scriptable, builtinclass, uuid(fd1310ba-d1be-4327-988e-92b39fcff6f4)]
+interface nsIStorageActivityService : nsISupports
+{
+  // This returns an array of nsIPrincipals, active between |from| and |to|
+  // timestamps. Note activities older than 1 day are forgotten.
+  // Activity details are not persisted, so this only covers activity since
+  // Firefox was started.  All codebase principals are logged, which includes
+  // non-system principals like "moz-extension://ID", "moz-safe-about:home",
+  // "about:newtab", so principals may need to be filtered before being used.
+  nsIArray getActiveOrigins(in PRTime from, in PRTime to);
+
+  // NOTE: This method is meant to be used for testing only.
+  // The activity of |origin| is moved to the specified timestamp |when|.
+  void moveOriginInTime(in nsIPrincipal origin, in PRTime when);
+
+  // TEST-ONLY method to support clearing all previously known activity.
+  void testOnlyReset();
+};
+
+%{ C++
+#define STORAGE_ACTIVITY_SERVICE_CONTRACTID "@mozilla.org/storage/activity-service;1"
+%}
--- a/dom/quota/ActorsParent.cpp
+++ b/dom/quota/ActorsParent.cpp
@@ -31,16 +31,17 @@
 #include "mozilla/CondVar.h"
 #include "mozilla/dom/PContent.h"
 #include "mozilla/dom/asmjscache/AsmJSCache.h"
 #include "mozilla/dom/cache/QuotaClient.h"
 #include "mozilla/dom/indexedDB/ActorsParent.h"
 #include "mozilla/dom/quota/PQuotaParent.h"
 #include "mozilla/dom/quota/PQuotaRequestParent.h"
 #include "mozilla/dom/quota/PQuotaUsageRequestParent.h"
+#include "mozilla/dom/StorageActivityService.h"
 #include "mozilla/ipc/BackgroundParent.h"
 #include "mozilla/ipc/BackgroundUtils.h"
 #include "mozilla/IntegerRange.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/TextUtils.h"
@@ -3020,16 +3021,21 @@ QuotaObject::EnableQuotaCheck()
 bool
 QuotaObject::LockedMaybeUpdateSize(int64_t aSize, bool aTruncate)
 {
   QuotaManager* quotaManager = QuotaManager::Get();
   MOZ_ASSERT(quotaManager);
 
   quotaManager->mQuotaMutex.AssertCurrentThreadOwns();
 
+  if (mWritingDone == false && mOriginInfo) {
+    mWritingDone = true;
+    StorageActivityService::SendActivity(mOriginInfo->mOrigin);
+  }
+
   if (mQuotaCheckDisabled) {
     return true;
   }
 
   if (mSize == aSize) {
     return true;
   }
 
--- a/dom/quota/QuotaObject.h
+++ b/dom/quota/QuotaObject.h
@@ -51,16 +51,17 @@ public:
   EnableQuotaCheck();
 
 private:
   QuotaObject(OriginInfo* aOriginInfo, const nsAString& aPath, int64_t aSize)
     : mOriginInfo(aOriginInfo)
     , mPath(aPath)
     , mSize(aSize)
     , mQuotaCheckDisabled(false)
+    , mWritingDone(false)
   {
     MOZ_COUNT_CTOR(QuotaObject);
   }
 
   ~QuotaObject()
   {
     MOZ_COUNT_DTOR(QuotaObject);
   }
@@ -81,13 +82,14 @@ private:
 
   mozilla::ThreadSafeAutoRefCnt mRefCnt;
 
   OriginInfo* mOriginInfo;
   nsString mPath;
   int64_t mSize;
 
   bool mQuotaCheckDisabled;
+  bool mWritingDone;
 };
 
 END_QUOTA_NAMESPACE
 
 #endif // mozilla_dom_quota_quotaobject_h__
--- a/dom/serviceworkers/ServiceWorkerManager.cpp
+++ b/dom/serviceworkers/ServiceWorkerManager.cpp
@@ -95,18 +95,16 @@
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::ipc;
 
 namespace mozilla {
 namespace dom {
 
-#define PURGE_DOMAIN_DATA "browser:purge-domain-data"
-#define PURGE_SESSION_HISTORY "browser:purge-session-history"
 #define CLEAR_ORIGIN_DATA "clear-origin-attributes-data"
 
 static_assert(nsIHttpChannelInternal::CORS_MODE_SAME_ORIGIN == static_cast<uint32_t>(RequestMode::Same_origin),
               "RequestMode enumeration value should match Necko CORS mode value.");
 static_assert(nsIHttpChannelInternal::CORS_MODE_NO_CORS == static_cast<uint32_t>(RequestMode::No_cors),
               "RequestMode enumeration value should match Necko CORS mode value.");
 static_assert(nsIHttpChannelInternal::CORS_MODE_CORS == static_cast<uint32_t>(RequestMode::Cors),
               "RequestMode enumeration value should match Necko CORS mode value.");
@@ -287,20 +285,16 @@ ServiceWorkerManager::Init(ServiceWorker
     MOZ_DIAGNOSTIC_ASSERT(aRegistrar);
 
     nsTArray<ServiceWorkerRegistrationData> data;
     aRegistrar->GetRegistrations(data);
     LoadRegistrations(data);
 
     if (obs) {
       DebugOnly<nsresult> rv;
-      rv = obs->AddObserver(this, PURGE_SESSION_HISTORY, false /* ownsWeak */);
-      MOZ_ASSERT(NS_SUCCEEDED(rv));
-      rv = obs->AddObserver(this, PURGE_DOMAIN_DATA, false /* ownsWeak */);
-      MOZ_ASSERT(NS_SUCCEEDED(rv));
       rv = obs->AddObserver(this, CLEAR_ORIGIN_DATA, false /* ownsWeak */);
       MOZ_ASSERT(NS_SUCCEEDED(rv));
     }
   }
 
   PBackgroundChild* actorChild = BackgroundChild::GetOrCreateForCurrentThread();
   if (NS_WARN_IF(!actorChild)) {
     MaybeStartShutdown();
@@ -411,18 +405,16 @@ ServiceWorkerManager::MaybeStartShutdown
     it1.UserData()->mJobQueues.Clear();
   }
 
   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   if (obs) {
     obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
 
     if (XRE_IsParentProcess()) {
-      obs->RemoveObserver(this, PURGE_SESSION_HISTORY);
-      obs->RemoveObserver(this, PURGE_DOMAIN_DATA);
       obs->RemoveObserver(this, CLEAR_ORIGIN_DATA);
     }
   }
 
   if (!mActor) {
     return;
   }
 
@@ -3058,24 +3050,16 @@ ServiceWorkerManager::ForceUnregister(Re
     entry.Data()->Cancel();
     entry.Remove();
   }
 
   // Since Unregister is async, it is ok to call it in an enumeration.
   Unregister(aRegistration->Principal(), nullptr, NS_ConvertUTF8toUTF16(aRegistration->Scope()));
 }
 
-NS_IMETHODIMP
-ServiceWorkerManager::RemoveAndPropagate(const nsACString& aHost)
-{
-  Remove(aHost);
-  PropagateRemove(aHost);
-  return NS_OK;
-}
-
 void
 ServiceWorkerManager::Remove(const nsACString& aHost)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   for (auto it1 = mRegistrationInfos.Iter(); !it1.Done(); it1.Next()) {
     ServiceWorkerManager::RegistrationDataPerPrincipal* data = it1.UserData();
     for (auto it2 = data->mInfos.Iter(); !it2.Done(); it2.Next()) {
@@ -3178,30 +3162,16 @@ ServiceWorkerManager::RemoveListener(nsI
   return NS_OK;
 }
 
 NS_IMETHODIMP
 ServiceWorkerManager::Observe(nsISupports* aSubject,
                               const char* aTopic,
                               const char16_t* aData)
 {
-  if (strcmp(aTopic, PURGE_SESSION_HISTORY) == 0) {
-    MOZ_ASSERT(XRE_IsParentProcess());
-    RemoveAll();
-    PropagateRemoveAll();
-    return NS_OK;
-  }
-
-  if (strcmp(aTopic, PURGE_DOMAIN_DATA) == 0) {
-    MOZ_ASSERT(XRE_IsParentProcess());
-    nsAutoString domain(aData);
-    RemoveAndPropagate(NS_ConvertUTF16toUTF8(domain));
-    return NS_OK;
-  }
-
   if (strcmp(aTopic, CLEAR_ORIGIN_DATA) == 0) {
     MOZ_ASSERT(XRE_IsParentProcess());
     OriginAttributesPattern pattern;
     MOZ_ALWAYS_TRUE(pattern.Init(nsAutoString(aData)));
 
     RemoveAllRegistrations(&pattern);
     return NS_OK;
   }
--- a/dom/serviceworkers/ServiceWorkerRegistrar.cpp
+++ b/dom/serviceworkers/ServiceWorkerRegistrar.cpp
@@ -14,16 +14,17 @@
 #include "nsILineInputStream.h"
 #include "nsIObserverService.h"
 #include "nsIOutputStream.h"
 #include "nsISafeOutputStream.h"
 
 #include "MainThreadUtils.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/dom/StorageActivityService.h"
 #include "mozilla/ErrorNames.h"
 #include "mozilla/ipc/BackgroundChild.h"
 #include "mozilla/ipc/BackgroundParent.h"
 #include "mozilla/ipc/PBackgroundChild.h"
 #include "mozilla/ModuleUtils.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "mozJSComponentLoader.h"
@@ -260,16 +261,17 @@ ServiceWorkerRegistrar::RegisterServiceW
 
   {
     MonitorAutoLock lock(mMonitor);
     MOZ_ASSERT(mDataLoaded);
     RegisterServiceWorkerInternal(aData);
   }
 
   MaybeScheduleSaveData();
+  StorageActivityService::SendActivity(aData.principal());
 }
 
 void
 ServiceWorkerRegistrar::UnregisterServiceWorker(
                                             const PrincipalInfo& aPrincipalInfo,
                                             const nsACString& aScope)
 {
   AssertIsOnBackgroundThread();
@@ -296,43 +298,54 @@ ServiceWorkerRegistrar::UnregisterServic
         deleted = true;
         break;
       }
     }
   }
 
   if (deleted) {
     MaybeScheduleSaveData();
+    StorageActivityService::SendActivity(aPrincipalInfo);
   }
 }
 
 void
 ServiceWorkerRegistrar::RemoveAll()
 {
   AssertIsOnBackgroundThread();
 
   if (mShuttingDown) {
     NS_WARNING("Failed to remove all the serviceWorkers during shutting down.");
     return;
   }
 
   bool deleted = false;
 
+  nsTArray<ServiceWorkerRegistrationData> data;
   {
     MonitorAutoLock lock(mMonitor);
     MOZ_ASSERT(mDataLoaded);
 
+    // Let's take a copy in order to inform StorageActivityService.
+    data = mData;
+
     deleted = !mData.IsEmpty();
     mData.Clear();
 
     mDataGeneration = GetNextGeneration();
   }
 
-  if (deleted) {
-    MaybeScheduleSaveData();
+  if (!deleted) {
+    return;
+  }
+
+  MaybeScheduleSaveData();
+
+  for (uint32_t i = 0, len = data.Length(); i < len; ++i) {
+    StorageActivityService::SendActivity(data[i].principal());
   }
 }
 
 void
 ServiceWorkerRegistrar::LoadData()
 {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(!mDataLoaded);
--- a/dom/serviceworkers/test/test_sanitize_domain.html
+++ b/dom/serviceworkers/test/test_sanitize_domain.html
@@ -33,17 +33,17 @@
       });
     }
 
     registerSW().then(function() {
       return testFrame("http://example.com/tests/dom/serviceworkers/test/sanitize/frame.html").then(function(body) {
         is(body, "intercepted", "Expected serviceworker to intercept request");
       });
     }).then(function() {
-      SpecialPowers.removeServiceWorkerDataForExampleDomain();
+      return SpecialPowers.removeServiceWorkerDataForExampleDomain();
     }).then(function() {
       return checkDomainRegistration("prefixexample.com", true /* exists */)
         .then(function(e) {
           return checkDomainRegistration("example.com", false /* exists */);
         }).then(function(e) {
           SimpleTest.finish();
         });
     })
new file mode 100644
--- /dev/null
+++ b/dom/storage/StorageActivityService.cpp
@@ -0,0 +1,318 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "StorageActivityService.h"
+
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/StaticPtr.h"
+#include "nsIMutableArray.h"
+#include "nsSupportsPrimitives.h"
+#include "nsXPCOM.h"
+
+// This const is used to know when origin activities should be purged because
+// too old. This value should be in sync with what the UI needs.
+#define TIME_MAX_SECS 86400 /* 24 hours */
+
+namespace mozilla {
+namespace dom {
+
+static StaticRefPtr<StorageActivityService> gStorageActivityService;
+static bool gStorageActivityShutdown = false;
+
+/* static */ void
+StorageActivityService::SendActivity(nsIPrincipal* aPrincipal)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!aPrincipal ||
+      BasePrincipal::Cast(aPrincipal)->Kind() != BasePrincipal::eCodebasePrincipal) {
+    // Only codebase principals.
+    return;
+  }
+
+  RefPtr<StorageActivityService> service = GetOrCreate();
+  if (NS_WARN_IF(!service)) {
+    return;
+  }
+
+  service->SendActivityInternal(aPrincipal);
+}
+
+/* static */ void
+StorageActivityService::SendActivity(const mozilla::ipc::PrincipalInfo& aPrincipalInfo)
+{
+  if (aPrincipalInfo.type() !=
+      mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) {
+    // only content principal.
+    return;
+  }
+
+  RefPtr<Runnable> r = NS_NewRunnableFunction(
+    "StorageActivityService::SendActivity",
+    [aPrincipalInfo] () {
+      MOZ_ASSERT(NS_IsMainThread());
+
+      nsCOMPtr<nsIPrincipal> principal =
+        mozilla::ipc::PrincipalInfoToPrincipal(aPrincipalInfo);
+
+      StorageActivityService::SendActivity(principal);
+    });
+
+  SystemGroup::Dispatch(TaskCategory::Other, r.forget());
+}
+
+/* static */ void
+StorageActivityService::SendActivity(const nsACString& aOrigin)
+{
+  MOZ_ASSERT(XRE_IsParentProcess());
+
+  nsCString origin;
+  origin.Assign(aOrigin);
+
+  RefPtr<Runnable> r = NS_NewRunnableFunction(
+    "StorageActivityService::SendActivity",
+    [origin] () {
+      MOZ_ASSERT(NS_IsMainThread());
+
+      RefPtr<StorageActivityService> service = GetOrCreate();
+      if (NS_WARN_IF(!service)) {
+        return;
+      }
+
+      service->SendActivityInternal(origin);
+    });
+
+  if (NS_IsMainThread()) {
+    Unused << r->Run();
+  } else {
+    SystemGroup::Dispatch(TaskCategory::Other, r.forget());
+  }
+}
+
+/* static */ already_AddRefed<StorageActivityService>
+StorageActivityService::GetOrCreate()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!gStorageActivityService && !gStorageActivityShutdown) {
+    RefPtr<StorageActivityService> service = new StorageActivityService();
+
+    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+    if (NS_WARN_IF(!obs)) {
+      return nullptr;
+    }
+
+    nsresult rv = obs->AddObserver(service, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return nullptr;
+    }
+
+    gStorageActivityService = service;
+  }
+
+  RefPtr<StorageActivityService> service = gStorageActivityService;
+  return service.forget();
+}
+
+StorageActivityService::StorageActivityService()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+}
+
+StorageActivityService::~StorageActivityService()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!mTimer);
+}
+
+void
+StorageActivityService::SendActivityInternal(nsIPrincipal* aPrincipal)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aPrincipal);
+  MOZ_ASSERT(BasePrincipal::Cast(aPrincipal)->Kind() == BasePrincipal::eCodebasePrincipal);
+
+  if (!XRE_IsParentProcess()) {
+    SendActivityToParent(aPrincipal);
+    return;
+  }
+
+  nsAutoCString origin;
+  nsresult rv = aPrincipal->GetOrigin(origin);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  SendActivityInternal(origin);
+}
+
+void
+StorageActivityService::SendActivityInternal(const nsACString& aOrigin)
+{
+  MOZ_ASSERT(XRE_IsParentProcess());
+
+  mActivities.Put(aOrigin, PR_Now());
+  MaybeStartTimer();
+}
+
+void
+StorageActivityService::SendActivityToParent(nsIPrincipal* aPrincipal)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!XRE_IsParentProcess());
+
+  PBackgroundChild* actor = BackgroundChild::GetOrCreateForCurrentThread();
+  if (NS_WARN_IF(!actor)) {
+    return;
+  }
+
+  mozilla::ipc::PrincipalInfo principalInfo;
+  nsresult rv =
+    mozilla::ipc::PrincipalToPrincipalInfo(aPrincipal, &principalInfo);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  actor->SendStorageActivity(principalInfo);
+}
+
+NS_IMETHODIMP
+StorageActivityService::Observe(nsISupports* aSubject, const char* aTopic,
+                                const char16_t* aData)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID));
+
+  MaybeStopTimer();
+
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (obs) {
+    obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+  }
+
+  gStorageActivityShutdown = true;
+  gStorageActivityService = nullptr;
+  return NS_OK;
+}
+
+void
+StorageActivityService::MaybeStartTimer()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!mTimer) {
+    mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+    mTimer->InitWithCallback(this,
+                             1000 * 5 * 60 /* any 5 minutes */,
+                             nsITimer::TYPE_REPEATING_SLACK);
+  }
+}
+
+void
+StorageActivityService::MaybeStopTimer()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (mTimer) {
+    mTimer->Cancel();
+    mTimer = nullptr;
+  }
+}
+
+NS_IMETHODIMP
+StorageActivityService::Notify(nsITimer* aTimer)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mTimer == aTimer);
+
+  uint64_t now = PR_Now();
+
+  for (auto iter = mActivities.Iter(); !iter.Done(); iter.Next()) {
+    if ((now - iter.UserData()) / PR_USEC_PER_SEC > TIME_MAX_SECS) {
+      iter.Remove();
+    }
+  }
+
+  // If no activities, let's stop the timer.
+  if (mActivities.Count() == 0) {
+    MaybeStopTimer();
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+StorageActivityService::GetActiveOrigins(PRTime aFrom, PRTime aTo,
+                                         nsIArray** aRetval)
+{
+  uint64_t now = PR_Now();
+  if (((now - aFrom) / PR_USEC_PER_SEC) > TIME_MAX_SECS ||
+       aFrom >= aTo) {
+    return NS_ERROR_RANGE_ERR;
+  }
+
+  nsresult rv = NS_OK;
+  nsCOMPtr<nsIMutableArray> devices =
+    do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  for (auto iter = mActivities.Iter(); !iter.Done(); iter.Next()) {
+    if (iter.UserData() >= aFrom && iter.UserData() <= aTo) {
+      RefPtr<BasePrincipal> principal =
+        BasePrincipal::CreateCodebasePrincipal(iter.Key());
+      MOZ_ASSERT(principal);
+
+      rv = devices->AppendElement(principal);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+    }
+  }
+
+  devices.forget(aRetval);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+StorageActivityService::MoveOriginInTime(nsIPrincipal* aPrincipal,
+                                         PRTime aWhen)
+{
+  if (!XRE_IsParentProcess()) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsAutoCString origin;
+  nsresult rv = aPrincipal->GetOrigin(origin);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  mActivities.Put(origin, aWhen / PR_USEC_PER_SEC);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+StorageActivityService::TestOnlyReset()
+{
+  mActivities.Clear();
+  return NS_OK;
+}
+
+NS_INTERFACE_MAP_BEGIN(StorageActivityService)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStorageActivityService)
+  NS_INTERFACE_MAP_ENTRY(nsIStorageActivityService)
+  NS_INTERFACE_MAP_ENTRY(nsIObserver)
+  NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
+  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(StorageActivityService)
+NS_IMPL_RELEASE(StorageActivityService)
+
+} // dom namespace
+} // mozilla namespace
new file mode 100644
--- /dev/null
+++ b/dom/storage/StorageActivityService.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_StorageActivityService_h
+#define mozilla_dom_StorageActivityService_h
+
+#include "nsDataHashtable.h"
+#include "nsIStorageActivityService.h"
+#include "nsITimer.h"
+#include "nsWeakReference.h"
+
+namespace mozilla {
+
+namespace ipc {
+class PrincipalInfo;
+} // ipc
+
+namespace dom {
+
+class StorageActivityService final : public nsIStorageActivityService
+                                   , public nsIObserver
+                                   , public nsITimerCallback
+                                   , public nsSupportsWeakReference
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSISTORAGEACTIVITYSERVICE
+  NS_DECL_NSIOBSERVER
+  NS_DECL_NSITIMERCALLBACK
+
+  // Main-thread only.
+  static void
+  SendActivity(nsIPrincipal* aPrincipal);
+
+  // Thread-safe.
+  static void
+  SendActivity(const mozilla::ipc::PrincipalInfo& aPrincipalInfo);
+
+  // Thread-safe but for parent process only!
+  static void
+  SendActivity(const nsACString& aOrigin);
+
+  // Used by XPCOM. Don't use it, use SendActivity() instead.
+  static already_AddRefed<StorageActivityService>
+  GetOrCreate();
+
+private:
+  StorageActivityService();
+  ~StorageActivityService();
+
+  void
+  SendActivityInternal(nsIPrincipal* aPrincipal);
+
+  void
+  SendActivityInternal(const nsACString& aOrigin);
+
+  void
+  SendActivityToParent(nsIPrincipal* aPrincipal);
+
+  void
+  MaybeStartTimer();
+
+  void
+  MaybeStopTimer();
+
+  // Activities grouped by origin (+OriginAttributes).
+  nsDataHashtable<nsCStringHashKey, PRTime> mActivities;
+
+  nsCOMPtr<nsITimer> mTimer;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_StorageActivityService_h
--- a/dom/storage/moz.build
+++ b/dom/storage/moz.build
@@ -7,29 +7,31 @@
 with Files("**"):
     BUG_COMPONENT = ("Core", "DOM")
 
 EXPORTS.mozilla.dom += [
     'LocalStorage.h',
     'LocalStorageManager.h',
     'SessionStorageManager.h',
     'Storage.h',
+    'StorageActivityService.h',
     'StorageIPC.h',
     'StorageNotifierService.h',
     'StorageUtils.h',
 ]
 
 UNIFIED_SOURCES += [
     'LocalStorage.cpp',
     'LocalStorageCache.cpp',
     'LocalStorageManager.cpp',
     'SessionStorage.cpp',
     'SessionStorageCache.cpp',
     'SessionStorageManager.cpp',
     'Storage.cpp',
+    'StorageActivityService.cpp',
     'StorageDBThread.cpp',
     'StorageDBUpdater.cpp',
     'StorageIPC.cpp',
     'StorageNotifierService.cpp',
     'StorageObserver.cpp',
     'StorageUtils.cpp',
 ]
 
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -2131,41 +2131,45 @@ AdjustDeltaForAllowedScrollDirections(
 nsEventStatus AsyncPanZoomController::OnScrollWheel(const ScrollWheelInput& aEvent)
 {
   // Get the scroll wheel's delta values in parent-layer pixels. But before
   // getting the values, we need to check if it is an auto-dir scroll and if it
   // should be adjusted, if both answers are yes, let's adjust X and Y values
   // first, and then get the delta values in parent-layer pixels based on the
   // adjusted values.
   bool adjustedByAutoDir = false;
+  auto deltaX = aEvent.mDeltaX;
+  auto deltaY = aEvent.mDeltaY;
   ParentLayerPoint delta;
   if (aEvent.IsAutoDir()) {
     // It's an auto-dir scroll, so check if its delta should be adjusted, if so,
     // adjust it.
     RecursiveMutexAutoLock lock(mRecursiveMutex);
-    auto deltaX = aEvent.mDeltaX;
-    auto deltaY = aEvent.mDeltaY;
     bool isRTL = IsContentOfHonouredTargetRightToLeft(aEvent.HonoursRoot());
     APZAutoDirWheelDeltaAdjuster adjuster(deltaX, deltaY, mX, mY, isRTL);
     if (adjuster.ShouldBeAdjusted()) {
       adjuster.Adjust();
-      // If the original delta values have been adjusted, we pass them to
-      // replace the original delta values in |aEvent| so that the delta values
-      // in parent-layer pixels are caculated based on the adjusted values, not
-      // the original ones.
-      // Pay special attention to the last two parameters. They are in a swaped
-      // order so that they still correspond to their delta after adjustment.
-      delta = GetScrollWheelDelta(aEvent,
-                                  deltaX, deltaY,
-                                  aEvent.mUserDeltaMultiplierY,
-                                  aEvent.mUserDeltaMultiplierX);
       adjustedByAutoDir = true;
     }
   }
-  if (!adjustedByAutoDir) {
+  // Ensure the calls to GetScrollWheelDelta are outside the mRecursiveMutex
+  // lock since these calls may acquire the APZ tree lock. Holding mRecursiveMutex
+  // while acquiring the APZ tree lock is lock ordering violation.
+  if (adjustedByAutoDir) {
+    // If the original delta values have been adjusted, we pass them to
+    // replace the original delta values in |aEvent| so that the delta values
+    // in parent-layer pixels are caculated based on the adjusted values, not
+    // the original ones.
+    // Pay special attention to the last two parameters. They are in a swaped
+    // order so that they still correspond to their delta after adjustment.
+    delta = GetScrollWheelDelta(aEvent,
+                                deltaX, deltaY,
+                                aEvent.mUserDeltaMultiplierY,
+                                aEvent.mUserDeltaMultiplierX);
+  } else {
     // If the original delta values haven't been adjusted by auto-dir, just pass
     // the |aEvent| and caculate the delta values in parent-layer pixels based
     // on the original delta values from |aEvent|.
     delta = GetScrollWheelDelta(aEvent);
   }
 
   APZC_LOG("%p got a scroll-wheel with delta in parent-layer pixels: %s\n",
            this, Stringify(delta).c_str());
--- a/ipc/glue/BackgroundParentImpl.cpp
+++ b/ipc/glue/BackgroundParentImpl.cpp
@@ -20,16 +20,17 @@
 #include "mozilla/dom/FileSystemRequestParent.h"
 #include "mozilla/dom/GamepadEventChannelParent.h"
 #include "mozilla/dom/GamepadTestChannelParent.h"
 #include "mozilla/dom/PGamepadEventChannelParent.h"
 #include "mozilla/dom/PGamepadTestChannelParent.h"
 #include "mozilla/dom/MessagePortParent.h"
 #include "mozilla/dom/ServiceWorkerManagerParent.h"
 #include "mozilla/dom/ServiceWorkerRegistrar.h"
+#include "mozilla/dom/StorageActivityService.h"
 #include "mozilla/dom/asmjscache/AsmJSCache.h"
 #include "mozilla/dom/cache/ActorUtils.h"
 #include "mozilla/dom/indexedDB/ActorsParent.h"
 #include "mozilla/dom/ipc/IPCBlobInputStreamParent.h"
 #include "mozilla/dom/ipc/PendingIPCBlobParent.h"
 #include "mozilla/dom/ipc/TemporaryIPCBlobParent.h"
 #include "mozilla/dom/quota/ActorsParent.h"
 #include "mozilla/dom/StorageIPC.h"
@@ -284,16 +285,19 @@ mozilla::ipc::IPCResult
 BackgroundParentImpl::RecvBroadcastLocalStorageChange(
                                             const nsString& aDocumentURI,
                                             const nsString& aKey,
                                             const nsString& aOldValue,
                                             const nsString& aNewValue,
                                             const PrincipalInfo& aPrincipalInfo,
                                             const bool& aIsPrivate)
 {
+  // Let's inform the StorageActivityService about this change.
+  dom::StorageActivityService::SendActivity(aPrincipalInfo);
+
   nsTArray<PBackgroundParent*> liveActorArray;
   if (NS_WARN_IF(!BackgroundParent::GetLiveActorArray(this, liveActorArray))) {
     return IPC_FAIL_NO_REASON(this);
   }
 
   for (auto* liveActor : liveActorArray) {
     if (liveActor != this) {
       Unused << liveActor->SendDispatchLocalStorageChange(
@@ -1031,16 +1035,23 @@ BackgroundParentImpl::DeallocPClientMana
 
 mozilla::ipc::IPCResult
 BackgroundParentImpl::RecvPClientManagerConstructor(mozilla::dom::PClientManagerParent* aActor)
 {
   mozilla::dom::InitClientManagerParent(aActor);
   return IPC_OK();
 }
 
+IPCResult
+BackgroundParentImpl::RecvStorageActivity(const PrincipalInfo& aPrincipalInfo)
+{
+  dom::StorageActivityService::SendActivity(aPrincipalInfo);
+  return IPC_OK();
+}
+
 } // namespace ipc
 } // namespace mozilla
 
 void
 TestParent::ActorDestroy(ActorDestroyReason aWhy)
 {
   mozilla::ipc::AssertIsInMainProcess();
   AssertIsOnBackgroundThread();
--- a/ipc/glue/BackgroundParentImpl.h
+++ b/ipc/glue/BackgroundParentImpl.h
@@ -279,14 +279,17 @@ protected:
   virtual bool
   DeallocPMIDIPortParent(PMIDIPortParent* aActor) override;
 
   virtual PMIDIManagerParent*
   AllocPMIDIManagerParent() override;
 
   virtual bool
   DeallocPMIDIManagerParent(PMIDIManagerParent* aActor) override;
+
+  virtual mozilla::ipc::IPCResult
+  RecvStorageActivity(const PrincipalInfo& aPrincipalInfo) override;
 };
 
 } // namespace ipc
 } // namespace mozilla
 
 #endif // mozilla_ipc_backgroundparentimpl_h__
--- a/ipc/glue/PBackground.ipdl
+++ b/ipc/glue/PBackground.ipdl
@@ -143,16 +143,20 @@ parent:
 
   async PTemporaryIPCBlob();
 
   async PClientManager();
 
   async PMIDIManager();
   async PMIDIPort(MIDIPortInfo portInfo, bool sysexEnabled);
 
+  // This method is used to propagate storage activities from the child actor
+  // to the parent actor. See StorageActivityService.
+  async StorageActivity(PrincipalInfo principalInfo);
+
 child:
   async PCache();
   async PCacheStreamControl();
 
   async PParentToChildStream();
 
   async PPendingIPCBlob(IPCBlob blob);
 
--- a/js/src/jit-test/tests/wasm/gc/anyref.js
+++ b/js/src/jit-test/tests/wasm/gc/anyref.js
@@ -145,16 +145,22 @@ assertEq(exports.ref_or_null(baguette, 0
 let ref = exports.ref_or_null(baguette, 1);
 assertEq(ref, baguette);
 assertEq(ref.calories, baguette.calories);
 
 ref = exports.nested(baguette, 0);
 assertEq(ref, baguette);
 assertEq(ref.calories, baguette.calories);
 
+if (wasmDebuggingIsSupported()) {
+    let g = newGlobal();
+    let dbg = new Debugger(g);
+    g.eval(`o = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary('(module (func (result anyref) (param anyref) get_local 0) (export "" 0))')));`);
+}
+
 // More interesting use cases about control flow joins.
 
 function assertJoin(body) {
     let val = { i: -1 };
     assertEq(wasmEvalText(`(module
         (func (export "test") (param $ref anyref) (param $i i32) (result anyref)
             ${body}
         )
--- a/js/src/jit/TypePolicy.h
+++ b/js/src/jit/TypePolicy.h
@@ -76,255 +76,280 @@ class NoTypePolicy
             return nullptr;
         }
     };
 };
 
 class BoxInputsPolicy final : public TypePolicy
 {
   public:
+    constexpr BoxInputsPolicy() { }
     SPECIALIZATION_DATA_;
     static MOZ_MUST_USE bool staticAdjustInputs(TempAllocator& alloc, MInstruction* def);
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* def) const override {
         return staticAdjustInputs(alloc, def);
     }
 };
 
 class ArithPolicy final : public TypePolicy
 {
   public:
+    constexpr ArithPolicy() { }
     SPECIALIZATION_DATA_;
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* def) const override;
 };
 
 class AllDoublePolicy final : public TypePolicy
 {
   public:
+    constexpr AllDoublePolicy() { }
     EMPTY_DATA_;
     static MOZ_MUST_USE bool staticAdjustInputs(TempAllocator& alloc, MInstruction* def);
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* def) const override {
         return staticAdjustInputs(alloc, def);
     }
 };
 
 class BitwisePolicy final : public TypePolicy
 {
   public:
+    constexpr BitwisePolicy() { }
     SPECIALIZATION_DATA_;
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* def) const override;
 };
 
 class ComparePolicy final : public TypePolicy
 {
   public:
+    constexpr ComparePolicy() { }
     EMPTY_DATA_;
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* def) const override;
 };
 
 class SameValuePolicy final : public TypePolicy
 {
   public:
+    constexpr SameValuePolicy() { }
     EMPTY_DATA_;
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* def) const override;
 };
 
 // Policy for MTest instructions.
 class TestPolicy final : public TypePolicy
 {
   public:
+    constexpr TestPolicy() { }
     EMPTY_DATA_;
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* ins) const override;
 };
 
 class TypeBarrierPolicy final : public TypePolicy
 {
   public:
+    constexpr TypeBarrierPolicy() { }
     EMPTY_DATA_;
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* ins) const override;
 };
 
 class CallPolicy final : public TypePolicy
 {
   public:
+    constexpr CallPolicy() { }
     EMPTY_DATA_;
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* def) const override;
 };
 
 // Policy for MPow. First operand Double; second Double or Int32.
 class PowPolicy final : public TypePolicy
 {
   public:
+    constexpr PowPolicy() { }
     SPECIALIZATION_DATA_;
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* ins) const override;
 };
 
 // Expect a string for operand Op. If the input is a Value, it is unboxed.
 template <unsigned Op>
 class StringPolicy final : public TypePolicy
 {
   public:
+    constexpr StringPolicy() { }
     EMPTY_DATA_;
     static MOZ_MUST_USE bool staticAdjustInputs(TempAllocator& alloc, MInstruction* def);
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* def) const override {
         return staticAdjustInputs(alloc, def);
     }
 };
 
 // Expect a string for operand Op. Else a ToString instruction is inserted.
 template <unsigned Op>
 class ConvertToStringPolicy final : public TypePolicy
 {
   public:
+    constexpr ConvertToStringPolicy() { }
     EMPTY_DATA_;
     static MOZ_MUST_USE bool staticAdjustInputs(TempAllocator& alloc, MInstruction* def);
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* def) const override {
         return staticAdjustInputs(alloc, def);
     }
 };
 
 // Expect an Boolean for operand Op. If the input is a Value, it is unboxed.
 template <unsigned Op>
 class BooleanPolicy final : private TypePolicy
 {
   public:
+    constexpr BooleanPolicy() { }
     EMPTY_DATA_;
     static MOZ_MUST_USE bool staticAdjustInputs(TempAllocator& alloc, MInstruction* def);
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* def) const override {
         return staticAdjustInputs(alloc, def);
     }
 };
 
 // Expects either an Int32 or a boxed Int32 for operand Op; may unbox if needed.
 template <unsigned Op>
 class UnboxedInt32Policy final : private TypePolicy
 {
   public:
+    constexpr UnboxedInt32Policy() { }
     EMPTY_DATA_;
     static MOZ_MUST_USE bool staticAdjustInputs(TempAllocator& alloc, MInstruction* def);
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* def) const override {
         return staticAdjustInputs(alloc, def);
     }
 };
 
 // Expect an Int for operand Op. Else a ToInt32 instruction is inserted.
 template <unsigned Op>
 class ConvertToInt32Policy final : public TypePolicy
 {
   public:
+    constexpr ConvertToInt32Policy() { }
     EMPTY_DATA_;
     static MOZ_MUST_USE bool staticAdjustInputs(TempAllocator& alloc, MInstruction* def);
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* def) const override {
         return staticAdjustInputs(alloc, def);
     }
 };
 
 // Expect an Int for operand Op. Else a TruncateToInt32 instruction is inserted.
 template <unsigned Op>
 class TruncateToInt32Policy final : public TypePolicy
 {
   public:
+    constexpr TruncateToInt32Policy() { }
     EMPTY_DATA_;
     static MOZ_MUST_USE bool staticAdjustInputs(TempAllocator& alloc, MInstruction* def);
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* def) const override {
         return staticAdjustInputs(alloc, def);
     }
 };
 
 // Expect a double for operand Op. If the input is a Value, it is unboxed.
 template <unsigned Op>
 class DoublePolicy final : public TypePolicy
 {
   public:
+    constexpr DoublePolicy() { }
     EMPTY_DATA_;
     static MOZ_MUST_USE bool staticAdjustInputs(TempAllocator& alloc, MInstruction* def);
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* def) const override {
         return staticAdjustInputs(alloc, def);
     }
 };
 
 // Expect a float32 for operand Op. If the input is a Value, it is unboxed.
 template <unsigned Op>
 class Float32Policy final : public TypePolicy
 {
   public:
+    constexpr Float32Policy() { }
     EMPTY_DATA_;
     static MOZ_MUST_USE bool staticAdjustInputs(TempAllocator& alloc, MInstruction* def);
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* def) const override {
         return staticAdjustInputs(alloc, def);
     }
 };
 
 // Expect a float32 OR a double for operand Op, but will prioritize Float32
 // if the result type is set as such. If the input is a Value, it is unboxed.
 template <unsigned Op>
 class FloatingPointPolicy final : public TypePolicy
 {
   public:
+    constexpr FloatingPointPolicy() { }
     SPECIALIZATION_DATA_;
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* def) const override;
 };
 
 template <unsigned Op>
 class NoFloatPolicy final : public TypePolicy
 {
   public:
+    constexpr NoFloatPolicy() { }
     EMPTY_DATA_;
     static MOZ_MUST_USE bool staticAdjustInputs(TempAllocator& alloc, MInstruction* def);
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* def) const override {
         return staticAdjustInputs(alloc, def);
     }
 };
 
 // Policy for guarding variadic instructions such as object / array state
 // instructions.
 template <unsigned FirstOp>
 class NoFloatPolicyAfter final : public TypePolicy
 {
   public:
+    constexpr NoFloatPolicyAfter() { }
     EMPTY_DATA_;
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* ins) const override;
 };
 
 // Box objects or strings as an input to a ToDouble instruction.
 class ToDoublePolicy final : public TypePolicy
 {
   public:
+    constexpr ToDoublePolicy() { }
     EMPTY_DATA_;
     static MOZ_MUST_USE bool staticAdjustInputs(TempAllocator& alloc, MInstruction* def);
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* def) const override {
         return staticAdjustInputs(alloc, def);
     }
 };
 
 // Box objects, strings and undefined as input to a ToInt32 instruction.
 class ToInt32Policy final : public TypePolicy
 {
   public:
+    constexpr ToInt32Policy() { }
     EMPTY_DATA_;
     static MOZ_MUST_USE bool staticAdjustInputs(TempAllocator& alloc, MInstruction* def);
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* def) const override {
         return staticAdjustInputs(alloc, def);
     }
 };
 
 // Box objects as input to a ToString instruction.
 class ToStringPolicy final : public TypePolicy
 {
   public:
+    constexpr ToStringPolicy() { }
     EMPTY_DATA_;
     static MOZ_MUST_USE bool staticAdjustInputs(TempAllocator& alloc, MInstruction* def);
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* def) const override {
         return staticAdjustInputs(alloc, def);
     }
 };
 
 template <unsigned Op>
 class ObjectPolicy final : public TypePolicy
 {
   public:
+    constexpr ObjectPolicy() { }
     EMPTY_DATA_;
     static MOZ_MUST_USE bool staticAdjustInputs(TempAllocator& alloc, MInstruction* ins);
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* ins) const override {
         return staticAdjustInputs(alloc, ins);
     }
 };
 
 // Single-object input. If the input is a Value, it is unboxed. If it is
@@ -332,81 +357,89 @@ class ObjectPolicy final : public TypePo
 typedef ObjectPolicy<0> SingleObjectPolicy;
 
 // Convert an operand to have a type identical to the scalar type of the
 // returned type of the instruction.
 template <unsigned Op>
 class SimdScalarPolicy final : public TypePolicy
 {
   public:
+    constexpr SimdScalarPolicy() { }
     EMPTY_DATA_;
     static MOZ_MUST_USE bool staticAdjustInputs(TempAllocator& alloc, MInstruction* def);
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* def) const override {
         return staticAdjustInputs(alloc, def);
     }
 };
 
 class SimdAllPolicy final : public TypePolicy
 {
   public:
+    constexpr SimdAllPolicy () { }
     SPECIALIZATION_DATA_;
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* ins) const override;
 };
 
 template <unsigned Op>
 class SimdPolicy final : public TypePolicy
 {
   public:
+    constexpr SimdPolicy() { }
     SPECIALIZATION_DATA_;
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* ins) const override;
 };
 
 class SimdSelectPolicy final : public TypePolicy
 {
   public:
+    constexpr SimdSelectPolicy() { }
     SPECIALIZATION_DATA_;
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* ins) const override;
 };
 
 class SimdShufflePolicy final : public TypePolicy
 {
   public:
+    constexpr SimdShufflePolicy() { }
     SPECIALIZATION_DATA_;
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* ins) const override;
 };
 
 // SIMD value-type policy, use the returned type of the instruction to determine
 // how to unbox its operand.
 template <unsigned Op>
 class SimdSameAsReturnedTypePolicy final : public TypePolicy
 {
   public:
+    constexpr SimdSameAsReturnedTypePolicy() { }
     EMPTY_DATA_;
     static MOZ_MUST_USE bool staticAdjustInputs(TempAllocator& alloc, MInstruction* ins);
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* ins) const override {
         return staticAdjustInputs(alloc, ins);
     }
 };
 
 template <unsigned Op>
 class BoxPolicy final : public TypePolicy
 {
   public:
+    constexpr BoxPolicy() { }
     EMPTY_DATA_;
     static MOZ_MUST_USE bool staticAdjustInputs(TempAllocator& alloc, MInstruction* ins);
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* ins) const override {
         return staticAdjustInputs(alloc, ins);
     }
 };
 
 // Boxes everything except inputs of type Type.
 template <unsigned Op, MIRType Type>
 class BoxExceptPolicy final : public TypePolicy
 {
   public:
+    constexpr BoxExceptPolicy() { }
     EMPTY_DATA_;
     static MOZ_MUST_USE bool staticAdjustInputs(TempAllocator& alloc, MInstruction* ins);
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* ins) const override {
         return staticAdjustInputs(alloc, ins);
     }
 };
 
 // Box if not a typical property id (string, symbol, int32).
@@ -433,89 +466,98 @@ class MixPolicy final : public TypePolic
     template <class P, class... Rest>
     static typename mozilla::EnableIf<(sizeof...(Rest) > 0), bool>::Type
     staticAdjustInputsHelper(TempAllocator& alloc, MInstruction* ins) {
         return P::staticAdjustInputs(alloc, ins) &&
                MixPolicy::staticAdjustInputsHelper<Rest...>(alloc, ins);
     }
 
   public:
+    constexpr MixPolicy() { }
     EMPTY_DATA_;
     static MOZ_MUST_USE bool staticAdjustInputs(TempAllocator& alloc, MInstruction* ins) {
         return MixPolicy::staticAdjustInputsHelper<Policies...>(alloc, ins);
     }
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* ins) const override {
         return staticAdjustInputs(alloc, ins);
     }
 };
 
 class CallSetElementPolicy final : public TypePolicy
 {
   public:
+    constexpr CallSetElementPolicy() { }
     EMPTY_DATA_;
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* def) const override;
 };
 
 // First operand will be boxed to a Value (except for an object)
 // Second operand (if specified) will forcefully be unboxed to an object
 class InstanceOfPolicy final : public TypePolicy
 {
   public:
+    constexpr InstanceOfPolicy() { }
     EMPTY_DATA_;
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* def) const override;
 };
 
 class StoreTypedArrayHolePolicy;
 
 class StoreUnboxedScalarPolicy : public TypePolicy
 {
   private:
+    constexpr StoreUnboxedScalarPolicy() { }
     static MOZ_MUST_USE bool adjustValueInput(TempAllocator& alloc, MInstruction* ins,
                                               Scalar::Type arrayType, MDefinition* value,
                                               int valueOperand);
 
     friend class StoreTypedArrayHolePolicy;
 
   public:
     EMPTY_DATA_;
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* ins) const override;
 };
 
 class StoreTypedArrayHolePolicy final : public StoreUnboxedScalarPolicy
 {
   public:
+    constexpr StoreTypedArrayHolePolicy() { }
     EMPTY_DATA_;
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* ins) const override;
 };
 
 class StoreUnboxedObjectOrNullPolicy final : public TypePolicy
 {
   public:
+    constexpr StoreUnboxedObjectOrNullPolicy() { }
     EMPTY_DATA_;
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* def) const override;
 };
 
 class StoreUnboxedStringPolicy final : public TypePolicy
 {
   public:
+    constexpr StoreUnboxedStringPolicy() { }
     EMPTY_DATA_;
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* def) const override;
 };
 
 // Accepts integers and doubles. Everything else is boxed.
 class ClampPolicy final : public TypePolicy
 {
   public:
+    constexpr ClampPolicy() { }
     EMPTY_DATA_;
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* ins) const override;
 };
 
 class FilterTypeSetPolicy final : public TypePolicy
 {
   public:
+    constexpr FilterTypeSetPolicy() { }
     EMPTY_DATA_;
     MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* ins) const override;
 };
 
 #undef SPECIALIZATION_DATA_
 #undef INHERIT_DATA_
 #undef EMPTY_DATA_
 
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -3424,16 +3424,19 @@ class BaseCompiler final : public BaseCo
             masm.store64(RegI64(ReturnReg64), resultsAddress);
             break;
           case ExprType::F64:
             masm.storeDouble(RegF64(ReturnDoubleReg), resultsAddress);
             break;
           case ExprType::F32:
             masm.storeFloat32(RegF32(ReturnFloat32Reg), resultsAddress);
             break;
+          case ExprType::AnyRef:
+            masm.storePtr(RegPtr(ReturnReg), resultsAddress);
+            break;
           default:
             MOZ_CRASH("Function return type");
         }
     }
 
     void restoreResult() {
         MOZ_ASSERT(env_.debugEnabled());
         size_t debugFrameOffset = masm.framePushed() - DebugFrame::offsetOfFrame();
@@ -3448,16 +3451,19 @@ class BaseCompiler final : public BaseCo
             masm.load64(resultsAddress, RegI64(ReturnReg64));
             break;
           case ExprType::F64:
             masm.loadDouble(resultsAddress, RegF64(ReturnDoubleReg));
             break;
           case ExprType::F32:
             masm.loadFloat32(resultsAddress, RegF32(ReturnFloat32Reg));
             break;
+          case ExprType::AnyRef:
+            masm.loadPtr(resultsAddress, RegPtr(ReturnReg));
+            break;
           default:
             MOZ_CRASH("Function return type");
         }
     }
 
     bool endFunction() {
         // Always branch to returnLabel_.
         masm.breakpoint();
--- a/layout/build/nsLayoutCID.h
+++ b/layout/build/nsLayoutCID.h
@@ -66,12 +66,16 @@
 // {5a75c25a-5e7e-4d90-8f7c-07eb15cc0aa8}
 #define QUOTAMANAGER_SERVICE_CID \
 { 0x5a75c25a, 0x5e7e, 0x4d90, { 0x8f, 0x7c, 0x07, 0xeb, 0x15, 0xcc, 0x0a, 0xa8 } }
 
 // {c74bde32-bcc7-4840-8430-c733351b212a}
 #define SERVICEWORKERMANAGER_CID \
 { 0xc74bde32, 0xbcc7, 0x4840, { 0x84, 0x30, 0xc7, 0x33, 0x35, 0x1b, 0x21, 0x2a } }
 
+// {69da374a-fda3-4a93-9fbc-d9304f66a7fe}
+#define STORAGEACTIVITYSERVICE_CID \
+{ 0x69da374a, 0xfda3, 0x4a93, { 0x9f, 0xbc, 0xd9, 0x30, 0x4f, 0x66, 0xa7, 0xfe } }
+
 #define NOTIFICATIONTELEMETRYSERVICE_CID \
 { 0x5995b782, 0x6a0e, 0x4066, { 0xaa, 0xc5, 0x27, 0x6f, 0x0a, 0x9a, 0xd8, 0xcf } }
 
 #endif /* nsLayoutCID_h__ */
--- a/layout/build/nsLayoutModule.cpp
+++ b/layout/build/nsLayoutModule.cpp
@@ -69,16 +69,17 @@
 #include "nsZipArchive.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/DOMRequest.h"
 #include "mozilla/dom/LocalStorageManager.h"
 #include "mozilla/dom/network/UDPSocketChild.h"
 #include "mozilla/dom/quota/QuotaManagerService.h"
 #include "mozilla/dom/ServiceWorkerManager.h"
 #include "mozilla/dom/SessionStorageManager.h"
+#include "mozilla/dom/StorageActivityService.h"
 #include "mozilla/dom/WorkerDebuggerManager.h"
 #include "mozilla/dom/Notification.h"
 #include "mozilla/OSFileConstants.h"
 #include "mozilla/Services.h"
 
 #ifdef MOZ_WEBSPEECH_TEST_BACKEND
 #include "mozilla/dom/FakeSpeechRecognitionService.h"
 #endif
@@ -206,16 +207,18 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(SessionSt
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(DOMRequestService,
                                          DOMRequestService::FactoryCreate)
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(QuotaManagerService,
                                          QuotaManagerService::FactoryCreate)
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(ServiceWorkerManager,
                                          ServiceWorkerManager::GetInstance)
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(WorkerDebuggerManager,
                                          WorkerDebuggerManager::GetInstance)
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(StorageActivityService,
+                                         StorageActivityService::GetOrCreate)
 
 #ifdef MOZ_WEBSPEECH
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsSynthVoiceRegistry,
                                          nsSynthVoiceRegistry::GetInstanceForService)
 #endif
 
 NS_GENERIC_FACTORY_CONSTRUCTOR(AudioChannelAgent)
 
@@ -552,16 +555,17 @@ NS_DEFINE_NAMED_CID(NS_HOSTOBJECTURI_CID
 NS_DEFINE_NAMED_CID(NS_HOSTOBJECTURIMUTATOR_CID);
 NS_DEFINE_NAMED_CID(NS_DOMPARSER_CID);
 NS_DEFINE_NAMED_CID(NS_DOMSESSIONSTORAGEMANAGER_CID);
 NS_DEFINE_NAMED_CID(NS_DOMLOCALSTORAGEMANAGER_CID);
 NS_DEFINE_NAMED_CID(NS_TEXTEDITOR_CID);
 NS_DEFINE_NAMED_CID(DOMREQUEST_SERVICE_CID);
 NS_DEFINE_NAMED_CID(QUOTAMANAGER_SERVICE_CID);
 NS_DEFINE_NAMED_CID(SERVICEWORKERMANAGER_CID);
+NS_DEFINE_NAMED_CID(STORAGEACTIVITYSERVICE_CID);
 NS_DEFINE_NAMED_CID(NOTIFICATIONTELEMETRYSERVICE_CID);
 NS_DEFINE_NAMED_CID(PUSHNOTIFIER_CID);
 NS_DEFINE_NAMED_CID(WORKERDEBUGGERMANAGER_CID);
 NS_DEFINE_NAMED_CID(NS_AUDIOCHANNELAGENT_CID);
 NS_DEFINE_NAMED_CID(NS_HTMLEDITOR_CID);
 NS_DEFINE_NAMED_CID(NS_EDITORCONTROLLER_CID);
 NS_DEFINE_NAMED_CID(NS_EDITINGCONTROLLER_CID);
 NS_DEFINE_NAMED_CID(NS_EDITORCOMMANDTABLE_CID);
@@ -794,16 +798,17 @@ static const mozilla::Module::CIDEntry k
   { &kNS_HOSTOBJECTURIMUTATOR_CID, false, nullptr, nsHostObjectURIMutatorConstructor },
   { &kNS_DOMPARSER_CID, false, nullptr, DOMParserConstructor },
   { &kNS_DOMSESSIONSTORAGEMANAGER_CID, false, nullptr, SessionStorageManagerConstructor },
   { &kNS_DOMLOCALSTORAGEMANAGER_CID, false, nullptr, LocalStorageManagerConstructor },
   { &kNS_TEXTEDITOR_CID, false, nullptr, TextEditorConstructor },
   { &kDOMREQUEST_SERVICE_CID, false, nullptr, DOMRequestServiceConstructor },
   { &kQUOTAMANAGER_SERVICE_CID, false, nullptr, QuotaManagerServiceConstructor },
   { &kSERVICEWORKERMANAGER_CID, false, nullptr, ServiceWorkerManagerConstructor },
+  { &kSTORAGEACTIVITYSERVICE_CID, false, nullptr, StorageActivityServiceConstructor },
   { &kNOTIFICATIONTELEMETRYSERVICE_CID, false, nullptr, NotificationTelemetryServiceConstructor },
   { &kPUSHNOTIFIER_CID, false, nullptr, PushNotifierConstructor },
   { &kWORKERDEBUGGERMANAGER_CID, true, nullptr, WorkerDebuggerManagerConstructor },
   { &kNS_AUDIOCHANNELAGENT_CID, true, nullptr, AudioChannelAgentConstructor },
   { &kNS_HTMLEDITOR_CID, false, nullptr, HTMLEditorConstructor },
   { &kNS_EDITORCONTROLLER_CID, false, nullptr, EditorControllerConstructor },
   { &kNS_EDITINGCONTROLLER_CID, false, nullptr, nsEditingControllerConstructor },
   { &kNS_EDITORCOMMANDTABLE_CID, false, nullptr, nsEditorCommandTableConstructor },
@@ -901,16 +906,17 @@ static const mozilla::Module::ContractID
   { "@mozilla.org/dom/localStorage-manager;1", &kNS_DOMLOCALSTORAGEMANAGER_CID },
   // Keeping the old ContractID for backward compatibility
   { "@mozilla.org/dom/storagemanager;1", &kNS_DOMLOCALSTORAGEMANAGER_CID },
   { "@mozilla.org/dom/sessionStorage-manager;1", &kNS_DOMSESSIONSTORAGEMANAGER_CID },
   { "@mozilla.org/editor/texteditor;1", &kNS_TEXTEDITOR_CID },
   { DOMREQUEST_SERVICE_CONTRACTID, &kDOMREQUEST_SERVICE_CID },
   { QUOTAMANAGER_SERVICE_CONTRACTID, &kQUOTAMANAGER_SERVICE_CID },
   { SERVICEWORKERMANAGER_CONTRACTID, &kSERVICEWORKERMANAGER_CID },
+  { STORAGE_ACTIVITY_SERVICE_CONTRACTID, &kSTORAGEACTIVITYSERVICE_CID },
   { NOTIFICATIONTELEMETRYSERVICE_CONTRACTID, &kNOTIFICATIONTELEMETRYSERVICE_CID },
   { PUSHNOTIFIER_CONTRACTID, &kPUSHNOTIFIER_CID },
   { WORKERDEBUGGERMANAGER_CONTRACTID, &kWORKERDEBUGGERMANAGER_CID },
   { NS_AUDIOCHANNELAGENT_CONTRACTID, &kNS_AUDIOCHANNELAGENT_CID },
   { "@mozilla.org/editor/htmleditor;1", &kNS_HTMLEDITOR_CID },
   { "@mozilla.org/editor/editorcontroller;1", &kNS_EDITORCONTROLLER_CID },
   { "@mozilla.org/editor/editingcontroller;1", &kNS_EDITINGCONTROLLER_CID },
   { "@mozilla.org/geolocation/service;1", &kNS_GEOLOCATION_SERVICE_CID },
--- a/mfbt/ScopeExit.h
+++ b/mfbt/ScopeExit.h
@@ -75,16 +75,17 @@
  * behaviors:
  *
  * - if |release()| has not been called, the cleanup is always performed at
  *   the end of the scope;
  * - if |release()| has been called, nothing will happen at the end of the
  *   scope.
  */
 
+#include "mozilla/Attributes.h"
 #include "mozilla/GuardObjects.h"
 #include "mozilla/Move.h"
 
 namespace mozilla {
 
 template <typename ExitFunction>
 class MOZ_STACK_CLASS ScopeExit {
   ExitFunction mExitFunction;
@@ -119,17 +120,17 @@ public:
 
 private:
   explicit ScopeExit(const ScopeExit&) = delete;
   ScopeExit& operator=(const ScopeExit&) = delete;
   ScopeExit& operator=(ScopeExit&&) = delete;
 };
 
 template <typename ExitFunction>
-ScopeExit<ExitFunction>
+MOZ_MUST_USE ScopeExit<ExitFunction>
 MakeScopeExit(ExitFunction&& exitFunction)
 {
   return ScopeExit<ExitFunction>(mozilla::Move(exitFunction));
 }
 
 } /* namespace mozilla */
 
 #endif /* mozilla_ScopeExit_h */
--- a/servo/components/style/applicable_declarations.rs
+++ b/servo/components/style/applicable_declarations.rs
@@ -1,16 +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/. */
 
 //! Applicable declarations management.
 
 use properties::PropertyDeclarationBlock;
-use rule_tree::{CascadeLevel, StyleSource};
+use rule_tree::{CascadeLevel, ShadowCascadeOrder, StyleSource};
 use servo_arc::Arc;
 use shared_lock::Locked;
 use smallvec::SmallVec;
 use std::fmt::{self, Debug};
 use std::mem;
 
 /// List of applicable declarations. This is a transient structure that shuttles
 /// declarations between selector matching and inserting into the rule tree, and
@@ -78,55 +78,65 @@ impl Debug for SourceOrderAndCascadeLeve
 pub struct ApplicableDeclarationBlock {
     /// The style source, either a style rule, or a property declaration block.
     #[ignore_malloc_size_of = "Arc"]
     pub source: StyleSource,
     /// The source order of the block, and the cascade level it belongs to.
     order_and_level: SourceOrderAndCascadeLevel,
     /// The specificity of the selector this block is represented by.
     pub specificity: u32,
+    /// The order in the tree of trees we carry on.
+    pub shadow_cascade_order: ShadowCascadeOrder,
 }
 
 impl ApplicableDeclarationBlock {
     /// Constructs an applicable declaration block from a given property
     /// declaration block and importance.
     #[inline]
     pub fn from_declarations(
         declarations: Arc<Locked<PropertyDeclarationBlock>>,
         level: CascadeLevel,
     ) -> Self {
         ApplicableDeclarationBlock {
             source: StyleSource::Declarations(declarations),
             order_and_level: SourceOrderAndCascadeLevel::new(0, level),
             specificity: 0,
+            shadow_cascade_order: 0,
         }
     }
 
     /// Constructs an applicable declaration block from the given components
     #[inline]
-    pub fn new(source: StyleSource, order: u32, level: CascadeLevel, specificity: u32) -> Self {
+    pub fn new(
+        source: StyleSource,
+        order: u32,
+        level: CascadeLevel,
+        specificity: u32,
+        shadow_cascade_order: u32,
+    ) -> Self {
         ApplicableDeclarationBlock {
-            source: source,
+            source,
             order_and_level: SourceOrderAndCascadeLevel::new(order, level),
-            specificity: specificity,
+            specificity,
+            shadow_cascade_order,
         }
     }
 
     /// Returns the source order of the block.
     #[inline]
     pub fn source_order(&self) -> u32 {
         self.order_and_level.order()
     }
 
     /// Returns the cascade level of the block.
     #[inline]
     pub fn level(&self) -> CascadeLevel {
         self.order_and_level.level()
     }
 
-    /// Convenience method to consume self and return the source alongside the
-    /// level.
+    /// Convenience method to consume self and return the right thing for the
+    /// rule tree to iterate over.
     #[inline]
-    pub fn order_and_level(self) -> (StyleSource, CascadeLevel) {
+    pub fn for_rule_tree(self) -> (StyleSource, CascadeLevel, ShadowCascadeOrder) {
         let level = self.level();
-        (self.source, level)
+        (self.source, level, self.shadow_cascade_order)
     }
 }
--- a/servo/components/style/rule_tree/mod.rs
+++ b/servo/components/style/rule_tree/mod.rs
@@ -157,16 +157,26 @@ impl StyleSource {
 ///
 /// The root node doesn't have a null pointer in the free list, but this value.
 const FREE_LIST_SENTINEL: *mut RuleNode = 0x01 as *mut RuleNode;
 
 /// A second sentinel value for the free list, indicating that it's locked (i.e.
 /// another thread is currently adding an entry). We spin if we find this value.
 const FREE_LIST_LOCKED: *mut RuleNode = 0x02 as *mut RuleNode;
 
+/// A counter to track how many inner shadow roots rules deep we are.
+///
+/// This is used to handle:
+///
+/// https://drafts.csswg.org/css-scoping/#shadow-cascading
+///
+/// In particular, it'd be `0` for the innermost shadow host, `1` for the next,
+/// and so on.
+pub type ShadowCascadeOrder = u32;
+
 impl RuleTree {
     /// Construct a new rule tree.
     pub fn new() -> Self {
         RuleTree {
             root: StrongRuleNode::new(Box::new(RuleNode::root())),
         }
     }
 
@@ -193,41 +203,61 @@ impl RuleTree {
     /// in the rule tree. This allows selector matching to ignore importance,
     /// while still maintaining the appropriate cascade order in the rule tree.
     pub fn insert_ordered_rules_with_important<'a, I>(
         &self,
         iter: I,
         guards: &StylesheetGuards,
     ) -> StrongRuleNode
     where
-        I: Iterator<Item = (StyleSource, CascadeLevel)>,
+        I: Iterator<Item = (StyleSource, CascadeLevel, ShadowCascadeOrder)>,
     {
         use self::CascadeLevel::*;
         let mut current = self.root.clone();
         let mut last_level = current.get().level;
 
         let mut found_important = false;
         let mut important_style_attr = None;
-        let mut important_author = SmallVec::<[StyleSource; 4]>::new();
+
+        let mut important_same_tree = SmallVec::<[StyleSource; 4]>::new();
+        let mut important_inner_shadow = SmallVec::<[SmallVec<[StyleSource; 4]>; 4]>::new();
+        important_inner_shadow.push(SmallVec::new());
+
         let mut important_user = SmallVec::<[StyleSource; 4]>::new();
         let mut important_ua = SmallVec::<[StyleSource; 4]>::new();
         let mut transition = None;
 
-        for (source, level) in iter {
-            debug_assert!(last_level <= level, "Not really ordered");
+        let mut last_cascade_order = 0;
+        for (source, level, shadow_cascade_order) in iter {
+            debug_assert!(level >= last_level, "Not really ordered");
             debug_assert!(!level.is_important(), "Important levels handled internally");
             let any_important = {
                 let pdb = source.read(level.guard(guards));
                 pdb.any_important()
             };
 
             if any_important {
                 found_important = true;
                 match level {
-                    AuthorNormal => important_author.push(source.clone()),
+                    InnerShadowNormal => {
+                        debug_assert!(
+                            shadow_cascade_order >= last_cascade_order,
+                            "Not really ordered"
+                        );
+                        if shadow_cascade_order > last_cascade_order &&
+                            !important_inner_shadow.last().unwrap().is_empty()
+                        {
+                            last_cascade_order = shadow_cascade_order;
+                            important_inner_shadow.push(SmallVec::new());
+                        }
+                        important_inner_shadow.last_mut().unwrap().push(source.clone())
+                    }
+                    SameTreeAuthorNormal => {
+                        important_same_tree.push(source.clone())
+                    },
                     UANormal => important_ua.push(source.clone()),
                     UserNormal => important_user.push(source.clone()),
                     StyleAttributeNormal => {
                         debug_assert!(important_style_attr.is_none());
                         important_style_attr = Some(source.clone());
                     },
                     _ => {},
                 };
@@ -260,24 +290,30 @@ impl RuleTree {
             return current;
         }
 
         //
         // Insert important declarations, in order of increasing importance,
         // followed by any transition rule.
         //
 
-        for source in important_author.drain() {
-            current = current.ensure_child(self.root.downgrade(), source, AuthorImportant);
+        for source in important_same_tree.drain() {
+            current = current.ensure_child(self.root.downgrade(), source, SameTreeAuthorImportant);
         }
 
         if let Some(source) = important_style_attr {
             current = current.ensure_child(self.root.downgrade(), source, StyleAttributeImportant);
         }
 
+        for mut list in important_inner_shadow.drain().rev() {
+            for source in list.drain() {
+                current = current.ensure_child(self.root.downgrade(), source, InnerShadowImportant);
+            }
+        }
+
         for source in important_user.drain() {
             current = current.ensure_child(self.root.downgrade(), source, UserImportant);
         }
 
         for source in important_ua.drain() {
             current = current.ensure_child(self.root.downgrade(), source, UAImportant);
         }
 
@@ -290,19 +326,20 @@ impl RuleTree {
 
     /// Given a list of applicable declarations, insert the rules and return the
     /// corresponding rule node.
     pub fn compute_rule_node(
         &self,
         applicable_declarations: &mut ApplicableDeclarationList,
         guards: &StylesheetGuards,
     ) -> StrongRuleNode {
-        let rules = applicable_declarations.drain().map(|d| d.order_and_level());
-        let rule_node = self.insert_ordered_rules_with_important(rules, guards);
-        rule_node
+        self.insert_ordered_rules_with_important(
+            applicable_declarations.drain().map(|d| d.for_rule_tree()),
+            guards,
+        )
     }
 
     /// Insert the given rules, that must be in proper order by specifity, and
     /// return the corresponding rule node representing the last inserted one.
     pub fn insert_ordered_rules<'a, I>(&self, iter: I) -> StrongRuleNode
     where
         I: Iterator<Item = (StyleSource, CascadeLevel)>,
     {
@@ -376,18 +413,18 @@ impl RuleTree {
                 // same as `pdb`, we're done, and `path` is still valid.
                 //
                 // TODO(emilio): Another potential optimization is the one where
                 // we can just replace the rule at that level for `pdb`, and
                 // then we don't need to re-create the children, and `path` is
                 // also equally valid. This is less likely, and would require an
                 // in-place mutation of the source, which is, at best, fiddly,
                 // so let's skip it for now.
-                let is_here_already = match &current.get().source {
-                    &StyleSource::Declarations(ref already_here) => {
+                let is_here_already = match current.get().source {
+                    StyleSource::Declarations(ref already_here) => {
                         pdb.with_arc(|arc| Arc::ptr_eq(arc, already_here))
                     },
                     _ => unreachable!("Replacing non-declarations style?"),
                 };
 
                 if is_here_already {
                     debug!("Picking the fast path in rule replacement");
                     return None;
@@ -495,41 +532,63 @@ const RULE_TREE_GC_INTERVAL: usize = 300
 ///
 /// Presentational hints for SVG and HTML are in the "author-level
 /// zero-specificity" level, that is, right after user rules, and before author
 /// rules.
 ///
 /// The order of variants declared here is significant, and must be in
 /// _ascending_ order of precedence.
 ///
+/// See also [4] for the Shadow DOM bits. We rely on the invariant that rules
+/// from outside the tree the element is in can't affect the element.
+///
+/// The opposite is not true (i.e., :host and ::slotted) from an "inner" shadow
+/// tree may affect an element connected to the document or an "outer" shadow
+/// tree.
+///
+/// We need to differentiate between rules from the same tree and "inner" shadow
+/// trees in order to be able to find the right position for the style attribute
+/// easily. Otherwise we wouldn't be able to avoid selector-matching when a
+/// style attribute is added or removed.
+///
 /// [1]: https://drafts.csswg.org/css-cascade/#cascade-origin
 /// [2]: https://drafts.csswg.org/css-cascade/#preshint
 /// [3]: https://html.spec.whatwg.org/multipage/#presentational-hints
+/// [4]: https://drafts.csswg.org/css-scoping/#shadow-cascading
 #[repr(u8)]
 #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd)]
 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
 pub enum CascadeLevel {
     /// Normal User-Agent rules.
     UANormal = 0,
     /// User normal rules.
     UserNormal,
     /// Presentational hints.
     PresHints,
-    /// Author normal rules.
-    AuthorNormal,
+    /// Shadow DOM styles from "inner" shadow trees.
+    ///
+    /// See above for why this is needed instead of merging InnerShadowNormal,
+    /// SameTreeAuthorNormal and StyleAttributeNormal inside something like
+    /// AuthorNormal.
+    InnerShadowNormal,
+    /// Author normal rules from the same tree the element is in.
+    SameTreeAuthorNormal,
     /// Style attribute normal rules.
     StyleAttributeNormal,
     /// SVG SMIL animations.
     SMILOverride,
     /// CSS animations and script-generated animations.
     Animations,
-    /// Author-supplied important rules.
-    AuthorImportant,
+    /// Author-supplied important rules from the same tree the element came
+    /// from.
+    SameTreeAuthorImportant,
     /// Style attribute important rules.
     StyleAttributeImportant,
+    /// Shadow DOM important rules.
+    InnerShadowImportant,
     /// User important rules.
     UserImportant,
     /// User-agent important rules.
     UAImportant,
     /// Transitions
     ///
     /// NB: If this changes from being last, change from_byte below.
     Transitions,
@@ -566,17 +625,18 @@ impl CascadeLevel {
         }
     }
 
     /// Returns whether this cascade level represents important rules of some
     /// sort.
     #[inline]
     pub fn is_important(&self) -> bool {
         match *self {
-            CascadeLevel::AuthorImportant |
+            CascadeLevel::SameTreeAuthorImportant |
+            CascadeLevel::InnerShadowImportant |
             CascadeLevel::StyleAttributeImportant |
             CascadeLevel::UserImportant |
             CascadeLevel::UAImportant => true,
             _ => false,
         }
     }
 
     /// Returns the importance relevant for this rule. Pretty similar to
@@ -1297,21 +1357,23 @@ impl StrongRuleNode {
                                     have_explicit_ua_inherit = true;
                                     inherited_properties.insert(id);
                                 }
                             }
                         }
                     },
                     // Author rules:
                     CascadeLevel::PresHints |
-                    CascadeLevel::AuthorNormal |
+                    CascadeLevel::SameTreeAuthorNormal |
+                    CascadeLevel::InnerShadowNormal |
                     CascadeLevel::StyleAttributeNormal |
                     CascadeLevel::SMILOverride |
                     CascadeLevel::Animations |
-                    CascadeLevel::AuthorImportant |
+                    CascadeLevel::SameTreeAuthorImportant |
+                    CascadeLevel::InnerShadowImportant |
                     CascadeLevel::StyleAttributeImportant |
                     CascadeLevel::Transitions => {
                         for (id, declaration) in longhands {
                             if properties.contains(id) {
                                 if !author_colors_allowed {
                                     if let PropertyDeclaration::BackgroundColor(ref color) =
                                         *declaration
                                     {
--- a/servo/components/style/selector_map.rs
+++ b/servo/components/style/selector_map.rs
@@ -9,17 +9,17 @@ use {Atom, LocalName, WeakAtom};
 use applicable_declarations::ApplicableDeclarationList;
 use context::QuirksMode;
 use dom::TElement;
 use fallible::FallibleVec;
 use hash::{HashMap, HashSet};
 use hash::map as hash_map;
 use hashglobe::FailedAllocationError;
 use precomputed_hash::PrecomputedHash;
-use rule_tree::CascadeLevel;
+use rule_tree::{CascadeLevel, ShadowCascadeOrder};
 use selector_parser::SelectorImpl;
 use selectors::matching::{matches_selector, ElementSelectorFlags, MatchingContext};
 use selectors::parser::{Combinator, Component, SelectorIter};
 use smallvec::SmallVec;
 use std::hash::{BuildHasherDefault, Hash, Hasher};
 use stylist::Rule;
 
 /// A hasher implementation that doesn't hash anything, because it expects its
@@ -158,16 +158,17 @@ impl SelectorMap<Rule> {
     pub fn get_all_matching_rules<E, F>(
         &self,
         element: E,
         rule_hash_target: E,
         matching_rules_list: &mut ApplicableDeclarationList,
         context: &mut MatchingContext<E::Impl>,
         flags_setter: &mut F,
         cascade_level: CascadeLevel,
+        shadow_cascade_order: ShadowCascadeOrder,
     ) where
         E: TElement,
         F: FnMut(&E, ElementSelectorFlags),
     {
         if self.is_empty() {
             return;
         }
 
@@ -180,80 +181,85 @@ impl SelectorMap<Rule> {
             if let Some(rules) = self.id_hash.get(id, quirks_mode) {
                 SelectorMap::get_matching_rules(
                     element,
                     rules,
                     matching_rules_list,
                     context,
                     flags_setter,
                     cascade_level,
+                    shadow_cascade_order,
                 )
             }
         }
 
         rule_hash_target.each_class(|class| {
             if let Some(rules) = self.class_hash.get(&class, quirks_mode) {
                 SelectorMap::get_matching_rules(
                     element,
                     rules,
                     matching_rules_list,
                     context,
                     flags_setter,
                     cascade_level,
+                    shadow_cascade_order,
                 )
             }
         });
 
         if let Some(rules) = self.local_name_hash.get(rule_hash_target.local_name()) {
             SelectorMap::get_matching_rules(
                 element,
                 rules,
                 matching_rules_list,
                 context,
                 flags_setter,
                 cascade_level,
+                shadow_cascade_order,
             )
         }
 
         SelectorMap::get_matching_rules(
             element,
             &self.other,
             matching_rules_list,
             context,
             flags_setter,
             cascade_level,
+            shadow_cascade_order,
         );
 
         // Sort only the rules we just added.
         matching_rules_list[init_len..]
             .sort_unstable_by_key(|block| (block.specificity, block.source_order()));
     }
 
     /// Adds rules in `rules` that match `element` to the `matching_rules` list.
     fn get_matching_rules<E, F>(
         element: E,
         rules: &[Rule],
         matching_rules: &mut ApplicableDeclarationList,
         context: &mut MatchingContext<E::Impl>,
         flags_setter: &mut F,
         cascade_level: CascadeLevel,
+        shadow_cascade_order: ShadowCascadeOrder,
     ) where
         E: TElement,
         F: FnMut(&E, ElementSelectorFlags),
     {
         for rule in rules {
             if matches_selector(
                 &rule.selector,
                 0,
                 Some(&rule.hashes),
                 &element,
                 context,
                 flags_setter,
             ) {
-                matching_rules.push(rule.to_applicable_declaration_block(cascade_level));
+                matching_rules.push(rule.to_applicable_declaration_block(cascade_level, shadow_cascade_order));
             }
         }
     }
 }
 
 impl<T: SelectorMapEntry> SelectorMap<T> {
     /// Inserts into the correct hash, trying id, class, and localname.
     pub fn insert(
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -18,17 +18,17 @@ use invalidation::media_queries::{Effect
 #[cfg(feature = "gecko")]
 use malloc_size_of::{MallocShallowSizeOf, MallocSizeOf, MallocSizeOfOps};
 #[cfg(feature = "gecko")]
 use malloc_size_of::MallocUnconditionalShallowSizeOf;
 use media_queries::Device;
 use properties::{self, CascadeFlags, ComputedValues};
 use properties::{AnimationRules, PropertyDeclarationBlock};
 use rule_cache::{RuleCache, RuleCacheConditions};
-use rule_tree::{CascadeLevel, RuleTree, StrongRuleNode, StyleSource};
+use rule_tree::{CascadeLevel, RuleTree, ShadowCascadeOrder, StrongRuleNode, StyleSource};
 use selector_map::{PrecomputedHashMap, SelectorMap, SelectorMapEntry};
 use selector_parser::{PerPseudoElementMap, PseudoElement, SelectorImpl, SnapshotMap};
 use selectors::NthIndexCache;
 use selectors::attr::{CaseSensitivity, NamespaceConstraint};
 use selectors::bloom::{BloomFilter, NonCountingBloomFilter};
 use selectors::matching::{matches_selector, ElementSelectorFlags, MatchingContext, MatchingMode};
 use selectors::matching::VisitedHandlingMode;
 use selectors::parser::{AncestorHashes, Combinator, Component, Selector};
@@ -688,17 +688,17 @@ impl Stylist {
                 },
                 None => Some(declarations),
             },
             None => extra_declarations.as_ref(),
         };
 
         match declarations {
             Some(decls) => self.rule_tree.insert_ordered_rules_with_important(
-                decls.into_iter().map(|a| (a.source.clone(), a.level())),
+                decls.into_iter().map(|a| a.clone().for_rule_tree()),
                 guards,
             ),
             None => self.rule_tree.root().clone(),
         }
     }
 
     /// Returns the style for an anonymous box of the given type.
     ///
@@ -1015,17 +1015,17 @@ impl Stylist {
                 AnimationRules(None, None),
                 rule_inclusion,
                 &mut declarations,
                 &mut matching_context,
                 &mut set_selector_flags,
             );
             if !declarations.is_empty() {
                 let rule_node = self.rule_tree.insert_ordered_rules_with_important(
-                    declarations.drain().map(|a| a.order_and_level()),
+                    declarations.drain().map(|a| a.for_rule_tree()),
                     guards,
                 );
                 if rule_node != *self.rule_tree.root() {
                     visited_rules = Some(rule_node);
                 }
             }
         }
 
@@ -1182,16 +1182,17 @@ impl Stylist {
         {
             map.get_all_matching_rules(
                 element,
                 rule_hash_target,
                 applicable_declarations,
                 context,
                 flags_setter,
                 CascadeLevel::UANormal,
+                0,
             );
         }
 
         // NB: the following condition, although it may look somewhat
         // inaccurate, would be equivalent to something like:
         //
         //     element.matches_user_and_author_rules() ||
         //     (is_implemented_pseudo &&
@@ -1203,16 +1204,17 @@ impl Stylist {
             if let Some(map) = self.cascade_data.user.normal_rules(pseudo_element) {
                 map.get_all_matching_rules(
                     element,
                     rule_hash_target,
                     applicable_declarations,
                     context,
                     flags_setter,
                     CascadeLevel::UserNormal,
+                    0,
                 );
             }
         }
 
         if pseudo_element.is_none() && !only_default_rules {
             // Presentational hints.
             //
             // These go before author rules, but after user rules, see:
@@ -1227,16 +1229,17 @@ impl Stylist {
                     for declaration in &applicable_declarations[length_before_preshints..] {
                         assert_eq!(declaration.level(), CascadeLevel::PresHints);
                     }
                 }
             }
         }
 
         let mut match_document_author_rules = matches_author_rules;
+        let mut shadow_cascade_order = 0;
 
         // XBL / Shadow DOM rules, which are author rules too.
         //
         // TODO(emilio): Cascade order here is wrong for Shadow DOM. In
         // particular, normally document rules override ::slotted() rules, but
         // for !important it should be the other way around. So probably we need
         // to add some sort of AuthorScoped cascade level or something.
         if matches_author_rules && !only_default_rules {
@@ -1244,19 +1247,21 @@ impl Stylist {
                 if let Some(map) = shadow.style_data().host_rules(pseudo_element) {
                     context.with_shadow_host(Some(rule_hash_target), |context| {
                         map.get_all_matching_rules(
                             element,
                             rule_hash_target,
                             applicable_declarations,
                             context,
                             flags_setter,
-                            CascadeLevel::AuthorNormal,
+                            CascadeLevel::InnerShadowNormal,
+                            shadow_cascade_order,
                         );
                     });
+                    shadow_cascade_order += 1;
                 }
             }
 
             // Match slotted rules in reverse order, so that the outer slotted
             // rules come before the inner rules (and thus have less priority).
             let mut slots = SmallVec::<[_; 3]>::new();
             let mut current = rule_hash_target.assigned_slot();
             while let Some(slot) = current {
@@ -1270,86 +1275,90 @@ impl Stylist {
                 if let Some(map) = styles.slotted_rules(pseudo_element) {
                     context.with_shadow_host(Some(shadow.host()), |context| {
                         map.get_all_matching_rules(
                             element,
                             rule_hash_target,
                             applicable_declarations,
                             context,
                             flags_setter,
-                            CascadeLevel::AuthorNormal,
+                            CascadeLevel::InnerShadowNormal,
+                            shadow_cascade_order,
                         );
                     });
+                    shadow_cascade_order += 1;
                 }
             }
 
             if let Some(containing_shadow) = rule_hash_target.containing_shadow() {
                 let cascade_data = containing_shadow.style_data();
                 if let Some(map) = cascade_data.normal_rules(pseudo_element) {
                     context.with_shadow_host(Some(containing_shadow.host()), |context| {
                         map.get_all_matching_rules(
                             element,
                             rule_hash_target,
                             applicable_declarations,
                             context,
                             flags_setter,
-                            CascadeLevel::AuthorNormal,
+                            CascadeLevel::SameTreeAuthorNormal,
+                            shadow_cascade_order,
                         );
                     });
+                    shadow_cascade_order += 1;
                 }
 
                 match_document_author_rules = false;
             }
         }
 
         // FIXME(emilio): It looks very wrong to match XBL rules even for
         // getDefaultComputedStyle!
         //
         // Also, this doesn't account for the author_styles_enabled stuff.
         let cut_xbl_binding_inheritance =
             element.each_xbl_cascade_data(|cascade_data, quirks_mode| {
                 if let Some(map) = cascade_data.normal_rules(pseudo_element) {
                     // NOTE(emilio): This is needed because the XBL stylist may
                     // think it has a different quirks mode than the document.
-                    //
-                    // FIXME(emilio): this should use the same VisitedMatchingMode
-                    // as `context`, write a test-case of :visited not working on
-                    // Shadow DOM and fix it!
                     let mut matching_context = MatchingContext::new(
                         context.matching_mode(),
                         context.bloom_filter,
                         context.nth_index_cache.as_mut().map(|s| &mut **s),
                         quirks_mode,
                     );
                     matching_context.pseudo_element_matching_fn =
                         context.pseudo_element_matching_fn;
 
+                    // SameTreeAuthorNormal instead of InnerShadowNormal to
+                    // preserve behavior, though that's kinda fishy...
                     map.get_all_matching_rules(
                         element,
                         rule_hash_target,
                         applicable_declarations,
                         &mut matching_context,
                         flags_setter,
-                        CascadeLevel::AuthorNormal,
+                        CascadeLevel::SameTreeAuthorNormal,
+                        shadow_cascade_order,
                     );
                 }
             });
 
         match_document_author_rules &= !cut_xbl_binding_inheritance;
 
         if match_document_author_rules && !only_default_rules {
             // Author normal rules.
             if let Some(map) = self.cascade_data.author.normal_rules(pseudo_element) {
                 map.get_all_matching_rules(
                     element,
                     rule_hash_target,
                     applicable_declarations,
                     context,
                     flags_setter,
-                    CascadeLevel::AuthorNormal,
+                    CascadeLevel::SameTreeAuthorNormal,
+                    shadow_cascade_order,
                 );
             }
         }
 
         if !only_default_rules {
             // Style attribute ("Normal override declarations").
             if let Some(sa) = style_attribute {
                 applicable_declarations.push(ApplicableDeclarationBlock::from_declarations(
@@ -2167,16 +2176,17 @@ impl CascadeData {
                                     .as_mut()
                                     .expect("Expected precomputed declarations for the UA level")
                                     .get_or_insert_with(&pseudo.canonical(), Vec::new)
                                     .push(ApplicableDeclarationBlock::new(
                                         StyleSource::Style(locked.clone()),
                                         self.rules_source_order,
                                         CascadeLevel::UANormal,
                                         selector.specificity(),
+                                        0,
                                     ));
                                 continue;
                             }
                         }
 
                         let hashes = AncestorHashes::new(&selector, quirks_mode);
 
                         let rule = Rule::new(
@@ -2463,19 +2473,20 @@ impl Rule {
         self.selector.specificity()
     }
 
     /// Turns this rule into an `ApplicableDeclarationBlock` for the given
     /// cascade level.
     pub fn to_applicable_declaration_block(
         &self,
         level: CascadeLevel,
+        shadow_cascade_order: ShadowCascadeOrder,
     ) -> ApplicableDeclarationBlock {
         let source = StyleSource::Style(self.style_rule.clone());
-        ApplicableDeclarationBlock::new(source, self.source_order, level, self.specificity())
+        ApplicableDeclarationBlock::new(source, self.source_order, level, self.specificity(), shadow_cascade_order)
     }
 
     /// Creates a new Rule.
     pub fn new(
         selector: Selector<SelectorImpl>,
         hashes: AncestorHashes,
         style_rule: Arc<Locked<StyleRule>>,
         source_order: u32,
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -2703,17 +2703,17 @@ pub unsafe extern "C" fn Servo_ComputedV
     let page_decls = match pseudo {
         PseudoElement::PageContent => {
             let mut declarations = vec![];
             let iter = data.stylist.iter_extra_data_origins_rev();
             for (data, origin) in iter {
                 let level = match origin {
                     Origin::UserAgent => CascadeLevel::UANormal,
                     Origin::User => CascadeLevel::UserNormal,
-                    Origin::Author => CascadeLevel::AuthorNormal,
+                    Origin::Author => CascadeLevel::SameTreeAuthorNormal,
                 };
                 for rule in data.pages.iter() {
                     declarations.push(ApplicableDeclarationBlock::from_declarations(
                         rule.read_with(level.guard(&guards)).block.clone(),
                         level
                     ));
                 }
             }
--- a/servo/ports/geckolib/tests/size_of.rs
+++ b/servo/ports/geckolib/tests/size_of.rs
@@ -30,17 +30,17 @@ size_of_test!(test_size_of_cv, ComputedV
 size_of_test!(test_size_of_option_arc_cv, Option<Arc<ComputedValues>>, 8);
 size_of_test!(test_size_of_option_rule_node, Option<StrongRuleNode>, 8);
 
 size_of_test!(test_size_of_element_styles, ElementStyles, 16);
 size_of_test!(test_size_of_element_data, ElementData, 24);
 
 size_of_test!(test_size_of_property_declaration, style::properties::PropertyDeclaration, 32);
 
-size_of_test!(test_size_of_application_declaration_block, ApplicableDeclarationBlock, 24);
+size_of_test!(test_size_of_application_declaration_block, ApplicableDeclarationBlock, 32);
 size_of_test!(test_size_of_rule_node, RuleNode, 80);
 
 // This is huge, but we allocate it on the stack and then never move it,
 // we only pass `&mut SourcePropertyDeclaration` references around.
 size_of_test!(test_size_of_parsed_declaration, style::properties::SourcePropertyDeclaration, 608);
 
 size_of_test!(test_size_of_computed_image, computed::image::Image, 32);
 size_of_test!(test_size_of_specified_image, specified::image::Image, 32);
--- a/taskcluster/ci/docker-image/kind.yml
+++ b/taskcluster/ci/docker-image/kind.yml
@@ -86,18 +86,16 @@ jobs:
   index-task:
     symbol: I(idx)
   funsize-update-generator:
     symbol: I(pg)
   google-play-strings:
     symbol: I(gps)
   funsize-balrog-submitter:
     symbol: I(fbs)
-  beet-mover:
-    symbol: I(bm)
   update-verify:
     symbol: I(uv)
   diffoscope:
     symbol: I(diff)
   periodic-updates:
     symbol: I(file)
   firefox-snap:
     symbol: I(snap)
deleted file mode 100644
--- a/taskcluster/docker/beet-mover/Dockerfile
+++ /dev/null
@@ -1,23 +0,0 @@
-FROM ubuntu:xenial
-
-RUN apt-get -q update \
-    && apt-get install --yes -q \
-    mercurial \
-    python-dev \
-    python-pip \
-    python-virtualenv \
-    libffi-dev \
-    liblzma-dev \
-    libssl-dev \
-    libyaml-dev \
-    libmysqlclient-dev \
-    clamav \
-    clamav-freshclam \
-    curl \
-    wget \
-    && apt-get clean
-
-COPY requirements.txt /tmp/
-RUN pip install -r /tmp/requirements.txt
-# Freshclam may be flaky, retry if it fails
-RUN for i in 1 2 3 4 5; do freshclam --verbose && break || sleep 15; done
deleted file mode 100644
--- a/taskcluster/docker/beet-mover/requirements.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-sh
-redo
--- a/taskcluster/docs/partials.rst
+++ b/taskcluster/docs/partials.rst
@@ -27,17 +27,18 @@ from either locations specified in the t
 locations.
 
 The 'extra' section of the task definition contains most of the payload, under
 the 'funsize' key. In here is a list of partials that this specific task will
 generate, and each entry includes the earlier (or 'from') version, and the most
 recent (or 'to') version, which for most releases will likely be a taskcluster
 artifact.
 
-.. code-block::
+.. code-block:: json
+
     {
        "to_mar": "https://queue.taskcluster.net/v1/task/EWtBFqVuT-WqG3tGLxWhmA/artifacts/public/build/ach/target.complete.mar",
        "product": "Firefox",
        "dest_mar": "target-60.0b8.partial.mar",
        "locale": "ach",
        "from_mar": "http://archive.mozilla.org/pub/firefox/candidates/60.0b8-candidates/build1/update/linux-i686/ach/firefox-60.0b8.complete.mar",
        "update_number": 2,
        "platform": "linux32",
deleted file mode 100644
--- a/testing/mozharness/configs/beetmover/en_us_build.yml.tmpl
+++ /dev/null
@@ -1,87 +0,0 @@
----
-metadata:
-    name: "Beet Mover Manifest"
-    description: "Maps artifact locations to s3 key names for the en-US locale"
-    owner: "release@mozilla.com"
-
-mapping:
-{% for locale in locales %}
-  {{ locale }}:
-
-    buildinfo:
-      artifact: {{ artifact_base_url }}/target.json
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.json
-    mozinfo:
-      artifact: {{ artifact_base_url }}/target.mozinfo.json
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.mozinfo.json
-    socorroinfo:
-      artifact: {{ artifact_base_url }}/target.txt
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.txt
-    jsshell:
-      artifact: {{ artifact_base_url }}/target.jsshell.zip
-      s3_key: {{ s3_prefix }}jsshell/jsshell-{{ platform }}.zip
-    mozharness_package:
-      artifact: {{ artifact_base_url }}/mozharness.zip
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/mozharness.zip
-    xpi:
-      artifact: {{ artifact_base_url }}/target.langpack.xpi
-      s3_key: {{ s3_prefix }}{{ platform }}/xpi/{{ locale }}.xpi
-    symbols:
-      artifact: {{ artifact_base_url }}/target.crashreporter-symbols.zip
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.crashreporter-symbols.zip
-
-  {% if platform == "win32" %}
-    buildid_info:
-      artifact: {{ artifact_base_url }}/target_info.txt
-      s3_key: {{ s3_prefix }}win32_info.txt
-    mar_tools_mar:
-      artifact: {{ artifact_base_url }}/host/bin/mar.exe
-      s3_key: {{ s3_prefix }}mar-tools/win32/mar.exe
-    mar_tools_mbdiff:
-      artifact: {{ artifact_base_url }}/host/bin/mbsdiff.exe
-      s3_key: {{ s3_prefix }}mar-tools/win32/mbsdiff.exe
-  {% endif %}
-
-  {% if platform == "win64" %}
-    buildid_info:
-      artifact: {{ artifact_base_url }}/target_info.txt
-      s3_key: {{ s3_prefix }}win64_info.txt
-    mar_tools_mar:
-      artifact: {{ artifact_base_url }}/host/bin/mar.exe
-      s3_key: {{ s3_prefix }}mar-tools/win64/mar.exe
-    mar_tools_mbdiff:
-      artifact: {{ artifact_base_url }}/host/bin/mbsdiff.exe
-      s3_key: {{ s3_prefix }}mar-tools/win64/mbsdiff.exe
-  {% endif %}
-
-  {% if platform == "linux-i686" %}
-    buildid_info:
-      artifact: {{ artifact_base_url }}/target_info.txt
-      s3_key: {{ s3_prefix }}linux_info.txt
-    mar_tools_mar:
-      artifact: {{ artifact_base_url }}/host/bin/mar
-      s3_key: {{ s3_prefix }}mar-tools/linux/mar
-    mar_tools_mbdiff:
-      artifact: {{ artifact_base_url }}/host/bin/mbsdiff
-      s3_key: {{ s3_prefix }}mar-tools/linux/mbsdiff
-  {% endif %}
-
-  {% if platform == "linux-x86_64" %}
-    buildid_info:
-      artifact: {{ artifact_base_url }}/target_info.txt
-      s3_key: {{ s3_prefix }}linux64_info.txt
-    mar_tools_mar:
-      artifact: {{ artifact_base_url }}/host/bin/mar
-      s3_key: {{ s3_prefix }}mar-tools/linux64/mar
-    mar_tools_mbdiff:
-      artifact: {{ artifact_base_url }}/host/bin/mbsdiff
-      s3_key: {{ s3_prefix }}mar-tools/linux64/mbsdiff
-  {% endif %}
-
-  {% if platform == "mac" %}
-    buildid_info:
-      artifact: {{ artifact_base_url }}/target_info.txt
-      s3_key: {{ s3_prefix }}macosx64_info.txt
-  {% endif %}
-
-{% endfor %}
deleted file mode 100644
--- a/testing/mozharness/configs/beetmover/en_us_repackage.yml.tmpl
+++ /dev/null
@@ -1,16 +0,0 @@
----
-metadata:
-    name: "Beet Mover Manifest"
-    description: "Maps artifact locations to s3 key names for the en-US locale"
-    owner: "release@mozilla.com"
-
-mapping:
-{% for locale in locales %}
-  {{ locale }}:
-  {% if platform == "mac" %}
-    package:
-      artifact: {{ artifact_base_url }}/target.dmg
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/Firefox {{ version }}.dmg
-  {% endif %}
-
-{% endfor %}
deleted file mode 100644
--- a/testing/mozharness/configs/beetmover/en_us_repackage_signing.yml.tmpl
+++ /dev/null
@@ -1,35 +0,0 @@
----
-metadata:
-    name: "Beet Mover Manifest"
-    description: "Maps artifact locations to s3 key names for the en-US locale"
-    owner: "release@mozilla.com"
-
-mapping:
-{% for locale in locales %}
-  {{ locale }}:
-  {% if platform == "win32" %}
-    complete_mar:
-      artifact: {{ artifact_base_url }}/target.complete.mar
-      s3_key: {{ s3_prefix }}update/{{ platform }}/{{ locale }}/firefox-{{ version }}.complete.mar
-    full_installer:
-      artifact: {{ artifact_base_url }}/target.installer.exe
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/Firefox Setup {{ version }}.exe
-    {% if "esr" not in version %}
-    stub_installer:
-      artifact: {{ artifact_base_url }}/target.stub-installer.exe
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/Firefox Installer.exe
-    {% endif %}
-  {% elif platform == "win64" %}
-    complete_mar:
-      artifact: {{ artifact_base_url }}/target.complete.mar
-      s3_key: {{ s3_prefix }}update/{{ platform }}/{{ locale }}/firefox-{{ version }}.complete.mar
-    full_installer:
-      artifact: {{ artifact_base_url }}/target.installer.exe
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/Firefox Setup {{ version }}.exe
-  {% else %}
-    complete_mar:
-      artifact: {{ artifact_base_url }}/target.complete.mar
-      s3_key: {{ s3_prefix }}update/{{ platform }}/{{ locale }}/firefox-{{ version }}.complete.mar
-  {% endif %}
-
-{% endfor %}
deleted file mode 100644
--- a/testing/mozharness/configs/beetmover/en_us_signing.yml.tmpl
+++ /dev/null
@@ -1,38 +0,0 @@
----
-metadata:
-    name: "Beet Mover Manifest"
-    description: "Maps artifact locations to s3 key names for the en-US locale"
-    owner: "release@mozilla.com"
-
-mapping:
-{% for locale in locales %}
-  {{ locale }}:
-  {% if platform == "win32" %}
-    package:
-      artifact: {{ artifact_base_url }}/target.zip
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.zip
-  {% endif %}
-
-  {% if platform == "win64" %}
-    package:
-      artifact: {{ artifact_base_url }}/target.zip
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.zip
-  {% endif %}
-
-  {% if platform == "linux-i686" %}
-    package:
-      artifact: {{ artifact_base_url }}/target.tar.bz2
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.tar.bz2
-  {% endif %}
-
-  {% if platform == "linux-x86_64" %}
-    package:
-      artifact: {{ artifact_base_url }}/target.tar.bz2
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.tar.bz2
-  {% endif %}
-
-  {% if platform == "mac" %}
-  # nothing to see here
-  {% endif %}
-
-{% endfor %}
deleted file mode 100644
--- a/testing/mozharness/configs/beetmover/l10n_changesets.tmpl
+++ /dev/null
@@ -1,11 +0,0 @@
----
-metadata:
-    name: "Beet Mover L10N Changesets"
-    description: "Maps artifact locations to s3 key names for L10N changesets"
-    owner: "release@mozilla.com"
-
-mapping:
-  all:
-    l10n_changesets:
-      artifact: {{ artifact_base_url }}/l10n_changesets.txt
-      s3_key: {{ s3_prefix }}l10n_changesets.txt
deleted file mode 100644
--- a/testing/mozharness/configs/beetmover/partials.yml.tmpl
+++ /dev/null
@@ -1,16 +0,0 @@
----
-metadata:
-    name: "Beet Mover Manifest"
-    description: "Maps artifact locations to s3 key names for partials"
-    owner: "release@mozilla.com"
-
-mapping:
-{% for locale in locales %}
-  {{ locale }}:
-    partial_mar:
-      artifact: {{ artifact_base_url }}/firefox-{{ partial_version }}-{{ version }}.{{ locale }}.{{ platform }}.partial.mar
-      s3_key: {{ s3_prefix }}update/{{ platform }}/{{ locale }}/firefox-{{ partial_version }}-{{ version }}.partial.mar
-    partial_mar_sig:
-      artifact: {{ artifact_base_url }}/firefox-{{ partial_version }}-{{ version }}.{{ locale }}.{{ platform }}.partial.mar.asc
-      s3_key: {{ s3_prefix }}update/{{ platform }}/{{ locale }}/firefox-{{ partial_version }}-{{ version }}.partial.mar.asc
-{% endfor %}
deleted file mode 100644
--- a/testing/mozharness/configs/beetmover/recompressed_completes.yml.tmpl
+++ /dev/null
@@ -1,16 +0,0 @@
----
-metadata:
-    name: "Beet Mover Manifest"
-    description: "Maps artifact locations to s3 key names for recompressed completes"
-    owner: "release@mozilla.com"
-
-mapping:
-{% for locale in locales %}
-  {{ locale }}:
-    complete_mar:
-      artifact: {{ artifact_base_url }}/firefox-{{ version }}.{{ locale }}.{{ platform }}.bz2.complete.mar
-      s3_key: {{ s3_prefix }}update/{{ platform }}/{{ locale }}/firefox-{{ version }}.bz2.complete.mar
-    complete_mar_sig:
-      artifact: {{ artifact_base_url }}/firefox-{{ version }}.{{ locale }}.{{ platform }}.bz2.complete.mar.asc
-      s3_key: {{ s3_prefix }}update/{{ platform }}/{{ locale }}/firefox-{{ version }}.bz2.complete.mar.asc
-{% endfor %}
deleted file mode 100644
--- a/testing/mozharness/configs/beetmover/repacks.yml.tmpl
+++ /dev/null
@@ -1,65 +0,0 @@
----
-metadata:
-    name: "Beet Mover Manifest"
-    description: "Maps artifact locations to s3 key names for the non en-US locales"
-    owner: "release@mozilla.com"
-
-mapping:
-{% for locale in locales %}
-  # common deliverables
-  {{ locale }}:
-    complete_mar:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.complete.mar
-      s3_key: {{ s3_prefix }}update/{{ platform }}/{{ locale }}/firefox-{{ version }}.complete.mar
-    checksum:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.checksums
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.checksums
-    checksum_sig:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.checksums.asc
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.checksums.asc
-    xpi:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.langpack.xpi
-      s3_key: {{ s3_prefix }}{{ platform }}/xpi/{{ locale }}.xpi
-
-  {% if platform == "win32" %}
-    full_installer:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.installer.exe
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/Firefox Setup {{ version }}.exe
-    {% if "esr" not in version %}
-    stub_installer:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.installer-stub.exe
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/Firefox Installer.exe
-    {% endif %}
-    package:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.zip
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.zip
-  {% endif %}
-
-  {% if platform == "win64" %}
-    full_installer:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.installer.exe
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/Firefox Setup {{ version }}.exe
-    package:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.zip
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.zip
-  {% endif %}
-
-  {% if platform == "linux-i686" %}
-    package:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.tar.bz2
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.tar.bz2
-  {% endif %}
-
-  {% if platform == "linux-x86_64" %}
-    package:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.tar.bz2
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.tar.bz2
-  {% endif %}
-
-  {% if platform == "mac" %}
-    package:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.dmg
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/Firefox {{ version }}.dmg
-  {% endif %}
-
-{% endfor %}
deleted file mode 100644
--- a/testing/mozharness/configs/beetmover/snap.yml.tmpl
+++ /dev/null
@@ -1,11 +0,0 @@
----
-metadata:
-    name: "Beet Mover Manifest"
-    description: "Maps artifact locations to s3 key names for snap iamge"
-    owner: "release@mozilla.com"
-
-mapping:
-  all:
-    snap:
-      artifact: {{ artifact_base_url }}/firefox-{{ version }}.snap
-      s3_key: {{ s3_prefix }}snap/firefox-{{ version }}.snap
deleted file mode 100644
--- a/testing/mozharness/configs/beetmover/snap_checksums.yml.tmpl
+++ /dev/null
@@ -1,14 +0,0 @@
----
-metadata:
-    name: "Beet Mover Manifest"
-    description: "Maps artifact locations to s3 key names for snap checksums"
-    owner: "release@mozilla.com"
-
-mapping:
-  all:
-    snap_checksum:
-      artifact: {{ artifact_base_url }}/firefox-{{ version }}.snap.checksums
-      s3_key: {{ s3_prefix }}snap/firefox-{{ version }}.snap.checksums
-    snap_checksum_asc:
-      artifact: {{ artifact_base_url }}/firefox-{{ version }}.snap.checksums.asc
-      s3_key: {{ s3_prefix }}snap/firefox-{{ version }}.snap.checksums.asc
deleted file mode 100644
--- a/testing/mozharness/configs/beetmover/source.yml.tmpl
+++ /dev/null
@@ -1,14 +0,0 @@
----
-metadata:
-    name: "Beet Mover Manifest"
-    description: "Maps artifact locations to s3 key names for source bundles"
-    owner: "release@mozilla.com"
-
-mapping:
-  all:
-    source_bundle:
-      artifact: {{ artifact_base_url }}/firefox-{{ version }}.bundle
-      s3_key: {{ s3_prefix }}source/firefox-{{ version }}.bundle
-    source_tar:
-      artifact: {{ artifact_base_url }}/firefox-{{ version }}.source.tar.xz
-      s3_key: {{ s3_prefix }}source/firefox-{{ version }}.source.tar.xz
deleted file mode 100644
--- a/testing/mozharness/configs/beetmover/source_checksums.yml.tmpl
+++ /dev/null
@@ -1,14 +0,0 @@
----
-metadata:
-    name: "Beet Mover Manifest"
-    description: "Maps artifact locations to s3 key names for source bundle checksums"
-    owner: "release@mozilla.com"
-
-mapping:
-  all:
-    source_checksum:
-      artifact: {{ artifact_base_url }}/firefox-{{ version }}.source.checksums
-      s3_key: {{ s3_prefix }}source/firefox-{{ version }}.source.checksums
-    source_checksum_asc:
-      artifact: {{ artifact_base_url }}/firefox-{{ version }}.source.checksums.asc
-      s3_key: {{ s3_prefix }}source/firefox-{{ version }}.source.checksums.asc
deleted file mode 100644
--- a/testing/mozharness/configs/beetmover/win32_to_win64_partials.yml.tmpl
+++ /dev/null
@@ -1,16 +0,0 @@
----
-metadata:
-    name: "Beet Mover Manifest"
-    description: "Maps artifact locations to s3 key names for partials"
-    owner: "release@mozilla.com"
-
-mapping:
-{% for locale in locales %}
-  {{ locale }}:
-    partial_mar:
-      artifact: {{ artifact_base_url }}/firefox-{{ partial_version }}-{{ version }}.{{ locale }}.win32-to-win64.partial.mar
-      s3_key: {{ s3_prefix }}update/win32-to-win64/{{ locale }}/firefox-{{ partial_version }}-{{ version }}.partial.mar
-    partial_mar_sig:
-      artifact: {{ artifact_base_url }}/firefox-{{ partial_version }}-{{ version }}.{{ locale }}.win32-to-win64.partial.mar.asc
-      s3_key: {{ s3_prefix }}update/win32-to-win64/{{ locale }}/firefox-{{ partial_version }}-{{ version }}.partial.mar.asc
-{% endfor %}
deleted file mode 100644
--- a/testing/mozharness/configs/releases/postrelease_fennec_beta.py
+++ /dev/null
@@ -1,18 +0,0 @@
-config = {
-    "log_name": "bump_beta",
-    "version_files": [{"file": "browser/config/version_display.txt"}],
-    "repo": {
-        "repo": "https://hg.mozilla.org/releases/mozilla-beta",
-        "branch": "default",
-        "dest": "mozilla-beta",
-        "vcs": "hg",
-        "clone_upstream_url": "https://hg.mozilla.org/mozilla-unified",
-    },
-    "vcs_share_base": "/builds/hg-shared",
-    "push_dest": "ssh://hg.mozilla.org/releases/mozilla-beta",
-    "ignore_no_changes": True,
-    "ssh_user": "ffxbld",
-    "ssh_key": "~/.ssh/ffxbld_rsa",
-    "ship_it_root": "https://ship-it.mozilla.org",
-    "ship_it_username":  "ship_it-ffxbld",
-}
deleted file mode 100644
--- a/testing/mozharness/configs/releases/postrelease_fennec_release.py
+++ /dev/null
@@ -1,22 +0,0 @@
-config = {
-    "log_name": "bump_release",
-    "version_files": [
-        {"file": "browser/config/version.txt"},
-        {"file": "browser/config/version_display.txt"},
-        {"file": "config/milestone.txt"},
-    ],
-    "repo": {
-        "repo": "https://hg.mozilla.org/releases/mozilla-release",
-        "branch": "default",
-        "dest": "mozilla-release",
-        "vcs": "hg",
-        "clone_upstream_url": "https://hg.mozilla.org/mozilla-unified",
-    },
-    "vcs_share_base": "/builds/hg-shared",
-    "push_dest": "ssh://hg.mozilla.org/releases/mozilla-release",
-    "ignore_no_changes": True,
-    "ssh_user": "ffxbld",
-    "ssh_key": "~/.ssh/ffxbld_rsa",
-    "ship_it_root": "https://ship-it.mozilla.org",
-    "ship_it_username":  "ship_it-ffxbld",
-}
deleted file mode 100644
--- a/testing/mozharness/configs/releases/postrelease_firefox_beta.py
+++ /dev/null
@@ -1,18 +0,0 @@
-config = {
-    "log_name": "bump_beta",
-    "version_files": [{"file": "browser/config/version_display.txt"}],
-    "repo": {
-        "repo": "https://hg.mozilla.org/releases/mozilla-beta",
-        "branch": "default",
-        "dest": "mozilla-beta",
-        "vcs": "hg",
-        "clone_upstream_url": "https://hg.mozilla.org/mozilla-unified",
-    },
-    "vcs_share_base": "/builds/hg-shared",
-    "push_dest": "ssh://hg.mozilla.org/releases/mozilla-beta",
-    "ignore_no_changes": True,
-    "ssh_user": "ffxbld",
-    "ssh_key": "~/.ssh/ffxbld_rsa",
-    "ship_it_root": "https://ship-it.mozilla.org",
-    "ship_it_username":  "ship_it-ffxbld",
-}
deleted file mode 100644
--- a/testing/mozharness/configs/releases/postrelease_firefox_esr52.py
+++ /dev/null
@@ -1,22 +0,0 @@
-config = {
-    "log_name": "bump_esr52",
-    "version_files": [
-        {"file": "browser/config/version.txt"},
-        {"file": "browser/config/version_display.txt"},
-        {"file": "config/milestone.txt"},
-    ],
-    "repo": {
-        "repo": "https://hg.mozilla.org/releases/mozilla-esr52",
-        "branch": "default",
-        "dest": "mozilla-esr52",
-        "vcs": "hg",
-        "clone_upstream_url": "https://hg.mozilla.org/mozilla-unified",
-    },
-    "vcs_share_base": "/builds/hg-shared",
-    "push_dest": "ssh://hg.mozilla.org/releases/mozilla-esr52",
-    "ignore_no_changes": True,
-    "ssh_user": "ffxbld",
-    "ssh_key": "~/.ssh/ffxbld_rsa",
-    "ship_it_root": "https://ship-it.mozilla.org",
-    "ship_it_username":  "ship_it-ffxbld",
-}
deleted file mode 100644
--- a/testing/mozharness/configs/releases/postrelease_firefox_release.py
+++ /dev/null
@@ -1,22 +0,0 @@
-config = {
-    "log_name": "bump_release",
-    "version_files": [
-        {"file": "browser/config/version.txt"},
-        {"file": "browser/config/version_display.txt"},
-        {"file": "config/milestone.txt"},
-    ],
-    "repo": {
-        "repo": "https://hg.mozilla.org/releases/mozilla-release",
-        "branch": "default",
-        "dest": "mozilla-release",
-        "vcs": "hg",
-        "clone_upstream_url": "https://hg.mozilla.org/mozilla-unified",
-    },
-    "vcs_share_base": "/builds/hg-shared",
-    "push_dest": "ssh://hg.mozilla.org/releases/mozilla-release",
-    "ignore_no_changes": True,
-    "ssh_user": "ffxbld",
-    "ssh_key": "~/.ssh/ffxbld_rsa",
-    "ship_it_root": "https://ship-it.mozilla.org",
-    "ship_it_username":  "ship_it-ffxbld",
-}
deleted file mode 100755
--- a/testing/mozharness/scripts/release/beet_mover.py
+++ /dev/null
@@ -1,378 +0,0 @@
-#!/usr/bin/env python
-# ***** BEGIN LICENSE BLOCK *****
-# 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/.
-# ***** END LICENSE BLOCK *****
-"""beet_mover.py.
-
-downloads artifacts, scans them and uploads them to s3
-"""
-import hashlib
-import sys
-import os
-import pprint
-import re
-from os import listdir
-from os.path import isfile, join
-import sh
-import redo
-
-sys.path.insert(1, os.path.dirname(os.path.dirname(sys.path[0])))
-from mozharness.base.log import FATAL
-from mozharness.base.python import VirtualenvMixin
-from mozharness.base.script import BaseScript
-from mozharness.mozilla.aws import pop_aws_auth_from_env
-import mozharness
-import mimetypes
-
-
-def get_hash(content, hash_type="md5"):
-    h = hashlib.new(hash_type)
-    h.update(content)
-    return h.hexdigest()
-
-
-CONFIG_OPTIONS = [
-    [["--template"], {
-        "dest": "template",
-        "help": "Specify jinja2 template file",
-    }],
-    [['--locale', ], {
-        "action": "extend",
-        "dest": "locales",
-        "type": "string",
-        "help": "Specify the locale(s) to upload."}],
-    [["--platform"], {
-        "dest": "platform",
-        "help": "Specify the platform of the build",
-    }],
-    [["--version"], {
-        "dest": "version",
-        "help": "full release version based on gecko and tag/stage identifier. e.g. '44.0b1'"
-    }],
-    [["--app-version"], {
-        "dest": "app_version",
-        "help": "numbered version based on gecko. e.g. '44.0'"
-    }],
-    [["--partial-version"], {
-        "dest": "partial_version",
-        "help": "the partial version the mar is based off of"
-    }],
-    [["--artifact-subdir"], {
-        "dest": "artifact_subdir",
-        "default": 'build',
-        "help": "subdir location for taskcluster artifacts after public/ base.",
-    }],
-    [["--build-num"], {
-        "dest": "build_num",
-        "help": "the release build identifier"
-    }],
-    [["--taskid"], {
-        "dest": "taskid",
-        "help": "taskcluster task id to download artifacts from",
-    }],
-    [["--bucket"], {
-        "dest": "bucket",
-        "help": "s3 bucket to move beets to.",
-    }],
-    [["--product"], {
-        "dest": "product",
-        "help": "product for which artifacts are beetmoved",
-    }],
-    [["--exclude"], {
-        "dest": "excludes",
-        "action": "append",
-        "help": "List of filename patterns to exclude. See script source for default",
-    }],
-    [["-s", "--scan-parallelization"], {
-        "dest": "scan_parallelization",
-        "default": 4,
-        "type": "int",
-        "help": "Number of concurrent file scans",
-    }],
-]
-
-DEFAULT_EXCLUDES = [
-    r"^.*tests.*$",
-    r"^.*crashreporter.*$",
-    r"^.*\.zip(\.asc)?$",
-    r"^.*\.log$",
-    r"^.*\.txt$",
-    r"^.*\.asc$",
-    r"^.*/partner-repacks.*$",
-    r"^.*.checksums(\.asc)?$",
-    r"^.*/logs/.*$",
-    r"^.*json$",
-    r"^.*/host.*$",
-    r"^.*/mar-tools/.*$",
-    r"^.*robocop.apk$",
-    r"^.*contrib.*"
-]
-CACHE_DIR = 'cache'
-
-MIME_MAP = {
-    '': 'text/plain',
-    '.asc': 'text/plain',
-    '.beet': 'text/plain',
-    '.bundle': 'application/octet-stream',
-    '.bz2': 'application/octet-stream',
-    '.checksums': 'text/plain',
-    '.dmg': 'application/x-iso9660-image',
-    '.mar': 'application/octet-stream',
-    '.xpi': 'application/x-xpinstall'
-}
-
-HASH_FORMATS = ["sha512", "sha256"]
-
-
-class BeetMover(BaseScript, VirtualenvMixin, object):
-    def __init__(self, aws_creds):
-        beetmover_kwargs = {
-            'config_options': CONFIG_OPTIONS,
-            'all_actions': [
-                # 'clobber',
-                'create-virtualenv',
-                'activate-virtualenv',
-                'generate-candidates-manifest',
-                'refresh-antivirus',
-                'verify-bits',  # beets
-                'download-bits',  # beets
-                'scan-bits',     # beets
-                'upload-bits',  # beets
-            ],
-            'require_config_file': False,
-            # Default configuration
-            'config': {
-                # base index url where to find taskcluster artifact based on taskid
-                "artifact_base_url": \
-                'https://queue.taskcluster.net/v1/task/{taskid}/artifacts/public/{subdir}',
-                "virtualenv_modules": [
-                    "boto",
-                    "PyYAML",
-                    "Jinja2",
-                    "redo",
-                    "cryptography==2.0.3",
-                    "mar",
-                ],
-                "virtualenv_path": "venv",
-            },
-        }
-        # todo do excludes need to be configured via command line for specific builds?
-        super(BeetMover, self).__init__(**beetmover_kwargs)
-
-        c = self.config
-        self.manifest = {}
-        # assigned in _post_create_virtualenv
-        self.virtualenv_imports = None
-        self.bucket = c['bucket']
-        if not all(aws_creds):
-            self.fatal('credentials must be passed in env: '
-                       '"AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"')
-        self.aws_key_id, self.aws_secret_key = aws_creds
-        # if excludes is set from command line, use it otherwise use defaults
-        self.excludes = self.config.get('excludes', DEFAULT_EXCLUDES)
-        dirs = self.query_abs_dirs()
-        self.dest_dir = os.path.join(dirs['abs_work_dir'], CACHE_DIR)
-        self.mime_fix()
-
-    def activate_virtualenv(self):
-        """
-        activates virtualenv and adds module imports to a instance wide namespace.
-
-        creating and activating a virtualenv onto the currently executing python interpreter is a
-        bit black magic. Rather than having import statements added in various places within the
-        script, we import them here immediately after we activate the newly created virtualenv
-        """
-        VirtualenvMixin.activate_virtualenv(self)
-
-        import boto
-        import yaml
-        import jinja2
-        self.virtualenv_imports = {
-            'boto': boto,
-            'yaml': yaml,
-            'jinja2': jinja2,
-        }
-        self.log("activated virtualenv with the modules: {}".format(str(self.virtualenv_imports)))
-
-    def _get_template_vars(self):
-        return {
-            "platform": self.config['platform'],
-            "locales": self.config.get('locales'),
-            "version": self.config['version'],
-            "app_version": self.config.get('app_version', ''),
-            "partial_version": self.config.get('partial_version', ''),
-            "build_num": self.config['build_num'],
-            # keep the trailing slash
-            "s3_prefix": 'pub/{prod}/candidates/{ver}-candidates/{n}/'.format(
-                prod=self.config['product'], ver=self.config['version'],
-                n=self.config['build_num']
-            ),
-            "artifact_base_url": self.config['artifact_base_url'].format(
-                    taskid=self.config['taskid'], subdir=self.config['artifact_subdir']
-            )
-        }
-
-    def generate_candidates_manifest(self):
-        """
-        generates and outputs a manifest that maps expected Taskcluster artifact names
-        to release deliverable names
-        """
-        self.log('generating manifest from {}...'.format(self.config['template']))
-        template_dir, template_file = os.path.split(os.path.abspath(self.config['template']))
-        jinja2 = self.virtualenv_imports['jinja2']
-        yaml = self.virtualenv_imports['yaml']
-
-        jinja_env = jinja2.Environment(loader=jinja2.FileSystemLoader(template_dir),
-                                       undefined=jinja2.StrictUndefined)
-        template = jinja_env.get_template(template_file)
-        self.manifest = yaml.safe_load(template.render(**self._get_template_vars()))
-
-        self.log("manifest generated:")
-        self.log(pprint.pformat(self.manifest['mapping']))
-
-    def verify_bits(self):
-        """
-        inspects each artifact and verifies that they were created by trustworthy tasks
-        """
-        # TODO
-        self.log('skipping verification. unimplemented...')
-
-    def refresh_antivirus(self):
-        self.info("Refreshing clamav db...")
-        try:
-            redo.retry(lambda:
-                       sh.freshclam("--stdout", "--verbose", _timeout=300,
-                                    _err_to_out=True))
-            self.info("Done.")
-        except sh.ErrorReturnCode:
-            self.warning("Freshclam failed, skipping DB update")
-
-    def download_bits(self):
-        """
-        downloads list of artifacts to self.dest_dir dir based on a given manifest
-        """
-        self.log('downloading and uploading artifacts to self_dest_dir...')
-        dirs = self.query_abs_dirs()
-
-        for locale in self.manifest['mapping']:
-            for deliverable in self.manifest['mapping'][locale]:
-                self.log("downloading '{}' deliverable for '{}' locale".format(deliverable,
-                                                                               locale))
-                source = self.manifest['mapping'][locale][deliverable]['artifact']
-                self.retry(
-                    self.download_file,
-                    args=[source],
-                    kwargs={'parent_dir': dirs['abs_work_dir']},
-                    error_level=FATAL)
-        self.log('Success!')
-
-    def _strip_prefix(self, s3_key):
-        """Return file name relative to prefix"""
-        # "abc/def/hfg".split("abc/de")[-1] == "f/hfg"
-        return s3_key.split(self._get_template_vars()["s3_prefix"])[-1]
-
-    def upload_bits(self):
-        """
-        uploads list of artifacts to s3 candidates dir based on a given manifest
-        """
-        self.log('uploading artifacts to s3...')
-        dirs = self.query_abs_dirs()
-
-        # connect to s3
-        boto = self.virtualenv_imports['boto']
-        conn = boto.connect_s3(self.aws_key_id, self.aws_secret_key)
-        bucket = conn.get_bucket(self.bucket)
-
-        for locale in self.manifest['mapping']:
-            for deliverable in self.manifest['mapping'][locale]:
-                self.log("uploading '{}' deliverable for '{}' locale".format(deliverable, locale))
-                # we have already downloaded the files locally so we can use that version
-                source = self.manifest['mapping'][locale][deliverable]['artifact']
-                s3_key = self.manifest['mapping'][locale][deliverable]['s3_key']
-                downloaded_file = os.path.join(dirs['abs_work_dir'],
-                                               self.get_filename_from_url(source))
-                # generate checksums for every uploaded file
-                beet_file_name = '{}.beet'.format(downloaded_file)
-                # upload checksums to a separate subdirectory
-                beet_dest = '{prefix}beetmover-checksums/{f}.beet'.format(
-                    prefix=self._get_template_vars()["s3_prefix"],
-                    f=self._strip_prefix(s3_key)
-                )
-                beet_contents = '\n'.join([
-                    '{hash} {fmt} {size} {name}'.format(
-                        hash=self.get_hash_for_file(downloaded_file, hash_type=fmt),
-                        fmt=fmt,
-                        size=os.path.getsize(downloaded_file),
-                        name=self._strip_prefix(s3_key)) for fmt in HASH_FORMATS
-                ])
-                self.write_to_file(beet_file_name, beet_contents)
-                self.upload_bit(source=downloaded_file, s3_key=s3_key,
-                                bucket=bucket)
-                self.upload_bit(source=beet_file_name, s3_key=beet_dest,
-                                bucket=bucket)
-        self.log('Success!')
-
-    def upload_bit(self, source, s3_key, bucket):
-        boto = self.virtualenv_imports['boto']
-        self.info('uploading to s3 with key: {}'.format(s3_key))
-        key = boto.s3.key.Key(bucket)  # create new key
-        key.key = s3_key  # set key name
-
-        self.info("Checking if `{}` already exists".format(s3_key))
-        key = bucket.get_key(s3_key)
-        if not key:
-            self.info("Uploading to `{}`".format(s3_key))
-            key = bucket.new_key(s3_key)
-            # set key value
-            mime_type, _ = mimetypes.guess_type(source)
-            self.retry(lambda: key.set_contents_from_filename(
-                       source, headers={'Content-Type': mime_type}), error_level=FATAL),
-        else:
-            if not get_hash(key.get_contents_as_string()) == get_hash(open(source).read()):
-                # for now, let's halt. If necessary, we can revisit this and allow for overwrites
-                #  to the same buildnum release with different bits
-                self.fatal("`{}` already exists with different checksum.".format(s3_key))
-            self.log("`{}` has the same MD5 checksum, not uploading".format(s3_key))
-
-    def scan_bits(self):
-
-        dirs = self.query_abs_dirs()
-
-        filenames = [f for f in listdir(dirs['abs_work_dir'])
-                     if isfile(join(dirs['abs_work_dir'], f))]
-        self.mkdir_p(self.dest_dir)
-        for file_name in filenames:
-            if self._matches_exclude(file_name):
-                self.info("Excluding {} from virus scan".format(file_name))
-            else:
-                self.info('Copying {} to {}'.format(file_name, self.dest_dir))
-                self.copyfile(os.path.join(dirs['abs_work_dir'], file_name),
-                              os.path.join(self.dest_dir, file_name))
-        self._scan_files()
-        self.info('Emptying {}'.format(self.dest_dir))
-        self.rmtree(self.dest_dir)
-
-    def _scan_files(self):
-        """Scan the files we've collected. We do the download and scan concurrently to make
-        it easier to have a coherent log afterwards. Uses the venv python."""
-        external_tools_path = os.path.join(os.path.abspath(os.path.dirname(
-                              os.path.dirname(mozharness.__file__))), 'external_tools')
-        self.run_command([self.query_python_path(), os.path.join(external_tools_path,
-                         'extract_and_run_command.py'),
-                          '-j{}'.format(self.config['scan_parallelization']),
-                          'clamscan', '--no-summary', '--', self.dest_dir])
-
-    def _matches_exclude(self, keyname):
-        return any(re.search(exclude, keyname) for exclude in self.excludes)
-
-    def mime_fix(self):
-        """ Add mimetypes for custom extensions """
-        mimetypes.init()
-        map(lambda (ext, mime_type,): mimetypes.add_type(mime_type, ext), MIME_MAP.items())
-
-
-if __name__ == '__main__':
-    beet_mover = BeetMover(pop_aws_auth_from_env())
-    beet_mover.run_and_exit()
deleted file mode 100644
--- a/testing/mozharness/scripts/release/postrelease_bouncer_aliases.py
+++ /dev/null
@@ -1,107 +0,0 @@
-#!/usr/bin/env python
-# lint_ignore=E501
-# ***** BEGIN LICENSE BLOCK *****
-# 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/.
-# ***** END LICENSE BLOCK *****
-""" postrelease_bouncer_aliases.py
-
-A script to replace the old-fashion way of updating the bouncer aliaes through
-tools script.
-"""
-
-import os
-import sys
-
-sys.path.insert(1, os.path.dirname(os.path.dirname(sys.path[0])))
-
-from mozharness.base.python import VirtualenvMixin, virtualenv_config_options
-from mozharness.base.script import BaseScript
-from mozharness.mozilla.buildbot import BuildbotMixin
-
-
-# PostReleaseBouncerAliases {{{1
-class PostReleaseBouncerAliases(BaseScript, VirtualenvMixin, BuildbotMixin):
-    config_options = virtualenv_config_options
-
-    def __init__(self, require_config_file=True):
-        super(PostReleaseBouncerAliases, self).__init__(
-            config_options=self.config_options,
-            require_config_file=require_config_file,
-            config={
-                "virtualenv_modules": [
-                    "redo",
-                    "requests",
-                ],
-                "virtualenv_path": "venv",
-                'credentials_file': 'oauth.txt',
-                'buildbot_json_path': 'buildprops.json',
-            },
-            all_actions=[
-                "create-virtualenv",
-                "activate-virtualenv",
-                "update-bouncer-aliases",
-            ],
-            default_actions=[
-                "create-virtualenv",
-                "activate-virtualenv",
-                "update-bouncer-aliases",
-            ],
-        )
-
-    def _pre_config_lock(self, rw_config):
-        super(PostReleaseBouncerAliases, self)._pre_config_lock(rw_config)
-        # override properties from buildbot properties here as defined by
-        # taskcluster properties
-        self.read_buildbot_config()
-        if not self.buildbot_config:
-            self.warning("Skipping buildbot properties overrides")
-            return
-        props = self.buildbot_config["properties"]
-        for prop in ['tuxedo_server_url', 'version']:
-            if props.get(prop):
-                self.info("Overriding %s with %s" % (prop, props[prop]))
-                self.config[prop] = props.get(prop)
-            else:
-                self.warning("%s could not be found within buildprops" % prop)
-                return
-
-    def _update_bouncer_alias(self, tuxedo_server_url, auth,
-                              related_product, alias):
-        from redo import retry
-        import requests
-
-        url = "%s/create_update_alias" % tuxedo_server_url
-        data = {"alias": alias, "related_product": related_product}
-        self.log("Updating {} to point to {} using {}".format(alias,
-                                                              related_product,
-                                                              url))
-
-        # Wrap the real call to hide credentials from retry's logging
-        def do_update_bouncer_alias():
-            r = requests.post(url, data=data, auth=auth,
-                              verify=False, timeout=60)
-            r.raise_for_status()
-
-        retry(do_update_bouncer_alias)
-
-    def update_bouncer_aliases(self):
-        tuxedo_server_url = self.config['tuxedo_server_url']
-        credentials_file = os.path.join(os.getcwd(),
-                                        self.config['credentials_file'])
-        credentials = {}
-        execfile(credentials_file, credentials)
-        auth = (credentials['tuxedoUsername'], credentials['tuxedoPassword'])
-        version = self.config['version']
-        for product, info in self.config["products"].iteritems():
-            if "alias" in info:
-                product_template = info["product-name"]
-                related_product = product_template % {"version": version}
-                self._update_bouncer_alias(tuxedo_server_url, auth,
-                                           related_product, info["alias"])
-
-
-# __main__ {{{1
-if __name__ == '__main__':
-    PostReleaseBouncerAliases().run_and_exit()
deleted file mode 100644
--- a/testing/mozharness/scripts/release/postrelease_mark_as_shipped.py
+++ /dev/null
@@ -1,110 +0,0 @@
-#!/usr/bin/env python
-# lint_ignore=E501
-# ***** BEGIN LICENSE BLOCK *****
-# 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/.
-# ***** END LICENSE BLOCK *****
-""" postrelease_mark_as_shipped.py
-
-A script to automate the manual way of updating a release as shipped in Ship-it
-following its successful ship-to-the-door opertion.
-"""
-import os
-import sys
-from datetime import datetime
-
-sys.path.insert(1, os.path.dirname(os.path.dirname(sys.path[0])))
-
-from mozharness.base.python import VirtualenvMixin, virtualenv_config_options
-from mozharness.base.script import BaseScript
-from mozharness.mozilla.buildbot import BuildbotMixin
-
-
-def build_release_name(product, version, buildno):
-    """Function to reconstruct the name of the release based on product,
-    version and buildnumber
-    """
-    return "{}-{}-build{}".format(product.capitalize(),
-                                  str(version), str(buildno))
-
-
-class MarkReleaseAsShipped(BaseScript, VirtualenvMixin, BuildbotMixin):
-    config_options = virtualenv_config_options
-
-    def __init__(self, require_config_file=True):
-        super(MarkReleaseAsShipped, self).__init__(
-            config_options=self.config_options,
-            require_config_file=require_config_file,
-            config={
-                "virtualenv_modules": [
-                    "shipitapi",
-                ],
-                "virtualenv_path": "venv",
-                "credentials_file": "oauth.txt",
-                "buildbot_json_path": "buildprops.json",
-                "timeout": 60,
-            },
-            all_actions=[
-                "create-virtualenv",
-                "activate-virtualenv",
-                "mark-as-shipped",
-            ],
-            default_actions=[
-                "create-virtualenv",
-                "activate-virtualenv",
-                "mark-as-shipped",
-            ],
-        )
-
-    def _pre_config_lock(self, rw_config):
-        super(MarkReleaseAsShipped, self)._pre_config_lock(rw_config)
-        # override properties from buildbot properties here as defined by
-        # taskcluster properties
-        self.read_buildbot_config()
-        if not self.buildbot_config:
-            self.warning("Skipping buildbot properties overrides")
-            return
-        props = self.buildbot_config['properties']
-        mandatory_props = ['product', 'version', 'build_number']
-        missing_props = []
-        for prop in mandatory_props:
-            if prop in props:
-                self.info("Overriding %s with %s" % (prop, props[prop]))
-                self.config[prop] = props.get(prop)
-            else:
-                self.warning("%s could not be found within buildprops" % prop)
-                missing_props.append(prop)
-
-        if missing_props:
-            raise Exception("%s not found in configs" % missing_props)
-
-        self.config['name'] = build_release_name(self.config['product'],
-                                                 self.config['version'],
-                                                 self.config['build_number'])
-
-    def mark_as_shipped(self):
-        """Method to make a simple call to Ship-it API to change a release
-        status to 'shipped'
-        """
-        credentials_file = os.path.join(os.getcwd(),
-                                        self.config["credentials_file"])
-        credentials = {}
-        execfile(credentials_file, credentials)
-        ship_it_credentials = credentials["ship_it_credentials"]
-        auth = (self.config["ship_it_username"],
-                ship_it_credentials.get(self.config["ship_it_username"]))
-        api_root = self.config['ship_it_root']
-
-        from shipitapi import Release
-        release_api = Release(auth, api_root=api_root,
-                              timeout=self.config['timeout'])
-        shipped_at = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')
-
-        self.info("Mark the release as shipped with %s timestamp" % shipped_at)
-        release_api.update(self.config['name'],
-                           status='shipped', shippedAt=shipped_at)
-
-
-if __name__ == '__main__':
-    MarkReleaseAsShipped().run_and_exit()
deleted file mode 100644
--- a/testing/mozharness/scripts/release/postrelease_version_bump.py
+++ /dev/null
@@ -1,231 +0,0 @@
-#!/usr/bin/env python
-# lint_ignore=E501
-# ***** BEGIN LICENSE BLOCK *****
-# 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/.
-# ***** END LICENSE BLOCK *****
-""" postrelease_version_bump.py
-
-A script to increase in-tree version number after shipping a release.
-"""
-
-from distutils.version import StrictVersion
-import os
-import sys
-
-sys.path.insert(1, os.path.dirname(os.path.dirname(sys.path[0])))
-from mozharness.base.vcs.vcsbase import MercurialScript
-from mozharness.base.vcs.mercurial import MercurialVCS
-from mozharness.mozilla.buildbot import BuildbotMixin
-from mozharness.mozilla.repo_manipulation import MercurialRepoManipulationMixin
-
-
-# PostReleaseVersionBump {{{1
-class PostReleaseVersionBump(MercurialScript, BuildbotMixin,
-                             MercurialRepoManipulationMixin):
-    config_options = [
-        [['--hg-user', ], {
-            "action": "store",
-            "dest": "hg_user",
-            "type": "string",
-            "default": "ffxbld <release@mozilla.com>",
-            "help": "Specify what user to use to commit to hg.",
-        }],
-        [['--next-version', ], {
-            "action": "store",
-            "dest": "next_version",
-            "type": "string",
-            "help": "Next version used in version bump",
-        }],
-        [['--ssh-user', ], {
-            "action": "store",
-            "dest": "ssh_user",
-            "type": "string",
-            "help": "SSH username with hg.mozilla.org permissions",
-        }],
-        [['--ssh-key', ], {
-            "action": "store",
-            "dest": "ssh_key",
-            "type": "string",
-            "help": "Path to SSH key.",
-        }],
-        [['--product', ], {
-            "action": "store",
-            "dest": "product",
-            "type": "string",
-            "help": "Product name",
-        }],
-        [['--version', ], {
-            "action": "store",
-            "dest": "version",
-            "type": "string",
-            "help": "Version",
-        }],
-        [['--build-number', ], {
-            "action": "store",
-            "dest": "build_number",
-            "type": "string",
-            "help": "Build number",
-        }],
-        [['--revision', ], {
-            "action": "store",
-            "dest": "revision",
-            "type": "string",
-            "help": "HG revision to tag",
-        }],
-    ]
-
-    def __init__(self, require_config_file=True):
-        super(PostReleaseVersionBump, self).__init__(
-            config_options=self.config_options,
-            all_actions=[
-                'clobber',
-                'clean-repos',
-                'pull',
-                'bump_postrelease',
-                'commit-changes',
-                'tag',
-                'push',
-            ],
-            default_actions=[
-                'clean-repos',
-                'pull',
-                'bump_postrelease',
-                'commit-changes',
-                'tag',
-                'push',
-            ],
-            config={
-                'buildbot_json_path': 'buildprops.json',
-            },
-            require_config_file=require_config_file
-        )
-
-    def _pre_config_lock(self, rw_config):
-        super(PostReleaseVersionBump, self)._pre_config_lock(rw_config)
-        # override properties from buildbot properties here as defined by
-        # taskcluster properties
-        self.read_buildbot_config()
-        if not self.buildbot_config:
-            self.warning("Skipping buildbot properties overrides")
-        else:
-            props = self.buildbot_config["properties"]
-            for prop in ['next_version', 'product', 'version', 'build_number',
-                         'revision']:
-                if props.get(prop):
-                    self.info("Overriding %s with %s" % (prop, props[prop]))
-                    self.config[prop] = props.get(prop)
-
-        if not self.config.get("next_version"):
-            self.fatal("Next version has to be set. Use --next-version or "
-                       "pass `next_version' via buildbot properties.")
-
-    def query_abs_dirs(self):
-        """ Allow for abs_from_dir and abs_to_dir
-            """
-        if self.abs_dirs:
-            return self.abs_dirs
-        self.abs_dirs = super(PostReleaseVersionBump, self).query_abs_dirs()
-        self.abs_dirs["abs_gecko_dir"] = os.path.join(
-                self.abs_dirs['abs_work_dir'], self.config["repo"]["dest"])
-        return self.abs_dirs
-
-    def query_repos(self):
-        """Build a list of repos to clone."""
-        return [self.config["repo"]]
-
-    def query_commit_dirs(self):
-        return [self.query_abs_dirs()["abs_gecko_dir"]]
-
-    def query_commit_message(self):
-        return "Automatic version bump. CLOSED TREE NO BUG a=release"
-
-    def query_push_dirs(self):
-        return self.query_commit_dirs()
-
-    def query_push_args(self, cwd):
-        # cwd is not used here
-        hg_ssh_opts = "ssh -l {user} -i {key}".format(
-            user=self.config["ssh_user"],
-            key=os.path.expanduser(self.config["ssh_key"])
-        )
-        return ["-e", hg_ssh_opts, "-r", "."]
-
-    def pull(self):
-        dirs = self.query_abs_dirs()
-        # bug 1417697 - clone default first, then pull to get the revision.
-        # This to deal with relbranches, which don't show up in mozilla-unified.
-        super(PostReleaseVersionBump, self).pull(
-                repos=self.query_repos())
-        vcs_obj = MercurialVCS(log_obj=self.log_obj, config=self.config)
-        vcs_obj.pull(
-            self.config['repo']['repo'],
-            dirs['abs_gecko_dir'],
-            update_dest=False,
-            revision=self.config['revision']
-        )
-
-    def bump_postrelease(self, *args, **kwargs):
-        """Bump version"""
-        dirs = self.query_abs_dirs()
-        for f in self.config["version_files"]:
-            curr_version = ".".join(self.get_version(dirs['abs_gecko_dir'], f["file"]))
-            next_version = self.config['next_version']
-
-            if StrictVersion(next_version) < StrictVersion(curr_version):
-                self.warning("Version bumping skipped due to conflicting values")
-                continue
-            elif StrictVersion(next_version) == StrictVersion(curr_version):
-                self.info("Version bumping skipped due to unchanged values")
-                continue
-            else:
-                self.replace(os.path.join(dirs['abs_gecko_dir'], f["file"]),
-                             curr_version, self.config["next_version"])
-
-    def check_tags(self, tag_names):
-        dirs = self.query_abs_dirs()
-        existing_tags = self.query_existing_tags(cwd=dirs['abs_gecko_dir'])
-        tags = []
-        for tag in tag_names:
-            if tag in existing_tags:
-                if self.config['revision'] == existing_tags[tag]:
-                    self.info(
-                        "Tag {} already exists on revision {}. Skipping...".format(
-                            tag, self.config['revision']
-                        )
-                    )
-                    continue
-                else:
-                    self.warning(
-                        "Tag {} exists on mismatched revision {}! Retagging...".format(
-                            tag, existing_tags[tag]
-                        )
-                    )
-            tags.append(tag)
-        return tags
-
-    def tag(self):
-        dirs = self.query_abs_dirs()
-        tags = ["{product}_{version}_BUILD{build_number}",
-                "{product}_{version}_RELEASE"]
-        tags = [t.format(product=self.config["product"].upper(),
-                         version=self.config["version"].replace(".", "_"),
-                         build_number=self.config["build_number"])
-                for t in tags]
-        tags = self.check_tags(tags)
-        if not tags:
-            self.info("No unique tags to add; skipping tagging.")
-            return
-        message = "No bug - Tagging {revision} with {tags} a=release CLOSED TREE"
-        message = message.format(
-            revision=self.config["revision"],
-            tags=', '.join(tags))
-        self.hg_tag(cwd=dirs["abs_gecko_dir"], tags=tags,
-                    revision=self.config["revision"], message=message,
-                    user=self.config["hg_user"], force=True)
-
-
-# __main__ {{{1
-if __name__ == '__main__':
-    PostReleaseVersionBump().run_and_exit()
--- a/testing/specialpowers/content/specialpowersAPI.js
+++ b/testing/specialpowers/content/specialpowersAPI.js
@@ -15,16 +15,17 @@ var global = this;
 ChromeUtils.import("chrome://specialpowers/content/MockFilePicker.jsm");
 ChromeUtils.import("chrome://specialpowers/content/MockColorPicker.jsm");
 ChromeUtils.import("chrome://specialpowers/content/MockPermissionPrompt.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
 ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+ChromeUtils.import("resource://gre/modules/ServiceWorkerCleanUp.jsm");
 
 // We're loaded with "this" not set to the global in some cases, so we
 // have to play some games to get at the global object here.  Normally
 // we'd try "this" from a function called with undefined this value,
 // but this whole file is in strict mode.  So instead fall back on
 // returning "this" from indirect eval, which returns the global.
 if (!(function() { var e = eval; return e("this"); })().File) { // eslint-disable-line no-eval
     Cu.importGlobalProperties(["File", "InspectorUtils", "NodeFilter"]);
@@ -1956,21 +1957,21 @@ SpecialPowersAPI.prototype = {
       "op": "notify",
       "observerTopic": topic,
       "observerData": data
     };
     this._sendSyncMessage("SPObserverService", msg);
   },
 
   removeAllServiceWorkerData() {
-    this.notifyObserversInParentProcess(null, "browser:purge-session-history", "");
+    return wrapIfUnwrapped(ServiceWorkerCleanUp.removeAll());
   },
 
   removeServiceWorkerDataForExampleDomain() {
-    this.notifyObserversInParentProcess(null, "browser:purge-domain-data", "example.com");
+    return wrapIfUnwrapped(ServiceWorkerCleanUp.removeFromHost("example.com"));
   },
 
   cleanUpSTSData(origin, flags) {
     return this._sendSyncMessage("SPCleanUpSTSData", {origin, flags: flags || 0});
   },
 
   requestDumpCoverageCounters() {
     this._sendSyncMessage("SPRequestDumpCoverageCounters", {});
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-scoping/shadow-cascade-order-001.html.ini
+++ /dev/null
@@ -1,43 +0,0 @@
-[shadow-cascade-order-001.html]
-  [D1. document vs ::slotted both with !important, ::slotted rule should win for open mode.]
-    expected: FAIL
-
-  [D2. document vs :host both with !important, :host rule should win for open mode.]
-    expected: FAIL
-
-  [D4. ::slotted vs :host both with !important, later in tree-of-trees rule should win for open mode.]
-    expected: FAIL
-
-  [D5. ::slotted vs inline both with !important, ::slotted rule should win for open mode.]
-    expected: FAIL
-
-  [D6. :host vs inline both with !important, :host rule should win for open mode.]
-    expected: FAIL
-
-  [E2. all styles with !important applied, rule in the last tree-of-trees should win for open mode.]
-    expected: FAIL
-
-  [F6. all rules with !important, the last rule in tree-of-trees should win for open mode.]
-    expected: FAIL
-
-  [D1. document vs ::slotted both with !important, ::slotted rule should win for closed mode.]
-    expected: FAIL
-
-  [D2. document vs :host both with !important, :host rule should win for closed mode.]
-    expected: FAIL
-
-  [D4. ::slotted vs :host both with !important, later in tree-of-trees rule should win for closed mode.]
-    expected: FAIL
-
-  [D5. ::slotted vs inline both with !important, ::slotted rule should win for closed mode.]
-    expected: FAIL
-
-  [D6. :host vs inline both with !important, :host rule should win for closed mode.]
-    expected: FAIL
-
-  [E2. all styles with !important applied, rule in the last tree-of-trees should win for closed mode.]
-    expected: FAIL
-
-  [F6. all rules with !important, the last rule in tree-of-trees should win for closed mode.]
-    expected: FAIL
-
--- a/toolkit/content/widgets/findbar.xml
+++ b/toolkit/content/widgets/findbar.xml
@@ -214,17 +214,17 @@
       </xul:description>
     </xul:hbox>
     <xul:toolbarbutton anonid="find-closebutton"
                        class="findbar-closebutton close-icon"
                        tooltiptext="&findCloseButton.tooltip;"
                        oncommand="close();"/>
     </content>
 
-    <implementation implements="nsIEditActionListener">
+    <implementation>
       <!-- Please keep in sync with toolkit/content/browser-content.js -->
       <field name="FIND_NORMAL">0</field>
       <field name="FIND_TYPEAHEAD">1</field>
       <field name="FIND_LINKS">2</field>
 
       <field name="__findMode">0</field>
       <property name="_findMode" onget="return this.__findMode;"
                 onset="this.__findMode = val; this._updateBrowserWithState(); return val;"/>
--- a/toolkit/forgetaboutsite/ForgetAboutSite.jsm
+++ b/toolkit/forgetaboutsite/ForgetAboutSite.jsm
@@ -6,16 +6,18 @@
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
 ChromeUtils.defineModuleGetter(this, "PlacesUtils",
                                "resource://gre/modules/PlacesUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "Downloads",
                                "resource://gre/modules/Downloads.jsm");
+ChromeUtils.defineModuleGetter(this, "ServiceWorkerCleanUp",
+                               "resource://gre/modules/ServiceWorkerCleanUp.jsm");
 
 var EXPORTED_SYMBOLS = ["ForgetAboutSite"];
 
 /**
  * Returns true if the string passed in is part of the root domain of the
  * current string.  For example, if this is "www.mozilla.org", and we pass in
  * "mozilla.org", this will return true.  It would return false the other way
  * around.
@@ -137,17 +139,21 @@ var ForgetAboutSite = {
         } catch (ex) {
           // Ignore entry
         } finally {
           resolve();
         }
       }));
     }
 
-    // Offline Storages
+    // ServiceWorkers
+    await ServiceWorkerCleanUp.removeFromHost("http://" + aDomain);
+    await ServiceWorkerCleanUp.removeFromHost("https://" + aDomain);
+
+    // Offline Storages. This must run after the ServiceWorkers promises.
     promises.push((async function() {
       // delete data from both HTTP and HTTPS sites
       let httpURI = NetUtil.newURI("http://" + aDomain);
       let httpsURI = NetUtil.newURI("https://" + aDomain);
       // Following code section has been reverted to the state before Bug 1238183,
       // but added a new argument to clearStoragesForPrincipal() for indicating
       // clear all storages under a given origin.
       let httpPrincipal = Services.scriptSecurityManager
new file mode 100644
--- /dev/null
+++ b/toolkit/forgetaboutsite/ServiceWorkerCleanUp.jsm
@@ -0,0 +1,60 @@
+/* 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";
+
+ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "serviceWorkerManager",
+                                   "@mozilla.org/serviceworkers/manager;1",
+                                   "nsIServiceWorkerManager");
+
+this.EXPORTED_SYMBOLS = ["ServiceWorkerCleanUp"];
+
+function unregisterServiceWorker(aSW) {
+  return new Promise(resolve => {
+    let unregisterCallback = {
+      unregisterSucceeded: resolve,
+      unregisterFailed: resolve, // We don't care about failures.
+      QueryInterface: XPCOMUtils.generateQI([Ci.nsIServiceWorkerUnregisterCallback])
+    };
+    serviceWorkerManager.propagateUnregister(aSW.principal, unregisterCallback, aSW.scope);
+  });
+}
+
+this.ServiceWorkerCleanUp = {
+  removeFromHost(aHost) {
+    let promises = [];
+    let serviceWorkers = serviceWorkerManager.getAllRegistrations();
+    for (let i = 0; i < serviceWorkers.length; i++) {
+      let sw = serviceWorkers.queryElementAt(i, Ci.nsIServiceWorkerRegistrationInfo);
+      if (sw.principal.URI.host == aHost) {
+        promises.push(unregisterServiceWorker(sw));
+      }
+    }
+    return Promise.all(promises);
+  },
+
+  removeFromPrincipal(aPrincipal) {
+    let promises = [];
+    let serviceWorkers = serviceWorkerManager.getAllRegistrations();
+    for (let i = 0; i < serviceWorkers.length; i++) {
+      let sw = serviceWorkers.queryElementAt(i, Ci.nsIServiceWorkerRegistrationInfo);
+      if (sw.principal.equals(aPrincipal)) {
+        promises.push(unregisterServiceWorker(sw));
+      }
+    }
+    return Promise.all(promises);
+  },
+
+  removeAll() {
+    let promises = [];
+    let serviceWorkers = serviceWorkerManager.getAllRegistrations();
+    for (let i = 0; i < serviceWorkers.length; i++) {
+      let sw = serviceWorkers.queryElementAt(i, Ci.nsIServiceWorkerRegistrationInfo);
+      promises.push(unregisterServiceWorker(sw));
+    }
+    return Promise.all(promises);
+  },
+};
--- a/toolkit/forgetaboutsite/moz.build
+++ b/toolkit/forgetaboutsite/moz.build
@@ -4,12 +4,13 @@
 # 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/.
 
 BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
 XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
 
 EXTRA_JS_MODULES += [
     'ForgetAboutSite.jsm',
+    'ServiceWorkerCleanUp.jsm',
 ]
 
 with Files('**'):
     BUG_COMPONENT = ('Toolkit', 'Forget About Site')
--- a/xpcom/threads/PrioritizedEventQueue.cpp
+++ b/xpcom/threads/PrioritizedEventQueue.cpp
@@ -180,17 +180,17 @@ PrioritizedEventQueue<InnerQueueT>::Sele
   return queue;
 }
 
 template<class InnerQueueT>
 already_AddRefed<nsIRunnable>
 PrioritizedEventQueue<InnerQueueT>::GetEvent(EventPriority* aPriority,
                                              const MutexAutoLock& aProofOfLock)
 {
-  MakeScopeExit([&] {
+  auto guard = MakeScopeExit([&] {
     mHasPendingEventsPromisedIdleEvent = false;
   });
 
 #ifndef RELEASE_OR_BETA
   // Clear mNextIdleDeadline so that it is possible to determine that
   // we're running an idle runnable in ProcessNextEvent.
   *mNextIdleDeadline = TimeStamp();
 #endif