Merge mozilla-central to inbound. a=merge CLOSED TREE
authorGurzau Raul <rgurzau@mozilla.com>
Wed, 10 Oct 2018 07:43:59 +0300
changeset 496227 436a8eff6195462f6df195f3dcbbb3fbfe5a7e6b
parent 496226 076aed819da6fe4a762fe294937afea76f888b0b (current diff)
parent 496141 bf31de5be0dcd71f3257485c66db5627ec3ed205 (diff)
child 496228 89e092c992ae26d44f3956ffd1c850a8bfcbed9f
push id9984
push userffxbld-merge
push dateMon, 15 Oct 2018 21:07:35 +0000
treeherdermozilla-beta@183d27ea8570 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to inbound. a=merge CLOSED TREE
Pipfile
Pipfile.lock
dom/notification/test/unit/common_test_notificationdb.js
testing/web-platform/meta/css/css-contain/contain-layout-018.html.ini
testing/web-platform/meta/css/css-contain/contain-paint-025.html.ini
toolkit/components/antitracking/test/browser/browser_blockingStorage.js
--- a/.eslintignore
+++ b/.eslintignore
@@ -207,17 +207,16 @@ dom/media/test/**
 dom/media/tests/**
 dom/media/webaudio/**
 dom/media/webspeech/**
 dom/messagechannel/**
 dom/midi/**
 dom/network/**
 dom/notification/Notification*.*
 dom/notification/test/browser/**
-dom/notification/test/unit/**
 dom/notification/test/mochitest/**
 dom/offline/**
 dom/payments/**
 dom/performance/**
 dom/permission/**
 dom/plugins/test/mochitest/**
 dom/plugins/test/unit/**
 dom/power/**
--- a/browser/components/enterprisepolicies/Policies.jsm
+++ b/browser/components/enterprisepolicies/Policies.jsm
@@ -193,16 +193,36 @@ var Policies = {
           setAndLockPref("network.cookie.lifetimePolicy", newLifetimePolicy);
         } else {
           setDefaultPref("network.cookie.lifetimePolicy", newLifetimePolicy);
         }
       }
     },
   },
 
+  "DNSOverHTTPS": {
+    onBeforeAddons(manager, param) {
+      if ("Enabled" in param) {
+        let mode = param.Enabled ? 2 : 5;
+        if (param.Locked) {
+          setAndLockPref("network.trr.mode", mode);
+        } else {
+          setDefaultPref("network.trr.mode", mode);
+        }
+      }
+      if (param.ProviderURL) {
+        if (param.Locked) {
+          setAndLockPref("network.trr.uri", param.ProviderURL.href);
+        } else {
+          setDefaultPref("network.trr.uri", param.ProviderURL.href);
+        }
+      }
+    },
+  },
+
   "DisableAppUpdate": {
     onBeforeAddons(manager, param) {
       if (param) {
         manager.disallowFeature("appUpdate");
       }
     },
   },
 
@@ -641,16 +661,22 @@ var Policies = {
         manager.disallowFeature("changeProxySettings");
         ProxyPolicies.configureProxySettings(param, setAndLockPref);
       } else {
         ProxyPolicies.configureProxySettings(param, setDefaultPref);
       }
     },
   },
 
+  "RequestedLocales": {
+    onBeforeAddons(manager, param) {
+      Services.locale.requestedLocales = param;
+    },
+  },
+
   "SanitizeOnShutdown": {
     onBeforeUIStartup(manager, param) {
       setAndLockPref("privacy.sanitize.sanitizeOnShutdown", param);
       if (param) {
         setAndLockPref("privacy.clearOnShutdown.cache", true);
         setAndLockPref("privacy.clearOnShutdown.cookies", true);
         setAndLockPref("privacy.clearOnShutdown.downloads", true);
         setAndLockPref("privacy.clearOnShutdown.formdata", true);
--- a/browser/components/enterprisepolicies/schemas/policies-schema.json
+++ b/browser/components/enterprisepolicies/schemas/policies-schema.json
@@ -136,16 +136,31 @@
         },
 
         "Locked": {
           "type": "boolean"
         }
       }
     },
 
+    "DNSOverHTTPS": {
+      "type": "object",
+      "properties": {
+        "Enabled": {
+          "type": "boolean"
+        },
+        "ProviderURL": {
+          "type": "URLorEmpty"
+        },
+        "Locked": {
+          "type": "boolean"
+        }
+      }
+    },
+
     "DisableAppUpdate": {
       "machine_only": true,
 
       "type": "boolean"
     },
 
     "DisableBuiltinPDFViewer": {
       "type": "boolean"
@@ -566,16 +581,23 @@
         },
 
         "AutoLogin": {
           "type": "boolean"
         }
       }
     },
 
+    "RequestedLocales": {
+      "type": "array",
+      "items": {
+        "type": "string"
+      }
+    },
+
     "SanitizeOnShutdown": {
       "type": "boolean"
     },
 
     "SearchBar": {
       "type": "string",
       "enum": ["unified", "separate"]
     },
--- a/browser/components/enterprisepolicies/tests/browser/browser.ini
+++ b/browser/components/enterprisepolicies/tests/browser/browser.ini
@@ -44,15 +44,16 @@ skip-if = (verify && debug && (os == 'ma
 [browser_policy_disable_profile_reset.js]
 [browser_policy_disable_profile_import.js]
 [browser_policy_disable_safemode.js]
 [browser_policy_disable_shield.js]
 [browser_policy_disable_telemetry.js]
 [browser_policy_display_bookmarks.js]
 [browser_policy_display_menu.js]
 [browser_policy_extensions.js]
+[browser_policy_locale.js]
 [browser_policy_override_postupdatepage.js]
 [browser_policy_permissions.js]
 [browser_policy_proxy.js]
 [browser_policy_search_engine.js]
 [browser_policy_searchbar.js]
 [browser_policy_set_homepage.js]
 [browser_policy_websitefilter.js]
--- a/browser/components/enterprisepolicies/tests/browser/browser_policies_simple_pref_policies.js
+++ b/browser/components/enterprisepolicies/tests/browser/browser_policies_simple_pref_policies.js
@@ -149,16 +149,45 @@ const POLICIES_TESTS = [
       "privacy.clearOnShutdown.downloads": true,
       "privacy.clearOnShutdown.formdata": true,
       "privacy.clearOnShutdown.history": true,
       "privacy.clearOnShutdown.sessions": true,
       "privacy.clearOnShutdown.siteSettings": true,
       "privacy.clearOnShutdown.offlineApps": true,
     },
   },
+
+  // POLICY: DNSOverHTTPS Locked
+  {
+    policies: {
+      "DNSOverHTTPS": {
+        "Enabled": true,
+        "ProviderURL": "http://example.com/provider",
+        "Locked": true,
+      },
+    },
+    lockedPrefs: {
+      "network.trr.mode": 2,
+      "network.trr.uri": "http://example.com/provider",
+    },
+  },
+
+  // POLICY: DNSOverHTTPS Unlocked
+  {
+    policies: {
+      "DNSOverHTTPS": {
+        "Enabled": false,
+        "ProviderURL": "http://example.com/provider",
+      },
+    },
+    unlockedPrefs: {
+      "network.trr.mode": 5,
+      "network.trr.uri": "http://example.com/provider",
+    },
+  },
 ];
 
 add_task(async function test_policy_remember_passwords() {
   for (let test of POLICIES_TESTS) {
     await setupPolicyEngineWithJson({
       "policies": test.policies,
     });
 
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_locale.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const REQ_LOC_CHANGE_EVENT = "intl:requested-locales-changed";
+
+function promiseLocaleChanged() {
+  return new Promise(resolve => {
+    let localeObserver = {
+      observe(aSubject, aTopic, aData) {
+        switch (aTopic) {
+          case REQ_LOC_CHANGE_EVENT:
+            let reqLocs = Services.locale.requestedLocales;
+            is(reqLocs[0], "de");
+            Services.obs.removeObserver(localeObserver, REQ_LOC_CHANGE_EVENT);
+            resolve();
+        }
+      },
+    };
+    Services.obs.addObserver(localeObserver, REQ_LOC_CHANGE_EVENT);
+  });
+}
+
+add_task(async function test_requested_locale() {
+  let localePromise = promiseLocaleChanged();
+  await setupPolicyEngineWithJson({
+    "policies": {
+      "RequestedLocales": ["de"],
+    },
+  });
+  await localePromise;
+});
new file mode 100644
--- /dev/null
+++ b/browser/config/mozconfigs/win32/mingwclang
@@ -0,0 +1,79 @@
+# Sets:
+#    MOZ_AUTOMATION flags
+#    SOCORRO_SYMBOL_UPLOAD_TOKEN_FILE - shouldn't be used?
+#    TOOLTOOL_DIR
+#    MAKECAB - shouldn't be used?
+. "$topsrcdir/build/mozconfig.win-common"
+
+# MinGW does not have (or need) makecab
+unset MAKECAB
+
+# Sets:
+#  build/mozconfig.common
+#    AUTOCLOBBER=1
+#    --enable-crashreporter
+#    --enable-release
+#    LLVM_CONFIG
+#    MOZ_ADDON_SIGNING
+#    MOZ_REQUIRE_SIGNING
+#    --enable-js-shell
+#  build/mozconfig.automation
+#    MOZ_AUTOMATION_ flags
+#  build/mozconfig.rust
+#    TOOLTOOL_DIR
+#    RUSTC
+#    CARGO
+. "$topsrcdir/browser/config/mozconfigs/common"
+
+# MinGW Stuff
+ac_add_options --target=i686-w64-mingw32
+ac_add_options --with-toolchain-prefix=i686-w64-mingw32-
+
+ac_add_options --disable-warnings-as-errors
+
+# Temporary config settings until we get these working on mingw
+ac_add_options --disable-accessibility # https://sourceforge.net/p/mingw-w64/bugs/648/
+
+# For now, we'll disable the sandbox, until we get get Bug 1461421 figured out
+ac_add_options --disable-sandbox
+
+# These aren't supported on mingw at this time
+ac_add_options --disable-maintenance-service
+ac_add_options --disable-webrtc # Bug 1393901
+ac_add_options --disable-geckodriver # Bug 1489320
+
+# Find our toolchain
+HOST_CC="$TOOLTOOL_DIR/clang/bin/clang"
+HOST_CXX="$TOOLTOOL_DIR/clang/bin/clang++"
+CC="$TOOLTOOL_DIR/clang/bin/i686-w64-mingw32-clang"
+CXX="$TOOLTOOL_DIR/clang/bin/i686-w64-mingw32-clang++"
+CXXFLAGS="-fms-extensions"
+CPP="$TOOLTOOL_DIR/clang/bin/i686-w64-mingw32-clang -E"
+AR=llvm-ar
+RANLIB=llvm-ranlib
+
+# For Stylo
+BINDGEN_CFLAGS="-I$TOOLTOOL_DIR/clang/i686-w64-mingw32/include/c++/v1 -I$TOOLTOOL_DIR/clang/lib/clang/7.0.0/include -I$TOOLTOOL_DIR/clang/i686-w64-mingw32/include"
+
+# Since we use windres from binutils without the rest of tools (like cpp), we need to
+# explicitly specify clang as a preprocessor.
+WINDRES="i686-w64-mingw32-windres --preprocessor=\"$CPP -xc\" -DRC_INVOKED"
+
+# Bug 1471698 - Work around binutils corrupting mingw clang binaries.
+LDFLAGS="-Wl,-S"
+STRIP=/bin/true
+OBJCOPY=/bin/true
+
+# We want to make sure we use binutils and other binaries in the tooltool
+# package.
+mk_add_options "export PATH=$TOOLTOOL_DIR/clang/bin:$TOOLTOOL_DIR/mingw32/bin:$TOOLTOOL_DIR/wine/bin:$TOOLTOOL_DIR/upx/bin:$TOOLTOOL_DIR/fxc2/bin:$PATH"
+
+LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}$TOOLTOOL_DIR/mingw32/lib64:$TOOLTOOL_DIR/clang/lib
+mk_add_options "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH"
+
+# Do not include the visual studio related mozconfigs of course
+
+ac_add_options --with-branding=browser/branding/nightly
+
+. "$topsrcdir/build/mozconfig.common.override"
+. "$topsrcdir/build/mozconfig.cache"
new file mode 100644
--- /dev/null
+++ b/browser/config/mozconfigs/win32/mingwclang-debug
@@ -0,0 +1,6 @@
+MOZ_AUTOMATION_L10N_CHECK=0
+
+. "$topsrcdir/browser/config/mozconfigs/win32/mingwclang"
+
+ac_add_options --enable-debug
+ac_add_options --disable-optimize
\ No newline at end of file
--- a/browser/locales/en-US/browser/policies/policies-descriptions.ftl
+++ b/browser/locales/en-US/browser/policies/policies-descriptions.ftl
@@ -65,16 +65,18 @@ policy-DisableSetAsDesktopBackground = D
 policy-DisableSystemAddonUpdate = Prevent the browser from installing and updating system add-ons.
 
 policy-DisableTelemetry = Turn off Telemetry.
 
 policy-DisplayBookmarksToolbar = Display the Bookmarks Toolbar by default.
 
 policy-DisplayMenuBar = Display the Menu Bar by default.
 
+policy-DNSOverHTTPS = Configure DNS over HTTPS.
+
 policy-DontCheckDefaultBrowser = Disable check for default browser on startup.
 
 # “lock” means that the user won’t be able to change this setting
 policy-EnableTrackingProtection = Enable or disable Content Blocking and optionally lock it.
 
 # A “locked” extension can’t be disabled or removed by the user. This policy
 # takes 3 keys (“Install”, ”Uninstall”, ”Locked”), you can either keep them in
 # English or translate them as verbs. See also:
@@ -99,16 +101,18 @@ policy-OverrideFirstRunPage = Override t
 policy-OverridePostUpdatePage = Override the post-update “What’s New” page. Set this policy to blank if you want to disable the post-update page.
 
 policy-Permissions = Configure permissions for camera, microphone, location and notifications.
 
 policy-PopupBlocking = Allow certain websites to display popups by default.
 
 policy-Proxy = Configure proxy settings.
 
+policy-RequestedLocales = Set the list of requested locales for the application in order of preference.
+
 policy-SanitizeOnShutdown = Clear all navigation data on shutdown.
 
 policy-SearchBar = Set the default location of the search bar. The user is still allowed to customize it.
 
 policy-SearchEngines = Configure search engine settings. This policy is only available on the Extended Support Release (ESR) version.
 
 # For more information, see https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Installation
 policy-SecurityDevices = Install PKCS #11 modules.
--- a/client.mk
+++ b/client.mk
@@ -150,22 +150,16 @@ endif
 build::  $(OBJDIR)/Makefile $(OBJDIR)/config.status
 	+$(MOZ_MAKE)
 
 ifdef MOZ_AUTOMATION
 build::
 	+$(MOZ_MAKE) automation/build
 endif
 
-ifdef MOZBUILD_MANAGE_SCCACHE_DAEMON
-build::
-	# Terminate sccache server. This prints sccache stats.
-	-$(MOZBUILD_MANAGE_SCCACHE_DAEMON) --stop-server
-endif
-
 # This makefile doesn't support parallel execution. It does pass
 # MOZ_MAKE_FLAGS to sub-make processes, so they will correctly execute
 # in parallel.
 .NOTPARALLEL:
 
 .PHONY: \
     build \
     configure
--- a/devtools/client/debugger/test/mochitest/head.js
+++ b/devtools/client/debugger/test/mochitest/head.js
@@ -164,17 +164,17 @@ function getTargetActorForUrl(aClient, a
 
   return deferred.promise;
 }
 
 function getAddonActorForId(aClient, aAddonId) {
   info("Get addon actor for ID: " + aAddonId);
   let deferred = promise.defer();
 
-  aClient.listAddons(aResponse => {
+  aClient.listAddons().then(aResponse => {
     let addonTargetActor = aResponse.addons.filter(aGrip => aGrip.id == aAddonId).pop();
     info("got addon actor for ID: " + aAddonId);
     deferred.resolve(addonTargetActor);
   });
 
   return deferred.promise;
 }
 
--- a/devtools/client/framework/target.js
+++ b/devtools/client/framework/target.js
@@ -281,33 +281,29 @@ TabTarget.prototype = {
    *       "substring": {
    *         "_retval": "primitive"
    *       }
    *     }
    *   }],
    *  "events": {}
    * }
    */
-  getActorDescription: function(actorName) {
+  getActorDescription: async function(actorName) {
     if (!this.client) {
       throw new Error("TabTarget#getActorDescription() can only be called on " +
                       "remote tabs.");
     }
 
-    return new Promise(resolve => {
-      if (this._protocolDescription &&
-          this._protocolDescription.types[actorName]) {
-        resolve(this._protocolDescription.types[actorName]);
-      } else {
-        this.client.mainRoot.protocolDescription(description => {
-          this._protocolDescription = description;
-          resolve(description.types[actorName]);
-        });
-      }
-    });
+    if (this._protocolDescription &&
+        this._protocolDescription.types[actorName]) {
+      return this._protocolDescription.types[actorName];
+    }
+    const description = await this.client.mainRoot.protocolDescription();
+    this._protocolDescription = description;
+    return description.types[actorName];
   },
 
   /**
    * Returns a boolean indicating whether or not the specific actor
    * type exists. Must be a remote target.
    *
    * @param {String} actorName
    * @return {Boolean}
--- a/devtools/server/actors/addon/webextension.js
+++ b/devtools/server/actors/addon/webextension.js
@@ -37,17 +37,17 @@ loader.lazyImporter(this, "ExtensionPare
  *        The connection to the client.
  * @param {AddonWrapper} addon
  *        The target addon.
  */
 const WebExtensionActor = protocol.ActorClassWithSpec(webExtensionSpec, {
   initialize(conn, addon) {
     this.conn = conn;
     this.addon = addon;
-    this.id = addon.id;
+    this.addonId = addon.id;
     this._childFormPromise = null;
 
     AddonManager.addAddonListener(this);
   },
 
   destroy() {
     AddonManager.removeAddonListener(this);
 
@@ -67,31 +67,31 @@ const WebExtensionActor = protocol.Actor
 
   reload() {
     return this.addon.reload().then(() => {
       return {};
     });
   },
 
   form() {
-    const policy = ExtensionParent.WebExtensionPolicy.getByID(this.id);
+    const policy = ExtensionParent.WebExtensionPolicy.getByID(this.addonId);
     return {
       actor: this.actorID,
-      id: this.id,
+      id: this.addonId,
       name: this.addon.name,
       url: this.addon.sourceURI ? this.addon.sourceURI.spec : undefined,
       iconURL: this.addon.iconURL,
       isSystem: this.addon.isSystem,
       debuggable: this.addon.isDebuggable,
       temporarilyInstalled: this.addon.temporarilyInstalled,
       type: this.addon.type,
       isWebExtension: this.addon.isWebExtension,
       isAPIExtension: this.addon.isAPIExtension,
       manifestURL: policy && policy.getURL("manifest.json"),
-      warnings: ExtensionParent.DebugUtils.getExtensionManifestWarnings(this.id),
+      warnings: ExtensionParent.DebugUtils.getExtensionManifestWarnings(this.addonId),
     };
   },
 
   connect() {
     if (this._childFormPormise) {
       return this._childFormPromise;
     }
 
@@ -119,17 +119,17 @@ const WebExtensionActor = protocol.Actor
     // if the child actor exits.
     this._childFormPromise = null;
     delete this._destroyProxy;
   },
 
   // AddonManagerListener callbacks.
 
   onInstalled(addon) {
-    if (addon.id != this.id) {
+    if (addon.id != this.addonId) {
       return;
     }
 
     // Update the AddonManager's addon object on reload/update.
     this.addon = addon;
   },
 
   onUninstalled(addon) {
@@ -141,17 +141,17 @@ const WebExtensionActor = protocol.Actor
   },
 });
 
 exports.WebExtensionActor = WebExtensionActor;
 
 function WebExtensionTargetActorProxy(connection, parentActor) {
   this._conn = connection;
   this._parentActor = parentActor;
-  this.addonId = parentActor.id;
+  this.addonId = parentActor.addonId;
 
   this._onChildExit = this._onChildExit.bind(this);
 
   this._form = null;
   this._browser = null;
   this._childActorID = null;
 }
 
--- a/devtools/server/actors/targets/addon.js
+++ b/devtools/server/actors/targets/addon.js
@@ -45,17 +45,17 @@ exports.AddonTargetActor = AddonTargetAc
 
 AddonTargetActor.prototype = {
   actorPrefix: "addonTarget",
 
   get exited() {
     return !this._addon;
   },
 
-  get id() {
+  get addonId() {
     return this._addon.id;
   },
 
   get url() {
     return this._addon.sourceURI ? this._addon.sourceURI.spec : undefined;
   },
 
   get attached() {
@@ -78,17 +78,17 @@ AddonTargetActor.prototype = {
     assert(this.actorID, "addon should have an actorID.");
     if (!this._consoleActor) {
       this._consoleActor = new AddonConsoleActor(this._addon, this.conn, this);
       this._contextPool.addActor(this._consoleActor);
     }
 
     return {
       actor: this.actorID,
-      id: this.id,
+      id: this.addonId,
       name: this._addon.name,
       url: this.url,
       iconURL: this._addon.iconURL,
       isSystem: this._addon.isSystem,
       debuggable: this._addon.isDebuggable,
       temporarilyInstalled: this._addon.temporarilyInstalled,
       type: this._addon.type,
       isWebExtension: this._addon.isWebExtension,
@@ -203,24 +203,24 @@ AddonTargetActor.prototype = {
    * added as a debuggee, false otherwise.
    */
   _shouldAddNewGlobalAsDebuggee: function(givenGlobal) {
     const global = unwrapDebuggerObjectGlobal(givenGlobal);
     try {
       // This will fail for non-Sandbox objects, hence the try-catch block.
       const metadata = Cu.getSandboxMetadata(global);
       if (metadata) {
-        return metadata.addonID === this.id;
+        return metadata.addonID === this.addonId;
       }
     } catch (e) {
       // ignore
     }
 
     if (global instanceof Ci.nsIDOMWindow) {
-      return global.document.nodePrincipal.addonId == this.id;
+      return global.document.nodePrincipal.addonId == this.addonId;
     }
 
     return false;
   },
 
   /**
    * Override the eligibility check for scripts and sources to make
    * sure every script and source with a URL is stored when debugging
--- a/devtools/server/actors/targets/webextension.js
+++ b/devtools/server/actors/targets/webextension.js
@@ -70,17 +70,17 @@ const webExtensionTargetPrototype = exte
  *        The chromeGlobal where this actor has been injected by the
  *        DebuggerServer.connectToFrame method.
  * @param {string} prefix
  *        the custom RDP prefix to use.
  * @param {string} addonId
  *        the addonId of the target WebExtension.
  */
 webExtensionTargetPrototype.initialize = function(conn, chromeGlobal, prefix, addonId) {
-  this.id = addonId;
+  this.addonId = addonId;
 
   // Try to discovery an existent extension page to attach (which will provide the initial
   // URL shown in the window tittle when the addon debugger is opened).
   let extensionWindow = this._searchForExtensionWindow();
   if (!extensionWindow) {
     this._createFallbackWindow();
     extensionWindow = this.fallbackWindow;
   }
@@ -106,17 +106,17 @@ webExtensionTargetPrototype.initialize =
   this._allowSource = this._allowSource.bind(this);
   this._onParentExit = this._onParentExit.bind(this);
 
   this._chromeGlobal.addMessageListener("debug:webext_parent_exit", this._onParentExit);
 
   // Set the consoleAPIListener filtering options
   // (retrieved and used in the related webconsole child actor).
   this.consoleAPIListenerOptions = {
-    addonId: this.id,
+    addonId: this.addonId,
   };
 
   this.aps = Cc["@mozilla.org/addons/policy-service;1"]
                .getService(Ci.nsIAddonPolicyService);
 
   // This creates a Debugger instance for debugging all the add-on globals.
   this.makeDebugger = makeDebugger.bind(null, {
     findDebuggees: dbg => {
@@ -142,17 +142,17 @@ webExtensionTargetPrototype.exit = funct
     chromeGlobal.removeMessageListener("debug:webext_parent_exit", this._onParentExit);
 
     chromeGlobal.sendAsyncMessage("debug:webext_child_exit", {
       actor: this.actorID
     });
   }
 
   this.addon = null;
-  this.id = null;
+  this.addonId = null;
 
   return ParentProcessTargetActor.prototype.exit.apply(this);
 };
 
 // Private helpers.
 
 webExtensionTargetPrototype._createFallbackWindow = function() {
   if (this.fallbackWindow) {
@@ -185,17 +185,17 @@ webExtensionTargetPrototype._destroyFall
 };
 
 // Discovery an extension page to use as a default target window.
 // NOTE: This currently fail to discovery an extension page running in a
 // windowless browser when running in non-oop mode, and the background page
 // is set later using _onNewExtensionWindow.
 webExtensionTargetPrototype._searchForExtensionWindow = function() {
   for (const window of Services.ww.getWindowEnumerator(null)) {
-    if (window.document.nodePrincipal.addonId == this.id) {
+    if (window.document.nodePrincipal.addonId == this.addonId) {
       return window;
     }
   }
 
   return undefined;
 };
 
 // Customized ParentProcessTargetActor/BrowsingContextTargetActor hooks.
@@ -225,17 +225,17 @@ webExtensionTargetPrototype._onNewExtens
   }
 };
 
 webExtensionTargetPrototype._attach = function() {
   // NOTE: we need to be sure that `this.window` can return a window before calling the
   // ParentProcessTargetActor.onAttach, or the BrowsingContextTargetActor will not be
   // subscribed to the child doc shell updates.
 
-  if (!this.window || this.window.document.nodePrincipal.addonId !== this.id) {
+  if (!this.window || this.window.document.nodePrincipal.addonId !== this.addonId) {
     // Discovery an existent extension page to attach.
     const extensionWindow = this._searchForExtensionWindow();
 
     if (!extensionWindow) {
       this._createFallbackWindow();
       this._setWindow(this.fallbackWindow);
     } else {
       this._setWindow(extensionWindow);
@@ -282,23 +282,23 @@ webExtensionTargetPrototype._docShellToW
 /**
  * Return an array of the json details related to an array/iterator of docShells.
  */
 webExtensionTargetPrototype._docShellsToWindows = function(docshells) {
   return ParentProcessTargetActor.prototype._docShellsToWindows.call(this, docshells)
                     .filter(windowDetails => {
                       // Filter the docShells based on the addon id of the window or
                       // its sameType top level frame.
-                      return windowDetails.addonID === this.id ||
-                             windowDetails.sameTypeRootAddonID === this.id;
+                      return windowDetails.addonID === this.addonId ||
+                             windowDetails.sameTypeRootAddonID === this.addonId;
                     });
 };
 
 webExtensionTargetPrototype.isExtensionWindow = function(window) {
-  return window.document.nodePrincipal.addonId == this.id;
+  return window.document.nodePrincipal.addonId == this.addonId;
 };
 
 webExtensionTargetPrototype.isExtensionWindowDescendent = function(window) {
   // Check if the source is coming from a descendant docShell of an extension window.
   const rootWin = window.docShell.sameTypeRootTreeItem.domWindow;
   return this.isExtensionWindow(rootWin);
 };
 
@@ -337,17 +337,17 @@ webExtensionTargetPrototype._allowSource
   // Filter out resource and chrome sources (which are related to the loaded internals).
   if (["resource", "chrome", "file"].includes(uri.scheme)) {
     return false;
   }
 
   try {
     const addonID = this.aps.extensionURIToAddonId(uri);
 
-    return addonID == this.id;
+    return addonID == this.addonId;
   } catch (err) {
     // extensionURIToAddonId raises an exception on non-extension URLs.
     return false;
   }
 };
 
 /**
  * Return true if the given global is associated with this addon and should be
@@ -380,17 +380,17 @@ webExtensionTargetPrototype._shouldAddNe
     return global.document.ownerGlobal &&
            this.isExtensionWindowDescendent(global.document.ownerGlobal);
   }
 
   try {
     // This will fail for non-Sandbox objects, hence the try-catch block.
     const metadata = Cu.getSandboxMetadata(global);
     if (metadata) {
-      return metadata.addonID === this.id;
+      return metadata.addonID === this.addonId;
     }
   } catch (e) {
     // Unable to retrieve the sandbox metadata.
   }
 
   return false;
 };
 
--- a/devtools/server/main.js
+++ b/devtools/server/main.js
@@ -1211,17 +1211,17 @@ DebuggerServerConnection.prototype = {
    */
   setAddonOptions(id, options) {
     const addonList = this.rootActor._parameters.addonList;
     if (!addonList) {
       return Promise.resolve();
     }
     return addonList.getList().then((addonTargetActors) => {
       for (const actor of addonTargetActors) {
-        if (actor.id != id) {
+        if (actor.addonId != id) {
           continue;
         }
         actor.setOptions(options);
         return;
       }
     });
   },
 
--- a/devtools/server/tests/mochitest/test_getProcess.html
+++ b/devtools/server/tests/mochitest/test_getProcess.html
@@ -56,17 +56,17 @@ function runTests() {
 
   function listProcess() {
     // Call listProcesses in order to start receiving new process notifications
     client.addListener("processListChanged", function listener() {
       client.removeListener("processListChanged", listener);
       ok(true, "Received processListChanged event");
       getProcess();
     });
-    client.mainRoot.listProcesses(response => {
+    client.mainRoot.listProcesses().then(response => {
       processCount = response.processes.length;
       // Create a remote iframe to spawn a new process
       createRemoteIframe();
     });
   }
 
   function createRemoteIframe() {
     iframe = document.createElement("iframe");
--- a/devtools/shared/client/debugger-client.js
+++ b/devtools/shared/client/debugger-client.js
@@ -321,26 +321,26 @@ DebuggerClient.prototype = {
 
     return deferred.promise;
   },
 
   /*
    * This function exists only to preserve DebuggerClient's interface;
    * new code should say 'client.mainRoot.listTabs()'.
    */
-  listTabs: function(options, onResponse) {
-    return this.mainRoot.listTabs(options, onResponse);
+  listTabs: function(options) {
+    return this.mainRoot.listTabs(options);
   },
 
   /*
    * This function exists only to preserve DebuggerClient's interface;
    * new code should say 'client.mainRoot.listAddons()'.
    */
-  listAddons: function(onResponse) {
-    return this.mainRoot.listAddons(onResponse);
+  listAddons: function() {
+    return this.mainRoot.listAddons();
   },
 
   getTab: function(filter) {
     return this.mainRoot.getTab(filter);
   },
 
   /**
    * Attach to a target actor:
--- a/docshell/base/LoadContext.cpp
+++ b/docshell/base/LoadContext.cpp
@@ -143,22 +143,20 @@ LoadContext::GetIsInIsolatedMozBrowserEl
 
   NS_ENSURE_ARG_POINTER(aIsInIsolatedMozBrowserElement);
 
   *aIsInIsolatedMozBrowserElement = mOriginAttributes.mInIsolatedMozBrowser;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-LoadContext::GetScriptableOriginAttributes(JS::MutableHandleValue aAttrs)
+LoadContext::GetScriptableOriginAttributes(JSContext* aCx,
+                                           JS::MutableHandleValue aAttrs)
 {
-  JSContext* cx = nsContentUtils::GetCurrentJSContext();
-  MOZ_ASSERT(cx);
-
-  bool ok = ToJSValue(cx, mOriginAttributes, aAttrs);
+  bool ok = ToJSValue(aCx, mOriginAttributes, aAttrs);
   NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
   return NS_OK;
 }
 
 NS_IMETHODIMP_(void)
 LoadContext::GetOriginAttributes(mozilla::OriginAttributes& aAttrs)
 {
   aAttrs = mOriginAttributes;
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -13699,22 +13699,20 @@ nsDocShell::GetIsTopLevelContentDocShell
     *aIsTopLevelContentDocShell = root.get() == static_cast<nsIDocShellTreeItem*>(this);
   }
 
   return NS_OK;
 }
 
 // Implements nsILoadContext.originAttributes
 NS_IMETHODIMP
-nsDocShell::GetScriptableOriginAttributes(JS::MutableHandle<JS::Value> aVal)
-{
-  JSContext* cx = nsContentUtils::GetCurrentJSContext();
-  MOZ_ASSERT(cx);
-
-  return GetOriginAttributes(cx, aVal);
+nsDocShell::GetScriptableOriginAttributes(JSContext* aCx,
+                                          JS::MutableHandle<JS::Value> aVal)
+{
+  return GetOriginAttributes(aCx, aVal);
 }
 
 // Implements nsIDocShell.GetOriginAttributes()
 NS_IMETHODIMP
 nsDocShell::GetOriginAttributes(JSContext* aCx,
                                 JS::MutableHandle<JS::Value> aVal)
 {
   bool ok = ToJSValue(aCx, mOriginAttributes, aVal);
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -225,17 +225,18 @@ public:
   NS_IMETHOD GetTopFrameElement(mozilla::dom::Element**) override;
   NS_IMETHOD GetNestedFrameId(uint64_t*) override;
   NS_IMETHOD GetIsContent(bool*) override;
   NS_IMETHOD GetUsePrivateBrowsing(bool*) override;
   NS_IMETHOD SetUsePrivateBrowsing(bool) override;
   NS_IMETHOD SetPrivateBrowsing(bool) override;
   NS_IMETHOD GetUseRemoteTabs(bool*) override;
   NS_IMETHOD SetRemoteTabs(bool) override;
-  NS_IMETHOD GetScriptableOriginAttributes(JS::MutableHandle<JS::Value>) override;
+  NS_IMETHOD GetScriptableOriginAttributes(JSContext*,
+                                           JS::MutableHandle<JS::Value>) override;
   NS_IMETHOD_(void) GetOriginAttributes(mozilla::OriginAttributes& aAttrs) override;
 
   // Restores a cached presentation from history (mLSHE).
   // This method swaps out the content viewer and simulates loads for
   // subframes. It then simulates the completion of the toplevel load.
   nsresult RestoreFromHistory();
 
   // Perform a URI load from a refresh timer. This is just like the
--- a/docshell/base/nsILoadContext.idl
+++ b/docshell/base/nsILoadContext.idl
@@ -131,16 +131,16 @@ interface nsILoadContext : nsISupports
    * containing document is chrome.
    */
   readonly attribute boolean isInIsolatedMozBrowserElement;
 
   /**
    * A dictionary of the non-default origin attributes associated with this
    * nsILoadContext.
    */
-  [binaryname(ScriptableOriginAttributes)]
+  [binaryname(ScriptableOriginAttributes), implicit_jscontext]
   readonly attribute jsval originAttributes;
 
   /**
    * The C++ getter for origin attributes.
    */
   [noscript, notxpcom] void GetOriginAttributes(out OriginAttributes aAttrs);
 };
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -13722,17 +13722,19 @@ already_AddRefed<mozilla::dom::Promise>
 nsIDocument::RequestStorageAccess(mozilla::ErrorResult& aRv)
 {
   nsIGlobalObject* global = GetScopeObject();
   if (!global) {
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return nullptr;
   }
 
-  RefPtr<Promise> promise = Promise::Create(global, aRv);
+  // Propagate user input event handling to the resolve handler
+  RefPtr<Promise> promise = Promise::Create(global, aRv,
+                                            Promise::ePropagateUserInteraction);
   if (aRv.Failed()) {
     return nullptr;
   }
 
   // Step 1. If the document already has been granted access, resolve.
   nsPIDOMWindowInner* inner = GetInnerWindow();
   nsGlobalWindowOuter* outer = nullptr;
   if (inner) {
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -3300,17 +3300,17 @@ public:
     return NS_OK;
   }
   NS_IMETHOD GetNestedFrameId(uint64_t*) NO_IMPL
   NS_IMETHOD GetIsContent(bool*) NO_IMPL
   NS_IMETHOD GetUsePrivateBrowsing(bool*) NO_IMPL
   NS_IMETHOD SetUsePrivateBrowsing(bool) NO_IMPL
   NS_IMETHOD SetPrivateBrowsing(bool) NO_IMPL
   NS_IMETHOD GetIsInIsolatedMozBrowserElement(bool*) NO_IMPL
-  NS_IMETHOD GetScriptableOriginAttributes(JS::MutableHandleValue) NO_IMPL
+  NS_IMETHOD GetScriptableOriginAttributes(JSContext*, JS::MutableHandleValue) NO_IMPL
   NS_IMETHOD_(void) GetOriginAttributes(mozilla::OriginAttributes& aAttrs) override {}
   NS_IMETHOD GetUseRemoteTabs(bool*) NO_IMPL
   NS_IMETHOD SetRemoteTabs(bool) NO_IMPL
   NS_IMETHOD GetUseTrackingProtection(bool*) NO_IMPL
   NS_IMETHOD SetUseTrackingProtection(bool) NO_IMPL
 #undef NO_IMPL
 
 protected:
--- a/dom/media/ipc/PVideoDecoderManager.ipdl
+++ b/dom/media/ipc/PVideoDecoderManager.ipdl
@@ -4,31 +4,33 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 include protocol PTexture;
 include protocol PVideoDecoder;
 include LayersSurfaces;
 include "mozilla/dom/MediaIPCUtils.h";
 using VideoInfo from "MediaInfo.h";
 using struct mozilla::layers::TextureFactoryIdentifier from "mozilla/layers/CompositorTypes.h";
+using mozilla::CreateDecoderParams::Option from "PlatformDecoderModule.h";
+using mozilla::CreateDecoderParams::OptionSet from "PlatformDecoderModule.h";
 
 namespace mozilla {
 namespace dom {
 
 sync protocol PVideoDecoderManager
 {
   manages PVideoDecoder;
 parent:
   // aBlacklistedD3D11Driver and aBlacklistedD3D9Driver are used to read back the blacklisted driver information
   // from GPU process to content process.
   // We should have added a new sync method to read back this information but, in that way, we also introduce one
   // more sync IPC call.
   // Considering that this information is only used for telemetry usage in bug 1393392 and should be removed once
   // we have collected enough data, we add these two return values here for convenience.
-  sync PVideoDecoder(VideoInfo info, float framerate, bool disallowHWDecoder, TextureFactoryIdentifier identifier)
+  sync PVideoDecoder(VideoInfo info, float framerate, OptionSet options, TextureFactoryIdentifier identifier)
          returns (bool success,
                   nsCString aBlacklistedD3D11Driver,
                   nsCString aBlacklistedD3D9Driver,
                   nsCString aErrorDescription);
 
   sync Readback(SurfaceDescriptorGPUVideo sd) returns (SurfaceDescriptor aResult);
 
   async DeallocateSurfaceDescriptorGPUVideo(SurfaceDescriptorGPUVideo sd);
--- a/dom/media/ipc/RemoteVideoDecoder.cpp
+++ b/dom/media/ipc/RemoteVideoDecoder.cpp
@@ -187,18 +187,17 @@ RemoteDecoderModule::CreateVideoDecoder(
   VideoDecoderManagerChild::GetManagerThread()->Dispatch(
     NS_NewRunnableFunction(
       "dom::RemoteDecoderModule::CreateVideoDecoder",
       [&]() {
         AutoCompleteTask complete(&task);
         result = object->mActor->InitIPDL(
           aParams.VideoConfig(),
           aParams.mRate.mValue,
-          aParams.mOptions.contains(
-            CreateDecoderParams::Option::HardwareDecoderNotAllowed),
+          aParams.mOptions,
           aParams.mKnowsCompositor->GetTextureFactoryIdentifier());
       }),
     NS_DISPATCH_NORMAL);
   task.Wait();
 
   if (NS_FAILED(result)) {
     if (aParams.mError) {
       *aParams.mError = result;
--- a/dom/media/ipc/VideoDecoderChild.cpp
+++ b/dom/media/ipc/VideoDecoderChild.cpp
@@ -172,17 +172,17 @@ VideoDecoderChild::ActorDestroy(ActorDes
                                 mBlacklistedD3D11Driver,
                                 mBlacklistedD3D9Driver);
 #endif // XP_WIN
 }
 
 MediaResult
 VideoDecoderChild::InitIPDL(const VideoInfo& aVideoInfo,
                             float aFramerate,
-                            bool aDisallowHWDecoder,
+                            const CreateDecoderParams::OptionSet& aOptions,
                             const layers::TextureFactoryIdentifier& aIdentifier)
 {
   RefPtr<VideoDecoderManagerChild> manager =
     VideoDecoderManagerChild::GetSingleton();
 
   // The manager isn't available because VideoDecoderManagerChild has been
   // initialized with null end points and we don't want to decode video on GPU
   // process anymore. Return false here so that we can fallback to other PDMs.
@@ -202,17 +202,17 @@ VideoDecoderChild::InitIPDL(const VideoI
   }
 
   mIPDLSelfRef = this;
   bool success = false;
   nsCString errorDescription;
   if (manager->SendPVideoDecoderConstructor(this,
                                             aVideoInfo,
                                             aFramerate,
-                                            aDisallowHWDecoder,
+                                            aOptions,
                                             aIdentifier,
                                             &success,
                                             &mBlacklistedD3D11Driver,
                                             &mBlacklistedD3D9Driver,
                                             &errorDescription)) {
     mCanSend = true;
   }
 
--- a/dom/media/ipc/VideoDecoderChild.h
+++ b/dom/media/ipc/VideoDecoderChild.h
@@ -46,17 +46,17 @@ public:
   bool IsHardwareAccelerated(nsACString& aFailureReason) const;
   nsCString GetDescriptionName() const;
   void SetSeekThreshold(const media::TimeUnit& aTime);
   MediaDataDecoder::ConversionRequired NeedsConversion() const;
 
   MOZ_IS_CLASS_INIT
   MediaResult InitIPDL(const VideoInfo& aVideoInfo,
                        float aFramerate,
-                       bool aDisallowHWDecoder,
+                       const CreateDecoderParams::OptionSet& aOptions,
                        const layers::TextureFactoryIdentifier& aIdentifier);
   void DestroyIPDL();
 
   // Called from IPDL when our actor has been destroyed
   void IPDLActorDestroyed();
 
   VideoDecoderManagerChild* GetManager();
 
--- a/dom/media/ipc/VideoDecoderManagerChild.cpp
+++ b/dom/media/ipc/VideoDecoderManagerChild.cpp
@@ -111,24 +111,25 @@ VideoDecoderManagerChild::GetManagerThre
 
 /* static */ AbstractThread*
 VideoDecoderManagerChild::GetManagerAbstractThread()
 {
   return sVideoDecoderChildAbstractThread;
 }
 
 PVideoDecoderChild*
-VideoDecoderManagerChild::AllocPVideoDecoderChild(const VideoInfo& aVideoInfo,
-                                                  const float& aFramerate,
-                                                  const bool& aDisallowHWDecoder,
-                                                  const layers::TextureFactoryIdentifier& aIdentifier,
-                                                  bool* aSuccess,
-                                                  nsCString* /* not used */,
-                                                  nsCString* /* not used */,
-                                                  nsCString* /* not used */)
+VideoDecoderManagerChild::AllocPVideoDecoderChild(
+  const VideoInfo& aVideoInfo,
+  const float& aFramerate,
+  const CreateDecoderParams::OptionSet& aOptions,
+  const layers::TextureFactoryIdentifier& aIdentifier,
+  bool* aSuccess,
+  nsCString* /* not used */,
+  nsCString* /* not used */,
+  nsCString* /* not used */)
 {
   return new VideoDecoderChild();
 }
 
 bool
 VideoDecoderManagerChild::DeallocPVideoDecoderChild(PVideoDecoderChild* actor)
 {
   VideoDecoderChild* child = static_cast<VideoDecoderChild*>(actor);
--- a/dom/media/ipc/VideoDecoderManagerChild.h
+++ b/dom/media/ipc/VideoDecoderManagerChild.h
@@ -65,24 +65,25 @@ public:
 protected:
   void InitIPDL();
 
   void ActorDestroy(ActorDestroyReason aWhy) override;
   void DeallocPVideoDecoderManagerChild() override;
 
   void HandleFatalError(const char* aMsg) const override;
 
-  PVideoDecoderChild* AllocPVideoDecoderChild(const VideoInfo& aVideoInfo,
-                                              const float& aFramerate,
-                                              const bool& aDisallowHWDecoder,
-                                              const layers::TextureFactoryIdentifier& aIdentifier,
-                                              bool* aSuccess,
-                                              nsCString* aBlacklistedD3D11Driver,
-                                              nsCString* aBlacklistedD3D9Driver,
-                                              nsCString* aErrorDescription) override;
+  PVideoDecoderChild* AllocPVideoDecoderChild(
+    const VideoInfo& aVideoInfo,
+    const float& aFramerate,
+    const CreateDecoderParams::OptionSet& aOptions,
+    const layers::TextureFactoryIdentifier& aIdentifier,
+    bool* aSuccess,
+    nsCString* aBlacklistedD3D11Driver,
+    nsCString* aBlacklistedD3D9Driver,
+    nsCString* aErrorDescription) override;
   bool DeallocPVideoDecoderChild(PVideoDecoderChild* actor) override;
 
 private:
   // Main thread only
   static void InitializeThread();
 
   VideoDecoderManagerChild()
     : mCanSend(false)
--- a/dom/media/ipc/VideoDecoderManagerParent.cpp
+++ b/dom/media/ipc/VideoDecoderManagerParent.cpp
@@ -196,32 +196,39 @@ VideoDecoderManagerParent::~VideoDecoder
 
 void
 VideoDecoderManagerParent::ActorDestroy(mozilla::ipc::IProtocol::ActorDestroyReason)
 {
   mThreadHolder = nullptr;
 }
 
 PVideoDecoderParent*
-VideoDecoderManagerParent::AllocPVideoDecoderParent(const VideoInfo& aVideoInfo,
-                                                    const float& aFramerate,
-                                                    const bool& aDisallowHWDecoder,
-                                                    const layers::TextureFactoryIdentifier& aIdentifier,
-                                                    bool* aSuccess,
-                                                    nsCString* aBlacklistedD3D11Driver,
-                                                    nsCString* aBlacklistedD3D9Driver,
-                                                    nsCString* aErrorDescription)
+VideoDecoderManagerParent::AllocPVideoDecoderParent(
+  const VideoInfo& aVideoInfo,
+  const float& aFramerate,
+  const CreateDecoderParams::OptionSet& aOptions,
+  const layers::TextureFactoryIdentifier& aIdentifier,
+  bool* aSuccess,
+  nsCString* aBlacklistedD3D11Driver,
+  nsCString* aBlacklistedD3D9Driver,
+  nsCString* aErrorDescription)
 {
-  RefPtr<TaskQueue> decodeTaskQueue = new TaskQueue(
-    GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER),
-    "VideoDecoderParent::mDecodeTaskQueue");
+  RefPtr<TaskQueue> decodeTaskQueue =
+    new TaskQueue(GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER),
+                  "VideoDecoderParent::mDecodeTaskQueue");
 
-  auto* parent = new VideoDecoderParent(
-    this, aVideoInfo, aFramerate, aDisallowHWDecoder, aIdentifier,
-    sManagerTaskQueue, decodeTaskQueue, aSuccess, aErrorDescription);
+  auto* parent = new VideoDecoderParent(this,
+                                        aVideoInfo,
+                                        aFramerate,
+                                        aOptions,
+                                        aIdentifier,
+                                        sManagerTaskQueue,
+                                        decodeTaskQueue,
+                                        aSuccess,
+                                        aErrorDescription);
 
 #ifdef XP_WIN
   *aBlacklistedD3D11Driver = GetFoundD3D11BlacklistedDLL();
   *aBlacklistedD3D9Driver = GetFoundD3D9BlacklistedDLL();
 #endif // XP_WIN
 
   return parent;
 }
--- a/dom/media/ipc/VideoDecoderManagerParent.h
+++ b/dom/media/ipc/VideoDecoderManagerParent.h
@@ -26,24 +26,25 @@ public:
   static void StartupThreads();
   static void ShutdownThreads();
 
   static void ShutdownVideoBridge();
 
   bool OnManagerThread();
 
 protected:
-  PVideoDecoderParent* AllocPVideoDecoderParent(const VideoInfo& aVideoInfo,
-                                                const float& aFramerate,
-                                                const bool& aDisallowHWDecoder,
-                                                const layers::TextureFactoryIdentifier& aIdentifier,
-                                                bool* aSuccess,
-                                                nsCString* aBlacklistedD3D11Driver,
-                                                nsCString* aBlacklistedD3D9Driver,
-                                                nsCString* aErrorDescription) override;
+  PVideoDecoderParent* AllocPVideoDecoderParent(
+    const VideoInfo& aVideoInfo,
+    const float& aFramerate,
+    const CreateDecoderParams::OptionSet& aOptions,
+    const layers::TextureFactoryIdentifier& aIdentifier,
+    bool* aSuccess,
+    nsCString* aBlacklistedD3D11Driver,
+    nsCString* aBlacklistedD3D9Driver,
+    nsCString* aErrorDescription) override;
   bool DeallocPVideoDecoderParent(PVideoDecoderParent* actor) override;
 
   mozilla::ipc::IPCResult RecvReadback(const SurfaceDescriptorGPUVideo& aSD, SurfaceDescriptor* aResult) override;
   mozilla::ipc::IPCResult RecvDeallocateSurfaceDescriptorGPUVideo(const SurfaceDescriptorGPUVideo& aSD) override;
 
   void ActorDestroy(mozilla::ipc::IProtocol::ActorDestroyReason) override;
 
   void DeallocPVideoDecoderManagerParent() override;
--- a/dom/media/ipc/VideoDecoderParent.cpp
+++ b/dom/media/ipc/VideoDecoderParent.cpp
@@ -37,25 +37,26 @@ public:
   layers::LayersIPCActor* GetLayersIPCActor() override
   {
     return VideoBridgeChild::GetSingleton();
   }
 private:
   virtual ~KnowsCompositorVideo() = default;
 };
 
-VideoDecoderParent::VideoDecoderParent(VideoDecoderManagerParent* aParent,
-                                       const VideoInfo& aVideoInfo,
-                                       float aFramerate,
-                                       bool aDisallowHWDecoder,
-                                       const layers::TextureFactoryIdentifier& aIdentifier,
-                                       TaskQueue* aManagerTaskQueue,
-                                       TaskQueue* aDecodeTaskQueue,
-                                       bool* aSuccess,
-                                       nsCString* aErrorDescription)
+VideoDecoderParent::VideoDecoderParent(
+  VideoDecoderManagerParent* aParent,
+  const VideoInfo& aVideoInfo,
+  float aFramerate,
+  const CreateDecoderParams::OptionSet& aOptions,
+  const layers::TextureFactoryIdentifier& aIdentifier,
+  TaskQueue* aManagerTaskQueue,
+  TaskQueue* aDecodeTaskQueue,
+  bool* aSuccess,
+  nsCString* aErrorDescription)
   : mParent(aParent)
   , mManagerTaskQueue(aManagerTaskQueue)
   , mDecodeTaskQueue(aDecodeTaskQueue)
   , mKnowsCompositor(new KnowsCompositorVideo)
   , mDestroyed(false)
 {
   MOZ_COUNT_CTOR(VideoDecoderParent);
   MOZ_ASSERT(OnManagerThread());
@@ -77,18 +78,17 @@ VideoDecoderParent::VideoDecoderParent(V
   RefPtr<WMFDecoderModule> pdm(new WMFDecoderModule());
   pdm->Startup();
 
   CreateDecoderParams params(aVideoInfo);
   params.mTaskQueue = mDecodeTaskQueue;
   params.mKnowsCompositor = mKnowsCompositor;
   params.mImageContainer = new layers::ImageContainer();
   params.mRate = CreateDecoderParams::VideoFrameRate(aFramerate);
-  params.mOptions = OptionSet(
-    aDisallowHWDecoder ? Option::HardwareDecoderNotAllowed : Option::Default);
+  params.mOptions = aOptions;
   MediaResult error(NS_OK);
   params.mError = &error;
 
   mDecoder = pdm->CreateVideoDecoder(params);
 
   if (NS_FAILED(error)) {
     MOZ_ASSERT(aErrorDescription);
     *aErrorDescription = error.Description();
--- a/dom/media/ipc/VideoDecoderParent.h
+++ b/dom/media/ipc/VideoDecoderParent.h
@@ -24,17 +24,17 @@ class VideoDecoderParent final : public 
 public:
   // We refcount this class since the task queue can have runnables
   // that reference us.
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VideoDecoderParent)
 
   VideoDecoderParent(VideoDecoderManagerParent* aParent,
                      const VideoInfo& aVideoInfo,
                      float aFramerate,
-                     bool aDisallowHWDecoder,
+                     const CreateDecoderParams::OptionSet& aOptions,
                      const layers::TextureFactoryIdentifier& aIdentifier,
                      TaskQueue* aManagerTaskQueue,
                      TaskQueue* aDecodeTaskQueue,
                      bool* aSuccess,
                      nsCString* aErrorDescription);
 
   void Destroy();
 
--- a/dom/media/platforms/PlatformDecoderModule.h
+++ b/dom/media/platforms/PlatformDecoderModule.h
@@ -8,16 +8,17 @@
 #define PlatformDecoderModule_h_
 
 #include "DecoderDoctorLogger.h"
 #include "GMPCrashHelper.h"
 #include "MediaEventSource.h"
 #include "MediaInfo.h"
 #include "MediaResult.h"
 #include "mozilla/EnumSet.h"
+#include "mozilla/EnumTypeTraits.h"
 #include "mozilla/MozPromise.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/TaskQueue.h"
 #include "mozilla/layers/KnowsCompositor.h"
 #include "mozilla/layers/LayersTypes.h"
 #include "nsTArray.h"
 #include <queue>
 
@@ -154,16 +155,25 @@ private:
   template <typename T1, typename T2, typename... Ts>
   void Set(T1&& a1, T2&& a2, Ts&&... args)
   {
     Set(std::forward<T1>(a1));
     Set(std::forward<T2>(a2), std::forward<Ts>(args)...);
   }
 };
 
+// Used for IPDL serialization.
+// The 'value' have to be the biggest enum from CreateDecoderParams::Option.
+template <>
+struct MaxEnumValue<::mozilla::CreateDecoderParams::Option>
+{
+  static constexpr unsigned int value = static_cast<unsigned int>(CreateDecoderParams::Option::HardwareDecoderNotAllowed);
+};
+
+
 // The PlatformDecoderModule interface is used by the MediaFormatReader to
 // abstract access to decoders provided by various
 // platforms.
 // Each platform (Windows, MacOSX, Linux, B2G etc) must implement a
 // PlatformDecoderModule to provide access to its decoders in order to get
 // decompressed H.264/AAC from the MediaFormatReader.
 //
 // Decoding is asynchronous, and should be performed on the task queue
--- a/dom/media/platforms/agnostic/VPXDecoder.cpp
+++ b/dom/media/platforms/agnostic/VPXDecoder.cpp
@@ -34,17 +34,18 @@ static VPXDecoder::Codec MimeTypeToCodec
     return VPXDecoder::Codec::VP9;
   }
   return VPXDecoder::Codec::Unknown;
 }
 
 static nsresult
 InitContext(vpx_codec_ctx_t* aCtx,
             const VideoInfo& aInfo,
-            const VPXDecoder::Codec aCodec)
+            const VPXDecoder::Codec aCodec,
+            bool aLowLatency)
 {
   int decode_threads = 2;
 
   vpx_codec_iface_t* dx = nullptr;
   if (aCodec == VPXDecoder::Codec::VP8) {
     dx = vpx_codec_vp8_dx();
   }
   else if (aCodec == VPXDecoder::Codec::VP9) {
@@ -54,31 +55,33 @@ InitContext(vpx_codec_ctx_t* aCtx,
     }
     else if (aInfo.mDisplay.width >= 1024) {
       decode_threads = 4;
     }
   }
   decode_threads = std::min(decode_threads, PR_GetNumberOfProcessors());
 
   vpx_codec_dec_cfg_t config;
-  config.threads = decode_threads;
+  config.threads = aLowLatency ? 1 : decode_threads;
   config.w = config.h = 0; // set after decode
 
   if (!dx || vpx_codec_dec_init(aCtx, dx, &config, 0)) {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
 VPXDecoder::VPXDecoder(const CreateDecoderParams& aParams)
   : mImageContainer(aParams.mImageContainer)
   , mImageAllocator(aParams.mKnowsCompositor)
   , mTaskQueue(aParams.mTaskQueue)
   , mInfo(aParams.VideoConfig())
   , mCodec(MimeTypeToCodec(aParams.VideoConfig().mMimeType))
+  , mLowLatency(
+      aParams.mOptions.contains(CreateDecoderParams::Option::LowLatency))
 {
   MOZ_COUNT_CTOR(VPXDecoder);
   PodZero(&mVPX);
   PodZero(&mVPXAlpha);
 }
 
 VPXDecoder::~VPXDecoder()
 {
@@ -94,22 +97,22 @@ VPXDecoder::Shutdown()
     vpx_codec_destroy(&self->mVPXAlpha);
     return ShutdownPromise::CreateAndResolve(true, __func__);
   });
 }
 
 RefPtr<MediaDataDecoder::InitPromise>
 VPXDecoder::Init()
 {
-  if (NS_FAILED(InitContext(&mVPX, mInfo, mCodec))) {
+  if (NS_FAILED(InitContext(&mVPX, mInfo, mCodec, mLowLatency))) {
     return VPXDecoder::InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                                                     __func__);
   }
   if (mInfo.HasAlpha()) {
-    if (NS_FAILED(InitContext(&mVPXAlpha, mInfo, mCodec))) {
+    if (NS_FAILED(InitContext(&mVPXAlpha, mInfo, mCodec, mLowLatency))) {
       return VPXDecoder::InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                                                       __func__);
     }
   }
   return VPXDecoder::InitPromise::CreateAndResolve(TrackInfo::kVideoTrack,
                                                    __func__);
 }
 
--- a/dom/media/platforms/agnostic/VPXDecoder.h
+++ b/dom/media/platforms/agnostic/VPXDecoder.h
@@ -73,13 +73,14 @@ private:
   vpx_codec_ctx_t mVPX;
 
   // VPx alpha decoder state
   vpx_codec_ctx_t mVPXAlpha;
 
   const VideoInfo& mInfo;
 
   const Codec mCodec;
+  const bool mLowLatency;
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp
+++ b/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp
@@ -97,38 +97,41 @@ public:
   RefPtr<InitPromise> Init() override
   {
     MOZ_ASSERT(!mIsShutdown);
     return mDecoder->Init();
   }
 
   RefPtr<DecodePromise> Decode(MediaRawData* aSample) override
   {
-    MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
-    MOZ_RELEASE_ASSERT(mDecrypts.Count() == 0,
-                       "Can only process one sample at a time");
-    RefPtr<DecodePromise> p = mDecodePromise.Ensure(__func__);
+    RefPtr<EMEDecryptor> self = this;
+    RefPtr<MediaRawData> sample = aSample;
+    return InvokeAsync(mTaskQueue, __func__, [self, this, sample]() {
+      MOZ_RELEASE_ASSERT(mDecrypts.Count() == 0,
+                         "Can only process one sample at a time");
+      RefPtr<DecodePromise> p = mDecodePromise.Ensure(__func__);
 
-    RefPtr<EMEDecryptor> self = this;
-    mSamplesWaitingForKey->WaitIfKeyNotUsable(aSample)
-      ->Then(mTaskQueue, __func__,
-             [self](RefPtr<MediaRawData> aSample) {
-               self->mKeyRequest.Complete();
-               self->ThrottleDecode(aSample);
-             },
-             [self]() {
-               self->mKeyRequest.Complete();
-             })
-      ->Track(mKeyRequest);
+      mSamplesWaitingForKey->WaitIfKeyNotUsable(sample)
+        ->Then(mTaskQueue,
+               __func__,
+               [self](const RefPtr<MediaRawData>& aSample) {
+                 self->mKeyRequest.Complete();
+                 self->ThrottleDecode(aSample);
+               },
+               [self]() { self->mKeyRequest.Complete(); })
+        ->Track(mKeyRequest);
 
-    return p;
+      return p;
+    });
   }
 
   void ThrottleDecode(MediaRawData* aSample)
   {
+    MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+
     RefPtr<EMEDecryptor> self = this;
     mThroughputLimiter.Throttle(aSample)
       ->Then(mTaskQueue, __func__,
              [self] (RefPtr<MediaRawData> aSample) {
                self->mThrottleRequest.Complete();
                self->AttemptDecode(aSample);
              },
              [self]() {
@@ -221,60 +224,65 @@ public:
                  self->mDecodePromise.RejectIfExists(aError, __func__);
                })
         ->Track(mDecodeRequest);
     }
   }
 
   RefPtr<FlushPromise> Flush() override
   {
-    MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
-    MOZ_ASSERT(!mIsShutdown);
-    mKeyRequest.DisconnectIfExists();
-    mThrottleRequest.DisconnectIfExists();
-    mDecodeRequest.DisconnectIfExists();
-    mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
-    mThroughputLimiter.Flush();
-    for (auto iter = mDecrypts.Iter(); !iter.Done(); iter.Next()) {
-      nsAutoPtr<DecryptPromiseRequestHolder>& holder = iter.Data();
-      holder->DisconnectIfExists();
-      iter.Remove();
-    }
-    RefPtr<SamplesWaitingForKey> k = mSamplesWaitingForKey;
-    return mDecoder->Flush()->Then(
-      mTaskQueue, __func__,
-      [k]() {
-        k->Flush();
-        return FlushPromise::CreateAndResolve(true, __func__);
+    RefPtr<EMEDecryptor> self = this;
+    return InvokeAsync(
+      mTaskQueue, __func__, [self, this]() -> RefPtr<FlushPromise> {
+        MOZ_ASSERT(!mIsShutdown);
+        mKeyRequest.DisconnectIfExists();
+        mThrottleRequest.DisconnectIfExists();
+        mDecodeRequest.DisconnectIfExists();
+        mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+        mThroughputLimiter.Flush();
+        for (auto iter = mDecrypts.Iter(); !iter.Done(); iter.Next()) {
+          nsAutoPtr<DecryptPromiseRequestHolder>& holder = iter.Data();
+          holder->DisconnectIfExists();
+          iter.Remove();
+        }
+        RefPtr<SamplesWaitingForKey> k = mSamplesWaitingForKey;
+        return mDecoder->Flush()->Then(mTaskQueue, __func__, [k]() {
+          k->Flush();
+          return FlushPromise::CreateAndResolve(true, __func__);
+        });
       });
   }
 
   RefPtr<DecodePromise> Drain() override
   {
-    MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
-    MOZ_ASSERT(!mIsShutdown);
-    MOZ_ASSERT(mDecodePromise.IsEmpty() && !mDecodeRequest.Exists(),
-               "Must wait for decoding to complete");
-    for (auto iter = mDecrypts.Iter(); !iter.Done(); iter.Next()) {
-      nsAutoPtr<DecryptPromiseRequestHolder>& holder = iter.Data();
-      holder->DisconnectIfExists();
-      iter.Remove();
-    }
-    return mDecoder->Drain();
+    RefPtr<EMEDecryptor> self = this;
+    return InvokeAsync(mTaskQueue, __func__, [self, this]() {
+      MOZ_ASSERT(!mIsShutdown);
+      MOZ_ASSERT(mDecodePromise.IsEmpty() && !mDecodeRequest.Exists(),
+                 "Must wait for decoding to complete");
+      for (auto iter = mDecrypts.Iter(); !iter.Done(); iter.Next()) {
+        nsAutoPtr<DecryptPromiseRequestHolder>& holder = iter.Data();
+        holder->DisconnectIfExists();
+        iter.Remove();
+      }
+      return mDecoder->Drain();
+    });
   }
 
   RefPtr<ShutdownPromise> Shutdown() override
   {
-    MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
-    MOZ_ASSERT(!mIsShutdown);
-    mIsShutdown = true;
-    mSamplesWaitingForKey = nullptr;
-    RefPtr<MediaDataDecoder> decoder = mDecoder.forget();
-    mProxy = nullptr;
-    return decoder->Shutdown();
+    RefPtr<EMEDecryptor> self = this;
+    return InvokeAsync(mTaskQueue, __func__, [self, this]() {
+      MOZ_ASSERT(!mIsShutdown);
+      mIsShutdown = true;
+      mSamplesWaitingForKey = nullptr;
+      RefPtr<MediaDataDecoder> decoder = mDecoder.forget();
+      mProxy = nullptr;
+      return decoder->Shutdown();
+    });
   }
 
   nsCString GetDescriptionName() const override
   {
     return mDecoder->GetDescriptionName();
   }
 
   ConversionRequired NeedsConversion() const override
@@ -326,60 +334,70 @@ EMEMediaDataDecoderProxy::EMEMediaDataDe
                                aParams.mOnWaitingForKeyEvent))
   , mProxy(aProxy)
 {
 }
 
 RefPtr<MediaDataDecoder::DecodePromise>
 EMEMediaDataDecoderProxy::Decode(MediaRawData* aSample)
 {
-  RefPtr<DecodePromise> p = mDecodePromise.Ensure(__func__);
-
   RefPtr<EMEMediaDataDecoderProxy> self = this;
-  mSamplesWaitingForKey->WaitIfKeyNotUsable(aSample)
-    ->Then(mTaskQueue, __func__,
-           [self, this](RefPtr<MediaRawData> aSample) {
-             mKeyRequest.Complete();
+  RefPtr<MediaRawData> sample = aSample;
+  return InvokeAsync(mTaskQueue, __func__, [self, this, sample]() {
+    RefPtr<DecodePromise> p = mDecodePromise.Ensure(__func__);
+    mSamplesWaitingForKey->WaitIfKeyNotUsable(sample)
+      ->Then(mTaskQueue,
+             __func__,
+             [self, this](RefPtr<MediaRawData> aSample) {
+               mKeyRequest.Complete();
 
-             MediaDataDecoderProxy::Decode(aSample)
-               ->Then(mTaskQueue, __func__,
-                      [self, this](const DecodedData& aResults) {
-                        mDecodeRequest.Complete();
-                        mDecodePromise.Resolve(aResults, __func__);
-                      },
-                      [self, this](const MediaResult& aError) {
-                        mDecodeRequest.Complete();
-                        mDecodePromise.Reject(aError, __func__);
-                      })
-               ->Track(mDecodeRequest);
-           },
-           [self]() {
-             self->mKeyRequest.Complete();
-             MOZ_CRASH("Should never get here");
-           })
-    ->Track(mKeyRequest);
+               MediaDataDecoderProxy::Decode(aSample)
+                 ->Then(mTaskQueue,
+                        __func__,
+                        [self, this](const DecodedData& aResults) {
+                          mDecodeRequest.Complete();
+                          mDecodePromise.Resolve(aResults, __func__);
+                        },
+                        [self, this](const MediaResult& aError) {
+                          mDecodeRequest.Complete();
+                          mDecodePromise.Reject(aError, __func__);
+                        })
+                 ->Track(mDecodeRequest);
+             },
+             [self]() {
+               self->mKeyRequest.Complete();
+               MOZ_CRASH("Should never get here");
+             })
+      ->Track(mKeyRequest);
 
-  return p;
+    return p;
+  });
 }
 
 RefPtr<MediaDataDecoder::FlushPromise>
 EMEMediaDataDecoderProxy::Flush()
 {
-  mKeyRequest.DisconnectIfExists();
-  mDecodeRequest.DisconnectIfExists();
-  mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
-  return MediaDataDecoderProxy::Flush();
+  RefPtr<EMEMediaDataDecoderProxy> self = this;
+  return InvokeAsync(mTaskQueue, __func__, [self, this]() {
+    mKeyRequest.DisconnectIfExists();
+    mDecodeRequest.DisconnectIfExists();
+    mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+    return MediaDataDecoderProxy::Flush();
+  });
 }
 
 RefPtr<ShutdownPromise>
 EMEMediaDataDecoderProxy::Shutdown()
 {
-  mSamplesWaitingForKey = nullptr;
-  mProxy = nullptr;
-  return MediaDataDecoderProxy::Shutdown();
+  RefPtr<EMEMediaDataDecoderProxy> self = this;
+  return InvokeAsync(mTaskQueue, __func__, [self, this]() {
+    mSamplesWaitingForKey = nullptr;
+    mProxy = nullptr;
+    return MediaDataDecoderProxy::Shutdown();
+  });
 }
 
 EMEDecoderModule::EMEDecoderModule(CDMProxy* aProxy, PDMFactory* aPDM)
   : mProxy(aProxy)
   , mPDM(aPDM)
 {
 }
 
@@ -426,17 +444,17 @@ EMEDecoderModule::CreateVideoDecoder(con
 
   MOZ_ASSERT(mPDM);
   RefPtr<MediaDataDecoder> decoder(mPDM->CreateDecoder(aParams));
   if (!decoder) {
     return nullptr;
   }
 
   RefPtr<MediaDataDecoder> emeDecoder(new EMEDecryptor(
-    decoder, mProxy, AbstractThread::GetCurrent()->AsTaskQueue(),
+    decoder, mProxy, aParams.mTaskQueue,
     aParams.mType, aParams.mOnWaitingForKeyEvent));
   return emeDecoder.forget();
 }
 
 already_AddRefed<MediaDataDecoder>
 EMEDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams)
 {
   MOZ_ASSERT(aParams.mConfig.mCrypto.mValid);
@@ -461,17 +479,17 @@ EMEDecoderModule::CreateAudioDecoder(con
   RefPtr<MediaDataDecoder> decoder(mPDM->CreateDecoder(aParams));
   if (!decoder) {
     return nullptr;
   }
 
   RefPtr<MediaDataDecoder> emeDecoder(
     new EMEDecryptor(decoder,
                      mProxy,
-                     AbstractThread::GetCurrent()->AsTaskQueue(),
+                     aParams.mTaskQueue,
                      aParams.mType,
                      aParams.mOnWaitingForKeyEvent,
                      std::move(converter)));
   return emeDecoder.forget();
 }
 
 bool
 EMEDecoderModule::SupportsMimeType(const nsACString& aMimeType,
--- a/dom/media/platforms/apple/AppleDecoderModule.cpp
+++ b/dom/media/platforms/apple/AppleDecoderModule.cpp
@@ -64,17 +64,18 @@ AppleDecoderModule::Startup()
 }
 
 already_AddRefed<MediaDataDecoder>
 AppleDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams)
 {
   RefPtr<MediaDataDecoder> decoder =
     new AppleVTDecoder(aParams.VideoConfig(),
                        aParams.mTaskQueue,
-                       aParams.mImageContainer);
+                       aParams.mImageContainer,
+                       aParams.mOptions);
   return decoder.forget();
 }
 
 already_AddRefed<MediaDataDecoder>
 AppleDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams)
 {
   RefPtr<MediaDataDecoder> decoder =
     new AppleATDecoder(aParams.AudioConfig(), aParams.mTaskQueue);
--- a/dom/media/platforms/apple/AppleVTDecoder.cpp
+++ b/dom/media/platforms/apple/AppleVTDecoder.cpp
@@ -23,39 +23,43 @@
 #define LOG(...) DDMOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, __VA_ARGS__)
 #define LOGEX(_this, ...)                                                      \
   DDMOZ_LOGEX(_this, sPDMLog, mozilla::LogLevel::Debug, __VA_ARGS__)
 
 namespace mozilla {
 
 AppleVTDecoder::AppleVTDecoder(const VideoInfo& aConfig,
                                TaskQueue* aTaskQueue,
-                               layers::ImageContainer* aImageContainer)
+                               layers::ImageContainer* aImageContainer,
+                               CreateDecoderParams::OptionSet aOptions)
   : mExtraData(aConfig.mExtraData)
   , mPictureWidth(aConfig.mImage.width)
   , mPictureHeight(aConfig.mImage.height)
   , mDisplayWidth(aConfig.mDisplay.width)
   , mDisplayHeight(aConfig.mDisplay.height)
   , mTaskQueue(aTaskQueue)
-  , mMaxRefFrames(H264::ComputeMaxRefFrames(aConfig.mExtraData))
+  , mMaxRefFrames(aOptions.contains(CreateDecoderParams::Option::LowLatency)
+                    ? 0
+                    : H264::ComputeMaxRefFrames(aConfig.mExtraData))
   , mImageContainer(aImageContainer)
 #ifdef MOZ_WIDGET_UIKIT
   , mUseSoftwareImages(true)
 #else
   , mUseSoftwareImages(false)
 #endif
   , mIsFlushing(false)
   , mMonitor("AppleVTDecoder")
   , mFormat(nullptr)
   , mSession(nullptr)
   , mIsHardwareAccelerated(false)
 {
   MOZ_COUNT_CTOR(AppleVTDecoder);
   // TODO: Verify aConfig.mime_type.
-  LOG("Creating AppleVTDecoder for %dx%d h.264 video", mDisplayWidth,
+  LOG("Creating AppleVTDecoder for %dx%d h.264 video",
+      mDisplayWidth,
       mDisplayHeight);
 
   // To ensure our PromiseHolder is only ever accessed with the monitor held.
   mPromise.SetMonitor(&mMonitor);
 }
 
 AppleVTDecoder::~AppleVTDecoder()
 {
--- a/dom/media/platforms/apple/AppleVTDecoder.h
+++ b/dom/media/platforms/apple/AppleVTDecoder.h
@@ -21,17 +21,18 @@ DDLoggedTypeDeclNameAndBase(AppleVTDecod
 
 class AppleVTDecoder
   : public MediaDataDecoder
   , public DecoderDoctorLifeLogger<AppleVTDecoder>
 {
 public:
   AppleVTDecoder(const VideoInfo& aConfig,
                  TaskQueue* aTaskQueue,
-                 layers::ImageContainer* aImageContainer);
+                 layers::ImageContainer* aImageContainer,
+                 CreateDecoderParams::OptionSet aOptions);
 
   class AppleFrameRef {
   public:
     media::TimeUnit decode_timestamp;
     media::TimeUnit composition_timestamp;
     media::TimeUnit duration;
     int64_t byte_offset;
     bool is_sync_point;
--- a/dom/media/platforms/wmf/WMFDecoderModule.cpp
+++ b/dom/media/platforms/wmf/WMFDecoderModule.cpp
@@ -89,29 +89,23 @@ WMFDecoderModule::Startup()
 {
   mWMFInitialized = SUCCEEDED(wmf::MFStartup());
   return mWMFInitialized ? NS_OK : NS_ERROR_FAILURE;
 }
 
 already_AddRefed<MediaDataDecoder>
 WMFDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams)
 {
-  if (aParams.mOptions.contains(CreateDecoderParams::Option::LowLatency)) {
-    // Latency on Windows is bad. Let's not attempt to decode with WMF decoders
-    // when low latency is required.
-    return nullptr;
-  }
-
   nsAutoPtr<WMFVideoMFTManager> manager(new WMFVideoMFTManager(
     aParams.VideoConfig(),
     aParams.mKnowsCompositor,
     aParams.mImageContainer,
     aParams.mRate.mValue,
-    sDXVAEnabled && !aParams.mOptions.contains(
-                      CreateDecoderParams::Option::HardwareDecoderNotAllowed)));
+    aParams.mOptions,
+    sDXVAEnabled));
 
   MediaResult result = manager->Init();
   if (NS_FAILED(result)) {
     if (aParams.mError) {
       *aParams.mError = result;
     }
     return nullptr;
   }
--- a/dom/media/platforms/wmf/WMFVideoMFTManager.cpp
+++ b/dom/media/platforms/wmf/WMFVideoMFTManager.cpp
@@ -141,26 +141,30 @@ GetCompositorBackendType(layers::KnowsCo
   return LayersBackend::LAYERS_NONE;
 }
 
 WMFVideoMFTManager::WMFVideoMFTManager(
   const VideoInfo& aConfig,
   layers::KnowsCompositor* aKnowsCompositor,
   layers::ImageContainer* aImageContainer,
   float aFramerate,
+  const CreateDecoderParams::OptionSet& aOptions,
   bool aDXVAEnabled)
   : mVideoInfo(aConfig)
   , mImageSize(aConfig.mImage)
   , mDecodedImageSize(aConfig.mImage)
   , mVideoStride(0)
   , mYUVColorSpace(YUVColorSpace::BT601)
   , mImageContainer(aImageContainer)
   , mKnowsCompositor(aKnowsCompositor)
-  , mDXVAEnabled(aDXVAEnabled)
+  , mDXVAEnabled(aDXVAEnabled &&
+                 !aOptions.contains(
+                   CreateDecoderParams::Option::HardwareDecoderNotAllowed))
   , mFramerate(aFramerate)
+  , mLowLatency(aOptions.contains(CreateDecoderParams::Option::LowLatency))
   // mVideoStride, mVideoWidth, mVideoHeight, mUseHwAccel are initialized in
   // Init().
 {
   MOZ_COUNT_CTOR(WMFVideoMFTManager);
 
   // Need additional checks/params to check vp8/vp9
   if (MP4Decoder::IsH264(aConfig.mMimeType)) {
     mStreamType = H264;
@@ -611,17 +615,17 @@ WMFVideoMFTManager::InitInternal()
                              RESULT_DETAIL("Can't create the MFT decoder.")));
 
   RefPtr<IMFAttributes> attr(decoder->GetAttributes());
   UINT32 aware = 0;
   if (attr) {
     attr->GetUINT32(MF_SA_D3D_AWARE, &aware);
     attr->SetUINT32(CODECAPI_AVDecNumWorkerThreads,
       WMFDecoderModule::GetNumDecoderThreads());
-    if (gfxPrefs::PDMWMFLowLatencyEnabled()) {
+    if (mLowLatency || gfxPrefs::PDMWMFLowLatencyEnabled()) {
       hr = attr->SetUINT32(CODECAPI_AVLowLatencyMode, TRUE);
       if (SUCCEEDED(hr)) {
         LOG("Enabling Low Latency Mode");
       } else {
         LOG("Couldn't enable Low Latency Mode");
       }
     }
   }
--- a/dom/media/platforms/wmf/WMFVideoMFTManager.h
+++ b/dom/media/platforms/wmf/WMFVideoMFTManager.h
@@ -22,16 +22,17 @@ class DXVA2Manager;
 
 class WMFVideoMFTManager : public MFTManager
 {
 public:
   WMFVideoMFTManager(const VideoInfo& aConfig,
                      layers::KnowsCompositor* aKnowsCompositor,
                      layers::ImageContainer* aImageContainer,
                      float aFramerate,
+                     const CreateDecoderParams::OptionSet& aOptions,
                      bool aDXVAEnabled);
   ~WMFVideoMFTManager();
 
   MediaResult Init();
 
   HRESULT Input(MediaRawData* aSample) override;
 
   HRESULT Output(int64_t aStreamOffset, RefPtr<MediaData>& aOutput) override;
@@ -118,13 +119,14 @@ private:
   const GUID& GetMediaSubtypeGUID();
 
   uint32_t mNullOutputCount = 0;
   bool mGotValidOutputAfterNullOutput = false;
   bool mGotExcessiveNullOutput = false;
   bool mIsValid = true;
   bool mIMFUsable = false;
   const float mFramerate;
+  const bool mLowLatency;
 };
 
 } // namespace mozilla
 
 #endif // WMFVideoMFTManager_h_
--- a/dom/media/platforms/wrappers/H264Converter.cpp
+++ b/dom/media/platforms/wrappers/H264Converter.cpp
@@ -29,203 +29,229 @@ H264Converter::H264Converter(PlatformDec
   , mDecoder(nullptr)
   , mGMPCrashHelper(aParams.mCrashHelper)
   , mLastError(NS_OK)
   , mType(aParams.mType)
   , mOnWaitingForKeyEvent(aParams.mOnWaitingForKeyEvent)
   , mDecoderOptions(aParams.mOptions)
   , mRate(aParams.mRate)
 {
+  mInConstructor = true;
   mLastError = CreateDecoder(mOriginalConfig, aParams.mDiagnostics);
+  mInConstructor = false;
   if (mDecoder) {
     MOZ_ASSERT(H264::HasSPS(mOriginalConfig.mExtraData));
     // The video metadata contains out of band SPS/PPS (AVC1) store it.
     mOriginalExtraData = mOriginalConfig.mExtraData;
   }
 }
 
 H264Converter::~H264Converter()
 {
 }
 
 RefPtr<MediaDataDecoder::InitPromise>
 H264Converter::Init()
 {
-  if (mDecoder) {
-    return mDecoder->Init();
-  }
+  RefPtr<H264Converter> self = this;
+  return InvokeAsync(mTaskQueue, __func__, [self, this]() {
+    if (mDecoder) {
+      return mDecoder->Init();
+    }
 
-  // We haven't been able to initialize a decoder due to a missing SPS/PPS.
-  return MediaDataDecoder::InitPromise::CreateAndResolve(
-           TrackType::kVideoTrack, __func__);
+    // We haven't been able to initialize a decoder due to a missing SPS/PPS.
+    return MediaDataDecoder::InitPromise::CreateAndResolve(
+      TrackType::kVideoTrack, __func__);
+  });
 }
 
 RefPtr<MediaDataDecoder::DecodePromise>
 H264Converter::Decode(MediaRawData* aSample)
 {
-  MOZ_RELEASE_ASSERT(mFlushPromise.IsEmpty(), "Flush operatin didn't complete");
+  RefPtr<H264Converter> self = this;
+  RefPtr<MediaRawData> sample = aSample;
+  return InvokeAsync(mTaskQueue, __func__, [self, this, sample]() {
+    MOZ_RELEASE_ASSERT(mFlushPromise.IsEmpty(),
+                       "Flush operatin didn't complete");
 
-  MOZ_RELEASE_ASSERT(!mDecodePromiseRequest.Exists() &&
-                       !mInitPromiseRequest.Exists(),
-                     "Can't request a new decode until previous one completed");
+    MOZ_RELEASE_ASSERT(
+      !mDecodePromiseRequest.Exists() && !mInitPromiseRequest.Exists(),
+      "Can't request a new decode until previous one completed");
 
-  if (!AnnexB::ConvertSampleToAVCC(aSample)) {
-    // We need AVCC content to be able to later parse the SPS.
-    // This is a no-op if the data is already AVCC.
-    return DecodePromise::CreateAndReject(
-      MediaResult(NS_ERROR_OUT_OF_MEMORY, RESULT_DETAIL("ConvertSampleToAVCC")),
-      __func__);
-  }
+    if (!AnnexB::ConvertSampleToAVCC(sample)) {
+      // We need AVCC content to be able to later parse the SPS.
+      // This is a no-op if the data is already AVCC.
+      return DecodePromise::CreateAndReject(
+        MediaResult(NS_ERROR_OUT_OF_MEMORY,
+                    RESULT_DETAIL("ConvertSampleToAVCC")),
+        __func__);
+    }
+
+    if (!AnnexB::IsAVCC(sample)) {
+      return DecodePromise::CreateAndReject(
+        MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                    RESULT_DETAIL("Invalid H264 content")),
+        __func__);
+    }
 
-  if (!AnnexB::IsAVCC(aSample)) {
-    return DecodePromise::CreateAndReject(
-      MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
-                  RESULT_DETAIL("Invalid H264 content")),
-      __func__);
-  }
+    MediaResult rv(NS_OK);
+    if (!mDecoder) {
+      // It is not possible to create an AVCC H264 decoder without SPS.
+      // As such, creation will fail if the extra_data just extracted doesn't
+      // contain a SPS.
+      rv = CreateDecoderAndInit(sample);
+      if (rv == NS_ERROR_NOT_INITIALIZED) {
+        // We are missing the required SPS to create the decoder.
+        // Ignore for the time being, the MediaRawData will be dropped.
+        return DecodePromise::CreateAndResolve(DecodedData(), __func__);
+      }
+    } else {
+      // Initialize the members that we couldn't if the extradata was given
+      // during H264Converter's construction.
+      if (!mNeedAVCC) {
+        mNeedAVCC =
+          Some(mDecoder->NeedsConversion() == ConversionRequired::kNeedAVCC);
+      }
+      if (!mCanRecycleDecoder) {
+        mCanRecycleDecoder = Some(CanRecycleDecoder());
+      }
+      rv = CheckForSPSChange(sample);
+    }
 
-  MediaResult rv(NS_OK);
-  if (!mDecoder) {
-    // It is not possible to create an AVCC H264 decoder without SPS.
-    // As such, creation will fail if the extra_data just extracted doesn't
-    // contain a SPS.
-    rv = CreateDecoderAndInit(aSample);
-    if (rv == NS_ERROR_NOT_INITIALIZED) {
-      // We are missing the required SPS to create the decoder.
-      // Ignore for the time being, the MediaRawData will be dropped.
+    if (rv == NS_ERROR_DOM_MEDIA_INITIALIZING_DECODER) {
+      // The decoder is pending initialization.
+      RefPtr<DecodePromise> p = mDecodePromise.Ensure(__func__);
+      return p;
+    }
+
+    if (NS_FAILED(rv)) {
+      return DecodePromise::CreateAndReject(rv, __func__);
+    }
+
+    if (mNeedKeyframe && !sample->mKeyframe) {
       return DecodePromise::CreateAndResolve(DecodedData(), __func__);
     }
-  } else {
-    // Initialize the members that we couldn't if the extradata was given during
-    // H264Converter's construction.
-    if (!mNeedAVCC) {
-      mNeedAVCC =
-        Some(mDecoder->NeedsConversion() == ConversionRequired::kNeedAVCC);
-    }
-    if (!mCanRecycleDecoder) {
-      mCanRecycleDecoder = Some(CanRecycleDecoder());
-    }
-    rv = CheckForSPSChange(aSample);
-  }
-
-  if (rv == NS_ERROR_DOM_MEDIA_INITIALIZING_DECODER) {
-    // The decoder is pending initialization.
-    RefPtr<DecodePromise> p = mDecodePromise.Ensure(__func__);
-    return p;
-  }
 
-  if (NS_FAILED(rv)) {
-    return DecodePromise::CreateAndReject(rv, __func__);
-  }
-
-  if (mNeedKeyframe && !aSample->mKeyframe) {
-    return DecodePromise::CreateAndResolve(DecodedData(), __func__);
-  }
+    auto res = !*mNeedAVCC
+                 ? AnnexB::ConvertSampleToAnnexB(sample, mNeedKeyframe)
+                 : Ok();
+    if (res.isErr()) {
+      return DecodePromise::CreateAndReject(
+        MediaResult(res.unwrapErr(), RESULT_DETAIL("ConvertSampleToAnnexB")),
+        __func__);
+    }
 
-  auto res = !*mNeedAVCC
-             ? AnnexB::ConvertSampleToAnnexB(aSample, mNeedKeyframe)
-             : Ok();
-  if (res.isErr()) {
-    return DecodePromise::CreateAndReject(
-      MediaResult(res.unwrapErr(), RESULT_DETAIL("ConvertSampleToAnnexB")),
-      __func__);
-  }
+    mNeedKeyframe = false;
 
-  mNeedKeyframe = false;
+    sample->mExtraData = mCurrentConfig.mExtraData;
 
-  aSample->mExtraData = mCurrentConfig.mExtraData;
-
-  return mDecoder->Decode(aSample);
+    return mDecoder->Decode(sample);
+  });
 }
 
 RefPtr<MediaDataDecoder::FlushPromise>
 H264Converter::Flush()
 {
-  mDecodePromiseRequest.DisconnectIfExists();
-  mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
-  mNeedKeyframe = true;
-  mPendingFrames.Clear();
+  RefPtr<H264Converter> self = this;
+  return InvokeAsync(mTaskQueue, __func__, [self, this]() {
+    mDecodePromiseRequest.DisconnectIfExists();
+    mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+    mNeedKeyframe = true;
+    mPendingFrames.Clear();
 
-  MOZ_RELEASE_ASSERT(mFlushPromise.IsEmpty(), "Previous flush didn't complete");
+    MOZ_RELEASE_ASSERT(mFlushPromise.IsEmpty(),
+                       "Previous flush didn't complete");
 
-  /*
-    When we detect a change of content in the H264 stream, we first drain the
-    current decoder (1), flush (2), shut it down (3) create a new decoder and
-    initialize it (4). It is possible for H264Converter::Flush to be called
-    during any of those times.
-    If during (1):
-      - mDrainRequest will not be empty.
-      - The old decoder can still be used, with the current extradata as stored
-        in mCurrentConfig.mExtraData.
+    /*
+      When we detect a change of content in the H264 stream, we first drain the
+      current decoder (1), flush (2), shut it down (3) create a new decoder and
+      initialize it (4). It is possible for H264Converter::Flush to be called
+      during any of those times.
+      If during (1):
+        - mDrainRequest will not be empty.
+        - The old decoder can still be used, with the current extradata as
+      stored in mCurrentConfig.mExtraData.
 
-    If during (2):
-      - mFlushRequest will not be empty.
-      - The old decoder can still be used, with the current extradata as stored
-        in mCurrentConfig.mExtraData.
+      If during (2):
+        - mFlushRequest will not be empty.
+        - The old decoder can still be used, with the current extradata as
+      stored in mCurrentConfig.mExtraData.
 
-    If during (3):
-      - mShutdownRequest won't be empty.
-      - mDecoder is empty.
-      - The old decoder is no longer referenced by the H264Converter.
+      If during (3):
+        - mShutdownRequest won't be empty.
+        - mDecoder is empty.
+        - The old decoder is no longer referenced by the H264Converter.
 
-    If during (4):
-      - mInitPromiseRequest won't be empty.
-      - mDecoder is set but not usable yet.
-  */
+      If during (4):
+        - mInitPromiseRequest won't be empty.
+        - mDecoder is set but not usable yet.
+    */
 
-  if (mDrainRequest.Exists() || mFlushRequest.Exists() ||
-      mShutdownRequest.Exists() || mInitPromiseRequest.Exists()) {
-    // We let the current decoder complete and will resume after.
-    return mFlushPromise.Ensure(__func__);
-  }
-  if (mDecoder) {
-    return mDecoder->Flush();
-  }
-  return FlushPromise::CreateAndResolve(true, __func__);
+    if (mDrainRequest.Exists() || mFlushRequest.Exists() ||
+        mShutdownRequest.Exists() || mInitPromiseRequest.Exists()) {
+      // We let the current decoder complete and will resume after.
+      RefPtr<FlushPromise> p = mFlushPromise.Ensure(__func__);
+      return p;
+    }
+    if (mDecoder) {
+      return mDecoder->Flush();
+    }
+    return FlushPromise::CreateAndResolve(true, __func__);
+  });
 }
 
 RefPtr<MediaDataDecoder::DecodePromise>
 H264Converter::Drain()
 {
-  MOZ_RELEASE_ASSERT(!mDrainRequest.Exists());
-  mNeedKeyframe = true;
-  if (mDecoder) {
-    return mDecoder->Drain();
-  }
-  return DecodePromise::CreateAndResolve(DecodedData(), __func__);
+  RefPtr<H264Converter> self = this;
+  return InvokeAsync(mTaskQueue, __func__, [self, this]() {
+    MOZ_RELEASE_ASSERT(!mDrainRequest.Exists());
+    mNeedKeyframe = true;
+    if (mDecoder) {
+      return mDecoder->Drain();
+    }
+    return DecodePromise::CreateAndResolve(DecodedData(), __func__);
+  });
 }
 
 RefPtr<ShutdownPromise>
 H264Converter::Shutdown()
 {
-  mInitPromiseRequest.DisconnectIfExists();
-  mDecodePromiseRequest.DisconnectIfExists();
-  mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
-  mDrainRequest.DisconnectIfExists();
-  mFlushRequest.DisconnectIfExists();
-  mFlushPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
-  mShutdownRequest.DisconnectIfExists();
+  RefPtr<H264Converter> self = this;
+  return InvokeAsync(mTaskQueue, __func__, [self, this]() {
+    mInitPromiseRequest.DisconnectIfExists();
+    mDecodePromiseRequest.DisconnectIfExists();
+    mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+    mDrainRequest.DisconnectIfExists();
+    mFlushRequest.DisconnectIfExists();
+    mFlushPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+    mShutdownRequest.DisconnectIfExists();
 
-  if (mShutdownPromise) {
-    // We have a shutdown in progress, return that promise instead as we can't
-    // shutdown a decoder twice.
-    return mShutdownPromise.forget();
-  }
-  return ShutdownDecoder();
+    if (mShutdownPromise) {
+      // We have a shutdown in progress, return that promise instead as we can't
+      // shutdown a decoder twice.
+      RefPtr<ShutdownPromise> p = mShutdownPromise.forget();
+      return p;
+    }
+    return ShutdownDecoder();
+  });
 }
 
 RefPtr<ShutdownPromise>
 H264Converter::ShutdownDecoder()
 {
-  mNeedAVCC.reset();
-  if (mDecoder) {
-    RefPtr<MediaDataDecoder> decoder = mDecoder.forget();
-    return decoder->Shutdown();
-  }
-  return ShutdownPromise::CreateAndResolve(true, __func__);
+  RefPtr<H264Converter> self = this;
+  return InvokeAsync(mTaskQueue, __func__, [self, this]() {
+    mNeedAVCC.reset();
+    if (mDecoder) {
+      RefPtr<MediaDataDecoder> decoder = mDecoder.forget();
+      return decoder->Shutdown();
+    }
+    return ShutdownPromise::CreateAndResolve(true, __func__);
+  });
 }
 
 bool
 H264Converter::IsHardwareAccelerated(nsACString& aFailureReason) const
 {
   if (mDecoder) {
     return mDecoder->IsHardwareAccelerated(aFailureReason);
   }
@@ -248,16 +274,20 @@ H264Converter::SetSeekThreshold(const me
     MediaDataDecoder::SetSeekThreshold(aTime);
   }
 }
 
 MediaResult
 H264Converter::CreateDecoder(const VideoInfo& aConfig,
                              DecoderDoctorDiagnostics* aDiagnostics)
 {
+  // This is the only one of two methods to run outside the TaskQueue when
+  // called from the constructor.
+  MOZ_ASSERT(mInConstructor || mTaskQueue->IsCurrentThreadIn());
+
   if (!H264::HasSPS(aConfig.mExtraData)) {
     // nothing found yet, will try again later
     return NS_ERROR_NOT_INITIALIZED;
   }
   UpdateConfigFromExtraData(aConfig.mExtraData);
 
   SPSData spsdata;
   if (H264::DecodeSPSFromExtraData(aConfig.mExtraData, spsdata)) {
@@ -306,16 +336,18 @@ H264Converter::CreateDecoder(const Video
   mNeedKeyframe = true;
 
   return NS_OK;
 }
 
 MediaResult
 H264Converter::CreateDecoderAndInit(MediaRawData* aSample)
 {
+  AssertOnTaskQueue();
+
   RefPtr<MediaByteBuffer> extra_data =
     H264::ExtractExtraData(aSample);
   bool inbandExtradata = H264::HasSPS(extra_data);
   if (!inbandExtradata &&
       !H264::HasSPS(mCurrentConfig.mExtraData)) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
@@ -326,17 +358,17 @@ H264Converter::CreateDecoderAndInit(Medi
   MediaResult rv =
     CreateDecoder(mCurrentConfig, /* DecoderDoctorDiagnostics* */ nullptr);
 
   if (NS_SUCCEEDED(rv)) {
     RefPtr<H264Converter> self = this;
     RefPtr<MediaRawData> sample = aSample;
     mDecoder->Init()
       ->Then(
-        AbstractThread::GetCurrent()->AsTaskQueue(),
+        mTaskQueue,
         __func__,
         [self, sample, this](const TrackType aTrackType) {
           mInitPromiseRequest.Complete();
           mNeedAVCC =
             Some(mDecoder->NeedsConversion() == ConversionRequired::kNeedAVCC);
           mCanRecycleDecoder = Some(CanRecycleDecoder());
 
           if (!mFlushPromise.IsEmpty()) {
@@ -373,16 +405,18 @@ H264Converter::CanRecycleDecoder() const
   MOZ_ASSERT(mDecoder);
   return StaticPrefs::MediaDecoderRecycleEnabled() &&
          mDecoder->SupportDecoderRecycling();
 }
 
 void
 H264Converter::DecodeFirstSample(MediaRawData* aSample)
 {
+  AssertOnTaskQueue();
+
   if (mNeedKeyframe && !aSample->mKeyframe) {
     mDecodePromise.Resolve(mPendingFrames, __func__);
     mPendingFrames.Clear();
     return;
   }
 
   auto res = !*mNeedAVCC
              ? AnnexB::ConvertSampleToAnnexB(aSample, mNeedKeyframe)
@@ -410,16 +444,18 @@ H264Converter::DecodeFirstSample(MediaRa
              mDecodePromise.Reject(aError, __func__);
            })
     ->Track(mDecodePromiseRequest);
 }
 
 MediaResult
 H264Converter::CheckForSPSChange(MediaRawData* aSample)
 {
+  AssertOnTaskQueue();
+
   RefPtr<MediaByteBuffer> extra_data =
     aSample->mKeyframe ? H264::ExtractExtraData(aSample) : nullptr;
   if (!H264::HasSPS(extra_data)) {
     MOZ_ASSERT(mCanRecycleDecoder.isSome());
     if (!*mCanRecycleDecoder) {
       // If the decoder can't be recycled, the out of band extradata will never
       // change as the H264Converter will be recreated by the MediaFormatReader
       // instead. So there's no point in testing for changes.
@@ -454,16 +490,18 @@ H264Converter::CheckForSPSChange(MediaRa
   // create a new one.
   DrainThenFlushDecoder(aSample);
   return NS_ERROR_DOM_MEDIA_INITIALIZING_DECODER;
 }
 
 void
 H264Converter::DrainThenFlushDecoder(MediaRawData* aPendingSample)
 {
+  AssertOnTaskQueue();
+
   RefPtr<MediaRawData> sample = aPendingSample;
   RefPtr<H264Converter> self = this;
   mDecoder->Drain()
     ->Then(AbstractThread::GetCurrent()->AsTaskQueue(),
            __func__,
            [self, sample, this](const MediaDataDecoder::DecodedData& aResults) {
              mDrainRequest.Complete();
              if (!mFlushPromise.IsEmpty()) {
@@ -489,33 +527,35 @@ H264Converter::DrainThenFlushDecoder(Med
              }
              mDecodePromise.Reject(aError, __func__);
            })
     ->Track(mDrainRequest);
 }
 
 void H264Converter::FlushThenShutdownDecoder(MediaRawData* aPendingSample)
 {
+  AssertOnTaskQueue();
+
   RefPtr<MediaRawData> sample = aPendingSample;
   RefPtr<H264Converter> self = this;
   mDecoder->Flush()
-    ->Then(AbstractThread::GetCurrent()->AsTaskQueue(),
+    ->Then(mTaskQueue,
            __func__,
            [self, sample, this]() {
              mFlushRequest.Complete();
 
              if (!mFlushPromise.IsEmpty()) {
                // A Flush is pending, abort the current operation.
                mFlushPromise.Resolve(true, __func__);
                return;
              }
 
              mShutdownPromise = ShutdownDecoder();
              mShutdownPromise
-               ->Then(AbstractThread::GetCurrent()->AsTaskQueue(),
+               ->Then(mTaskQueue,
                       __func__,
                       [self, sample, this]() {
                         mShutdownRequest.Complete();
                         mShutdownPromise = nullptr;
 
                         if (!mFlushPromise.IsEmpty()) {
                           // A Flush is pending, abort the current operation.
                           mFlushPromise.Resolve(true, __func__);
@@ -544,16 +584,20 @@ void H264Converter::FlushThenShutdownDec
              mDecodePromise.Reject(aError, __func__);
            })
     ->Track(mFlushRequest);
 }
 
 void
 H264Converter::UpdateConfigFromExtraData(MediaByteBuffer* aExtraData)
 {
+  // This is the only one of two methods to run outside the TaskQueue when
+  // called from the constructor.
+  MOZ_ASSERT(mInConstructor || mTaskQueue->IsCurrentThreadIn());
+
   SPSData spsdata;
   if (H264::DecodeSPSFromExtraData(aExtraData, spsdata) &&
       spsdata.pic_width > 0 && spsdata.pic_height > 0) {
     H264::EnsureSPSIsSane(spsdata);
     mCurrentConfig.mImage.width = spsdata.pic_width;
     mCurrentConfig.mImage.height = spsdata.pic_height;
     mCurrentConfig.mDisplay.width = spsdata.display_width;
     mCurrentConfig.mDisplay.height = spsdata.display_height;
--- a/dom/media/platforms/wrappers/H264Converter.h
+++ b/dom/media/platforms/wrappers/H264Converter.h
@@ -3,18 +3,18 @@
 /* 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_H264Converter_h
 #define mozilla_H264Converter_h
 
 #include "PlatformDecoderModule.h"
+#include "mozilla/Atomics.h"
 #include "mozilla/Maybe.h"
-
 namespace mozilla {
 
 class DecoderDoctorDiagnostics;
 
 DDLoggedTypeDeclNameAndBase(H264Converter, MediaDataDecoder);
 
 // H264Converter is a MediaDataDecoder wrapper used to ensure that
 // only AVCC or AnnexB is fed to the underlying MediaDataDecoder.
@@ -61,16 +61,21 @@ public:
       return mDecoder->NeedsConversion();
     }
     // Default so no conversion is performed.
     return ConversionRequired::kNeedAVCC;
   }
   MediaResult GetLastError() const { return mLastError; }
 
 private:
+  void AssertOnTaskQueue()
+  {
+    MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+  }
+
   // Will create the required MediaDataDecoder if need AVCC and we have a SPS NAL.
   // Returns NS_ERROR_FAILURE if error is permanent and can't be recovered and
   // will set mError accordingly.
   MediaResult CreateDecoder(const VideoInfo& aConfig,
                          DecoderDoctorDiagnostics* aDiagnostics);
   MediaResult CreateDecoderAndInit(MediaRawData* aSample);
   MediaResult CheckForSPSChange(MediaRawData* aSample);
   void UpdateConfigFromExtraData(MediaByteBuffer* aExtraData);
@@ -105,13 +110,15 @@ private:
   Maybe<bool> mNeedAVCC;
   MediaResult mLastError;
   bool mNeedKeyframe = true;
   const TrackInfo::TrackType mType;
   MediaEventProducer<TrackInfo::TrackType>* const mOnWaitingForKeyEvent;
   const CreateDecoderParams::OptionSet mDecoderOptions;
   const CreateDecoderParams::VideoFrameRate mRate;
   Maybe<bool> mCanRecycleDecoder;
+  // Used for debugging purposes only
+  Atomic<bool> mInConstructor;
 };
 
 } // namespace mozilla
 
 #endif // mozilla_H264Converter_h
rename from dom/notification/test/unit/common_test_notificationdb.js
rename to dom/notification/test/unit/head_notificationdb.js
--- a/dom/notification/test/unit/common_test_notificationdb.js
+++ b/dom/notification/test/unit/head_notificationdb.js
@@ -1,17 +1,17 @@
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 function getNotificationObject(app, id, tag) {
   return {
     origin: "https://" + app + ".gaiamobile.org/",
-    id: id,
+    id,
     title: app + "Notification:" + Date.now(),
     dir: "auto",
     lang: "",
     body: app + " notification body",
     tag: tag || "",
     icon: "icon.png"
   };
 }
@@ -25,17 +25,17 @@ var calendarNotification =
 // Helper to start the NotificationDB
 function startNotificationDB() {
   ChromeUtils.import("resource://gre/modules/NotificationDB.jsm");
 }
 
 // Helper function to add a listener, send message and treat the reply
 function addAndSend(msg, reply, callback, payload, runNext = true) {
   let handler = {
-    receiveMessage: function(message) {
+    receiveMessage(message) {
       if (message.name === reply) {
         Services.cpmm.removeMessageListener(reply, handler);
         callback(message);
         if (runNext) {
           run_next_test();
         }
       }
     }
--- a/dom/notification/test/unit/test_notificationdb.js
+++ b/dom/notification/test/unit/test_notificationdb.js
@@ -12,94 +12,94 @@ add_test(function test_get_none() {
   let msgReply = "Notification:GetAll:Return:OK";
   let msgHandler = function(message) {
     Assert.equal(requestID, message.data.requestID);
     Assert.equal(0, message.data.notifications.length);
   };
 
   addAndSend("Notification:GetAll", msgReply, msgHandler, {
     origin: systemNotification.origin,
-    requestID: requestID
+    requestID
   });
 });
 
 // Store one notification
 add_test(function test_send_one() {
   let requestID = 1;
   let msgReply = "Notification:Save:Return:OK";
   let msgHandler = function(message) {
     Assert.equal(requestID, message.data.requestID);
   };
 
   addAndSend("Notification:Save", msgReply, msgHandler, {
     origin: systemNotification.origin,
     notification: systemNotification,
-    requestID: requestID
+    requestID
   });
 });
 
 // Get one notification, one exists
 add_test(function test_get_one() {
   let requestID = 2;
   let msgReply = "Notification:GetAll:Return:OK";
   let msgHandler = function(message) {
     Assert.equal(requestID, message.data.requestID);
     Assert.equal(1, message.data.notifications.length);
     // compare the content
     compareNotification(systemNotification, message.data.notifications[0]);
   };
 
   addAndSend("Notification:GetAll", msgReply, msgHandler, {
     origin: systemNotification.origin,
-    requestID: requestID
+    requestID
   });
 });
 
 // Delete one notification
 add_test(function test_delete_one() {
   let requestID = 3;
   let msgReply = "Notification:Delete:Return:OK";
   let msgHandler = function(message) {
     Assert.equal(requestID, message.data.requestID);
   };
 
   addAndSend("Notification:Delete", msgReply, msgHandler, {
     origin: systemNotification.origin,
     id: systemNotification.id,
-    requestID: requestID
+    requestID
   });
 });
 
 // Get one notification, none exists
 add_test(function test_get_none_again() {
   let requestID = 4;
   let msgReply = "Notification:GetAll:Return:OK";
   let msgHandler = function(message) {
     Assert.equal(requestID, message.data.requestID);
     Assert.equal(0, message.data.notifications.length);
   };
 
   addAndSend("Notification:GetAll", msgReply, msgHandler, {
     origin: systemNotification.origin,
-    requestID: requestID
+    requestID
   });
 });
 
 // Delete one notification that do not exists anymore
 add_test(function test_delete_one_nonexistent() {
   let requestID = 5;
   let msgReply = "Notification:Delete:Return:OK";
   let msgHandler = function(message) {
     Assert.equal(requestID, message.data.requestID);
   };
 
   addAndSend("Notification:Delete", msgReply, msgHandler, {
     origin: systemNotification.origin,
     id: systemNotification.id,
-    requestID: requestID
+    requestID
   });
 });
 
 // Store two notifications with the same id
 add_test(function test_send_two_get_one() {
   let requestID = 6;
   let calls = 0;
 
@@ -120,17 +120,17 @@ add_test(function test_send_two_get_one(
         requestID: (requestID + 2)
       });
     }
   };
 
   addAndSend("Notification:Save", msgSaveReply, msgSaveHandler, {
     origin: systemNotification.origin,
     notification: systemNotification,
-    requestID: requestID
+    requestID
   }, false);
 
   addAndSend("Notification:Save", msgSaveReply, msgSaveHandler, {
     origin: systemNotification.origin,
     notification: systemNotification,
     requestID: (requestID + 1)
   }, false);
 });
@@ -141,33 +141,33 @@ add_test(function test_delete_previous()
   let msgReply = "Notification:Delete:Return:OK";
   let msgHandler = function(message) {
     Assert.equal(requestID, message.data.requestID);
   };
 
   addAndSend("Notification:Delete", msgReply, msgHandler, {
     origin: systemNotification.origin,
     id: systemNotification.id,
-    requestID: requestID
+    requestID
   });
 });
 
 // Store two notifications from same origin with the same tag
 add_test(function test_send_two_get_one() {
   let requestID = 10;
   let tag = "voicemail";
 
   let systemNotification1 =
     getNotificationObject("system", "{f271f9ee-3955-4c10-b1f2-af552fb270ee}", tag);
   let systemNotification2 =
     getNotificationObject("system", "{8ef9a628-f0f4-44b4-820d-c117573c33e3}", tag);
 
   let msgGetReply = "Notification:GetAll:Return:OK";
   let msgGetNotifHandler = {
-    receiveMessage: function(message) {
+    receiveMessage(message) {
       if (message.name === msgGetReply) {
         Services.cpmm.removeMessageListener(msgGetReply, msgGetNotifHandler);
         let notifications = message.data.notifications;
         // same tag, so replaced
         Assert.equal(1, notifications.length);
         // compare the content
         compareNotification(systemNotification2, notifications[0]);
         run_next_test();
@@ -188,17 +188,17 @@ add_test(function test_send_two_get_one(
         requestID: message.data.requestID + 2 // 12, 13
       });
     }
   };
 
   addAndSend("Notification:Save", msgSaveReply, msgSaveHandler, {
     origin: systemNotification1.origin,
     notification: systemNotification1,
-    requestID: requestID // 10
+    requestID // 10
   }, false);
 
   addAndSend("Notification:Save", msgSaveReply, msgSaveHandler, {
     origin: systemNotification2.origin,
     notification: systemNotification2,
     requestID: (requestID + 1) // 11
   }, false);
 });
@@ -209,17 +209,17 @@ add_test(function test_delete_previous()
   let msgReply = "Notification:Delete:Return:OK";
   let msgHandler = function(message) {
     Assert.equal(requestID, message.data.requestID);
   };
 
   addAndSend("Notification:Delete", msgReply, msgHandler, {
     origin: systemNotification.origin,
     id: "{8ef9a628-f0f4-44b4-820d-c117573c33e3}",
-    requestID: requestID
+    requestID
   });
 });
 
 // Store two notifications from two origins with the same tag
 add_test(function test_send_two_get_two() {
   let requestID = 20;
   let tag = "voicemail";
 
@@ -227,17 +227,17 @@ add_test(function test_send_two_get_two(
   systemNotification1.tag = tag;
 
   let calendarNotification2 = calendarNotification;
   calendarNotification2.tag = tag;
 
   let msgGetReply = "Notification:GetAll:Return:OK";
   let msgGetCalls = 0;
   let msgGetHandler = {
-    receiveMessage: function(message) {
+    receiveMessage(message) {
       if (message.name === msgGetReply) {
         msgGetCalls++;
         let notifications = message.data.notifications;
 
         // one notification per origin
         Assert.equal(1, notifications.length);
 
         // first call should be system notification
@@ -254,17 +254,17 @@ add_test(function test_send_two_get_two(
       }
     }
   };
   Services.cpmm.addMessageListener(msgGetReply, msgGetHandler);
 
   let msgSaveReply = "Notification:Save:Return:OK";
   let msgSaveCalls = 0;
   let msgSaveHandler = {
-    receiveMessage: function(message) {
+    receiveMessage(message) {
       if (message.name === msgSaveReply) {
         msgSaveCalls++;
         if (msgSaveCalls === 2) {
           Services.cpmm.removeMessageListener(msgSaveReply, msgSaveHandler);
 
           // Trigger getall for each origin
           Services.cpmm.sendAsyncMessage("Notification:GetAll", {
             origin: systemNotification1.origin,
@@ -279,17 +279,17 @@ add_test(function test_send_two_get_two(
       }
     }
   };
   Services.cpmm.addMessageListener(msgSaveReply, msgSaveHandler);
 
   Services.cpmm.sendAsyncMessage("Notification:Save", {
     origin: systemNotification1.origin,
     notification: systemNotification1,
-    requestID: requestID // 20
+    requestID // 20
   });
 
   Services.cpmm.sendAsyncMessage("Notification:Save", {
     origin: calendarNotification2.origin,
     notification: calendarNotification2,
     requestID: (requestID + 1) // 21
   });
 });
@@ -300,11 +300,11 @@ add_test(function test_delete_previous()
   let msgReply = "Notification:Delete:Return:OK";
   let msgHandler = function(message) {
     Assert.equal(requestID, message.data.requestID);
   };
 
   addAndSend("Notification:Delete", msgReply, msgHandler, {
     origin: systemNotification.origin,
     id: "{2bc883bf-2809-4432-b0f4-f54e10372764}",
-    requestID: requestID
+    requestID
   });
 });
--- a/dom/notification/test/unit/test_notificationdb_bug1024090.js
+++ b/dom/notification/test/unit/test_notificationdb_bug1024090.js
@@ -1,16 +1,16 @@
 "use strict";
 
 function run_test() {
   do_get_profile();
   run_next_test();
 }
 
-/// For bug 1024090: test edge case of notificationstore.json
+// For bug 1024090: test edge case of notificationstore.json
 add_test(function test_bug1024090_purge() {
   ChromeUtils.import("resource://gre/modules/osfile.jsm");
   const NOTIFICATION_STORE_PATH =
     OS.Path.join(OS.Constants.Path.profileDir, "notificationstore.json");
   let cleanup = OS.File.writeAtomic(NOTIFICATION_STORE_PATH, "");
   cleanup.then(
     function onSuccess() {
       ok(true, "Notification database cleaned.");
@@ -31,26 +31,26 @@ add_test(function test_bug1024090_send_o
   let msgReply = "Notification:Save:Return:OK";
   let msgHandler = function(message) {
     equal(requestID, message.data.requestID, "Checking requestID");
   };
 
   addAndSend("Notification:Save", msgReply, msgHandler, {
     origin: systemNotification.origin,
     notification: systemNotification,
-    requestID: requestID
+    requestID
   });
 });
 
 // Get one notification, one exists
 add_test(function test_bug1024090_get_one() {
   let requestID = 2;
   let msgReply = "Notification:GetAll:Return:OK";
   let msgHandler = function(message) {
     equal(requestID, message.data.requestID, "Checking requestID");
     equal(1, message.data.notifications.length, "One notification stored");
   };
 
   addAndSend("Notification:GetAll", msgReply, msgHandler, {
     origin: systemNotification.origin,
-    requestID: requestID
+    requestID
   });
 });
--- a/dom/notification/test/unit/xpcshell.ini
+++ b/dom/notification/test/unit/xpcshell.ini
@@ -1,6 +1,6 @@
 [DEFAULT]
-head = common_test_notificationdb.js
+head = head_notificationdb.js
 skip-if = toolkit == 'android'
 
 [test_notificationdb.js]
 [test_notificationdb_bug1024090.js]
--- a/dom/plugins/ipc/PluginUtilsWin.cpp
+++ b/dom/plugins/ipc/PluginUtilsWin.cpp
@@ -9,71 +9,38 @@
 #include "PluginUtilsWin.h"
 #include "PluginModuleParent.h"
 #include "mozilla/StaticMutex.h"
 
 namespace mozilla {
 namespace plugins {
 namespace PluginUtilsWin {
 
+class AudioNotification;
 typedef nsTHashtable<nsPtrHashKey<PluginModuleParent>> PluginModuleSet;
 StaticMutex sMutex;
 
 class AudioDeviceMessageRunnable : public Runnable
 {
 public:
-  explicit AudioDeviceMessageRunnable(const PluginModuleSet* aAudioNotificationSet,
-                                      NPAudioDeviceChangeDetailsIPC aChangeDetails) :
-    Runnable("AudioDeviceMessageRunnableCD")
-    , mChangeDetails(aChangeDetails)
-    , mMessageType(DEFAULT_DEVICE_CHANGED)
-    , mAudioNotificationSet(aAudioNotificationSet)
-  {}
-
-  explicit AudioDeviceMessageRunnable(const PluginModuleSet* aAudioNotificationSet,
-                                      NPAudioDeviceStateChangedIPC aDeviceState) :
-    Runnable("AudioDeviceMessageRunnableSC")
-    , mDeviceState(aDeviceState)
-    , mMessageType(DEVICE_STATE_CHANGED)
-    , mAudioNotificationSet(aAudioNotificationSet)
-  {}
+  explicit AudioDeviceMessageRunnable(AudioNotification* aAudioNotification,
+                                      NPAudioDeviceChangeDetailsIPC aChangeDetails);
 
-  NS_IMETHOD Run() override
-  {
-    StaticMutexAutoLock lock(sMutex);
-    PLUGIN_LOG_DEBUG(("Notifying %d plugins of audio device change.",
-                                            mAudioNotificationSet->Count()));
+  explicit AudioDeviceMessageRunnable(AudioNotification* aAudioNotification,
+                                      NPAudioDeviceStateChangedIPC aDeviceState);
 
-    for (auto iter = mAudioNotificationSet->ConstIter(); !iter.Done(); iter.Next()) {
-      PluginModuleParent* pluginModule = iter.Get()->GetKey();
-      bool success = false;
-      switch (mMessageType) {
-      case DEFAULT_DEVICE_CHANGED:
-        success = pluginModule->SendNPP_SetValue_NPNVaudioDeviceChangeDetails(mChangeDetails);
-        break;
-      case DEVICE_STATE_CHANGED:
-        success = pluginModule->SendNPP_SetValue_NPNVaudioDeviceStateChanged(mDeviceState);
-        break;
-      default:
-        MOZ_ASSERT_UNREACHABLE("bad AudioDeviceMessageRunnable state");
-      }
-      if(!success) {
-        return NS_ERROR_FAILURE;
-      }
-    }
-    return NS_OK;
-  }
+  NS_IMETHOD Run() override;
 
 protected:
   // The potential payloads for the message.  Type determined by mMessageType.
   NPAudioDeviceChangeDetailsIPC mChangeDetails;
   NPAudioDeviceStateChangedIPC mDeviceState;
   enum { DEFAULT_DEVICE_CHANGED, DEVICE_STATE_CHANGED } mMessageType;
 
-  const PluginModuleSet* mAudioNotificationSet;
+  AudioNotification* mAudioNotification;
 };
 
 class AudioNotification final : public IMMNotificationClient
 {
 public:
   AudioNotification() :
       mIsRegistered(false)
     , mRefCt(1)
@@ -102,17 +69,17 @@ public:
   {
     NPAudioDeviceChangeDetailsIPC changeDetails;
     changeDetails.flow = (int32_t)flow;
     changeDetails.role = (int32_t)role;
     changeDetails.defaultDevice = device_id ? std::wstring(device_id) : L"";
 
     // Make sure that plugin is notified on the main thread.
     RefPtr<AudioDeviceMessageRunnable> runnable =
-      new AudioDeviceMessageRunnable(&mAudioNotificationSet, changeDetails);
+      new AudioDeviceMessageRunnable(this, changeDetails);
     NS_DispatchToMainThread(runnable);
     return S_OK;
   }
 
   HRESULT STDMETHODCALLTYPE
   OnDeviceAdded(LPCWSTR device_id) override
   {
     return S_OK;
@@ -128,17 +95,17 @@ public:
   OnDeviceStateChanged(LPCWSTR device_id, DWORD new_state) override
   {
     NPAudioDeviceStateChangedIPC deviceStateIPC;
     deviceStateIPC.device = device_id ? std::wstring(device_id) : L"";
     deviceStateIPC.state = (uint32_t)new_state;
 
     // Make sure that plugin is notified on the main thread.
     RefPtr<AudioDeviceMessageRunnable> runnable =
-      new AudioDeviceMessageRunnable(&mAudioNotificationSet, deviceStateIPC);
+      new AudioDeviceMessageRunnable(this, deviceStateIPC);
     NS_DispatchToMainThread(runnable);
     return S_OK;
   }
 
   HRESULT STDMETHODCALLTYPE
   OnPropertyValueChanged(LPCWSTR device_id, const PROPERTYKEY key) override
   {
     return S_OK;
@@ -207,16 +174,21 @@ public:
 
   /*
    * Are any modules registered for audio notifications?
    */
   bool HasModules() {
     return !mAudioNotificationSet.IsEmpty();
   }
 
+  const PluginModuleSet* GetModuleSet() const
+  {
+    return &mAudioNotificationSet;
+  }
+
 private:
   bool mIsRegistered;   // only used to make sure that Unregister is called before destroying a Valid instance.
   LONG mRefCt;
   IMMDeviceEnumerator* mDeviceEnum;
 
   // Set of plugin modules that have registered to be notified when the audio device
   // changes.
   PluginModuleSet mAudioNotificationSet;
@@ -262,11 +234,61 @@ RegisterForAudioDeviceChanges(PluginModu
       sAudioNotification->Unregister();
       sAudioNotification->Release();
       sAudioNotification = nullptr;
     }
   }
   return NS_OK;
 }
 
+AudioDeviceMessageRunnable::AudioDeviceMessageRunnable(AudioNotification* aAudioNotification,
+                                                       NPAudioDeviceChangeDetailsIPC aChangeDetails) :
+  Runnable("AudioDeviceMessageRunnableCD")
+  , mChangeDetails(aChangeDetails)
+  , mMessageType(DEFAULT_DEVICE_CHANGED)
+  , mAudioNotification(aAudioNotification)
+{
+  // We increment the AudioNotification ref-count here -- the runnable will
+  // decrement it when it is done with us.
+  mAudioNotification->AddRef();
+}
+
+AudioDeviceMessageRunnable::AudioDeviceMessageRunnable(AudioNotification* aAudioNotification,
+                                                       NPAudioDeviceStateChangedIPC aDeviceState) :
+  Runnable("AudioDeviceMessageRunnableSC")
+  , mDeviceState(aDeviceState)
+  , mMessageType(DEVICE_STATE_CHANGED)
+  , mAudioNotification(aAudioNotification)
+{
+  // We increment the AudioNotification ref-count here -- the runnable will
+  // decrement it when it is done with us.
+  mAudioNotification->AddRef();
+}
+
+NS_IMETHODIMP
+AudioDeviceMessageRunnable::Run()
+{
+  StaticMutexAutoLock lock(sMutex);
+  PLUGIN_LOG_DEBUG(("Notifying %d plugins of audio device change.",
+                    mAudioNotification->GetModuleSet()->Count()));
+
+  bool success = true;
+  for (auto iter = mAudioNotification->GetModuleSet()->ConstIter(); !iter.Done(); iter.Next()) {
+    PluginModuleParent* pluginModule = iter.Get()->GetKey();
+    switch (mMessageType) {
+    case DEFAULT_DEVICE_CHANGED:
+      success &= pluginModule->SendNPP_SetValue_NPNVaudioDeviceChangeDetails(mChangeDetails);
+      break;
+    case DEVICE_STATE_CHANGED:
+      success &= pluginModule->SendNPP_SetValue_NPNVaudioDeviceStateChanged(mDeviceState);
+      break;
+    default:
+      MOZ_ASSERT_UNREACHABLE("bad AudioDeviceMessageRunnable state");
+    }
+  }
+  mAudioNotification->Release();
+  return success ? NS_OK : NS_ERROR_FAILURE;
+}
+
+
 }   // namespace PluginUtilsWin
 }   // namespace plugins
 }   // namespace mozilla
--- a/dom/promise/Promise.cpp
+++ b/dom/promise/Promise.cpp
@@ -6,19 +6,21 @@
 
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/Promise-inl.h"
 
 #include "js/Debug.h"
 
 #include "mozilla/Atomics.h"
 #include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/EventStateManager.h"
 #include "mozilla/OwningNonNull.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ResultExtensions.h"
+#include "mozilla/Unused.h"
 
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/DOMException.h"
 #include "mozilla/dom/DOMExceptionBinding.h"
 #include "mozilla/dom/MediaStreamError.h"
 #include "mozilla/dom/PromiseBinding.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/WorkerPrivate.h"
@@ -84,66 +86,86 @@ Promise::Promise(nsIGlobalObject* aGloba
 
 Promise::~Promise()
 {
   mozilla::DropJSObjects(this);
 }
 
 // static
 already_AddRefed<Promise>
-Promise::Create(nsIGlobalObject* aGlobal, ErrorResult& aRv)
+Promise::Create(nsIGlobalObject* aGlobal,
+                ErrorResult& aRv,
+                PropagateUserInteraction aPropagateUserInteraction)
 {
   if (!aGlobal) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
   RefPtr<Promise> p = new Promise(aGlobal);
-  p->CreateWrapper(nullptr, aRv);
+  p->CreateWrapper(nullptr, aRv, aPropagateUserInteraction);
   if (aRv.Failed()) {
     return nullptr;
   }
   return p.forget();
 }
 
+bool
+Promise::MaybePropagateUserInputEventHandling()
+{
+  JS::PromiseUserInputEventHandlingState state =
+    EventStateManager::IsHandlingUserInput() ?
+      JS::PromiseUserInputEventHandlingState::HadUserInteractionAtCreation :
+      JS::PromiseUserInputEventHandlingState::DidntHaveUserInteractionAtCreation;
+  JS::Rooted<JSObject*> p(RootingCx(), mPromiseObj);
+  return JS::SetPromiseUserInputEventHandlingState(p, state);
+}
+
 // static
 already_AddRefed<Promise>
 Promise::Resolve(nsIGlobalObject* aGlobal, JSContext* aCx,
-                 JS::Handle<JS::Value> aValue, ErrorResult& aRv)
+                 JS::Handle<JS::Value> aValue,
+                 ErrorResult& aRv,
+                 PropagateUserInteraction aPropagateUserInteraction)
 {
   JSAutoRealm ar(aCx, aGlobal->GetGlobalJSObject());
   JS::Rooted<JSObject*> p(aCx,
                           JS::CallOriginalPromiseResolve(aCx, aValue));
   if (!p) {
     aRv.NoteJSContextException(aCx);
     return nullptr;
   }
 
-  return CreateFromExisting(aGlobal, p);
+  return CreateFromExisting(aGlobal, p, aPropagateUserInteraction);
 }
 
 // static
 already_AddRefed<Promise>
 Promise::Reject(nsIGlobalObject* aGlobal, JSContext* aCx,
                 JS::Handle<JS::Value> aValue, ErrorResult& aRv)
 {
   JSAutoRealm ar(aCx, aGlobal->GetGlobalJSObject());
   JS::Rooted<JSObject*> p(aCx,
                           JS::CallOriginalPromiseReject(aCx, aValue));
   if (!p) {
     aRv.NoteJSContextException(aCx);
     return nullptr;
   }
 
-  return CreateFromExisting(aGlobal, p);
+  // This promise will never be resolved, so we pass
+  // eDontPropagateUserInteraction for aPropagateUserInteraction
+  // unconditionally.
+  return CreateFromExisting(aGlobal, p, eDontPropagateUserInteraction);
 }
 
 // static
 already_AddRefed<Promise>
 Promise::All(JSContext* aCx,
-             const nsTArray<RefPtr<Promise>>& aPromiseList, ErrorResult& aRv)
+             const nsTArray<RefPtr<Promise>>& aPromiseList,
+             ErrorResult& aRv,
+             PropagateUserInteraction aPropagateUserInteraction)
 {
   JS::Rooted<JSObject*> globalObj(aCx, JS::CurrentGlobalOrNull(aCx));
   if (!globalObj) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
   nsCOMPtr<nsIGlobalObject> global = xpc::NativeGlobal(globalObj);
@@ -169,17 +191,17 @@ Promise::All(JSContext* aCx,
   }
 
   JS::Rooted<JSObject*> result(aCx, JS::GetWaitForAllPromise(aCx, promises));
   if (!result) {
     aRv.NoteJSContextException(aCx);
     return nullptr;
   }
 
-  return CreateFromExisting(global, result);
+  return CreateFromExisting(global, result, aPropagateUserInteraction);
 }
 
 void
 Promise::Then(JSContext* aCx,
               // aCalleeGlobal may not be in the compartment of aCx, when called over
               // Xrays.
               JS::Handle<JSObject*> aCalleeGlobal,
               AnyCallback* aResolveCallback, AnyCallback* aRejectCallback,
@@ -266,30 +288,35 @@ Result<RefPtr<Promise>, nsresult>
 Promise::ThenWithoutCycleCollection(
   const std::function<already_AddRefed<Promise>(JSContext* aCx,
                                                 JS::HandleValue aValue)>& aCallback)
 {
   return ThenWithCycleCollectedArgs(aCallback);
 }
 
 void
-Promise::CreateWrapper(JS::Handle<JSObject*> aDesiredProto, ErrorResult& aRv)
+Promise::CreateWrapper(JS::Handle<JSObject*> aDesiredProto,
+                       ErrorResult& aRv,
+                       PropagateUserInteraction aPropagateUserInteraction)
 {
   AutoJSAPI jsapi;
   if (!jsapi.Init(mGlobal)) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return;
   }
   JSContext* cx = jsapi.cx();
   mPromiseObj = JS::NewPromiseObject(cx, nullptr, aDesiredProto);
   if (!mPromiseObj) {
     JS_ClearPendingException(cx);
     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return;
   }
+  if (aPropagateUserInteraction == ePropagateUserInteraction) {
+    Unused << MaybePropagateUserInputEventHandling();
+  }
 }
 
 void
 Promise::MaybeResolve(JSContext* aCx,
                       JS::Handle<JS::Value> aValue)
 {
   NS_ASSERT_OWNINGTHREAD(Promise);
 
@@ -486,22 +513,27 @@ Promise::HandleException(JSContext* aCx)
     // This is only called from MaybeSomething, so it's OK to MaybeReject here.
     MaybeReject(aCx, exn);
   }
 }
 
 // static
 already_AddRefed<Promise>
 Promise::CreateFromExisting(nsIGlobalObject* aGlobal,
-                            JS::Handle<JSObject*> aPromiseObj)
+                            JS::Handle<JSObject*> aPromiseObj,
+                            PropagateUserInteraction aPropagateUserInteraction)
 {
   MOZ_ASSERT(js::GetObjectCompartment(aGlobal->GetGlobalJSObject()) ==
              js::GetObjectCompartment(aPromiseObj));
   RefPtr<Promise> p = new Promise(aGlobal);
   p->mPromiseObj = aPromiseObj;
+  if (aPropagateUserInteraction == ePropagateUserInteraction &&
+      !p->MaybePropagateUserInputEventHandling()) {
+    return nullptr;
+  }
   return p.forget();
 }
 
 
 void
 Promise::MaybeResolveWithUndefined()
 {
   NS_ASSERT_OWNINGTHREAD(Promise);
--- a/dom/promise/Promise.h
+++ b/dom/promise/Promise.h
@@ -46,22 +46,34 @@ class Promise : public nsISupports,
   friend class PromiseWorkerProxyRunnable;
 
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_PROMISE_IID)
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS_FINAL
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Promise)
   MOZ_DECLARE_WEAKREFERENCE_TYPENAME(Promise)
 
+  enum PropagateUserInteraction
+  {
+    eDontPropagateUserInteraction,
+    ePropagateUserInteraction
+  };
+
   // Promise creation tries to create a JS reflector for the Promise, so is
   // fallible.  Furthermore, we don't want to do JS-wrapping on a 0-refcount
   // object, so we addref before doing that and return the addrefed pointer
   // here.
+  // Pass ePropagateUserInteraction for aPropagateUserInteraction if you want
+  // the promise resolve handler to be called as if we were handling user
+  // input events in case we are currently handling user input events.
   static already_AddRefed<Promise>
-  Create(nsIGlobalObject* aGlobal, ErrorResult& aRv);
+  Create(nsIGlobalObject* aGlobal,
+         ErrorResult& aRv,
+         PropagateUserInteraction aPropagateUserInteraction =
+           eDontPropagateUserInteraction);
 
   // Reports a rejected Promise by sending an error report.
   static void ReportRejectedPromise(JSContext* aCx, JS::HandleObject aPromise);
 
   typedef void (Promise::*MaybeFunc)(JSContext* aCx,
                                      JS::Handle<JS::Value> aValue);
 
   void MaybeResolve(JSContext* aCx,
@@ -111,33 +123,44 @@ public:
   nsIGlobalObject* GetParentObject() const
   {
     return mGlobal;
   }
 
   // Do the equivalent of Promise.resolve in the compartment of aGlobal.  The
   // compartment of aCx is ignored.  Errors are reported on the ErrorResult; if
   // aRv comes back !Failed(), this function MUST return a non-null value.
+  // Pass ePropagateUserInteraction for aPropagateUserInteraction if you want
+  // the promise resolve handler to be called as if we were handling user
+  // input events in case we are currently handling user input events.
   static already_AddRefed<Promise>
   Resolve(nsIGlobalObject* aGlobal, JSContext* aCx,
-          JS::Handle<JS::Value> aValue, ErrorResult& aRv);
+          JS::Handle<JS::Value> aValue,
+          ErrorResult& aRv,
+          PropagateUserInteraction aPropagateUserInteraction =
+            eDontPropagateUserInteraction);
 
   // Do the equivalent of Promise.reject in the compartment of aGlobal.  The
   // compartment of aCx is ignored.  Errors are reported on the ErrorResult; if
   // aRv comes back !Failed(), this function MUST return a non-null value.
   static already_AddRefed<Promise>
   Reject(nsIGlobalObject* aGlobal, JSContext* aCx,
          JS::Handle<JS::Value> aValue, ErrorResult& aRv);
 
   // Do the equivalent of Promise.all in the current compartment of aCx.  Errors
   // are reported on the ErrorResult; if aRv comes back !Failed(), this function
   // MUST return a non-null value.
+  // Pass ePropagateUserInteraction for aPropagateUserInteraction if you want
+  // the promise resolve handler to be called as if we were handling user
+  // input events in case we are currently handling user input events.
   static already_AddRefed<Promise>
   All(JSContext* aCx, const nsTArray<RefPtr<Promise>>& aPromiseList,
-      ErrorResult& aRv);
+      ErrorResult& aRv,
+      PropagateUserInteraction aPropagateUserInteraction =
+        eDontPropagateUserInteraction);
 
   void
   Then(JSContext* aCx,
        // aCalleeGlobal may not be in the compartment of aCx, when called over
        // Xrays.
        JS::Handle<JSObject*> aCalleeGlobal,
        AnyCallback* aResolveCallback, AnyCallback* aRejectCallback,
        JS::MutableHandle<JS::Value> aRetval,
@@ -188,19 +211,24 @@ public:
   void AppendNativeHandler(PromiseNativeHandler* aRunnable);
 
   JSObject* GlobalJSObject() const;
 
   JS::Compartment* Compartment() const;
 
   // Create a dom::Promise from a given SpiderMonkey Promise object.
   // aPromiseObj MUST be in the compartment of aGlobal's global JS object.
+  // Pass ePropagateUserInteraction for aPropagateUserInteraction if you want
+  // the promise resolve handler to be called as if we were handling user
+  // input events in case we are currently handling user input events.
   static already_AddRefed<Promise>
   CreateFromExisting(nsIGlobalObject* aGlobal,
-                     JS::Handle<JSObject*> aPromiseObj);
+                     JS::Handle<JSObject*> aPromiseObj,
+                     PropagateUserInteraction aPropagateUserInteraction =
+                       eDontPropagateUserInteraction);
 
   enum class PromiseState {
     Pending,
     Resolved,
     Rejected
   };
 
   PromiseState State() const;
@@ -212,17 +240,23 @@ protected:
   // Promise::CreateFromExisting.  I wish we could enforce that from inside this
   // class too, somehow.
   explicit Promise(nsIGlobalObject* aGlobal);
 
   virtual ~Promise();
 
   // Do JS-wrapping after Promise creation.  Passing null for aDesiredProto will
   // use the default prototype for the sort of Promise we have.
-  void CreateWrapper(JS::Handle<JSObject*> aDesiredProto, ErrorResult& aRv);
+  // Pass ePropagateUserInteraction for aPropagateUserInteraction if you want
+  // the promise resolve handler to be called as if we were handling user
+  // input events in case we are currently handling user input events.
+  void CreateWrapper(JS::Handle<JSObject*> aDesiredProto,
+                     ErrorResult& aRv,
+                     PropagateUserInteraction aPropagateUserInteraction =
+                       eDontPropagateUserInteraction);
 
 private:
   template <typename T>
   void MaybeSomething(T&& aArgument, MaybeFunc aFunc) {
     MOZ_ASSERT(PromiseObj()); // It was preserved!
 
     AutoEntryScript aes(mGlobal, "Promise resolution or rejection");
     JSContext* cx = aes.cx();
@@ -233,16 +267,18 @@ private:
       return;
     }
 
     (this->*aFunc)(cx, val);
   }
 
   void HandleException(JSContext* aCx);
 
+  bool MaybePropagateUserInputEventHandling();
+
   RefPtr<nsIGlobalObject> mGlobal;
 
   JS::Heap<JSObject*> mPromiseObj;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(Promise, NS_PROMISE_IID)
 
 } // namespace dom
--- a/gfx/layers/wr/WebRenderCommandBuilder.cpp
+++ b/gfx/layers/wr/WebRenderCommandBuilder.cpp
@@ -1859,16 +1859,19 @@ WebRenderCommandBuilder::GenerateFallbac
   // rerasterize on MotionMark
   bool differentScale = gfx::FuzzyEqual(scale.width, oldScale.width, 1e-6f) &&
                         gfx::FuzzyEqual(scale.height, oldScale.height, 1e-6f);
 
   LayoutDeviceToLayerScale2D layerScale(scale.width, scale.height);
   auto scaledBounds = bounds * layerScale;
   auto dtRect = RoundedOut(scaledBounds);
   auto dtSize = dtRect.Size();
+  if (dtSize.IsEmpty()) {
+    return nullptr;
+  }
 
   aImageRect = dtRect / layerScale;
 
   auto offset = aImageRect.TopLeft();
 
   nsDisplayItemGeometry* geometry = fallbackData->GetGeometry();
 
   bool needPaint = true;
--- a/gfx/src/RelativeLuminanceUtils.h
+++ b/gfx/src/RelativeLuminanceUtils.h
@@ -31,17 +31,17 @@ public:
     float r = ComputeComponent(NS_GET_R(aColor));
     float g = ComputeComponent(NS_GET_G(aColor));
     float b = ComputeComponent(NS_GET_B(aColor));
     float luminance = ComputeFromComponents(r, g, b);
     float factor = aLuminance / luminance;
     uint8_t r1 = DecomputeComponent(r * factor);
     uint8_t g1 = DecomputeComponent(g * factor);
     uint8_t b1 = DecomputeComponent(b * factor);
-    return NS_RGB(r1, g1, b1);
+    return NS_RGBA(r1, g1, b1, NS_GET_A(aColor));
   }
 
 private:
   static float ComputeComponent(uint8_t aComponent)
   {
     float v = float(aComponent) / 255.0f;
     if (v <= 0.03928f) {
       return v / 12.92f;
--- a/js/public/CallNonGenericMethod.h
+++ b/js/public/CallNonGenericMethod.h
@@ -41,18 +41,19 @@ CallMethodIfWrapped(JSContext* cx, IsAcc
 // and indicates whether the provided value is an acceptable |this| for the
 // method; it must be a pure function only of its argument.
 //
 //   static const JSClass AnswerClass = { ... };
 //
 //   static bool
 //   IsAnswerObject(const Value& v)
 //   {
-//       if (!v.isObject())
+//       if (!v.isObject()) {
 //           return false;
+//       }
 //       return JS_GetClass(&v.toObject()) == &AnswerClass;
 //   }
 //
 // The second function implements the NativeImpl signature and defines the
 // behavior of the method when it is provided an acceptable |this| value.
 // Aside from some typing niceties -- see the CallArgs interface for details --
 // its interface is the same as that of JSNative.
 //
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -97,20 +97,22 @@ IsArray(JSContext* cx, HandleObject obj,
  * If you think this design is awful, you're not alone.  But as it's the
  * standard, we must represent these boolean "success" values somehow.
  * ObjectOpSuccess is the class for this. It's like a bool, but when it's false
  * it also stores an error code.
  *
  * Typical usage:
  *
  *     ObjectOpResult result;
- *     if (!DefineProperty(cx, obj, id, ..., result))
+ *     if (!DefineProperty(cx, obj, id, ..., result)) {
  *         return false;
- *     if (!result)
+ *     }
+ *     if (!result) {
  *         return result.reportError(cx, obj, id);
+ *     }
  *
  * Users don't have to call `result.report()`; another possible ending is:
  *
  *     argv.rval().setBoolean(result.reallyOk());
  *     return true;
  */
 class ObjectOpResult
 {
@@ -156,18 +158,19 @@ class ObjectOpResult
         return true;
     }
 
     /*
      * Set this ObjectOpResult to false with an error code.
      *
      * Always returns true, as a convenience. Typical usage will be:
      *
-     *     if (funny condition)
+     *     if (funny condition) {
      *         return result.fail(JSMSG_CANT_DO_THE_THINGS);
+     *     }
      *
      * The true return value indicates that no exception is pending, and it
      * would be OK to ignore the failure and continue.
      */
     bool fail(uint32_t msg) {
         MOZ_ASSERT(msg != OkCode);
         MOZ_ASSERT((msg & SoftFailBit) == 0);
         code_ = msg;
--- a/js/public/Debug.h
+++ b/js/public/Debug.h
@@ -92,18 +92,19 @@ namespace dbg {
 // the object shown above, given a Builder passed to it by Debugger:
 //
 //    bool
 //    MyScriptEntryReason::explain(JSContext* cx,
 //                                 Builder& builder,
 //                                 Builder::Object& result)
 //    {
 //        JSObject* eventObject = ... obtain debuggee event object somehow ...;
-//        if (!eventObject)
+//        if (!eventObject) {
 //            return false;
+//        }
 //        result = builder.newObject(cx);
 //        return result &&
 //               result.defineProperty(cx, "eventType", SafelyFetchType(eventObject)) &&
 //               result.defineProperty(cx, "event", eventObject);
 //    }
 //
 //
 // Object::defineProperty also accepts an Object as the value to store on the
--- a/js/public/UbiNode.h
+++ b/js/public/UbiNode.h
@@ -970,18 +970,19 @@ class PreComputedEdgeRange : public Edge
 // stable across GC. The init calls emplace on |noGC|'s AutoCheckCannotGC, whose
 // lifetime must extend at least as long as the RootList itself.
 //
 // Example usage:
 //
 //    {
 //        mozilla::Maybe<JS::AutoCheckCannotGC> maybeNoGC;
 //        JS::ubi::RootList rootList(cx, maybeNoGC);
-//        if (!rootList.init())
+//        if (!rootList.init()) {
 //            return false;
+//        }
 //
 //        // The AutoCheckCannotGC is guaranteed to exist if init returned true.
 //        MOZ_ASSERT(maybeNoGC.isSome());
 //
 //        JS::ubi::Node root(&rootList);
 //
 //        ...
 //    }
--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -659,16 +659,23 @@ const Class PromiseReactionRecord::class
 
 static void
 AddPromiseFlags(PromiseObject& promise, int32_t flag)
 {
     int32_t flags = promise.flags();
     promise.setFixedSlot(PromiseSlot_Flags, Int32Value(flags | flag));
 }
 
+static void
+RemovePromiseFlags(PromiseObject& promise, int32_t flag)
+{
+    int32_t flags = promise.flags();
+    promise.setFixedSlot(PromiseSlot_Flags, Int32Value(flags & ~flag));
+}
+
 static bool
 PromiseHasAnyFlag(PromiseObject& promise, int32_t flag)
 {
     return promise.flags() & flag;
 }
 
 static bool ResolvePromiseFunction(JSContext* cx, unsigned argc, Value* vp);
 static bool RejectPromiseFunction(JSContext* cx, unsigned argc, Value* vp);
@@ -3287,16 +3294,31 @@ PromiseThenNewPromiseCapability(JSContex
 
         if (createDependent == CreateDependentPromise::Always ||
             !IsNativeFunction(C, PromiseConstructor))
         {
             // Step 4.
             if (!NewPromiseCapability(cx, C, resultCapability, true)) {
                 return false;
             }
+
+            RootedObject unwrappedPromise(cx, promiseObj);
+            if (IsWrapper(promiseObj)) {
+              unwrappedPromise = UncheckedUnwrap(promiseObj);
+            }
+            RootedObject unwrappedNewPromise(cx, resultCapability.promise());
+            if (IsWrapper(resultCapability.promise())) {
+              unwrappedNewPromise = UncheckedUnwrap(resultCapability.promise());
+            }
+            if (unwrappedPromise->is<PromiseObject>() &&
+                unwrappedNewPromise->is<PromiseObject>())
+            {
+              unwrappedNewPromise->as<PromiseObject>()
+                .copyUserInteractionFlagsFrom(*unwrappedPromise.as<PromiseObject>());
+            }
         }
     }
 
     return true;
 }
 
 // ES2016, 25.4.5.3., steps 3-5.
 MOZ_MUST_USE bool
@@ -3365,16 +3387,17 @@ OriginalPromiseThenBuiltin(JSContext* cx
     // Steps 3-4.
     Rooted<PromiseCapability> resultCapability(cx);
     if (rvalUsed) {
         PromiseObject* resultPromise = CreatePromiseObjectWithoutResolutionFunctions(cx);
         if (!resultPromise) {
             return false;
         }
 
+        resultPromise->copyUserInteractionFlagsFrom(promiseVal.toObject().as<PromiseObject>());
         resultCapability.promise().set(resultPromise);
     }
 
     // Step 5.
     if (!PerformPromiseThen(cx, promise, onFulfilled, onRejected, resultCapability)) {
         return false;
     }
 
@@ -4432,16 +4455,45 @@ PromiseObject::onSettled(JSContext* cx, 
 
     if (promise->state() == JS::PromiseState::Rejected && promise->isUnhandled()) {
         cx->runtime()->addUnhandledRejectedPromise(cx, promise);
     }
 
     Debugger::onPromiseSettled(cx, promise);
 }
 
+void
+PromiseObject::setRequiresUserInteractionHandling(bool state)
+{
+    if (state) {
+        AddPromiseFlags(*this, PROMISE_FLAG_REQUIRES_USER_INTERACTION_HANDLING);
+    } else {
+        RemovePromiseFlags(*this, PROMISE_FLAG_REQUIRES_USER_INTERACTION_HANDLING);
+    }
+}
+
+void
+PromiseObject::setHadUserInteractionUponCreation(bool state)
+{
+    MOZ_ASSERT(this->state() == JS::PromiseState::Pending);
+    if (state) {
+        AddPromiseFlags(*this, PROMISE_FLAG_HAD_USER_INTERACTION_UPON_CREATION);
+    } else {
+        RemovePromiseFlags(*this, PROMISE_FLAG_HAD_USER_INTERACTION_UPON_CREATION);
+    }
+}
+
+void
+PromiseObject::copyUserInteractionFlagsFrom(PromiseObject& rhs)
+{
+    MOZ_ASSERT(state() == JS::PromiseState::Pending);
+    setRequiresUserInteractionHandling(rhs.requiresUserInteractionHandling());
+    setHadUserInteractionUponCreation(rhs.hadUserInteractionUponCreation());
+}
+
 JSFunction*
 js::PromiseLookup::getPromiseConstructor(JSContext* cx)
 {
     const Value& val = cx->global()->getConstructor(JSProto_Promise);
     return val.isObject() ? &val.toObject().as<JSFunction>() : nullptr;
 }
 
 NativeObject*
--- a/js/src/builtin/Promise.h
+++ b/js/src/builtin/Promise.h
@@ -59,16 +59,34 @@ enum PromiseSlots {
 
 // This promise uses the default resolving functions.
 // The PromiseSlot_RejectFunction slot is not used.
 #define PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS 0x08
 
 // This promise is the return value of an async function invocation.
 #define PROMISE_FLAG_ASYNC    0x10
 
+// This promise knows how to propagate information required to keep track of
+// whether an activation behavior was in progress when the original promise in
+// the promise chain was created.  This is a concept defined in the HTML spec:
+// https://html.spec.whatwg.org/multipage/interaction.html#triggered-by-user-activation
+// It is used by the embedder in order to request SpiderMonkey to keep track of
+// this information in a Promise, and also to propagate it to newly created
+// promises while processing Promise#then.
+#define PROMISE_FLAG_REQUIRES_USER_INTERACTION_HANDLING 0x20
+
+// This flag indicates whether an activation behavior was in progress when the
+// original promise in the promise chain was created.  Activation behavior is a
+// concept defined by the HTML spec:
+// https://html.spec.whatwg.org/multipage/interaction.html#triggered-by-user-activation
+// This flag is only effective when the
+// PROMISE_FLAG_REQUIRES_USER_INTERACTION_HANDLING is set.  Also, it is only
+// possible to set this flag on pending promises.
+#define PROMISE_FLAG_HAD_USER_INTERACTION_UPON_CREATION 0x40
+
 class AutoSetNewObjectMetadata;
 
 class PromiseObject : public NativeObject
 {
   public:
     static const unsigned RESERVED_SLOTS = PromiseSlots;
     static const Class class_;
     static const Class protoClass_;
@@ -131,16 +149,30 @@ class PromiseObject : public NativeObjec
 
     // Return the process-unique ID of this promise. Only used by the debugger.
     uint64_t getID();
 
     bool isUnhandled() {
         MOZ_ASSERT(state() == JS::PromiseState::Rejected);
         return !(flags() & PROMISE_FLAG_HANDLED);
     }
+
+    bool requiresUserInteractionHandling() {
+        return (flags() & PROMISE_FLAG_REQUIRES_USER_INTERACTION_HANDLING);
+    }
+
+    void setRequiresUserInteractionHandling(bool state);
+
+    bool hadUserInteractionUponCreation() {
+        return (flags() & PROMISE_FLAG_HAD_USER_INTERACTION_UPON_CREATION);
+    }
+
+    void setHadUserInteractionUponCreation(bool state);
+
+    void copyUserInteractionFlagsFrom(PromiseObject& rhs);
 };
 
 /**
  * Unforgeable version of the JS builtin Promise.all.
  *
  * Takes an AutoObjectVector of Promise objects and returns a promise that's
  * resolved with an array of resolution values when all those promises have
  * been resolved, or rejected with the rejection value of the first rejected
--- a/js/src/builtin/intl/CommonFunctions.h
+++ b/js/src/builtin/intl/CommonFunctions.h
@@ -132,18 +132,19 @@ using GetAvailable = const char* (*)(int
 
 /**
  * Return an object whose own property names are the locales indicated as
  * available by |countAvailable| that provides an overall count, and by
  * |getAvailable| that when called passing a number less than that count,
  * returns the corresponding locale as a borrowed string.  For example:
  *
  *   RootedValue v(cx);
- *   if (!GetAvailableLocales(cx, unum_countAvailable, unum_getAvailable, &v))
+ *   if (!GetAvailableLocales(cx, unum_countAvailable, unum_getAvailable, &v)) {
  *       return false;
+ *   }
  */
 extern bool
 GetAvailableLocales(JSContext* cx, CountAvailable countAvailable, GetAvailable getAvailable,
                     JS::MutableHandle<JS::Value> result);
 
 } // namespace intl
 
 } // namespace js
--- a/js/src/frontend/FoldConstants.h
+++ b/js/src/frontend/FoldConstants.h
@@ -19,20 +19,22 @@ template <class ParseHandler> class PerH
 // `print(2 + 2)` would become `print(4)`.
 //
 // pnp is the address of a pointer variable that points to the root node of the
 // AST. On success, *pnp points to the root node of the new tree, which may be
 // the same node (unchanged or modified in place) or a new node.
 //
 // Usage:
 //    pn = parser->statement();
-//    if (!pn)
+//    if (!pn) {
 //        return false;
-//    if (!FoldConstants(cx, &pn, parser))
+//    }
+//    if (!FoldConstants(cx, &pn, parser)) {
 //        return false;
+//    }
 extern MOZ_MUST_USE bool
 FoldConstants(JSContext* cx, ParseNode** pnp, PerHandlerParser<FullParseHandler>* parser);
 
 inline MOZ_MUST_USE bool
 FoldConstants(JSContext* cx, typename SyntaxParseHandler::Node* pnp,
               PerHandlerParser<SyntaxParseHandler>* parser)
 {
     return true;
--- a/js/src/frontend/JumpList.h
+++ b/js/src/frontend/JumpList.h
@@ -16,25 +16,28 @@ namespace frontend {
 
 // Linked list of jump instructions that need to be patched. The linked list is
 // stored in the bytes of the incomplete bytecode that will be patched, so no
 // extra memory is needed, and patching the instructions destroys the list.
 //
 // Example:
 //
 //     JumpList brList;
-//     if (!emitJump(JSOP_IFEQ, &brList))
+//     if (!emitJump(JSOP_IFEQ, &brList)) {
 //         return false;
+//     }
 //     ...
 //     JumpTarget label;
-//     if (!emitJumpTarget(&label))
+//     if (!emitJumpTarget(&label)) {
 //         return false;
+//     }
 //     ...
-//     if (!emitJump(JSOP_GOTO, &brList))
+//     if (!emitJump(JSOP_GOTO, &brList)) {
 //         return false;
+//     }
 //     ...
 //     patchJumpsToTarget(brList, label);
 //
 //                 +-> -1
 //                 |
 //                 |
 //    ifeq ..   <+ +                +-+   ifeq ..
 //    ..         |                  |     ..
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -800,29 +800,32 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE
      * When using PossibleError one should set a pending error at the location
      * where an error occurs. From that point, the error may be resolved
      * (invalidated) or left until the PossibleError is checked.
      *
      * Ex:
      *   PossibleError possibleError(*this);
      *   possibleError.setPendingExpressionErrorAt(pos, JSMSG_BAD_PROP_ID);
      *   // A JSMSG_BAD_PROP_ID ParseError is reported, returns false.
-     *   if (!possibleError.checkForExpressionError())
+     *   if (!possibleError.checkForExpressionError()) {
      *       return false; // we reach this point with a pending exception
+     *   }
      *
      *   PossibleError possibleError(*this);
      *   possibleError.setPendingExpressionErrorAt(pos, JSMSG_BAD_PROP_ID);
      *   // Returns true, no error is reported.
-     *   if (!possibleError.checkForDestructuringError())
+     *   if (!possibleError.checkForDestructuringError()) {
      *       return false; // not reached, no pending exception
+     *   }
      *
      *   PossibleError possibleError(*this);
      *   // Returns true, no error is reported.
-     *   if (!possibleError.checkForExpressionError())
+     *   if (!possibleError.checkForExpressionError()) {
      *       return false; // not reached, no pending exception
+     *   }
      */
     class MOZ_STACK_CLASS PossibleError
     {
       private:
         enum class ErrorKind { Expression, Destructuring, DestructuringWarning };
 
         enum class ErrorState { None, Pending };
 
--- a/js/src/gc/GC-inl.h
+++ b/js/src/gc/GC-inl.h
@@ -284,21 +284,23 @@ class ZoneCellIter<TenuredCell> {
         cellIter.next();
         settle();
     }
 };
 
 // Iterator over the cells in a Zone, where the GC type (JSString, JSObject) is
 // known, for a single AllocKind. Example usages:
 //
-//   for (auto obj = zone->cellIter<JSObject>(AllocKind::OBJECT0); !obj.done(); obj.next())
+//   for (auto obj = zone->cellIter<JSObject>(AllocKind::OBJECT0); !obj.done(); obj.next()) {
 //       ...
+//   }
 //
-//   for (auto script = zone->cellIter<JSScript>(); !script.done(); script.next())
+//   for (auto script = zone->cellIter<JSScript>(); !script.done(); script.next()) {
 //       f(script->code());
+//   }
 //
 // As this code demonstrates, you can use 'script' as if it were a JSScript*.
 // Its actual type is ZoneCellIter<JSScript>, but for most purposes it will
 // autoconvert to JSScript*.
 //
 // Note that in the JSScript case, ZoneCellIter is able to infer the AllocKind
 // from the type 'JSScript', whereas in the JSObject case, the kind must be
 // given (because there are multiple AllocKinds for objects).
--- a/js/src/jit/BaselineDebugModeOSR.h
+++ b/js/src/jit/BaselineDebugModeOSR.h
@@ -28,18 +28,19 @@ namespace jit {
 // It is usually used in fallback stubs which may trigger on-stack
 // recompilation by calling out into the VM. Example use:
 //
 //     DebugModeOSRVolatileStub<FallbackStubT*> stub(frame, stub_)
 //
 //     // Call out to the VM
 //     // Other effectful operations like TypeScript::Monitor
 //
-//     if (stub.invalid())
+//     if (stub.invalid()) {
 //         return true;
+//     }
 //
 //     // First use of stub after VM call.
 //
 template <typename T>
 class DebugModeOSRVolatileStub
 {
     T stub_;
     BaselineFrame* frame_;
--- a/js/src/jit/MacroAssembler.cpp
+++ b/js/src/jit/MacroAssembler.cpp
@@ -2822,18 +2822,19 @@ MacroAssembler::alignJitStackBasedOnNArg
     // Which implies that |argN| is aligned if |nargs| is even, and offset by
     // |sizeof(Value)| if |nargs| is odd.
     MOZ_ASSERT(JitStackValueAlignment == 2);
 
     // Thus the |padding| is offset by |sizeof(Value)| if |nargs| is even, and
     // aligned if |nargs| is odd.
 
     // if (nargs % 2 == 0) {
-    //     if (sp % JitStackAlignment == 0)
+    //     if (sp % JitStackAlignment == 0) {
     //         sp -= sizeof(Value);
+    //     }
     //     MOZ_ASSERT(sp % JitStackAlignment == JitStackAlignment - sizeof(Value));
     // } else {
     //     sp = sp & ~(JitStackAlignment - 1);
     // }
     Label odd, end;
     Label* maybeAssert = &end;
 #ifdef DEBUG
     Label assert;
--- a/js/src/jit/OptimizationTracking.cpp
+++ b/js/src/jit/OptimizationTracking.cpp
@@ -1266,18 +1266,19 @@ IonTrackedOptimizationsTypeInfo::ForEach
             // debug spewing of all optimization information), it needs to be
             // converted to an offset from the beginning of the shared library
             // for use with utilities like `addr2line` on Linux and `atos` on
             // OS X. Converting to an offset may be done via dladdr():
             //
             //   void* addr = JS_FUNC_TO_DATA_PTR(void*, fun->native());
             //   uintptr_t offset;
             //   Dl_info info;
-            //   if (dladdr(addr, &info) != 0)
+            //   if (dladdr(addr, &info) != 0) {
             //       offset = uintptr_t(addr) - uintptr_t(info.dli_fbase);
+            //   }
             //
             char locationBuf[20];
             if (!name) {
                 uintptr_t addr = JS_FUNC_TO_DATA_PTR(uintptr_t, fun->native());
                 snprintf(locationBuf, mozilla::ArrayLength(locationBuf), "%" PRIxPTR, addr);
             }
             op_.readType("native", name, name ? nullptr : locationBuf, Nothing());
             return;
--- a/js/src/jit/arm/Lowering-arm.cpp
+++ b/js/src/jit/arm/Lowering-arm.cpp
@@ -263,34 +263,16 @@ LIRGeneratorARM::lowerForBitAndAndBranch
                                          MDefinition* lhs, MDefinition* rhs)
 {
     baab->setOperand(0, useRegisterAtStart(lhs));
     baab->setOperand(1, useRegisterOrConstantAtStart(rhs));
     add(baab, mir);
 }
 
 void
-LIRGeneratorARM::defineUntypedPhi(MPhi* phi, size_t lirIndex)
-{
-    LPhi* type = current->getPhi(lirIndex + VREG_TYPE_OFFSET);
-    LPhi* payload = current->getPhi(lirIndex + VREG_DATA_OFFSET);
-
-    uint32_t typeVreg = getVirtualRegister();
-    phi->setVirtualRegister(typeVreg);
-
-    uint32_t payloadVreg = getVirtualRegister();
-    MOZ_ASSERT(typeVreg + 1 == payloadVreg);
-
-    type->setDef(0, LDefinition(typeVreg, LDefinition::TYPE));
-    payload->setDef(0, LDefinition(payloadVreg, LDefinition::PAYLOAD));
-    annotate(type);
-    annotate(payload);
-}
-
-void
 LIRGeneratorARM::lowerUntypedPhiInput(MPhi* phi, uint32_t inputPosition, LBlock* block, size_t lirIndex)
 {
     MDefinition* operand = phi->getOperand(inputPosition);
     LPhi* type = block->getPhi(lirIndex + VREG_TYPE_OFFSET);
     LPhi* payload = block->getPhi(lirIndex + VREG_DATA_OFFSET);
     type->setOperand(inputPosition, LUse(operand->virtualRegister() + VREG_TYPE_OFFSET, LUse::ANY));
     payload->setOperand(inputPosition, LUse(VirtualRegisterOfPayload(operand), LUse::ANY));
 }
--- a/js/src/jit/arm/Lowering-arm.h
+++ b/js/src/jit/arm/Lowering-arm.h
@@ -32,17 +32,16 @@ class LIRGeneratorARM : public LIRGenera
 
     inline LDefinition tempToUnbox() {
         return LDefinition::BogusTemp();
     }
 
     bool needTempForPostBarrier() { return false; }
 
     void lowerUntypedPhiInput(MPhi* phi, uint32_t inputPosition, LBlock* block, size_t lirIndex);
-    void defineUntypedPhi(MPhi* phi, size_t lirIndex);
     void lowerInt64PhiInput(MPhi* phi, uint32_t inputPosition, LBlock* block, size_t lirIndex);
     void defineInt64Phi(MPhi* phi, size_t lirIndex);
 
     void lowerForShift(LInstructionHelper<1, 2, 0>* ins, MDefinition* mir, MDefinition* lhs,
                        MDefinition* rhs);
     void lowerUrshD(MUrsh* mir);
 
     void lowerForALU(LInstructionHelper<1, 1, 0>* ins, MDefinition* mir,
--- a/js/src/jit/arm64/Architecture-arm64.h
+++ b/js/src/jit/arm64/Architecture-arm64.h
@@ -127,16 +127,17 @@ class Registers {
 
     static const Code Invalid = invalid_reg;
 
     static const uint32_t Total = 32;
     static const uint32_t TotalPhys = 32;
     static const uint32_t Allocatable = 27; // No named special-function registers.
 
     static const SetType AllMask = 0xFFFFFFFF;
+    static const SetType NoneMask = 0x0;
 
     static const SetType ArgRegMask =
         (1 << Registers::x0) | (1 << Registers::x1) |
         (1 << Registers::x2) | (1 << Registers::x3) |
         (1 << Registers::x4) | (1 << Registers::x5) |
         (1 << Registers::x6) | (1 << Registers::x7) |
         (1 << Registers::x8);
 
@@ -246,16 +247,17 @@ class FloatRegisters
 
     static Code FromName(const char* name);
 
     static const Code Invalid = invalid_fpreg;
 
     static const uint32_t Total = 64;
     static const uint32_t TotalPhys = 32;
     static const SetType AllMask = 0xFFFFFFFFFFFFFFFFULL;
+    static const SetType NoneMask = 0x0ULL;
     static const SetType AllPhysMask = 0xFFFFFFFFULL;
     static const SetType SpreadCoefficient = 0x100000001ULL;
 
     static const uint32_t Allocatable = 31; // Without d31, the scratch register.
 
     // d31 is the ScratchFloatReg.
     static const SetType NonVolatileMask =
         SetType((1 << FloatRegisters::d8) | (1 << FloatRegisters::d9) |
--- a/js/src/jit/arm64/CodeGenerator-arm64.cpp
+++ b/js/src/jit/arm64/CodeGenerator-arm64.cpp
@@ -43,17 +43,17 @@ CodeGeneratorARM64::generateOutOfLineCod
         return false;
     }
 
     if (deoptLabel_.used()) {
         // All non-table-based bailouts will go here.
         masm.bind(&deoptLabel_);
 
         // Store the frame size, so the handler can recover the IonScript.
-        masm.Mov(x30, frameSize());
+        masm.push(Imm32(frameSize()));
 
         TrampolinePtr handler = gen->jitRuntime()->getGenericBailoutHandler();
         masm.jump(handler);
     }
 
     return !masm.oom();
 }
 
@@ -153,18 +153,18 @@ CodeGeneratorARM64::bailoutIf(Assembler:
     addOutOfLineCode(ool, new(alloc()) BytecodeSite(tree, tree->script()->code()));
 
     masm.B(ool->entry(), condition);
 }
 
 void
 CodeGeneratorARM64::bailoutFrom(Label* label, LSnapshot* snapshot)
 {
-    MOZ_ASSERT(label->used());
-    MOZ_ASSERT(!label->bound());
+    MOZ_ASSERT_IF(!masm.oom(), label->used());
+    MOZ_ASSERT_IF(!masm.oom(), !label->bound());
 
     encode(snapshot);
 
     // Though the assembler doesn't track all frame pushes, at least make sure
     // the known value makes sense.
     MOZ_ASSERT_IF(frameClass_ != FrameSizeClass::None() && deoptTable_,
                   frameClass_.frameSize() == masm.framePushed());
 
--- a/js/src/jit/arm64/Lowering-arm64.cpp
+++ b/js/src/jit/arm64/Lowering-arm64.cpp
@@ -176,22 +176,16 @@ template void LIRGeneratorARM64::lowerFo
 void
 LIRGeneratorARM64::lowerForBitAndAndBranch(LBitAndAndBranch* baab, MInstruction* mir,
                                          MDefinition* lhs, MDefinition* rhs)
 {
     MOZ_CRASH("lowerForBitAndAndBranch");
 }
 
 void
-LIRGeneratorARM64::defineUntypedPhi(MPhi* phi, size_t lirIndex)
-{
-    defineTypedPhi(phi, lirIndex);
-}
-
-void
 LIRGeneratorARM64::lowerUntypedPhiInput(MPhi* phi, uint32_t inputPosition,
                                         LBlock* block, size_t lirIndex)
 {
     lowerTypedPhiInput(phi, inputPosition, block, lirIndex);
 }
 
 void
 LIRGeneratorARM64::lowerForShift(LInstructionHelper<1, 2, 0>* ins,
--- a/js/src/jit/arm64/Lowering-arm64.h
+++ b/js/src/jit/arm64/Lowering-arm64.h
@@ -34,17 +34,16 @@ class LIRGeneratorARM64 : public LIRGene
     bool needTempForPostBarrier() { return true; }
 
     // ARM64 has a scratch register, so no need for another temp for dispatch ICs.
     LDefinition tempForDispatchCache(MIRType outputType = MIRType::None) {
         return LDefinition::BogusTemp();
     }
 
     void lowerUntypedPhiInput(MPhi* phi, uint32_t inputPosition, LBlock* block, size_t lirIndex);
-    void defineUntypedPhi(MPhi* phi, size_t lirIndex);
     void lowerInt64PhiInput(MPhi*, uint32_t, LBlock*, size_t) { MOZ_CRASH("NYI"); }
     void defineInt64Phi(MPhi*, size_t) { MOZ_CRASH("NYI"); }
     void lowerForShift(LInstructionHelper<1, 2, 0>* ins, MDefinition* mir, MDefinition* lhs,
                        MDefinition* rhs);
     void lowerUrshD(MUrsh* mir);
 
     void lowerForALU(LInstructionHelper<1, 1, 0>* ins, MDefinition* mir, MDefinition* input);
     void lowerForALU(LInstructionHelper<1, 2, 0>* ins, MDefinition* mir,
--- a/js/src/jit/arm64/Trampoline-arm64.cpp
+++ b/js/src/jit/arm64/Trampoline-arm64.cpp
@@ -17,19 +17,21 @@
 #include "jit/MacroAssembler-inl.h"
 #include "jit/SharedICHelpers-inl.h"
 
 using namespace js;
 using namespace js::jit;
 
 // All registers to save and restore. This includes the stack pointer, since we
 // use the ability to reference register values on the stack by index.
+// TODO: This is almost certainly incorrect.
+// TODO: All uses need auditing.
 static const LiveRegisterSet AllRegs =
     LiveRegisterSet(GeneralRegisterSet(Registers::AllMask & ~(1 << 31 | 1 << 30 | 1 << 29| 1 << 28)),
-                FloatRegisterSet(FloatRegisters::AllMask));
+                    FloatRegisterSet(FloatRegisters::AllMask));
 
 /* This method generates a trampoline on ARM64 for a c++ function with
  * the following signature:
  *   bool blah(void* code, int argc, Value* argv, JSObject* scopeChain, Value* vp)
  *   ...using standard AArch64 calling convention
  */
 void
 JitRuntime::generateEnterJIT(JSContext* cx, MacroAssembler& masm)
@@ -410,119 +412,109 @@ JitRuntime::generateArgumentsRectifier(M
     masm.Add(masm.GetStackPointer64(), masm.GetStackPointer64(),
              Operand(x4, vixl::LSR, FRAMESIZE_SHIFT));
 
     // Pop the return address from earlier and branch.
     masm.ret();
 }
 
 static void
-PushBailoutFrame(MacroAssembler& masm, uint32_t frameClass, Register spArg)
+PushBailoutFrame(MacroAssembler& masm, Register spArg)
 {
-    // the stack should look like:
-    // [IonFrame]
-    // bailoutFrame.registersnapshot
-    // bailoutFrame.fpsnapshot
-    // bailoutFrame.snapshotOffset
-    // bailoutFrame.frameSize
+    const LiveRegisterSet First28GeneralRegisters =
+        LiveRegisterSet(GeneralRegisterSet(
+                            Registers::AllMask & ~(1 << 31 | 1 << 30 | 1 << 29 | 1 << 28)),
+                        FloatRegisterSet(FloatRegisters::NoneMask));
 
-    // STEP 1a: Save our register sets to the stack so Bailout() can read
-    // everything.
-    // sp % 8 == 0
-
-    // We don't have to push everything, but this is likely easier.
-    // Setting regs_.
-    masm.subFromStackPtr(Imm32(Registers::TotalPhys * sizeof(void*)));
-    for (uint32_t i = 0; i < Registers::TotalPhys; i += 2) {
-        masm.Stp(ARMRegister::XRegFromCode(i),
-                 ARMRegister::XRegFromCode(i + 1),
-                 MemOperand(masm.GetStackPointer64(), i * sizeof(void*)));
-    }
+    const LiveRegisterSet AllFloatRegisters =
+        LiveRegisterSet(GeneralRegisterSet(Registers::NoneMask),
+                        FloatRegisterSet(FloatRegisters::AllMask));
 
-    // Since our datastructures for stack inspection are compile-time fixed,
-    // if there are only 16 double registers, then we need to reserve
-    // space on the stack for the missing 16.
-    masm.subFromStackPtr(Imm32(FloatRegisters::TotalPhys * sizeof(double)));
-    for (uint32_t i = 0; i < FloatRegisters::TotalPhys; i += 2) {
-        masm.Stp(ARMFPRegister::DRegFromCode(i),
-                 ARMFPRegister::DRegFromCode(i + 1),
-                 MemOperand(masm.GetStackPointer64(), i * sizeof(void*)));
-    }
+    // Push all general-purpose registers.
+    //
+    // The bailout frame expects all registers to be present, including SP.
+    // But the ARM64 ABI does not treat SP as a normal register that can
+    // be pushed. So pushing happens in two phases.
+    //
+    // Registers are pushed in reverse order of code.
+
+    // First, push the last four registers, passing zero for sp.
+    // Zero is pushed for x28 and x31: the pseudo-SP and SP, respectively.
+    masm.asVIXL().Push(xzr, x30, x29, xzr);
 
-    // STEP 1b: Push both the "return address" of the function call (the address
-    //          of the instruction after the call that we used to get here) as
-    //          well as the callee token onto the stack. The return address is
-    //          currently in r14. We will proceed by loading the callee token
-    //          into a sacrificial register <= r14, then pushing both onto the
-    //          stack.
+    // Second, push the first 28 registers that serve no special purpose.
+    masm.PushRegsInMask(First28GeneralRegisters);
+
+    // Finally, push all floating-point registers, completing the BailoutStack.
+    masm.PushRegsInMask(AllFloatRegisters);
 
-    // Now place the frameClass onto the stack, via a register.
-    masm.Mov(x9, frameClass);
-
-    // And onto the stack. Since the stack is full, we need to put this one past
-    // the end of the current stack. Sadly, the ABI says that we need to always
-    // point to the lowest place that has been written. The OS is free to do
-    // whatever it wants below sp.
-    masm.push(r30, r9);
+    // The stack saved in spArg must be (higher entries have higher memory addresses):
+    // - snapshotOffset_
+    // - frameSize_
+    // - regs_
+    // - fpregs_ (spArg + 0)
     masm.moveStackPtrTo(spArg);
 }
 
 static void
-GenerateBailoutThunk(MacroAssembler& masm, uint32_t frameClass, Label* bailoutTail)
+GenerateBailoutThunk(MacroAssembler& masm, Label* bailoutTail)
 {
-    PushBailoutFrame(masm, frameClass, r0);
+    PushBailoutFrame(masm, r0);
 
     // SP % 8 == 4
     // STEP 1c: Call the bailout function, giving a pointer to the
     //          structure we just blitted onto the stack.
     // Make space for the BaselineBailoutInfo* outparam.
-    const int sizeOfBailoutInfo = sizeof(void*) * 2;
-    masm.reserveStack(sizeOfBailoutInfo);
+    masm.reserveStack(sizeof(void*));
     masm.moveStackPtrTo(r1);
 
     masm.setupUnalignedABICall(r2);
     masm.passABIArg(r0);
     masm.passABIArg(r1);
     masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, Bailout), MoveOp::GENERAL,
                      CheckUnsafeCallWithABI::DontCheckOther);
 
-    masm.Ldr(x2, MemOperand(masm.GetStackPointer64(), 0));
-    masm.addToStackPtr(Imm32(sizeOfBailoutInfo));
+    // Get the bailoutInfo outparam.
+    masm.pop(r2);
 
-    static const uint32_t BailoutDataSize = sizeof(void*) * Registers::Total +
-                                            sizeof(double) * FloatRegisters::TotalPhys;
-
-    if (frameClass == NO_FRAME_SIZE_CLASS_ID) {
-        vixl::UseScratchRegisterScope temps(&masm.asVIXL());
-        const ARMRegister scratch64 = temps.AcquireX();
+    // Stack is:
+    //     [frame]
+    //     snapshotOffset
+    //     frameSize
+    //     [bailoutFrame]
+    //
+    // We want to remove both the bailout frame and the topmost Ion frame's stack.
 
-        masm.Ldr(scratch64, MemOperand(masm.GetStackPointer64(), sizeof(uintptr_t)));
-        masm.addToStackPtr(Imm32(BailoutDataSize + 32));
-        masm.addToStackPtr(scratch64.asUnsized());
-    } else {
-        uint32_t frameSize = FrameSizeClass::FromClass(frameClass).frameSize();
-        masm.addToStackPtr(Imm32(frameSize + BailoutDataSize + sizeof(void*)));
-    }
+    // Remove the bailoutFrame.
+    static const uint32_t BailoutDataSize = sizeof(RegisterDump);
+    masm.addToStackPtr(Imm32(BailoutDataSize));
 
-    // Jump to shared bailout tail. The BailoutInfo pointer has to be in r9.
+    // Pop the frame, snapshotOffset, and frameSize.
+    vixl::UseScratchRegisterScope temps(&masm.asVIXL());
+    const ARMRegister scratch64 = temps.AcquireX();
+    masm.Ldr(scratch64, MemOperand(masm.GetStackPointer64(), 0x0));
+    masm.addPtr(Imm32(2 * sizeof(void*)), scratch64.asUnsized());
+    masm.addToStackPtr(scratch64.asUnsized());
+
+    // Jump to shared bailout tail. The BailoutInfo pointer has to be in r2.
     masm.jump(bailoutTail);
 }
 
 JitRuntime::BailoutTable
 JitRuntime::generateBailoutTable(MacroAssembler& masm, Label* bailoutTail, uint32_t frameClass)
 {
     MOZ_CRASH("arm64 does not use bailout tables");
 }
 
 void
 JitRuntime::generateBailoutHandler(MacroAssembler& masm, Label* bailoutTail)
 {
     bailoutHandlerOffset_ = startTrampolineCode(masm);
 
-    GenerateBailoutThunk(masm, NO_FRAME_SIZE_CLASS_ID, bailoutTail);
+    GenerateBailoutThunk(masm, bailoutTail);
 }
 
 bool
 JitRuntime::generateVMWrapper(JSContext* cx, MacroAssembler& masm, const VMFunction& f)
 {
     MOZ_ASSERT(functionWrappers_);
 
     uint32_t wrapperOffset = startTrampolineCode(masm);
--- a/js/src/jit/mips32/Lowering-mips32.cpp
+++ b/js/src/jit/mips32/Lowering-mips32.cpp
@@ -123,34 +123,16 @@ LIRGenerator::visitReturn(MReturn* ret)
     LReturn* ins = new(alloc()) LReturn;
     ins->setOperand(0, LUse(JSReturnReg_Type));
     ins->setOperand(1, LUse(JSReturnReg_Data));
     fillBoxUses(ins, 0, opd);
     add(ins);
 }
 
 void
-LIRGeneratorMIPS::defineUntypedPhi(MPhi* phi, size_t lirIndex)
-{
-    LPhi* type = current->getPhi(lirIndex + VREG_TYPE_OFFSET);
-    LPhi* payload = current->getPhi(lirIndex + VREG_DATA_OFFSET);
-
-    uint32_t typeVreg = getVirtualRegister();
-    phi->setVirtualRegister(typeVreg);
-
-    uint32_t payloadVreg = getVirtualRegister();
-    MOZ_ASSERT(typeVreg + 1 == payloadVreg);
-
-    type->setDef(0, LDefinition(typeVreg, LDefinition::TYPE));
-    payload->setDef(0, LDefinition(payloadVreg, LDefinition::PAYLOAD));
-    annotate(type);
-    annotate(payload);
-}
-
-void
 LIRGeneratorMIPS::lowerUntypedPhiInput(MPhi* phi, uint32_t inputPosition,
                                        LBlock* block, size_t lirIndex)
 {
     MDefinition* operand = phi->getOperand(inputPosition);
     LPhi* type = block->getPhi(lirIndex + VREG_TYPE_OFFSET);
     LPhi* payload = block->getPhi(lirIndex + VREG_DATA_OFFSET);
     type->setOperand(inputPosition, LUse(operand->virtualRegister() + VREG_TYPE_OFFSET,
                                          LUse::ANY));
--- a/js/src/jit/mips32/Lowering-mips32.h
+++ b/js/src/jit/mips32/Lowering-mips32.h
@@ -23,17 +23,16 @@ class LIRGeneratorMIPS : public LIRGener
     LBoxAllocation useBoxFixed(MDefinition* mir, Register reg1, Register reg2,
                                bool useAtStart = false);
 
     inline LDefinition tempToUnbox() {
         return LDefinition::BogusTemp();
     }
 
     void lowerUntypedPhiInput(MPhi* phi, uint32_t inputPosition, LBlock* block, size_t lirIndex);
-    void defineUntypedPhi(MPhi* phi, size_t lirIndex);
 
     void lowerInt64PhiInput(MPhi* phi, uint32_t inputPosition, LBlock* block, size_t lirIndex);
     void defineInt64Phi(MPhi* phi, size_t lirIndex);
 
     void lowerTruncateDToInt32(MTruncateToInt32* ins);
     void lowerTruncateFToInt32(MTruncateToInt32* ins);
 
     void lowerDivI64(MDiv* div);
--- a/js/src/jit/mips64/Lowering-mips64.cpp
+++ b/js/src/jit/mips64/Lowering-mips64.cpp
@@ -141,22 +141,16 @@ LIRGenerator::visitReturn(MReturn* ret)
     MOZ_ASSERT(opd->type() == MIRType::Value);
 
     LReturn* ins = new(alloc()) LReturn;
     ins->setOperand(0, useFixed(opd, JSReturnReg));
     add(ins);
 }
 
 void
-LIRGeneratorMIPS64::defineUntypedPhi(MPhi* phi, size_t lirIndex)
-{
-    defineTypedPhi(phi, lirIndex);
-}
-
-void
 LIRGeneratorMIPS64::lowerUntypedPhiInput(MPhi* phi, uint32_t inputPosition,
                                          LBlock* block, size_t lirIndex)
 {
     lowerTypedPhiInput(phi, inputPosition, block, lirIndex);
 }
 
 void
 LIRGeneratorMIPS64::lowerTruncateDToInt32(MTruncateToInt32* ins)
--- a/js/src/jit/mips64/Lowering-mips64.h
+++ b/js/src/jit/mips64/Lowering-mips64.h
@@ -26,17 +26,16 @@ class LIRGeneratorMIPS64 : public LIRGen
     LBoxAllocation useBoxFixed(MDefinition* mir, Register reg1, Register reg2,
                                bool useAtStart = false);
 
     inline LDefinition tempToUnbox() {
         return temp();
     }
 
     void lowerUntypedPhiInput(MPhi* phi, uint32_t inputPosition, LBlock* block, size_t lirIndex);
-    void defineUntypedPhi(MPhi* phi, size_t lirIndex);
 
     void lowerTruncateDToInt32(MTruncateToInt32* ins);
     void lowerTruncateFToInt32(MTruncateToInt32* ins);
 
     void lowerDivI64(MDiv* div);
     void lowerModI64(MMod* mod);
     void lowerUDivI64(MDiv* div);
     void lowerUModI64(MMod* mod);
--- a/js/src/jit/none/Lowering-none.h
+++ b/js/src/jit/none/Lowering-none.h
@@ -26,17 +26,16 @@ class LIRGeneratorNone : public LIRGener
     LAllocation useByteOpRegister(MDefinition*) { MOZ_CRASH(); }
     LAllocation useByteOpRegisterAtStart(MDefinition*) { MOZ_CRASH(); }
     LAllocation useByteOpRegisterOrNonDoubleConstant(MDefinition*) { MOZ_CRASH(); }
     LDefinition tempByteOpRegister() { MOZ_CRASH(); }
     LDefinition tempToUnbox() { MOZ_CRASH(); }
     bool needTempForPostBarrier() { MOZ_CRASH(); }
     void lowerUntypedPhiInput(MPhi*, uint32_t, LBlock*, size_t) { MOZ_CRASH(); }
     void lowerInt64PhiInput(MPhi*, uint32_t, LBlock*, size_t) { MOZ_CRASH(); }
-    void defineUntypedPhi(MPhi*, size_t) { MOZ_CRASH(); }
     void defineInt64Phi(MPhi*, size_t) { MOZ_CRASH(); }
     void lowerForShift(LInstructionHelper<1, 2, 0>*, MDefinition*, MDefinition*, MDefinition*) {
         MOZ_CRASH();
     }
     void lowerUrshD(MUrsh*) { MOZ_CRASH(); }
     template <typename T>
     void lowerForALU(T, MDefinition*, MDefinition*, MDefinition* v = nullptr) { MOZ_CRASH(); }
     template <typename T>
--- a/js/src/jit/shared/Lowering-shared.cpp
+++ b/js/src/jit/shared/Lowering-shared.cpp
@@ -72,27 +72,47 @@ LIRGeneratorShared::ReorderCommutative(M
 
     if (ShouldReorderCommutative(lhs, rhs, ins)) {
         *rhsp = lhs;
         *lhsp = rhs;
     }
 }
 
 void
-LIRGeneratorShared::defineTypedPhi(MPhi* phi, size_t lirIndex)
+LIRGeneratorShared::definePhiOneRegister(MPhi* phi, size_t lirIndex)
 {
     LPhi* lir = current->getPhi(lirIndex);
 
     uint32_t vreg = getVirtualRegister();
 
     phi->setVirtualRegister(vreg);
     lir->setDef(0, LDefinition(vreg, LDefinition::TypeFrom(phi->type())));
     annotate(lir);
 }
 
+#ifdef JS_NUNBOX32
+void
+LIRGeneratorShared::definePhiTwoRegisters(MPhi* phi, size_t lirIndex)
+{
+    LPhi* type = current->getPhi(lirIndex + VREG_TYPE_OFFSET);
+    LPhi* payload = current->getPhi(lirIndex + VREG_DATA_OFFSET);
+
+    uint32_t typeVreg = getVirtualRegister();
+    phi->setVirtualRegister(typeVreg);
+
+    uint32_t payloadVreg = getVirtualRegister();
+    MOZ_ASSERT(typeVreg + 1 == payloadVreg);
+
+    type->setDef(0, LDefinition(typeVreg, LDefinition::TYPE));
+    payload->setDef(0, LDefinition(payloadVreg, LDefinition::PAYLOAD));
+    annotate(type);
+    annotate(payload);
+}
+#endif
+
 void
 LIRGeneratorShared::lowerTypedPhiInput(MPhi* phi, uint32_t inputPosition, LBlock* block, size_t lirIndex)
 {
     MDefinition* operand = phi->getOperand(inputPosition);
     LPhi* lir = block->getPhi(lirIndex);
     lir->setOperand(inputPosition, LUse(operand->virtualRegister(), LUse::ANY));
 }
 
--- a/js/src/jit/shared/Lowering-shared.h
+++ b/js/src/jit/shared/Lowering-shared.h
@@ -262,17 +262,35 @@ class LIRGeneratorShared
         }
         return vreg;
     }
 
     template <typename T> void annotate(T* ins);
     template <typename T> void add(T* ins, MInstruction* mir = nullptr);
 
     void lowerTypedPhiInput(MPhi* phi, uint32_t inputPosition, LBlock* block, size_t lirIndex);
-    void defineTypedPhi(MPhi* phi, size_t lirIndex);
+
+    void definePhiOneRegister(MPhi* phi, size_t lirIndex);
+#ifdef JS_NUNBOX32
+    void definePhiTwoRegisters(MPhi* phi, size_t lirIndex);
+#endif
+
+    void defineTypedPhi(MPhi* phi, size_t lirIndex) {
+        // One register containing the payload.
+        definePhiOneRegister(phi, lirIndex);
+    }
+    void defineUntypedPhi(MPhi* phi, size_t lirIndex) {
+#ifdef JS_NUNBOX32
+        // Two registers: one for the type, one for the payload.
+        definePhiTwoRegisters(phi, lirIndex);
+#else
+        // One register containing the full Value.
+        definePhiOneRegister(phi, lirIndex);
+#endif
+    }
 
     LOsiPoint* popOsiPoint() {
         LOsiPoint* tmp = osiPoint_;
         osiPoint_ = nullptr;
         return tmp;
     }
 
     LRecoverInfo* getRecoverInfo(MResumePoint* rp);
--- a/js/src/jit/x64/Lowering-x64.cpp
+++ b/js/src/jit/x64/Lowering-x64.cpp
@@ -134,22 +134,16 @@ LIRGenerator::visitReturn(MReturn* ret)
     MOZ_ASSERT(opd->type() == MIRType::Value);
 
     LReturn* ins = new(alloc()) LReturn;
     ins->setOperand(0, useFixed(opd, JSReturnReg));
     add(ins);
 }
 
 void
-LIRGeneratorX64::defineUntypedPhi(MPhi* phi, size_t lirIndex)
-{
-    defineTypedPhi(phi, lirIndex);
-}
-
-void
 LIRGeneratorX64::lowerUntypedPhiInput(MPhi* phi, uint32_t inputPosition, LBlock* block, size_t lirIndex)
 {
     lowerTypedPhiInput(phi, inputPosition, block, lirIndex);
 }
 
 void
 LIRGeneratorX64::defineInt64Phi(MPhi* phi, size_t lirIndex)
 {
--- a/js/src/jit/x64/Lowering-x64.h
+++ b/js/src/jit/x64/Lowering-x64.h
@@ -15,17 +15,16 @@ namespace jit {
 class LIRGeneratorX64 : public LIRGeneratorX86Shared
 {
   protected:
     LIRGeneratorX64(MIRGenerator* gen, MIRGraph& graph, LIRGraph& lirGraph)
       : LIRGeneratorX86Shared(gen, graph, lirGraph)
     { }
 
     void lowerUntypedPhiInput(MPhi* phi, uint32_t inputPosition, LBlock* block, size_t lirIndex);
-    void defineUntypedPhi(MPhi* phi, size_t lirIndex);
     void lowerInt64PhiInput(MPhi* phi, uint32_t inputPosition, LBlock* block, size_t lirIndex);
     void defineInt64Phi(MPhi* phi, size_t lirIndex);
 
     void lowerForALUInt64(LInstructionHelper<INT64_PIECES, 2 * INT64_PIECES, 0>* ins,
                           MDefinition* mir, MDefinition* lhs, MDefinition* rhs);
     void lowerForMulInt64(LMulI64* ins, MMul* mir, MDefinition* lhs, MDefinition* rhs);
 
     // Returns a box allocation. reg2 is ignored on 64-bit platforms.
--- a/js/src/jit/x86/Lowering-x86.cpp
+++ b/js/src/jit/x86/Lowering-x86.cpp
@@ -158,35 +158,16 @@ LIRGenerator::visitReturn(MReturn* ret)
     LReturn* ins = new(alloc()) LReturn;
     ins->setOperand(0, LUse(JSReturnReg_Type));
     ins->setOperand(1, LUse(JSReturnReg_Data));
     fillBoxUses(ins, 0, opd);
     add(ins);
 }
 
 void
-LIRGeneratorX86::defineUntypedPhi(MPhi* phi, size_t lirIndex)
-{
-    LPhi* type = current->getPhi(lirIndex + VREG_TYPE_OFFSET);
-    LPhi* payload = current->getPhi(lirIndex + VREG_DATA_OFFSET);
-
-    uint32_t typeVreg = getVirtualRegister();
-
-    phi->setVirtualRegister(typeVreg);
-
-    uint32_t payloadVreg = getVirtualRegister();
-    MOZ_ASSERT(typeVreg + 1 == payloadVreg);
-
-    type->setDef(0, LDefinition(typeVreg, LDefinition::TYPE));
-    payload->setDef(0, LDefinition(payloadVreg, LDefinition::PAYLOAD));
-    annotate(type);
-    annotate(payload);
-}
-
-void
 LIRGeneratorX86::lowerUntypedPhiInput(MPhi* phi, uint32_t inputPosition, LBlock* block, size_t lirIndex)
 {
     MDefinition* operand = phi->getOperand(inputPosition);
     LPhi* type = block->getPhi(lirIndex + VREG_TYPE_OFFSET);
     LPhi* payload = block->getPhi(lirIndex + VREG_DATA_OFFSET);
     type->setOperand(inputPosition, LUse(operand->virtualRegister() + VREG_TYPE_OFFSET, LUse::ANY));
     payload->setOperand(inputPosition, LUse(VirtualRegisterOfPayload(operand), LUse::ANY));
 }
--- a/js/src/jit/x86/Lowering-x86.h
+++ b/js/src/jit/x86/Lowering-x86.h
@@ -35,17 +35,16 @@ class LIRGeneratorX86 : public LIRGenera
 
     inline LDefinition tempToUnbox() {
         return LDefinition::BogusTemp();
     }
 
     bool needTempForPostBarrier() { return true; }
 
     void lowerUntypedPhiInput(MPhi* phi, uint32_t inputPosition, LBlock* block, size_t lirIndex);
-    void defineUntypedPhi(MPhi* phi, size_t lirIndex);
 
     void lowerInt64PhiInput(MPhi* phi, uint32_t inputPosition, LBlock* block, size_t lirIndex);
     void defineInt64Phi(MPhi* phi, size_t lirIndex);
 
     void lowerForALUInt64(LInstructionHelper<INT64_PIECES, 2 * INT64_PIECES, 0>* ins,
                           MDefinition* mir, MDefinition* lhs, MDefinition* rhs);
     void lowerForMulInt64(LMulI64* ins, MMul* mir, MDefinition* lhs, MDefinition* rhs);
 
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4547,16 +4547,67 @@ JS::AddPromiseReactions(JSContext* cx, J
 {
     RootedObject resultPromise(cx);
     bool result = CallOriginalPromiseThenImpl(cx, promiseObj, onResolvedObj, onRejectedObj,
                                               &resultPromise, CreateDependentPromise::Never);
     MOZ_ASSERT(!resultPromise);
     return result;
 }
 
+JS_PUBLIC_API(JS::PromiseUserInputEventHandlingState)
+JS::GetPromiseUserInputEventHandlingState(JS::HandleObject promiseObj_)
+{
+    JSObject* promiseObj = CheckedUnwrap(promiseObj_);
+    if (!promiseObj || !promiseObj->is<PromiseObject>()) {
+        return JS::PromiseUserInputEventHandlingState::DontCare;
+    }
+
+    auto& promise = promiseObj->as<PromiseObject>();
+    if (!promise.requiresUserInteractionHandling()) {
+      return JS::PromiseUserInputEventHandlingState::DontCare;
+    }
+    if (promise.hadUserInteractionUponCreation()) {
+      return JS::PromiseUserInputEventHandlingState::HadUserInteractionAtCreation;
+    }
+    return JS::PromiseUserInputEventHandlingState::DidntHaveUserInteractionAtCreation;
+}
+
+JS_PUBLIC_API(bool)
+JS::SetPromiseUserInputEventHandlingState(JS::HandleObject promiseObj_,
+                                          JS::PromiseUserInputEventHandlingState state)
+{
+    JSObject* promiseObj = CheckedUnwrap(promiseObj_);
+    if (!promiseObj || !promiseObj->is<PromiseObject>()) {
+        return false;
+    }
+
+    auto& promise = promiseObj->as<PromiseObject>();
+    if (promise.state() != JS::PromiseState::Pending) {
+      return false;
+    }
+
+    switch (state) {
+      case JS::PromiseUserInputEventHandlingState::DontCare:
+        promise.setRequiresUserInteractionHandling(false);
+        break;
+      case JS::PromiseUserInputEventHandlingState::HadUserInteractionAtCreation:
+        promise.setRequiresUserInteractionHandling(true);
+        promise.setHadUserInteractionUponCreation(true);
+        break;
+      case JS::PromiseUserInputEventHandlingState::DidntHaveUserInteractionAtCreation:
+        promise.setRequiresUserInteractionHandling(true);
+        promise.setHadUserInteractionUponCreation(false);
+        break;
+      default:
+        MOZ_ASSERT_UNREACHABLE("Invalid PromiseUserInputEventHandlingState enum value");
+        return false;
+    }
+    return true;
+}
+
 /**
  * Unforgeable version of Promise.all for internal use.
  *
  * Takes a dense array of Promise objects and returns a promise that's
  * resolved with an array of resolution values when all those promises ahve
  * been resolved, or rejected with the rejection value of the first rejected
  * promise.
  *
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -184,17 +184,17 @@ struct JSFreeOp {
 
 typedef bool
 (* JSInterruptCallback)(JSContext* cx);
 
 typedef JSObject*
 (* JSGetIncumbentGlobalCallback)(JSContext* cx);
 
 typedef bool
-(* JSEnqueuePromiseJobCallback)(JSContext* cx, JS::HandleObject job,
+(* JSEnqueuePromiseJobCallback)(JSContext* cx, JS::HandleObject promise, JS::HandleObject job,
                                 JS::HandleObject allocationSite, JS::HandleObject incumbentGlobal,
                                 void* data);
 
 namespace JS {
 
 enum class PromiseRejectionHandlingState {
     Unhandled,
     Handled
@@ -2521,18 +2521,19 @@ JS_DeleteProperty(JSContext* cx, JS::Han
 extern JS_PUBLIC_API(bool)
 JS_DeleteElement(JSContext* cx, JS::HandleObject obj, uint32_t index);
 
 /**
  * Get an array of the non-symbol enumerable properties of obj.
  * This function is roughly equivalent to:
  *
  *     var result = [];
- *     for (key in obj)
+ *     for (key in obj) {
  *         result.push(key);
+ *     }
  *     return result;
  *
  * This is the closest thing we currently have to the ES6 [[Enumerate]]
  * internal method.
  *
  * The array of ids returned by JS_Enumerate must be rooted to protect its
  * contents from garbage collection. Use JS::Rooted<JS::IdVector>.
  */
@@ -3378,16 +3379,61 @@ CallOriginalPromiseThen(JSContext* cx, J
  * Asserts if the passed-in `promise` object isn't an unwrapped instance of
  * `Promise` or a subclass or `onResolve` and `onReject` aren't both callable
  * objects.
  */
 extern JS_PUBLIC_API(bool)
 AddPromiseReactions(JSContext* cx, JS::HandleObject promise,
                     JS::HandleObject onResolve, JS::HandleObject onReject);
 
+// This enum specifies whether a promise is expected to keep track of information
+// that is useful for embedders to implement user activation behavior handling as
+// specified in the HTML spec:
+// https://html.spec.whatwg.org/multipage/interaction.html#triggered-by-user-activation
+// By default, promises created by SpiderMonkey do not make any attempt to keep
+// track of information about whether an activation behavior was being processed
+// when the original promise in a promise chain was created.  If the embedder sets
+// either of the HadUserInteractionAtCreation or DidntHaveUserInteractionAtCreation
+// flags on a promise after creating it, SpiderMonkey will propagate that flag to
+// newly created promises when processing Promise#then and will make it possible
+// to query this flag off of a promise further down the chain later using the
+// GetPromiseUserInputEventHandlingState() API.
+enum class PromiseUserInputEventHandlingState {
+  // Don't keep track of this state (default for all promises)
+  DontCare,
+  // Keep track of this state, the original promise in the chain was created
+  // while an activation behavior was being processed.
+  HadUserInteractionAtCreation,
+  // Keep track of this state, the original promise in the chain was created
+  // while an activation behavior was not being processed.
+  DidntHaveUserInteractionAtCreation
+};
+
+/**
+ * Returns the given Promise's activation behavior state flag per above as a
+ * JS::PromiseUserInputEventHandlingState value.  All promises are created with
+ * the DontCare state by default.
+ *
+ * Returns JS::PromiseUserInputEventHandlingState::DontCare if the given object
+ * is a wrapper that can't safely be unwrapped.
+ */
+extern JS_PUBLIC_API(PromiseUserInputEventHandlingState)
+GetPromiseUserInputEventHandlingState(JS::HandleObject promise);
+
+/**
+ * Sets the given Promise's activation behavior state flag per above as a
+ * JS::PromiseUserInputEventHandlingState value.
+ *
+ * Returns false if the given object is a wrapper that can't safely be unwrapped,
+ * or if the promise isn't pending.
+ */
+extern JS_PUBLIC_API(bool)
+SetPromiseUserInputEventHandlingState(JS::HandleObject promise,
+                                      JS::PromiseUserInputEventHandlingState state);
+
 /**
  * Unforgeable version of the JS builtin Promise.all.
  *
  * Takes an AutoObjectVector of Promise objects and returns a promise that's
  * resolved with an array of resolution values when all those promises have
  * been resolved, or rejected with the rejection value of the first rejected
  * promise.
  *
@@ -3726,18 +3772,19 @@ JS_PutEscapedString(JSContext* cx, char*
  * JSFlatString type is returned by JS_FlattenString and expected by
  * JS_GetFlatStringChars. Note, though, that this is purely a syntactic
  * distinction: the input and output of JS_FlattenString are the same actual
  * GC-thing. If a JSString is known to be flat, JS_ASSERT_STRING_IS_FLAT can be
  * used to make a debug-checked cast. Example:
  *
  *   // in a fallible context
  *   JSFlatString* fstr = JS_FlattenString(cx, str);
- *   if (!fstr)
+ *   if (!fstr) {
  *     return false;
+ *   }
  *   MOZ_ASSERT(fstr == JS_ASSERT_STRING_IS_FLAT(str));
  *
  *   // in an infallible context, for the same 'str'
  *   AutoCheckCannotGC nogc;
  *   const char16_t* chars = JS_GetTwoByteFlatStringChars(nogc, fstr)
  *   MOZ_ASSERT(chars);
  *
  * Flat strings and interned strings are always null-terminated, so
@@ -4737,27 +4784,31 @@ GetWasmModule(HandleObject obj);
 
 extern JS_PUBLIC_API(RefPtr<WasmModule>)
 DeserializeWasmModule(PRFileDesc* bytecode, JS::UniqueChars filename, unsigned line);
 
 /**
  * Convenience class for imitating a JS level for-of loop. Typical usage:
  *
  *     ForOfIterator it(cx);
- *     if (!it.init(iterable))
+ *     if (!it.init(iterable)) {
  *       return false;
+ *     }
  *     RootedValue val(cx);
  *     while (true) {
  *       bool done;
- *       if (!it.next(&val, &done))
+ *       if (!it.next(&val, &done)) {
  *         return false;
- *       if (done)
+ *       }
+ *       if (done) {
  *         break;
- *       if (!DoStuff(cx, val))
+ *       }
+ *       if (!DoStuff(cx, val)) {
  *         return false;
+ *       }
  *     }
  */
 class MOZ_STACK_CLASS JS_PUBLIC_API(ForOfIterator) {
   protected:
     JSContext* cx_;
     /*
      * Use the ForOfPIC on the global object (see vm/GlobalObject.h) to try
      * to optimize iteration across arrays.
--- a/js/src/vm/JSContext.cpp
+++ b/js/src/vm/JSContext.cpp
@@ -1131,17 +1131,18 @@ JSContext::recoverFromOutOfMemory()
         if (isExceptionPending()) {
             MOZ_ASSERT(isThrowingOutOfMemory());
             clearPendingException();
         }
     }
 }
 
 static bool
-InternalEnqueuePromiseJobCallback(JSContext* cx, JS::HandleObject job,
+InternalEnqueuePromiseJobCallback(JSContext* cx, JS::HandleObject promise,
+                                  JS::HandleObject job,
                                   JS::HandleObject allocationSite,
                                   JS::HandleObject incumbentGlobal, void* data)
 {
     MOZ_ASSERT(job);
     JS::JobQueueMayNotBeEmpty(cx);
     if (!cx->jobQueue->append(job)) {
         ReportOutOfMemory(cx);
         return false;
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -658,17 +658,18 @@ JSRuntime::enqueuePromiseJob(JSContext* 
         // builtin/Promise.cpp for details.
         if (IsWrapper(promise)) {
             unwrappedPromise = UncheckedUnwrap(promise);
         }
         if (unwrappedPromise->is<PromiseObject>()) {
             allocationSite = JS::GetPromiseAllocationSite(unwrappedPromise);
         }
     }
-    return cx->enqueuePromiseJobCallback(cx, job, allocationSite, incumbentGlobal, data);
+    return cx->enqueuePromiseJobCallback(cx, promise, job, allocationSite,
+                                         incumbentGlobal, data);
 }
 
 void
 JSRuntime::addUnhandledRejectedPromise(JSContext* cx, js::HandleObject promise)
 {
     MOZ_ASSERT(promise->is<PromiseObject>());
     if (!cx->promiseRejectionTrackerCallback) {
         return;
--- a/js/xpconnect/idl/nsIXPConnect.idl
+++ b/js/xpconnect/idl/nsIXPConnect.idl
@@ -86,95 +86,16 @@ interface nsIXPConnectWrappedJS : nsIXPC
 // and QIing to nsIXPConnectWrappedJSUnmarkGray is always supposed to fail.
 [builtinclass, uuid(c02a0ce6-275f-4ea1-9c23-08494898b070)]
 interface nsIXPConnectWrappedJSUnmarkGray : nsIXPConnectWrappedJS
 {
 };
 
 /***************************************************************************/
 
-/**
- * This is a sort of a placeholder interface. It is not intended to be
- * implemented. It exists to give the nsIXPCSecurityManager an iid on
- * which to gate a specific activity in XPConnect.
- *
- * That activity is...
- *
- * When JavaScript code uses a component that is itself implemented in
- * JavaScript then XPConnect will build a wrapper rather than directly
- * expose the JSObject of the component. This allows components implemented
- * in JavaScript to 'look' just like any other xpcom component (from the
- * perspective of the JavaScript caller). This insulates the component from
- * the caller and hides any properties or methods that are not part of the
- * interface as declared in xpidl. Usually this is a good thing.
- *
- * However, in some cases it is useful to allow the JS caller access to the
- * JS component's underlying implementation. In order to facilitate this
- * XPConnect supports the 'wrappedJSObject' property. The caller code can do:
- *
- * // 'foo' is some xpcom component (that might be implemented in JS).
- * try {
- *   var bar = foo.wrappedJSObject;
- *   if(bar) {
- *      // bar is the underlying JSObject. Do stuff with it here.
- *   }
- * } catch(e) {
- *   // security exception?
- * }
- *
- * Recall that 'foo' above is an XPConnect wrapper, not the underlying JS
- * object. The property get "foo.wrappedJSObject" will only succeed if three
- * conditions are met:
- *
- * 1) 'foo' really is an XPConnect wrapper around a JSObject.
- * 2) The underlying JSObject actually implements a "wrappedJSObject"
- *    property that returns a JSObject. This is called by XPConnect. This
- *    restriction allows wrapped objects to only allow access to the underlying
- *    JSObject if they choose to do so. Ususally this just means that 'foo'
- *    would have a property tht looks like:
- *       this.wrappedJSObject = this.
- * 3) The implemementation of nsIXPCSecurityManager (if installed) allows
- *    a property get on the interface below. Although the JSObject need not
- *    implement 'nsIXPCWrappedJSObjectGetter', XPConnect will ask the
- *    security manager if it is OK for the caller to access the only method
- *    in nsIXPCWrappedJSObjectGetter before allowing the activity. This fits
- *    in with the security manager paradigm and makes control over accessing
- *    the property on this interface the control factor for getting the
- *    underlying wrapped JSObject of a JS component from JS code.
- *
- * Notes:
- *
- * a) If 'foo' above were the underlying JSObject and not a wrapper at all,
- *    then this all just works and XPConnect is not part of the picture at all.
- * b) One might ask why 'foo' should not just implement an interface through
- *    which callers might get at the underlying object. There are three reasons:
- *   i)   XPConnect would still have to do magic since JSObject is not a
- *        scriptable type.
- *   ii)  JS Components might use aggregation (like C++ objects) and have
- *        different JSObjects for different interfaces 'within' an aggregate
- *        object. But, using an additional interface only allows returning one
- *        underlying JSObject. However, this allows for the possibility that
- *        each of the aggregte JSObjects could return something different.
- *        Note that one might do: this.wrappedJSObject = someOtherObject;
- *   iii) Avoiding the explicit interface makes it easier for both the caller
- *        and the component.
- *
- *  Anyway, some future implementation of nsIXPCSecurityManager might want
- *  do special processing on 'nsIXPCSecurityManager::CanGetProperty' when
- *  the interface id is that of nsIXPCWrappedJSObjectGetter.
- */
-
-[builtinclass, uuid(254bb2e0-6439-11d4-8fe0-0010a4e73d9a)]
-interface nsIXPCWrappedJSObjectGetter : nsISupports
-{
-    readonly attribute nsISupports neverCalled;
-};
-
-/***************************************************************************/
-
 
 [noscript, builtinclass, uuid(768507b5-b981-40c7-8276-f6a1da502a24)]
 interface nsIXPConnect : nsISupports
 {
 %{ C++
     // This gets a non-addref'd pointer.
     static nsIXPConnect* XPConnect();
 %}
--- a/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
@@ -148,17 +148,73 @@ XPC_WN_Shared_toPrimitive(JSContext* cx,
 // A "double wrapped object" is a user JSObject that has been wrapped as a
 // wrappedJS in order to be used by native code and then re-wrapped by a
 // wrappedNative wrapper to be used by JS code. One might think of it as:
 //    wrappedNative(wrappedJS(underlying_JSObject))
 // This is done (as opposed to just unwrapping the wrapped JS and automatically
 // returning the underlying JSObject) so that JS callers will see what looks
 // Like any other xpcom object - and be limited to use its interfaces.
 //
-// See the comment preceding nsIXPCWrappedJSObjectGetter in nsIXPConnect.idl.
+
+/**
+ * When JavaScript code uses a component that is itself implemented in
+ * JavaScript then XPConnect will build a wrapper rather than directly
+ * expose the JSObject of the component. This allows components implemented
+ * in JavaScript to 'look' just like any other xpcom component (from the
+ * perspective of the JavaScript caller). This insulates the component from
+ * the caller and hides any properties or methods that are not part of the
+ * interface as declared in xpidl. Usually this is a good thing.
+ *
+ * However, in some cases it is useful to allow the JS caller access to the
+ * JS component's underlying implementation. In order to facilitate this
+ * XPConnect supports the 'wrappedJSObject' property. This 'wrappedJSObject'
+ * property is different than the XrayWrapper meaning. (The naming collision
+ * avoids having more than one magic XPConnect property name, but is
+ * confusing.)
+ *
+ * The caller code can do:
+ *
+ * // 'foo' is some xpcom component (that might be implemented in JS).
+ * var bar = foo.wrappedJSObject;
+ * if(bar) {
+ *    // bar is the underlying JSObject. Do stuff with it here.
+ * }
+ *
+ * Recall that 'foo' above is an XPConnect wrapper, not the underlying JS
+ * object. The property get "foo.wrappedJSObject" will only succeed if three
+ * conditions are met:
+ *
+ * 1) 'foo' really is an XPConnect wrapper around a JSObject.
+ * 2) The underlying JSObject actually implements a "wrappedJSObject"
+ *    property that returns a JSObject. This is called by XPConnect. This
+ *    restriction allows wrapped objects to only allow access to the underlying
+ *    JSObject if they choose to do so. Usually this just means that 'foo'
+ *    would have a property that looks like:
+ *       this.wrappedJSObject = this.
+ * 3) The caller must be system JS and not content. Double-wrapped XPCWJS should
+ *    not be exposed to content except with enablePrivilege or a remote-XUL
+ *    domain.
+ *
+ * Notes:
+ *
+ * a) If 'foo' above were the underlying JSObject and not a wrapper at all,
+ *    then this all just works and XPConnect is not part of the picture at all.
+ * b) One might ask why 'foo' should not just implement an interface through
+ *    which callers might get at the underlying object. There are three reasons:
+ *   i)   XPConnect would still have to do magic since JSObject is not a
+ *        scriptable type.
+ *   ii)  JS Components might use aggregation (like C++ objects) and have
+ *        different JSObjects for different interfaces 'within' an aggregate
+ *        object. But, using an additional interface only allows returning one
+ *        underlying JSObject. However, this allows for the possibility that
+ *        each of the aggregte JSObjects could return something different.
+ *        Note that one might do: this.wrappedJSObject = someOtherObject;
+ *   iii) Avoiding the explicit interface makes it easier for both the caller
+ *        and the component.
+ */
 
 static JSObject*
 GetDoubleWrappedJSObject(XPCCallContext& ccx, XPCWrappedNative* wrapper)
 {
     RootedObject obj(ccx);
     nsCOMPtr<nsIXPConnectWrappedJS>
         underware = do_QueryInterface(wrapper->GetIdentityObject());
     if (underware) {
--- a/layout/generic/BlockReflowInput.cpp
+++ b/layout/generic/BlockReflowInput.cpp
@@ -708,16 +708,62 @@ FloatMarginISize(const ReflowInput& aCBR
 
   return floatISize +
          aFloatOffsetState.ComputedLogicalMargin().Size(wm).
            ConvertTo(cbwm, wm).ISize(cbwm) +
          aFloatOffsetState.ComputedLogicalBorderPadding().Size(wm).
            ConvertTo(cbwm, wm).ISize(cbwm);
 }
 
+// A frame property that stores the last shape source / margin / etc. if there's
+// any shape, in order to invalidate the float area properly when it changes.
+//
+// TODO(emilio): This could really belong to GetRegionFor / StoreRegionFor, but
+// when I tried it was a bit awkward because of the logical -> physical
+// conversion that happens there.
+//
+// Maybe all this code could be refactored to make this cleaner, but keeping the
+// two properties separated was slightly nicer.
+struct ShapeInvalidationData
+{
+
+  StyleShapeSource mShapeOutside;
+  float mShapeImageThreshold = 0.0;
+  nsStyleCoord mShapeMargin;
+
+  ShapeInvalidationData() = default;
+
+  explicit ShapeInvalidationData(const nsStyleDisplay& aDisplay)
+  {
+    Update(aDisplay);
+  }
+
+  static bool IsNeeded(const nsStyleDisplay& aDisplay)
+  {
+    return aDisplay.mShapeOutside.GetType() != StyleShapeSourceType::None;
+  }
+
+  void Update(const nsStyleDisplay& aDisplay)
+  {
+    MOZ_ASSERT(IsNeeded(aDisplay));
+    mShapeOutside = aDisplay.mShapeOutside;
+    mShapeImageThreshold = aDisplay.mShapeImageThreshold;
+    mShapeMargin = aDisplay.mShapeMargin;
+  }
+
+  bool Matches(const nsStyleDisplay& aDisplay) const
+  {
+    return mShapeOutside == aDisplay.mShapeOutside &&
+           mShapeImageThreshold == aDisplay.mShapeImageThreshold &&
+           mShapeMargin == aDisplay.mShapeMargin;
+  }
+};
+
+NS_DECLARE_FRAME_PROPERTY_DELETABLE(ShapeInvalidationDataProperty, ShapeInvalidationData)
+
 bool
 BlockReflowInput::FlowAndPlaceFloat(nsIFrame* aFloat)
 {
   MOZ_ASSERT(aFloat->GetParent() == mBlock);
 
   WritingMode wm = mReflowInput.GetWritingMode();
   // Save away the Y coordinate before placing the float. We will
   // restore mBCoord at the end after placing the float. This is
@@ -728,16 +774,19 @@ BlockReflowInput::FlowAndPlaceFloat(nsIF
 
   // Grab the float's display information
   const nsStyleDisplay* floatDisplay = aFloat->StyleDisplay();
 
   // The float's old region, so we can propagate damage.
   LogicalRect oldRegion = nsFloatManager::GetRegionFor(wm, aFloat,
                                                        ContainerSize());
 
+  ShapeInvalidationData* invalidationData =
+    aFloat->GetProperty(ShapeInvalidationDataProperty());
+
   // Enforce CSS2 9.5.1 rule [2], i.e., make sure that a float isn't
   // ``above'' another float that preceded it in the flow.
   mBCoord = std::max(FloatManager()->GetLowestFloatTop(), mBCoord);
 
   // See if the float should clear any preceding floats...
   // XXX We need to mark this float somehow so that it gets reflowed
   // when floats are inserted before it.
   if (StyleClear::None != floatDisplay->mBreakType) {
@@ -991,28 +1040,45 @@ BlockReflowInput::FlowAndPlaceFloat(nsIF
     region.BSize(wm) = std::max(region.BSize(wm),
                                 ContentBSize() - floatPos.B(wm));
   }
   FloatManager()->AddFloat(aFloat, region, wm, ContainerSize());
 
   // store region
   nsFloatManager::StoreRegionFor(wm, aFloat, region, ContainerSize());
 
-  // If the float's dimensions have changed, note the damage in the
+  const bool invalidationDataNeeded =
+    ShapeInvalidationData::IsNeeded(*floatDisplay);
+
+  // If the float's dimensions or shape have changed, note the damage in the
   // float manager.
-  if (!region.IsEqualEdges(oldRegion)) {
+  if (!region.IsEqualEdges(oldRegion) ||
+      !!invalidationData != invalidationDataNeeded ||
+      (invalidationData && !invalidationData->Matches(*floatDisplay))) {
     // XXXwaterson conservative: we could probably get away with noting
     // less damage; e.g., if only height has changed, then only note the
     // area into which the float has grown or from which the float has
     // shrunk.
     nscoord blockStart = std::min(region.BStart(wm), oldRegion.BStart(wm));
     nscoord blockEnd = std::max(region.BEnd(wm), oldRegion.BEnd(wm));
     FloatManager()->IncludeInDamage(blockStart, blockEnd);
   }
 
+  if (invalidationDataNeeded) {
+    if (invalidationData) {
+      invalidationData->Update(*floatDisplay);
+    } else {
+      aFloat->SetProperty(ShapeInvalidationDataProperty(),
+                          new ShapeInvalidationData(*floatDisplay));
+    }
+  } else if (invalidationData) {
+    invalidationData = nullptr;
+    aFloat->DeleteProperty(ShapeInvalidationDataProperty());
+  }
+
   if (!reflowStatus.IsFullyComplete()) {
     mBlock->SplitFloat(*this, aFloat, reflowStatus);
   } else {
     MOZ_ASSERT(!aFloat->GetNextInFlow());
   }
 
 #ifdef DEBUG
   if (nsBlockFrame::gNoisyFloatManager) {
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -10962,18 +10962,19 @@ bool
 nsIFrame::IsStackingContext(EffectSet* aEffectSet,
                             const nsStyleDisplay* aStyleDisplay,
                             const nsStylePosition* aStylePosition,
                             const nsStyleEffects* aStyleEffects,
                             bool aIsPositioned)
 {
   return HasOpacity(aEffectSet) ||
          IsTransformed(aStyleDisplay) ||
-         aStyleDisplay->IsContainPaint() ||
-         aStyleDisplay->IsContainLayout() ||
+         (IsFrameOfType(eSupportsContainLayoutAndPaint) &&
+          (aStyleDisplay->IsContainPaint() ||
+           aStyleDisplay->IsContainLayout())) ||
          // strictly speaking, 'perspective' doesn't require visual atomicity,
          // but the spec says it acts like the rest of these
          ChildrenHavePerspective(aStyleDisplay) ||
          aStyleEffects->mMixBlendMode != NS_STYLE_BLEND_NORMAL ||
          nsSVGIntegrationUtils::UsingEffectsForFrame(this) ||
          (aIsPositioned && (aStyleDisplay->IsPositionForcingStackingContext() ||
                             aStylePosition->mZIndex.GetUnit() == eStyleUnit_Integer)) ||
          (aStyleDisplay->mWillChangeBitField & NS_STYLE_WILL_CHANGE_STACKING_CONTEXT) ||
--- a/layout/reftests/css-shapes/dynamic-shape-outside-1-ref.html
+++ b/layout/reftests/css-shapes/dynamic-shape-outside-1-ref.html
@@ -1,17 +1,16 @@
 <!DOCTYPE html>
 <html>
 <head>
 <meta http-equiv="content-type" content="text/html; charset=UTF-8">
 <title>Reference: Dynamic change to shape-outside</title>
 <style>
 .circle {
   float: left;
-  border-radius: 50%;
   width: 25vmin;
   height: 25vmin;
   background: #f06;
   shape-outside: circle();
 }
 </style>
 </head>
 <body>
--- a/layout/reftests/css-shapes/dynamic-shape-outside-1.html
+++ b/layout/reftests/css-shapes/dynamic-shape-outside-1.html
@@ -1,17 +1,16 @@
 <!DOCTYPE html>
 <html class="reftest-wait">
 <head>
 <meta http-equiv="content-type" content="text/html; charset=UTF-8">
 <title>Dynamic change to shape-outside</title>
 <style>
 .circle {
   float: left;
-  border-radius: 50%;
   width: 25vmin;
   height: 25vmin;
   background: #f06;
 }
 </style>
 <script>
 function doTest() {
   let circle = document.querySelector(".circle");
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-shapes/dynamic-shape-outside-2.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+<title>Dynamic change to shape-outside</title>
+<style>
+.circle {
+  float: left;
+  width: 25vmin;
+  height: 25vmin;
+  background: #f06;
+  shape-outside: circle(100%);
+}
+</style>
+<script>
+function doTest() {
+  let circle = document.querySelector(".circle");
+  circle.style.shapeOutside = "circle()";
+
+  document.documentElement.removeAttribute('class');
+}
+document.addEventListener("MozReftestInvalidate", doTest);
+</script>
+</head>
+<body>
+<div class="circle"></div>
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent
+scelerisque pretium interdum. Nam rhoncus auctor consequat. Duis porta
+tortor eu nibh efficitur, eget tempor tortor semper. Integer eu sem
+placerat, sodales tortor at, ornare orci. Fusce id malesuada est. Aenean
+ mattis augue sapien. Fusce efficitur ante sed porttitor blandit. Nam
+facilisis aliquam elit, ut consequat quam interdum sit amet. Integer
+bibendum turpis at mi dapibus dictum. Donec id lorem arcu. Pellentesque
+tortor nunc, semper a dui vel, maximus varius orci. Maecenas posuere
+enim in tempor imperdiet.
+</body>
+</html>
--- a/layout/reftests/css-shapes/reftest.list
+++ b/layout/reftests/css-shapes/reftest.list
@@ -1,9 +1,10 @@
-fuzzy-if(skiaContent,0-1,0-161) fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu),0-16,0-161) pref(layout.css.shape-outside.enabled,true) == dynamic-shape-outside-1.html dynamic-shape-outside-1-ref.html
+== dynamic-shape-outside-1.html dynamic-shape-outside-1-ref.html
+== dynamic-shape-outside-2.html dynamic-shape-outside-1-ref.html
 
 == shape-outside-empty-circle-1.html shape-outside-empty-point-ref.html
 == shape-outside-empty-circle-2.html shape-outside-empty-circle-ref.html
 == shape-outside-empty-circle-3.html shape-outside-empty-nothing-ref.html
 
 == shape-outside-empty-ellipse-1.html shape-outside-empty-point-ref.html
 == shape-outside-empty-ellipse-2.html shape-outside-empty-circle-ref.html
 == shape-outside-empty-ellipse-3.html shape-outside-empty-point-ref.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/contain/contain-layout-ignored-cases-ib-split-001-ref.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>CSS Reftest Reference</title>
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <style>
+    .abspos-box {
+      position: absolute;
+      width: 200px;
+      height: 200px;
+    }
+
+    /* The boxes should stack in the order that I've listed their CSS classes
+       here.  The class names' first word (outside/before/inside/after) refers
+       to the boxes' DOM position, and "background"/"midground"/"foreground"
+       refers to their z-index values. */
+
+    .before-IB-background {
+      background: darkmagenta;
+      z-index: -1;
+      top: 50px;
+      left: 50px;
+    }
+    .after-IB-background {
+      background: magenta;
+      z-index: -1;
+      top: 70px;
+      left: 70px;
+    }
+    .outside-span-midground {
+      background: darkkhaki;
+      top: 90px;
+      left: 90px;
+    }
+    .inside-IB-midground {
+      background: khaki;
+      top: 110px;
+      left: 110px;
+    }
+    .before-IB-foreground {
+      background: darkcyan;
+      z-index: 1;
+      top: 130px;
+      left: 130px;
+    }
+    .after-IB-foreground {
+      background: cyan;
+      z-index: 1;
+      top: 150px;
+      left: 150px;
+    }
+  </style>
+</head>
+<body>
+  <!-- The expectation here is that 'abspos-box' elements will all interact in
+       the same top-level stacking context. That means the box ordering should
+       be (back to front): darkmagenta/magenta/darkkhaki/khaki/darkcyan/cyan,
+       with the boxes stacked (visually) from top-left to bottom-right. -->
+
+  <div class="abspos-box outside-span-midground"></div>
+
+  <!-- Note: this file is identical to the testcase,
+       except for the lack of "contain: layout" on this span. -->
+  <span>
+    <div class="abspos-box before-IB-background"></div>
+    <div class="abspos-box before-IB-foreground"></div>
+    <!-- This unstyled div crates the IB split: -->
+    <div>
+      <div class="abspos-box inside-IB-midground"></div>
+    </div>
+    <div class="abspos-box after-IB-background"></div>
+    <div class="abspos-box after-IB-foreground"></div>
+  </span>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/contain/contain-layout-ignored-cases-ib-split-001.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>CSS Test: 'contain: layout' should have no effect on non-atomic inline
+         (including its block part, if there's a block-in-inline split)</title>
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-layout">
+  <link rel="match" href="contain-layout-ignored-cases-ib-split-001-ref.html">
+  <style>
+    .abspos-box {
+      position: absolute;
+      width: 200px;
+      height: 200px;
+    }
+
+    /* The boxes should stack in the order that I've listed their CSS classes
+       here.  The class names' first word (outside/before/inside/after) refers
+       to the boxes' DOM position, and "background"/"midground"/"foreground"
+       refers to their z-index values. */
+
+    .before-IB-background {
+      background: darkmagenta;
+      z-index: -1;
+      top: 50px;
+      left: 50px;
+    }
+    .after-IB-background {
+      background: magenta;
+      z-index: -1;
+      top: 70px;
+      left: 70px;
+    }
+    .outside-span-midground {
+      background: darkkhaki;
+      top: 90px;
+      left: 90px;
+    }
+    .inside-IB-midground {
+      background: khaki;
+      top: 110px;
+      left: 110px;
+    }
+    .before-IB-foreground {
+      background: darkcyan;
+      z-index: 1;
+      top: 130px;
+      left: 130px;
+    }
+    .after-IB-foreground {
+      background: cyan;
+      z-index: 1;
+      top: 150px;
+      left: 150px;
+    }
+  </style>
+</head>
+<body>
+  <!-- The expectation here is that 'abspos-box' elements will all interact in
+       the same top-level stacking context. That means the box ordering should
+       be (back to front): darkmagenta/magenta/darkkhaki/khaki/darkcyan/cyan,
+       with the boxes stacked (visually) from top-left to bottom-right. -->
+
+  <div class="abspos-box outside-span-midground"></div>
+
+  <span style="contain: layout">
+    <div class="abspos-box before-IB-background"></div>
+    <div class="abspos-box before-IB-foreground"></div>
+    <!-- This unstyled div crates the IB split: -->
+    <div>
+      <div class="abspos-box inside-IB-midground"></div>
+    </div>
+    <div class="abspos-box after-IB-background"></div>
+    <div class="abspos-box after-IB-foreground"></div>
+  </span>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/contain/contain-paint-ignored-cases-ib-split-001-ref.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>CSS Reftest Reference</title>
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <style>
+    .abspos-box {
+      position: absolute;
+      width: 200px;
+      height: 200px;
+    }
+
+    /* The boxes should stack in the order that I've listed their CSS classes
+       here.  The class names' first word (outside/before/inside/after) refers
+       to the boxes' DOM position, and "background"/"midground"/"foreground"
+       refers to their z-index values. */
+
+    .before-IB-background {
+      background: darkmagenta;
+      z-index: -1;
+      top: 50px;
+      left: 50px;
+    }
+    .after-IB-background {
+      background: magenta;
+      z-index: -1;
+      top: 70px;
+      left: 70px;
+    }
+    .outside-span-midground {
+      background: darkkhaki;
+      top: 90px;
+      left: 90px;
+    }
+    .inside-IB-midground {
+      background: khaki;
+      top: 110px;
+      left: 110px;
+    }
+    .before-IB-foreground {
+      background: darkcyan;
+      z-index: 1;
+      top: 130px;
+      left: 130px;
+    }
+    .after-IB-foreground {
+      background: cyan;
+      z-index: 1;
+      top: 150px;
+      left: 150px;
+    }
+  </style>
+</head>
+<body>
+  <!-- The expectation here is that 'abspos-box' elements will all interact in
+       the same top-level stacking context. That means the box ordering should
+       be (back to front): darkmagenta/magenta/darkkhaki/khaki/darkcyan/cyan,
+       with the boxes stacked (visually) from top-left to bottom-right. -->
+
+  <div class="abspos-box outside-span-midground"></div>
+
+  <!-- Note: this file is identical to the testcase,
+       except for the lack of "contain: paint" on this span. -->
+  <span>
+    <div class="abspos-box before-IB-background"></div>
+    <div class="abspos-box before-IB-foreground"></div>
+    <!-- This unstyled div crates the IB split: -->
+    <div>
+      <div class="abspos-box inside-IB-midground"></div>
+    </div>
+    <div class="abspos-box after-IB-background"></div>
+    <div class="abspos-box after-IB-foreground"></div>
+  </span>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/contain/contain-paint-ignored-cases-ib-split-001.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>CSS Test: 'contain: paint' should have no effect on non-atomic inline
+         (including its block part, if there's a block-in-inline split)</title>
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-paint">
+  <link rel="match" href="contain-paint-ignored-cases-ib-split-001-ref.html">
+  <style>
+    .abspos-box {
+      position: absolute;
+      width: 200px;
+      height: 200px;
+    }
+
+    /* The boxes should stack in the order that I've listed their CSS classes
+       here.  The class names' first word (outside/before/inside/after) refers
+       to the boxes' DOM position, and "background"/"midground"/"foreground"
+       refers to their z-index values. */
+
+    .before-IB-background {
+      background: darkmagenta;
+      z-index: -1;
+      top: 50px;
+      left: 50px;
+    }
+    .after-IB-background {
+      background: magenta;
+      z-index: -1;
+      top: 70px;
+      left: 70px;
+    }
+    .outside-span-midground {
+      background: darkkhaki;
+      top: 90px;
+      left: 90px;
+    }
+    .inside-IB-midground {
+      background: khaki;
+      top: 110px;
+      left: 110px;
+    }
+    .before-IB-foreground {
+      background: darkcyan;
+      z-index: 1;
+      top: 130px;
+      left: 130px;
+    }
+    .after-IB-foreground {
+      background: cyan;
+      z-index: 1;
+      top: 150px;
+      left: 150px;
+    }
+  </style>
+</head>
+<body>
+  <!-- The expectation here is that 'abspos-box' elements will all interact in
+       the same top-level stacking context. That means the box ordering should
+       be (back to front): darkmagenta/magenta/darkkhaki/khaki/darkcyan/cyan,
+       with the boxes stacked (visually) from top-left to bottom-right. -->
+
+  <div class="abspos-box outside-span-midground"></div>
+
+  <span style="contain: paint">
+    <div class="abspos-box before-IB-background"></div>
+    <div class="abspos-box before-IB-foreground"></div>
+    <!-- This unstyled div crates the IB split: -->
+    <div>
+      <div class="abspos-box inside-IB-midground"></div>
+    </div>
+    <div class="abspos-box after-IB-background"></div>
+    <div class="abspos-box after-IB-foreground"></div>
+  </span>
+</body>
+</html>
--- a/layout/reftests/w3c-css/submitted/contain/reftest.list
+++ b/layout/reftests/w3c-css/submitted/contain/reftest.list
@@ -5,16 +5,17 @@ default-preferences pref(layout.css.cont
 == contain-paint-clip-003.html contain-paint-clip-003-ref.html
 == contain-paint-clip-004.html contain-paint-clip-004-ref.html
 == contain-paint-clip-005.html contain-paint-clip-003-ref.html
 pref(layout.css.overflow-clip-box.enabled,true) == contain-paint-clip-006.html contain-paint-clip-006-ref.html
 == contain-paint-containing-block-absolute-001.html contain-paint-containing-block-absolute-001-ref.html
 == contain-paint-containing-block-fixed-001.html contain-paint-containing-block-fixed-001-ref.html
 == contain-paint-formatting-context-float-001.html contain-paint-formatting-context-float-001-ref.html
 == contain-paint-formatting-context-margin-001.html contain-paint-formatting-context-margin-001-ref.html
+== contain-paint-ignored-cases-ib-split-001.html contain-paint-ignored-cases-ib-split-001-ref.html
 == contain-paint-ignored-cases-internal-table-001a.html contain-paint-ignored-cases-internal-table-001-ref.html
 == contain-paint-ignored-cases-internal-table-001b.html contain-paint-ignored-cases-internal-table-001-ref.html
 == contain-paint-ignored-cases-no-principal-box-001.html contain-paint-ignored-cases-no-principal-box-001-ref.html
 == contain-paint-ignored-cases-ruby-containing-block-001.html contain-paint-ignored-cases-ruby-containing-block-001-ref.html
 == contain-paint-ignored-cases-ruby-stacking-and-clipping-001.html contain-paint-ignored-cases-ruby-stacking-and-clipping-001-ref.html
 == contain-paint-stacking-context-001a.html contain-paint-stacking-context-001-ref.html
 == contain-paint-stacking-context-001b.html contain-paint-stacking-context-001-ref.html
 == contain-size-button-001.html contain-size-button-001-ref.html
@@ -31,11 +32,12 @@ fuzzy-if(webrender&&winWidget,0-3,0-2) =
 == contain-layout-overflow-001.html contain-layout-overflow-001-ref.html
 == contain-layout-overflow-002.html contain-layout-overflow-002-ref.html
 == contain-size-table-caption-001.html contain-size-table-caption-001-ref.html
 == contain-layout-stacking-context-001.html contain-paint-stacking-context-001-ref.html
 == contain-layout-formatting-context-float-001.html contain-paint-formatting-context-float-001-ref.html
 == contain-layout-formatting-context-margin-001.html contain-layout-formatting-context-margin-001-ref.html
 == contain-layout-containing-block-fixed-001.html contain-paint-containing-block-fixed-001-ref.html
 == contain-layout-containing-block-absolute-001.html contain-paint-containing-block-absolute-001-ref.html
+== contain-layout-ignored-cases-ib-split-001.html contain-layout-ignored-cases-ib-split-001-ref.html
 == contain-layout-ignored-cases-no-principal-box-001.html contain-paint-ignored-cases-no-principal-box-001-ref.html
 == contain-layout-ignored-cases-no-principal-box-002.html contain-layout-ignored-cases-no-principal-box-002-ref.html
 == contain-layout-ignored-cases-no-principal-box-003.html contain-layout-ignored-cases-no-principal-box-003-ref.html
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -3732,27 +3732,17 @@ nsStyleDisplay::CalcDifference(const nsS
 
   if (mShapeOutside != aNewData.mShapeOutside ||
       mShapeMargin != aNewData.mShapeMargin ||
       mShapeImageThreshold != aNewData.mShapeImageThreshold) {
     if (aNewData.mFloat != StyleFloat::None) {
       // If we are floating, and our shape-outside, shape-margin, or
       // shape-image-threshold are changed, our descendants are not impacted,
       // but our ancestor and siblings are.
-      //
-      // This is similar to a float-only change, but since the ISize of the
-      // float area changes arbitrarily along its block axis, more is required
-      // to get the siblings to adjust properly. Hinting overflow change is
-      // sufficient to trigger the correct calculation, but may be too
-      // heavyweight.
-
-      // XXX What is the minimum hint to ensure mShapeInfo is regenerated in
-      // the next reflow?
-      hint |= nsChangeHint_ReflowHintsForFloatAreaChange |
-              nsChangeHint_ScrollbarChange;
+      hint |= nsChangeHint_ReflowHintsForFloatAreaChange;
     } else {
       // shape-outside or shape-margin or shape-image-threshold changed,
       // but we don't need to reflow because we're not floating.
       hint |= nsChangeHint_NeutralChange;
     }
   }
 
   if (mVerticalAlign != aNewData.mVerticalAlign) {
--- a/layout/style/test/test_dynamic_change_causing_reflow.html
+++ b/layout/style/test/test_dynamic_change_causing_reflow.html
@@ -90,16 +90,29 @@ const gTestcases = [
 
   // * Changing 'height' should cause reflow, but not frame construction.
   {
     beforeStyle: "height: 10px",
     afterStyle:  "height: 15px",
     expectReflow: true,
   },
 
+  // * Changing 'shape-outside' on a non-floating box should not cause anything to happen.
+  {
+    beforeStyle: "shape-outside: none",
+    afterStyle:  "shape-outside: circle()",
+  },
+
+  // * Changing 'shape-outside' should cause reflow, but not frame construction.
+  {
+    beforeStyle: "float: left; shape-outside: none",
+    afterStyle:  "float: left; shape-outside: circle()",
+    expectReflow: true,
+  },
+
   // * Changing 'overflow' on <body> should cause reflow,
   // but not frame reconstruction
   {
     elem: document.body,
     /* beforeStyle: implicitly 'overflow:visible' */
     afterStyle:  "overflow: hidden",
     expectConstruction: false,
     expectReflow: true,
--- a/media/webrtc/signaling/src/media-conduit/MediaDataDecoderCodec.cpp
+++ b/media/webrtc/signaling/src/media-conduit/MediaDataDecoderCodec.cpp
@@ -14,23 +14,28 @@ MediaDataDecoderCodec::CreateEncoder(
 {
   return nullptr;
 }
 
 /* static */ WebrtcVideoDecoder*
 MediaDataDecoderCodec::CreateDecoder(
   webrtc::VideoCodecType aCodecType)
 {
-  if (!StaticPrefs::MediaNavigatorMediadatadecoderEnabled()) {
+  if (!StaticPrefs::MediaNavigatorMediadatadecoderEnabled() &&
+      !StaticPrefs::MediaNavigatorMediadatadecoderH264Enabled()) {
     return nullptr;
   }
 
   switch (aCodecType) {
     case webrtc::VideoCodecType::kVideoCodecVP8:
     case webrtc::VideoCodecType::kVideoCodecVP9:
+      if (!StaticPrefs::MediaNavigatorMediadatadecoderEnabled()) {
+        return nullptr;
+      }
+      MOZ_FALLTHROUGH;
     case webrtc::VideoCodecType::kVideoCodecH264:
       break;
     default:
       return nullptr;
   }
   return new WebrtcMediaDataDecoder();
 }
 
--- a/media/webrtc/signaling/src/media-conduit/WebrtcMediaDataDecoderCodec.cpp
+++ b/media/webrtc/signaling/src/media-conduit/WebrtcMediaDataDecoderCodec.cpp
@@ -21,18 +21,16 @@ WebrtcMediaDataDecoder::WebrtcMediaDataD
       layers::ImageContainer::ASYNCHRONOUS))
   , mFactory(new PDMFactory())
   , mTrackType(TrackInfo::kUndefinedTrack)
 {
 }
 
 WebrtcMediaDataDecoder::~WebrtcMediaDataDecoder()
 {
-  mTaskQueue->BeginShutdown();
-  mTaskQueue->AwaitShutdownAndIdle();
 }
 
 int32_t
 WebrtcMediaDataDecoder::InitDecode(const webrtc::VideoCodec* aCodecSettings,
                                    int32_t aNumberOfCores)
 {
   nsCString codec;
   switch (aCodecSettings->codecType) {
@@ -154,24 +152,20 @@ WebrtcMediaDataDecoder::RegisterDecodeCo
 {
   mCallback = aCallback;
   return WEBRTC_VIDEO_CODEC_OK;
 }
 
 int32_t
 WebrtcMediaDataDecoder::Release()
 {
-  RefPtr<ShutdownPromise> p =
-    mDecoder->Flush()->Then(mTaskQueue,
-                            __func__,
-                            [this]() { return mDecoder->Shutdown(); },
-                            [this]() { return mDecoder->Shutdown(); });
-  media::Await(do_AddRef(mThreadPool), p);
+  RefPtr<MediaDataDecoder> decoder = mDecoder.forget();
+  decoder->Flush()->Then(
+    mTaskQueue, __func__, [decoder]() { decoder->Shutdown(); });
 
-  mDecoder = nullptr;
   mNeedKeyframe = true;
 
   return WEBRTC_VIDEO_CODEC_OK;
 }
 
 bool
 WebrtcMediaDataDecoder::OnTaskQueue() const
 {
--- a/mobile/android/gradle/with_gecko_binaries.gradle
+++ b/mobile/android/gradle/with_gecko_binaries.gradle
@@ -37,30 +37,32 @@ task buildOmnijars(type:Exec) {
             throw new GradleException("Process '${commandLine}' finished with non-zero exit value ${execResult.exitValue}:\n\n${standardOutput.toString()}")
         }
     }
 }
 
 ext.configureVariantWithGeckoBinaries = { variant ->
     // :app needs the full Fennec omni.ja, whereas other projects need the
     // GeckoView-specific omni.ja.
-    def omnijar_dir = "app".equals(project.name) ? "fennec" : "geckoview"
-    def distDir = "${topobjdir}/dist/${omnijar_dir}"
+    def omnijarDir = { "${topobjdir}/dist/${it}" }("app".equals(project.name) ? "fennec" : "geckoview")
+    // All projects take the same Gecko libraries, which (for historical
+    // reasons) are in "fennec".
+    def distDir = "${topobjdir}/dist/fennec"
 
     def syncOmnijarFromDistDir = task("syncOmnijarFromDistDirFor${variant.name.capitalize()}", type: Sync) {
         onlyIf {
             if (source.empty) {
-                throw new StopExecutionException("Required omnijar not found in ${distDir}/{omni.ja,assets/omni.ja}.  Have you built and packaged?")
+                throw new StopExecutionException("Required omnijar not found in ${omnijarDir}/{omni.ja,assets/omni.ja}.  Have you built and packaged?")
             }
             return true
         }
 
         into("${project.buildDir}/moz.build/src/${variant.name}/omnijar")
-        from("${distDir}/omni.ja",
-             "${distDir}/assets/omni.ja") {
+        from("${omnijarDir}/omni.ja",
+             "${omnijarDir}/assets/omni.ja") {
             // Throw an exception if we find multiple, potentially conflicting omni.ja files.
             duplicatesStrategy 'fail'
         }
     }
 
     def syncLibsFromDistDir = task("syncLibsFromDistDirFor${variant.name.capitalize()}", type: Sync) {
         onlyIf {
             if (source.empty) {
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -19,17 +19,24 @@
 @BINPATH@/update.locale
 #ifdef MOZ_UPDATER
 @BINPATH@/updater.ini
 #endif
 @BINPATH@/dictionaries/*
 @BINPATH@/hyphenation/*
 @BINPATH@/localization/*
 
-[assets destdir="assets/@ANDROID_CPU_ARCH@"]
+; We want fennec/assets for both Fennec and GeckoView, so we turn
+; geckoview/assets into fennec/assets.
+#ifndef MOZ_GECKOVIEW_JAR
+[assets xz_compress="1" destdir="assets/@ANDROID_CPU_ARCH@"]
+#else
+[assets xz_compress="1" destdir="../fennec/assets/@ANDROID_CPU_ARCH@"]
+#endif
+
 #ifndef MOZ_STATIC_JS
 @BINPATH@/@DLL_PREFIX@mozjs@DLL_SUFFIX@
 #endif
 #ifdef MOZ_DMD
 @BINPATH@/@DLL_PREFIX@dmd@DLL_SUFFIX@
 #endif
 #ifndef MOZ_FOLD_LIBS
 @BINPATH@/@DLL_PREFIX@plc4@DLL_SUFFIX@
@@ -66,17 +73,24 @@
 @BINPATH@/@DLL_PREFIX@nssdbm3.chk
 #endif
 #endif
 
 #ifndef MOZ_FOLD_LIBS
 @BINPATH@/@DLL_PREFIX@mozsqlite3@DLL_SUFFIX@
 #endif
 
+; We want fennec/lib for both Fennec and GeckoView, so we turn
+; geckoview/lib into fennec/lib.
+#ifndef MOZ_GECKOVIEW_JAR
 [lib destdir="lib/@ANDROID_CPU_ARCH@"]
+#else
+[lib destdir="../fennec/lib/@ANDROID_CPU_ARCH@"]
+#endif
+
 @BINPATH@/@DLL_PREFIX@mozglue@DLL_SUFFIX@
 # This should be MOZ_CHILD_PROCESS_NAME, but that has a "lib/" prefix.
 @BINPATH@/@MOZ_CHILD_PROCESS_NAME@
 
 #ifdef MOZ_ANDROID_GOOGLE_VR
 @BINPATH@/@DLL_PREFIX@gvr@DLL_SUFFIX@
 #endif
 
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -1061,16 +1061,23 @@ PREF("media.navigator.hardware.vp8_decod
 
 // Use MediaDataDecoder API for WebRTC. This includes hardware acceleration for
 // decoding.
 VARCACHE_PREF(
   "media.navigator.mediadatadecoder_enabled",
    MediaNavigatorMediadatadecoderEnabled,
   bool, false
 )
+// Use MediaDataDecoder API for WebRTC. This includes hardware acceleration for
+// decoding.
+VARCACHE_PREF(
+  "media.navigator.mediadatadecoder_h264_enabled",
+   MediaNavigatorMediadatadecoderH264Enabled,
+  bool, false
+)
 #endif // MOZ_WEBRTC
 
 #ifdef MOZ_OMX
 VARCACHE_PREF(
   "media.omx.enabled",
    MediaOmxEnabled,
   bool, false
 )
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -442,16 +442,18 @@ pref("media.navigator.video.default_widt
 pref("media.navigator.video.default_height",0); // adaptive default
 pref("media.peerconnection.enabled", true);
 pref("media.peerconnection.video.enabled", true);
 pref("media.navigator.video.max_fs", 12288); // Enough for 2048x1536
 pref("media.navigator.video.max_fr", 60);
 pref("media.navigator.video.h264.level", 31); // 0x42E01f - level 3.1
 pref("media.navigator.video.h264.max_br", 0);
 pref("media.navigator.video.h264.max_mbps", 0);
+pref("media.navigator.mediadatadecoder_enabled", false);
+pref("media.navigator.mediadatadecoder_h264_enabled", false);
 pref("media.peerconnection.video.vp9_enabled", true);
 pref("media.peerconnection.video.vp9_preferred", false);
 pref("media.getusermedia.aec", 1);
 pref("media.getusermedia.browser.enabled", false);
 pref("media.getusermedia.channels", 0);
 #if defined(ANDROID)
 pref("media.getusermedia.camera.off_while_disabled.enabled", false);
 pref("media.getusermedia.microphone.off_while_disabled.enabled", false);
@@ -1456,17 +1458,17 @@ pref("javascript.options.discardSystemSo
 pref("javascript.options.mem.high_water_mark", 128);
 
 // JSGC_MAX_BYTES
 // SpiderMonkey defaults to 2^32-1 bytes, but this is measured in MB so that
 // cannot be represented directly in order to show it in about:config.
 pref("javascript.options.mem.max", -1);
 
 // JSGC_MAX_NURSERY_BYTES
-#if defined(ANDROID) || defined(XP_IOS) || defined(MOZ_B2G)
+#if defined(ANDROID) || defined(XP_IOS)
 pref("javascript.options.mem.nursery.max_kb", 4096);
 #else
 pref("javascript.options.mem.nursery.max_kb", 16384);
 #endif
 
 // JSGC_MODE
 pref("javascript.options.mem.gc_per_zone", true);
 pref("javascript.options.mem.gc_incremental", true);
--- a/python/mozbuild/mozbuild/action/package_fennec_apk.py
+++ b/python/mozbuild/mozbuild/action/package_fennec_apk.py
@@ -87,47 +87,16 @@ def package_fennec_apk(inputs=[], omni_j
             compress = None  # Take default from Jarrer.
             if p.endswith('.so'):
                 # Asset libraries are special.
                 if f.open().read(5)[1:] == '7zXZ':
                     print('%s is already compressed' % p)
                     # We need to store (rather than deflate) compressed libraries
                     # (even if we don't compress them ourselves).
                     compress = False
-                elif buildconfig.substs.get('XZ'):
-                    cmd = [buildconfig.substs.get('XZ'), '-zkf',
-                           mozpath.join(finder.base, p)]
-
-                    # For now, the mozglue XZStream ELF loader can only support xz files
-                    # with a single stream that contains a single block. In xz, there is no
-                    # explicit option to set the max block count. Instead, we force xz to use
-                    # single thread mode, which results in a single block.
-                    cmd.extend(['--threads=1'])
-
-                    bcj = None
-                    if buildconfig.substs.get('MOZ_THUMB2'):
-                        bcj = '--armthumb'
-                    elif buildconfig.substs.get('CPU_ARCH') == 'arm':
-                        bcj = '--arm'
-                    elif buildconfig.substs.get('CPU_ARCH') == 'x86':
-                        bcj = '--x86'
-
-                    if bcj:
-                        cmd.extend([bcj])
-                    # We need to explicitly specify the LZMA filter chain to ensure consistent builds
-                    # across platforms. Note that the dict size must be less then 16MiB per the hardcoded
-                    # value in mozglue/linker/XZStream.cpp. This is the default LZMA filter chain for for
-                    # xz-utils version 5.0. See:
-                    # https://github.com/xz-mirror/xz/blob/v5.0.0/src/liblzma/lzma/lzma_encoder_presets.c
-                    # https://github.com/xz-mirror/xz/blob/v5.0.0/src/liblzma/api/lzma/container.h#L31
-                    cmd.extend(['--lzma2=dict=8MiB,lc=3,lp=0,pb=2,mode=normal,nice=64,mf=bt4,depth=0'])
-                    print('xz-compressing %s with %s' % (p, ' '.join(cmd)))
-                    subprocess.check_output(cmd)
-                    os.rename(f.path + '.xz', f.path)
-                    compress = False
 
             add(mozpath.join('assets', p), f, compress=compress)
 
     for lib_dir in lib_dirs:
         finder = FileFinder(lib_dir)
         for p, f in finder.find('**'):
             add(mozpath.join('lib', p), f)
 
--- a/python/mozbuild/mozbuild/controller/building.py
+++ b/python/mozbuild/mozbuild/controller/building.py
@@ -1359,24 +1359,18 @@ class BuildDriver(MozbuildObject):
     def install_tests(self, test_objs):
         """Install test files."""
 
         if self.is_clobber_needed():
             print(INSTALL_TESTS_CLOBBER.format(
                   clobber_file=os.path.join(self.topobjdir, 'CLOBBER')))
             sys.exit(1)
 
-        if not test_objs:
-            # If we don't actually have a list of tests to install we install
-            # test and support files wholesale.
-            self._run_make(target='install-test-files', pass_thru=True,
-                           print_directory=False)
-        else:
-            install_test_files(mozpath.normpath(self.topsrcdir), self.topobjdir,
-                               '_tests', test_objs)
+        install_test_files(mozpath.normpath(self.topsrcdir), self.topobjdir,
+                           '_tests', test_objs)
 
     def _clobber_configure(self):
         # This is an optimistic treatment of the CLOBBER file for when we have
         # some trust in the build system: an update to the CLOBBER file is
         # interpreted to mean that configure will fail during an incremental
         # build, which is handled by removing intermediate configure artifacts
         # and subsections of the objdir related to python and testing before
         # proceeding.
--- a/python/mozbuild/mozbuild/testing.py
+++ b/python/mozbuild/mozbuild/testing.py
@@ -198,27 +198,25 @@ def _resolve_installs(paths, topobjdir, 
                     manifest.add_pattern_link(*install_info)
                 if len(install_info) == 2:
                     manifest.add_link(*install_info)
             except ValueError:
                 # A duplicate value here is pretty likely when running
                 # multiple directories at once, and harmless.
                 pass
 
-def install_test_files(topsrcdir, topobjdir, tests_root, test_objs):
-    """Installs the requested test files to the objdir. This is invoked by
-    test runners to avoid installing tens of thousands of test files when
-    only a few tests need to be run.
-    """
+
+def _make_install_manifest(topsrcdir, topobjdir, test_objs):
+
     flavor_info = {flavor: (root, prefix, install)
                    for (flavor, root, prefix, install) in TEST_MANIFESTS.values()}
-    objdir_dest = mozpath.join(topobjdir, tests_root)
 
     converter = SupportFilesConverter()
     install_info = TestInstallInfo()
+
     for o in test_objs:
         flavor = o['flavor']
         if flavor not in flavor_info:
             # This is a test flavor that isn't installed by the build system.
             continue
         root, prefix, install = flavor_info[flavor]
         if not install:
             # This flavor isn't installed to the objdir.
@@ -247,27 +245,45 @@ def install_test_files(topsrcdir, topobj
         if dest in install_info.external_installs:
             continue
         manifest.add_link(source, dest)
     for base, pattern, dest in install_info.pattern_installs:
         manifest.add_pattern_link(base, pattern, dest)
 
     _resolve_installs(install_info.deferred_installs, topobjdir, manifest)
 
+    return manifest
+
+
+def install_test_files(topsrcdir, topobjdir, tests_root, test_objs):
+    """Installs the requested test files to the objdir. This is invoked by
+    test runners to avoid installing tens of thousands of test files when
+    only a few tests need to be run.
+    """
+
+    if test_objs:
+        manifest = _make_install_manifest(topsrcdir, topobjdir, test_objs)
+    else:
+        # If we don't actually have a list of tests to install we install
+        # test and support files wholesale.
+        manifest = InstallManifest(mozpath.join(topobjdir, '_build_manifests',
+                                                'install', '_test_files'))
+
     harness_files_manifest = mozpath.join(topobjdir, '_build_manifests',
                                           'install', tests_root)
+
     if os.path.isfile(harness_files_manifest):
         # If the backend has generated an install manifest for test harness
         # files they are treated as a monolith and installed each time we
         # run tests. Fortunately there are not very many.
         manifest |= InstallManifest(harness_files_manifest)
 
     copier = FileCopier()
     manifest.populate_registry(copier)
-    copier.copy(objdir_dest,
+    copier.copy(mozpath.join(topobjdir, tests_root),
                 remove_unaccounted=False)
 
 
 # Convenience methods for test manifest reading.
 def read_manifestparser_manifest(context, manifest_path):
     path = manifest_path.full_path
     return manifestparser.TestManifest(manifests=[path], strict=True,
                                        rootdir=context.config.topsrcdir,
--- a/python/mozbuild/mozpack/executables.py
+++ b/python/mozbuild/mozpack/executables.py
@@ -117,8 +117,53 @@ def elfhack(path):
     Execute the elfhack command on the given path.
     '''
     from buildconfig import topobjdir
     cmd = [os.path.join(topobjdir, 'build/unix/elfhack/elfhack'), path]
     if 'ELF_HACK_FLAGS' in os.environ:
         cmd[1:0] = os.environ['ELF_HACK_FLAGS'].split()
     if subprocess.call(cmd) != 0:
         errors.fatal('Error executing ' + ' '.join(cmd))
+
+
+def xz_compress(path):
+    '''
+    Execute xz to compress the given path.
+    '''
+    if open(path, 'rb').read(5)[1:] == '7zXZ':
+        print('%s is already compressed' % path)
+        return
+    
+    from buildconfig import substs
+    xz = substs.get('XZ')
+    cmd = [xz, '-zkf', path]
+
+    # For now, the mozglue XZStream ELF loader can only support xz files
+    # with a single stream that contains a single block. In xz, there is no
+    # explicit option to set the max block count. Instead, we force xz to use
+    # single thread mode, which results in a single block.
+    cmd.extend(['--threads=1'])
+
+    bcj = None
+    if substs.get('MOZ_THUMB2'):
+        bcj = '--armthumb'
+    elif substs.get('CPU_ARCH') == 'arm':
+        bcj = '--arm'
+    elif substs.get('CPU_ARCH') == 'x86':
+        bcj = '--x86'
+
+    if bcj:
+        cmd.extend([bcj])
+
+    # We need to explicitly specify the LZMA filter chain to ensure consistent builds
+    # across platforms. Note that the dict size must be less then 16MiB per the hardcoded
+    # value in mozglue/linker/XZStream.cpp. This is the default LZMA filter chain for for
+    # xz-utils version 5.0. See:
+    # https://github.com/xz-mirror/xz/blob/v5.0.0/src/liblzma/lzma/lzma_encoder_presets.c
+    # https://github.com/xz-mirror/xz/blob/v5.0.0/src/liblzma/api/lzma/container.h#L31
+    cmd.extend(['--lzma2=dict=8MiB,lc=3,lp=0,pb=2,mode=normal,nice=64,mf=bt4,depth=0'])
+    print('xz-compressing %s with %s' % (path, ' '.join(cmd)))
+
+    if subprocess.call(cmd) != 0:
+        errors.fatal('Error executing ' + ' '.join(cmd))
+        return
+
+    os.rename(path + '.xz', path)
--- a/python/mozbuild/mozpack/files.py
+++ b/python/mozbuild/mozpack/files.py
@@ -16,16 +16,17 @@ from itertools import chain
 from mozbuild.preprocessor import Preprocessor
 from mozbuild.util import FileAvoidWrite
 from mozpack.executables import (
     is_executable,
     may_strip,
     strip,
     may_elfhack,
     elfhack,
+    xz_compress,
 )
 from mozpack.chrome.manifest import (
     ManifestEntry,
     ManifestInterfaces,
 )
 from io import BytesIO
 from mozpack.errors import (
     ErrorMessage,
@@ -275,34 +276,40 @@ class File(BaseFile):
         return (self.path,)
 
 
 class ExecutableFile(File):
     '''
     File class for executable and library files on OS/2, OS/X and ELF systems.
     (see mozpack.executables.is_executable documentation).
     '''
+    def __init__(self, path, xz_compress=False):
+        File.__init__(self, path)
+        self.xz_compress = xz_compress
+
     def copy(self, dest, skip_if_older=True):
         real_dest = dest
         if not isinstance(dest, basestring):
             fd, dest = mkstemp()
             os.close(fd)
             os.remove(dest)
         assert isinstance(dest, basestring)
         # If File.copy didn't actually copy because dest is newer, check the
         # file sizes. If dest is smaller, it means it is already stripped and
-        # elfhacked, so we can skip.
+        # elfhacked and xz_compressed, so we can skip.
         if not File.copy(self, dest, skip_if_older) and \
                 os.path.getsize(self.path) > os.path.getsize(dest):
             return False
         try:
             if may_strip(dest):
                 strip(dest)
             if may_elfhack(dest):
                 elfhack(dest)
+            if self.xz_compress:
+                xz_compress(dest)
         except ErrorMessage:
             os.remove(dest)
             raise
 
         if real_dest != dest:
             f = File(dest)
             ret = f.copy(real_dest, skip_if_older)
             os.remove(dest)
--- a/python/mozbuild/mozpack/packager/__init__.py
+++ b/python/mozbuild/mozpack/packager/__init__.py
@@ -11,46 +11,56 @@ from mozpack.errors import errors
 from mozpack.chrome.manifest import (
     Manifest,
     ManifestBinaryComponent,
     ManifestChrome,
     ManifestInterfaces,
     is_manifest,
     parse_manifest,
 )
+from mozpack.files import (
+    ExecutableFile,
+)
 import mozpack.path as mozpath
 from collections import deque
 import json
 
 
 class Component(object):
     '''
     Class that represents a component in a package manifest.
     '''
-    def __init__(self, name, destdir=''):
+    def __init__(self, name, destdir='', xz_compress=False):
         if name.find(' ') > 0:
             errors.fatal('Malformed manifest: space in component name "%s"'
                          % component)
         self._name = name
         self._destdir = destdir
+        self._xz_compress = xz_compress
 
     def __repr__(self):
         s = self.name
         if self.destdir:
             s += ' destdir="%s"' % self.destdir
+        if self.xz_compress:
+            s += ' xz_compress="1"'
         return s
 
     @property
     def name(self):
         return self._name
 
     @property
     def destdir(self):
         return self._destdir
 
+    @property
+    def xz_compress(self):
+        return self._xz_compress
+
     @staticmethod
     def _triples(lst):
         '''
         Split [1, 2, 3, 4, 5, 6, 7] into [(1, 2, 3), (4, 5, 6)].
         '''
         return zip(*[iter(lst)] * 3)
 
     KEY_VALUE_RE = re.compile(r'''
@@ -112,20 +122,21 @@ class Component(object):
         Create a component from a string.
         '''
         try:
             name, options = Component._split_component_and_options(string)
         except ValueError as e:
             errors.fatal('Malformed manifest: %s' % e)
             return
         destdir = options.pop('destdir', '')
+        xz_compress = options.pop('xz_compress', '0') != '0'
         if options:
             errors.fatal('Malformed manifest: options %s not recognized'
                          % options.keys())
-        return Component(name, destdir=destdir)
+        return Component(name, destdir=destdir, xz_compress=xz_compress)
 
 
 class PackageManifestParser(object):
     '''
     Class for parsing of a package manifest, after preprocessing.
 
     A package manifest is a list of file paths, with some syntaxic sugar:
         [] designates a toplevel component. Example: [xpcom]
@@ -400,16 +411,18 @@ class SimpleManifestSink(object):
         '''
         assert not self._closed
         added = False
         for p, f in self._finder.find(pattern):
             added = True
             if is_manifest(p):
                 self._manifests.add(p)
             dest = mozpath.join(component.destdir, SimpleManifestSink.normalize_path(p))
+            if isinstance(f, ExecutableFile):
+                f.xz_compress = component.xz_compress
             self.packager.add(dest, f)
         if not added:
             errors.error('Missing file(s): %s' % pattern)
 
     def remove(self, component, pattern):
         '''
         Remove files with the given pattern in the given component.
         '''
--- a/servo/components/style/font_face.rs
+++ b/servo/components/style/font_face.rs
@@ -156,17 +156,17 @@ impl FontWeightRange {
     pub fn compute(&self) -> ComputedFontWeightRange {
         ComputedFontWeightRange(self.0.compute().0, self.1.compute().0)
     }
 }
 
 /// The font-stretch descriptor:
 ///
 /// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-stretch
-#[derive(Clone, Debug, PartialEq,)]
+#[derive(Clone, Debug, PartialEq)]
 pub struct FontStretchRange(pub FontStretch, pub FontStretch);
 impl_range!(FontStretchRange, FontStretch);
 
 /// The computed representation of the above, so that
 /// Gecko can read them easily.
 #[repr(C)]
 #[allow(missing_docs)]
 pub struct ComputedFontStretchRange(f32, f32);
--- a/servo/components/style/gecko/conversions.rs
+++ b/servo/components/style/gecko/conversions.rs
@@ -105,18 +105,18 @@ impl From<nsStyleCoord_CalcValue> for Le
         }
     }
 }
 
 // FIXME(emilio): A lot of these impl From should probably become explicit or
 // disappear as we move more stuff to cbindgen.
 impl From<nsStyleCoord_CalcValue> for NonNegativeLengthOrPercentageOrAuto {
     fn from(other: nsStyleCoord_CalcValue) -> Self {
+        use style_traits::values::specified::AllowedNumericType;
         use values::generics::NonNegative;
-        use style_traits::values::specified::AllowedNumericType;
         NonNegative(if other.mLength < 0 || other.mPercent < 0. {
             LengthOrPercentageOrAuto::Calc(
                 CalcLengthOrPercentage::with_clamping_mode(
                     Au(other.mLength).into(),
                     if other.mHasPercent { Some(Percentage(other.mPercent)) } else { None },
                     AllowedNumericType::NonNegative,
                 )
             )
--- a/servo/components/style/gecko/url.rs
+++ b/servo/components/style/gecko/url.rs
@@ -271,17 +271,17 @@ impl ToComputedValue for SpecifiedImageU
     fn from_computed_value(computed: &Self::ComputedValue) -> Self {
         computed.0.clone()
     }
 }
 
 fn serialize_computed_url<W>(
     url_value_data: &URLValueData,
     dest: &mut CssWriter<W>,
-    get_url: unsafe extern "C" fn(*const URLValueData, *mut nsCString) -> (),
+    get_url: unsafe extern "C" fn(*const URLValueData, *mut nsCString),
 ) -> fmt::Result
 where
     W: Write,
 {
     dest.write_str("url(")?;
     unsafe {
         let mut string = nsCString::new();
         get_url(url_value_data, &mut string);
--- a/taskcluster/ci/build/windows.yml
+++ b/taskcluster/ci/build/windows.yml
@@ -970,16 +970,87 @@ win32-mingw32/debug:
         - linux64-wine
         - linux64-cbindgen
         - linux64-sccache
         - linux64-mingw32-gcc
         - linux64-mingw32-nsis
         - linux64-mingw32-fxc2
         - linux64-node
 
+win32-mingwclang/opt:
+    description: "Win32 MinGW-Clang Opt"
+    index:
+        product: firefox
+        job-name: win32-mingwclang-opt
+    treeherder:
+        platform: windows-mingw32/all
+        symbol: WMC32(Bo)
+        tier: 2
+    worker-type: aws-provisioner-v1/gecko-{level}-b-linux
+    worker:
+        docker-image: {in-tree: mingw32-build}
+        max-run-time: 7200
+        env:
+            PERFHERDER_EXTRA_OPTIONS: "opt 32 clang"
+    run:
+        using: mozharness
+        actions: [build]
+        script: mozharness/scripts/fx_desktop_build.py
+        config:
+            - builds/releng_base_firefox.py
+            - builds/releng_base_windows_32_mingw_builds.py
+            - builds/releng_sub_windows_configs/32_mingwclang.py
+        need-xvfb: false
+    toolchains:
+        - mingw32-rust
+        - linux64-upx
+        - linux64-wine
+        - linux64-sccache
+        - linux64-cbindgen
+        - linux64-node
+        - linux64-clang-7-mingw-x86
+        - linux64-mingw32-nsis
+        - linux64-mingw32-fxc2
+
+win32-mingwclang/debug:
+    description: "Win32 MinGW-Clang Debug"
+    index:
+        product: firefox
+        job-name: win32-mingwclang-debug
+    treeherder:
+        platform: windows-mingw32/all
+        symbol: WMC32(Bd)
+        tier: 2
+    worker-type: aws-provisioner-v1/gecko-{level}-b-linux
+    worker:
+        docker-image: {in-tree: mingw32-build}
+        max-run-time: 72000
+        env:
+            PERFHERDER_EXTRA_OPTIONS: "debug 32 clang"
+    run:
+        using: mozharness
+        actions: [build]
+        script: mozharness/scripts/fx_desktop_build.py
+        config:
+            - builds/releng_base_firefox.py
+            - builds/releng_base_windows_32_mingw_builds.py
+            - builds/releng_sub_windows_configs/32_mingwclang.py
+            - builds/releng_sub_windows_configs/32_mingwclang_debug.py
+        need-xvfb: false
+    toolchains:
+        - mingw32-rust
+        - linux64-upx
+        - linux64-wine
+        - linux64-sccache
+        - linux64-cbindgen
+        - linux64-node
+        - linux64-clang-7-mingw-x86
+        - linux64-mingw32-nsis
+        - linux64-mingw32-fxc2
+
 win64-mingwclang/opt:
     description: "Win64 MinGW-Clang Opt"
     index:
         product: firefox
         job-name: win64-mingwclang-opt
     treeherder:
         platform: windows-mingw32/all
         symbol: WMC64(Bo)
--- a/taskcluster/ci/config.yml
+++ b/taskcluster/ci/config.yml
@@ -63,16 +63,17 @@ treeherder:
         'I': 'Docker Image Builds'
         'TL': 'Toolchain builds for Linux 64-bits'
         'TM': 'Toolchain builds for OSX'
         'TMW': 'Toolchain builds for Windows MinGW'
         'TW32': 'Toolchain builds for Windows 32-bits'
         'TW64': 'Toolchain builds for Windows 64-bits'
         'WM32': 'MinGW builds for Windows 32-bits'
         'WM64': 'MinGW builds for Windows 64-bits'
+        'WMC32': 'MinGW-Clang builds for Windows 32-bits'
         'WMC64': 'MinGW-Clang builds for Windows 64-bits'
         'Searchfox': 'Searchfox builds'
         'SM': 'Spidermonkey builds'
         'pub': 'APK publishing'
         'p': 'Partial generation'
         'ps': 'Partials signing'
         'Rel': 'Release promotion'
         'Snap': 'Snap image generation'
--- a/taskcluster/scripts/misc/build-clang-7-mingw.sh
+++ b/taskcluster/scripts/misc/build-clang-7-mingw.sh
@@ -5,20 +5,22 @@ set -x -e -v
 
 if [[ $# -eq 0 ]]; then
     echo "Provide either x86 or x64 to specify a toolchain."
     exit 1;
 elif [ "$1" == "x86" ]; then
   machine="i686"
   compiler_rt_machine="i386"
   crt_flags="--enable-lib32 --disable-lib64"
+  WRAPPER_FLAGS="-fsjlj-exceptions"
 elif [ "$1" == "x64" ]; then
   machine="x86_64"
   compiler_rt_machine="x86_64"
   crt_flags="--disable-lib32 --enable-lib64"
+  WRAPPER_FLAGS=""
 else
   echo "Provide either x86 or x64 to specify a toolchain."
   exit 1;
 fi
 
 WORKSPACE=$HOME/workspace
 HOME_DIR=$WORKSPACE/build
 UPLOAD_DIR=$HOME/artifacts
@@ -27,17 +29,17 @@ TOOLCHAIN_DIR=$WORKSPACE/moz-toolchain
 INSTALL_DIR=$TOOLCHAIN_DIR/build/stage3/clang
 CROSS_PREFIX_DIR=$INSTALL_DIR/$machine-w64-mingw32
 SRC_DIR=$TOOLCHAIN_DIR/src
 
 CLANG_VERSION=7.0.0
 make_flags="-j$(nproc)"
 
 mingw_version=cfd85ebed773810429bf2164c3a985895b7dbfe3
-libunwind_version=86ab23972978242b6f9e27cebc239f3e8428b1af
+libunwind_version=1f89d78bb488bc71cfdee8281fc0834e9fbe5dce
 
 binutils_version=2.27
 binutils_ext=bz2
 binutils_sha=369737ce51587f92466041a97ab7d2358c6d9e1b6490b3940eb09fb0a9a6ac88
 
 # This is default value of _WIN32_WINNT. Gecko configure script explicitly sets this,
 # so this is not used to build Gecko itself. We default to 0x600, which is Windows Vista.
 default_win32_winnt=0x600
@@ -72,27 +74,29 @@ prepare() {
   tar -jxf binutils-$binutils_version.tar.$binutils_ext
 
   popd
 }
 
 install_wrappers() {
   pushd $INSTALL_DIR/bin
 
+  compiler_flags="--sysroot \$DIR/../$machine-w64-mingw32 -rtlib=compiler-rt -stdlib=libc++ -fuse-ld=lld $WRAPPER_FLAGS -fuse-cxa-atexit -Qunused-arguments"
+
   cat <<EOF >$machine-w64-mingw32-clang
 #!/bin/sh
 DIR="\$(cd "\$(dirname "\$0")" && pwd)"
-\$DIR/clang -target $machine-w64-mingw32 --sysroot \$DIR/../$machine-w64-mingw32 -rtlib=compiler-rt -stdlib=libc++ -fuse-ld=lld -fdwarf-exceptions -Qunused-arguments "\$@"
+\$DIR/clang -target $machine-w64-mingw32 $compiler_flags "\$@"
 EOF
   chmod +x $machine-w64-mingw32-clang
 
   cat <<EOF >$machine-w64-mingw32-clang++
 #!/bin/sh
 DIR="\$(cd "\$(dirname "\$0")" && pwd)"
-\$DIR/clang -target $machine-w64-mingw32 --sysroot \$DIR/../$machine-w64-mingw32 --driver-mode=g++ -rtlib=compiler-rt -stdlib=libc++ -fuse-ld=lld -fdwarf-exceptions -Qunused-arguments "\$@"
+\$DIR/clang -target $machine-w64-mingw32 --driver-mode=g++ $compiler_flags "\$@"
 EOF
   chmod +x $machine-w64-mingw32-clang++
 
   CC="$machine-w64-mingw32-clang"
   CXX="$machine-w64-mingw32-clang++"
 
   popd
 }
new file mode 100644
--- /dev/null
+++ b/testing/mozharness/configs/builds/releng_sub_windows_configs/32_mingwclang.py
@@ -0,0 +1,7 @@
+config = {
+    'base_name': 'WINNT_5.2_MINGWCLANG_%(branch)s',
+    'platform': 'win32-mingwclang',
+    'stage_platform': 'win32-mingwclang',
+    'mozconfig_platform': 'win32',
+    'mozconfig_variant': 'mingwclang',
+}
new file mode 100644
--- /dev/null
+++ b/testing/mozharness/configs/builds/releng_sub_windows_configs/32_mingwclang_debug.py
@@ -0,0 +1,3 @@
+config = {
+    'mozconfig_variant': 'mingwclang-debug',
+}
--- a/testing/mozharness/mozharness/mozilla/building/buildbase.py
+++ b/testing/mozharness/mozharness/mozilla/building/buildbase.py
@@ -1689,16 +1689,26 @@ or run without that action (ie: --no-{ac
             self.mkdir_p(env['UPLOAD_PATH'])
 
     def _post_fatal(self, message=None, exit_code=None):
         if not self.return_code:  # only overwrite return_code if it's 0
             self.error('setting return code to 2 because fatal was called')
             self.return_code = 2
 
     @PostScriptRun
+    def _shutdown_sccache(self):
+        '''If sccache was in use for this build, shut down the sccache server.'''
+        if os.environ.get('USE_SCCACHE') == '1':
+            topsrcdir = self.query_abs_dirs()['abs_src_dir']
+            sccache = os.path.join(topsrcdir, 'sccache2', 'sccache')
+            if self._is_windows():
+                sccache += '.exe'
+            self.run_command([sccache, '--stop-server'], cwd=topsrcdir)
+
+    @PostScriptRun
     def _summarize(self):
         """ If this is run in automation, ensure the return code is valid and
         set it to one if it's not. Finally, log any summaries we collected
         from the script run.
         """
         if self.config.get("is_automation"):
             # let's ignore all mention of tbpl status until this
             # point so it will be easier to manage
--- a/testing/profiles/perf/user.js
+++ b/testing/profiles/perf/user.js
@@ -90,8 +90,10 @@ user_pref("plugins.flashBlock.enabled", 
 user_pref("privacy.reduceTimerPrecision", false); // Bug 1445243 - reduces precision of tests
 user_pref("privacy.trackingprotection.annotate_channels", false);
 user_pref("privacy.trackingprotection.enabled", false);
 user_pref("privacy.trackingprotection.introURL", "http://127.0.0.1/trackingprotection/tour");
 user_pref("privacy.trackingprotection.pbmode.enabled", false);
 user_pref("security.enable_java", false);
 user_pref("security.fileuri.strict_origin_policy", false);
 user_pref("toolkit.telemetry.server", "https://127.0.0.1/telemetry-dummy/");
+user_pref("startup.homepage_welcome_url", "");
+user_pref("startup.homepage_welcome_url.additional", "");
--- a/testing/talos/talos/test.py
+++ b/testing/talos/talos/test.py
@@ -427,19 +427,17 @@ class damp(PageloaderTest):
     tppagecycles = 5
     tploadnocache = True
     tpmozafterpaint = False
     gecko_profile_interval = 10
     gecko_profile_entries = 2000000
     win_counters = w7_counters = linux_counters = mac_counters = None
     filters = filter.ignore_first.prepare(1) + filter.median.prepare()
     preferences = {'devtools.memory.enabled': True,
-                   'addon.test.damp.webserver': '${webserver}',
-                   'startup.homepage_welcome_url': '',
-                   'startup.homepage_welcome_url.additional': ''}
+                   'addon.test.damp.webserver': '${webserver}'}
     unit = 'ms'
     subtest_alerts = True
     perfherder_framework = 'devtools'
 
 
 @register_test()
 class glterrain(PageloaderTest):
     """
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-contain/contain-layout-018.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[contain-layout-018.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-contain/contain-paint-025.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[contain-paint-025.html]
-  expected: FAIL
--- a/toolkit/components/antitracking/test/browser/browser.ini
+++ b/toolkit/components/antitracking/test/browser/browser.ini
@@ -13,17 +13,19 @@ support-files =
   3rdPartyOpenUI.html
   empty.js
   popup.html
 
 [browser_backgroundImageAssertion.js]
 [browser_blockingCookies.js]
 support-files = server.sjs
 [browser_blockingIndexedDb.js]
-[browser_blockingStorage.js]
+[browser_blockingLocalStorage.js]
+skip-if = serviceworker_e10s
+[browser_blockingSessionStorage.js]
 skip-if = serviceworker_e10s
 [browser_blockingWorkers.js]
 skip-if = (os == "win" && os_version == "6.1" && bits == 32 && !debug) # Bug 1491937
 [browser_blockingMessaging.js]
 [browser_blockingNoOpener.js]
 [browser_existingCookiesForSubresources.js]
 [browser_imageCache1.js]
 [browser_imageCache1-1.js]
@@ -46,11 +48,12 @@ skip-if = serviceworker_e10s
 skip-if = serviceworker_e10s
 [browser_subResources.js]
 support-files = subResources.sjs
 [browser_script.js]
 support-files = tracker.js
 [browser_userInteraction.js]
 [browser_storageAccessPrivateWindow.js]
 skip-if = serviceworker_e10s
+[browser_storageAccessPromiseResolveHandlerUserInteraction.js]
 [browser_storageAccessSandboxed.js]
 skip-if = serviceworker_e10s
 [browser_storageAccessWithHeuristics.js]
rename from toolkit/components/antitracking/test/browser/browser_blockingStorage.js
rename to toolkit/components/antitracking/test/browser/browser_blockingLocalStorage.js
--- a/toolkit/components/antitracking/test/browser/browser_blockingStorage.js
+++ b/toolkit/components/antitracking/test/browser/browser_blockingLocalStorage.js
@@ -13,35 +13,16 @@ AntiTracking.runTest("localStorage",
     ok(true, "LocalStorage is allowed");
   },
   async _ => {
     await new Promise(resolve => {
       Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value => resolve());
     });
   });
 
-AntiTracking.runTest("sessionStorage",
-  async _ => {
-    sessionStorage.foo = 42;
-    ok(true, "SessionStorage is always allowed");
-  },
-  async _ => {
-    sessionStorage.foo = 42;
-    ok(true, "SessionStorage is always allowed");
-  },
-  async _ => {
-    await new Promise(resolve => {
-      Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value => resolve());
-    });
-  },
-  [],
-  true,
-  true,
-  false);
-
 AntiTracking.runTest("localStorage and Storage Access API",
   async _ => {
     let hasAccess = await document.hasStorageAccess();
     ok(!hasAccess, "Doesn't yet have storage access");
 
     try {
       localStorage.foo = 42;
       ok(false, "LocalStorage cannot be used!");
@@ -93,65 +74,8 @@ AntiTracking.runTest("localStorage and S
     ok(true, "LocalStorage is allowed");
   },
   async _ => {
     await new Promise(resolve => {
       Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value => resolve());
     });
   },
   null, false, false);
-
-AntiTracking.runTest("sessionStorage and Storage Access API",
-  async _ => {
-    let hasAccess = await document.hasStorageAccess();
-    ok(!hasAccess, "Doesn't yet have storage access");
-
-    sessionStorage.foo = 42;
-    ok(true, "SessionStorage is always allowed");
-
-    let dwu = SpecialPowers.getDOMWindowUtils(window);
-    let helper = dwu.setHandlingUserInput(true);
-
-    let p;
-    try {
-      p = document.requestStorageAccess();
-    } finally {
-      helper.destruct();
-    }
-    await p;
-
-    hasAccess = await document.hasStorageAccess();
-    ok(hasAccess, "Now has storage access");
-
-    sessionStorage.foo = 42;
-    ok(true, "SessionStorage is allowed after calling the storage access API too");
-  },
-  async _ => {
-    let hasAccess = await document.hasStorageAccess();
-    ok(!hasAccess, "Doesn't yet have storage access");
-
-    sessionStorage.foo = 42;
-    ok(true, "SessionStorage is always allowed");
-
-    let dwu = SpecialPowers.getDOMWindowUtils(window);
-    let helper = dwu.setHandlingUserInput(true);
-
-    let p;
-    try {
-      p = document.requestStorageAccess();
-    } finally {
-      helper.destruct();
-    }
-    await p;
-
-    hasAccess = await document.hasStorageAccess();
-    ok(hasAccess, "Now has storage access");
-
-    // For non-tracking windows, calling the API is a no-op
-    sessionStorage.foo = 42;
-    ok(true, "SessionStorage is allowed after calling the storage access API too");
-  },
-  async _ => {
-    await new Promise(resolve => {
-      Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value => resolve());
-    });
-  },
-  null, false, false);
new file mode 100644
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_blockingSessionStorage.js
@@ -0,0 +1,75 @@
+AntiTracking.runTest("sessionStorage",
+  async _ => {
+    sessionStorage.foo = 42;
+    ok(true, "SessionStorage is always allowed");
+  },
+  async _ => {
+    sessionStorage.foo = 42;
+    ok(true, "SessionStorage is always allowed");
+  },
+  async _ => {
+    await new Promise(resolve => {
+      Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value => resolve());
+    });
+  },
+  [],
+  true,
+  true,
+  false);
+
+AntiTracking.runTest("sessionStorage and Storage Access API",
+  async _ => {
+    let hasAccess = await document.hasStorageAccess();
+    ok(!hasAccess, "Doesn't yet have storage access");
+
+    sessionStorage.foo = 42;
+    ok(true, "SessionStorage is always allowed");
+
+    let dwu = SpecialPowers.getDOMWindowUtils(window);
+    let helper = dwu.setHandlingUserInput(true);
+
+    let p;
+    try {
+      p = document.requestStorageAccess();
+    } finally {
+      helper.destruct();
+    }
+    await p;
+
+    hasAccess = await document.hasStorageAccess();
+    ok(hasAccess, "Now has storage access");
+
+    sessionStorage.foo = 42;
+    ok(true, "SessionStorage is allowed after calling the storage access API too");
+  },
+  async _ => {
+    let hasAccess = await document.hasStorageAccess();
+    ok(!hasAccess, "Doesn't yet have storage access");
+
+    sessionStorage.foo = 42;
+    ok(true, "SessionStorage is always allowed");
+
+    let dwu = SpecialPowers.getDOMWindowUtils(window);
+    let helper = dwu.setHandlingUserInput(true);
+
+    let p;
+    try {
+      p = document.requestStorageAccess();
+    } finally {
+      helper.destruct();
+    }
+    await p;
+
+    hasAccess = await document.hasStorageAccess();
+    ok(hasAccess, "Now has storage access");
+
+    // For non-tracking windows, calling the API is a no-op
+    sessionStorage.foo = 42;
+    ok(true, "SessionStorage is allowed after calling the storage access API too");
+  },
+  async _ => {
+    await new Promise(resolve => {
+      Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value => resolve());
+    });
+  },
+  null, false, false);
new file mode 100644
--- /dev/null
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessPromiseResolveHandlerUserInteraction.js
@@ -0,0 +1,47 @@
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+AntiTracking.runTest("Storage Access API returns promises that maintain user activation",
+  // blocking callback
+  async _ => {
+    let dwu = SpecialPowers.getDOMWindowUtils(window);
+    let helper = dwu.setHandlingUserInput(true);
+
+    let p;
+    let threw = false;
+    try {
+      p = document.requestStorageAccess();
+    } catch (e) {
+      threw = true;
+    } finally {
+      helper.destruct();
+    }
+    ok(!threw, "requestStorageAccess should not throw");
+    threw = false;
+    await p.then(() => {
+    });
+    threw = false;
+    try {
+      await p.then(() => {
+        ok(dwu.isHandlingUserInput,
+           "Promise handler must run as if we're handling user input");
+      });
+    } catch (e) {
+      threw = true;
+    }
+    ok(!threw, "requestStorageAccess should be available");
+  },
+
+  null, // non-blocking callback
+  // cleanup function
+  async _ => {
+    await new Promise(resolve => {
+      Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value => resolve());
+    });
+  },
+  [["dom.storage_access.enabled", true]], // extra prefs
+  false, // no window open test
+  false, // no user-interaction test
+  true, // expect blocking notifications
+  false, // run in normal window
+  null // iframe sandbox
+);
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -5354,395 +5354,450 @@
     "description": "PLACES: Days from last maintenance"
   },
   "UPDATE_CHECK_NO_UPDATE_EXTERNAL" : {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "count",
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: count of no updates were found for a background update check (externally initiated)"
   },
   "UPDATE_CHECK_NO_UPDATE_NOTIFY" : {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "count",
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: count of no updates were found for a background update check (timer initiated)"
   },
   "UPDATE_CHECK_CODE_EXTERNAL": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 50,
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: background update check result code except for no updates found (externally initiated)"
   },
   "UPDATE_CHECK_CODE_NOTIFY": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 50,
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: background update check result code except for no updates found (timer initiated)"
   },
   "UPDATE_CHECK_EXTENDED_ERROR_EXTERNAL": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "count",
     "keyed": true,
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: keyed count (key names are prefixed with AUS_CHECK_EX_ERR_) of background update check extended error code (externally initiated)"
   },
   "UPDATE_CHECK_EXTENDED_ERROR_NOTIFY": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "count",
     "keyed": true,
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: keyed count (key names are prefixed with AUS_CHECK_EX_ERR_) of background update check extended error code (timer initiated)"
   },
   "UPDATE_INVALID_LASTUPDATETIME_EXTERNAL": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "count",
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: count of systems that have a last update time greater than the current time (externally initiated)"
   },
   "UPDATE_INVALID_LASTUPDATETIME_NOTIFY": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "count",
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: count of systems that have a last update time greater than the current time (timer initiated)"
   },
   "UPDATE_LAST_NOTIFY_INTERVAL_DAYS_EXTERNAL": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "exponential",
     "n_buckets": 60,
     "high": 365,
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: interval in days since the last background update check (externally initiated)"
   },
   "UPDATE_LAST_NOTIFY_INTERVAL_DAYS_NOTIFY": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "exponential",
     "n_buckets": 30,
     "high": 180,
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: interval in days since the last background update check (timer initiated)"
   },
   "UPDATE_PING_COUNT_EXTERNAL": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "count",
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1151267],
     "description": "Update: count of systems for this ping for comparison with other pings (externally initiated)"
   },
   "UPDATE_PING_COUNT_NOTIFY": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "count",
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1151267],
     "description": "Update: count of systems for this ping for comparison with other pings (timer initiated)"
   },
   "UPDATE_SERVICE_INSTALLED_EXTERNAL": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "boolean",
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: whether the service is installed (externally initiated)"
   },
   "UPDATE_SERVICE_INSTALLED_NOTIFY": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "boolean",
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: whether the service is installed (timer initiated)"
   },
   "UPDATE_SERVICE_MANUALLY_UNINSTALLED_EXTERNAL": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "count",
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: count of systems that manually uninstalled the service (externally initiated)"
   },
   "UPDATE_SERVICE_MANUALLY_UNINSTALLED_NOTIFY": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "count",
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: count of systems that manually uninstalled the service (timer initiated)"
   },
   "UPDATE_UNABLE_TO_APPLY_EXTERNAL": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "count",
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1151267],
     "description": "Update: count of systems that cannot apply updates (externally initiated)"
   },
   "UPDATE_UNABLE_TO_APPLY_NOTIFY": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "count",
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1151267],
     "description": "Update: count of systems that cannot apply updates (timer initiated)"
   },
   "UPDATE_CANNOT_STAGE_EXTERNAL": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "count",
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: count of systems that cannot stage updates (externally initiated)"
   },
   "UPDATE_CANNOT_STAGE_NOTIFY": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "count",
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: count of systems that cannot stage updates (timer initiated)"
   },
   "UPDATE_PREF_UPDATE_CANCELATIONS_EXTERNAL": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 100,
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: number of sequential update elevation request cancelations greater than 0 (externally initiated)"
   },
   "UPDATE_PREF_UPDATE_CANCELATIONS_NOTIFY": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 100,
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: number of sequential update elevation request cancelations greater than 0 (timer initiated)"
   },
   "UPDATE_PREF_SERVICE_ERRORS_EXTERNAL": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 30,
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: number of sequential update service errors greater than 0 (externally initiated)"
   },
   "UPDATE_PREF_SERVICE_ERRORS_NOTIFY": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 30,
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: number of sequential update service errors greater than 0 (timer initiated)"
   },
   "UPDATE_NOT_PREF_UPDATE_AUTO_EXTERNAL": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "count",
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: count of when the app.update.auto boolean preference is not the default value of true (true values are not submitted)"
   },
   "UPDATE_NOT_PREF_UPDATE_AUTO_NOTIFY": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "count",
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: count of when the app.update.auto boolean preference is not the default value of true (true values are not submitted)"
   },
   "UPDATE_NOT_PREF_UPDATE_STAGING_ENABLED_EXTERNAL": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "count",
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: count of when the app.update.staging.enabled boolean preference is not the default value of true (true values are not submitted)"
   },
   "UPDATE_NOT_PREF_UPDATE_STAGING_ENABLED_NOTIFY": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "count",
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: count of when the app.update.staging.enabled boolean preference is not the default value of true (true values are not submitted)"
   },
   "UPDATE_NOT_PREF_UPDATE_SERVICE_ENABLED_EXTERNAL": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "count",
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: count of when the app.update.service.enabled boolean preference is not the default value of true (true values are not submitted)"
   },
   "UPDATE_NOT_PREF_UPDATE_SERVICE_ENABLED_NOTIFY": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "count",
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: count of when the app.update.service.enabled boolean preference is not the default value of true (true values are not submitted)"
   },
   "UPDATE_DOWNLOAD_CODE_COMPLETE": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 50,
     "releaseChannelCollection": "opt-out",
-    "description": "Update: complete patch download result code"
+    "bug_numbers": [1137447],
+    "description": "Update: complete patch type download result code"
   },
   "UPDATE_DOWNLOAD_CODE_PARTIAL": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 50,
     "releaseChannelCollection": "opt-out",
-    "description": "Update: partial patch download result code"
+    "bug_numbers": [1137447],
+    "description": "Update: partial patch type download result code"
+  },
+  "UPDATE_DOWNLOAD_CODE_UNKNOWN": {
+    "record_in_processes": ["main"],
+    "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
+    "expires_in_version": "never",
+    "kind": "enumerated",
+    "n_values": 50,
+    "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
+    "description": "Update: unknown patch type download result code"
   },
   "UPDATE_STATE_CODE_COMPLETE_STARTUP": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 20,
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: the state of a complete update from update.status on startup"
   },
   "UPDATE_STATE_CODE_PARTIAL_STARTUP": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 20,
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: the state of a partial patch update from update.status on startup"
   },
   "UPDATE_STATE_CODE_UNKNOWN_STARTUP": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 20,
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: the state of an unknown patch update from update.status on startup"
   },
   "UPDATE_STATE_CODE_COMPLETE_STAGE": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 20,
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: the state of a complete patch update from update.status after staging"
   },
   "UPDATE_STATE_CODE_PARTIAL_STAGE": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 20,
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: the state of a partial patch update from update.status after staging"
   },
   "UPDATE_STATE_CODE_UNKNOWN_STAGE": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 20,
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: the state of an unknown patch update from update.status after staging"
   },
   "UPDATE_STATUS_ERROR_CODE_COMPLETE_STARTUP": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 100,
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: the status error code for a failed complete patch update from update.status on startup"
   },
   "UPDATE_STATUS_ERROR_CODE_PARTIAL_STARTUP": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 100,
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: the status error code for a failed partial patch update from update.status on startup"
   },
   "UPDATE_STATUS_ERROR_CODE_UNKNOWN_STARTUP": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 100,
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: the status error code for a failed unknown patch update from update.status on startup"
   },
   "UPDATE_STATUS_ERROR_CODE_COMPLETE_STAGE": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 100,
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: the status error code for a failed complete patch update from update.status after staging"
   },
   "UPDATE_STATUS_ERROR_CODE_PARTIAL_STAGE": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 100,
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: the status error code for a failed partial patch update from update.status after staging"
   },
   "UPDATE_STATUS_ERROR_CODE_UNKNOWN_STAGE": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 100,
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: the status error code for a failed unknown patch update from update.status after staging"
   },
   "UPDATE_WIZ_LAST_PAGE_CODE": {
     "record_in_processes": ["main"],
     "alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 30,
     "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1137447],
     "description": "Update: the update wizard page displayed when the UI was closed (mapped in toolkit/mozapps/update/UpdateTelemetry.jsm)"
   },
   "UPTAKE_REMOTE_CONTENT_RESULT_1": {
     "record_in_processes": ["all"],
     "expires_in_version": "never",
     "kind": "categorical",
     "keyed": true,
     "labels": ["up_to_date", "success", "backoff", "pref_disabled", "parse_error", "content_error", "sign_error", "sign_retry_error", "conflict_error", "sync_error", "apply_error", "server_error", "certificate_error", "download_error", "timeout_error", "network_error", "offline_error", "cleanup_error", "unknown_error", "custom_1_error", "custom_2_error", "custom_3_error", "custom_4_error", "custom_5_error"],
--- a/toolkit/components/telemetry/docs/collection/events.rst
+++ b/toolkit/components/telemetry/docs/collection/events.rst
@@ -217,17 +217,17 @@ Register new events from add-ons.
   * ``extra_keys`` - *(optional, list of strings)* The valid extra keys for the event.
   * ``record_on_release`` - *(optional, bool)*
   * ``expired`` - *(optional, bool)* Whether this event entry is expired. This allows recording it without error, but it will be discarded. Defaults to false.
 
 For events recorded from add-ons, registration happens at runtime. Any new events must first be registered through this function before they can be recorded.
 The registered categories will automatically be enabled for recording.
 If a dynamic event uses the same category as a static event, the category will also be enabled upon registration.
 
-After registration, the events can be recorded through the ``recordEvent()`` function. They will be submitted in the main pings payload under ``processes.dynamic.events``.
+After registration, the events can be recorded through the ``recordEvent()`` function. They will be submitted in event pings like static events are, under the ``dynamic`` process.
 
 New events registered here are subject to the same limitations as the ones registered through ``Events.yaml``, although the naming was in parts updated to recent policy changes.
 
 When add-ons are updated, they may re-register all of their events. In that case, any changes to events that are already registered are ignored. The only exception is expiry; an event that is re-registered with ``expired: true`` will not be recorded anymore.
 
 Example:
 
 .. code-block:: js
--- a/toolkit/components/telemetry/histogram-whitelists.json
+++ b/toolkit/components/telemetry/histogram-whitelists.json
@@ -1128,61 +1128,16 @@
     "TOTAL_COUNT_LOW_ERRORS",
     "TRANSACTION_WAIT_TIME_HTTP",
     "TRANSACTION_WAIT_TIME_SPDY",
     "TRANSLATED_CHARACTERS",
     "TRANSLATED_PAGES",
     "TRANSLATED_PAGES_BY_LANGUAGE",
     "TRANSLATION_OPPORTUNITIES",
     "TRANSLATION_OPPORTUNITIES_BY_LANGUAGE",
-    "UPDATE_CANNOT_STAGE_EXTERNAL",
-    "UPDATE_CANNOT_STAGE_NOTIFY",
-    "UPDATE_CHECK_CODE_EXTERNAL",
-    "UPDATE_CHECK_CODE_NOTIFY",
-    "UPDATE_CHECK_EXTENDED_ERROR_EXTERNAL",
-    "UPDATE_CHECK_EXTENDED_ERROR_NOTIFY",
-    "UPDATE_CHECK_NO_UPDATE_EXTERNAL",
-    "UPDATE_CHECK_NO_UPDATE_NOTIFY",
-    "UPDATE_DOWNLOAD_CODE_COMPLETE",
-    "UPDATE_DOWNLOAD_CODE_PARTIAL",
-    "UPDATE_INVALID_LASTUPDATETIME_EXTERNAL",
-    "UPDATE_INVALID_LASTUPDATETIME_NOTIFY",
-    "UPDATE_LAST_NOTIFY_INTERVAL_DAYS_EXTERNAL",
-    "UPDATE_LAST_NOTIFY_INTERVAL_DAYS_NOTIFY",
-    "UPDATE_NOT_PREF_UPDATE_AUTO_EXTERNAL",
-    "UPDATE_NOT_PREF_UPDATE_AUTO_NOTIFY",
-    "UPDATE_NOT_PREF_UPDATE_SERVICE_ENABLED_EXTERNAL",
-    "UPDATE_NOT_PREF_UPDATE_SERVICE_ENABLED_NOTIFY",
-    "UPDATE_NOT_PREF_UPDATE_STAGING_ENABLED_EXTERNAL",
-    "UPDATE_NOT_PREF_UPDATE_STAGING_ENABLED_NOTIFY",
-    "UPDATE_PING_COUNT_EXTERNAL",
-    "UPDATE_PING_COUNT_NOTIFY",
-    "UPDATE_PREF_SERVICE_ERRORS_EXTERNAL",
-    "UPDATE_PREF_SERVICE_ERRORS_NOTIFY",
-    "UPDATE_PREF_UPDATE_CANCELATIONS_EXTERNAL",
-    "UPDATE_PREF_UPDATE_CANCELATIONS_NOTIFY",
-    "UPDATE_SERVICE_INSTALLED_EXTERNAL",
-    "UPDATE_SERVICE_INSTALLED_NOTIFY",
-    "UPDATE_SERVICE_MANUALLY_UNINSTALLED_EXTERNAL",
-    "UPDATE_SERVICE_MANUALLY_UNINSTALLED_NOTIFY",
-    "UPDATE_STATE_CODE_COMPLETE_STAGE",
-    "UPDATE_STATE_CODE_COMPLETE_STARTUP",
-    "UPDATE_STATE_CODE_PARTIAL_STAGE",
-    "UPDATE_STATE_CODE_PARTIAL_STARTUP",
-    "UPDATE_STATE_CODE_UNKNOWN_STAGE",
-    "UPDATE_STATE_CODE_UNKNOWN_STARTUP",
-    "UPDATE_STATUS_ERROR_CODE_COMPLETE_STAGE",
-    "UPDATE_STATUS_ERROR_CODE_COMPLETE_STARTUP",
-    "UPDATE_STATUS_ERROR_CODE_PARTIAL_STAGE",
-    "UPDATE_STATUS_ERROR_CODE_PARTIAL_STARTUP",
-    "UPDATE_STATUS_ERROR_CODE_UNKNOWN_STAGE",
-    "UPDATE_STATUS_ERROR_CODE_UNKNOWN_STARTUP",
-    "UPDATE_UNABLE_TO_APPLY_EXTERNAL",
-    "UPDATE_UNABLE_TO_APPLY_NOTIFY",
-    "UPDATE_WIZ_LAST_PAGE_CODE",
     "URLCLASSIFIER_CL_CHECK_TIME",
     "URLCLASSIFIER_LC_COMPLETIONS",
     "URLCLASSIFIER_LC_PREFIXES",
     "URLCLASSIFIER_PS_CONSTRUCT_TIME",
     "URLCLASSIFIER_PS_FALLOCATE_TIME",
     "URLCLASSIFIER_PS_FILELOAD_TIME",
     "VIDEO_CANPLAYTYPE_H264_CONSTRAINT_SET_FLAG",
     "VIDEO_CANPLAYTYPE_H264_LEVEL",
--- a/toolkit/crashreporter/tools/symbolstore.py
+++ b/toolkit/crashreporter/tools/symbolstore.py
@@ -593,17 +593,20 @@ class Dumper:
                             # MSVC creates `dynamic initializer for '...'`
                             # symbols.
                             elif "`dynamic initializer for '" in line:
                                 ctors += 1
 
                         # pass through all other lines unchanged
                         f.write(line)
                 f.close()
-                proc.wait()
+                retcode = proc.wait()
+                if retcode != 0:
+                    raise RuntimeError(
+                        "dump_syms failed with error code %d" % retcode)
                 # we output relative paths so callers can get a list of what
                 # was generated
                 print(rel_path)
                 if self.srcsrv and vcs_root:
                     # add source server indexing to the pdb file
                     self.SourceServerIndexing(debug_file, guid, sourceFileStream, vcs_root)
                 # only copy debug the first time if we have multiple architectures
                 if self.copy_debug and arch_num == 0:
--- a/toolkit/crashreporter/tools/unit-symbolstore.py
+++ b/toolkit/crashreporter/tools/unit-symbolstore.py
@@ -296,16 +296,17 @@ if target_platform() == 'WINNT':
             os.environ["PDBSTR_PATH"] = "pdbstr"
             # mock calls to `dump_syms`, `hg parent` and
             # `hg showconfig paths.default`
             mock_Popen.return_value.stdout = iter([
                     "MODULE os x86 %s %s" % ("X" * 33, test_files[0]),
                     "FILE 0 %s" % sourcefile,
                     "PUBLIC xyz 123"
                     ])
+            mock_Popen.return_value.wait.return_value = 0
             mock_communicate = mock_Popen.return_value.communicate
             mock_communicate.side_effect = [("abcd1234", ""),
                                             ("http://example.com/repo", ""),
                                             ]
             # And mock the call to pdbstr to capture the srcsrv stream data.
             global srcsrv_stream
             srcsrv_stream = None
             def mock_pdbstr(args, cwd="", **kwargs):
@@ -436,16 +437,17 @@ class TestFileMapping(HelperMixin, unitt
                 [
                     'FILE %d %s\n' % (i,s) for i, s in enumerate(files)
                 ] +
                 [
                     'PUBLIC xyz 123\n'
                 ]
             )
         mock_Popen.return_value.stdout = mk_output(dumped_files)
+        mock_Popen.return_value.wait.return_value = 0
 
         d = symbolstore.Dumper('dump_syms', self.symboldir,
                                file_mapping=file_mapping)
         f = os.path.join(self.objdir, 'somefile')
         open(f, 'wb').write('blah')
         d.Process(f)
         expected_output = ''.join(mk_output(expected_files))
         symbol_file = os.path.join(self.symboldir,
--- a/toolkit/mozapps/installer/packager.mk
+++ b/toolkit/mozapps/installer/packager.mk
@@ -29,17 +29,19 @@ stage-package: multilocale.txt locale-ma
 		$(if $(MOZ_PACKAGER_MINIFY_JS),--minify-js \
 		  $(addprefix --js-binary ,$(JS_BINARY)) \
 		) \
 		$(if $(JARLOG_DIR),$(addprefix --jarlog ,$(wildcard $(JARLOG_FILE_AB_CD)))) \
 		$(if $(OPTIMIZEJARS),--optimizejars) \
 		$(addprefix --compress ,$(JAR_COMPRESSION)) \
 		$(MOZ_PKG_MANIFEST) '$(DIST)' '$(DIST)'/$(MOZ_PKG_DIR)$(if $(MOZ_PKG_MANIFEST),,$(_BINPATH)) \
 		$(if $(filter omni,$(MOZ_PACKAGER_FORMAT)),$(if $(NON_OMNIJAR_FILES),--non-resource $(NON_OMNIJAR_FILES)))
+ifdef MOZ_AUTOMATION
 	$(PYTHON) $(MOZILLA_DIR)/toolkit/mozapps/installer/find-dupes.py $(DEFINES) $(ACDEFINES) $(MOZ_PKG_DUPEFLAGS) $(DIST)/$(MOZ_PKG_DIR)
+endif # MOZ_AUTOMATION
 ifndef MOZ_IS_COMM_TOPDIR
 	# Package mozharness
 	$(call py_action,test_archive, \
 		mozharness \
 		$(ABS_DIST)/$(PKG_PATH)$(MOZHARNESS_PACKAGE))
 endif # MOZ_IS_COMM_TOPDIR
 ifdef MOZ_PACKAGE_JSSHELL
 	# Package JavaScript Shell
--- a/toolkit/mozapps/installer/upload-files-APK.mk
+++ b/toolkit/mozapps/installer/upload-files-APK.mk
@@ -27,17 +27,17 @@ endif
 # produces signed, unaligned APK files, but this expects unsigned, unaligned
 # APK files.  The |zip -d| discards any existing signature, turning a signed,
 # unaligned APK into an unsigned, unaligned APK.  Sadly |zip -q| doesn't
 # silence a warning about "nothing to do" so we pipe to /dev/null.
 RELEASE_SIGN_ANDROID_APK = \
   cp $(1) $(2)-unaligned.apk && \
   ($(ZIP) -d $(2)-unaligned.apk 'META-INF/*' > /dev/null || true) && \
   $(RELEASE_JARSIGNER) $(2)-unaligned.apk && \
-  $(ZIPALIGN) -f -v 4 $(2)-unaligned.apk $(2) && \
+  $(ZIPALIGN) $(if $(MOZ_AUTOMATION),-v) -f 4 $(2)-unaligned.apk $(2) && \
   $(RM) $(2)-unaligned.apk
 
 # Files packed into the APK root.  Packing files into the APK root is not
 # supported by modern Android build systems, including Gradle, so don't add to
 # this list without Android peer approval.
 ROOT_FILES := \
   application.ini \
   package-name.txt \
--- a/toolkit/mozapps/update/UpdateTelemetry.jsm
+++ b/toolkit/mozapps/update/UpdateTelemetry.jsm
@@ -151,18 +151,18 @@ var AUSTLMY = {
   // Patch type Complete
   PATCH_COMPLETE: "COMPLETE",
   // Patch type partial
   PATCH_PARTIAL: "PARTIAL",
   // Patch type unknown
   PATCH_UNKNOWN: "UNKNOWN",
 
   /**
-   * Values for the UPDATE_DOWNLOAD_CODE_COMPLETE and
-   * UPDATE_DOWNLOAD_CODE_PARTIAL Telemetry histograms.
+   * Values for the UPDATE_DOWNLOAD_CODE_COMPLETE, UPDATE_DOWNLOAD_CODE_PARTIAL,
+   * and UPDATE_DOWNLOAD_CODE_UNKNOWN Telemetry histograms.
    */
   DWNLD_SUCCESS: 0,
   DWNLD_RETRY_OFFLINE: 1,
   DWNLD_RETRY_NET_TIMEOUT: 2,
   DWNLD_RETRY_CONNECTION_REFUSED: 3,
   DWNLD_RETRY_NET_RESET: 4,
   DWNLD_ERR_NO_UPDATE: 5,
   DWNLD_ERR_NO_UPDATE_PATCH: 6,
@@ -179,16 +179,17 @@ var AUSTLMY = {
    *
    * @param  aIsComplete
    *         If true the histogram is for a patch type complete, if false the
    *         histogram is for a patch type partial, and when undefined the
    *         histogram is for an unknown patch type. This is used to determine
    *         the histogram ID out of the following histogram IDs:
    *         UPDATE_DOWNLOAD_CODE_COMPLETE
    *         UPDATE_DOWNLOAD_CODE_PARTIAL
+   *         UPDATE_DOWNLOAD_CODE_UNKNOWN
    * @param  aCode
    *         An integer value as defined by the values that start with DWNLD_ in
    *         the above section.
    */
   pingDownloadCode: function UT_pingDownloadCode(aIsComplete, aCode) {
     let patchType = this.PATCH_UNKNOWN;
     if (aIsComplete === true) {
       patchType = this.PATCH_COMPLETE;
--- a/uriloader/prefetch/OfflineCacheUpdateParent.cpp
+++ b/uriloader/prefetch/OfflineCacheUpdateParent.cpp
@@ -257,24 +257,22 @@ OfflineCacheUpdateParent::SetRemoteTabs(
 NS_IMETHODIMP
 OfflineCacheUpdateParent::GetIsInIsolatedMozBrowserElement(bool* aIsInIsolatedMozBrowserElement)
 {
     NS_ENSURE_TRUE(mLoadingPrincipal, NS_ERROR_UNEXPECTED);
     return mLoadingPrincipal->GetIsInIsolatedMozBrowserElement(aIsInIsolatedMozBrowserElement);
 }
 
 NS_IMETHODIMP
-OfflineCacheUpdateParent::GetScriptableOriginAttributes(JS::MutableHandleValue aAttrs)
+OfflineCacheUpdateParent::GetScriptableOriginAttributes(JSContext* aCx,
+                                                        JS::MutableHandleValue aAttrs)
 {
     NS_ENSURE_TRUE(mLoadingPrincipal, NS_ERROR_UNEXPECTED);
 
-    JSContext* cx = nsContentUtils::GetCurrentJSContext();
-    MOZ_ASSERT(cx);
-
-    nsresult rv = mLoadingPrincipal->GetOriginAttributes(cx, aAttrs);
+    nsresult rv = mLoadingPrincipal->GetOriginAttributes(aCx, aAttrs);
     NS_ENSURE_SUCCESS(rv, rv);
 
     return NS_OK;
 }
 
 NS_IMETHODIMP_(void)
 OfflineCacheUpdateParent::GetOriginAttributes(mozilla::OriginAttributes& aAttrs)
 {
--- a/widget/windows/nsNativeThemeWin.cpp
+++ b/widget/windows/nsNativeThemeWin.cpp
@@ -700,22 +700,23 @@ nsresult nsNativeThemeWin::GetCachedMini
     case StyleAppearance::Menuseparator:
     {
       SIZE gutterSize(GetGutterSize(aTheme, hdc));
       aResult->width += gutterSize.cx;
       break;
     }
 
     case StyleAppearance::Menuarrow:
-    {
       // Use the width of the arrow glyph as padding. See the drawing
       // code for details.
       aResult->width *= 2;
       break;
-    }
+
+    default:
+      break;
   }
 
   ::ReleaseDC(nullptr, hdc);
 
   mMinimumWidgetSizeCacheValid[cacheBitIndex] |= cacheBit;
   mMinimumWidgetSizeCache[cacheIndex] = *aResult;
 
   return NS_OK;
@@ -821,18 +822,19 @@ mozilla::Maybe<nsUXThemeClass> nsNativeT
     case StyleAppearance::MozWindowButtonMinimize:
     case StyleAppearance::MozWindowButtonMaximize:
     case StyleAppearance::MozWindowButtonRestore:
     case StyleAppearance::MozWindowButtonBox:
     case StyleAppearance::MozWindowButtonBoxMaximized:
     case StyleAppearance::MozWinGlass:
     case StyleAppearance::MozWinBorderlessGlass:
       return Some(eUXWindowFrame);
+    default:
+      return Nothing();
   }
-  return Nothing();
 }
 
 HANDLE
 nsNativeThemeWin::GetTheme(WidgetType aWidgetType)
 {
   mozilla::Maybe<nsUXThemeClass> themeClass = GetThemeClass(aWidgetType);
   if (themeClass.isNothing()) {
     return nullptr;
@@ -1503,21 +1505,21 @@ nsNativeThemeWin::GetThemePartAndState(n
       return NS_OK;
     case StyleAppearance::MozWindowButtonBox:
     case StyleAppearance::MozWindowButtonBoxMaximized:
     case StyleAppearance::MozWinGlass:
     case StyleAppearance::MozWinBorderlessGlass:
       aPart = -1;
       aState = 0;
       return NS_OK;
+    default:
+      aPart = 0;
+      aState = 0;
+      return NS_ERROR_FAILURE;
   }
-
-  aPart = 0;
-  aState = 0;
-  return NS_ERROR_FAILURE;
 }
 
 static bool
 AssumeThemePartAndStateAreTransparent(int32_t aPart, int32_t aState)
 {
   if (!(IsWin8Point1OrLater() && nsUXThemeData::IsHighContrastOn()) &&
       aPart == MENU_POPUPITEM && aState == MBI_NORMAL) {
     return true;
@@ -1552,32 +1554,38 @@ IsScrollbarWidthThin(ComputedStyle* aSty
 
 static bool
 IsScrollbarWidthThin(nsIFrame* aFrame)
 {
   ComputedStyle* style = nsLayoutUtils::StyleForScrollbar(aFrame);
   return IsScrollbarWidthThin(style);
 }
 
+static bool
+ShouldDrawCustomScrollbar(ComputedStyle* aStyle)
+{
+  return aStyle->StyleUI()->HasCustomScrollbars() ||
+    IsScrollbarWidthThin(aStyle);
+}
+
 NS_IMETHODIMP
 nsNativeThemeWin::DrawWidgetBackground(gfxContext* aContext,
                                        nsIFrame* aFrame,
                                        WidgetType aWidgetType,
                                        const nsRect& aRect,
                                        const nsRect& aDirtyRect)
 {
   if (aWidgetType == StyleAppearance::MenulistButton &&
       StaticPrefs::layout_css_webkit_appearance_enabled()) {
     aWidgetType = StyleAppearance::Menulist;
   }
 
   if (IsWidgetScrollbarPart(aWidgetType)) {
     ComputedStyle* style = nsLayoutUtils::StyleForScrollbar(aFrame);
-    if (style->StyleUI()->HasCustomScrollbars() ||
-        IsScrollbarWidthThin(style)) {
+    if (ShouldDrawCustomScrollbar(style)) {
       return DrawCustomScrollbarPart(aContext, aFrame, style,
                                      aWidgetType, aRect, aDirtyRect);
     }
   }
 
   HANDLE theme = GetTheme(aWidgetType);
   if (!theme)
     return ClassicDrawWidgetBackground(aContext, aFrame, aWidgetType, aRect, aDirtyRect); 
@@ -1588,35 +1596,34 @@ nsNativeThemeWin::DrawWidgetBackground(g
       case StyleAppearance::MozWindowTitlebar:
       case StyleAppearance::MozWindowTitlebarMaximized:
       case StyleAppearance::MozWindowFrameLeft:
       case StyleAppearance::MozWindowFrameRight:
       case StyleAppearance::MozWindowFrameBottom:
         // Nothing to draw, these areas are glass. Minimum dimensions
         // should be set, so xul content should be layed out correctly.
         return NS_OK;
-      break;
       case StyleAppearance::MozWindowButtonClose:
       case StyleAppearance::MozWindowButtonMinimize:
       case StyleAppearance::MozWindowButtonMaximize:
       case StyleAppearance::MozWindowButtonRestore:
         // Not conventional bitmaps, can't be retrieved. If we fall
         // through here and call the theme library we'll get aero
         // basic bitmaps. 
         return NS_OK;
-      break;
       case StyleAppearance::MozWinGlass:
       case StyleAppearance::MozWinBorderlessGlass:
         // Nothing to draw, this is the glass background.
         return NS_OK;
       case StyleAppearance::MozWindowButtonBox:
       case StyleAppearance::MozWindowButtonBoxMaximized:
         // We handle these through nsIWidget::UpdateThemeGeometries
         return NS_OK;
-      break;
+      default:
+        break;
     }
   }
 
   int32_t part, state;
   nsresult rv = GetThemePartAndState(aFrame, aWidgetType, part, state);
   if (NS_FAILED(rv))
     return rv;
 
@@ -2018,28 +2025,16 @@ ScaleForFrameDPI(LayoutDeviceIntMargin* 
     aMargin->top = NSToIntRound(aMargin->top * themeScale);
     aMargin->left = NSToIntRound(aMargin->left * themeScale);
     aMargin->bottom = NSToIntRound(aMargin->bottom * themeScale);
     aMargin->right = NSToIntRound(aMargin->right * themeScale);
   }
 }
 
 static void
-ScaleForFrameDPI(nsIntMargin* aMargin, nsIFrame* aFrame)
-{
-  double themeScale = GetThemeDpiScaleFactor(aFrame);
-  if (themeScale != 1.0) {
-    aMargin->top = NSToIntRound(aMargin->top * themeScale);
-    aMargin->left = NSToIntRound(aMargin->left * themeScale);
-    aMargin->bottom = NSToIntRound(aMargin->bottom * themeScale);
-    aMargin->right = NSToIntRound(aMargin->right * themeScale);
-  }
-}
-
-static void
 ScaleForFrameDPI(LayoutDeviceIntSize* aSize, nsIFrame* aFrame)
 {
   double themeScale = GetThemeDpiScaleFactor(aFrame);
   if (themeScale != 1.0) {
     aSize->width = NSToIntRound(aSize->width * themeScale);
     aSize->height = NSToIntRound(aSize->height * themeScale);
   }
 }
@@ -2136,16 +2131,18 @@ nsNativeThemeWin::GetWidgetPadding(nsDev
   switch (aWidgetType) {
     // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
     // and have a meaningful baseline, so they can't have
     // author-specified padding.
     case StyleAppearance::Checkbox:
     case StyleAppearance::Radio:
       aResult->SizeTo(0, 0, 0, 0);
       return true;
+    default:
+      break;
   }
 
   bool ok = true;
 
   if (aWidgetType == StyleAppearance::MozWindowButtonBox ||
       aWidgetType == StyleAppearance::MozWindowButtonBoxMaximized) {
     aResult->SizeTo(0, 0, 0, 0);
 
@@ -2374,16 +2371,18 @@ nsNativeThemeWin::GetMinimumWidgetSize(n
     case StyleAppearance::Tabpanels:
     case StyleAppearance::Tabpanel:
     case StyleAppearance::Listbox:
     case StyleAppearance::Treeview:
     case StyleAppearance::Menuitemtext:
     case StyleAppearance::MozWinGlass:
     case StyleAppearance::MozWinBorderlessGlass:
       return NS_OK; // Don't worry about it.
+    default:
+      break;
   }
 
   if (aWidgetType == StyleAppearance::Menuitem && IsTopLevelMenu(aFrame))
       return NS_OK; // Don't worry about it for top level menus
 
   // Call GetSystemMetrics to determine size for WinXP scrollbars
   // (GetThemeSysSize API returns the optimal size for the theme, but 
   //  Windows appears to always use metrics when drawing standard scrollbars)
@@ -2552,16 +2551,19 @@ nsNativeThemeWin::GetMinimumWidgetSize(n
 
     case StyleAppearance::MozWindowFrameLeft:
     case StyleAppearance::MozWindowFrameRight:
     case StyleAppearance::MozWindowFrameBottom:
       aResult->width = GetSystemMetrics(SM_CXFRAME);
       aResult->height = GetSystemMetrics(SM_CYFRAME);
       *aIsOverridable = false;
       return rv;
+
+    default:
+      break;
   }
 
   int32_t part, state;
   rv = GetThemePartAndState(aFrame, aWidgetType, part, state);
   if (NS_FAILED(rv))
     return rv;
 
   rv = GetCachedMinimumWidgetSize(aFrame, theme, themeClass.value(), aWidgetType, part,
@@ -2754,16 +2756,39 @@ nsNativeThemeWin::ThemeGeometryTypeForWi
     default:
       return eThemeGeometryTypeUnknown;
   }
 }
 
 nsITheme::Transparency
 nsNativeThemeWin::GetWidgetTransparency(nsIFrame* aFrame, WidgetType aWidgetType)
 {
+  if (IsWidgetScrollbarPart(aWidgetType)) {
+    ComputedStyle* style = nsLayoutUtils::StyleForScrollbar(aFrame);
+    if (ShouldDrawCustomScrollbar(style)) {
+      if (style->StyleUI()->mScrollbarTrackColor.MaybeTransparent()) {
+        return eTransparent;
+      }
+      // DrawCustomScrollbarPart doesn't draw the track background for
+      // widgets on it, and these widgets are thinner than the track,
+      // so we need to return transparent for them.
+      switch (aWidgetType) {
+        case StyleAppearance::ScrollbarthumbHorizontal:
+        case StyleAppearance::ScrollbarthumbVertical:
+        case StyleAppearance::ScrollbarbuttonUp:
+        case StyleAppearance::ScrollbarbuttonDown:
+        case StyleAppearance::ScrollbarbuttonLeft:
+        case StyleAppearance::ScrollbarbuttonRight:
+          return eTransparent;
+        default:
+          break;
+      }
+    }
+  }
+
   switch (aWidgetType) {
   case StyleAppearance::ScrollbarSmall:
   case StyleAppearance::Scrollbar:
   case StyleAppearance::Scrollcorner:
   case StyleAppearance::Statusbar:
     // Knowing that scrollbars and statusbars are opaque improves
     // performance, because we create layers for them. This better be
     // true across all Windows themes! If it's not true, we should
@@ -2774,16 +2799,18 @@ nsNativeThemeWin::GetWidgetTransparency(
   case StyleAppearance::ScaleHorizontal:
   case StyleAppearance::ScaleVertical:
   case StyleAppearance::Progressbar:
   case StyleAppearance::ProgressbarVertical:
   case StyleAppearance::Progresschunk:
   case StyleAppearance::ProgresschunkVertical:
   case StyleAppearance::Range:
     return eTransparent;
+  default:
+    break;
   }
 
   HANDLE theme = GetTheme(aWidgetType);
   // For the classic theme we don't really have a way of knowing
   if (!theme) {
     // menu backgrounds and tooltips which can't be themed are opaque
     if (aWidgetType == StyleAppearance::Menupopup || aWidgetType == StyleAppearance::Tooltip) {
       return eOpaque;
@@ -2887,18 +2914,19 @@ nsNativeThemeWin::ClassicThemeSupportsWi
     case StyleAppearance::MozWindowFrameBottom:
     case StyleAppearance::MozWindowButtonClose:
     case StyleAppearance::MozWindowButtonMinimize:
     case StyleAppearance::MozWindowButtonMaximize:
     case StyleAppearance::MozWindowButtonRestore:
     case StyleAppearance::MozWindowButtonBox:
     case StyleAppearance::MozWindowButtonBoxMaximized:
       return true;
+    default:
+      return false;
   }
-  return false;
 }
 
 LayoutDeviceIntMargin
 nsNativeThemeWin::ClassicGetWidgetBorder(nsDeviceContext* aContext, 
                                          nsIFrame* aFrame,
                                          WidgetType aWidgetType)
 {
   if (aWidgetType == StyleAppearance::MenulistButton &&
@@ -3432,16 +3460,18 @@ nsresult nsNativeThemeWin::ClassicGetThe
           aState = DFCS_SCROLLDOWN;
           break;
         case StyleAppearance::ScrollbarbuttonLeft:
           aState = DFCS_SCROLLLEFT;
           break;
         case StyleAppearance::ScrollbarbuttonRight:
           aState = DFCS_SCROLLRIGHT;
           break;
+        default:
+          break;
       }
 
       if (IsDisabled(aFrame, contentState))
         aState |= DFCS_INACTIVE;
       else {
         if (contentState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE))
           aState |= DFCS_PUSHED | DFCS_FLAT;
       }
@@ -3457,16 +3487,18 @@ nsresult nsNativeThemeWin::ClassicGetThe
       switch (aWidgetType) {
         case StyleAppearance::SpinnerUpbutton:
           aState = DFCS_SCROLLUP;
           break;
         case StyleAppearance::InnerSpinButton:
         case StyleAppearance::SpinnerDownbutton:
           aState = DFCS_SCROLLDOWN;
           break;
+        default:
+          break;
       }
 
       if (IsDisabled(aFrame, contentState))
         aState |= DFCS_INACTIVE;
       else {
         if (contentState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE))
           aState |= DFCS_PUSHED;
       }
@@ -3521,18 +3553,19 @@ nsresult nsNativeThemeWin::ClassicGetThe
                                                                 aWidgetType));
       return NS_OK;
     case StyleAppearance::MozWindowButtonRestore:
       aPart = DFC_CAPTION;
       aState = DFCS_CAPTIONRESTORE |
                GetClassicWindowFrameButtonState(GetContentState(aFrame,
                                                                 aWidgetType));
       return NS_OK;
+    default:
+      return NS_ERROR_FAILURE;
   }
-  return NS_ERROR_FAILURE;
 }
 
 // Draw classic Windows tab
 // (no system API for this, but DrawEdge can draw all the parts of a tab)
 static void DrawTab(HDC hdc, const RECT& R, int32_t aPosition, bool aSelected,
                     bool aDrawLeft, bool aDrawRight)
 {
   int32_t leftFlag, topFlag, rightFlag, lightFlag, shadeFlag;  
@@ -4148,54 +4181,16 @@ nsNativeThemeWin::GetWidgetNativeDrawing
 
     case StyleAppearance::Menulist:
     case StyleAppearance::MenulistTextfield:
       return
         gfxWindowsNativeDrawing::CANNOT_DRAW_TO_COLOR_ALPHA |
         gfxWindowsNativeDrawing::CAN_AXIS_ALIGNED_SCALE |
         gfxWindowsNativeDrawing::CANNOT_COMPLEX_TRANSFORM;
 
-    // need to check these others
-    case StyleAppearance::Range:
-    case StyleAppearance::RangeThumb:
-    case StyleAppearance::ScrollbarbuttonUp:
-    case StyleAppearance::ScrollbarbuttonDown:
-    case StyleAppearance::ScrollbarbuttonLeft:
-    case StyleAppearance::ScrollbarbuttonRight:
-    case StyleAppearance::ScrollbarthumbVertical:
-    case StyleAppearance::ScrollbarthumbHorizontal:
-    case StyleAppearance::ScrollbarVertical:
-    case StyleAppearance::ScrollbarHorizontal:
-    case StyleAppearance::Scrollcorner:
-    case StyleAppearance::ScaleHorizontal:
-    case StyleAppearance::ScaleVertical:
-    case StyleAppearance::ScalethumbHorizontal:
-    case StyleAppearance::ScalethumbVertical:
-    case StyleAppearance::InnerSpinButton:
-    case StyleAppearance::SpinnerUpbutton:
-    case StyleAppearance::SpinnerDownbutton:
-    case StyleAppearance::Listbox:
-    case StyleAppearance::Treeview:
-    case StyleAppearance::Tooltip:
-    case StyleAppearance::Statusbar:
-    case StyleAppearance::Statusbarpanel:
-    case StyleAppearance::Resizerpanel:
-    case StyleAppearance::Resizer:
-    case StyleAppearance::Progressbar:
-    case StyleAppearance::ProgressbarVertical:
-    case StyleAppearance::Progresschunk:
-    case StyleAppearance::ProgresschunkVertical:
-    case StyleAppearance::Tab:
-    case StyleAppearance::Tabpanel:
-    case StyleAppearance::Tabpanels:
-    case StyleAppearance::Menubar:
-    case StyleAppearance::Menupopup:
-    case StyleAppearance::Menuitem:
-      break;
-
     // the dropdown button /almost/ renders correctly with scaling,
     // except that the graphic in the dropdown button (the downward arrow)
     // doesn't get scaled up.
     case StyleAppearance::MenulistButton:
     case StyleAppearance::MozMenulistButton:
     // these are definitely no; they're all graphics that don't get scaled up
     case StyleAppearance::Checkbox:
     case StyleAppearance::Radio:
@@ -4204,28 +4199,24 @@ nsNativeThemeWin::GetWidgetNativeDrawing
     case StyleAppearance::Radiomenuitem:
     case StyleAppearance::Menucheckbox:
     case StyleAppearance::Menuradio:
     case StyleAppearance::Menuarrow:
       return
         gfxWindowsNativeDrawing::CANNOT_DRAW_TO_COLOR_ALPHA |
         gfxWindowsNativeDrawing::CANNOT_AXIS_ALIGNED_SCALE |
         gfxWindowsNativeDrawing::CANNOT_COMPLEX_TRANSFORM;
+
+    // need to check these others
+    default:
+      return
+        gfxWindowsNativeDrawing::CANNOT_DRAW_TO_COLOR_ALPHA |
+        gfxWindowsNativeDrawing::CANNOT_AXIS_ALIGNED_SCALE |
+        gfxWindowsNativeDrawing::CANNOT_COMPLEX_TRANSFORM;
   }
-
-  return
-    gfxWindowsNativeDrawing::CANNOT_DRAW_TO_COLOR_ALPHA |
-    gfxWindowsNativeDrawing::CANNOT_AXIS_ALIGNED_SCALE |
-    gfxWindowsNativeDrawing::CANNOT_COMPLEX_TRANSFORM;
-}
-
-static COLORREF
-ToColorRef(nscolor aColor)
-{
-  return RGB(NS_GET_R(aColor), NS_GET_G(aColor), NS_GET_B(aColor));
 }
 
 static nscolor
 GetScrollbarButtonColor(nscolor aTrackColor, EventStates aStates)
 {
   // See numbers in GetScrollbarArrowColor.
   // This function is written based on ratios between values listed there.
 
@@ -4277,19 +4268,19 @@ GetScrollbarArrowColor(nscolor aButtonCo
     // be constexpr because of std::pow.
     const float GRAY96_LUMINANCE = 0.117f;
     return RelativeLuminanceUtils::Adjust(aButtonColor, GRAY96_LUMINANCE);
   }
   // The contrast ratio of a color to black equals that to white when its
   // luminance is around 0.18, with a contrast ratio ~4.6 to both sides,
   // thus the value below. It's the lumanince of gray 118.
   if (luminance >= 0.18) {
-    return NS_RGB(0, 0, 0);
+    return NS_RGBA(0, 0, 0, NS_GET_A(aButtonColor));
   }
-  return NS_RGB(255, 255, 255);
+  return NS_RGBA(255, 255, 255, NS_GET_A(aButtonColor));
 }
 
 static nscolor
 AdjustScrollbarFaceColor(nscolor aFaceColor, EventStates aStates)
 {
   // In Windows 10, scrollbar thumb has the following colors:
   //
   // State  | Color    | Luminance
@@ -4326,133 +4317,131 @@ AdjustScrollbarFaceColor(nscolor aFaceCo
 nsresult
 nsNativeThemeWin::DrawCustomScrollbarPart(gfxContext* aContext,
                                           nsIFrame* aFrame,
                                           ComputedStyle* aStyle,
                                           WidgetType aWidgetType,
                                           const nsRect& aRect,
                                           const nsRect& aClipRect)
 {
-  MOZ_ASSERT(!aStyle->StyleUI()->mScrollbarFaceColor.IsAuto() ||
-             !aStyle->StyleUI()->mScrollbarTrackColor.IsAuto() ||
-             IsScrollbarWidthThin(aStyle));
-
   EventStates eventStates = GetContentState(aFrame, aWidgetType);
 
-  gfxRect tr(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()),
-          dr(aClipRect.X(), aClipRect.Y(),
-             aClipRect.Width(), aClipRect.Height());
-
-  nscolor trackColor =
-    GetScrollbarTrackColor(aStyle, &GetScrollbarTrackColorForAuto);
-  HBRUSH dcBrush = (HBRUSH) GetStockObject(DC_BRUSH);
-
+  gfxContextAutoSaveRestore autoSave(aContext);
+  RefPtr<gfxContext> ctx = aContext;
   gfxFloat p2a = gfxFloat(aFrame->PresContext()->AppUnitsPerDevPixel());
-  tr.Scale(1.0 / p2a);
-  dr.Scale(1.0 / p2a);
-
-  RefPtr<gfxContext> ctx = aContext;
-
-  uint32_t flags = GetWidgetNativeDrawingFlags(aWidgetType);
-  gfxWindowsNativeDrawing nativeDrawing(ctx, dr, flags);
-
-  do {
-    HDC hdc = nativeDrawing.BeginNativeDrawing();
-    if (!hdc) {
-      return NS_ERROR_FAILURE;
+  gfxRect clipRect =
+    ThebesRect(LayoutDevicePixel::FromAppUnits(aClipRect, p2a).ToUnknownRect());
+  ctx->Clip(clipRect);
+  gfxRect rect =
+    ThebesRect(LayoutDevicePixel::FromAppUnits(aRect, p2a).ToUnknownRect());
+
+  const nsStyleUI* ui = aStyle->StyleUI();
+  nscolor trackColor = ui->mScrollbarTrackColor.IsAuto()
+    ? GetScrollbarTrackColorForAuto(aStyle)
+    : ui->mScrollbarTrackColor.CalcColor(aStyle);
+  switch (aWidgetType) {
+    case StyleAppearance::ScrollbarHorizontal:
+    case StyleAppearance::ScrollbarVertical:
+    case StyleAppearance::Scrollcorner: {
+      ctx->SetColor(Color::FromABGR(trackColor));
+      ctx->Rectangle(rect);
+      ctx->Fill();
+      return NS_OK;
     }
-
-    RECT widgetRect;
-    nativeDrawing.TransformToNativeRect(tr, widgetRect);
-    ::SetDCBrushColor(hdc, ToColorRef(trackColor));
-    ::SelectObject(hdc, dcBrush);
-    ::FillRect(hdc, &widgetRect, dcBrush);
-
-    RECT componentRect;
-    // Scrollbar thumb and button are two CSS pixels thinner than the track.
-    gfxRect tr2 = tr;
-    gfxFloat dev2css = round(AppUnitsPerCSSPixel() / p2a);
-    if (aWidgetType == StyleAppearance::ScrollbarthumbVertical ||
-        aWidgetType == StyleAppearance::ScrollbarbuttonUp ||
-        aWidgetType == StyleAppearance::ScrollbarbuttonDown) {
-        tr2.Deflate(dev2css, 0);
-    } else {
-      tr2.Deflate(0, dev2css);
+    default:
+      break;
+  }
+
+  // Scrollbar thumb and button are two CSS pixels thinner than the track.
+  gfxRect bgRect = rect;
+  gfxFloat dev2css = round(AppUnitsPerCSSPixel() / p2a);
+  switch (aWidgetType) {
+    case StyleAppearance::ScrollbarthumbVertical:
+    case StyleAppearance::ScrollbarbuttonUp:
+    case StyleAppearance::ScrollbarbuttonDown:
+      bgRect.Deflate(dev2css, 0);
+      break;
+    case StyleAppearance::ScrollbarthumbHorizontal:
+    case StyleAppearance::ScrollbarbuttonLeft:
+    case StyleAppearance::ScrollbarbuttonRight:
+      bgRect.Deflate(0, dev2css);
+      break;
+    default:
+      MOZ_ASSERT_UNREACHABLE("Unknown widget type");
+  }
+
+  switch (aWidgetType) {
+    case StyleAppearance::ScrollbarthumbVertical:
+    case StyleAppearance::ScrollbarthumbHorizontal: {
+      nscolor faceColor = ui->mScrollbarFaceColor.IsAuto()
+        ? GetScrollbarFaceColorForAuto(aStyle)
+        : ui->mScrollbarFaceColor.CalcColor(aStyle);
+      faceColor = AdjustScrollbarFaceColor(faceColor, eventStates);
+      ctx->SetColor(Color::FromABGR(faceColor));
+      ctx->Rectangle(bgRect);
+      ctx->Fill();
+      break;
     }
-    nativeDrawing.TransformToNativeRect(tr2, componentRect);
-
-    switch (aWidgetType) {
-      case StyleAppearance::ScrollbarthumbVertical:
-      case StyleAppearance::ScrollbarthumbHorizontal: {
-        nscolor faceColor =
-          GetScrollbarFaceColor(aStyle, &GetScrollbarFaceColorForAuto);
-        faceColor = AdjustScrollbarFaceColor(faceColor, eventStates);
-        ::SetDCBrushColor(hdc, ToColorRef(faceColor));
-        ::FillRect(hdc, &componentRect, dcBrush);
-        break;
+    case StyleAppearance::ScrollbarbuttonUp:
+    case StyleAppearance::ScrollbarbuttonDown:
+    case StyleAppearance::ScrollbarbuttonLeft:
+    case StyleAppearance::ScrollbarbuttonRight: {
+      nscolor buttonColor = GetScrollbarButtonColor(trackColor, eventStates);
+      ctx->SetColor(Color::FromABGR(buttonColor));
+      ctx->Rectangle(bgRect);
+      ctx->Fill();
+
+      // We use the path of scrollbar up arrow on Windows 10 which is
+      // in a 17x17 area.
+      const gfxFloat kSize = 17.0;
+      // Setup the transform matrix.
+      gfxFloat width = rect.Width();
+      gfxFloat height = rect.Height();
+      gfxFloat size = std::min(width, height);
+      gfxFloat left = (width - size) / 2.0 + rect.x;
+      gfxFloat top = (height - size) / 2.0 + rect.y;
+      gfxFloat scale = size / kSize;
+      gfxFloat rad = 0.0;
+      if (aWidgetType == StyleAppearance::ScrollbarbuttonRight) {
+        rad = M_PI / 2;
+      } else if (aWidgetType == StyleAppearance::ScrollbarbuttonDown) {
+        rad = M_PI;
+      } else if (aWidgetType == StyleAppearance::ScrollbarbuttonLeft) {
+        rad = -M_PI / 2;
       }
-      case StyleAppearance::ScrollbarbuttonUp:
-      case StyleAppearance::ScrollbarbuttonDown:
-      case StyleAppearance::ScrollbarbuttonLeft:
-      case StyleAppearance::ScrollbarbuttonRight: {
-        nscolor buttonColor = GetScrollbarButtonColor(trackColor, eventStates);
-        ::SetDCBrushColor(hdc, ToColorRef(buttonColor));
-        ::FillRect(hdc, &componentRect, dcBrush);
-
-        // kPath is the path of scrollbar up arrow on Windows 10
-        // in a 17x17 area.
-        const LONG kSize = 17;
-        const POINT kPath[] = {
-          { 5, 9 }, { 8, 6 }, { 11, 9 }, { 11, 11 }, { 8, 8 }, { 5, 11 },
-        };
-        const size_t kCount = ArrayLength(kPath);
-        // Calculate necessary parameters for positioning the arrow.
-        LONG width = widgetRect.right - widgetRect.left;
-        LONG height = widgetRect.bottom - widgetRect.top;
-        LONG size = std::min(width, height);
-        LONG left = (width - size) / 2 + widgetRect.left;
-        LONG top = (height - size) / 2 + widgetRect.top;
-        float unit = float(size) / kSize;
-        POINT path[kCount];
-        // Flip the path for different direction, then resize and align
-        // it to the middle of the area.
-        for (size_t i = 0; i < kCount; i++) {
-          if (aWidgetType == StyleAppearance::ScrollbarbuttonUp) {
-            path[i] = kPath[i];
-          } else if (aWidgetType == StyleAppearance::ScrollbarbuttonDown) {
-            path[i].x = kPath[i].x;
-            path[i].y = kSize - kPath[i].y;
-          } else if (aWidgetType == StyleAppearance::ScrollbarbuttonLeft) {
-            path[i].x = kPath[i].y;
-            path[i].y = kPath[i].x;
-          } else {
-            path[i].x = kSize - kPath[i].y;
-            path[i].y = kPath[i].x;
-          }
-          path[i].x = left + (LONG) round(unit * path[i].x);
-          path[i].y = top + (LONG) round(unit * path[i].y);
-        }
-        // Paint the arrow.
-        COLORREF arrowColor = ToColorRef(GetScrollbarArrowColor(buttonColor));
-        // XXX Somehow we need to paint with both pen and brush to get
-        //     the desired shape. Can we do so only with brush?
-        ::SetDCPenColor(hdc, arrowColor);
-        ::SetDCBrushColor(hdc, arrowColor);
-        ::SelectObject(hdc, GetStockObject(DC_PEN));
-        ::Polygon(hdc, path, kCount);
-        break;
+      gfx::Matrix mat = ctx->CurrentMatrix();
+      mat.PreTranslate(left, top);
+      mat.PreScale(scale, scale);
+      if (rad != 0.0) {
+        const gfxFloat kOffset = kSize / 2.0;
+        mat.PreTranslate(kOffset, kOffset);
+        mat.PreRotate(rad);
+        mat.PreTranslate(-kOffset, -kOffset);
       }
-      default:
-        break;
+      ctx->SetMatrix(mat);
+      // The arrow should not have antialias applied.
+      ctx->SetAntialiasMode(gfx::AntialiasMode::NONE);
+      // Set the arrow path.
+      ctx->NewPath();
+      ctx->MoveTo(gfxPoint(5.0, 9.0));
+      ctx->LineTo(gfxPoint(8.5, 6.0));
+      ctx->LineTo(gfxPoint(12.0, 9.0));
+      ctx->LineTo(gfxPoint(12.0, 12.0));
+      ctx->LineTo(gfxPoint(8.5, 9.0));
+      ctx->LineTo(gfxPoint(5.0, 12.0));
+      ctx->ClosePath();
+      // And paint the arrow.
+      nscolor arrowColor = GetScrollbarArrowColor(buttonColor);
+      ctx->SetColor(Color::FromABGR(arrowColor));
+      ctx->Fill();
+      break;
     }
-
-    nativeDrawing.EndNativeDrawing();
-  } while (nativeDrawing.ShouldRenderAgain());
-
-  nativeDrawing.PaintToContext();
+    default:
+      MOZ_ASSERT_UNREACHABLE("Unknown widget type");
+  }
   return NS_OK;
 }
 
 ///////////////////////////////////////////
 // Creation Routine
 ///////////////////////////////////////////
 
 // from nsWindow.cpp
--- a/xpcom/base/CycleCollectedJSContext.cpp
+++ b/xpcom/base/CycleCollectedJSContext.cpp
@@ -4,42 +4,43 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/CycleCollectedJSContext.h"
 #include <algorithm>
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/CycleCollectedJSRuntime.h"
+#include "mozilla/EventStateManager.h"
 #include "mozilla/Move.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Sprintf.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TimelineConsumers.h"
 #include "mozilla/TimelineMarker.h"
 #include "mozilla/Unused.h"
 #include "mozilla/DebuggerOnGCRunnable.h"
 #include "mozilla/dom/DOMJSClass.h"
 #include "mozilla/dom/DOMException.h"
 #include "mozilla/dom/ProfileTimelineMarkerBinding.h"
-#include "mozilla/dom/Promise.h"
 #include "mozilla/dom/PromiseBinding.h"
 #include "mozilla/dom/PromiseDebugging.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "jsapi.h"
 #include "js/Debug.h"
 #include "js/GCAPI.h"
 #include "js/Utility.h"
 #include "nsContentUtils.h"
 #include "nsCycleCollectionNoteRootCallback.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsCycleCollector.h"
 #include "nsDOMJSUtils.h"
 #include "nsDOMMutationObserver.h"
 #include "nsJSUtils.h"
+#include "nsPIDOMWindow.h"
 #include "nsWrapperCache.h"
 #include "nsStringBuffer.h"
 
 #include "nsThread.h"
 #include "nsThreadUtils.h"
 #include "xpcpublic.h"
 
 using namespace mozilla;
@@ -198,37 +199,56 @@ size_t
 CycleCollectedJSContext::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
 {
   return 0;
 }
 
 class PromiseJobRunnable final : public MicroTaskRunnable
 {
 public:
-  PromiseJobRunnable(JS::HandleObject aCallback,
+  PromiseJobRunnable(JS::HandleObject aPromise,
+                     JS::HandleObject aCallback,
                      JS::HandleObject aCallbackGlobal,
                      JS::HandleObject aAllocationSite,
                      nsIGlobalObject* aIncumbentGlobal)
-    :mCallback(
+    : mCallback(
        new PromiseJobCallback(aCallback, aCallbackGlobal, aAllocationSite,
                               aIncumbentGlobal))
+    , mPropagateUserInputEventHandling(false)
   {
     MOZ_ASSERT(js::IsFunctionObject(aCallback));
+
+    if (aPromise) {
+      JS::PromiseUserInputEventHandlingState state =
+        JS::GetPromiseUserInputEventHandlingState(aPromise);
+      mPropagateUserInputEventHandling =
+        state == JS::PromiseUserInputEventHandlingState::HadUserInteractionAtCreation;
+    }
   }
 
   virtual ~PromiseJobRunnable()
   {
   }
 
 protected:
   virtual void Run(AutoSlowOperation& aAso) override
   {
     JSObject* callback = mCallback->CallbackPreserveColor();
     nsIGlobalObject* global = callback ? xpc::NativeGlobal(callback) : nullptr;
     if (global && !global->IsDying()) {
+      // Propagate the user input event handling bit if needed.
+      nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(global);
+      nsCOMPtr<nsIDocument> doc;
+      if (win) {
+        doc = win->GetExtantDoc();
+      }
+      AutoHandlingUserInputStatePusher userInpStatePusher(mPropagateUserInputEventHandling,
+                                                          nullptr,
+                                                          doc);
+
       mCallback->Call("promise callback");
       aAso.CheckForInterrupt();
     }
     // Now that mCallback is no longer needed, clear any pointers it contains to
     // JS GC things. This removes any storebuffer entries associated with those
     // pointers, which can cause problems by taking up memory and by triggering
     // minor GCs. This otherwise would not happen until the next minor GC or
     // cycle collection.
@@ -239,49 +259,52 @@ protected:
   {
     nsIGlobalObject* global =
       xpc::NativeGlobal(mCallback->CallbackPreserveColor());
     return global && global->IsInSyncOperation();
   }
 
 private:
   RefPtr<PromiseJobCallback> mCallback;
+  bool mPropagateUserInputEventHandling;
 };
 
 /* static */
 JSObject*
 CycleCollectedJSContext::GetIncumbentGlobalCallback(JSContext* aCx)
 {
   nsIGlobalObject* global = mozilla::dom::GetIncumbentGlobal();
   if (global) {
     return global->GetGlobalJSObject();
   }
   return nullptr;
 }
 
 /* static */
 bool
 CycleCollectedJSContext::EnqueuePromiseJobCallback(JSContext* aCx,
+                                                   JS::HandleObject aPromise,
                                                    JS::HandleObject aJob,
                                                    JS::HandleObject aAllocationSite,
                                                    JS::HandleObject aIncumbentGlobal,
                                                    void* aData)
 {
   CycleCollectedJSContext* self = static_cast<CycleCollectedJSContext*>(aData);
   MOZ_ASSERT(aCx == self->Context());
   MOZ_ASSERT(Get() == self);
 
   nsIGlobalObject* global = nullptr;
   if (aIncumbentGlobal) {
     global = xpc::NativeGlobal(aIncumbentGlobal);
   }
   JS::RootedObject jobGlobal(aCx, JS::CurrentGlobalOrNull(aCx));
-  RefPtr<MicroTaskRunnable> runnable = new PromiseJobRunnable(aJob, jobGlobal,
-                                                              aAllocationSite,
-                                                              global);
+  RefPtr<PromiseJobRunnable> runnable = new PromiseJobRunnable(aPromise, aJob,
+                                                               jobGlobal,
+                                                               aAllocationSite,
+                                                               global);
   self->DispatchToMicroTask(runnable.forget());
   return true;
 }
 
 /* static */
 void
 CycleCollectedJSContext::PromiseRejectionTrackerCallback(JSContext* aCx,
                                                          JS::HandleObject aPromise,
--- a/xpcom/base/CycleCollectedJSContext.h
+++ b/xpcom/base/CycleCollectedJSContext.h
@@ -108,16 +108,17 @@ protected:
   size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
 private:
   MOZ_IS_CLASS_INIT
   void InitializeCommon();
 
   static JSObject* GetIncumbentGlobalCallback(JSContext* aCx);
   static bool EnqueuePromiseJobCallback(JSContext* aCx,
+                                        JS::HandleObject aPromise,
                                         JS::HandleObject aJob,
                                         JS::HandleObject aAllocationSite,
                                         JS::HandleObject aIncumbentGlobal,
                                         void* aData);
   static void PromiseRejectionTrackerCallback(JSContext* aCx,
                                               JS::HandleObject aPromise,
                                               JS::PromiseRejectionHandlingState state,
                                               void* aData);