Merge inbound to mozilla-central. a=merge
authorNoemi Erli <nerli@mozilla.com>
Sat, 10 Mar 2018 11:58:34 +0200
changeset 462527 0817a733d45a48800e68c9e2a5035fd17bfcdee2
parent 462526 8a0f43c3f5dd1fcb889d601a547e1d06b2964171 (current diff)
parent 462471 dd8202671cf14a9cb5e73b918edad64f386e9fb5 (diff)
child 462530 a0014a24f1e72bb1746cb9975068953d1f3451e2
child 462536 7e4e60cf4a341bffb21701051fae02a86ee48425
child 462538 c97c9a7a66da925f0193df9175674f383eb1db28
push id1683
push usersfraser@mozilla.com
push dateThu, 26 Apr 2018 16:43:40 +0000
treeherdermozilla-release@5af6cb21869d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone60.0a1
first release with
nightly linux32
0817a733d45a / 60.0a1 / 20180310100108 / files
nightly linux64
0817a733d45a / 60.0a1 / 20180310100108 / files
nightly mac
0817a733d45a / 60.0a1 / 20180310100108 / files
nightly win32
0817a733d45a / 60.0a1 / 20180310100108 / files
nightly win64
0817a733d45a / 60.0a1 / 20180310100108 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
image/test/reftest/generic/1443232-1.gif
image/test/reftest/generic/1443232-1.html
js/src/jsarray.cpp
js/src/jsarray.h
js/src/jsarrayinlines.h
js/src/jsbool.cpp
js/src/jsbool.h
js/src/jsboolinlines.h
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSessionSettings.java
modules/libpref/init/all.js
toolkit/mozapps/extensions/test/addons/test_install5/chrome.manifest
toolkit/mozapps/extensions/test/addons/test_install5/install.rdf
toolkit/mozapps/extensions/test/xpcshell/test_hasbinarycomponents.js
--- a/browser/components/enterprisepolicies/Policies.jsm
+++ b/browser/components/enterprisepolicies/Policies.jsm
@@ -102,16 +102,25 @@ var Policies = {
     onAllWindowsRestored(manager, param) {
       BookmarksPolicies.processBookmarks(param);
     }
   },
 
   "Cookies": {
     onBeforeUIStartup(manager, param) {
       addAllowDenyPermissions("cookie", param.Allow, param.Block);
+
+      if (param.Block) {
+        const hosts = param.Block.map(uri => uri.host).sort().join("\n");
+        runOncePerModification("clearCookiesForBlockedHosts", hosts, () => {
+          for (let blocked of param.Block) {
+            Services.cookies.removeCookiesWithOriginAttributes("{}", blocked.host);
+          }
+        });
+      }
     }
   },
 
   "CreateMasterPassword": {
     onBeforeUIStartup(manager, param) {
       if (!param) {
         manager.disallowFeature("createMasterPassword");
       }
--- a/browser/components/enterprisepolicies/tests/EnterprisePolicyTesting.jsm
+++ b/browser/components/enterprisepolicies/tests/EnterprisePolicyTesting.jsm
@@ -44,9 +44,22 @@ this.EnterprisePolicyTesting = {
     if (customSchema) {
       let schemaModule = ChromeUtils.import("resource:///modules/policies/schema.jsm", {});
       schemaModule.schema = customSchema;
     }
 
     Services.obs.notifyObservers(null, "EnterprisePolicies:Restart");
     return promise;
   },
+
+  resetRunOnceState: function resetRunOnceState() {
+    const runOnceBaseKeys = [
+      "browser.policies.runonce.",
+      "browser.policies.runOncePerModification."
+    ];
+    for (let base of runOnceBaseKeys) {
+      for (let key of Services.prefs.getChildList(base, {})) {
+        if (Services.prefs.prefHasUserValue(key))
+          Services.prefs.clearUserPref(key);
+      }
+    }
+  },
 };
--- a/browser/components/enterprisepolicies/tests/browser/browser.ini
+++ b/browser/components/enterprisepolicies/tests/browser/browser.ini
@@ -17,16 +17,17 @@ support-files =
 [browser_policies_validate_and_parse_API.js]
 [browser_policy_app_update.js]
 [browser_policy_block_about_addons.js]
 [browser_policy_block_about_config.js]
 [browser_policy_block_about_profiles.js]
 [browser_policy_block_about_support.js]
 [browser_policy_block_set_desktop_background.js]
 [browser_policy_bookmarks.js]
+[browser_policy_clear_blocked_cookies.js]
 [browser_policy_default_browser_check.js]
 [browser_policy_disable_formhistory.js]
 [browser_policy_disable_fxaccounts.js]
 [browser_policy_disable_fxscreenshots.js]
 [browser_policy_disable_masterpassword.js]
 [browser_policy_disable_pocket.js]
 [browser_policy_disable_privatebrowsing.js]
 [browser_policy_disable_shield.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_clear_blocked_cookies.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+add_task(async function setup() {
+  EnterprisePolicyTesting.resetRunOnceState();
+  const expiry = Date.now() + 24 * 60 * 60;
+  Services.cookies.add("example.com", "/", "secure", "true", true, false, false, expiry, {});
+  Services.cookies.add("example.com", "/", "insecure", "true", false, false, false, expiry, {});
+  Services.cookies.add("example.org", "/", "secure", "true", true, false, false, expiry, {});
+  Services.cookies.add("example.org", "/", "insecure", "true", false, false, false, expiry, {});
+  Services.cookies.add("example.net", "/", "secure", "true", true, false, false, expiry, {});
+  await setupPolicyEngineWithJson({
+    "policies": {
+      "Cookies": {
+        "Block": [
+          "http://example.com",
+          "https://example.org:8080"
+        ]
+      }
+    }
+  });
+});
+
+function retrieve_all_cookies(host) {
+  const values = [];
+  const cookies = Services.cookies.getCookiesFromHost(host, {});
+  while (cookies.hasMoreElements()) {
+    const cookie = cookies.getNext().QueryInterface(Ci.nsICookie);
+    values.push({
+      host: cookie.host,
+      name: cookie.name,
+      path: cookie.path
+    });
+  }
+  return values;
+}
+
+add_task(async function test_cookies_for_blocked_sites_cleared() {
+  const cookies = {
+    hostname: retrieve_all_cookies("example.com"),
+    origin: retrieve_all_cookies("example.org"),
+    keep: retrieve_all_cookies("example.net")
+  };
+  const expected = {
+    hostname: [],
+    origin: [],
+    keep: [
+      {host: "example.net",
+       name: "secure",
+       path: "/"}
+    ]
+  };
+  is(JSON.stringify(cookies), JSON.stringify(expected),
+     "All stored cookies for blocked origins should be cleared");
+});
+
+add_task(function teardown() {
+  for (let host of ["example.com", "example.org", "example.net"]) {
+    Services.cookies.removeCookiesWithOriginAttributes("{}", host);
+  }
+  EnterprisePolicyTesting.resetRunOnceState();
+});
new file mode 100644
--- /dev/null
+++ b/build/mozconfig.lld-link
@@ -0,0 +1,8 @@
+if test -d "$topsrcdir/clang/bin"; then
+    CLANG_DIR=`cd "$topsrcdir/clang/bin" ; pwd`
+    export PATH="${CLANG_DIR}:${PATH}"
+
+    mk_export_correct_style PATH
+fi
+
+export LINKER=lld-link
--- a/docshell/base/timeline/JavascriptTimelineMarker.h
+++ b/docshell/base/timeline/JavascriptTimelineMarker.h
@@ -3,16 +3,19 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_JavascriptTimelineMarker_h_
 #define mozilla_JavascriptTimelineMarker_h_
 
 #include "TimelineMarker.h"
+
+#include "mozilla/Maybe.h"
+
 #include "mozilla/dom/ProfileTimelineMarkerBinding.h"
 #include "mozilla/dom/RootedDictionary.h"
 #include "mozilla/dom/ToJSValue.h"
 
 namespace mozilla {
 
 class JavascriptTimelineMarker : public TimelineMarker
 {
@@ -58,17 +61,17 @@ public:
         JS::Rooted<JSString*> asyncCause(aCx, JS_NewUCStringCopyN(aCx, mAsyncCause.BeginReading(),
                                                                   mAsyncCause.Length()));
         if (!asyncCause) {
           JS_ClearPendingException(aCx);
           return;
         }
 
         if (JS::IsSavedFrame(asyncStack) &&
-            !JS::CopyAsyncStack(aCx, asyncStack, asyncCause, &parentFrame, 0)) {
+            !JS::CopyAsyncStack(aCx, asyncStack, asyncCause, &parentFrame, mozilla::Nothing())) {
           JS_ClearPendingException(aCx);
         } else {
           stackFrame.mAsyncParent = parentFrame;
         }
       }
 
       JS::Rooted<JS::Value> newStack(aCx);
       if (ToJSValue(aCx, stackFrame, &newStack)) {
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -56,18 +56,16 @@
 
 // DOM base includes
 #include "nsIDOMWindow.h"
 #include "nsPIDOMWindow.h"
 #include "nsIDOMConstructor.h"
 
 // DOM core includes
 #include "nsError.h"
-#include "nsIDOMXULButtonElement.h"
-#include "nsIDOMXULCheckboxElement.h"
 
 // Event related includes
 #include "nsIDOMEventTarget.h"
 
 // CSS related includes
 #include "nsMemory.h"
 
 // includes needed for the prototype chain interfaces
@@ -177,26 +175,16 @@ static nsDOMClassInfoData sClassInfoData
                                        nsMessageManagerSH<nsDOMGenericSH>,
                                        DOM_DEFAULT_SCRIPTABLE_FLAGS |
                                        XPC_SCRIPTABLE_WANT_ENUMERATE |
                                        XPC_SCRIPTABLE_IS_GLOBAL_OBJECT)
   NS_DEFINE_CHROME_ONLY_CLASSINFO_DATA(ChromeMessageBroadcaster, nsDOMGenericSH,
                                        DOM_DEFAULT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CHROME_ONLY_CLASSINFO_DATA(ChromeMessageSender, nsDOMGenericSH,
                                        DOM_DEFAULT_SCRIPTABLE_FLAGS)
-
-
-  NS_DEFINE_CHROME_XBL_CLASSINFO_DATA(XULControlElement, nsDOMGenericSH,
-                                      DOM_DEFAULT_SCRIPTABLE_FLAGS)
-  NS_DEFINE_CHROME_XBL_CLASSINFO_DATA(XULLabeledControlElement, nsDOMGenericSH,
-                                      DOM_DEFAULT_SCRIPTABLE_FLAGS)
-  NS_DEFINE_CHROME_XBL_CLASSINFO_DATA(XULButtonElement, nsDOMGenericSH,
-                                      DOM_DEFAULT_SCRIPTABLE_FLAGS)
-  NS_DEFINE_CHROME_XBL_CLASSINFO_DATA(XULCheckboxElement, nsDOMGenericSH,
-                                      DOM_DEFAULT_SCRIPTABLE_FLAGS)
 };
 
 nsIXPConnect *nsDOMClassInfo::sXPConnect = nullptr;
 bool nsDOMClassInfo::sIsInitialized = false;
 
 
 jsid nsDOMClassInfo::sConstructor_id     = JSID_VOID;
 jsid nsDOMClassInfo::sWrappedJSObject_id = JSID_VOID;
@@ -450,32 +438,16 @@ nsDOMClassInfo::Init()
 
   DOM_CLASSINFO_MAP_BEGIN_NO_CLASS_IF(ChromeMessageSender, nsISupports)
     DOM_CLASSINFO_MAP_ENTRY(nsIFrameScriptLoader)
     DOM_CLASSINFO_MAP_ENTRY(nsIProcessScriptLoader)
     DOM_CLASSINFO_MAP_ENTRY(nsIMessageListenerManager)
     DOM_CLASSINFO_MAP_ENTRY(nsIMessageSender)
   DOM_CLASSINFO_MAP_END
 
-  DOM_CLASSINFO_MAP_BEGIN_NO_CLASS_IF(XULControlElement, nsIDOMXULControlElement)
-    DOM_CLASSINFO_MAP_ENTRY(nsIDOMXULControlElement)
-  DOM_CLASSINFO_MAP_END
-
-  DOM_CLASSINFO_MAP_BEGIN_NO_CLASS_IF(XULLabeledControlElement, nsIDOMXULLabeledControlElement)
-    DOM_CLASSINFO_MAP_ENTRY(nsIDOMXULLabeledControlElement)
-  DOM_CLASSINFO_MAP_END
-
-  DOM_CLASSINFO_MAP_BEGIN_NO_CLASS_IF(XULButtonElement, nsIDOMXULButtonElement)
-    DOM_CLASSINFO_MAP_ENTRY(nsIDOMXULButtonElement)
-  DOM_CLASSINFO_MAP_END
-
-  DOM_CLASSINFO_MAP_BEGIN_NO_CLASS_IF(XULCheckboxElement, nsIDOMXULCheckboxElement)
-    DOM_CLASSINFO_MAP_ENTRY(nsIDOMXULCheckboxElement)
-  DOM_CLASSINFO_MAP_END
-
   static_assert(MOZ_ARRAY_LENGTH(sClassInfoData) == eDOMClassInfoIDCount,
                 "The number of items in sClassInfoData doesn't match the "
                 "number of nsIDOMClassInfo ID's, this is bad! Fix it!");
 
 #ifdef DEBUG
   for (size_t i = 0; i < eDOMClassInfoIDCount; i++) {
     if (!sClassInfoData[i].mConstructorFptr ||
         sClassInfoData[i].mDebugID != i) {
--- a/dom/base/nsDOMClassInfoID.h
+++ b/dom/base/nsDOMClassInfoID.h
@@ -19,21 +19,16 @@ enum nsDOMClassInfoID
   eDOMClassInfo_DOMPrototype_id,
   eDOMClassInfo_DOMConstructor_id,
 
   eDOMClassInfo_ContentFrameMessageManager_id,
   eDOMClassInfo_ContentProcessMessageManager_id,
   eDOMClassInfo_ChromeMessageBroadcaster_id,
   eDOMClassInfo_ChromeMessageSender_id,
 
-  eDOMClassInfo_XULControlElement_id,
-  eDOMClassInfo_XULLabeledControlElement_id,
-  eDOMClassInfo_XULButtonElement_id,
-  eDOMClassInfo_XULCheckboxElement_id,
-
   // This one better be the last one in this list
   eDOMClassInfoIDCount
 };
 
 /**
  * nsIClassInfo helper macros
  */
 
--- a/dom/bindings/BindingDeclarations.h
+++ b/dom/bindings/BindingDeclarations.h
@@ -538,12 +538,23 @@ enum class CallerType : uint32_t {
 // A class that can be passed (by value or const reference) to indicate that the
 // caller is always a system caller.  This can be used as the type of an
 // argument to force only system callers to call a function.
 class SystemCallerGuarantee {
 public:
   operator CallerType() const { return CallerType::System; }
 };
 
+class ProtoAndIfaceCache;
+typedef void (*CreateInterfaceObjectsMethod)(JSContext* aCx,
+                                             JS::Handle<JSObject*> aGlobal,
+                                             ProtoAndIfaceCache& aCache,
+                                             bool aDefineOnGlobal);
+JS::Handle<JSObject*> GetPerInterfaceObjectHandle(
+  JSContext* aCx,
+  size_t aSlotId,
+  CreateInterfaceObjectsMethod aCreator,
+  bool aDefineOnGlobal);
+
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_BindingDeclarations_h__
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -3800,10 +3800,46 @@ UnprivilegedJunkScopeOrWorkerGlobal()
   if (NS_IsMainThread()) {
     return xpc::UnprivilegedJunkScope();
   }
 
   return GetCurrentThreadWorkerGlobal();
 }
 } // namespace binding_detail
 
+JS::Handle<JSObject*>
+GetPerInterfaceObjectHandle(JSContext* aCx,
+                            size_t aSlotId,
+                            CreateInterfaceObjectsMethod aCreator,
+                            bool aDefineOnGlobal)
+{
+  /* Make sure our global is sane.  Hopefully we can remove this sometime */
+  JSObject* global = JS::CurrentGlobalOrNull(aCx);
+  if (!(js::GetObjectClass(global)->flags & JSCLASS_DOM_GLOBAL)) {
+    return nullptr;
+  }
+
+  /* Check to see whether the interface objects are already installed */
+  ProtoAndIfaceCache& protoAndIfaceCache = *GetProtoAndIfaceCache(global);
+  if (!protoAndIfaceCache.HasEntryInSlot(aSlotId)) {
+    JS::Rooted<JSObject*> rootedGlobal(aCx, global);
+    aCreator(aCx, rootedGlobal, protoAndIfaceCache, aDefineOnGlobal);
+  }
+
+  /*
+   * The object might _still_ be null, but that's OK.
+   *
+   * Calling fromMarkedLocation() is safe because protoAndIfaceCache is
+   * traced by TraceProtoAndIfaceCache() and its contents are never
+   * changed after they have been set.
+   *
+   * Calling address() avoids the read barrier that does gray unmarking, but
+   * it's not possible for the object to be gray here.
+   */
+
+  const JS::Heap<JSObject*>& entrySlot =
+    protoAndIfaceCache.EntrySlotMustExist(aSlotId);
+  MOZ_ASSERT(JS::ObjectIsNotGray(entrySlot));
+  return JS::Handle<JSObject*>::fromMarkedLocation(entrySlot.address());
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -3395,107 +3395,76 @@ class CGCreateInterfaceObjectsMethod(CGA
 
         return CGList(
             [getParentProto, getConstructorProto, initIds,
              prefCache, CGGeneric(call), defineAliases, unforgeableHolderSetup,
              makeProtoPrototypeImmutable],
             "\n").define()
 
 
-class CGGetPerInterfaceObject(CGAbstractMethod):
-    """
-    A method for getting a per-interface object (a prototype object or interface
-    constructor object).
-    """
-    def __init__(self, descriptor, name, idPrefix="", extraArgs=[]):
-        args = [Argument('JSContext*', 'aCx')] + extraArgs
-        CGAbstractMethod.__init__(self, descriptor, name,
-                                  'JS::Handle<JSObject*>', args)
-        self.id = idPrefix + "id::" + self.descriptor.name
-
-    def definition_body(self):
-        return fill(
-            """
-            /* Make sure our global is sane.  Hopefully we can remove this sometime */
-            JSObject* global = JS::CurrentGlobalOrNull(aCx);
-            if (!(js::GetObjectClass(global)->flags & JSCLASS_DOM_GLOBAL)) {
-              return nullptr;
-            }
-
-            /* Check to see whether the interface objects are already installed */
-            ProtoAndIfaceCache& protoAndIfaceCache = *GetProtoAndIfaceCache(global);
-            if (!protoAndIfaceCache.HasEntryInSlot(${id})) {
-              JS::Rooted<JSObject*> rootedGlobal(aCx, global);
-              CreateInterfaceObjects(aCx, rootedGlobal, protoAndIfaceCache, aDefineOnGlobal);
-            }
-
-            /*
-             * The object might _still_ be null, but that's OK.
-             *
-             * Calling fromMarkedLocation() is safe because protoAndIfaceCache is
-             * traced by TraceProtoAndIfaceCache() and its contents are never
-             * changed after they have been set.
-             *
-             * Calling address() avoids the read read barrier that does gray
-             * unmarking, but it's not possible for the object to be gray here.
-             */
-
-            const JS::Heap<JSObject*>& entrySlot = protoAndIfaceCache.EntrySlotMustExist(${id});
-            MOZ_ASSERT(JS::ObjectIsNotGray(entrySlot));
-            return JS::Handle<JSObject*>::fromMarkedLocation(entrySlot.address());
-            """,
-            id=self.id)
-
-
-class CGGetProtoObjectHandleMethod(CGGetPerInterfaceObject):
+class CGGetProtoObjectHandleMethod(CGAbstractMethod):
     """
     A method for getting the interface prototype object.
     """
     def __init__(self, descriptor):
-        CGGetPerInterfaceObject.__init__(self, descriptor, "GetProtoObjectHandle",
-                                         "prototypes::")
+        CGAbstractMethod.__init__(
+            self, descriptor, "GetProtoObjectHandle",
+            'JS::Handle<JSObject*>',
+            [Argument('JSContext*', 'aCx')],
+            inline=True)
 
     def definition_body(self):
-        return dedent("""
+        return fill(
+            """
             /* Get the interface prototype object for this class.  This will create the
                object as needed. */
-            bool aDefineOnGlobal = true;
-
-            """) + CGGetPerInterfaceObject.definition_body(self)
+            return GetPerInterfaceObjectHandle(aCx, prototypes::id::${name},
+                                               &CreateInterfaceObjects,
+                                               /* aDefineOnGlobal = */ true);
+
+            """,
+            name=self.descriptor.name)
 
 
 class CGGetProtoObjectMethod(CGAbstractMethod):
     """
     A method for getting the interface prototype object.
     """
     def __init__(self, descriptor):
         CGAbstractMethod.__init__(
             self, descriptor, "GetProtoObject", "JSObject*",
             [Argument('JSContext*', 'aCx')])
 
     def definition_body(self):
         return "return GetProtoObjectHandle(aCx);\n"
 
 
-class CGGetConstructorObjectHandleMethod(CGGetPerInterfaceObject):
+class CGGetConstructorObjectHandleMethod(CGAbstractMethod):
     """
     A method for getting the interface constructor object.
     """
     def __init__(self, descriptor):
-        CGGetPerInterfaceObject.__init__(
+        CGAbstractMethod.__init__(
             self, descriptor, "GetConstructorObjectHandle",
-            "constructors::",
-            extraArgs=[Argument("bool", "aDefineOnGlobal", "true")])
+            'JS::Handle<JSObject*>',
+            [Argument('JSContext*', 'aCx'),
+             Argument('bool', 'aDefineOnGlobal', 'true')],
+            inline=True)
 
     def definition_body(self):
-        return dedent("""
+        return fill(
+            """
             /* Get the interface object for this class.  This will create the object as
                needed. */
 
-            """) + CGGetPerInterfaceObject.definition_body(self)
+            return GetPerInterfaceObjectHandle(aCx, constructors::id::${name},
+                                               &CreateInterfaceObjects,
+                                               aDefineOnGlobal);
+            """,
+            name=self.descriptor.name)
 
 
 class CGGetConstructorObjectMethod(CGAbstractMethod):
     """
     A method for getting the interface constructor object.
     """
     def __init__(self, descriptor):
         CGAbstractMethod.__init__(
@@ -14381,16 +14350,17 @@ class CGBindingRoot(CGThing):
         hasNonEmptyDictionaries = any(
             len(dict.members) > 0 for dict in dictionaries)
         callbacks = config.getCallbacks(webIDLFile)
         callbackDescriptors = config.getDescriptors(webIDLFile=webIDLFile,
                                                     isCallback=True)
         jsImplemented = config.getDescriptors(webIDLFile=webIDLFile,
                                               isJSImplemented=True)
         bindingDeclareHeaders["nsWeakReference.h"] = jsImplemented
+        bindingDeclareHeaders["mozilla/dom/PrototypeList.h"] = descriptors
         bindingHeaders["nsIGlobalObject.h"] = jsImplemented
         bindingHeaders["AtomList.h"] = hasNonEmptyDictionaries or jsImplemented or callbackDescriptors
 
         def descriptorClearsPropsInSlots(descriptor):
             if not descriptor.wrapperCache:
                 return False
             return any(m.isAttr() and m.getExtendedAttribute("StoreInSlot")
                        for m in descriptor.interface.members)
@@ -17327,16 +17297,17 @@ class GlobalGenRoots():
                                       "Too many prototypes!"))
 
         # Wrap all of that in our namespaces.
         idEnum = CGNamespace.build(['mozilla', 'dom', 'prototypes'],
                                    CGWrapper(idEnum, pre='\n'))
         idEnum = CGWrapper(idEnum, post='\n')
 
         curr = CGList([CGGeneric(define="#include <stdint.h>\n\n"),
+                       CGGeneric(declare='#include "jsfriendapi.h"\n\n'),
                        idEnum])
 
         # Let things know the maximum length of the prototype chain.
         maxMacroName = "MAX_PROTOTYPE_CHAIN_LENGTH"
         maxMacro = CGGeneric(declare="#define " + maxMacroName + " " + str(config.maxProtoChainLength))
         curr.append(CGWrapper(maxMacro, post='\n\n'))
         curr.append(fieldSizeAssert(maxMacroName, "depth",
                                     "Some inheritance chain is too long!"))
--- a/dom/canvas/WebGL2ContextUniforms.cpp
+++ b/dom/canvas/WebGL2ContextUniforms.cpp
@@ -167,16 +167,21 @@ WebGL2Context::GetActiveUniforms(JSConte
         return;
 
     if (!ValidateUniformEnum(this, pname, funcName))
         return;
 
     if (!ValidateObject("getActiveUniforms: program", program))
         return;
 
+    if (!program.IsLinked()) {
+        ErrorInvalidOperation("%s: `program` must be linked.", funcName);
+        return;
+    }
+
     const auto& numActiveUniforms = program.LinkInfo()->uniforms.size();
     for (const auto& curIndex : uniformIndices) {
         if (curIndex >= numActiveUniforms) {
             ErrorInvalidValue("%s: Too-large active uniform index queried.", funcName);
             return;
         }
     }
 
--- a/dom/credentialmanagement/CredentialsContainer.cpp
+++ b/dom/credentialmanagement/CredentialsContainer.cpp
@@ -3,16 +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/. */
 
 #include "mozilla/dom/CredentialsContainer.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/WebAuthnManager.h"
 #include "nsContentUtils.h"
+#include "nsFocusManager.h"
+#include "nsIDocShell.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CredentialsContainer, mParent, mManager)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(CredentialsContainer)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(CredentialsContainer)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CredentialsContainer)
@@ -35,17 +37,60 @@ CreateAndReject(nsPIDOMWindowInner* aPar
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   promise->MaybeReject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
   return promise.forget();
 }
 
-bool
+static bool
+IsInActiveTab(nsPIDOMWindowInner* aParent)
+{
+  // Returns whether aParent is an inner window somewhere in the active tab.
+  // The active tab is the selected (i.e. visible) tab in the focused window.
+  MOZ_ASSERT(aParent);
+
+  nsCOMPtr<nsIDocument> doc(aParent->GetExtantDoc());
+  if (NS_WARN_IF(!doc)) {
+    return false;
+  }
+
+  nsCOMPtr<nsIDocShell> docShell = doc->GetDocShell();
+  if (!docShell) {
+    return false;
+  }
+
+  bool isActive = false;
+  docShell->GetIsActive(&isActive);
+  if (!isActive) {
+    return false;
+  }
+
+  nsCOMPtr<nsIDocShellTreeItem> rootItem;
+  docShell->GetRootTreeItem(getter_AddRefs(rootItem));
+  if (!rootItem) {
+    return false;
+  }
+  nsCOMPtr<nsPIDOMWindowOuter> rootWin = rootItem->GetWindow();
+  if (!rootWin) {
+    return false;
+  }
+
+  nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+  if (!fm) {
+    return false;
+  }
+
+  nsCOMPtr<mozIDOMWindowProxy> activeWindow;
+  fm->GetActiveWindow(getter_AddRefs(activeWindow));
+  return activeWindow == rootWin;
+}
+
+static bool
 IsSameOriginWithAncestors(nsPIDOMWindowInner* aParent)
 {
   // This method returns true if aParent is either not in a frame / iframe, or
   // is in a frame or iframe and all ancestors for aParent are the same origin.
   // This is useful for Credential Management because we need to prohibit
   // iframes, but not break mochitests (which use iframes to embed the tests).
   MOZ_ASSERT(aParent);
 
@@ -101,49 +146,43 @@ CredentialsContainer::WrapObject(JSConte
 {
   return CredentialsContainerBinding::Wrap(aCx, this, aGivenProto);
 }
 
 already_AddRefed<Promise>
 CredentialsContainer::Get(const CredentialRequestOptions& aOptions,
                           ErrorResult& aRv)
 {
-  if (!IsSameOriginWithAncestors(mParent)) {
+  if (!IsSameOriginWithAncestors(mParent) || !IsInActiveTab(mParent)) {
     return CreateAndReject(mParent, aRv);
   }
 
-  // TODO: Check that we're an active document, too. See bug 1409202.
-
   EnsureWebAuthnManager();
   return mManager->GetAssertion(aOptions.mPublicKey, aOptions.mSignal);
 }
 
 already_AddRefed<Promise>
 CredentialsContainer::Create(const CredentialCreationOptions& aOptions,
                              ErrorResult& aRv)
 {
-  if (!IsSameOriginWithAncestors(mParent)) {
+  if (!IsSameOriginWithAncestors(mParent) || !IsInActiveTab(mParent)) {
     return CreateAndReject(mParent, aRv);
   }
 
-  // TODO: Check that we're an active document, too. See bug 1409202.
-
   EnsureWebAuthnManager();
   return mManager->MakeCredential(aOptions.mPublicKey, aOptions.mSignal);
 }
 
 already_AddRefed<Promise>
 CredentialsContainer::Store(const Credential& aCredential, ErrorResult& aRv)
 {
-  if (!IsSameOriginWithAncestors(mParent)) {
+  if (!IsSameOriginWithAncestors(mParent) || !IsInActiveTab(mParent)) {
     return CreateAndReject(mParent, aRv);
   }
 
-  // TODO: Check that we're an active document, too. See bug 1409202.
-
   EnsureWebAuthnManager();
   return mManager->Store(aCredential);
 }
 
 already_AddRefed<Promise>
 CredentialsContainer::PreventSilentAccess(ErrorResult& aRv)
 {
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mParent);
--- a/dom/credentialmanagement/moz.build
+++ b/dom/credentialmanagement/moz.build
@@ -17,8 +17,9 @@ UNIFIED_SOURCES += [
     'CredentialsContainer.cpp',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 
 MOCHITEST_MANIFESTS += ['tests/mochitest/mochitest.ini']
+BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
new file mode 100644
--- /dev/null
+++ b/dom/credentialmanagement/tests/browser/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+  "extends": [
+    "plugin:mozilla/browser-test"
+  ]
+};
new file mode 100644
--- /dev/null
+++ b/dom/credentialmanagement/tests/browser/browser.ini
@@ -0,0 +1,1 @@
+[browser_active_document.js]
new file mode 100644
--- /dev/null
+++ b/dom/credentialmanagement/tests/browser/browser_active_document.js
@@ -0,0 +1,133 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const TEST_URL = "https://example.com/";
+
+function arrivingHereIsBad(aResult) {
+  ok(false, "Bad result! Received a: " + aResult);
+}
+
+function expectNotAllowedError(aResult) {
+  let expected = "NotAllowedError";
+  is(aResult.slice(0, expected.length), expected, `Expecting a ${expected}`);
+}
+
+function promiseMakeCredential(tab) {
+  return ContentTask.spawn(tab.linkedBrowser, null, async function() {
+    const cose_alg_ECDSA_w_SHA256 = -7;
+
+    let publicKey = {
+      rp: {id: content.document.domain, name: "none", icon: "none"},
+      user: {id: new Uint8Array(), name: "none", icon: "none", displayName: "none"},
+      challenge: content.crypto.getRandomValues(new Uint8Array(16)),
+      timeout: 5000, // the minimum timeout is actually 15 seconds
+      pubKeyCredParams: [{type: "public-key", alg: cose_alg_ECDSA_w_SHA256}],
+    };
+
+    return content.navigator.credentials.create({publicKey});
+  });
+}
+
+function promiseGetAssertion(tab) {
+  return ContentTask.spawn(tab.linkedBrowser, null, async function() {
+    let newCredential = {
+      type: "public-key",
+      id: content.crypto.getRandomValues(new Uint8Array(16)),
+      transports: ["usb"],
+    };
+
+    let publicKey = {
+      challenge: content.crypto.getRandomValues(new Uint8Array(16)),
+      timeout: 5000, // the minimum timeout is actually 15 seconds
+      rpId: content.document.domain,
+      allowCredentials: [newCredential]
+    };
+
+    return content.navigator.credentials.get({publicKey});
+  });
+}
+
+add_task(async function test_setup() {
+  await SpecialPowers.pushPrefEnv({
+    "set": [
+      ["security.webauth.webauthn", true],
+      ["security.webauth.webauthn_enable_softtoken", true],
+      ["security.webauth.webauthn_enable_usbtoken", false]
+    ]
+  });
+});
+
+add_task(async function test_background_tab() {
+  // Open two tabs, the last one will selected.
+  let tab_bg = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+  let tab_fg = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+  // Requests from background tabs must fail.
+  await promiseMakeCredential(tab_bg)
+    .then(arrivingHereIsBad)
+    .catch(expectNotAllowedError);
+
+  // Requests from background tabs must fail.
+  await promiseGetAssertion(tab_bg)
+    .then(arrivingHereIsBad)
+    .catch(expectNotAllowedError);
+
+  // Close tabs.
+  await BrowserTestUtils.removeTab(tab_bg);
+  await BrowserTestUtils.removeTab(tab_fg);
+});
+
+add_task(async function test_background_window() {
+  // Open a tab, then a new window.
+  let tab_bg = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+  let win = await BrowserTestUtils.openNewBrowserWindow();
+
+  // Requests from selected tabs not in the active window must fail.
+  await promiseMakeCredential(tab_bg)
+    .then(arrivingHereIsBad)
+    .catch(expectNotAllowedError);
+
+  // Requests from selected tabs not in the active window must fail.
+  await promiseGetAssertion(tab_bg)
+    .then(arrivingHereIsBad)
+    .catch(expectNotAllowedError);
+
+  // Close tab and window.
+  await BrowserTestUtils.closeWindow(win);
+  await BrowserTestUtils.removeTab(tab_bg);
+});
+
+add_task(async function test_minimized() {
+  let env = Cc["@mozilla.org/process/environment;1"]
+              .getService(Ci.nsIEnvironment);
+  // Minimizing windows doesn't supported in headless mode.
+  if (env.get("MOZ_HEADLESS")) {
+    return;
+  }
+
+  // Open a window with a tab.
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+  // Minimize the window.
+  window.minimize();
+  await TestUtils.waitForCondition(() => !tab.linkedBrowser.docShellIsActive);
+
+  // Requests from minimized windows must fail.
+  await promiseMakeCredential(tab)
+    .then(arrivingHereIsBad)
+    .catch(expectNotAllowedError);
+
+  // Requests from minimized windows must fail.
+  await promiseGetAssertion(tab)
+    .then(arrivingHereIsBad)
+    .catch(expectNotAllowedError);
+
+  // Restore the window.
+  await new Promise(resolve => SimpleTest.waitForFocus(resolve, window));
+
+  // Close tab.
+  await BrowserTestUtils.removeTab(tab);
+});
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -2002,18 +2002,18 @@ ContentParent::LaunchSubprocess(ProcessP
   nsAutoCStringN<1024> intPrefs;
   nsAutoCStringN<1024> stringPrefs;
 
   size_t prefsLen;
   ContentPrefs::GetEarlyPrefs(&prefsLen);
 
   for (unsigned int i = 0; i < prefsLen; i++) {
     const char* prefName = ContentPrefs::GetEarlyPref(i);
-    MOZ_ASSERT_IF(i > 0,
-                  strcmp(prefName, ContentPrefs::GetEarlyPref(i - 1)) > 0);
+    MOZ_ASSERT(i == 0 || strcmp(prefName, ContentPrefs::GetEarlyPref(i - 1)) > 0,
+               "Content process preferences should be sorted alphabetically.");
 
     if (!Preferences::MustSendToContentProcesses(prefName)) {
       continue;
     }
 
     switch (Preferences::GetType(prefName)) {
     case nsIPrefBranch::PREF_INT:
       intPrefs.Append(nsPrintfCString("%u:%d|", i, Preferences::GetInt(prefName)));
@@ -2913,16 +2913,19 @@ ContentParent::Observe(nsISupports* aSub
     Unused << SendUnlinkGhosts();
   }
   else if (!strcmp(aTopic, "last-pb-context-exited")) {
     Unused << SendLastPrivateDocShellDestroyed();
   }
 #ifdef ACCESSIBILITY
   else if (aData && !strcmp(aTopic, "a11y-init-or-shutdown")) {
     if (*aData == '1') {
+      // Shut down the preallocated process manager to avoid recycled
+      // content processes.
+      PreallocatedProcessManager::Disable();
       // Make sure accessibility is running in content process when
       // accessibility gets initiated in chrome process.
 #if defined(XP_WIN)
       // Don't init content a11y if we detect an incompat version of JAWS in use.
       if (!mozilla::a11y::Compatibility::IsOldJAWS()) {
         Unused << SendActivateA11y(::GetCurrentThreadId(),
                                    a11y::AccessibleWrap::GetContentProcessIdFor(ChildID()));
       }
--- a/dom/ipc/ContentPrefs.cpp
+++ b/dom/ipc/ContentPrefs.cpp
@@ -124,16 +124,17 @@ const char* mozilla::dom::ContentPrefs::
   "javascript.options.ion.offthread_compilation",
   "javascript.options.ion.threshold",
   "javascript.options.ion.unsafe_eager_compilation",
   "javascript.options.jit.full_debug_checks",
   "javascript.options.native_regexp",
   "javascript.options.parallel_parsing",
   "javascript.options.shared_memory",
   "javascript.options.spectre.index_masking",
+  "javascript.options.spectre.jit_to_C++_calls",
   "javascript.options.spectre.object_mitigations.barriers",
   "javascript.options.spectre.string_mitigations",
   "javascript.options.spectre.value_masking",
   "javascript.options.streams",
   "javascript.options.strict",
   "javascript.options.strict.debug",
   "javascript.options.throw_on_asmjs_validation_failure",
   "javascript.options.throw_on_debuggee_would_run",
--- a/dom/ipc/PreallocatedProcessManager.cpp
+++ b/dom/ipc/PreallocatedProcessManager.cpp
@@ -38,16 +38,17 @@ public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
   // See comments on PreallocatedProcessManager for these methods.
   void AddBlocker(ContentParent* aParent);
   void RemoveBlocker(ContentParent* aParent);
   already_AddRefed<ContentParent> Take();
   bool Provide(ContentParent* aParent);
+  void Disable();
 
 private:
   static mozilla::StaticRefPtr<PreallocatedProcessManagerImpl> sSingleton;
 
   PreallocatedProcessManagerImpl();
   ~PreallocatedProcessManagerImpl() {}
   DISALLOW_EVIL_CONSTRUCTORS(PreallocatedProcessManagerImpl);
 
@@ -55,17 +56,16 @@ private:
 
   bool CanAllocate();
   void AllocateAfterDelay();
   void AllocateOnIdle();
   void AllocateNow();
 
   void RereadPrefs();
   void Enable();
-  void Disable();
   void CloseProcess();
 
   void ObserveProcessShutdown(nsISupports* aSubject);
 
   bool mEnabled;
   bool mShutdown;
   RefPtr<ContentParent> mPreallocatedProcess;
   nsTHashtable<nsUint64HashKey> mBlockers;
@@ -330,9 +330,15 @@ PreallocatedProcessManager::Take()
 }
 
 /* static */ bool
 PreallocatedProcessManager::Provide(ContentParent* aParent)
 {
   return GetPPMImpl()->Provide(aParent);
 }
 
+/* static */ void
+PreallocatedProcessManager::Disable()
+{
+  GetPPMImpl()->Disable();
+}
+
 } // namespace mozilla
--- a/dom/ipc/PreallocatedProcessManager.h
+++ b/dom/ipc/PreallocatedProcessManager.h
@@ -50,18 +50,30 @@ public:
    * null.
    *
    * After you Take() the preallocated process, you need to call one of the
    * Allocate* functions (or change the dom.ipc.processPrelaunch pref from
    * false to true) before we'll create a new process.
    */
   static already_AddRefed<ContentParent> Take();
 
+  /**
+   * ContentParent entry point for recycling existing content processes that are
+   * no longer in use. This class currently only caches one instance. It is safe
+   * to call this repeatedly with the same process.
+   *
+   * @returns boolean indicating if aParent is cached for reuse.
+   */
   static bool Provide(ContentParent* aParent);
 
+  /**
+   * Disables this service and discards any cached content processes.
+   */
+  static void Disable();
+
 private:
   PreallocatedProcessManager();
   DISALLOW_EVIL_CONSTRUCTORS(PreallocatedProcessManager);
 };
 
 } // namespace mozilla
 
 #endif // defined mozilla_PreallocatedProcessManager_h
--- a/dom/tests/mochitest/general/test_interfaces.js
+++ b/dom/tests/mochitest/general/test_interfaces.js
@@ -1260,30 +1260,22 @@ var interfaceNamesInGlobalScope =
     {name: "XPathEvaluator", insecureContext: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "XPathExpression", insecureContext: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "XPathResult", insecureContext: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "XSLTProcessor", insecureContext: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "XULButtonElement", insecureContext: true, xbl: true},
-// IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "XULCheckboxElement", insecureContext: true, xbl: true},
-// IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "XULCommandEvent", insecureContext: true, xbl: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "XULControlElement", insecureContext: true, xbl: true},
-// IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "XULDocument", insecureContext: true, xbl: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "XULElement", insecureContext: true, xbl: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "XULLabeledControlElement", insecureContext: true, xbl: true},
-// IMPORTANT: Do not change this list without review from a DOM peer!
   ];
 // IMPORTANT: Do not change the list above without review from a DOM peer!
 
 function createInterfaceMap(isXBLScope) {
   var interfaceMap = {};
 
   function addInterfaces(interfaces)
   {
--- a/dom/webauthn/WebAuthnManagerBase.cpp
+++ b/dom/webauthn/WebAuthnManagerBase.cpp
@@ -2,20 +2,23 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/WebAuthnManagerBase.h"
 #include "mozilla/dom/WebAuthnTransactionChild.h"
 #include "mozilla/dom/Event.h"
+#include "nsGlobalWindowInner.h"
+#include "nsPIWindowRoot.h"
 
 namespace mozilla {
 namespace dom {
 
+NS_NAMED_LITERAL_STRING(kDeactivateEvent, "deactivate");
 NS_NAMED_LITERAL_STRING(kVisibilityChange, "visibilitychange");
 
 WebAuthnManagerBase::WebAuthnManagerBase(nsPIDOMWindowInner* aParent)
   : mParent(aParent)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aParent);
 }
@@ -68,61 +71,86 @@ WebAuthnManagerBase::ActorDestroyed()
  * Event Handling
  **********************************************************************/
 
 void
 WebAuthnManagerBase::ListenForVisibilityEvents()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  nsCOMPtr<nsIDocument> doc = mParent->GetExtantDoc();
-  if (NS_WARN_IF(!doc)) {
+  nsCOMPtr<nsPIDOMWindowOuter> outer = mParent->GetOuterWindow();
+  if (NS_WARN_IF(!outer)) {
     return;
   }
 
-  nsresult rv = doc->AddSystemEventListener(kVisibilityChange, this,
-                                            /* use capture */ true,
-                                            /* wants untrusted */ false);
+  nsCOMPtr<EventTarget> windowRoot = outer->GetTopWindowRoot();
+  if (NS_WARN_IF(!windowRoot)) {
+    return;
+  }
+
+  nsresult rv = windowRoot->AddEventListener(kDeactivateEvent, this,
+                                             /* use capture */ true,
+                                             /* wants untrusted */ false);
+  Unused << NS_WARN_IF(NS_FAILED(rv));
+
+  rv = windowRoot->AddEventListener(kVisibilityChange, this,
+                                    /* use capture */ true,
+                                    /* wants untrusted */ false);
   Unused << NS_WARN_IF(NS_FAILED(rv));
 }
 
 void
 WebAuthnManagerBase::StopListeningForVisibilityEvents()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  nsCOMPtr<nsIDocument> doc = mParent->GetExtantDoc();
-  if (NS_WARN_IF(!doc)) {
+  nsCOMPtr<nsPIDOMWindowOuter> outer = mParent->GetOuterWindow();
+  if (NS_WARN_IF(!outer)) {
     return;
   }
 
-  nsresult rv = doc->RemoveSystemEventListener(kVisibilityChange, this,
-                                               /* use capture */ true);
+  nsCOMPtr<EventTarget> windowRoot = outer->GetTopWindowRoot();
+  if (NS_WARN_IF(!windowRoot)) {
+    return;
+  }
+
+  nsresult rv = windowRoot->RemoveEventListener(kDeactivateEvent, this,
+                                                /* use capture */ true);
+  Unused << NS_WARN_IF(NS_FAILED(rv));
+
+  rv = windowRoot->RemoveEventListener(kVisibilityChange, this,
+                                       /* use capture */ true);
   Unused << NS_WARN_IF(NS_FAILED(rv));
 }
 
 NS_IMETHODIMP
 WebAuthnManagerBase::HandleEvent(nsIDOMEvent* aEvent)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aEvent);
 
   nsAutoString type;
   aEvent->GetType(type);
-  if (!type.Equals(kVisibilityChange)) {
+  if (!type.Equals(kDeactivateEvent) && !type.Equals(kVisibilityChange)) {
     return NS_ERROR_FAILURE;
   }
 
-  nsCOMPtr<nsIDocument> doc =
-    do_QueryInterface(aEvent->InternalDOMEvent()->GetTarget());
-  if (NS_WARN_IF(!doc)) {
-    return NS_ERROR_FAILURE;
+  // The "deactivate" event on the root window has no
+  // "current inner window" and thus GetTarget() is always null.
+  if (type.Equals(kVisibilityChange)) {
+    nsCOMPtr<nsIDocument> doc =
+      do_QueryInterface(aEvent->InternalDOMEvent()->GetTarget());
+    if (NS_WARN_IF(!doc) || !doc->Hidden()) {
+      return NS_OK;
+    }
+
+    nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(doc->GetInnerWindow());
+    if (NS_WARN_IF(!win) || !win->IsTopInnerWindow()) {
+      return NS_OK;
+    }
   }
 
-  if (doc->Hidden()) {
-    CancelTransaction(NS_ERROR_ABORT);
-  }
-
+  CancelTransaction(NS_ERROR_DOM_ABORT_ERR);
   return NS_OK;
 }
 
 }
 }
--- a/dom/webauthn/tests/browser/browser_abort_visibility.js
+++ b/dom/webauthn/tests/browser/browser_abort_visibility.js
@@ -69,25 +69,29 @@ function startGetAssertionRequest(tab) {
     }).catch(() => {
       status.value = "aborted";
     });
 
     status.value = "pending";
   });
 }
 
+add_task(async function test_setup() {
+  await SpecialPowers.pushPrefEnv({
+    "set": [
+      ["security.webauth.webauthn", true],
+      ["security.webauth.webauthn_enable_softtoken", false],
+      ["security.webauth.webauthn_enable_usbtoken", true]
+    ]
+  });
+});
 
 // Test that MakeCredential() and GetAssertion() requests
 // are aborted when the current tab loses its focus.
-add_task(async function test_abort() {
-  // Enable the USB token.
-  Services.prefs.setBoolPref("security.webauth.webauthn", true);
-  Services.prefs.setBoolPref("security.webauth.webauthn_enable_softtoken", false);
-  Services.prefs.setBoolPref("security.webauth.webauthn_enable_usbtoken", true);
-
+add_task(async function test_switch_tab() {
   // Create a new tab for the MakeCredential() request.
   let tab_create = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
 
   // Start the request.
   await startMakeCredentialRequest(tab_create);
   await assertStatus(tab_create, "pending");
 
   // Open another tab and switch to it. The first will lose focus.
@@ -100,14 +104,95 @@ add_task(async function test_abort() {
 
   // Switch back to the first tab, the get() request is aborted.
   await BrowserTestUtils.switchTab(gBrowser, tab_create);
   await waitForStatus(tab_get, "aborted");
 
   // Close tabs.
   await BrowserTestUtils.removeTab(tab_create);
   await BrowserTestUtils.removeTab(tab_get);
+});
 
-  // Cleanup.
-  Services.prefs.clearUserPref("security.webauth.webauthn");
-  Services.prefs.clearUserPref("security.webauth.webauthn_enable_softtoken");
-  Services.prefs.clearUserPref("security.webauth.webauthn_enable_usbtoken");
+add_task(async function test_new_window_make() {
+  // Create a new tab for the MakeCredential() request.
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+  // Start a MakeCredential request.
+  await startMakeCredentialRequest(tab);
+  await assertStatus(tab, "pending");
+
+  // Open a new window. The tab will lose focus.
+  let win = await BrowserTestUtils.openNewBrowserWindow();
+  await waitForStatus(tab, "aborted");
+  await BrowserTestUtils.closeWindow(win);
+
+  // Close tab.
+  await BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_new_window_get() {
+  // Create a new tab for the GetAssertion() request.
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+  // Start a GetAssertion request.
+  await startGetAssertionRequest(tab);
+  await assertStatus(tab, "pending");
+
+  // Open a new window. The tab will lose focus.
+  let win = await BrowserTestUtils.openNewBrowserWindow();
+  await waitForStatus(tab, "aborted");
+  await BrowserTestUtils.closeWindow(win);
+
+  // Close tab.
+  await BrowserTestUtils.removeTab(tab);
 });
+
+add_task(async function test_minimize_make() {
+  let env = Cc["@mozilla.org/process/environment;1"]
+              .getService(Ci.nsIEnvironment);
+  // Minimizing windows doesn't supported in headless mode.
+  if (env.get("MOZ_HEADLESS")) {
+    return;
+  }
+
+  // Create a new tab for the MakeCredential() request.
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+  // Start a MakeCredential request.
+  await startMakeCredentialRequest(tab);
+  await assertStatus(tab, "pending");
+
+  // Minimize the window.
+  window.minimize();
+  await waitForStatus(tab, "aborted");
+
+  // Restore the window.
+  await new Promise(resolve => SimpleTest.waitForFocus(resolve, window));
+
+  // Close tab.
+  await BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_minimize_get() {
+  let env = Cc["@mozilla.org/process/environment;1"]
+              .getService(Ci.nsIEnvironment);
+  // Minimizing windows doesn't supported in headless mode.
+  if (env.get("MOZ_HEADLESS")) {
+    return;
+  }
+
+  // Create a new tab for the GetAssertion() request.
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+  // Start a GetAssertion request.
+  await startGetAssertionRequest(tab);
+  await assertStatus(tab, "pending");
+
+  // Minimize the window.
+  window.minimize();
+  await waitForStatus(tab, "aborted");
+
+  // Restore the window.
+  await new Promise(resolve => SimpleTest.waitForFocus(resolve, window));
+
+  // Close tab.
+  await BrowserTestUtils.removeTab(tab);
+});
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..198519e7c0d4b80aa5df525114cd3d4558f87f80
GIT binary patch
literal 42434
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
MZ&#l21Ary}7l)4DO#lD@
new file mode 100644
--- /dev/null
+++ b/image/test/crashtests/1443232-1.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<body>
+<img id='m1'><br/>
+<script>
+  var im1=document.getElementById('m1');
+  var step_state=0;
+  function handle_step(){
+    step_state+=1;
+    if(step_state == 1){
+      im1.src='data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACwAAAAAAQABAAACAkQBADs=';
+    }
+    else if(step_state==2){
+      im1.src='1443232-1.gif';
+    }
+    else if(step_state==3){
+      if(im1.height==2)
+        im1.height=1;
+      im1.height=2;
+      im1.getBoundingClientRect();
+      setTimeout(function(){document.documentElement.classList.remove("reftest-wait");}, 1000);
+    }
+  }
+  document.addEventListener('DOMContentLoaded', function(){
+    im1.addEventListener('load', handle_step, false);
+    im1.src='data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
+  });
+</script>
+</body>
+</html>
--- a/image/test/crashtests/crashtests.list
+++ b/image/test/crashtests/crashtests.list
@@ -24,16 +24,17 @@ load 1241728-1.html
 load 1241729-1.html
 load 1242093-1.html
 load 1242778-1.png
 load 1249576-1.png
 load 1253362-1.html
 load 1355898-1.html
 load 1375842-1.html
 load 1413762-1.gif
+pref(image.downscale-during-decode.enabled,true) load 1443232-1.html
 load colormap-range.gif
 HTTP load delayedframe.sjs # A 3-frame animated GIF with an inordinate delay between the second and third frame
 
 # Animated gifs with a very large canvas, but tiny actual content.
 load delaytest.html?523528-1.gif
 load delaytest.html?523528-2.gif
 
 # Bug 1160801 - Ensure that we handle invalid disposal types.
deleted file mode 100644
index 198519e7c0d4b80aa5df525114cd3d4558f87f80..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
--- a/image/test/reftest/generic/1443232-1.html
+++ /dev/null
@@ -1,30 +0,0 @@
-<!DOCTYPE html>
-<html class="reftest-wait">
-<body>
-<img id='m1'><br/>
-<script>
-  var im1=document.getElementById('m1');
-  var step_state=0;
-  function handle_step(){
-    step_state+=1;
-    if(step_state == 1){
-      im1.src='data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACwAAAAAAQABAAACAkQBADs=';
-    }
-    else if(step_state==2){
-      im1.src='1443232-1.gif';
-    }
-    else if(step_state==3){
-      if(im1.height==2)
-        im1.height=1;
-      im1.height=2;
-      im1.getBoundingClientRect();
-      setTimeout(function(){document.documentElement.classList.remove("reftest-wait");}, 1000);
-    }
-  }
-  document.addEventListener('DOMContentLoaded', function(){
-    im1.addEventListener('load', handle_step, false);
-    im1.src='data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
-  });
-</script>
-</body>
-</html>
--- a/image/test/reftest/generic/reftest.list
+++ b/image/test/reftest/generic/reftest.list
@@ -1,3 +1,1 @@
 HTTP == accept-image-catchall.html accept-image-catchall-ref.html
-# skip for now to investigate why it fails on android
-# pref(image.downscale-during-decode.enabled,true) == 1443232-1.html about:blank
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/1438121-async-function.js
@@ -0,0 +1,119 @@
+const mainGlobal = this;
+const debuggerGlobal = newGlobal();
+
+function Memory({global}) {
+  this.dbg = new (debuggerGlobal.Debugger);
+  this.gDO = this.dbg.addDebuggee(global);
+}
+
+Memory.prototype = {
+  constructor: Memory,
+  attach() { return Promise.resolve('fake attach result'); },
+  detach() { return Promise.resolve('fake detach result'); },
+  startRecordingAllocations() {
+    this.dbg.memory.trackingAllocationSites = true;
+    return Promise.resolve('fake startRecordingAllocations result');
+  },
+  stopRecordingAllocations() {
+    this.dbg.memory.trackingAllocationSites = false;
+    return Promise.resolve('fake stopRecordingAllocations result');
+  },
+  getAllocations() {
+    return Promise.resolve({ allocations: this.dbg.memory.drainAllocationsLog() });
+  }
+};
+
+function ok(cond, msg) {
+  assertEq(!!cond, true, `ok(${uneval(cond)}, ${uneval(msg)})`);
+}
+
+const is = assertEq;
+
+function startServerAndGetSelectedTabMemory() {
+  let memory = new Memory({ global: mainGlobal });
+  return Promise.resolve({ memory, client: 'fake client' });
+}
+
+function destroyServerAndFinish() {
+  return Promise.resolve('fake destroyServerAndFinish result');
+}
+
+(async function body() {
+  let { memory, client } = await startServerAndGetSelectedTabMemory();
+  await memory.attach();
+
+  await memory.startRecordingAllocations();
+  ok(true, "Can start recording allocations");
+
+  // Allocate some objects.
+
+  let alloc1, alloc2, alloc3;
+
+  /* eslint-disable max-nested-callbacks */
+  (function outer() {
+    (function middle() {
+      (function inner() {
+        alloc1 = {}; alloc1.line = Error().lineNumber;
+        alloc2 = []; alloc2.line = Error().lineNumber;
+        // eslint-disable-next-line new-parens
+        alloc3 = new function () {}; alloc3.line = Error().lineNumber;
+      }());
+    }());
+  }());
+  /* eslint-enable max-nested-callbacks */
+
+  let response = await memory.getAllocations();
+
+  await memory.stopRecordingAllocations();
+  ok(true, "Can stop recording allocations");
+
+  // Filter out allocations by library and test code, and get only the
+  // allocations that occurred in our test case above.
+
+  function isTestAllocation(alloc) {
+    let frame = alloc.frame;
+    return frame
+      && frame.functionDisplayName === "inner"
+      && (frame.line === alloc1.line
+          || frame.line === alloc2.line
+          || frame.line === alloc3.line);
+  }
+
+  let testAllocations = response.allocations.filter(isTestAllocation);
+  ok(testAllocations.length >= 3,
+     "Should find our 3 test allocations (plus some allocations for the error "
+     + "objects used to get line numbers)");
+
+  // For each of the test case's allocations, ensure that the parent frame
+  // indices are correct. Also test that we did get an allocation at each
+  // line we expected (rather than a bunch on the first line and none on the
+  // others, etc).
+
+  let expectedLines = new Set([alloc1.line, alloc2.line, alloc3.line]);
+
+  for (let alloc of testAllocations) {
+    let innerFrame = alloc.frame;
+    ok(innerFrame, "Should get the inner frame");
+    is(innerFrame.functionDisplayName, "inner");
+    expectedLines.delete(innerFrame.line);
+
+    let middleFrame = innerFrame.parent;
+    ok(middleFrame, "Should get the middle frame");
+    is(middleFrame.functionDisplayName, "middle");
+
+    let outerFrame = middleFrame.parent;
+    ok(outerFrame, "Should get the outer frame");
+    is(outerFrame.functionDisplayName, "outer");
+
+    // Not going to test the rest of the frames because they are Task.jsm
+    // and promise frames and it gets gross. Plus, I wouldn't want this test
+    // to start failing if they changed their implementations in a way that
+    // added or removed stack frames here.
+  }
+
+  is(expectedLines.size, 0,
+     "Should have found all the expected lines");
+
+  await memory.detach();
+  destroyServerAndFinish(client);
+})().catch(e => { print("Error: " + e + "\nstack:\n" + e.stack); quit(1); });
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/1438121-generator.js
@@ -0,0 +1,131 @@
+const mainGlobal = this;
+const debuggerGlobal = newGlobal();
+
+function Memory({global}) {
+  this.dbg = new (debuggerGlobal.Debugger);
+  this.gDO = this.dbg.addDebuggee(global);
+}
+
+Memory.prototype = {
+  constructor: Memory,
+  attach() { return Promise.resolve('fake attach result'); },
+  detach() { return Promise.resolve('fake detach result'); },
+  startRecordingAllocations() {
+    this.dbg.memory.trackingAllocationSites = true;
+    return Promise.resolve('fake startRecordingAllocations result');
+  },
+  stopRecordingAllocations() {
+    this.dbg.memory.trackingAllocationSites = false;
+    return Promise.resolve('fake stopRecordingAllocations result');
+  },
+  getAllocations() {
+    return Promise.resolve({ allocations: this.dbg.memory.drainAllocationsLog() });
+  }
+};
+
+function ok(cond, msg) {
+  assertEq(!!cond, true, `ok(${uneval(cond)}, ${uneval(msg)})`);
+}
+
+const is = assertEq;
+
+function startServerAndGetSelectedTabMemory() {
+  let memory = new Memory({ global: mainGlobal });
+  return Promise.resolve({ memory, client: 'fake client' });
+}
+
+function destroyServerAndFinish() {
+  return Promise.resolve('fake destroyServerAndFinish result');
+}
+
+function* body() {
+  let { memory, client } = yield startServerAndGetSelectedTabMemory();
+  yield memory.attach();
+
+  yield memory.startRecordingAllocations();
+  ok(true, "Can start recording allocations");
+
+  // Allocate some objects.
+
+  let alloc1, alloc2, alloc3;
+
+  /* eslint-disable max-nested-callbacks */
+  (function outer() {
+    (function middle() {
+      (function inner() {
+        alloc1 = {}; alloc1.line = Error().lineNumber;
+        alloc2 = []; alloc2.line = Error().lineNumber;
+        // eslint-disable-next-line new-parens
+        alloc3 = new function () {}; alloc3.line = Error().lineNumber;
+      }());
+    }());
+  }());
+  /* eslint-enable max-nested-callbacks */
+
+  let response = yield memory.getAllocations();
+
+  yield memory.stopRecordingAllocations();
+  ok(true, "Can stop recording allocations");
+
+  // Filter out allocations by library and test code, and get only the
+  // allocations that occurred in our test case above.
+
+  function isTestAllocation(alloc) {
+    let frame = alloc.frame;
+    return frame
+      && frame.functionDisplayName === "inner"
+      && (frame.line === alloc1.line
+          || frame.line === alloc2.line
+          || frame.line === alloc3.line);
+  }
+
+  let testAllocations = response.allocations.filter(isTestAllocation);
+  ok(testAllocations.length >= 3,
+     "Should find our 3 test allocations (plus some allocations for the error "
+     + "objects used to get line numbers)");
+
+  // For each of the test case's allocations, ensure that the parent frame
+  // indices are correct. Also test that we did get an allocation at each
+  // line we expected (rather than a bunch on the first line and none on the
+  // others, etc).
+
+  let expectedLines = new Set([alloc1.line, alloc2.line, alloc3.line]);
+
+  for (let alloc of testAllocations) {
+    let innerFrame = alloc.frame;
+    ok(innerFrame, "Should get the inner frame");
+    is(innerFrame.functionDisplayName, "inner");
+    expectedLines.delete(innerFrame.line);
+
+    let middleFrame = innerFrame.parent;
+    ok(middleFrame, "Should get the middle frame");
+    is(middleFrame.functionDisplayName, "middle");
+
+    let outerFrame = middleFrame.parent;
+    ok(outerFrame, "Should get the outer frame");
+    is(outerFrame.functionDisplayName, "outer");
+
+    // Not going to test the rest of the frames because they are Task.jsm
+    // and promise frames and it gets gross. Plus, I wouldn't want this test
+    // to start failing if they changed their implementations in a way that
+    // added or removed stack frames here.
+  }
+
+  is(expectedLines.size, 0,
+     "Should have found all the expected lines");
+
+  yield memory.detach();
+  destroyServerAndFinish(client);
+}
+
+const generator = body();
+loop(generator.next());
+
+function loop({ value: promise, done }) {
+  if (done)
+    return;
+  promise
+    .catch(e => loop(generator.throw(e)))
+    .then(v => { loop(generator.next(v)); })
+    .catch(e => { print(`Error: ${e}\nstack:\n${e.stack}`); });
+}
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -4300,16 +4300,24 @@ CodeGenerator::visitCallNative(LCallNati
     emitTracelogStopEvent(TraceLogger_Call);
 
     // Test for failure.
     masm.branchIfFalseBool(ReturnReg, masm.failureLabel());
 
     // Load the outparam vp[0] into output register(s).
     masm.loadValue(Address(masm.getStackPointer(), NativeExitFrameLayout::offsetOfResult()), JSReturnOperand);
 
+    // Until C++ code is instrumented against Spectre, prevent speculative
+    // execution from returning any private data.
+    if (JitOptions.spectreJitToCxxCalls && !call->mir()->ignoresReturnValue() &&
+        call->mir()->hasLiveDefUses())
+    {
+        masm.speculationBarrier();
+    }
+
     // The next instruction is removing the footer of the exit frame, so there
     // is no need for leaveFakeExitFrame.
 
     // Move the StackPointer back to its original location, unwinding the native exit frame.
     masm.adjustStack(NativeExitFrameLayout::Size() - unusedStack);
     MOZ_ASSERT(masm.framePushed() == initialStack);
 }
 
@@ -4438,16 +4446,21 @@ CodeGenerator::visitCallDOMNative(LCallD
         // Test for failure.
         masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel());
 
         // Load the outparam vp[0] into output register(s).
         masm.loadValue(Address(masm.getStackPointer(), IonDOMMethodExitFrameLayout::offsetOfResult()),
                        JSReturnOperand);
     }
 
+    // Until C++ code is instrumented against Spectre, prevent speculative
+    // execution from returning any private data.
+    if (JitOptions.spectreJitToCxxCalls && call->mir()->hasLiveDefUses())
+        masm.speculationBarrier();
+
     // The next instruction is removing the footer of the exit frame, so there
     // is no need for leaveFakeExitFrame.
 
     // Move the StackPointer back to its original location, unwinding the native exit frame.
     masm.adjustStack(IonDOMMethodExitFrameLayout::Size() - unusedStack);
     MOZ_ASSERT(masm.framePushed() == initialStack);
 }
 
@@ -11988,16 +12001,22 @@ CodeGenerator::visitGetDOMProperty(LGetD
         masm.loadValue(Address(masm.getStackPointer(), IonDOMExitFrameLayout::offsetOfResult()),
                        JSReturnOperand);
     } else {
         masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel());
 
         masm.loadValue(Address(masm.getStackPointer(), IonDOMExitFrameLayout::offsetOfResult()),
                        JSReturnOperand);
     }
+
+    // Until C++ code is instrumented against Spectre, prevent speculative
+    // execution from returning any private data.
+    if (JitOptions.spectreJitToCxxCalls && ins->mir()->hasLiveDefUses())
+        masm.speculationBarrier();
+
     masm.adjustStack(IonDOMExitFrameLayout::Size());
 
     masm.bind(&haveValue);
 
     MOZ_ASSERT(masm.framePushed() == initialStack);
 }
 
 void
--- a/js/src/jit/JitOptions.cpp
+++ b/js/src/jit/JitOptions.cpp
@@ -233,16 +233,17 @@ DefaultJitOptions::DefaultJitOptions()
         if (!forcedRegisterAllocator.isSome())
             Warn(forcedRegisterAllocatorEnv, env);
     }
 
     SET_DEFAULT(spectreIndexMasking, true);
     SET_DEFAULT(spectreObjectMitigationsBarriers, true);
     SET_DEFAULT(spectreStringMitigations, true);
     SET_DEFAULT(spectreValueMasking, true);
+    SET_DEFAULT(spectreJitToCxxCalls, true);
 
     // Toggles whether unboxed plain objects can be created by the VM.
     SET_DEFAULT(disableUnboxedObjects, false);
 
     // Test whether Atomics are allowed in asm.js code.
     SET_DEFAULT(asmJSAtomicsEnable, false);
 
     // Toggles the optimization whereby offsets are folded into loads and not
--- a/js/src/jit/JitOptions.h
+++ b/js/src/jit/JitOptions.h
@@ -97,16 +97,17 @@ struct DefaultJitOptions
 
     // Spectre mitigation flags. Each mitigation has its own flag in order to
     // measure the effectiveness of each mitigation with various proof of
     // concept.
     bool spectreIndexMasking;
     bool spectreObjectMitigationsBarriers;
     bool spectreStringMitigations;
     bool spectreValueMasking;
+    bool spectreJitToCxxCalls;
 
     // The options below affect the rest of the VM, and not just the JIT.
     bool disableUnboxedObjects;
 
     DefaultJitOptions();
     bool isSmallFunction(JSScript* script) const;
     void setEagerCompilation();
     void setCompilerWarmUpThreshold(uint32_t warmUpThreshold);
--- a/js/src/jit/MacroAssembler.cpp
+++ b/js/src/jit/MacroAssembler.cpp
@@ -3651,16 +3651,54 @@ MacroAssembler::emitPreBarrierFastPath(J
 #else
 # error "Unknown architecture"
 #endif
 
     // No barrier is needed if the bit is set, |word & mask != 0|.
     branchTestPtr(Assembler::NonZero, temp2, temp1, noBarrier);
 }
 
+// ========================================================================
+// Spectre Mitigations.
+
+void
+MacroAssembler::spectreMaskIndex(Register index, Register length, Register output)
+{
+    MOZ_ASSERT(JitOptions.spectreIndexMasking);
+    MOZ_ASSERT(length != output);
+    MOZ_ASSERT(index != output);
+
+    move32(Imm32(0), output);
+    cmp32Move32(Assembler::Below, index, length, index, output);
+}
+
+void
+MacroAssembler::spectreMaskIndex(Register index, const Address& length, Register output)
+{
+    MOZ_ASSERT(JitOptions.spectreIndexMasking);
+    MOZ_ASSERT(index != length.base);
+    MOZ_ASSERT(length.base != output);
+    MOZ_ASSERT(index != output);
+
+    move32(Imm32(0), output);
+    cmp32Move32(Assembler::Below, index, length, index, output);
+}
+
+void
+MacroAssembler::boundsCheck32PowerOfTwo(Register index, uint32_t length, Label* failure)
+{
+    MOZ_ASSERT(mozilla::IsPowerOfTwo(length));
+    branch32(Assembler::AboveOrEqual, index, Imm32(length), failure);
+
+    // Note: it's fine to clobber the input register, as this is a no-op: it
+    // only affects speculative execution.
+    if (JitOptions.spectreIndexMasking)
+        and32(Imm32(length - 1), index);
+}
+
 //}}} check_macroassembler_style
 
 void
 MacroAssembler::memoryBarrierBefore(const Synchronization& sync) {
     memoryBarrier(sync.barrierBefore);
 }
 
 void
@@ -3702,51 +3740,16 @@ MacroAssembler::debugAssertObjHasFixedSl
                  Address(scratch, Shape::offsetOfSlotInfo()),
                  Imm32(Shape::fixedSlotsMask()),
                  &hasFixedSlots);
     assumeUnreachable("Expected a fixed slot");
     bind(&hasFixedSlots);
 #endif
 }
 
-void
-MacroAssembler::spectreMaskIndex(Register index, Register length, Register output)
-{
-    MOZ_ASSERT(JitOptions.spectreIndexMasking);
-    MOZ_ASSERT(length != output);
-    MOZ_ASSERT(index != output);
-
-    move32(Imm32(0), output);
-    cmp32Move32(Assembler::Below, index, length, index, output);
-}
-
-void
-MacroAssembler::spectreMaskIndex(Register index, const Address& length, Register output)
-{
-    MOZ_ASSERT(JitOptions.spectreIndexMasking);
-    MOZ_ASSERT(index != length.base);
-    MOZ_ASSERT(length.base != output);
-    MOZ_ASSERT(index != output);
-
-    move32(Imm32(0), output);
-    cmp32Move32(Assembler::Below, index, length, index, output);
-}
-
-void
-MacroAssembler::boundsCheck32PowerOfTwo(Register index, uint32_t length, Label* failure)
-{
-    MOZ_ASSERT(mozilla::IsPowerOfTwo(length));
-    branch32(Assembler::AboveOrEqual, index, Imm32(length), failure);
-
-    // Note: it's fine to clobber the input register, as this is a no-op: it
-    // only affects speculative execution.
-    if (JitOptions.spectreIndexMasking)
-        and32(Imm32(length - 1), index);
-}
-
 template <typename T, size_t N, typename P>
 static bool
 AddPendingReadBarrier(Vector<T*, N, P>& list, T* value)
 {
     // Check if value is already present in tail of list.
     // TODO: Consider using a hash table here.
     const size_t TailWindow = 4;
 
--- a/js/src/jit/MacroAssembler.h
+++ b/js/src/jit/MacroAssembler.h
@@ -1952,16 +1952,42 @@ class MacroAssembler : public MacroAssem
                           Register offsetTemp, Register maskTemp)
         DEFINED_ON(mips_shared);
 
     void atomicEffectOpJS(Scalar::Type arrayType, const Synchronization& sync, AtomicOp op,
                           Register value, const BaseIndex& mem, Register valueTemp,
                           Register offsetTemp, Register maskTemp)
         DEFINED_ON(mips_shared);
 
+    // ========================================================================
+    // Spectre Mitigations.
+    //
+    // Spectre attacks are side-channel attacks based on cache pollution or
+    // slow-execution of some instructions. We have multiple spectre mitigations
+    // possible:
+    //
+    //   - Stop speculative executions, with memory barriers. Memory barriers
+    //     force all branches depending on loads to be resolved, and thus
+    //     resolve all miss-speculated paths.
+    //
+    //   - Use conditional move instructions. Some CPUs have a branch predictor,
+    //     and not a flag predictor. In such cases, using a conditional move
+    //     instruction to zero some pointer/index is enough to add a
+    //     data-dependency which prevents any futher executions until the load is
+    //     resolved.
+
+    void spectreMaskIndex(Register index, Register length, Register output);
+    void spectreMaskIndex(Register index, const Address& length, Register output);
+
+    // The length must be a power of two. Performs a bounds check and Spectre index
+    // masking.
+    void boundsCheck32PowerOfTwo(Register index, uint32_t length, Label* failure);
+
+    void speculationBarrier() PER_SHARED_ARCH;
+
     //}}} check_macroassembler_decl_style
   public:
 
     // Emits a test of a value against all types in a TypeSet. A scratch
     // register is required.
     template <typename Source>
     void guardTypeSet(const Source& address, const TypeSet* types, BarrierKind kind,
                       Register unboxScratch, Register objScratch, Register spectreRegToZero,
@@ -2136,23 +2162,16 @@ class MacroAssembler : public MacroAssem
     using MacroAssemblerSpecific::store32;
     void store32(const RegisterOrInt32Constant& key, const Address& dest) {
         if (key.isRegister())
             store32(key.reg(), dest);
         else
             store32(Imm32(key.constant()), dest);
     }
 
-    void spectreMaskIndex(Register index, Register length, Register output);
-    void spectreMaskIndex(Register index, const Address& length, Register output);
-
-    // The length must be a power of two. Performs a bounds check and Spectre index
-    // masking.
-    void boundsCheck32PowerOfTwo(Register index, uint32_t length, Label* failure);
-
     template <typename T>
     void guardedCallPreBarrier(const T& address, MIRType type) {
         Label done;
 
         branchTestNeedsIncrementalBarrier(Assembler::Zero, &done);
 
         if (type == MIRType::Value)
             branchTestGCThing(Assembler::NotEqual, address, &done);
--- a/js/src/jit/VMFunctions.h
+++ b/js/src/jit/VMFunctions.h
@@ -144,16 +144,22 @@ struct VMFunction
         // JSContext * + args + (OutParam? *)
         return 1 + explicitArgc() + ((outParam == Type_Void) ? 0 : 1);
     }
 
     DataType failType() const {
         return returnType;
     }
 
+    // Whether this function returns anything more than a boolean flag for
+    // failures.
+    bool returnsData() const {
+        return returnType == Type_Pointer || outParam != Type_Void;
+    }
+
     ArgProperties argProperties(uint32_t explicitArg) const {
         return ArgProperties((argumentProperties >> (2 * explicitArg)) & 3);
     }
 
     RootType argRootType(uint32_t explicitArg) const {
         return RootType((argumentRootTypes >> (3 * explicitArg)) & 7);
     }
 
--- a/js/src/jit/arm/Assembler-arm.cpp
+++ b/js/src/jit/arm/Assembler-arm.cpp
@@ -2158,16 +2158,26 @@ Assembler::as_isb_trap()
 {
     // ISB is "mcr 15, 0, r0, c7, c5, 4".
     // ARMv7 manual, "VMSA CP15 c7 register summary".
     // Flagged as "legacy" starting with ARMv8, may be disabled on chip, see
     // ARMv8 manual E2.7.3 and G3.18.16.
     return writeInst(0xee070f94);
 }
 
+BufferOffset
+Assembler::as_csdb()
+{
+    // NOP (see as_nop) on architectures where this instruction is not defined.
+    //
+    // https://developer.arm.com/-/media/developer/pdf/Cache_Speculation_Side-channels_22Feb18.pdf
+    // CSDB A32: 1110_0011_0010_0000_1111_0000_0001_0100
+    return writeInst(0xe320f000 | 0x14);
+}
+
 // Control flow stuff:
 
 // bx can *only* branch to a register, never to an immediate.
 BufferOffset
 Assembler::as_bx(Register r, Condition c)
 {
     BufferOffset ret = writeInst(((int) c) | OpBx | r.code());
     return ret;
--- a/js/src/jit/arm/Assembler-arm.h
+++ b/js/src/jit/arm/Assembler-arm.h
@@ -1601,16 +1601,19 @@ class Assembler : public AssemblerShared
     BufferOffset as_dsb(BarrierOption option = BarrierSY);
     BufferOffset as_isb();
 
     // Memory synchronization for architectures before ARMv7.
     BufferOffset as_dsb_trap();
     BufferOffset as_dmb_trap();
     BufferOffset as_isb_trap();
 
+    // Speculation barrier
+    BufferOffset as_csdb();
+
     // Control flow stuff:
 
     // bx can *only* branch to a register never to an immediate.
     BufferOffset as_bx(Register r, Condition c = Always);
 
     // Branch can branch to an immediate *or* to a register. Branches to
     // immediates are pc relative, branches to registers are absolute.
     BufferOffset as_b(BOffImm off, Condition c, Label* documentation = nullptr);
--- a/js/src/jit/arm/MacroAssembler-arm.cpp
+++ b/js/src/jit/arm/MacroAssembler-arm.cpp
@@ -5780,16 +5780,27 @@ MacroAssembler::convertUInt64ToDouble(Re
         movePtr(ImmPtr(&TO_DOUBLE_HIGH_SCALE), scratch);
         ma_vldr(Operand(Address(scratch, 0)).toVFPAddr(), scratchDouble);
     }
     mulDouble(scratchDouble, dest);
     convertUInt32ToDouble(src.low, scratchDouble);
     addDouble(scratchDouble, dest);
 }
 
+// ========================================================================
+// Spectre Mitigations.
+
+void
+MacroAssembler::speculationBarrier()
+{
+    // Spectre mitigation recommended by ARM for cases where csel/cmov cannot be
+    // used.
+    as_csdb();
+}
+
 //}}} check_macroassembler_style
 
 void
 MacroAssemblerARM::wasmTruncateToInt32(FloatRegister input, Register output, MIRType fromType,
                                        bool isUnsigned, bool isSaturating, Label* oolEntry)
 {
     // vcvt* converts NaN into 0, so check for NaNs here.
     if (!isSaturating) {
--- a/js/src/jit/arm/Simulator-arm.cpp
+++ b/js/src/jit/arm/Simulator-arm.cpp
@@ -277,16 +277,19 @@ class SimInstruction {
     // Test for miscellaneous instructions encodings of type 0 instructions.
     inline bool isMiscType0() const {
         return bit(24) == 1 && bit(23) == 0 && bit(20) == 0 && (bit(7) == 0);
     }
 
     // Test for a nop instruction, which falls under type 1.
     inline bool isNopType1() const { return bits(24, 0) == 0x0120F000; }
 
+    // Test for a nop instruction, which falls under type 1.
+    inline bool isCsdbType1() const { return bits(24, 0) == 0x0120F014; }
+
     // Test for a stop instruction.
     inline bool isStop() const {
         return typeValue() == 7 && bit(24) == 1 && svcValue() >= kStopCode;
     }
 
     // Test for a udf instruction, which falls under type 3.
     inline bool isUDF() const {
       return (instructionBits() & 0xfff000f0) == 0xe7f000f0;
@@ -3382,16 +3385,18 @@ Simulator::decodeType01(SimInstruction* 
                 break;
             }
         } else {
             printf("%08x\n", instr->instructionBits());
             MOZ_CRASH();
         }
     } else if ((type == 1) && instr->isNopType1()) {
         // NOP.
+    } else if ((type == 1) && instr->isCsdbType1()) {
+        // Speculation barrier. (No-op for the simulator)
     } else {
         int rd = instr->rdValue();
         int rn = instr->rnValue();
         int32_t rn_val = get_register(rn);
         int32_t shifter_operand = 0;
         bool shifter_carry_out = 0;
         if (type == 0) {
             shifter_operand = getShiftRm(instr, &shifter_carry_out);
--- a/js/src/jit/arm/Trampoline-arm.cpp
+++ b/js/src/jit/arm/Trampoline-arm.cpp
@@ -873,16 +873,22 @@ JitRuntime::generateVMWrapper(JSContext*
             masm.assumeUnreachable("Unable to load into float reg, with no FP support.");
         masm.freeStack(sizeof(double));
         break;
 
       default:
         MOZ_ASSERT(f.outParam == Type_Void);
         break;
     }
+
+    // Until C++ code is instrumented against Spectre, prevent speculative
+    // execution from returning any private data.
+    if (f.returnsData() && JitOptions.spectreJitToCxxCalls)
+        masm.speculationBarrier();
+
     masm.leaveExitFrame();
     masm.retn(Imm32(sizeof(ExitFrameLayout) +
                     f.explicitStackSlots() * sizeof(void*) +
                     f.extraValuesToPop * sizeof(Value)));
 
     return functionWrappers_->putNew(&f, wrapperOffset);
 }
 
--- a/js/src/jit/arm/disasm/Constants-arm.h
+++ b/js/src/jit/arm/disasm/Constants-arm.h
@@ -650,16 +650,19 @@ class Instruction {
     inline bool IsMiscType0() const { return (Bit(24) == 1)
             && (Bit(23) == 0)
             && (Bit(20) == 0)
             && ((Bit(7) == 0)); }
 
     // Test for a nop instruction, which falls under type 1.
     inline bool IsNopType1() const { return Bits(24, 0) == 0x0120F000; }
 
+    // Test for a nop instruction, which falls under type 1.
+    inline bool IsCsdbType1() const { return Bits(24, 0) == 0x0120F014; }
+
     // Test for a stop instruction.
     inline bool IsStop() const {
         return (TypeValue() == 7) && (Bit(24) == 1) && (SvcValue() >= kStopCode);
     }
 
     // Special accessors that test for existence of a value.
     inline bool HasS()    const { return SValue() == 1; }
     inline bool HasB()    const { return BValue() == 1; }
--- a/js/src/jit/arm/disasm/Disasm-arm.cpp
+++ b/js/src/jit/arm/disasm/Disasm-arm.cpp
@@ -947,16 +947,18 @@ Decoder::DecodeType01(Instruction* instr
                 Unknown(instr);  // not used by V8
                 break;
             }
         } else {
             Unknown(instr);  // not used by V8
         }
     } else if ((type == 1) && instr->IsNopType1()) {
         Format(instr, "nop'cond");
+    } else if ((type == 1) && instr->IsCsdbType1()) {
+        Format(instr, "csdb'cond");
     } else {
         switch (instr->OpcodeField()) {
           case AND: {
             Format(instr, "and'cond's 'rd, 'rn, 'shift_op");
              break;
           }
           case EOR: {
             Format(instr, "eor'cond's 'rd, 'rn, 'shift_op");
--- a/js/src/jit/arm64/MacroAssembler-arm64.cpp
+++ b/js/src/jit/arm64/MacroAssembler-arm64.cpp
@@ -1857,12 +1857,22 @@ MacroAssembler::atomicEffectOpJS(Scalar:
 
 void
 MacroAssembler::atomicEffectOpJS(Scalar::Type arrayType, const Synchronization& sync, AtomicOp op,
                            Register value, const Address& mem, Register temp)
 {
     atomicEffectOp(arrayType, sync, op, value, mem, temp);
 }
 
+// ========================================================================
+// Spectre Mitigations.
+
+void
+MacroAssembler::speculationBarrier()
+{
+    // Conditional speculation barrier.
+    csdb();
+}
+
 //}}} check_macroassembler_style
 
 } // namespace jit
 } // namespace js
--- a/js/src/jit/arm64/Trampoline-arm64.cpp
+++ b/js/src/jit/arm64/Trampoline-arm64.cpp
@@ -697,16 +697,21 @@ JitRuntime::generateVMWrapper(JSContext*
         masm.freeStack(sizeof(uintptr_t));
         break;
 
       default:
         MOZ_ASSERT(f.outParam == Type_Void);
         break;
     }
 
+    // Until C++ code is instrumented against Spectre, prevent speculative
+    // execution from returning any private data.
+    if (f.returnsData() && JitOptions.spectreJitToCxxCalls)
+        masm.speculationBarrier();
+
     masm.leaveExitFrame();
     masm.retn(Imm32(sizeof(ExitFrameLayout) +
               f.explicitStackSlots() * sizeof(void*) +
               f.extraValuesToPop * sizeof(Value)));
 
     return functionWrappers_->putNew(&f, wrapperOffset);
 }
 
--- a/js/src/jit/arm64/vixl/Assembler-vixl.h
+++ b/js/src/jit/arm64/vixl/Assembler-vixl.h
@@ -1813,16 +1813,23 @@ class Assembler : public MozBaseAssemble
 
   // Alias for system instructions.
   // No-op.
   BufferOffset nop() {
     return hint(NOP);
   }
   static void nop(Instruction* at);
 
+  // Alias for system instructions.
+  // Conditional speculation barrier.
+  BufferOffset csdb() {
+    return hint(CSDB);
+  }
+  static void csdb(Instruction* at);
+
   // FP and NEON instructions.
   // Move double precision immediate to FP register.
   void fmov(const VRegister& vd, double imm);
 
   // Move single precision immediate to FP register.
   void fmov(const VRegister& vd, float imm);
 
   // Move FP register to register.
--- a/js/src/jit/arm64/vixl/Constants-vixl.h
+++ b/js/src/jit/arm64/vixl/Constants-vixl.h
@@ -316,17 +316,20 @@ enum Extend {
 };
 
 enum SystemHint {
   NOP   = 0,
   YIELD = 1,
   WFE   = 2,
   WFI   = 3,
   SEV   = 4,
-  SEVL  = 5
+  SEVL  = 5,
+  // No-op on architectures where this instruction is not defined.
+  // https://developer.arm.com/-/media/developer/pdf/Cache_Speculation_Side-channels_22Feb18.pdf
+  CSDB  = 0x14
 };
 
 enum BarrierDomain {
   OuterShareable = 0,
   NonShareable   = 1,
   InnerShareable = 2,
   FullSystem     = 3
 };
--- a/js/src/jit/arm64/vixl/Decoder-vixl.cpp
+++ b/js/src/jit/arm64/vixl/Decoder-vixl.cpp
@@ -223,17 +223,17 @@ void Decoder::DecodeBranchSystemExceptio
                 (masked_003FF0E0 == 0x003FF0E0) ||
                 (instr->Mask(0x00388000) == 0x00008000) ||
                 (instr->Mask(0x0038E000) == 0x00000000) ||
                 (instr->Mask(0x0039E000) == 0x00002000) ||
                 (instr->Mask(0x003AE000) == 0x00002000) ||
                 (instr->Mask(0x003CE000) == 0x00042000) ||
                 (instr->Mask(0x003FFFC0) == 0x000320C0) ||
                 (instr->Mask(0x003FF100) == 0x00032100) ||
-                (instr->Mask(0x003FF200) == 0x00032200) ||
+                // (instr->Mask(0x003FF200) == 0x00032200) || // match CSDB
                 (instr->Mask(0x003FF400) == 0x00032400) ||
                 (instr->Mask(0x003FF800) == 0x00032800) ||
                 (instr->Mask(0x0038F000) == 0x00005000) ||
                 (instr->Mask(0x0038E000) == 0x00006000)) {
               VisitUnallocated(instr);
             } else {
               VisitSystem(instr);
             }
--- a/js/src/jit/arm64/vixl/Disasm-vixl.cpp
+++ b/js/src/jit/arm64/vixl/Disasm-vixl.cpp
@@ -1316,16 +1316,21 @@ void Disassembler::VisitSystem(const Ins
     }
   } else if (instr->Mask(SystemHintFMask) == SystemHintFixed) {
     switch (instr->ImmHint()) {
       case NOP: {
         mnemonic = "nop";
         form = NULL;
         break;
       }
+      case CSDB: {
+        mnemonic = "csdb";
+        form = NULL;
+        break;
+      }
     }
   } else if (instr->Mask(MemBarrierFMask) == MemBarrierFixed) {
     switch (instr->Mask(MemBarrierMask)) {
       case DMB: {
         mnemonic = "dmb";
         form = "'M";
         break;
       }
--- a/js/src/jit/arm64/vixl/Instructions-vixl.h
+++ b/js/src/jit/arm64/vixl/Instructions-vixl.h
@@ -304,16 +304,17 @@ class Instruction {
   bool IsBR() const;
   bool IsBLR() const;
   bool IsTBZ() const;
   bool IsTBNZ() const;
   bool IsCBZ() const;
   bool IsCBNZ() const;
   bool IsLDR() const;
   bool IsNOP() const;
+  bool IsCSDB() const;
   bool IsADR() const;
   bool IsADRP() const;
   bool IsMovz() const;
   bool IsMovk() const;
   bool IsBranchLinkImm() const;
   bool IsTargetReachable(Instruction* target) const;
   ptrdiff_t ImmPCRawOffset() const;
   void SetImmPCRawOffset(ptrdiff_t offset);
--- a/js/src/jit/arm64/vixl/MacroAssembler-vixl.h
+++ b/js/src/jit/arm64/vixl/MacroAssembler-vixl.h
@@ -1118,16 +1118,20 @@ class MacroAssembler : public js::jit::A
     VIXL_ASSERT(!rm.IsZero());
     SingleEmissionCheckScope guard(this);
     mul(rd, rn, rm);
   }
   void Nop() {
     SingleEmissionCheckScope guard(this);
     nop();
   }
+  void Csdb() {
+    SingleEmissionCheckScope guard(this);
+    csdb();
+  }
   void Rbit(const Register& rd, const Register& rn) {
     VIXL_ASSERT(!rd.IsZero());
     VIXL_ASSERT(!rn.IsZero());
     SingleEmissionCheckScope guard(this);
     rbit(rd, rn);
   }
   void Ret(const Register& xn = lr) {
     VIXL_ASSERT(!xn.IsZero());
--- a/js/src/jit/arm64/vixl/MozAssembler-vixl.cpp
+++ b/js/src/jit/arm64/vixl/MozAssembler-vixl.cpp
@@ -404,16 +404,21 @@ void Assembler::svc(Instruction* at, int
 }
 
 
 void Assembler::nop(Instruction* at) {
   hint(at, NOP);
 }
 
 
+void Assembler::csdb(Instruction* at) {
+  hint(at, CSDB);
+}
+
+
 BufferOffset Assembler::Logical(const Register& rd, const Register& rn,
                                 const Operand operand, LogicalOp op)
 {
   VIXL_ASSERT(rd.size() == rn.size());
   if (operand.IsImmediate()) {
     int64_t immediate = operand.immediate();
     unsigned reg_size = rd.size();
 
--- a/js/src/jit/arm64/vixl/MozInstructions-vixl.cpp
+++ b/js/src/jit/arm64/vixl/MozInstructions-vixl.cpp
@@ -80,16 +80,21 @@ bool Instruction::IsLDR() const {
 }
 
 
 bool Instruction::IsNOP() const {
   return Mask(SystemHintMask) == HINT && ImmHint() == NOP;
 }
 
 
+bool Instruction::IsCSDB() const {
+  return Mask(SystemHintMask) == HINT && ImmHint() == CSDB;
+}
+
+
 bool Instruction::IsADR() const {
   return Mask(PCRelAddressingMask) == ADR;
 }
 
 
 bool Instruction::IsADRP() const {
   return Mask(PCRelAddressingMask) == ADRP;
 }
--- a/js/src/jit/arm64/vixl/Simulator-vixl.cpp
+++ b/js/src/jit/arm64/vixl/Simulator-vixl.cpp
@@ -2321,16 +2321,17 @@ void Simulator::VisitSystem(const Instru
         }
         break;
       }
     }
   } else if (instr->Mask(SystemHintFMask) == SystemHintFixed) {
     VIXL_ASSERT(instr->Mask(SystemHintMask) == HINT);
     switch (instr->ImmHint()) {
       case NOP: break;
+      case CSDB: break;
       default: VIXL_UNIMPLEMENTED();
     }
   } else if (instr->Mask(MemBarrierFMask) == MemBarrierFixed) {
     __sync_synchronize();
   } else if ((instr->Mask(SystemSysFMask) == SystemSysFixed)) {
     switch (instr->Mask(SystemSysMask)) {
       case SYS: SysOp_W(instr->SysOp(), xreg(instr->Rt())); break;
       default: VIXL_UNIMPLEMENTED();
--- a/js/src/jit/x64/Trampoline-x64.cpp
+++ b/js/src/jit/x64/Trampoline-x64.cpp
@@ -761,16 +761,22 @@ JitRuntime::generateVMWrapper(JSContext*
         masm.loadPtr(Address(esp, 0), ReturnReg);
         masm.freeStack(sizeof(uintptr_t));
         break;
 
       default:
         MOZ_ASSERT(f.outParam == Type_Void);
         break;
     }
+
+    // Until C++ code is instrumented against Spectre, prevent speculative
+    // execution from returning any private data.
+    if (f.returnsData() && JitOptions.spectreJitToCxxCalls)
+        masm.speculationBarrier();
+
     masm.leaveExitFrame();
     masm.retn(Imm32(sizeof(ExitFrameLayout) +
                     f.explicitStackSlots() * sizeof(void*) +
                     f.extraValuesToPop * sizeof(Value)));
 
     return functionWrappers_->putNew(&f, wrapperOffset);
 }
 
--- a/js/src/jit/x86-shared/BaseAssembler-x86-shared.h
+++ b/js/src/jit/x86-shared/BaseAssembler-x86-shared.h
@@ -3738,19 +3738,23 @@ threeByteOpImmSimd("vblendps", VEX_PD, O
 
     void ret_i(int32_t imm)
     {
         spew("ret        $%d", imm);
         m_formatter.oneByteOp(OP_RET_Iz);
         m_formatter.immediate16u(imm);
     }
 
+    void lfence() {
+        spew("lfence");
+        m_formatter.twoByteOp(OP_FENCE, (RegisterID)0, 0b101);
+    }
     void mfence() {
         spew("mfence");
-        m_formatter.twoByteOp(OP_FENCE, (RegisterID)0, 6);
+        m_formatter.twoByteOp(OP_FENCE, (RegisterID)0, 0b110);
     }
 
     // Assembler admin methods:
 
     JmpDst label()
     {
         JmpDst r = JmpDst(m_formatter.size());
         spew(".set .Llabel%d, .", r.offset());
--- a/js/src/jit/x86-shared/MacroAssembler-x86-shared.cpp
+++ b/js/src/jit/x86-shared/MacroAssembler-x86-shared.cpp
@@ -1413,9 +1413,21 @@ MacroAssembler::atomicFetchOpJS(Scalar::
 void
 MacroAssembler::atomicFetchOpJS(Scalar::Type arrayType, const Synchronization& sync, AtomicOp op,
                                 Imm32 value, const BaseIndex& mem, Register temp1, Register temp2,
                                 AnyRegister output)
 {
     AtomicFetchOpJS(*this, arrayType, sync, op, value, mem, temp1, temp2, output);
 }
 
+// ========================================================================
+// Spectre Mitigations.
+
+void
+MacroAssembler::speculationBarrier()
+{
+    // Spectre mitigation recommended by Intel and AMD suggest to use lfence as
+    // a way to force all speculative execution of instructions to end.
+    MOZ_ASSERT(HasSSE2());
+    masm.lfence();
+}
+
 //}}} check_macroassembler_style
--- a/js/src/jit/x86/Trampoline-x86.cpp
+++ b/js/src/jit/x86/Trampoline-x86.cpp
@@ -777,16 +777,22 @@ JitRuntime::generateVMWrapper(JSContext*
         else
             masm.assumeUnreachable("Unable to pop to float reg, with no FP support.");
         break;
 
       default:
         MOZ_ASSERT(f.outParam == Type_Void);
         break;
     }
+
+    // Until C++ code is instrumented against Spectre, prevent speculative
+    // execution from returning any private data.
+    if (f.returnsData() && JitOptions.spectreJitToCxxCalls)
+        masm.speculationBarrier();
+
     masm.leaveExitFrame();
     masm.retn(Imm32(sizeof(ExitFrameLayout) +
                     f.explicitStackSlots() * sizeof(void*) +
                     f.extraValuesToPop * sizeof(Value)));
 
     return functionWrappers_->putNew(&f, wrapperOffset);
 }
 
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -7282,16 +7282,19 @@ JS_SetGlobalJitCompilerOption(JSContext*
         jit::JitOptions.spectreObjectMitigationsBarriers = !!value;
         break;
       case JSJITCOMPILER_SPECTRE_STRING_MITIGATIONS:
         jit::JitOptions.spectreStringMitigations = !!value;
         break;
       case JSJITCOMPILER_SPECTRE_VALUE_MASKING:
         jit::JitOptions.spectreValueMasking = !!value;
         break;
+      case JSJITCOMPILER_SPECTRE_JIT_TO_CXX_CALLS:
+        jit::JitOptions.spectreJitToCxxCalls = !!value;
+        break;
       case JSJITCOMPILER_ASMJS_ATOMICS_ENABLE:
         jit::JitOptions.asmJSAtomicsEnable = !!value;
         break;
       case JSJITCOMPILER_WASM_FOLD_OFFSETS:
         jit::JitOptions.wasmFoldOffsets = !!value;
         break;
       case JSJITCOMPILER_WASM_DELAY_TIER2:
         jit::JitOptions.wasmDelayTier2 = !!value;
@@ -7753,17 +7756,17 @@ JS::CaptureCurrentStack(JSContext* cx, J
         return false;
     stackp.set(frame.get());
     return true;
 }
 
 JS_PUBLIC_API(bool)
 JS::CopyAsyncStack(JSContext* cx, JS::HandleObject asyncStack,
                    JS::HandleString asyncCause, JS::MutableHandleObject stackp,
-                   unsigned maxFrameCount)
+                   const Maybe<size_t>& maxFrameCount)
 {
     AssertHeapIsIdle();
     CHECK_REQUEST(cx);
     MOZ_RELEASE_ASSERT(cx->compartment());
 
     js::AssertObjectIsSavedFrameOrWrapper(cx, asyncStack);
     JSCompartment* compartment = cx->compartment();
     Rooted<SavedFrame*> frame(cx);
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -5889,16 +5889,17 @@ JS_SetOffthreadIonCompilationEnabled(JSC
     Register(OFFTHREAD_COMPILATION_ENABLE, "offthread-compilation.enable")  \
     Register(FULL_DEBUG_CHECKS, "jit.full-debug-checks")                    \
     Register(JUMP_THRESHOLD, "jump-threshold")                              \
     Register(SIMULATOR_ALWAYS_INTERRUPT, "simulator.always-interrupt")      \
     Register(SPECTRE_INDEX_MASKING, "spectre.index-masking")                \
     Register(SPECTRE_OBJECT_MITIGATIONS_BARRIERS, "spectre.object-mitigations.barriers") \
     Register(SPECTRE_STRING_MITIGATIONS, "spectre.string-mitigations")      \
     Register(SPECTRE_VALUE_MASKING, "spectre.value-masking")                \
+    Register(SPECTRE_JIT_TO_CXX_CALLS, "spectre.jit-to-C++-calls")          \
     Register(ASMJS_ATOMICS_ENABLE, "asmjs.atomics.enable")                  \
     Register(WASM_FOLD_OFFSETS, "wasm.fold-offsets")                        \
     Register(WASM_DELAY_TIER2, "wasm.delay-tier2")
 
 typedef enum JSJitCompilerOption {
 #define JIT_COMPILER_DECLARE(key, str) \
     JSJITCOMPILER_ ## key,
 
@@ -6501,24 +6502,24 @@ CaptureCurrentStack(JSContext* cx, Mutab
  * by some other object.  This may be used when you need to treat a
  * given stack trace as an async parent.  If you just need to capture
  * the current stack, async parents and all, use CaptureCurrentStack
  * instead.
  *
  * Here |asyncStack| is the async stack to prepare.  It is copied into
  * |cx|'s current compartment, and the newest frame is given
  * |asyncCause| as its asynchronous cause.  If |maxFrameCount| is
- * non-zero, capture at most the youngest |maxFrameCount| frames.  The
+ * |Some(n)|, capture at most the youngest |n| frames.  The
  * new stack object is written to |stackp|.  Returns true on success,
  * or sets an exception and returns |false| on error.
  */
 extern JS_PUBLIC_API(bool)
 CopyAsyncStack(JSContext* cx, HandleObject asyncStack,
                HandleString asyncCause, MutableHandleObject stackp,
-               unsigned maxFrameCount);
+               const mozilla::Maybe<size_t>& maxFrameCount);
 
 /*
  * Accessors for working with SavedFrame JSObjects
  *
  * Each of these functions assert that if their `HandleObject savedFrame`
  * argument is non-null, its JSClass is the SavedFrame class (or it is a
  * cross-compartment or Xray wrapper around an object with the SavedFrame class)
  * and the object is not the SavedFrame.prototype object.
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -440,17 +440,17 @@ ForgetSourceHook(JSContext* cx);
  *
  * Note that the embedding still has to trigger processing of job queues at
  * right time(s), such as after evaluation of a script has run to completion.
  */
 extern JS_FRIEND_API(bool)
 UseInternalJobQueues(JSContext* cx, bool cooperative = false);
 
 /**
- * Enqueue job on the run queue.
+ * Enqueue |job| on the internal job queue.
  *
  * This is useful in tests for creating situations where a call occurs with no
  * other JavaScript on the stack.
  */
 extern JS_FRIEND_API(bool)
 EnqueueJob(JSContext* cx, JS::HandleObject job);
 
 /**
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -8596,21 +8596,23 @@ SetContextOptions(JSContext* cx, const O
     }
 
     if (const char* str = op.getStringOption("spectre-mitigations")) {
         if (strcmp(str, "on") == 0) {
             jit::JitOptions.spectreIndexMasking = true;
             jit::JitOptions.spectreObjectMitigationsBarriers = true;
             jit::JitOptions.spectreStringMitigations = true;
             jit::JitOptions.spectreValueMasking = true;
+            jit::JitOptions.spectreJitToCxxCalls = true;
         } else if (strcmp(str, "off") == 0) {
             jit::JitOptions.spectreIndexMasking = false;
             jit::JitOptions.spectreObjectMitigationsBarriers = false;
             jit::JitOptions.spectreStringMitigations = false;
             jit::JitOptions.spectreValueMasking = false;
+            jit::JitOptions.spectreJitToCxxCalls = false;
         } else {
             return OptionFailure("spectre-mitigations", str);
         }
     }
 
     if (const char* str = op.getStringOption("ion-scalar-replacement")) {
         if (strcmp(str, "on") == 0)
             jit::JitOptions.disableScalarReplacement = false;
--- a/js/src/vm/SavedStacks.cpp
+++ b/js/src/vm/SavedStacks.cpp
@@ -69,60 +69,96 @@ LiveSavedFrameCache::trace(JSTracer* trc
 
 bool
 LiveSavedFrameCache::insert(JSContext* cx, FramePtr& framePtr, const jsbytecode* pc,
                             HandleSavedFrame savedFrame)
 {
     MOZ_ASSERT(savedFrame);
     MOZ_ASSERT(initialized());
 
+#ifdef DEBUG
+    // There should not already be an entry for this frame. Checking the full stack
+    // really slows down some tests, so just check the first and last five hundred.
+    size_t limit = std::min(frames->length() / 2, size_t(500));
+    for (size_t i = 0; i < limit; i++) {
+        MOZ_ASSERT(Key(framePtr) != (*frames)[i].key);
+        MOZ_ASSERT(Key(framePtr) != (*frames)[frames->length() - 1 - i].key);
+    }
+#endif
+
     if (!frames->emplaceBack(framePtr, pc, savedFrame)) {
         ReportOutOfMemory(cx);
         return false;
     }
 
     framePtr.setHasCachedSavedFrame();
 
     return true;
 }
 
 void
 LiveSavedFrameCache::find(JSContext* cx, FramePtr& framePtr, const jsbytecode* pc,
                           MutableHandleSavedFrame frame) const
 {
     MOZ_ASSERT(initialized());
-
     MOZ_ASSERT(framePtr.hasCachedSavedFrame());
-    Key key(framePtr);
-    size_t numberStillValid = 0;
 
-    frame.set(nullptr);
-    for (auto* p = frames->begin(); p < frames->end(); p++) {
-        numberStillValid++;
-        if (key == p->key && pc == p->pc) {
-            frame.set(p->savedFrame);
-            break;
-        }
+    // If we flushed the cache due to a compartment mismatch, then we shouldn't
+    // expect to find any frames in the cache.
+    if (frames->empty()) {
+        frame.set(nullptr);
+        return;
     }
 
-    if (!frame) {
+    // All our SavedFrames should be in the same compartment. If the last
+    // entry's SavedFrame's compartment doesn't match cx's, flush the cache.
+    if (frames->back().savedFrame->compartment() != cx->compartment()) {
+#ifdef DEBUG
+        // Check that they are, indeed, all in the same compartment.
+        auto compartment = frames->back().savedFrame->compartment();
+        for (const auto& f : (*frames))
+            MOZ_ASSERT(compartment == f.savedFrame->compartment());
+#endif
         frames->clear();
+        frame.set(nullptr);
         return;
     }
 
-    MOZ_ASSERT(0 < numberStillValid && numberStillValid <= frames->length());
+    Key key(framePtr);
+    while (key != frames->back().key) {
+        MOZ_ASSERT(frames->back().savedFrame->compartment() == cx->compartment());
 
-    if (frame->compartment() != cx->compartment()) {
-        frame.set(nullptr);
-        numberStillValid--;
+        // We know that the cache does contain an entry for frameIter's frame,
+        // since its bit is set. That entry must be below this one in the stack,
+        // so frames->back() must correspond to a frame younger than
+        // frameIter's. If frameIter is the youngest frame with its bit set,
+        // then its entry is the youngest that is valid, and we can pop this
+        // entry. Even if frameIter is not the youngest frame with its bit set,
+        // since we're going to push new cache entries for all frames younger
+        // than frameIter, we must pop it anyway.
+        frames->popBack();
+
+        // If the frame's bit was set, the frame should always have an entry in
+        // the cache. (If we purged the entire cache because its SavedFrames had
+        // been captured for a different compartment, then we would have
+        // returned early above.)
+        MOZ_ASSERT(!frames->empty());
     }
 
-    // Everything after the cached SavedFrame are stale younger frames we have
-    // since popped.
-    frames->shrinkBy(frames->length() - numberStillValid);
+    // The youngest valid frame may have run some code, so its current pc may
+    // not match its cache entry's pc. In this case, just treat it as a miss. No
+    // older frame has executed any code; it would have been necessary to pop
+    // this frame for that to happen, but this frame's bit is set.
+    if (pc != frames->back().pc) {
+        frames->popBack();
+        frame.set(nullptr);
+        return;
+    }
+
+    frame.set(frames->back().savedFrame);
 }
 
 struct SavedFrame::Lookup {
     Lookup(JSAtom* source, uint32_t line, uint32_t column,
            JSAtom* functionDisplayName, JSAtom* asyncCause, SavedFrame* parent,
            JSPrincipals* principals,
            const Maybe<LiveSavedFrameCache::FramePtr>& framePtr = Nothing(),
            jsbytecode* pc = nullptr, Activation* activation = nullptr)
@@ -191,16 +227,17 @@ class MOZ_STACK_CLASS SavedFrame::AutoLo
     explicit AutoLookupVector(JSContext* cx)
       : JS::CustomAutoRooter(cx),
         lookups(cx)
     { }
 
     typedef Vector<Lookup, ASYNC_STACK_MAX_FRAME_COUNT> LookupVector;
     inline LookupVector* operator->() { return &lookups; }
     inline HandleLookup operator[](size_t i) { return HandleLookup(lookups[i]); }
+    inline HandleLookup back() { return HandleLookup(lookups.back()); }
 
   private:
     LookupVector lookups;
 
     virtual void trace(JSTracer* trc) override {
         for (size_t i = 0; i < lookups.length(); i++)
             lookups[i].trace(trc);
     }
@@ -1185,28 +1222,36 @@ SavedStacks::saveCurrentStack(JSContext*
 
     AutoGeckoProfilerEntry pseudoFrame(cx, "js::SavedStacks::saveCurrentStack");
     FrameIter iter(cx);
     return insertFrames(cx, iter, frame, mozilla::Move(capture));
 }
 
 bool
 SavedStacks::copyAsyncStack(JSContext* cx, HandleObject asyncStack, HandleString asyncCause,
-                            MutableHandleSavedFrame adoptedStack, uint32_t maxFrameCount)
+                            MutableHandleSavedFrame adoptedStack,
+                            const Maybe<size_t>& maxFrameCount)
 {
     MOZ_ASSERT(initialized());
     MOZ_RELEASE_ASSERT(cx->compartment());
     assertSameCompartment(cx, this);
 
+    RootedAtom asyncCauseAtom(cx, AtomizeString(cx, asyncCause));
+    if (!asyncCauseAtom)
+        return false;
+
     RootedObject asyncStackObj(cx, CheckedUnwrap(asyncStack));
     MOZ_RELEASE_ASSERT(asyncStackObj);
     MOZ_RELEASE_ASSERT(js::SavedFrame::isSavedFrameAndNotProto(*asyncStackObj));
-    RootedSavedFrame frame(cx, &asyncStackObj->as<js::SavedFrame>());
+    adoptedStack.set(&asyncStackObj->as<js::SavedFrame>());
 
-    return adoptAsyncStack(cx, frame, asyncCause, adoptedStack, maxFrameCount);
+    if (!adoptAsyncStack(cx, adoptedStack, asyncCauseAtom, maxFrameCount))
+        return false;
+
+    return true;
 }
 
 void
 SavedStacks::sweep()
 {
     frames.sweep();
     pcLocationMap.sweep();
 }
@@ -1232,17 +1277,17 @@ SavedStacks::clear()
 
 size_t
 SavedStacks::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf)
 {
     return frames.sizeOfExcludingThis(mallocSizeOf) +
            pcLocationMap.sizeOfExcludingThis(mallocSizeOf);
 }
 
-// Given that we have captured a stqck frame with the given principals and
+// Given that we have captured a stack frame with the given principals and
 // source, return true if the requested `StackCapture` has been satisfied and
 // stack walking can halt. Return false otherwise (and stack walking and frame
 // capturing should continue).
 static inline bool
 captureIsSatisfied(JSContext* cx, JSPrincipals* principals, const JSAtom* source,
                    JS::StackCapture& capture)
 {
     class Matcher
@@ -1276,239 +1321,238 @@ captureIsSatisfied(JSContext* cx, JSPrin
     Matcher m(cx, principals, source);
     return capture.match(m);
 }
 
 bool
 SavedStacks::insertFrames(JSContext* cx, FrameIter& iter, MutableHandleSavedFrame frame,
                           JS::StackCapture&& capture)
 {
-    // In order to lookup a cached SavedFrame object, we need to have its parent
+    // In order to look up a cached SavedFrame object, we need to have its parent
     // SavedFrame, which means we need to walk the stack from oldest frame to
     // youngest. However, FrameIter walks the stack from youngest frame to
     // oldest. The solution is to append stack frames to a vector as we walk the
     // stack with FrameIter, and then do a second pass through that vector in
     // reverse order after the traversal has completed and get or create the
     // SavedFrame objects at that time.
     //
     // To avoid making many copies of FrameIter (whose copy constructor is
     // relatively slow), we use a vector of `SavedFrame::Lookup` objects, which
     // only contain the FrameIter data we need. The `SavedFrame::Lookup`
     // objects are partially initialized with everything except their parent
     // pointers on the first pass, and then we fill in the parent pointers as we
     // return in the second pass.
 
-    Activation* asyncActivation = nullptr;
-    RootedSavedFrame asyncStack(cx, nullptr);
-    RootedString asyncCause(cx, nullptr);
-    bool parentIsInCache = false;
-    RootedSavedFrame cachedFrame(cx, nullptr);
-    Maybe<LiveSavedFrameCache::FramePtr> framePtr = LiveSavedFrameCache::FramePtr::create(iter);
+    // Accumulate the vector of Lookup objects here, youngest to oldest.
+    SavedFrame::AutoLookupVector stackChain(cx);
 
-    // Accumulate the vector of Lookup objects in |stackChain|.
-    SavedFrame::AutoLookupVector stackChain(cx);
+    // If we find an async parent or a cached saved frame, then that supplies
+    // the parent of the frames we have placed in stackChain. If we walk the
+    // stack all the way to the end, this remains null.
+    RootedSavedFrame parent(cx, nullptr);
+
+    // Once we've seen one frame with its hasCachedSavedFrame bit set, all its
+    // parents (that can be cached) ought to have it set too.
+    DebugOnly<bool> seenCached = false;
+
     while (!iter.done()) {
         Activation& activation = *iter.activation();
+        Maybe<LiveSavedFrameCache::FramePtr> framePtr = LiveSavedFrameCache::FramePtr::create(iter);
 
-        if (asyncActivation && asyncActivation != &activation) {
-            // We found an async stack in the previous activation, and we
-            // walked past the oldest frame of that activation, we're done.
-            // However, we only want to use the async parent if it was
-            // explicitly requested; if we got here otherwise, we have
-            // a direct parent, which we prefer.
-            if (asyncActivation->asyncCallIsExplicit())
-                break;
-            asyncActivation = nullptr;
+        if (framePtr) {
+            MOZ_ASSERT_IF(seenCached, framePtr->hasCachedSavedFrame());
+            seenCached |= framePtr->hasCachedSavedFrame();
         }
 
-        if (!asyncActivation) {
-            asyncStack = activation.asyncStack();
-            if (asyncStack) {
-                // While walking from the youngest to the oldest frame, we found
-                // an activation that has an async stack set. We will use the
-                // youngest frame of the async stack as the parent of the oldest
-                // frame of this activation. We still need to iterate over other
-                // frames in this activation before reaching the oldest frame.
-                AutoCompartmentUnchecked ac(cx, iter.compartment());
-                const char* cause = activation.asyncCause();
-                UTF8Chars utf8Chars(cause, strlen(cause));
-                size_t twoByteCharsLen = 0;
-                char16_t* twoByteChars = UTF8CharsToNewTwoByteCharsZ(cx, utf8Chars,
-                                                                     &twoByteCharsLen).get();
-                if (!twoByteChars)
-                    return false;
+        if (capture.is<JS::AllFrames>() && framePtr && framePtr->hasCachedSavedFrame())
+        {
+            auto* cache = activation.getLiveSavedFrameCache(cx);
+            if (!cache)
+                return false;
+            cache->find(cx, *framePtr, iter.pc(), &parent);
 
-                // We expect that there will be a relatively small set of
-                // asyncCause reasons ("setTimeout", "promise", etc.), so we
-                // atomize the cause here in hopes of being able to benefit
-                // from reuse.
-                asyncCause = JS_AtomizeUCStringN(cx, twoByteChars, twoByteCharsLen);
-                js_free(twoByteChars);
-                if (!asyncCause)
-                    return false;
-                asyncActivation = &activation;
-            }
+            // Even though iter.hasCachedSavedFrame() was true, we can't
+            // necessarily stop walking the stack here. We can get cache misses
+            // for two reasons:
+            // 1) This is the youngest valid frame in the cache, and it has run
+            //    code and advanced to a new pc since it was cached.
+            // 2) The cache was populated with SavedFrames captured for a
+            //    different compartment, and got purged completely. We will
+            //    repopulate it from scratch.
+            if (parent)
+                break;
         }
 
+        // We'll be pushing this frame onto stackChain. Gather the information
+        // needed to construct the SavedFrame::Lookup.
         Rooted<LocationValue> location(cx);
         {
             AutoCompartmentUnchecked ac(cx, iter.compartment());
             if (!cx->compartment()->savedStacks().getLocation(cx, iter, &location))
                 return false;
         }
-
-        // The bit set means that the next older parent (frame, pc) pair *must*
-        // be in the cache.
-        if (capture.is<JS::AllFrames>())
-            parentIsInCache = framePtr && framePtr->hasCachedSavedFrame();
-
+        auto displayAtom = (iter.isWasm() || iter.isFunctionFrame()) ? iter.functionDisplayAtom() : nullptr;
         auto principals = iter.compartment()->principals();
-        auto displayAtom = (iter.isWasm() || iter.isFunctionFrame()) ? iter.functionDisplayAtom() : nullptr;
-
         MOZ_ASSERT_IF(framePtr && !iter.isWasm(), iter.pc());
 
         if (!stackChain->emplaceBack(location.source(),
                                      location.line(),
                                      location.column(),
                                      displayAtom,
-                                     nullptr,
-                                     nullptr,
+                                     nullptr, // asyncCause
+                                     nullptr, // parent (not known yet)
                                      principals,
                                      framePtr,
                                      iter.pc(),
                                      &activation))
         {
             ReportOutOfMemory(cx);
             return false;
         }
 
         if (captureIsSatisfied(cx, principals, location.source(), capture)) {
-            // The frame we just saved was the last one we were asked to save.
-            // If we had an async stack, ensure we don't use any of its frames.
-            asyncStack.set(nullptr);
+            // The stack should end after the frame we just saved.
+            parent.set(nullptr);
             break;
         }
 
         ++iter;
         framePtr = LiveSavedFrameCache::FramePtr::create(iter);
 
-        if (parentIsInCache &&
-            framePtr &&
-            framePtr->hasCachedSavedFrame())
+        if (iter.activation() != &activation && capture.is<JS::AllFrames>()) {
+            // If there were no cache hits in the entire activation, clear its
+            // cache so we'll be able to push new ones when we build the
+            // SavedFrame chain.
+            activation.clearLiveSavedFrameCache();
+        }
+
+        // If we have crossed into a new activation, check whether the prior
+        // activation had an async parent set.
+        //
+        // If the async call was explicit (async function resumptions, most
+        // testing facilities), then the async parent stack has priority over
+        // any actual frames still on the JavaScript stack. If the async call
+        // was implicit (DOM CallbackObject::CallSetup calls), then the async
+        // parent stack is used only if there were no other frames on the
+        // stack.
+        //
+        // Captures using FirstSubsumedFrame expect us to ignore async parents.
+        if (iter.activation() != &activation &&
+            activation.asyncStack() &&
+            (activation.asyncCallIsExplicit() || iter.done()) &&
+            !capture.is<JS::FirstSubsumedFrame>())
         {
-            auto* cache = activation.getLiveSavedFrameCache(cx);
-            if (!cache)
+            // Atomize the async cause string. There should only be a few
+            // different strings used.
+            const char* cause = activation.asyncCause();
+            RootedAtom causeAtom(cx, AtomizeUTF8Chars(cx, cause, strlen(cause)));
+            if (!causeAtom)
                 return false;
-            cache->find(cx, *framePtr, iter.pc(), &cachedFrame);
-            if (cachedFrame)
-                break;
+
+            // Translate our capture into a frame count limit for
+            // adoptAsyncStack, which will impose further limits.
+            Maybe<size_t> maxFrames =
+                !capture.is<JS::MaxFrames>() ? Nothing()
+                : capture.as<JS::MaxFrames>().maxFrames == 0 ? Nothing()
+                : Some(capture.as<JS::MaxFrames>().maxFrames);
+
+            // Clip the stack if needed, attach the async cause string to the
+            // top frame, and copy it into our compartment if necessary.
+            parent.set(activation.asyncStack());
+            if (!adoptAsyncStack(cx, &parent, causeAtom, maxFrames))
+                return false;
+            break;
         }
 
         if (capture.is<JS::MaxFrames>())
             capture.as<JS::MaxFrames>().maxFrames--;
     }
 
-    // Limit the depth of the async stack, if any, and ensure that the
-    // SavedFrame instances we use are stored in the same compartment as the
-    // rest of the synchronous stack chain.
-    RootedSavedFrame parentFrame(cx, cachedFrame);
-    if (asyncStack && !capture.is<JS::FirstSubsumedFrame>()) {
-        uint32_t maxAsyncFrames = capture.is<JS::MaxFrames>()
-            ? capture.as<JS::MaxFrames>().maxFrames
-            : ASYNC_STACK_MAX_FRAME_COUNT;
-        if (!adoptAsyncStack(cx, asyncStack, asyncCause, &parentFrame, maxAsyncFrames))
-            return false;
-    }
-
     // Iterate through |stackChain| in reverse order and get or create the
     // actual SavedFrame instances.
+    frame.set(parent);
     for (size_t i = stackChain->length(); i != 0; i--) {
         SavedFrame::HandleLookup lookup = stackChain[i-1];
-        lookup->parent = parentFrame;
-        parentFrame.set(getOrCreateSavedFrame(cx, lookup));
-        if (!parentFrame)
+        lookup->parent = frame;
+        frame.set(getOrCreateSavedFrame(cx, lookup));
+        if (!frame)
             return false;
 
-        if (capture.is<JS::AllFrames>() && lookup->framePtr && parentFrame != cachedFrame) {
+        if (capture.is<JS::AllFrames>() && lookup->framePtr) {
             auto* cache = lookup->activation->getLiveSavedFrameCache(cx);
-            if (!cache || !cache->insert(cx, *lookup->framePtr, lookup->pc, parentFrame))
+            if (!cache || !cache->insert(cx, *lookup->framePtr, lookup->pc, frame))
                 return false;
         }
     }
 
-    frame.set(parentFrame);
     return true;
 }
 
 bool
-SavedStacks::adoptAsyncStack(JSContext* cx, HandleSavedFrame asyncStack,
-                             HandleString asyncCause,
-                             MutableHandleSavedFrame adoptedStack,
-                             uint32_t maxFrameCount)
+SavedStacks::adoptAsyncStack(JSContext* cx, MutableHandleSavedFrame asyncStack,
+                             HandleAtom asyncCause,
+                             const Maybe<size_t>& maxFrameCount)
 {
-    RootedAtom asyncCauseAtom(cx, AtomizeString(cx, asyncCause));
-    if (!asyncCauseAtom)
-        return false;
+    MOZ_ASSERT(asyncStack);
+    MOZ_ASSERT(asyncCause);
 
-    // If maxFrameCount is zero, the caller asked for an unlimited number of
+    // If maxFrameCount is Nothing, the caller asked for an unlimited number of
     // stack frames, but async stacks are not limited by the available stack
     // memory, so we need to set an arbitrary limit when collecting them. We
     // still don't enforce an upper limit if the caller requested more frames.
-    uint32_t maxFrames = maxFrameCount > 0 ? maxFrameCount : ASYNC_STACK_MAX_FRAME_COUNT;
+    size_t maxFrames = maxFrameCount.valueOr(ASYNC_STACK_MAX_FRAME_COUNT);
 
-    // Accumulate the vector of Lookup objects in |stackChain|.
+    // Turn the chain of frames starting with asyncStack into a vector of Lookup
+    // objects in |stackChain|, youngest to oldest.
     SavedFrame::AutoLookupVector stackChain(cx);
     SavedFrame* currentSavedFrame = asyncStack;
-    SavedFrame* firstSavedFrameParent = nullptr;
-    for (uint32_t i = 0; i < maxFrames && currentSavedFrame; i++) {
+    while (currentSavedFrame && stackChain->length() < maxFrames) {
         if (!stackChain->emplaceBack(*currentSavedFrame)) {
             ReportOutOfMemory(cx);
             return false;
         }
 
         currentSavedFrame = currentSavedFrame->getParent();
-
-        // Attach the asyncCause to the youngest frame.
-        if (i == 0) {
-            stackChain->back().asyncCause = asyncCauseAtom;
-            firstSavedFrameParent = currentSavedFrame;
-        }
     }
 
-    // This is the 1-based index of the oldest frame we care about.
-    size_t oldestFramePosition = stackChain->length();
-    RootedSavedFrame parentFrame(cx, nullptr);
+    // Attach the asyncCause to the youngest frame.
+    stackChain[0]->asyncCause = asyncCause;
 
+    // If we walked the entire stack, and it's in cx's compartment, we don't
+    // need to rebuild the full chain again using the lookup objects - we can
+    // just use the existing chain. Only the asyncCause on the youngest frame
+    // needs to be changed.
     if (currentSavedFrame == nullptr &&
-        asyncStack->compartment() == cx->compartment()) {
-        // If we consumed the full async stack, and the stack is in the same
-        // compartment as the one requested, we don't need to rebuild the full
-        // chain again using the lookup objects, we can just reference the
-        // existing chain and change the asyncCause on the younger frame.
-        oldestFramePosition = 1;
-        parentFrame = firstSavedFrameParent;
-    } else if (maxFrameCount == 0 &&
-               oldestFramePosition == ASYNC_STACK_MAX_FRAME_COUNT) {
-        // If we captured the maximum number of frames and the caller requested
-        // no specific limit, we only return half of them. This means that for
-        // the next iterations, it's likely we can use the optimization above.
-        oldestFramePosition = ASYNC_STACK_MAX_FRAME_COUNT / 2;
+        asyncStack->compartment() == cx->compartment())
+    {
+        SavedFrame::HandleLookup lookup = stackChain[0];
+        lookup->parent = asyncStack->getParent();
+        asyncStack.set(getOrCreateSavedFrame(cx, lookup));
+        return !!asyncStack;
     }
 
+    // If we captured the maximum number of frames and the caller requested no
+    // specific limit, we only return half of them. This means that if we do
+    // many subsequent captures with the same async stack, it's likely we can
+    // use the optimization above.
+    if (maxFrameCount.isNothing() && currentSavedFrame)
+        stackChain->shrinkBy(ASYNC_STACK_MAX_FRAME_COUNT / 2);
+
     // Iterate through |stackChain| in reverse order and get or create the
     // actual SavedFrame instances.
-    for (size_t i = oldestFramePosition; i != 0; i--) {
-        SavedFrame::HandleLookup lookup = stackChain[i-1];
-        lookup->parent = parentFrame;
-        parentFrame.set(getOrCreateSavedFrame(cx, lookup));
-        if (!parentFrame)
+    asyncStack.set(nullptr);
+    while (!stackChain->empty()) {
+        SavedFrame::HandleLookup lookup = stackChain.back();
+        lookup->parent = asyncStack;
+        asyncStack.set(getOrCreateSavedFrame(cx, lookup));
+        if (!asyncStack)
             return false;
+        stackChain->popBack();
     }
 
-    adoptedStack.set(parentFrame);
     return true;
 }
 
 SavedFrame*
 SavedStacks::getOrCreateSavedFrame(JSContext* cx, SavedFrame::HandleLookup lookup)
 {
     const SavedFrame::Lookup& lookupInstance = lookup.get();
     DependentAddPtr<SavedFrame::Set> p(cx, frames, lookupInstance);
--- a/js/src/vm/SavedStacks.h
+++ b/js/src/vm/SavedStacks.h
@@ -165,17 +165,17 @@ class SavedStacks {
 
     MOZ_MUST_USE bool init();
     bool initialized() const { return frames.initialized(); }
     MOZ_MUST_USE bool saveCurrentStack(JSContext* cx, MutableHandleSavedFrame frame,
                                        JS::StackCapture&& capture = JS::StackCapture(JS::AllFrames()));
     MOZ_MUST_USE bool copyAsyncStack(JSContext* cx, HandleObject asyncStack,
                                      HandleString asyncCause,
                                      MutableHandleSavedFrame adoptedStack,
-                                     uint32_t maxFrameCount = 0);
+                                     const Maybe<size_t>& maxFrameCount);
     void sweep();
     void trace(JSTracer* trc);
     uint32_t count();
     void clear();
     void chooseSamplingProbability(JSCompartment*);
 
     // Set the sampling random number generator's state to |state0| and
     // |state1|. One or the other must be non-zero. See the comments for
@@ -218,20 +218,19 @@ class SavedStacks {
         {
             stacks.creatingSavedFrame = false;
         }
     };
 
     MOZ_MUST_USE bool insertFrames(JSContext* cx, FrameIter& iter,
                                    MutableHandleSavedFrame frame,
                                    JS::StackCapture&& capture);
-    MOZ_MUST_USE bool adoptAsyncStack(JSContext* cx, HandleSavedFrame asyncStack,
-                                      HandleString asyncCause,
-                                      MutableHandleSavedFrame adoptedStack,
-                                      uint32_t maxFrameCount);
+    MOZ_MUST_USE bool adoptAsyncStack(JSContext* cx, MutableHandleSavedFrame asyncStack,
+                                      HandleAtom asyncCause,
+                                      const Maybe<size_t>& maxFrameCount);
     SavedFrame* getOrCreateSavedFrame(JSContext* cx, SavedFrame::HandleLookup lookup);
     SavedFrame* createFrameFromLookup(JSContext* cx, SavedFrame::HandleLookup lookup);
 
     // Cache for memoizing PCToLineNumber lookups.
 
     struct PCKey {
         PCKey(JSScript* script, jsbytecode* pc) : script(script), pc(pc) { }
 
--- a/js/src/vm/Stack-inl.h
+++ b/js/src/vm/Stack-inl.h
@@ -1004,17 +1004,17 @@ struct LiveSavedFrameCache::FramePtr::Ha
 
 inline bool
 LiveSavedFrameCache::FramePtr::hasCachedSavedFrame() const {
     return ptr.match(HasCachedMatcher());
 }
 
 struct LiveSavedFrameCache::FramePtr::SetHasCachedMatcher {
     template<typename Frame>
-    void match(Frame* f) const { f->setHasCachedSavedFrame(); }
+    void match(Frame* f) { f->setHasCachedSavedFrame(); }
 };
 
 inline void
 LiveSavedFrameCache::FramePtr::setHasCachedSavedFrame() {
     ptr.match(SetHasCachedMatcher());
 }
 
 } /* namespace js */
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -1098,78 +1098,149 @@ struct DefaultHasher<AbstractFramePtr> {
         return k == l;
     }
 };
 
 /*****************************************************************************/
 
 // SavedFrame caching to minimize stack walking.
 //
-// SavedFrames are hash consed to minimize expensive (with regards to both space
-// and time) allocations in the face of many stack frames that tend to share the
-// same older tail frames. Despite that, in scenarios where we are frequently
-// saving the same or similar stacks, such as when the Debugger's allocation
-// site tracking is enabled, these older stack frames still get walked
-// repeatedly just to create the lookup structs to find their corresponding
-// SavedFrames in the hash table. This stack walking is slow, and we would like
-// to minimize it.
+// Since each SavedFrame object includes a 'parent' pointer to the SavedFrame
+// for its caller, if we could easily find the right SavedFrame for a given
+// stack frame, we wouldn't need to walk the rest of the stack. Traversing deep
+// stacks can be expensive, and when we're profiling or instrumenting code, we
+// may want to capture JavaScript stacks frequently, so such cases would benefit
+// if we could avoid walking the entire stack.
+//
+// We could have a cache mapping frame addresses to their SavedFrame objects,
+// but invalidating its entries would be a challenge. Popping a stack frame is
+// extremely performance-sensitive, and SpiderMonkey stack frames can be OSR'd,
+// thrown, rematerialized, and perhaps meet other fates; we would rather our
+// cache not depend on handling so many tricky cases.
+//
+// It turns out that we can keep the cache accurate by reserving a single bit in
+// the stack frame, which must be clear on any newly pushed frame. When we
+// insert an entry into the cache mapping a given frame address to its
+// SavedFrame, we set the bit in the frame. Then, we take care to probe the
+// cache only for frames whose bit is set; the bit tells us that the frame has
+// never left the stack, so its cache entry must be accurate, at least about
+// which function the frame is executing (the line may have changed; more about
+// that below). The code refers to this bit as the 'hasCachedSavedFrame' flag.
+//
+// We could manage such a cache replacing least-recently used entries, but we
+// can do better than that: the cache can be a stack, of which we need examine
+// only entries from the top.
 //
-// We have reserved a bit on most of SpiderMonkey's various frame
-// representations (the exceptions being asm and inlined ion frames). As we
-// create SavedFrame objects for live stack frames in SavedStacks::insertFrames,
-// we set this bit and append the SavedFrame object to the cache. As we walk the
-// stack, if we encounter a frame that has this bit set, that indicates that we
-// have already captured a SavedFrame object for the given stack frame during a
-// previous call to insertFrames. Rather than continuing the expensive stack
-// walk, we do a quick and cache-friendly linear search through the frame cache.
-// Upon finishing the search, stale entries are removed.
+// First, observe that stacks are walked from the youngest frame to the oldest,
+// but SavedFrame chains are built from oldest to youngest, to ensure common
+// tails are shared. This means that capturing a stack is necessarily a
+// two-phase process: walk the stack, and then build the SavedFrames.
+//
+// Naturally, the first time we capture the stack, the cache is empty, and we
+// must traverse the entire stack. As we build each SavedFrame, we push an entry
+// associating the frame's address to its SavedFrame on the cache, and set the
+// frame's bit. At the end, every frame has its bit set and an entry in the
+// cache.
+//
+// Then the program runs some more. Some, none, or all of the frames are popped.
+// Any new frames are pushed with their bit clear. Any frame with its bit set
+// has never left the stack. The cache is left untouched.
 //
-// The frame cache maintains the invariant that its first E[0] .. E[j-1]
-// entries are live and sorted from oldest to younger frames, where 0 < j < n
-// and n = the length of the cache. When searching the cache, we require
-// that we are considering the youngest live frame whose bit is set. Every
-// cache entry E[i] where i >= j is a stale entry. Consider the following
-// scenario:
+// For the next capture, we walk the stack up to the first frame with its bit
+// set, if there is one. Call it F; it must have a cache entry. We pop entries
+// from the cache - all invalid, because they are above F's entry, and hence
+// younger - until we find the entry matching F's address. Since F's bit is set,
+// we know it never left the stack, and hence that no younger frame could have
+// had a colliding address. And since the frame's bit was set when we pushed the
+// cache entry, we know the entry is still valid.
+//
+// F's cache entry's SavedFrame covers the rest of the stack, so we don't need
+// to walk the stack any further. Now we begin building SavedFrame objects for
+// the new frames, pushing cache entries, and setting bits on the frames. By the
+// end, the cache again covers the full stack, and every frame's bit is set.
+//
+// If we walk the stack to the end, and find no frame with its bit set, then the
+// entire cache is invalid. At this point, it must be emptied, so that the new
+// entries we are about to push are the only frames in the cache.
+//
+// For example, suppose we have the following stack (let 'A > B' mean "A called
+// B", so the frames are listed oldest first):
 //
-//     P  >  Q  >  R  >  S          Initial stack, bits not set.
-//     P* >  Q* >  R* >  S*         Capture a SavedFrame stack, set bits.
-//     P* >  Q* >  R*               Return from S.
-//     P* >  Q*                     Return from R.
-//     P* >  Q* >  T                Call T, its bit is not set.
+//     P  > Q  > R  > S          Initial stack, bits not set.
+//     P* > Q* > R* > S*         Capture a SavedFrame stack, set bits.
+//                               The cache now holds: P > Q > R > S.
+//     P* > Q* > R*              Return from S.
+//     P* > Q*                   Return from R.
+//     P* > Q* > T  > U          Call T and U. New frames have clear bits.
+//
+// If we capture the stack now, the cache still holds:
+//
+//     P  > Q  > R  > S
+//
+// As we traverse the stack, we'll cross U and T, and then find Q with its bit
+// set. We pop entries from the cache until we find the entry for Q; this
+// removes entries R and S, which were indeed invalid. In Q's cache entry, we
+// find the SavedFrame representing the stack P > Q. Now we build SavedFrames
+// for the new portion of the stack, pushing an entry for T and setting the bit
+// on the frame, and then doing the same for U. In the end, the call stack again
+// has bits set on all its frames:
+//
+//     P* > Q* > T* > U*         All frames are now in the cache.
+//
+// And the cache again holds entries for the entire stack:
+//
+//     P  > Q  > T  > U
+//
+// Some details:
 //
-// The frame cache was populated with [P, Q, R, S] when we captured a
-// SavedFrame stack, but because we returned from frames R and S, their
-// entries in the frame cache are now stale. This fact is unbeknownst to us
-// because we do not observe frame pops. Upon capturing a second stack, we
-// start stack walking at the youngest frame T, which does not have its bit
-// set and must take the hash table lookup slow path rather than the frame
-// cache short circuit. Next we proceed to Q and find that it has its bit
-// set, and it is therefore the youngest live frame with its bit set. We
-// search through the frame cache from oldest to youngest and find the cache
-// entry matching Q. We know that T is the next younger live frame from Q
-// and that T does not have an entry in the frame cache because its bit was
-// not set. Therefore, we have found entry E[j-1] and the subsequent entries
-// are stale and should be purged from the frame cache.
+// - When we find a cache entry whose frame address matches our frame F, we know
+//   that F has never left the stack, but it may certainly be the case that
+//   execution took place in that frame, and that the current source position
+//   within F's function has changed. This means that the entry's SavedFrame,
+//   which records the source line and column as well as the function, is not
+//   correct. To detect this case, when we push a cache entry, we record the
+//   frame's pc. When consulting the cache, if a frame's address matches but its
+//   pc does not, then we pop the cache entry and continue walking the stack.
+//   The next stack frame will definitely hit: since its callee frame never left
+//   the stack, the calling frame never got the chance to execute.
+//
+// - Generators, at least conceptually, have long-lived stack frames that
+//   disappear from the stack when the generator yields, and reappear on the
+//   stack when the generator's 'next' method is called. When a generator's
+//   frame is placed again atop the stack, its bit must be cleared - for the
+//   purposes of the cache, treating the frame as a new frame - to respect the
+//   invariants we used to justify the algorithm above. Async function
+//   activations usually appear atop empty stacks, since they are invoked as a
+//   promise callback, but the same rule applies.
 //
-// Note that the youngest frame with a valid entry may have run some code and
-// advanced to a different pc. Each cache entry records the pc for which its
-// SavedFrame is appropriate, and a pc mismatch causes the entry to be purged.
+// - SpiderMonkey has many types of stack frames, and not all have a place to
+//   store a bit indicating a cached SavedFrame. But as long as we don't create
+//   cache entries for frames we can't mark, simply omitting them from the cache
+//   is harmless. Uncacheable frame types include inlined Ion frames and
+//   non-Debug wasm frames. The LiveSavedFrameCache::FramePtr type represents
+//   only pointers to frames that can be cached, so if you have a FramePtr, you
+//   don't need to further check the frame for cachability. FramePtr provides
+//   access to the hasCachedSavedFrame bit.
+//
+// - We actually break up the cache into one cache per Activation. Popping an
+//   activation invalidates all its cache entries, simply by freeing the cache
+//   altogether.
 //
-// We have a LiveSavedFrameCache for each activation to minimize the number of
-// entries that must be scanned through, and to avoid the headaches of
-// maintaining a cache for each compartment and invalidating stale cache entries
-// in the presence of cross-compartment calls.
-//
-// The entire chain of SavedFrames for a given stack capture is created in the
-// compartment of the code that requested the capture, *not* in that of the
-// frames it represents, so in general, different compartments may have
-// different SavedFrame objects representing the same actual stack frame. The
-// LiveSavedFrameCache simply records whichever SavedFrames were created most
-// recently, so if there's a compartment mismatch, we throw away the whole
-// cache.
+// - The entire chain of SavedFrames for a given stack capture is created in the
+//   compartment of the code that requested the capture, *not* in that of the
+//   frames it represents, so in general, different compartments may have
+//   different SavedFrame objects representing the same actual stack frame. The
+//   LiveSavedFrameCache simply records whichever SavedFrames were created most
+//   recently. When we find a cache hit, we check the entry's SavedFrame's
+//   compartment against the current compartment; if they do not match, we flush
+//   the entire cache. This means that it is not always true that, if a frame's
+//   bit it set, it must have an entry in the cache. But we can still assert
+//   that, if a frame's bit is set and the cache is not completely empty, the
+//   frame will have an entry. When the cache is flushed, it will be repopulated
+//   immediately with the new capture's frames.
 class LiveSavedFrameCache
 {
   public:
     // The address of a live frame for which we can cache SavedFrames: it has a
     // 'hasCachedSavedFrame' bit we can examine and set, and can be converted to
     // a Key to index the cache.
     class FramePtr {
         // We use jit::CommonFrameLayout for both Baseline frames and Ion
@@ -1183,16 +1254,18 @@ class LiveSavedFrameCache
 
         template<typename Frame>
         explicit FramePtr(Frame ptr) : ptr(ptr) { }
 
         struct HasCachedMatcher;
         struct SetHasCachedMatcher;
 
       public:
+        // If iter's frame is of a type that can be cached, construct a FramePtr
+        // for its frame. Otherwise, return Nothing.
         static inline mozilla::Maybe<FramePtr> create(const FrameIter& iter);
 
         inline bool hasCachedSavedFrame() const;
         inline void setHasCachedSavedFrame();
 
         bool operator==(const FramePtr& rhs) const { return rhs.ptr == this->ptr; }
         bool operator!=(const FramePtr& rhs) const { return !(rhs == *this); }
     };
@@ -1254,20 +1327,41 @@ class LiveSavedFrameCache
             JS_ReportOutOfMemory(cx);
             return false;
         }
         return true;
     }
 
     void trace(JSTracer* trc);
 
-    void find(JSContext* cx, FramePtr& frameptr, const jsbytecode* pc,
+    // Set |frame| to the cached SavedFrame corresponding to |framePtr| at |pc|.
+    // |framePtr|'s hasCachedSavedFrame bit must be set. Remove all cache
+    // entries for frames younger than that one.
+    //
+    // This may set |frame| to nullptr if |pc| is different from the pc supplied
+    // when the cache entry was inserted. In this case, the cached SavedFrame
+    // (probably) has the wrong source position. Entries for younger frames are
+    // still removed. The next frame, if any, will be a cache hit.
+    //
+    // This may also set |frame| to nullptr if the cache was populated with
+    // SavedFrame objects for a different compartment than cx's current
+    // compartment. In this case, the entire cache is flushed.
+    void find(JSContext* cx, FramePtr& framePtr, const jsbytecode* pc,
               MutableHandleSavedFrame frame) const;
+
+    // Push a cache entry mapping |framePtr| and |pc| to |savedFrame| on the top
+    // of the cache's stack. You must insert entries for frames from oldest to
+    // youngest. They must all be younger than the frame that the |find| method
+    // found a hit for; or you must have cleared the entire cache with the
+    // |clear| method.
     bool insert(JSContext* cx, FramePtr& framePtr, const jsbytecode* pc,
                 HandleSavedFrame savedFrame);
+
+    // Remove all entries from the cache.
+    void clear() { if (frames) frames->clear(); }
 };
 
 static_assert(sizeof(LiveSavedFrameCache) == sizeof(uintptr_t),
               "Every js::Activation has a LiveSavedFrameCache, so we need to be pretty careful "
               "about avoiding bloat. If you're adding members to LiveSavedFrameCache, maybe you "
               "should consider figuring out a way to make js::Activation have a "
               "LiveSavedFrameCache* instead of a Rooted<LiveSavedFrameCache>.");
 
@@ -1403,16 +1497,17 @@ class Activation
         return asyncCause_;
     }
 
     bool asyncCallIsExplicit() const {
         return asyncCallIsExplicit_;
     }
 
     inline LiveSavedFrameCache* getLiveSavedFrameCache(JSContext* cx);
+    void clearLiveSavedFrameCache() { frameCache_.get().clear(); }
 
   private:
     Activation(const Activation& other) = delete;
     void operator=(const Activation& other) = delete;
 };
 
 // This variable holds a special opcode value which is greater than all normal
 // opcodes, and is chosen such that the bitwise or of this value with any
--- a/js/xpconnect/src/XPCJSContext.cpp
+++ b/js/xpconnect/src/XPCJSContext.cpp
@@ -808,16 +808,17 @@ ReloadPrefsCallback(const char* pref, vo
     bool streams = Preferences::GetBool(JS_OPTIONS_DOT_STR "streams");
 
     bool spectreIndexMasking = Preferences::GetBool(JS_OPTIONS_DOT_STR "spectre.index_masking");
     bool spectreObjectMitigationsBarriers =
         Preferences::GetBool(JS_OPTIONS_DOT_STR "spectre.object_mitigations.barriers");
     bool spectreStringMitigations =
         Preferences::GetBool(JS_OPTIONS_DOT_STR "spectre.string_mitigations");
     bool spectreValueMasking = Preferences::GetBool(JS_OPTIONS_DOT_STR "spectre.value_masking");
+    bool spectreJitToCxxCalls = Preferences::GetBool(JS_OPTIONS_DOT_STR "spectre.jit_to_C++_calls");
 
     sSharedMemoryEnabled = Preferences::GetBool(JS_OPTIONS_DOT_STR "shared_memory");
 
 #ifdef DEBUG
     sExtraWarningsForSystemJS = Preferences::GetBool(JS_OPTIONS_DOT_STR "strict.debug");
 #endif
 
 #ifdef JS_GC_ZEAL
@@ -875,16 +876,18 @@ ReloadPrefsCallback(const char* pref, vo
 #endif
 
     JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_SPECTRE_INDEX_MASKING, spectreIndexMasking);
     JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_SPECTRE_OBJECT_MITIGATIONS_BARRIERS,
                                   spectreObjectMitigationsBarriers);
     JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_SPECTRE_STRING_MITIGATIONS,
                                   spectreStringMitigations);
     JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_SPECTRE_VALUE_MASKING, spectreValueMasking);
+    JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_SPECTRE_JIT_TO_CXX_CALLS,
+                                  spectreJitToCxxCalls);
 }
 
 XPCJSContext::~XPCJSContext()
 {
     MOZ_COUNT_DTOR_INHERITED(XPCJSContext, CycleCollectedJSContext);
     // Elsewhere we abort immediately if XPCJSContext initialization fails.
     // Therefore the context must be non-null.
     MOZ_ASSERT(MaybeContext());
--- a/js/xpconnect/src/XPCShellImpl.cpp
+++ b/js/xpconnect/src/XPCShellImpl.cpp
@@ -36,17 +36,19 @@
 #include "nsIXULRuntime.h"
 #include "GeckoProfiler.h"
 
 #ifdef ANDROID
 #include <android/log.h>
 #endif
 
 #ifdef XP_WIN
+#include "mozilla/ScopeExit.h"
 #include "mozilla/widget/AudioSession.h"
+#include "mozilla/WinDllServices.h"
 #include <windows.h>
 #if defined(MOZ_SANDBOX)
 #include "sandboxBroker.h"
 #endif
 #endif
 
 #ifdef MOZ_CODE_COVERAGE
 #include "mozilla/CodeCoverageHandler.h"
@@ -1298,16 +1300,22 @@ XRE_XPCShellMain(int argc, char** argv, 
         gfxPrefs::GetSingleton();
         // Initialize e10s check on the main thread, if not already done
         BrowserTabsRemoteAutostart();
 #ifdef XP_WIN
         // Plugin may require audio session if installed plugin can initialize
         // asynchronized.
         AutoAudioSession audioSession;
 
+        // Ensure that DLL Services are running
+        RefPtr<DllServices> dllSvc(DllServices::Get());
+        auto dllServicesDisable = MakeScopeExit([&dllSvc]() {
+          dllSvc->Disable();
+        });
+
 #if defined(MOZ_SANDBOX)
         // Required for sandboxed child processes.
         if (aShellData->sandboxBrokerServices) {
           SandboxBroker::Initialize(aShellData->sandboxBrokerServices);
           SandboxBroker::GeckoDependentInitialize();
         } else {
           NS_WARNING("Failed to initialize broker services, sandboxed "
                      "processes will fail to start.");
new file mode 100644
--- /dev/null
+++ b/mobile/android/chrome/geckoview/ErrorPageEventHandler.js
@@ -0,0 +1,56 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+ChromeUtils.defineModuleGetter(this, "SSLExceptions",
+                               "resource://gre/modules/SSLExceptions.jsm");
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+  Services: "resource://gre/modules/Services.jsm",
+});
+
+var EXPORTED_SYMBOLS = ["ErrorPageEventHandler"];
+
+var ErrorPageEventHandler = {
+  handleEvent: function(aEvent) {
+    switch (aEvent.type) {
+      case "click": {
+        // Don't trust synthetic events
+        if (!aEvent.isTrusted)
+          return;
+
+        let target = aEvent.originalTarget;
+        let errorDoc = target.ownerDocument;
+
+        // If the event came from an ssl error page, it is probably either the "Add
+        // Exception…" or "Get me out of here!" button
+        if (errorDoc.documentURI.startsWith("about:certerror?e=nssBadCert")) {
+          let perm = errorDoc.getElementById("permanentExceptionButton");
+          let temp = errorDoc.getElementById("temporaryExceptionButton");
+          if (target == temp || target == perm) {
+            // Handle setting a cert exception and reloading the page
+            try {
+              // Add a new SSL exception for this URL
+              let uri = Services.io.newURI(errorDoc.location.href);
+              let sslExceptions = new SSLExceptions();
+
+              if (target == perm)
+                sslExceptions.addPermanentException(uri, errorDoc.defaultView);
+              else
+                sslExceptions.addTemporaryException(uri, errorDoc.defaultView);
+            } catch (e) {
+              dump("Failed to set cert exception: " + e + "\n");
+            }
+            errorDoc.location.reload();
+          } else if (target == errorDoc.getElementById("getMeOutOfHereButton")) {
+            errorDoc.location = "about:home";
+          }
+        }
+        break;
+      }
+    }
+  }
+};
new file mode 100644
--- /dev/null
+++ b/mobile/android/chrome/geckoview/GeckoViewContentSettings.js
@@ -0,0 +1,60 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+ChromeUtils.import("resource://gre/modules/GeckoViewContentModule.jsm");
+ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "dump", () =>
+    ChromeUtils.import("resource://gre/modules/AndroidLog.jsm",
+                       {}).AndroidLog.d.bind(null, "ViewSettings[C]"));
+
+function debug(aMsg) {
+  // dump(aMsg);
+}
+
+// Handles GeckoView content settings including:
+// * tracking protection
+// * desktop mode
+class GeckoViewContentSettings extends GeckoViewContentModule {
+  init() {
+    debug("init");
+    this._useTrackingProtection = false;
+    this._useDesktopMode = false;
+  }
+
+  onSettingsUpdate() {
+    debug("onSettingsUpdate");
+
+    this.useTrackingProtection = !!this.settings.useTrackingProtection;
+    this.useDesktopMode = !!this.settings.useDesktopMode;
+  }
+
+  get useTrackingProtection() {
+    return this._useTrackingProtection;
+  }
+
+  set useTrackingProtection(aUse) {
+    if (aUse != this._useTrackingProtection) {
+      docShell.useTrackingProtection = aUse;
+      this._useTrackingProtection = aUse;
+    }
+  }
+
+  get useDesktopMode() {
+    return this._useDesktopMode;
+  }
+
+  set useDesktopMode(aUse) {
+    if (this.useDesktopMode === aUse) {
+      return;
+    }
+    let utils = content.QueryInterface(Ci.nsIInterfaceRequestor)
+                .getInterface(Ci.nsIDOMWindowUtils);
+    utils.setDesktopModeViewport(aUse);
+    this._useDesktopMode = aUse;
+  }
+}
+
+var settings = new GeckoViewContentSettings("GeckoViewSettings", this);
--- a/mobile/android/chrome/geckoview/GeckoViewNavigationContent.js
+++ b/mobile/android/chrome/geckoview/GeckoViewNavigationContent.js
@@ -2,22 +2,23 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 ChromeUtils.import("resource://gre/modules/GeckoViewContentModule.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetters(this, {
-  Services: "resource://gre/modules/Services.jsm",
+  ErrorPageEventHandler: "chrome://geckoview/content/ErrorPageEventHandler.js",
+  LoadURIDelegate: "resource://gre/modules/LoadURIDelegate.jsm",
 });
 
 XPCOMUtils.defineLazyGetter(this, "dump", () =>
-    ChromeUtils.import("resource://gre/modules/AndroidLog.jsm",
-                       {}).AndroidLog.d.bind(null, "ViewNavigationContent"));
+  ChromeUtils.import("resource://gre/modules/AndroidLog.jsm",
+                     {}).AndroidLog.d.bind(null, "ViewNavigation[C]"));
 
 function debug(aMsg) {
   // dump(aMsg);
 }
 
 class GeckoViewNavigationContent extends GeckoViewContentModule {
   register() {
     debug("register");
@@ -30,31 +31,19 @@ class GeckoViewNavigationContent extends
 
     docShell.loadURIDelegate = null;
   }
 
   // nsILoadURIDelegate.
   loadURI(aUri, aWhere, aFlags, aTriggeringPrincipal) {
     debug("loadURI " + (aUri && aUri.spec) + " " + aWhere + " " + aFlags);
 
-    let message = {
-      type: "GeckoView:OnLoadUri",
-      uri: aUri ? aUri.displaySpec : "",
-      where: aWhere,
-      flags: aFlags
-    };
-
-    debug("dispatch " + JSON.stringify(message));
+    // TODO: Remove this when we have a sensible error API.
+    if (aUri && aUri.displaySpec.startsWith("about:certerror")) {
+      addEventListener("click", ErrorPageEventHandler, true);
+    }
 
-    let handled = undefined;
-    this.eventDispatcher.sendRequestForResult(message).then(response => {
-      handled = response;
-    }, () => {
-      // There was an error or listener was not registered in GeckoSession, treat as unhandled.
-      handled = false;
-    });
-    Services.tm.spinEventLoopUntil(() => handled !== undefined);
-
-    return handled;
+    return LoadURIDelegate.load(this.eventDispatcher, aUri, aWhere, aFlags,
+                                aTriggeringPrincipal);
   }
 }
 
 var navigationListener = new GeckoViewNavigationContent("GeckoViewNavigation", this);
--- a/mobile/android/chrome/geckoview/jar.mn
+++ b/mobile/android/chrome/geckoview/jar.mn
@@ -1,12 +1,14 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 geckoview.jar:
 % content geckoview %content/
 
+  content/ErrorPageEventHandler.js
   content/geckoview.xul
   content/geckoview.js
   content/GeckoViewContent.js
+  content/GeckoViewContentSettings.js
   content/GeckoViewNavigationContent.js
   content/GeckoViewScrollContent.js
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
@@ -128,17 +128,17 @@ public class GeckoSession extends LayerS
             }
         };
 
     private final GeckoSessionHandler<NavigationDelegate> mNavigationHandler =
         new GeckoSessionHandler<NavigationDelegate>(
             "GeckoViewNavigation", this,
             new String[]{
                 "GeckoView:LocationChange",
-                "GeckoView:OnLoadUri",
+                "GeckoView:OnLoadRequest",
                 "GeckoView:OnNewSession"
             }
         ) {
             // This needs to match nsIBrowserDOMWindow.idl
             private int convertGeckoTarget(int geckoTarget) {
                 switch (geckoTarget) {
                     case 0: // OPEN_DEFAULTWINDOW
                     case 1: // OPEN_CURRENTWINDOW
@@ -155,17 +155,17 @@ public class GeckoSession extends LayerS
                                       final EventCallback callback) {
                 if ("GeckoView:LocationChange".equals(event)) {
                     delegate.onLocationChange(GeckoSession.this,
                                               message.getString("uri"));
                     delegate.onCanGoBack(GeckoSession.this,
                                          message.getBoolean("canGoBack"));
                     delegate.onCanGoForward(GeckoSession.this,
                                             message.getBoolean("canGoForward"));
-                } else if ("GeckoView:OnLoadUri".equals(event)) {
+                } else if ("GeckoView:OnLoadRequest".equals(event)) {
                     final String uri = message.getString("uri");
                     final int where = convertGeckoTarget(message.getInt("where"));
                     final boolean result =
                         delegate.onLoadRequest(GeckoSession.this, uri, where);
                     callback.sendSuccess(result);
                 } else if ("GeckoView:OnNewSession".equals(event)) {
                     final String uri = message.getString("uri");
                     delegate.onNewSession(GeckoSession.this, uri,
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSessionSettings.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSessionSettings.java
@@ -72,16 +72,22 @@ public final class GeckoSessionSettings 
     /*
      * Key to enable and disable multiprocess browsing (e10s).
      * Read-only once session is open.
      */
     public static final Key<Boolean> USE_MULTIPROCESS =
         new Key<Boolean>("useMultiprocess", /* initOnly */ true, /* values */ null);
 
     /*
+     * Key to enable and disable desktop mode browsing.
+     */
+    public static final Key<Boolean> USE_DESKTOP_MODE =
+        new Key<Boolean>("useDesktopMode");
+
+    /*
      * Key to specify which display-mode we should use
      */
     public static final Key<Integer> DISPLAY_MODE =
         new Key<Integer>("displayMode", /* initOnly */ false,
                          Arrays.asList(DISPLAY_MODE_BROWSER, DISPLAY_MODE_MINIMAL_UI,
                                        DISPLAY_MODE_STANDALONE, DISPLAY_MODE_FULLSCREEN));
 
     public static final Key<Boolean> USE_REMOTE_DEBUGGER =
@@ -108,16 +114,17 @@ public final class GeckoSessionSettings 
         }
 
         mBundle = new GeckoBundle();
         mBundle.putString(CHROME_URI.name, null);
         mBundle.putInt(SCREEN_ID.name, 0);
         mBundle.putBoolean(USE_TRACKING_PROTECTION.name, false);
         mBundle.putBoolean(USE_PRIVATE_MODE.name, false);
         mBundle.putBoolean(USE_MULTIPROCESS.name, true);
+        mBundle.putBoolean(USE_DESKTOP_MODE.name, false);
         mBundle.putInt(DISPLAY_MODE.name, DISPLAY_MODE_BROWSER);
         mBundle.putBoolean(USE_REMOTE_DEBUGGER.name, false);
     }
 
     public void setBoolean(final Key<Boolean> key, final boolean value) {
         synchronized (mBundle) {
             if (valueChangedLocked(key, value)) {
                 mBundle.putBoolean(key.name, value);
--- a/mobile/android/modules/geckoview/GeckoViewContentModule.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewContentModule.jsm
@@ -21,19 +21,27 @@ XPCOMUtils.defineLazyGetter(this, "dump"
 
 class GeckoViewContentModule {
   constructor(aModuleName, aMessageManager) {
     this.moduleName = aModuleName;
     this.messageManager = aMessageManager;
     this.eventDispatcher = EventDispatcher.forMessageManager(aMessageManager);
 
     this.messageManager.addMessageListener(
+      "GeckoView:UpdateSettings",
+      aMsg => {
+        this.settings = aMsg.data;
+        this.onSettingsUpdate();
+      }
+    );
+    this.messageManager.addMessageListener(
       "GeckoView:Register",
       aMsg => {
         if (aMsg.data.module == this.moduleName) {
+          this.settings = aMsg.data.settings;
           this.register();
         }
       }
     );
     this.messageManager.addMessageListener(
       "GeckoView:Unregister",
       aMsg => {
         if (aMsg.data.module == this.moduleName) {
@@ -43,9 +51,10 @@ class GeckoViewContentModule {
     );
 
     this.init();
   }
 
   init() {}
   register() {}
   unregister() {}
+  onSettingsUpdate() {}
 }
--- a/mobile/android/modules/geckoview/GeckoViewModule.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewModule.jsm
@@ -20,23 +20,28 @@ class GeckoViewModule {
   constructor(aModuleName, aWindow, aBrowser, aEventDispatcher) {
     this.isRegistered = false;
     this.window = aWindow;
     this.browser = aBrowser;
     this.eventDispatcher = aEventDispatcher;
     this.moduleName = aModuleName;
 
     this.eventDispatcher.registerListener(
-      () => this.onSettingsUpdate(), "GeckoView:UpdateSettings"
+      (aEvent, aData, aCallback) => {
+        this.messageManager.sendAsyncMessage("GeckoView:UpdateSettings",
+                                             this.settings);
+        this.onSettingsUpdate();
+      }, "GeckoView:UpdateSettings"
     );
 
     this.eventDispatcher.registerListener(
       (aEvent, aData, aCallback) => {
         if (aData.module == this.moduleName) {
           this._register();
+          aData.settings = this.settings;
           this.messageManager.sendAsyncMessage("GeckoView:Register", aData);
         }
       }, "GeckoView:Register"
     );
 
     this.eventDispatcher.registerListener(
       (aEvent, aData, aCallback) => {
         if (aData.module == this.moduleName) {
--- a/mobile/android/modules/geckoview/GeckoViewNavigation.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewNavigation.jsm
@@ -4,20 +4,21 @@
 
 "use strict";
 
 var EXPORTED_SYMBOLS = ["GeckoViewNavigation"];
 
 ChromeUtils.import("resource://gre/modules/GeckoViewModule.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
-ChromeUtils.defineModuleGetter(this, "EventDispatcher",
-  "resource://gre/modules/Messaging.jsm");
-ChromeUtils.defineModuleGetter(this, "Services",
-  "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetters(this, {
+  EventDispatcher: "resource://gre/modules/Messaging.jsm",
+  LoadURIDelegate: "resource://gre/modules/LoadURIDelegate.jsm",
+  Services: "resource://gre/modules/Services.jsm",
+});
 
 XPCOMUtils.defineLazyGetter(this, "dump", () =>
     ChromeUtils.import("resource://gre/modules/AndroidLog.jsm",
                        {}).AndroidLog.d.bind(null, "ViewNavigation"));
 
 function debug(aMsg) {
   // dump(aMsg);
 }
@@ -147,26 +148,50 @@ class GeckoViewNavigation extends GeckoV
     const browser = this.handleNewSession(aUri, null, aWhere, aFlags, null);
     if (browser) {
       browser.setAttribute("nextTabParentId", aNextTabParentId);
     }
 
     return browser;
   }
 
+  handleOpenUri(aUri, aOpener, aWhere, aFlags, aTriggeringPrincipal,
+                aNextTabParentId) {
+    let browser = this.browser;
+    if (LoadURIDelegate.load(this.eventDispatcher, aUri, aWhere, aFlags,
+                             aTriggeringPrincipal)) {
+      return browser;
+    }
+
+    if (aWhere === Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW ||
+        aWhere === Ci.nsIBrowserDOMWindow.OPEN_NEWTAB ||
+        aWhere === Ci.nsIBrowserDOMWindow.OPEN_SWITCHTAB) {
+      browser = this.handleNewSession(aUri, aOpener, aWhere, aFlags,
+                                      aTriggeringPrincipal);
+    }
+    if (!browser) {
+      // Should we throw?
+      return null;
+    }
+    browser.loadURI(aUri.spec, null, null, null, null, aTriggeringPrincipal);
+    return browser;
+  }
+
   // nsIBrowserDOMWindow.
   openURI(aUri, aOpener, aWhere, aFlags, aTriggeringPrincipal) {
-    return this.createContentWindow(aUri, aOpener, aWhere, aFlags,
-                                    aTriggeringPrincipal);
+    const browser = this.handleOpenUri(aUri, aOpener, aWhere, aFlags,
+                                       aTriggeringPrincipal, null);
+    return browser && browser.contentWindow;
   }
 
   // nsIBrowserDOMWindow.
   openURIInFrame(aUri, aParams, aWhere, aFlags, aNextTabParentId, aName) {
-    return this.createContentWindowInFrame(aUri, aParams, aWhere, aFlags,
-                                           aNextTabParentId, aName);
+    const browser = this.handleOpenUri(aUri, null, aWhere, aFlags, null,
+                                       aNextTabParentId);
+    return browser;
   }
 
   // nsIBrowserDOMWindow.
   isTabContentWindow(aWindow) {
     return this.browser.contentWindow === aWindow;
   }
 
   // nsIBrowserDOMWindow.
--- a/mobile/android/modules/geckoview/GeckoViewSettings.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewSettings.jsm
@@ -4,64 +4,61 @@
 
 "use strict";
 
 var EXPORTED_SYMBOLS = ["GeckoViewSettings"];
 
 ChromeUtils.import("resource://gre/modules/GeckoViewModule.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
-ChromeUtils.defineModuleGetter(this, "SafeBrowsing",
-  "resource://gre/modules/SafeBrowsing.jsm");
+XPCOMUtils.defineLazyModuleGetters(this, {
+  SafeBrowsing: "resource://gre/modules/SafeBrowsing.jsm",
+  Services: "resource://gre/modules/Services.jsm",
+});
+
+XPCOMUtils.defineLazyGetter(
+  this, "DESKTOP_USER_AGENT",
+  function() {
+    return Cc["@mozilla.org/network/protocol;1?name=http"]
+           .getService(Ci.nsIHttpProtocolHandler).userAgent
+           .replace(/Android \d.+?; [a-zA-Z]+/, "X11; Linux x86_64")
+           .replace(/Gecko\/[0-9\.]+/, "Gecko/20100101");
+  });
 
 XPCOMUtils.defineLazyGetter(this, "dump", () =>
     ChromeUtils.import("resource://gre/modules/AndroidLog.jsm",
                        {}).AndroidLog.d.bind(null, "ViewSettings"));
 
 function debug(aMsg) {
   // dump(aMsg);
 }
 
 // Handles GeckoView settings including:
-// * tracking protection
 // * multiprocess
+// * user agent override
 class GeckoViewSettings extends GeckoViewModule {
   init() {
     this._isSafeBrowsingInit = false;
-    this._useTrackingProtection = false;
+    this._useDesktopMode = false;
 
     // We only allow to set this setting during initialization, further updates
     // will be ignored.
     this.useMultiprocess = !!this.settings.useMultiprocess;
     this._displayMode = Ci.nsIDocShell.DISPLAY_MODE_BROWSER;
+
+    this.messageManager.loadFrameScript(
+      "chrome://geckoview/content/GeckoViewContentSettings.js", true);
   }
 
   onSettingsUpdate() {
     debug("onSettingsUpdate: " + JSON.stringify(this.settings));
 
+    this.displayMode = this.settings.displayMode;
     this.useTrackingProtection = !!this.settings.useTrackingProtection;
-    this.displayMode = this.settings.displayMode;
-  }
-
-  get useTrackingProtection() {
-    return this._useTrackingProtection;
-  }
-
-  set useTrackingProtection(aUse) {
-    if (aUse && !this._isSafeBrowsingInit) {
-      SafeBrowsing.init();
-      this._isSafeBrowsingInit = true;
-    }
-    if (aUse != this._useTrackingProtection) {
-      this.messageManager.loadFrameScript("data:," +
-        `docShell.useTrackingProtection = ${aUse}`,
-        true
-      );
-      this._useTrackingProtection = aUse;
-    }
+    this.useDesktopMode = !!this.settings.useDesktopMode;
   }
 
   get useMultiprocess() {
     return this.browser.getAttribute("remote") == "true";
   }
 
   set useMultiprocess(aUse) {
     if (aUse == this.useMultiprocess) {
@@ -73,16 +70,55 @@ class GeckoViewSettings extends GeckoVie
     if (aUse) {
       this.browser.setAttribute("remote", "true");
     } else {
       this.browser.removeAttribute("remote");
     }
     parentNode.appendChild(this.browser);
   }
 
+  set useTrackingProtection(aUse) {
+    if (aUse && !this._isSafeBrowsingInit) {
+      SafeBrowsing.init();
+      this._isSafeBrowsingInit = true;
+    }
+  }
+
+  onUserAgentRequest(aSubject, aTopic, aData) {
+    debug("onUserAgentRequest");
+
+    let channel = aSubject.QueryInterface(Ci.nsIHttpChannel);
+
+    if (this.browser.outerWindowID !== channel.topLevelOuterContentWindowId) {
+      return;
+    }
+
+    if (this.useDesktopMode) {
+      channel.setRequestHeader("User-Agent", DESKTOP_USER_AGENT, false);
+    }
+  }
+
+  get useDesktopMode() {
+    return this._useDesktopMode;
+  }
+
+  set useDesktopMode(aUse) {
+    if (this.useDesktopMode === aUse) {
+      return;
+    }
+    if (aUse) {
+      Services.obs.addObserver(this.onUserAgentRequest.bind(this),
+                               "http-on-useragent-request");
+    } else {
+      Services.obs.removeObserver(this.onUserAgentRequest.bind(this),
+                                  "http-on-useragent-request");
+    }
+    this._useDesktopMode = aUse;
+  }
+
   get displayMode() {
     return this._displayMode;
   }
 
   set displayMode(aMode) {
     if (!this.useMultiprocess) {
       this.window.QueryInterface(Ci.nsIInterfaceRequestor)
                    .getInterface(Ci.nsIDocShell)
new file mode 100644
--- /dev/null
+++ b/mobile/android/modules/geckoview/LoadURIDelegate.jsm
@@ -0,0 +1,38 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+var EXPORTED_SYMBOLS = ["LoadURIDelegate"];
+
+ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+  Services: "resource://gre/modules/Services.jsm",
+});
+
+var LoadURIDelegate = {
+  // Delegate URI loading to the app.
+  // Return whether the loading has been handled.
+  load: function(aEventDispatcher, aUri, aWhere, aFlags, aTriggeringPrincipal) {
+    const message = {
+      type: "GeckoView:OnLoadRequest",
+      uri: aUri ? aUri.displaySpec : "",
+      where: aWhere,
+      flags: aFlags
+    };
+
+    let handled = undefined;
+    aEventDispatcher.sendRequestForResult(message).then(response => {
+      handled = response;
+    }, () => {
+      // There was an error or listener was not registered in GeckoSession,
+      // treat as unhandled.
+      handled = false;
+    });
+    Services.tm.spinEventLoopUntil(() => handled !== undefined);
+
+    return handled;
+  }
+};
--- a/mobile/android/modules/geckoview/moz.build
+++ b/mobile/android/modules/geckoview/moz.build
@@ -12,10 +12,11 @@ EXTRA_JS_MODULES += [
     'GeckoViewNavigation.jsm',
     'GeckoViewProgress.jsm',
     'GeckoViewRemoteDebugger.jsm',
     'GeckoViewScroll.jsm',
     'GeckoViewSettings.jsm',
     'GeckoViewTab.jsm',
     'GeckoViewTrackingProtection.jsm',
     'GeckoViewUtils.jsm',
+    'LoadURIDelegate.jsm',
     'Messaging.jsm',
 ]
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1564,16 +1564,17 @@ pref("javascript.options.shared_memory",
 pref("javascript.options.throw_on_debuggee_would_run", false);
 pref("javascript.options.dump_stack_on_debuggee_would_run", false);
 
 // Spectre security vulnerability mitigations.
 pref("javascript.options.spectre.index_masking", true);
 pref("javascript.options.spectre.object_mitigations.barriers", true);
 pref("javascript.options.spectre.string_mitigations", true);
 pref("javascript.options.spectre.value_masking", true);
+pref("javascript.options.spectre.jit_to_C++_calls", true);
 
 // Streams API
 pref("javascript.options.streams", false);
 
 // advanced prefs
 pref("advanced.mailftp",                    false);
 pref("image.animation_mode",                "normal");
 
@@ -5104,16 +5105,17 @@ pref("extensions.webExtensionsMinPlatfor
 pref("extensions.legacy.enabled", true);
 pref("extensions.allow-non-mpc-extensions", true);
 
 // Other webextensions prefs
 pref("extensions.webextensions.keepStorageOnUninstall", false);
 pref("extensions.webextensions.keepUuidOnUninstall", false);
 // Redirect basedomain used by identity api
 pref("extensions.webextensions.identity.redirectDomain", "extensions.allizom.org");
+pref("extensions.webextensions.restrictedDomains", "accounts-static.cdn.mozilla.net,accounts.firefox.com,addons.cdn.mozilla.net,addons.mozilla.org,api.accounts.firefox.com,content.cdn.mozilla.net,content.cdn.mozilla.net,discovery.addons.mozilla.org,input.mozilla.org,install.mozilla.org,oauth.accounts.firefox.com,profile.accounts.firefox.com,support.mozilla.org,sync.services.mozilla.com,testpilot.firefox.com");
 // Whether or not webextension themes are supported.
 pref("extensions.webextensions.themes.enabled", false);
 pref("extensions.webextensions.themes.icons.enabled", false);
 pref("extensions.webextensions.remote", false);
 // Whether or not the moz-extension resource loads are remoted. For debugging
 // purposes only. Setting this to false will break moz-extension URI loading
 // unless other process sandboxing and extension remoting prefs are changed.
 pref("extensions.webextensions.protocol.remote", true);
--- a/security/sandbox/common/SandboxSettings.cpp
+++ b/security/sandbox/common/SandboxSettings.cpp
@@ -22,20 +22,17 @@ int GetEffectiveContentSandboxLevel() {
 // Nightly, where it can be set to 0).
 #if !defined(NIGHTLY_BUILD) && (defined(XP_WIN) || defined(XP_MACOSX))
   if (level < 1) {
     level = 1;
   }
 #endif
 #ifdef XP_LINUX
   // Level 4 and up will break direct access to audio.
-  // Bug 1438391: also VirtualGL lazily connecting to X.
-  if (level > 3 &&
-      (!Preferences::GetBool("media.cubeb.sandbox") ||
-       PR_GetEnv("VGL_ISACTIVE") != nullptr)) {
+  if (level > 3 && !Preferences::GetBool("media.cubeb.sandbox")) {
     level = 3;
   }
 #endif
 
   return level;
 }
 
 bool IsContentSandboxEnabled() {
--- a/security/sandbox/linux/SandboxBrokerClient.cpp
+++ b/security/sandbox/linux/SandboxBrokerClient.cpp
@@ -10,16 +10,17 @@
 
 #include <errno.h>
 #include <fcntl.h>
 #include <stdio.h>
 #include <string.h>
 #include <sys/socket.h>
 #include <sys/stat.h>
 #include <sys/types.h>
+#include <sys/un.h>
 #include <unistd.h>
 
 #include "mozilla/Assertions.h"
 #include "mozilla/NullPtr.h"
 #include "base/strings/safe_sprintf.h"
 
 namespace mozilla {
 
@@ -238,10 +239,44 @@ SandboxBrokerClient::Rmdir(const char* a
 
 int
 SandboxBrokerClient::Readlink(const char* aPath, void* aBuff, size_t aSize)
 {
   Request req = {SANDBOX_FILE_READLINK, 0, aSize};
   return DoCall(&req, aPath, nullptr, aBuff, false);
 }
 
+int
+SandboxBrokerClient::Connect(const sockaddr_un* aAddr, size_t aLen, int aType)
+{
+  static const size_t maxLen = sizeof(aAddr->sun_path);
+  const char* path = aAddr->sun_path;
+  const auto addrEnd = reinterpret_cast<const char*>(aAddr) + aLen;
+  // Ensure that the length isn't impossibly small.
+  if (addrEnd <= path) {
+    return -EINVAL;
+  }
+  // Unix domain only
+  if (aAddr->sun_family != AF_UNIX) {
+    return -EAFNOSUPPORT;
+  }
+  // How much of sun_path may be accessed?
+  auto bufLen = static_cast<size_t>(addrEnd - path);
+  if (bufLen > maxLen) {
+    bufLen = maxLen;
+  }
+  // Require null-termination.  (Linux doesn't require it, but
+  // applications usually null-terminate for portability, and not
+  // handling unterminated strings means we don't have to copy the path.)
+  const size_t pathLen = strnlen(path, bufLen);
+  if (pathLen == bufLen) {
+    return -ENAMETOOLONG;
+  }
+  // Abstract addresses aren't handled (yet?).
+  if (pathLen == 0) {
+    return -ECONNREFUSED;
+  }
+
+  const Request req = { SANDBOX_SOCKET_CONNECT, aType, 0 };
+  return DoCall(&req, path, nullptr, nullptr, true);
+}
+
 } // namespace mozilla
-
--- a/security/sandbox/linux/SandboxBrokerClient.h
+++ b/security/sandbox/linux/SandboxBrokerClient.h
@@ -17,16 +17,17 @@
 // returned by SandboxBroker::Create, passed to the child over IPC.
 //
 // The operations exposed here can be called from any thread and in
 // async signal handlers, like the corresponding system calls.  The
 // intended use is from a seccomp-bpf SIGSYS handler, to transparently
 // replace those syscalls, but they could also be used directly.
 
 struct stat;
+struct sockaddr_un;
 
 namespace mozilla {
 
 class SandboxBrokerClient final : private SandboxBrokerCommon {
  public:
   explicit SandboxBrokerClient(int aFd);
   ~SandboxBrokerClient();
 
@@ -37,16 +38,17 @@ class SandboxBrokerClient final : privat
   int Chmod(const char* aPath, int aMode);
   int Link(const char* aPath, const char* aPath2);
   int Mkdir(const char* aPath, int aMode);
   int Symlink(const char* aOldPath, const char* aNewPath);
   int Rename(const char* aOldPath, const char* aNewPath);
   int Unlink(const char* aPath);
   int Rmdir(const char* aPath);
   int Readlink(const char* aPath, void* aBuf, size_t aBufSize);
+  int Connect(const struct sockaddr_un* aAddr, size_t aLen, int aType);
 
  private:
   int mFileDesc;
 
   int DoCall(const Request* aReq,
              const char* aPath,
              const char* aPath2,
              void *aReponseBuff,
--- a/security/sandbox/linux/SandboxFilter.cpp
+++ b/security/sandbox/linux/SandboxFilter.cpp
@@ -28,16 +28,17 @@
 #include <linux/net.h>
 #include <linux/prctl.h>
 #include <linux/sched.h>
 #include <string.h>
 #include <sys/ioctl.h>
 #include <sys/mman.h>
 #include <sys/socket.h>
 #include <sys/syscall.h>
+#include <sys/un.h>
 #include <sys/utsname.h>
 #include <time.h>
 #include <unistd.h>
 #include <vector>
 #include <algorithm>
 
 #include "sandbox/linux/bpf_dsl/bpf_dsl.h"
 #include "sandbox/linux/system_headers/linux_seccomp.h"
@@ -617,16 +618,131 @@ private:
       MOZ_ASSERT(false);
       rv = -ENOSYS;
     }
 
     close(fd);
     return rv;
   }
 
+  // This just needs to return something to stand in for the
+  // unconnected socket until ConnectTrap, below, and keep track of
+  // the socket type somehow.  Half a socketpair *is* a socket, so it
+  // should result in minimal confusion in the caller.
+  static intptr_t FakeSocketTrapCommon(int domain, int type, int protocol) {
+    int fds[2];
+    // X11 client libs will still try to getaddrinfo() even for a
+    // local connection.  Also, WebRTC still has vestigial network
+    // code trying to do things in the content process.  Politely tell
+    // them no.
+    if (domain != AF_UNIX) {
+      return -EAFNOSUPPORT;
+    }
+    if (socketpair(domain, type, protocol, fds) != 0) {
+      return -errno;
+    }
+    close(fds[1]);
+    return fds[0];
+  }
+
+  static intptr_t FakeSocketTrap(ArgsRef aArgs, void* aux) {
+    return FakeSocketTrapCommon(static_cast<int>(aArgs.args[0]),
+                                static_cast<int>(aArgs.args[1]),
+                                static_cast<int>(aArgs.args[2]));
+  }
+
+  static intptr_t FakeSocketTrapLegacy(ArgsRef aArgs, void* aux) {
+    const auto innerArgs = reinterpret_cast<unsigned long*>(aArgs.args[1]);
+
+    return FakeSocketTrapCommon(static_cast<int>(innerArgs[0]),
+                                static_cast<int>(innerArgs[1]),
+                                static_cast<int>(innerArgs[2]));
+  }
+
+  static Maybe<int>
+  DoGetSockOpt(int fd, int optname) {
+    int optval;
+    socklen_t optlen = sizeof(optval);
+
+    if (getsockopt(fd, SOL_SOCKET, optname, &optval, &optlen) != 0) {
+      return Nothing();
+    }
+    MOZ_RELEASE_ASSERT(static_cast<size_t>(optlen) == sizeof(optval));
+    return Some(optval);
+  }
+
+  // Substitute the newly connected socket from the broker for the
+  // original socket.  This is meant to be used on a fd from
+  // FakeSocketTrap, above, but it should also work to simulate
+  // re-connect()ing a real connected socket.
+  //
+  // Warning: This isn't quite right if the socket is dup()ed, because
+  // other duplicates will still be the original socket, but hopefully
+  // nothing we're dealing with does that.
+  static intptr_t ConnectTrapCommon(SandboxBrokerClient* aBroker, int aFd,
+                                    const struct sockaddr_un* aAddr,
+                                    socklen_t aLen) {
+    if (aFd < 0) {
+      return -EBADF;
+    }
+    const auto maybeDomain = DoGetSockOpt(aFd, SO_DOMAIN);
+    if (!maybeDomain) {
+      return -errno;
+    }
+    if (*maybeDomain != AF_UNIX) {
+      return -EAFNOSUPPORT;
+    }
+    const auto maybeType = DoGetSockOpt(aFd, SO_TYPE);
+    if (!maybeType) {
+      return -errno;
+    }
+    const int oldFlags = fcntl(aFd, F_GETFL);
+    if (oldFlags == -1) {
+      return -errno;
+    }
+    const int newFd = aBroker->Connect(aAddr, aLen, *maybeType);
+    if (newFd < 0) {
+      return newFd;
+    }
+    // Copy over the nonblocking flag.  The connect() won't be
+    // nonblocking in that case, but that shouldn't matter for
+    // AF_UNIX.  The other fcntl-settable flags are either irrelevant
+    // for sockets (e.g., O_APPEND) or would be blocked by this
+    // seccomp-bpf policy, so they're ignored.
+    if (fcntl(newFd, F_SETFL, oldFlags & O_NONBLOCK) != 0) {
+      close(newFd);
+      return -errno;
+    }
+    if (dup2(newFd, aFd) < 0) {
+      close(newFd);
+      return -errno;
+    }
+    close(newFd);
+    return 0;
+  }
+
+  static intptr_t ConnectTrap(ArgsRef aArgs, void* aux) {
+    typedef const struct sockaddr_un* AddrPtr;
+
+    return ConnectTrapCommon(static_cast<SandboxBrokerClient*>(aux),
+                             static_cast<int>(aArgs.args[0]),
+                             reinterpret_cast<AddrPtr>(aArgs.args[1]),
+                             static_cast<socklen_t>(aArgs.args[2]));
+  }
+
+  static intptr_t ConnectTrapLegacy(ArgsRef aArgs, void* aux) {
+    const auto innerArgs = reinterpret_cast<unsigned long*>(aArgs.args[1]);
+    typedef const struct sockaddr_un* AddrPtr;
+
+    return ConnectTrapCommon(static_cast<SandboxBrokerClient*>(aux),
+                             static_cast<int>(innerArgs[0]),
+                             reinterpret_cast<AddrPtr>(innerArgs[1]),
+                             static_cast<socklen_t>(innerArgs[2]));
+  }
+
 public:
   ContentSandboxPolicy(SandboxBrokerClient* aBroker,
                        ContentProcessSandboxParams&& aParams)
     : mBroker(aBroker)
     , mParams(Move(aParams))
     , mAllowSysV(PR_GetEnv("MOZ_SANDBOX_ALLOW_SYSV") != nullptr)
     { }
 
@@ -649,35 +765,36 @@ public:
           return Some(Trap(SocketpairUnpackTrap, nullptr));
         }
         // Otherwise, we can't filter the args if the platform passes
         // them by pointer.
         return Some(Allow());
       }
       Arg<int> domain(0), type(1);
       return Some(If(domain == AF_UNIX,
-                     Switch(type & ~SOCK_CLOEXEC)
+                     Switch(type & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
                      .Case(SOCK_STREAM, Allow())
                      .Case(SOCK_SEQPACKET, Allow())
                      .Case(SOCK_DGRAM, Trap(SocketpairDatagramTrap, nullptr))
                      .Default(InvalidSyscall()))
                   .Else(InvalidSyscall()));
     }
 
 #ifdef ANDROID
     case SYS_SOCKET:
       return Some(Error(EACCES));
 #else // #ifdef DESKTOP
-    case SYS_SOCKET: // DANGEROUS
-      // Some things try to get a socket but can work without one,
-      // like sctp_userspace_get_mtu_from_ifn in WebRTC, so this is
-      // silently disallowed.
-      return Some(AllowBelowLevel(4, Error(EACCES)));
-    case SYS_CONNECT: // DANGEROUS
-      return Some(AllowBelowLevel(4));
+    case SYS_SOCKET: {
+      const auto trapFn = aHasArgs ? FakeSocketTrap : FakeSocketTrapLegacy;
+      return Some(AllowBelowLevel(4, Trap(trapFn, nullptr)));
+    }
+    case SYS_CONNECT: {
+      const auto trapFn = aHasArgs ? ConnectTrap : ConnectTrapLegacy;
+      return Some(AllowBelowLevel(4, Trap(trapFn, mBroker)));
+    }
     case SYS_RECV:
     case SYS_SEND:
     case SYS_GETSOCKOPT:
     case SYS_SETSOCKOPT:
     case SYS_GETSOCKNAME:
     case SYS_GETPEERNAME:
     case SYS_SHUTDOWN:
       return Some(Allow());
@@ -936,16 +1053,17 @@ public:
       return Allow();
 #endif
 
     case __NR_getrusage:
     case __NR_times:
       return Allow();
 
     case __NR_dup:
+    case __NR_dup2: // See ConnectTrapCommon
       return Allow();
 
     CASES_FOR_getuid:
     CASES_FOR_getgid:
     CASES_FOR_geteuid:
     CASES_FOR_getegid:
       return Allow();
 
--- a/security/sandbox/linux/broker/SandboxBroker.cpp
+++ b/security/sandbox/linux/broker/SandboxBroker.cpp
@@ -10,16 +10,17 @@
 #include "SandboxBrokerUtils.h"
 
 #include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <sys/socket.h>
 #include <sys/stat.h>
 #include <sys/types.h>
+#include <sys/un.h>
 #include <unistd.h>
 
 #ifdef XP_LINUX
 #include <sys/prctl.h>
 #endif
 
 #include "base/string_util.h"
 #include "mozilla/Assertions.h"
@@ -500,16 +501,55 @@ DoLink(const char* aPath, const char* aP
     return link(aPath, aPath2);
   }
   if (aOper == SandboxBrokerCommon::Operation::SANDBOX_FILE_SYMLINK) {
     return symlink(aPath, aPath2);
   }
   MOZ_CRASH("SandboxBroker: Unknown link operation");
 }
 
+static int
+DoConnect(const char* aPath, size_t aLen, int aType)
+{
+  // Deny SOCK_DGRAM for the same reason it's denied for socketpair.
+  if (aType != SOCK_STREAM && aType != SOCK_SEQPACKET) {
+    errno = EACCES;
+    return -1;
+  }
+  // Ensure that the address is a pathname.  (An empty string
+  // resulting from an abstract address probably shouldn't have made
+  // it past the policy check, but check explicitly just in case.)
+  if (aPath[0] == '\0') {
+    errno = ECONNREFUSED;
+    return -1;
+  }
+
+  // Try to copy the name into a normal-sized sockaddr_un, with
+  // null-termination:
+  struct sockaddr_un sun;
+  memset(&sun, 0, sizeof(sun));
+  sun.sun_family = AF_UNIX;
+  if (aLen + 1 > sizeof(sun.sun_path)) {
+    errno = ENAMETOOLONG;
+    return -1;
+  }
+  memcpy(&sun.sun_path, aPath, aLen);
+
+  // Finally, the actual socket connection.
+  const int fd = socket(AF_UNIX, aType | SOCK_CLOEXEC, 0);
+  if (fd < 0) {
+    return -1;
+  }
+  if (connect(fd, reinterpret_cast<struct sockaddr*>(&sun), sizeof(sun)) < 0) {
+    close(fd);
+    return -1;
+  }
+  return fd;
+}
+
 size_t
 SandboxBroker::ConvertToRealPath(char* aPath, size_t aBufSize, size_t aPathLen)
 {
   if (strstr(aPath, "..") != nullptr) {
     char* result = realpath(aPath, nullptr);
     if (result != nullptr) {
       base::strlcpy(aPath, result, aBufSize);
       free(result);
@@ -742,31 +782,31 @@ SandboxBroker::ThreadMain(void)
 
       // First string is guaranteed to be 0-terminated.
       pathLen = first_len;
 
       // Look up the first pathname but first translate relative paths.
       pathLen = ConvertToRealPath(pathBuf, sizeof(pathBuf), pathLen);
       perms = mPolicy->Lookup(nsDependentCString(pathBuf, pathLen));
 
-      // We don't have read permissions on the requested dir.
-      if (!(perms & MAY_READ)) {
-          // Was it a tempdir that we can remap?
-          pathLen = RemapTempDirs(pathBuf, sizeof(pathBuf), pathLen);
-          perms = mPolicy->Lookup(nsDependentCString(pathBuf, pathLen));
-          if (!(perms & MAY_READ)) {
-            // Did we arrive from a symlink in a path that is not writable?
-            // Then try to figure out the original path and see if that is
-            // readable. Work on the original path, this reverses
-            // ConvertToRealPath above.
-            int symlinkPerms = SymlinkPermissions(recvBuf, first_len);
-            if (symlinkPerms > 0) {
-              perms = symlinkPerms;
-            }
+      // We don't have permissions on the requested dir.
+      if (!perms) {
+        // Was it a tempdir that we can remap?
+        pathLen = RemapTempDirs(pathBuf, sizeof(pathBuf), pathLen);
+        perms = mPolicy->Lookup(nsDependentCString(pathBuf, pathLen));
+        if (!perms) {
+          // Did we arrive from a symlink in a path that is not writable?
+          // Then try to figure out the original path and see if that is
+          // readable. Work on the original path, this reverses
+          // ConvertToRealPath above.
+          int symlinkPerms = SymlinkPermissions(recvBuf, first_len);
+          if (symlinkPerms > 0) {
+            perms = symlinkPerms;
           }
+        }
       }
 
       // Same for the second path.
       pathLen2 = strnlen(pathBuf2, kMaxPathLen);
       if (pathLen2 > 0) {
         // Force 0 termination.
         pathBuf2[pathLen2] = '\0';
         pathLen2 = ConvertToRealPath(pathBuf2, sizeof(pathBuf2), pathLen2);
@@ -947,16 +987,29 @@ SandboxBroker::ThreadMain(void)
             ios[1].iov_len = respSize;
           } else {
             resp.mError = -errno;
           }
         } else {
           AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
         }
         break;
+
+      case SANDBOX_SOCKET_CONNECT:
+        if (permissive || (perms & MAY_CONNECT) != 0) {
+          openedFd = DoConnect(pathBuf, pathLen, req.mFlags);
+          if (openedFd >= 0) {
+            resp.mError = 0;
+          } else {
+            resp.mError = -errno;
+          }
+        } else {
+          AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
+        }
+        break;
       }
     } else {
       MOZ_ASSERT(perms == 0);
       AuditDenial(req.mOp, req.mFlags, perms, pathBuf);
     }
 
     const size_t numIO = ios[1].iov_len > 0 ? 2 : 1;
     const ssize_t sent = SendWithFd(respfd, ios, numIO, openedFd);
--- a/security/sandbox/linux/broker/SandboxBroker.h
+++ b/security/sandbox/linux/broker/SandboxBroker.h
@@ -48,16 +48,18 @@ class SandboxBroker final
     // This flag is for testing policy changes -- when the client is
     // used with the seccomp-bpf integration, an access to this file
     // will invoke a crash dump with the context of the syscall.
     // (This overrides all other flags.)
     CRASH_INSTEAD = 1 << 4,
     // Applies to everything below this path, including subdirs created
     // at runtime
     RECURSIVE     = 1 << 5,
+    // Allow Unix-domain socket connections to a path
+    MAY_CONNECT   = 1 << 6,
   };
   // Bitwise operations on enum values return ints, so just use int in
   // the hash table type (and below) to avoid cluttering code with casts.
   typedef nsDataHashtable<nsCStringHashKey, int> PathPermissionMap;
 
   class Policy {
     PathPermissionMap mMap;
   public:
--- a/security/sandbox/linux/broker/SandboxBrokerCommon.cpp
+++ b/security/sandbox/linux/broker/SandboxBrokerCommon.cpp
@@ -36,17 +36,18 @@ const char* SandboxBrokerCommon::Operati
   "stat",
   "chmod",
   "link",
   "symlink",
   "mkdir",
   "rename",
   "rmdir",
   "unlink",
-  "readlink"
+  "readlink",
+  "connect"
 };
 
 /* static */ ssize_t
 SandboxBrokerCommon::RecvWithFd(int aFd, const iovec* aIO, size_t aNumIO,
                                     int* aPassedFdPtr)
 {
   struct msghdr msg = {};
   msg.msg_iov = const_cast<iovec*>(aIO);
--- a/security/sandbox/linux/broker/SandboxBrokerCommon.h
+++ b/security/sandbox/linux/broker/SandboxBrokerCommon.h
@@ -32,23 +32,25 @@ public:
     SANDBOX_FILE_CHMOD,
     SANDBOX_FILE_LINK,
     SANDBOX_FILE_SYMLINK,
     SANDBOX_FILE_MKDIR,
     SANDBOX_FILE_RENAME,
     SANDBOX_FILE_RMDIR,
     SANDBOX_FILE_UNLINK,
     SANDBOX_FILE_READLINK,
+    SANDBOX_SOCKET_CONNECT,
   };
   // String versions of the above
   static const char* OperationDescription[];
 
   struct Request {
     Operation mOp;
     // For open, flags; for access, "mode"; for stat, O_NOFOLLOW for lstat.
+    // For connect, the socket type.
     int mFlags;
     // Size of return value buffer, if any
     size_t mBufSize;
     // The rest of the packet is the pathname.
     // SCM_RIGHTS for response socket attached.
   };
 
   struct Response {
--- a/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp
+++ b/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp
@@ -365,16 +365,31 @@ SandboxBrokerPolicyFactory::SandboxBroke
     if (bloatLen >= 4) {
       nsAutoCString bloatStr(bloatLog);
       bloatStr.Truncate(bloatLen - 4);
       policy->AddPrefix(rdwrcr, bloatStr.get());
     }
   }
 #endif
 
+  // Allow Primus to contact the Bumblebee daemon to manage GPU
+  // switching on NVIDIA Optimus systems.
+  const char* bumblebeeSocket = PR_GetEnv("BUMBLEBEE_SOCKET");
+  if (bumblebeeSocket == nullptr) {
+    bumblebeeSocket = "/var/run/bumblebee.socket";
+  }
+  policy->AddPath(SandboxBroker::MAY_CONNECT, bumblebeeSocket);
+
+  // Allow local X11 connections, for Primus and VirtualGL to contact
+  // the secondary X server.
+  policy->AddPrefix(SandboxBroker::MAY_CONNECT, "/tmp/.X11-unix/X");
+  if (const auto xauth = PR_GetEnv("XAUTHORITY")) {
+    policy->AddPath(rdonly, xauth);
+  }
+
   mCommonContentPolicy.reset(policy);
 #endif
 }
 
 #ifdef MOZ_CONTENT_SANDBOX
 UniquePtr<SandboxBroker::Policy>
 SandboxBrokerPolicyFactory::GetContentPolicy(int aPid, bool aFileProcess)
 {
@@ -502,20 +517,19 @@ SandboxBrokerPolicyFactory::GetContentPo
       // case we know it already exists).  See bug 1335329.
       nsPrintfCString pulsePath("%s/pulse", userDir);
       policy->AddPath(rdonly, pulsePath.get());
     }
   }
 #endif // MOZ_WIDGET_GTK
 
   if (allowPulse) {
-    // See bug 1384986 comment #1.
-    if (const auto xauth = PR_GetEnv("XAUTHORITY")) {
-      policy->AddPath(rdonly, xauth);
-    }
+    // PulseAudio also needs access to read the $XAUTHORITY file (see
+    // bug 1384986 comment #1), but that's already allowed for hybrid
+    // GPU drivers (see above).
     policy->AddPath(rdonly, "/var/lib/dbus/machine-id");
   }
 
   // Return the common policy.
   policy->FixRecursivePermissions();
   return policy;
 }
 
--- a/taskcluster/ci/test/mochitest.yml
+++ b/taskcluster/ci/test/mochitest.yml
@@ -45,17 +45,17 @@ mochitest:
             default: 5
     e10s:
         by-test-platform:
             linux64-jsdcov/opt: false
             linux32/debug: both
             default: true
     max-run-time:
         by-test-platform:
-            android-4.3-arm7-api-16/debug: 7200
+            android.*: 7200
             default: 5400
     allow-software-gl-layers: false
     tier:
         by-test-platform:
             windows10-64-asan.*: 3
             default: default
     mozharness:
         mochitest-flavor: plain
@@ -165,17 +165,20 @@ mochitest-chrome:
         by-test-platform:
             android.*: xlarge
             default: default
     chunks:
         by-test-platform:
             android-4.3-arm7-api-16/debug: 8
             android.*: 4
             default: 3
-    max-run-time: 3600
+    max-run-time:
+        by-test-platform:
+            android.*: 7200
+            default: 3600
     e10s: false
     mozharness:
         mochitest-flavor: chrome
         extra-options:
             by-test-platform:
                 android.*:
                     - --test-suite=mochitest-chrome
                 default: []
@@ -216,16 +219,20 @@ mochitest-devtools-chrome:
     description: "Mochitest devtools-chrome run"
     suite:
         by-test-platform:
             linux64-jsdcov/opt: mochitest/mochitest-devtools-chrome-coverage
             default: mochitest/mochitest-devtools-chrome-chunked
     treeherder-symbol: M(dt)
     loopback-video: true
     max-run-time: 5400
+    run-on-projects:
+        by-test-platform:
+            linux64-jsdcov/opt: ['try']  # jsdcov only on try, Bug 1442823
+            default: built-projects
     chunks:
         by-test-platform:
             windows10-64-ccov/debug: 10
             default: 8
     e10s:
         by-test-platform:
             linux64-jsdcov/opt: false
             default: true
--- a/toolkit/components/extensions/MatchPattern.cpp
+++ b/toolkit/components/extensions/MatchPattern.cpp
@@ -126,16 +126,25 @@ const nsCString&
 URLInfo::Host() const
 {
   if (mHost.IsVoid()) {
     Unused << mURI->GetHost(mHost);
   }
   return mHost;
 }
 
+const nsAtom*
+URLInfo::HostAtom() const
+{
+  if (!mHostAtom) {
+    mHostAtom = NS_Atomize(Host());
+  }
+  return mHostAtom;
+}
+
 const nsString&
 URLInfo::FilePath() const
 {
   if (mFilePath.IsEmpty()) {
     nsCString path;
     nsCOMPtr<nsIURL> url = do_QueryInterface(mURI);
     if (url && NS_SUCCEEDED(url->GetFilePath(path))) {
       AppendUTF8toUTF16(path, mFilePath);
--- a/toolkit/components/extensions/MatchPattern.h
+++ b/toolkit/components/extensions/MatchPattern.h
@@ -150,31 +150,33 @@ public:
   URLInfo(const URLInfo& aOther)
     : URLInfo(aOther.mURI.get())
   {}
 
   nsIURI* URI() const { return mURI; }
 
   nsAtom* Scheme() const;
   const nsCString& Host() const;
+  const nsAtom* HostAtom() const;
   const nsString& Path() const;
   const nsString& FilePath() const;
   const nsString& Spec() const;
   const nsCString& CSpec() const;
 
   bool InheritsPrincipal() const;
 
 private:
   nsIURI* URINoRef() const;
 
   nsCOMPtr<nsIURI> mURI;
   mutable nsCOMPtr<nsIURI> mURINoRef;
 
   mutable RefPtr<nsAtom> mScheme;
   mutable nsCString mHost;
+  mutable RefPtr<nsAtom> mHostAtom;
 
   mutable nsString mPath;
   mutable nsString mFilePath;
   mutable nsString mSpec;
   mutable nsCString mCSpec;
 
   mutable Maybe<bool> mInheritsPrincipal;
 };
--- a/toolkit/components/extensions/WebExtensionPolicy.cpp
+++ b/toolkit/components/extensions/WebExtensionPolicy.cpp
@@ -6,16 +6,17 @@
 #include "mozilla/ExtensionPolicyService.h"
 #include "mozilla/extensions/WebExtensionContentScript.h"
 #include "mozilla/extensions/WebExtensionPolicy.h"
 
 #include "mozilla/AddonManagerWebAPI.h"
 #include "mozilla/ResultExtensions.h"
 #include "nsEscape.h"
 #include "nsIDocShell.h"
+#include "nsIObserver.h"
 #include "nsISubstitutingProtocolHandler.h"
 #include "nsNetUtil.h"
 #include "nsPrintfCString.h"
 
 namespace mozilla {
 namespace extensions {
 
 using namespace dom;
@@ -29,16 +30,19 @@ static const char kBackgroundPageHTMLSta
 
 static const char kBackgroundPageHTMLScript[] = "\n\
     <script type=\"text/javascript\" src=\"%s\"></script>";
 
 static const char kBackgroundPageHTMLEnd[] = "\n\
   <body>\n\
 </html>";
 
+static const char kRestrictedDomainPref[] =
+  "extensions.webextensions.restrictedDomains";
+
 static inline ExtensionPolicyService&
 EPS()
 {
   return ExtensionPolicyService::GetSingleton();
 }
 
 static nsISubstitutingProtocolHandler*
 Proto()
@@ -260,16 +264,116 @@ WebExtensionPolicy::UseRemoteWebExtensio
 }
 
 /* static */ bool
 WebExtensionPolicy::IsExtensionProcess(GlobalObject& aGlobal)
 {
   return EPS().IsExtensionProcess();
 }
 
+namespace {
+  /**
+   * Maintains a dynamically updated AtomSet based on the comma-separated
+   * values in the given string pref.
+   */
+  class AtomSetPref : public nsIObserver
+                    , public nsSupportsWeakReference
+  {
+  public:
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSIOBSERVER
+
+    static already_AddRefed<AtomSetPref>
+    Create(const char* aPref)
+    {
+      RefPtr<AtomSetPref> self = new AtomSetPref(aPref);
+      Preferences::AddWeakObserver(self, aPref);
+      return self.forget();
+    }
+
+    const AtomSet& Get() const;
+
+    bool Contains(const nsAtom* aAtom) const
+    {
+      return Get().Contains(aAtom);
+    }
+
+  protected:
+    virtual ~AtomSetPref() = default;
+
+    explicit AtomSetPref(const char* aPref) : mPref(aPref)
+    {}
+
+  private:
+    mutable RefPtr<AtomSet> mAtomSet;
+    const char* mPref;
+  };
+
+  const AtomSet&
+  AtomSetPref::Get() const
+  {
+    if (!mAtomSet) {
+      nsAutoCString eltsString;
+      Unused << Preferences::GetCString(mPref, eltsString);
+
+      AutoTArray<nsString, 32> elts;
+      for (const nsACString& elt : eltsString.Split(',')) {
+        elts.AppendElement(NS_ConvertUTF8toUTF16(elt));
+        elts.LastElement().StripWhitespace();
+      }
+      mAtomSet = new AtomSet(elts);
+    }
+
+    return *mAtomSet;
+  }
+
+  NS_IMETHODIMP
+  AtomSetPref::Observe(nsISupports *aSubject, const char *aTopic,
+                       const char16_t *aData)
+  {
+    mAtomSet = nullptr;
+    return NS_OK;
+  }
+
+  NS_IMPL_ISUPPORTS(AtomSetPref, nsIObserver, nsISupportsWeakReference)
+};
+
+/* static */ bool
+WebExtensionPolicy::IsRestrictedDoc(const DocInfo& aDoc)
+{
+  // With the exception of top-level about:blank documents with null
+  // principals, we never match documents that have non-codebase principals,
+  // including those with null principals or system principals.
+  if (aDoc.Principal() && !aDoc.Principal()->GetIsCodebasePrincipal()) {
+    return true;
+  }
+
+  return IsRestrictedURI(aDoc.PrincipalURL());
+}
+
+/* static */ bool
+WebExtensionPolicy::IsRestrictedURI(const URLInfo &aURI)
+{
+  static RefPtr<AtomSetPref> domains;
+  if (!domains) {
+    domains = AtomSetPref::Create(kRestrictedDomainPref);
+    ClearOnShutdown(&domains);
+  }
+
+  if (domains->Contains(aURI.HostAtom())) {
+    return true;
+  }
+
+  if (AddonManagerWebAPI::IsValidSite(aURI.URI())) {
+    return true;
+  }
+
+  return false;
+}
+
 nsCString
 WebExtensionPolicy::BackgroundPageHTML() const
 {
   nsAutoCString result;
 
   if (mBackgroundScripts.IsNull()) {
     result.SetIsVoid(true);
     return result;
@@ -358,17 +462,16 @@ WebExtensionContentScript::WebExtensionC
     mIncludeGlobs.SetValue().AppendElements(aInit.mIncludeGlobs.Value());
   }
 
   if (!aInit.mExcludeGlobs.IsNull()) {
     mExcludeGlobs.SetValue().AppendElements(aInit.mExcludeGlobs.Value());
   }
 }
 
-
 bool
 WebExtensionContentScript::Matches(const DocInfo& aDoc) const
 {
   if (!mFrameID.IsNull()) {
     if (aDoc.FrameID() != mFrameID.Value()) {
       return false;
     }
   } else {
@@ -385,30 +488,21 @@ WebExtensionContentScript::Matches(const
   // matchAboutBlank is true and it has the null principal. In all other
   // cases, we test the URL of the principal that it inherits.
   if (mMatchAboutBlank && aDoc.IsTopLevel() &&
       aDoc.URL().Spec().EqualsLiteral("about:blank") &&
       aDoc.Principal() && aDoc.Principal()->GetIsNullPrincipal()) {
     return true;
   }
 
-  // With the exception of top-level about:blank documents with null
-  // principals, we never match documents that have non-codebase principals,
-  // including those with null principals or system principals.
-  if (aDoc.Principal() && !aDoc.Principal()->GetIsCodebasePrincipal()) {
+  if (mExtension->IsRestrictedDoc(aDoc)) {
     return false;
   }
 
-  // Content scripts are not allowed on pages that have elevated
-  // privileges via mozAddonManager (see bug 1280234)
-  if (AddonManagerWebAPI::IsValidSite(aDoc.PrincipalURL().URI())) {
-    return false;
-  }
-
-  URLInfo urlinfo(aDoc.PrincipalURL());
+  auto& urlinfo = aDoc.PrincipalURL();
   if (mHasActiveTabPermission && aDoc.ShouldMatchActiveTabPermission() &&
       MatchPattern::MatchesAllURLs(urlinfo)) {
     return true;
   }
 
   return MatchesURI(urlinfo);
 }
 
@@ -426,17 +520,17 @@ WebExtensionContentScript::MatchesURI(co
   if (!mIncludeGlobs.IsNull() && !mIncludeGlobs.Value().Matches(aURL.Spec())) {
     return false;
   }
 
   if (!mExcludeGlobs.IsNull() && mExcludeGlobs.Value().Matches(aURL.Spec())) {
     return false;
   }
 
-  if (AddonManagerWebAPI::IsValidSite(aURL.URI())) {
+  if (mExtension->IsRestrictedURI(aURL)) {
     return false;
   }
 
   return true;
 }
 
 
 JSObject*
--- a/toolkit/components/extensions/WebExtensionPolicy.h
+++ b/toolkit/components/extensions/WebExtensionPolicy.h
@@ -20,16 +20,17 @@
 #include "nsWrapperCache.h"
 
 namespace mozilla {
 namespace extensions {
 
 using dom::WebExtensionInit;
 using dom::WebExtensionLocalizeCallback;
 
+class DocInfo;
 class WebExtensionContentScript;
 
 class WebExtensionPolicy final : public nsISupports
                                , public nsWrapperCache
                                , public SupportsWeakPtr<WebExtensionPolicy>
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
@@ -62,33 +63,37 @@ public:
   void RegisterContentScript(WebExtensionContentScript& script,
                              ErrorResult& aRv);
 
   void UnregisterContentScript(const WebExtensionContentScript& script,
                                ErrorResult& aRv);
 
   bool CanAccessURI(const URLInfo& aURI, bool aExplicit = false) const
   {
-    return mHostPermissions && mHostPermissions->Matches(aURI, aExplicit);
+    return (!IsRestrictedURI(aURI) &&
+            mHostPermissions && mHostPermissions->Matches(aURI, aExplicit));
   }
 
   bool IsPathWebAccessible(const nsAString& aPath) const
   {
     return mWebAccessiblePaths.Matches(aPath);
   }
 
   bool HasPermission(const nsAtom* aPermission) const
   {
     return mPermissions->Contains(aPermission);
   }
   bool HasPermission(const nsAString& aPermission) const
   {
     return mPermissions->Contains(aPermission);
   }
 
+  static bool IsRestrictedDoc(const DocInfo& aDoc);
+  static bool IsRestrictedURI(const URLInfo& aURI);
+
   nsCString BackgroundPageHTML() const;
 
   void Localize(const nsAString& aInput, nsString& aResult) const;
 
   const nsString& Name() const
   {
     return mName;
   }
--- a/toolkit/components/extensions/test/xpcshell/test_ext_i18n_css.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_i18n_css.js
@@ -49,32 +49,44 @@ let extensionData = {
     "applications": {
       "gecko": {
         "id": "i18n_css@mochi.test",
       },
     },
 
     "web_accessible_resources": ["foo.css", "foo.txt", "locale.css"],
 
-    "content_scripts": [{
-      "matches": ["http://*/*/file_sample.html"],
-      "css": ["foo.css"],
-    }],
+    "content_scripts": [
+      {
+        "matches": ["http://*/*/file_sample.html"],
+        "css": ["foo.css"],
+        "run_at": "document_start",
+      },
+      {
+        "matches": ["http://*/*/file_sample.html"],
+        "js": ["content.js"],
+      },
+    ],
 
     "default_locale": "en",
   },
 
   files: {
     "_locales/en/messages.json": JSON.stringify({
       "foo": {
         "message": "max-width: 42px",
         "description": "foo",
       },
     }),
 
+    "content.js": function() {
+      let style = getComputedStyle(document.body);
+      browser.test.sendMessage("content-maxWidth", style.maxWidth);
+    },
+
     "foo.css": "body { __MSG_foo__; }",
     "bar.CsS": "body { __MSG_foo__; }",
     "foo.txt": "body { __MSG_foo__; }",
     "locale.css": '* { content: "__MSG_@@ui_locale__ __MSG_@@bidi_dir__ __MSG_@@bidi_reversed_dir__ __MSG_@@bidi_start_edge__ __MSG_@@bidi_end_edge__" }',
   },
 };
 
 async function test_i18n_css(options = {}) {
@@ -96,25 +108,17 @@ async function test_i18n_css(options = {
   }
 
   let css = await fetch(cssURL);
 
   equal(css, "body { max-width: 42px; }", "CSS file localized in mochitest scope");
 
   let contentPage = await ExtensionTestUtils.loadContentPage(`${BASE_URL}/file_sample.html`);
 
-  // workaround for extension may not be ready for applying foo.css
-  await new Promise(executeSoon);
-
-  let maxWidth = await ContentTask.spawn(contentPage.browser, {}, async function() {
-    /* globals content */
-    let style = content.getComputedStyle(content.document.body);
-
-    return style.maxWidth;
-  });
+  let maxWidth = await extension.awaitMessage("content-maxWidth");
 
   equal(maxWidth, "42px", "stylesheet correctly applied");
 
   await contentPage.close();
 
   cssURL = cssURL.replace(/foo.css$/, "locale.css");
 
   css = await fetch(cssURL);
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_filterResponseData.js
@@ -0,0 +1,128 @@
+"use strict";
+
+XPCOMUtils.defineLazyServiceGetter(this, "proxyService",
+                                   "@mozilla.org/network/protocol-proxy-service;1",
+                                   "nsIProtocolProxyService");
+
+const server = createHttpServer();
+const gHost = "localhost";
+const gPort = server.identity.primaryPort;
+
+const HOSTS = new Set([
+  "example.com",
+  "example.org",
+  "example.net",
+]);
+
+for (let host of HOSTS) {
+  server.identity.add("http", host, 80);
+}
+
+const proxyFilter = {
+  proxyInfo: proxyService.newProxyInfo("http", gHost, gPort, 0, 4096, null),
+
+  applyFilter(service, channel, defaultProxyInfo, callback) {
+    if (HOSTS.has(channel.URI.host)) {
+      callback.onProxyFilterResult(this.proxyInfo);
+    } else {
+      callback.onProxyFilterResult(defaultProxyInfo);
+    }
+  },
+};
+
+proxyService.registerChannelFilter(proxyFilter, 0);
+registerCleanupFunction(() => {
+  proxyService.unregisterChannelFilter(proxyFilter);
+});
+
+server.registerPathHandler("/redirect", (request, response) => {
+  let params = new URLSearchParams(request.queryString);
+  response.setStatusLine(request.httpVersion, 302, "Moved Temporarily");
+  response.setHeader("Location", params.get("redirect_uri"));
+  response.setHeader("Access-Control-Allow-Origin", "*");
+});
+
+server.registerPathHandler("/dummy", (request, response) => {
+  response.setStatusLine(request.httpVersion, 200, "OK");
+  response.setHeader("Access-Control-Allow-Origin", "*");
+  response.write("ok");
+});
+
+Cu.importGlobalProperties(["fetch"]);
+
+add_task(async function() {
+  const {fetch} = Cu.Sandbox("http://example.com/", {wantGlobalProperties: ["fetch"]});
+
+  let extension = ExtensionTestUtils.loadExtension({
+    background() {
+      let pending = [];
+
+      browser.webRequest.onBeforeRequest.addListener(
+        data => {
+          let filter = browser.webRequest.filterResponseData(data.requestId);
+
+          let url = new URL(data.url);
+
+          if (url.searchParams.get("redirect_uri")) {
+            pending.push(
+              new Promise(resolve => { filter.onerror = resolve; }).then(() => {
+                browser.test.assertEq("Channel redirected", filter.error,
+                                      "Got correct error for redirected filter");
+              }));
+          }
+
+          filter.onstart = () => {
+            filter.write(new TextEncoder().encode(data.url));
+          };
+          filter.ondata = event => {
+            let str = new TextDecoder().decode(event.data);
+            browser.test.assertEq("ok", str, `Got unfiltered data for ${data.url}`);
+          };
+          filter.onstop = () => {
+            filter.close();
+          };
+        }, {
+          urls: ["<all_urls>"],
+        },
+        ["blocking"]);
+
+      browser.test.onMessage.addListener(async msg => {
+        if (msg === "done") {
+          await Promise.all(pending);
+          browser.test.notifyPass("stream-filter");
+        }
+      });
+    },
+
+    manifest: {
+      permissions: [
+        "webRequest",
+        "webRequestBlocking",
+        "http://example.com/",
+        "http://example.org/",
+      ],
+    },
+  });
+
+  await extension.startup();
+
+  let results = [
+    ["http://example.com/dummy", "http://example.com/dummy"],
+    ["http://example.org/dummy", "http://example.org/dummy"],
+    ["http://example.net/dummy", "ok"],
+    ["http://example.com/redirect?redirect_uri=http://example.com/dummy", "http://example.com/dummy"],
+    ["http://example.com/redirect?redirect_uri=http://example.org/dummy", "http://example.org/dummy"],
+    ["http://example.com/redirect?redirect_uri=http://example.net/dummy", "ok"],
+    ["http://example.net/redirect?redirect_uri=http://example.com/dummy", "http://example.com/dummy"],
+  ].map(async ([url, expectedResponse]) => {
+    let resp = await fetch(url);
+    let text = await resp.text();
+    equal(text, expectedResponse, `Expected response for ${url}`);
+  });
+
+  await Promise.all(results);
+
+  extension.sendMessage("done");
+  await extension.awaitFinish("stream-filter");
+  await extension.unload();
+});
--- a/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
@@ -76,16 +76,17 @@ head = head.js head_sync.js
 skip-if = os == "android"
 [test_ext_storage_sync_crypto.js]
 skip-if = os == "android"
 [test_ext_storage_telemetry.js]
 skip-if = os == "android" # checking for telemetry needs to be updated: 1384923
 [test_ext_trustworthy_origin.js]
 [test_ext_topSites.js]
 skip-if = os == "android"
+[test_ext_webRequest_filterResponseData.js]
 [test_native_manifests.js]
 subprocess = true
 skip-if = os == "android"
 [test_ext_permissions.js]
 skip-if = os == "android" # Bug 1350559
 [test_proxy_listener.js]
 [test_proxy_scripts.js]
 [test_proxy_scripts_results.js]
--- a/toolkit/components/extensions/webrequest/ChannelWrapper.cpp
+++ b/toolkit/components/extensions/webrequest/ChannelWrapper.cpp
@@ -370,37 +370,31 @@ ChannelWrapper::IsSystemLoad() const
     if (nsIPrincipal* prin = loadInfo->TriggeringPrincipal()) {
       return IsSystemPrincipal(prin);
     }
   }
   return false;
 }
 
 bool
-ChannelWrapper::GetCanModify(ErrorResult& aRv) const
+ChannelWrapper::CanModify() const
 {
-  nsCOMPtr<nsIURI> uri = FinalURI();
-  nsAutoCString spec;
-  if (uri) {
-    uri->GetSpec(spec);
-  }
-  if (!uri || AddonManagerWebAPI::IsValidSite(uri)) {
+  if (WebExtensionPolicy::IsRestrictedURI(FinalURLInfo())) {
     return false;
   }
 
   if (nsCOMPtr<nsILoadInfo> loadInfo = GetLoadInfo()) {
     if (nsIPrincipal* prin = loadInfo->LoadingPrincipal()) {
       if (IsSystemPrincipal(prin)) {
         return false;
       }
 
-      if (prin->GetIsCodebasePrincipal() &&
-          (NS_FAILED(prin->GetURI(getter_AddRefs(uri))) ||
-           AddonManagerWebAPI::IsValidSite(uri))) {
-          return false;
+      auto* docURI = DocumentURLInfo();
+      if (docURI && WebExtensionPolicy::IsRestrictedURI(*docURI)) {
+        return false;
       }
     }
   }
   return true;
 }
 
 already_AddRefed<nsIURI>
 ChannelWrapper::GetOriginURI() const
@@ -642,17 +636,17 @@ ChannelWrapper::GetFrameAncestors(nsILoa
  * Response filtering
  *****************************************************************************/
 
 void
 ChannelWrapper::RegisterTraceableChannel(const WebExtensionPolicy& aAddon, nsITabParent* aTabParent)
 {
   // We can't attach new listeners after the response has started, so don't
   // bother registering anything.
-  if (mResponseStarted) {
+  if (mResponseStarted || !CanModify()) {
     return;
   }
 
   mAddonEntries.Put(aAddon.Id(), aTabParent);
   if (!mChannelEntry) {
     mChannelEntry = WebRequestService::GetSingleton().RegisterChannel(this);
     CheckEventListeners();
   }
--- a/toolkit/components/extensions/webrequest/ChannelWrapper.h
+++ b/toolkit/components/extensions/webrequest/ChannelWrapper.h
@@ -204,17 +204,21 @@ public:
   already_AddRefed<nsIURI> GetDocumentURI() const;
 
 
   already_AddRefed<nsILoadContext> GetLoadContext() const;
 
   already_AddRefed<nsIDOMElement> GetBrowserElement() const;
 
 
-  bool GetCanModify(ErrorResult& aRv) const;
+  bool CanModify() const;
+  bool GetCanModify(ErrorResult& aRv) const
+  {
+    return CanModify();
+  }
 
 
   void GetProxyInfo(dom::Nullable<dom::MozProxyInfo>& aRetVal, ErrorResult& aRv) const;
 
   void GetRemoteAddress(nsCString& aRetVal) const;
 
 
   void GetRequestHeaders(nsTArray<dom::MozHTTPHeader>& aRetVal, ErrorResult& aRv) const;
--- a/toolkit/components/extensions/webrequest/PStreamFilter.ipdl
+++ b/toolkit/components/extensions/webrequest/PStreamFilter.ipdl
@@ -13,21 +13,23 @@ parent:
   async Write(uint8_t[] data);
 
   async FlushedData();
 
   async Suspend();
   async Resume();
   async Close();
   async Disconnect();
+  async Destroy();
 
 child:
   async Resumed();
   async Suspended();
   async Closed();
+  async Error(nsCString error);
 
   async FlushData();
 
   async StartRequest();
   async Data(uint8_t[] data);
   async StopRequest(nsresult aStatus);
 };
 
--- a/toolkit/components/extensions/webrequest/StreamFilterChild.cpp
+++ b/toolkit/components/extensions/webrequest/StreamFilterChild.cpp
@@ -300,16 +300,28 @@ StreamFilterChild::RecvInitialized(bool 
     if (mStreamFilter) {
       mStreamFilter->FireErrorEvent(NS_LITERAL_STRING("Invalid request ID"));
       mStreamFilter = nullptr;
     }
   }
 }
 
 IPCResult
+StreamFilterChild::RecvError(const nsCString& aError)
+{
+  mState = State::Error;
+  if (mStreamFilter) {
+    mStreamFilter->FireErrorEvent(NS_ConvertUTF8toUTF16(aError));
+    mStreamFilter = nullptr;
+  }
+  SendDestroy();
+  return IPC_OK();
+}
+
+IPCResult
 StreamFilterChild::RecvClosed() {
   MOZ_DIAGNOSTIC_ASSERT(mState == State::Closing);
 
   SetNextState();
   return IPC_OK();
 }
 
 IPCResult
--- a/toolkit/components/extensions/webrequest/StreamFilterChild.h
+++ b/toolkit/components/extensions/webrequest/StreamFilterChild.h
@@ -94,16 +94,17 @@ public:
 
   void  RecvInitialized(bool aSuccess);
 
 protected:
 
   virtual IPCResult RecvStartRequest() override;
   virtual IPCResult RecvData(Data&& data) override;
   virtual IPCResult RecvStopRequest(const nsresult& aStatus) override;
+  virtual IPCResult RecvError(const nsCString& aError) override;
 
   virtual IPCResult RecvClosed() override;
   virtual IPCResult RecvSuspended() override;
   virtual IPCResult RecvResumed() override;
   virtual IPCResult RecvFlushData() override;
 
   virtual void DeallocPStreamFilterChild() override;
 
--- a/toolkit/components/extensions/webrequest/StreamFilterParent.cpp
+++ b/toolkit/components/extensions/webrequest/StreamFilterParent.cpp
@@ -220,16 +220,17 @@ StreamFilterParent::Broken()
   mState = State::Disconnecting;
 
   RefPtr<StreamFilterParent> self(this);
   RunOnIOThread(FUNC, [=] {
     self->FlushBufferedData();
 
     RunOnActorThread(FUNC, [=] {
       if (self->IPCActive()) {
+        self->mDisconnected = true;
         self->mState = State::Disconnected;
       }
     });
   });
 }
 
 /*****************************************************************************
  * State change requests
@@ -263,16 +264,25 @@ StreamFilterParent::Destroy()
   ActorThread()->Dispatch(
     NewRunnableMethod("StreamFilterParent::Close",
                       this,
                       &StreamFilterParent::Close),
     NS_DISPATCH_NORMAL);
 }
 
 IPCResult
+StreamFilterParent::RecvDestroy()
+{
+  AssertIsActorThread();
+  Destroy();
+  return IPC_OK();
+}
+
+
+IPCResult
 StreamFilterParent::RecvSuspend()
 {
   AssertIsActorThread();
 
   if (mState == State::TransferringData) {
     RefPtr<StreamFilterParent> self(this);
     RunOnMainThread(FUNC, [=] {
       self->mChannel->Suspend();
@@ -306,17 +316,16 @@ StreamFilterParent::RecvResume()
         if (self->IPCActive()) {
           self->CheckResult(self->SendResumed());
         }
       });
     });
   }
   return IPC_OK();
 }
-
 IPCResult
 StreamFilterParent::RecvDisconnect()
 {
   AssertIsActorThread();
 
   if (mState == State::Suspended) {
     RefPtr<StreamFilterParent> self(this);
     RunOnMainThread(FUNC, [=] {
@@ -341,16 +350,17 @@ StreamFilterParent::RecvFlushedData()
   Destroy();
 
   RefPtr<StreamFilterParent> self(this);
   RunOnIOThread(FUNC, [=] {
     self->FlushBufferedData();
 
     RunOnActorThread(FUNC, [=] {
       self->mState = State::Disconnected;
+      self->mDisconnected = true;
     });
   });
   return IPC_OK();
 }
 
 /*****************************************************************************
  * Data output
  *****************************************************************************/
@@ -401,17 +411,29 @@ StreamFilterParent::Write(Data& aData)
 
 NS_IMETHODIMP
 StreamFilterParent::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
 {
   AssertIsMainThread();
 
   mContext = aContext;
 
-  if (mState != State::Disconnected) {
+  if (aRequest != mChannel) {
+    mDisconnected = true;
+
+    RefPtr<StreamFilterParent> self(this);
+    RunOnActorThread(FUNC, [=] {
+      if (self->IPCActive()) {
+        self->mState = State::Disconnected;
+        CheckResult(self->SendError(NS_LITERAL_CSTRING("Channel redirected")));
+      }
+    });
+  }
+
+  if (!mDisconnected) {
     RefPtr<StreamFilterParent> self(this);
     RunOnActorThread(FUNC, [=] {
       if (self->IPCActive()) {
         self->mState = State::TransferringData;
         self->CheckResult(self->SendStartRequest());
       }
     });
   }
@@ -434,17 +456,17 @@ StreamFilterParent::OnStartRequest(nsIRe
 NS_IMETHODIMP
 StreamFilterParent::OnStopRequest(nsIRequest* aRequest,
                                   nsISupports* aContext,
                                   nsresult aStatusCode)
 {
   AssertIsMainThread();
 
   mReceivedStop = true;
-  if (mState == State::Disconnected) {
+  if (mDisconnected) {
     return EmitStopRequest(aStatusCode);
   }
 
   RefPtr<StreamFilterParent> self(this);
   RunOnActorThread(FUNC, [=] {
     if (self->IPCActive()) {
       self->CheckResult(self->SendStopRequest(aStatusCode));
     }
@@ -480,17 +502,17 @@ NS_IMETHODIMP
 StreamFilterParent::OnDataAvailable(nsIRequest* aRequest,
                                     nsISupports* aContext,
                                     nsIInputStream* aInputStream,
                                     uint64_t aOffset,
                                     uint32_t aCount)
 {
   AssertIsIOThread();
 
-  if (mState == State::Disconnected) {
+  if (mDisconnected) {
     // If we're offloading data in a thread pool, it's possible that we'll
     // have buffered some additional data while waiting for the buffer to
     // flush. So, if there's any buffered data left, flush that before we
     // flush this incoming data.
     //
     // Note: When in the eDisconnected state, the buffer list is guaranteed
     // never to be accessed by another thread during an OnDataAvailable call.
     if (!mBufferedData.isEmpty()) {
--- a/toolkit/components/extensions/webrequest/StreamFilterParent.h
+++ b/toolkit/components/extensions/webrequest/StreamFilterParent.h
@@ -88,16 +88,17 @@ protected:
   virtual ~StreamFilterParent();
 
   virtual IPCResult RecvWrite(Data&& aData) override;
   virtual IPCResult RecvFlushedData() override;
   virtual IPCResult RecvSuspend() override;
   virtual IPCResult RecvResume() override;
   virtual IPCResult RecvClose() override;
   virtual IPCResult RecvDisconnect() override;
+  virtual IPCResult RecvDestroy() override;
 
   virtual void DeallocPStreamFilterParent() override;
 
 private:
   bool IPCActive()
   {
     return (mState != State::Closed &&
             mState != State::Disconnecting &&
@@ -170,16 +171,17 @@ private:
   nsCOMPtr<nsIEventTarget> mIOThread;
 
   RefPtr<net::ChannelEventQueue> mQueue;
 
   Mutex mBufferMutex;
 
   bool mReceivedStop;
   bool mSentStop;
+  bool mDisconnected = false;
 
   nsCOMPtr<nsISupports> mContext;
   uint64_t mOffset;
 
   volatile State mState;
 };
 
 } // namespace extensions
--- a/toolkit/components/startup/nsAppStartup.cpp
+++ b/toolkit/components/startup/nsAppStartup.cpp
@@ -22,16 +22,18 @@
 #include "nsIWindowWatcher.h"
 #include "nsIXULRuntime.h"
 #include "nsIXULWindow.h"
 #include "nsNativeCharsetUtils.h"
 #include "nsThreadUtils.h"
 #include "nsAutoPtr.h"
 #include "nsString.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/Unused.h"
 #include "GeckoProfiler.h"
 
 #include "prprf.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsWidgetsCID.h"
 #include "nsAppRunner.h"
 #include "nsAppShellCID.h"
 #include "nsXPCOMCIDInternal.h"
@@ -917,31 +919,45 @@ nsAppStartup::TrackStartupCrashBegin(boo
   nsCOMPtr<nsIPrefService> prefs = Preferences::GetService();
   rv = static_cast<Preferences *>(prefs.get())->SavePrefFileBlocking(); // flush prefs to disk since we are tracking crashes
   NS_ENSURE_SUCCESS(rv, rv);
 
   GetAutomaticSafeModeNecessary(aIsSafeModeNecessary);
   return rv;
 }
 
+static nsresult
+RemoveIncompleteStartupFile()
+{
+  nsCOMPtr<nsIFile> file;
+  MOZ_TRY(NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR, getter_AddRefs(file)));
+
+  MOZ_TRY_VAR(file, mozilla::startup::GetIncompleteStartupFile(file));
+  return file->Remove(false);
+}
+
 NS_IMETHODIMP
 nsAppStartup::TrackStartupCrashEnd()
 {
   bool inSafeMode = false;
   nsCOMPtr<nsIXULRuntime> xr = do_GetService(XULRUNTIME_SERVICE_CONTRACTID);
   if (xr)
     xr->GetInSafeMode(&inSafeMode);
 
   // return if we already ended or we're restarting into safe mode
   if (mStartupCrashTrackingEnded || (mIsSafeModeNecessary && !inSafeMode))
     return NS_OK;
   mStartupCrashTrackingEnded = true;
 
   StartupTimeline::Record(StartupTimeline::STARTUP_CRASH_DETECTION_END);
 
+  // Remove the incomplete startup canary file, so the next startup doesn't
+  // detect a recent startup crash.
+  Unused << NS_WARN_IF(NS_FAILED(RemoveIncompleteStartupFile()));
+
   // Use the timestamp of XRE_main as an approximation for the lock file timestamp.
   // See MAX_STARTUP_BUFFER for the buffer time period.
   TimeStamp mainTime = StartupTimeline::Get(StartupTimeline::MAIN);
   nsresult rv;
 
   if (mainTime.IsNull()) {
     NS_WARNING("Could not get StartupTimeline::MAIN time.");
   } else {
--- a/toolkit/components/telemetry/Telemetry.cpp
+++ b/toolkit/components/telemetry/Telemetry.cpp
@@ -3,17 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include <algorithm>
 
 #include <prio.h>
 #include <prproces.h>
-#ifdef XP_LINUX
+#if defined(XP_UNIX) && !defined(XP_DARWIN)
 #include <time.h>
 #else
 #include <chrono>
 #endif
 
 #include "mozilla/dom/ToJSValue.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/Atomics.h"
@@ -55,16 +55,17 @@
 #include "ipc/TelemetryIPCAccumulator.h"
 #include "TelemetryScalar.h"
 #include "TelemetryEvent.h"
 #include "WebrtcTelemetry.h"
 #include "nsTHashtable.h"
 #include "nsHashKeys.h"
 #include "nsBaseHashtable.h"
 #include "nsClassHashtable.h"
+#include "nsDataHashtable.h"
 #include "nsXULAppAPI.h"
 #include "nsReadableUtils.h"
 #include "nsThreadUtils.h"
 #if defined(XP_WIN)
 #include "nsUnicharUtils.h"
 #endif
 #include "nsNetCID.h"
 #include "nsNetUtil.h"
@@ -94,16 +95,17 @@
 #include "KeyedStackCapturer.h"
 #endif // MOZ_GECKO_PROFILER
 
 namespace {
 
 using namespace mozilla;
 using namespace mozilla::HangMonitor;
 using Telemetry::Common::AutoHashtable;
+using Telemetry::Common::ToJSString;
 using mozilla::dom::Promise;
 using mozilla::dom::AutoJSAPI;
 using mozilla::Telemetry::HangReports;
 using mozilla::Telemetry::CombinedStacks;
 using mozilla::Telemetry::ComputeAnnotationsKey;
 using mozilla::Telemetry::TelemetryIOInterposeObserver;
 
 #if defined(MOZ_GECKO_PROFILER)
@@ -786,26 +788,32 @@ TelemetryImpl::SnapshotCapturedStacks(bo
 }
 
 #if defined(MOZ_GECKO_PROFILER)
 class GetLoadedModulesResultRunnable final : public Runnable
 {
   nsMainThreadPtrHandle<Promise> mPromise;
   SharedLibraryInfo mRawModules;
   nsCOMPtr<nsIThread> mWorkerThread;
+#if defined(XP_WIN)
+  nsDataHashtable<nsStringHashKey, nsString> mCertSubjects;
+#endif // defined(XP_WIN)
 
 public:
   GetLoadedModulesResultRunnable(const nsMainThreadPtrHandle<Promise>& aPromise,
                                  const SharedLibraryInfo& rawModules)
     : mozilla::Runnable("GetLoadedModulesResultRunnable")
     , mPromise(aPromise)
     , mRawModules(rawModules)
     , mWorkerThread(do_GetCurrentThread())
   {
     MOZ_ASSERT(!NS_IsMainThread());
+#if defined(XP_WIN)
+    ObtainCertSubjects();
+#endif // defined(XP_WIN)
   }
 
   NS_IMETHOD
   Run() override
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     mWorkerThread->Shutdown();
@@ -895,22 +903,19 @@ public:
 
       if (!JS_DefineProperty(cx, moduleObj, "version", version, JSPROP_ENUMERATE)) {
         mPromise->MaybeReject(NS_ERROR_FAILURE);
         return NS_OK;
       }
 
 #if defined(XP_WIN)
       // Cert Subject.
-      // NB: Currently we cannot lower this down to the profiler layer due to
-      // differing startup dependencies between the profiler and DllServices.
-      RefPtr<DllServices> dllSvc(DllServices::Get());
-      auto orgName = dllSvc->GetBinaryOrgName(info.GetModulePath().get());
-      if (orgName) {
-        JS::RootedString jsOrg(cx, JS_NewUCStringCopyZ(cx, (char16_t*) orgName.get()));
+      nsString* subject = mCertSubjects.GetValue(info.GetModulePath());
+      if (subject) {
+        JS::RootedString jsOrg(cx, ToJSString(cx, *subject));
         if (!jsOrg) {
           mPromise->MaybeReject(NS_ERROR_FAILURE);
           return NS_OK;
         }
 
         JS::RootedValue certSubject(cx);
         certSubject.setString(jsOrg);
 
@@ -926,16 +931,38 @@ public:
         mPromise->MaybeReject(NS_ERROR_FAILURE);
         return NS_OK;
       }
     }
 
     mPromise->MaybeResolve(moduleArray);
     return NS_OK;
   }
+
+private:
+#if defined(XP_WIN)
+  void ObtainCertSubjects()
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+
+    // NB: Currently we cannot lower this down to the profiler layer due to
+    // differing startup dependencies between the profiler and DllServices.
+    RefPtr<DllServices> dllSvc(DllServices::Get());
+
+    for (unsigned int i = 0, n = mRawModules.GetSize(); i != n; i++) {
+      const SharedLibrary& info = mRawModules.GetEntry(i);
+
+      auto orgName = dllSvc->GetBinaryOrgName(info.GetModulePath().get());
+      if (orgName) {
+        mCertSubjects.Put(info.GetModulePath(),
+                          nsDependentString(orgName.get()));
+      }
+    }
+  }
+#endif // defined(XP_WIN)
 };
 
 class GetLoadedModulesRunnable final : public Runnable
 {
   nsMainThreadPtrHandle<Promise> mPromise;
 
 public:
   explicit GetLoadedModulesRunnable(
@@ -1669,25 +1696,25 @@ NS_IMETHODIMP
 TelemetryImpl::MsSinceProcessStart(double* aResult)
 {
   return Telemetry::Common::MsSinceProcessStart(aResult);
 }
 
 NS_IMETHODIMP
 TelemetryImpl::MsSystemNow(double* aResult)
 {
-#ifdef XP_LINUX
+#if defined(XP_UNIX) && !defined(XP_DARWIN)
   timespec ts;
   clock_gettime(CLOCK_REALTIME, &ts);
   *aResult = ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
 #else
   using namespace std::chrono;
   milliseconds ms = duration_cast<milliseconds>(system_clock::now().time_since_epoch());
   *aResult = static_cast<double>(ms.count());
-#endif // XP_LINUX
+#endif // XP_UNIX && !XP_DARWIN
 
   return NS_OK;
 }
 
 // Telemetry Scalars IDL Implementation
 
 NS_IMETHODIMP
 TelemetryImpl::ScalarAdd(const nsACString& aName, JS::HandleValue aVal, JSContext* aCx)
--- a/toolkit/components/telemetry/TelemetryCommon.cpp
+++ b/toolkit/components/telemetry/TelemetryCommon.cpp
@@ -172,11 +172,17 @@ IsValidIdentifierString(const nsACString
 
 JSString*
 ToJSString(JSContext* cx, const nsACString& aStr)
 {
   const NS_ConvertUTF8toUTF16 wide(aStr);
   return JS_NewUCStringCopyN(cx, wide.Data(), wide.Length());
 }
 
+JSString*
+ToJSString(JSContext* cx, const nsAString& aStr)
+{
+  return JS_NewUCStringCopyN(cx, aStr.Data(), aStr.Length());
+}
+
 } // namespace Common
 } // namespace Telemetry
 } // namespace mozilla
--- a/toolkit/components/telemetry/TelemetryCommon.h
+++ b/toolkit/components/telemetry/TelemetryCommon.h
@@ -114,13 +114,23 @@ IsValidIdentifierString(const nsACString
  *
  * @param cx The JS context.
  * @param aStr The UTF8 string.
  * @returns a JavaScript string.
  */
 JSString*
 ToJSString(JSContext* cx, const nsACString& aStr);
 
+/**
+ * Convert the given UTF16 string to a JavaScript string.
+ *
+ * @param cx The JS context.
+ * @param aStr The UTF16 string.
+ * @returns a JavaScript string.
+ */
+JSString*
+ToJSString(JSContext* cx, const nsAString& aStr);
+
 } // namespace Common
 } // namespace Telemetry
 } // namespace mozilla
 
 #endif // TelemetryCommon_h__
--- a/toolkit/components/telemetry/TelemetryEnvironment.jsm
+++ b/toolkit/components/telemetry/TelemetryEnvironment.jsm
@@ -702,17 +702,17 @@ EnvironmentAddonBuilder.prototype = {
           let installDate = new Date(Math.max(0, addon.installDate));
           Object.assign(activeAddons[addon.id], {
             blocklisted: (addon.blocklistState !== Ci.nsIBlocklistService.STATE_NOT_BLOCKED),
             description: limitStringToLength(addon.description, MAX_ADDON_STRING_LENGTH),
             name: limitStringToLength(addon.name, MAX_ADDON_STRING_LENGTH),
             userDisabled: enforceBoolean(addon.userDisabled),
             appDisabled: addon.appDisabled,
             foreignInstall: enforceBoolean(addon.foreignInstall),
-            hasBinaryComponents: addon.hasBinaryComponents,
+            hasBinaryComponents: false,
             installDay: Utils.millisecondsToDays(installDate.getTime()),
             signedState: addon.signedState,
           });
         }
       } catch (ex) {
         this._environment._log.error("_getActiveAddons - An addon was discarded due to an error", ex);
         continue;
       }
@@ -742,17 +742,17 @@ EnvironmentAddonBuilder.prototype = {
         blocklisted: (theme.blocklistState !== Ci.nsIBlocklistService.STATE_NOT_BLOCKED),
         description: limitStringToLength(theme.description, MAX_ADDON_STRING_LENGTH),
         name: limitStringToLength(theme.name, MAX_ADDON_STRING_LENGTH),
         userDisabled: enforceBoolean(theme.userDisabled),
         appDisabled: theme.appDisabled,
         version: limitStringToLength(theme.version, MAX_ADDON_STRING_LENGTH),
         scope: theme.scope,
         foreignInstall: enforceBoolean(theme.foreignInstall),
-        hasBinaryComponents: theme.hasBinaryComponents,
+        hasBinaryComponents: false,
         installDay: Utils.millisecondsToDays(installDate.getTime()),
         updateDay: Utils.millisecondsToDays(updateDate.getTime()),
       };
     }
 
     return activeTheme;
   },
 
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js
@@ -747,17 +747,16 @@ function checkPlugin(data) {
 
   Assert.ok(Array.isArray(data.mimeTypes));
   for (let type of data.mimeTypes) {
     Assert.ok(checkString(type));
   }
 }
 
 function checkTheme(data) {
-  // "hasBinaryComponents" is not available when testing.
   const EXPECTED_THEME_FIELDS_TYPES = {
     id: "string",
     blocklisted: "boolean",
     name: "string",
     userDisabled: "boolean",
     appDisabled: "boolean",
     version: "string",
     scope: "number",
--- a/toolkit/modules/addons/WebRequest.jsm
+++ b/toolkit/modules/addons/WebRequest.jsm
@@ -759,17 +759,17 @@ HttpObserverManager = {
           data.requestHeaders = requestHeaders.toArray();
         }
 
         if (opts.responseHeaders) {
           responseHeaders = responseHeaders || new ResponseHeaderChanger(channel);
           data.responseHeaders = responseHeaders.toArray();
         }
 
-        if (opts.requestBody) {
+        if (opts.requestBody && channel.canModify) {
           requestBody = requestBody || WebRequestUpload.createRequestBody(channel.channel);
           data.requestBody = requestBody;
         }
 
         try {
           let result = callback(data);
 
           // isProxy is set during onAuth if the auth request is for a proxy.
--- a/toolkit/mozapps/extensions/internal/XPIInstall.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIInstall.jsm
@@ -22,18 +22,16 @@ ChromeUtils.import("resource://gre/modul
 ChromeUtils.defineModuleGetter(this, "AddonRepository",
                                "resource://gre/modules/addons/AddonRepository.jsm");
 ChromeUtils.defineModuleGetter(this, "AddonSettings",
                                "resource://gre/modules/addons/AddonSettings.jsm");
 ChromeUtils.defineModuleGetter(this, "AppConstants",
                                "resource://gre/modules/AppConstants.jsm");
 XPCOMUtils.defineLazyGetter(this, "CertUtils",
                             () => ChromeUtils.import("resource://gre/modules/CertUtils.jsm", {}));
-ChromeUtils.defineModuleGetter(this, "ChromeManifestParser",
-                               "resource://gre/modules/ChromeManifestParser.jsm");
 ChromeUtils.defineModuleGetter(this, "ExtensionData",
                                "resource://gre/modules/Extension.jsm");
 ChromeUtils.defineModuleGetter(this, "FileUtils",
                                "resource://gre/modules/FileUtils.jsm");
 XPCOMUtils.defineLazyGetter(this, "IconDetails", () => {
   return ChromeUtils.import("resource://gre/modules/ExtensionParent.jsm", {}).ExtensionParent.IconDetails;
 });
 ChromeUtils.defineModuleGetter(this, "LightweightThemeManager",
@@ -338,17 +336,16 @@ async function loadManifestFromWebManife
   let addon = new AddonInternal();
   addon.id = bss.id;
   addon.version = manifest.version;
   addon.type = extension.type === "extension" ?
                "webextension" : `webextension-${extension.type}`;
   addon.unpack = false;
   addon.strictCompatibility = true;
   addon.bootstrap = true;
-  addon.hasBinaryComponents = false;
   addon.multiprocessCompatible = true;
   addon.internalName = null;
   addon.updateURL = bss.update_url;
   addon.updateKey = null;
   addon.optionsBrowserStyle = true;
   addon.optionsURL = null;
   addon.optionsType = null;
   addon.aboutURL = null;
@@ -798,21 +795,16 @@ var loadManifestFromDir = async function
       addon.icons[48] = "icon.png";
     }
 
     let icon64File = getFile("icon64.png", aDir);
 
     if (icon64File.exists()) {
       addon.icons[64] = "icon64.png";
     }
-
-    let file = getFile("chrome.manifest", aDir);
-    let chromeManifest = ChromeManifestParser.parseSync(Services.io.newFileURI(file));
-    addon.hasBinaryComponents = ChromeManifestParser.hasType(chromeManifest,
-                                                             "binary-component");
     return addon;
   }
 
   let file = getManifestFileForDir(aDir);
   if (!file) {
     throw new Error("Directory " + aDir.path + " does not contain a valid " +
                     "install manifest");
   }
@@ -871,26 +863,16 @@ var loadManifestFromZipReader = async fu
       addon.icons[32] = "icon.png";
       addon.icons[48] = "icon.png";
     }
 
     if (aZipReader.hasEntry("icon64.png")) {
       addon.icons[64] = "icon64.png";
     }
 
-    // Binary components can only be loaded from unpacked addons.
-    if (addon.unpack) {
-      let uri = buildJarURI(aZipReader.file, "chrome.manifest");
-      let chromeManifest = ChromeManifestParser.parseSync(uri);
-      addon.hasBinaryComponents = ChromeManifestParser.hasType(chromeManifest,
-                                                               "binary-component");
-    } else {
-      addon.hasBinaryComponents = false;
-    }
-
     return addon;
   }
 
   let entry = getManifestEntryForZipReader(aZipReader);
   if (!entry) {
     throw new Error("File " + aZipReader.file.path + " does not contain a valid " +
                     "install manifest");
   }
@@ -2733,18 +2715,17 @@ UpdateChecker.prototype = {
 
     let ignoreMaxVersion = false;
     let ignoreStrictCompat = false;
     if (!AddonManager.checkCompatibility) {
       ignoreMaxVersion = true;
       ignoreStrictCompat = true;
     } else if (this.addon.type in COMPATIBLE_BY_DEFAULT_TYPES &&
                !AddonManager.strictCompatibility &&
-               !this.addon.strictCompatibility &&
-               !this.addon.hasBinaryComponents) {
+               !this.addon.strictCompatibility) {
       ignoreMaxVersion = true;
     }
 
     // Always apply any compatibility update for the current version
     let compatUpdate = AUC.getCompatibilityUpdate(aUpdates, this.addon.version,
                                                   this.syncCompatibility,
                                                   null, null,
                                                   ignoreMaxVersion,
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -4996,18 +4996,17 @@ AddonInternal.prototype = {
     if (app.id == Services.appinfo.ID)
       version = aAppVersion;
     else if (app.id == TOOLKIT_ID)
       version = aPlatformVersion;
 
     // Only extensions and dictionaries can be compatible by default; themes
     // and language packs always use strict compatibility checking.
     if (this.type in COMPATIBLE_BY_DEFAULT_TYPES &&
-        !AddonManager.strictCompatibility && !this.strictCompatibility &&
-        !this.hasBinaryComponents) {
+        !AddonManager.strictCompatibility && !this.strictCompatibility) {
 
       // The repository can specify compatibility overrides.
       // Note: For now, only blacklisting is supported by overrides.
       let overrides = AddonRepository.getCompatibilityOverridesSync(this.id);
       if (overrides) {
         let override = AddonRepository.findMatchingCompatOverride(this.version,
                                                                   overrides);
         if (override) {
@@ -5747,17 +5746,17 @@ function defineAddonWrapperProperty(name
   Object.defineProperty(AddonWrapper.prototype, name, {
     get: getter,
     enumerable: true,
   });
 }
 
 ["id", "syncGUID", "version", "isCompatible", "isPlatformCompatible",
  "providesUpdatesSecurely", "blocklistState", "blocklistURL", "appDisabled",
- "softDisabled", "skinnable", "size", "foreignInstall", "hasBinaryComponents",
+ "softDisabled", "skinnable", "size", "foreignInstall",
  "strictCompatibility", "updateURL", "dependencies",
  "getDataDirectory", "multiprocessCompatible", "signedState", "mpcOptedOut",
  "isCorrectlySigned"].forEach(function(aProp) {
    defineAddonWrapperProperty(aProp, function() {
      let addon = addonFor(this);
      return (aProp in addon) ? addon[aProp] : undefined;
    });
 });
--- a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
+++ b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
@@ -53,17 +53,17 @@ const KEY_APP_TEMPORARY               = 
 // Properties to save in JSON file
 const PROP_JSON_FIELDS = ["id", "syncGUID", "location", "version", "type",
                           "internalName", "updateURL", "updateKey", "optionsURL",
                           "optionsType", "optionsBrowserStyle", "aboutURL",
                           "defaultLocale", "visible", "active", "userDisabled",
                           "appDisabled", "pendingUninstall", "installDate",
                           "updateDate", "applyBackgroundUpdates", "bootstrap", "path",
                           "skinnable", "size", "sourceURI", "releaseNotesURI",
-                          "softDisabled", "foreignInstall", "hasBinaryComponents",
+                          "softDisabled", "foreignInstall",
                           "strictCompatibility", "locales", "targetApplications",
                           "targetPlatforms", "multiprocessCompatible", "signedState",
                           "seen", "dependencies", "hasEmbeddedWebExtension", "mpcOptedOut",
                           "userPermissions", "icons", "iconURL", "icon64URL",
                           "blocklistState", "blocklistURL", "startupData"];
 
 // Time to wait before async save of XPI JSON database, in milliseconds
 const ASYNC_SAVE_DELAY_MS = 20;
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/addons/test_install5/chrome.manifest
+++ /dev/null
@@ -1,1 +0,0 @@
-binary-component components/mycomponent.so
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/addons/test_install5/install.rdf
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0"?>
-
-<!-- An extension that is incompatible with the XPCShell test suite and
-     has binary components, so won't be compatible-by-default. -->
-<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
-
-  <Description about="urn:mozilla:install-manifest">
-    <em:id>addon5@tests.mozilla.org</em:id>
-    <em:version>1.0</em:version>
-
-    <!-- Front End MetaData -->
-    <em:name>Real Test 5</em:name>
-    <em:description>Test Description</em:description>
-    <em:unpack>true</em:unpack>
-
-    <em:targetApplication>
-      <Description>
-        <em:id>xpcshell@tests.mozilla.org</em:id>
-        <em:minVersion>0</em:minVersion>
-        <em:maxVersion>0</em:maxVersion>
-      </Description>
-    </em:targetApplication>
-
-  </Description>
-</RDF>
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/test_hasbinarycomponents.js
+++ /dev/null
@@ -1,82 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- */
-
-// Tests detection of binary components via parsing of chrome manifests.
-
-const profileDir = gProfD.clone();
-profileDir.append("extensions");
-
-function run_test() {
-  do_test_pending();
-  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "2", "2");
-
-  startupManager();
-
-  installAllFiles([do_get_addon("test_chromemanifest_1"),
-                   do_get_addon("test_chromemanifest_2"),
-                   do_get_addon("test_chromemanifest_3"),
-                   do_get_addon("test_chromemanifest_4"),
-                   do_get_addon("test_chromemanifest_5")],
-                  async function() {
-
-    await promiseRestartManager();
-
-    AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
-                                 "addon2@tests.mozilla.org",
-                                 "addon3@tests.mozilla.org",
-                                 "addon4@tests.mozilla.org",
-                                 "addon5@tests.mozilla.org"],
-                                async function([a1, a2, a3, a4, a5]) {
-      // addon1 has no binary components
-      Assert.notEqual(a1, null);
-      Assert.ok(!a1.userDisabled);
-      Assert.ok(!a1.hasBinaryComponents);
-      Assert.ok(a1.isCompatible);
-      Assert.ok(!a1.appDisabled);
-      Assert.ok(a1.isActive);
-      Assert.ok(isExtensionInAddonsList(profileDir, a1.id));
-
-      // addon2 has a binary component, is compatible
-      Assert.notEqual(a2, null);
-      Assert.ok(!a2.userDisabled);
-      Assert.ok(a2.hasBinaryComponents);
-      Assert.ok(a2.isCompatible);
-      Assert.ok(!a2.appDisabled);
-      Assert.ok(a2.isActive);
-      Assert.ok(isExtensionInAddonsList(profileDir, a2.id));
-
-      // addon3 has a binary component, is incompatible
-      Assert.notEqual(a3, null);
-      Assert.ok(!a3.userDisabled);
-      Assert.ok(a2.hasBinaryComponents);
-      Assert.ok(!a3.isCompatible);
-      Assert.ok(a3.appDisabled);
-      Assert.ok(!a3.isActive);
-      Assert.ok(!isExtensionInAddonsList(profileDir, a3.id));
-
-      // addon4 has a binary component listed in a sub-manifest, is incompatible
-      Assert.notEqual(a4, null);
-      Assert.ok(!a4.userDisabled);
-      Assert.ok(a2.hasBinaryComponents);
-      Assert.ok(!a4.isCompatible);
-      Assert.ok(a4.appDisabled);
-      Assert.ok(!a4.isActive);
-      Assert.ok(!isExtensionInAddonsList(profileDir, a4.id));
-
-      // addon5 has a binary component, but is set to not unpack
-      Assert.notEqual(a5, null);
-      Assert.ok(!a5.userDisabled);
-      if (TEST_UNPACKED)
-        Assert.ok(a5.hasBinaryComponents);
-      else
-        Assert.ok(!a5.hasBinaryComponents);
-      Assert.ok(a5.isCompatible);
-      Assert.ok(!a5.appDisabled);
-      Assert.ok(a5.isActive);
-      Assert.ok(isExtensionInAddonsList(profileDir, a5.id));
-
-      executeSoon(do_test_finished);
-    });
-  });
-}
--- a/toolkit/mozapps/extensions/test/xpcshell/test_install.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_install.js
@@ -1398,70 +1398,17 @@ function finish_test_27(aInstall) {
   }, [
     "onInstallCancelled"
   ]);
 
   aInstall.cancel();
 
   ensure_test_completed();
 
-  run_test_28();
-}
-
-// Tests that an install that isn't strictly compatible and has
-// binary components correctly has appDisabled set (see bug 702868).
-function run_test_28() {
-  prepare_test({ }, [
-    "onNewInstall"
-  ]);
-
-  let url = "http://localhost:" + gPort + "/addons/test_install5.xpi";
-  AddonManager.getInstallForURL(url, function(install) {
-    ensure_test_completed();
-
-    Assert.notEqual(install, null);
-    Assert.equal(install.version, "1.0");
-    Assert.equal(install.name, "Real Test 5");
-    Assert.equal(install.state, AddonManager.STATE_AVAILABLE);
-
-    AddonManager.getInstallsByTypes(null, function(activeInstalls) {
-      Assert.equal(activeInstalls.length, 1);
-      Assert.equal(activeInstalls[0], install);
-
-      prepare_test({}, [
-        "onDownloadStarted",
-        "onDownloadEnded",
-        "onInstallStarted"
-      ], check_test_28);
-      install.install();
-    });
-  }, "application/x-xpinstall", null, "Real Test 5", null, "1.0");
-}
-
-function check_test_28(install) {
-  ensure_test_completed();
-  Assert.equal(install.version, "1.0");
-  Assert.equal(install.name, "Real Test 5");
-  Assert.equal(install.state, AddonManager.STATE_INSTALLING);
-  Assert.equal(install.existingAddon, null);
-  Assert.ok(!install.addon.isCompatible);
-  Assert.ok(install.addon.appDisabled);
-
-  prepare_test({}, [
-    "onInstallCancelled"
-  ], finish_test_28);
-  return false;
-}
-
-function finish_test_28(install) {
-  prepare_test({}, [
-    "onDownloadCancelled"
-  ], run_test_29);
-
-  install.cancel();
+  run_test_29();
 }
 
 // Tests that an install with a matching compatibility override has appDisabled
 // set correctly.
 function run_test_29() {
   Services.prefs.setBoolPref("extensions.getAddons.cache.enabled", true);
 
   prepare_test({ }, [
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
@@ -209,17 +209,16 @@ tags = blocklist
 [test_gfxBlacklist_Vendor.js]
 tags = blocklist
 [test_gfxBlacklist_Version.js]
 tags = blocklist
 [test_gfxBlacklist_prefs.js]
 # Bug 1248787 - consistently fails
 skip-if = true
 tags = blocklist
-[test_hasbinarycomponents.js]
 [test_install.js]
 [test_install_icons.js]
 # Bug 676992: test consistently hangs on Android
 skip-if = os == "android"
 [test_install_strictcompat.js]
 # Bug 676992: test consistently hangs on Android
 skip-if = os == "android"
 run-sequentially = Uses hardcoded ports in xpi files.
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -11,16 +11,17 @@
 #include "mozilla/Attributes.h"
 #include "mozilla/ChaosMode.h"
 #include "mozilla/IOInterposer.h"
 #include "mozilla/Likely.h"
 #include "mozilla/MemoryChecking.h"
 #include "mozilla/Poison.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Printf.h"
+#include "mozilla/ResultExtensions.h"
 #include "mozilla/ScopeExit.h"
 #include "mozilla/Services.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/intl/LocaleService.h"
 
 #include "nsAppRunner.h"
 #include "mozilla/XREAppData.h"
 #include "mozilla/Bootstrap.h"
@@ -96,20 +97,18 @@
 
 #ifdef XP_WIN
 #include "nsIWinAppHelper.h"
 #include <windows.h>
 #include <intrin.h>
 #include <math.h>
 #include "cairo/cairo-features.h"
 #include "mozilla/WindowsDllBlocklist.h"
-#include "mozilla/mscom/EnsureMTA.h"
 #include "mozilla/mscom/MainThreadRuntime.h"
 #include "mozilla/widget/AudioSession.h"
-#include "mozilla/WindowsVersion.h"
 
 #ifndef PROCESS_DEP_ENABLE
 #define PROCESS_DEP_ENABLE 0x1
 #endif
 #endif
 
 #if defined(MOZ_CONTENT_SANDBOX)
 #include "mozilla/SandboxSettings.h"
@@ -232,16 +231,17 @@
 #include "mozilla/CodeCoverageHandler.h"
 #endif
 
 extern uint32_t gRestartMode;
 extern void InstallSignalHandlers(const char *ProgramName);
 
 #define FILE_COMPATIBILITY_INFO NS_LITERAL_CSTRING("compatibility.ini")
 #define FILE_INVALIDATE_CACHES NS_LITERAL_CSTRING(".purgecaches")
+#define FILE_STARTUP_INCOMPLETE NS_LITERAL_STRING(".startup-incomplete")
 
 int    gArgc;
 char **gArgv;
 
 static const char gToolkitVersion[] = NS_STRINGIFY(GRE_MILESTONE);
 static const char gToolkitBuildID[] = NS_STRINGIFY(MOZ_BUILDID);
 
 static nsIProfileLock* gProfileLock;
@@ -296,16 +296,17 @@ void XRE_LibFuzzerSetDriver(LibFuzzerDri
 #endif
 #endif // FUZZING
 
 namespace mozilla {
 int (*RunGTest)(int*, char**) = 0;
 } // namespace mozilla
 
 using namespace mozilla;
+using namespace mozilla::startup;
 using mozilla::Unused;
 using mozilla::scache::StartupCache;
 using mozilla::dom::ContentParent;
 using mozilla::dom::ContentChild;
 using mozilla::intl::LocaleService;
 
 // Save literal putenv string to environment variable.
 static void
@@ -3120,16 +3121,18 @@ public:
     mAppData = nullptr;
   }
 
   int XRE_main(int argc, char* argv[], const BootstrapConfig& aConfig);
   int XRE_mainInit(bool* aExitFlag);
   int XRE_mainStartup(bool* aExitFlag);
   nsresult XRE_mainRun();
 
+  Result<bool, nsresult> CheckLastStartupWasCrash();
+
   nsCOMPtr<nsINativeAppSupport> mNativeApp;
   nsCOMPtr<nsIToolkitProfileService> mProfileSvc;
   nsCOMPtr<nsIFile> mProfD;
   nsCOMPtr<nsIFile> mProfLD;
   nsCOMPtr<nsIProfileLock> mProfileLock;
 #ifdef MOZ_ENABLE_XREMOTE
   nsCOMPtr<nsIRemoteService> mRemoteService;
   nsProfileLock mRemoteLock;
@@ -3785,16 +3788,61 @@ static void SetShutdownChecks() {
       gShutdownChecks = SCM_RECORD;
     } else if (strcmp(mozShutdownChecksEnv, "nothing") == 0) {
       gShutdownChecks = SCM_NOTHING;
     }
   }
 
 }
 
+namespace mozilla {
+namespace startup {
+  Result<nsCOMPtr<nsIFile>, nsresult>
+  GetIncompleteStartupFile(nsIFile* aProfLD)
+  {
+    nsCOMPtr<nsIFile> crashFile;
+    MOZ_TRY(aProfLD->Clone(getter_AddRefs(crashFile)));
+    MOZ_TRY(crashFile->Append(FILE_STARTUP_INCOMPLETE));
+    return Move(crashFile);
+  }
+}
+}
+
+// Check whether the last startup attempt resulted in a crash within the
+// last 6 hours.
+// Note that this duplicates the logic in nsAppStartup::TrackStartupCrashBegin,
+// which runs too late for our purposes.
+Result<bool, nsresult>
+XREMain::CheckLastStartupWasCrash()
+{
+  constexpr int32_t MAX_TIME_SINCE_STARTUP = 6 * 60 * 60 * 1000;
+
+  nsCOMPtr<nsIFile> crashFile;
+  MOZ_TRY_VAR(crashFile, GetIncompleteStartupFile(mProfLD));
+
+  // Attempt to create the incomplete startup canary file. If the file already
+  // exists, this fails, and we know the last startup was a success. If it
+  // doesn't already exist, it is created, and will be removed at the end of
+  // the startup crash detection window.
+  AutoFDClose fd;
+  Unused << crashFile->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE | PR_EXCL,
+                                        0666, &fd.rwget());
+  if (fd) {
+    return false;
+  }
+
+  PRTime lastModifiedTime;
+  MOZ_TRY(crashFile->GetLastModifiedTime(&lastModifiedTime));
+
+  // If the file exists, and was created within the appropriate time window,
+  // the last startup was recent and resulted in a crash.
+  PRTime now = PR_Now() / PR_USEC_PER_MSEC;
+  return now - lastModifiedTime <= MAX_TIME_SINCE_STARTUP;
+}
+
 /*
  * XRE_mainStartup - Initializes the profile and various other services.
  * Main() will exit early if either return value != 0 or if aExitFlag is
  * true.
  */
 int
 XREMain::XRE_mainStartup(bool* aExitFlag)
 {
@@ -4201,36 +4249,34 @@ XREMain::XRE_mainStartup(bool* aExitFlag
   // profile was started with.  The format of the version stamp is defined
   // by the BuildVersion function.
   // Also check to see if something has happened to invalidate our
   // fastload caches, like an extension upgrade or installation.
 
   // If we see .purgecaches, that means someone did a make.
   // Re-register components to catch potential changes.
   nsCOMPtr<nsIFile> flagFile;
-
   rv = NS_ERROR_FILE_NOT_FOUND;
-  nsCOMPtr<nsIFile> fFlagFile;
   if (mAppData->directory) {
-    rv = mAppData->directory->Clone(getter_AddRefs(fFlagFile));
-  }
-  flagFile = do_QueryInterface(fFlagFile);
+    rv = mAppData->directory->Clone(getter_AddRefs(flagFile));
+  }
   if (flagFile) {
     flagFile->AppendNative(FILE_INVALIDATE_CACHES);
   }
 
   bool cachesOK;
   bool versionOK = CheckCompatibility(mProfD, version, osABI,
                                       mDirProvider.GetGREDir(),
                                       mAppData->directory, flagFile,
                                       &cachesOK);
-  if (CheckArg("purgecaches")) {
-    cachesOK = false;
-  }
-  if (PR_GetEnv("MOZ_PURGE_CACHES")) {
+
+  bool lastStartupWasCrash = CheckLastStartupWasCrash().unwrapOr(false);
+
+  if (CheckArg("purgecaches") || PR_GetEnv("MOZ_PURGE_CACHES") ||
+      lastStartupWasCrash) {
     cachesOK = false;
   }
 
   // Every time a profile is loaded by a build with a different version,
   // it updates the compatibility.ini file saying what version last wrote
   // the fastload caches.  On subsequent launches if the version matches,
   // there is no need for re-registration.  If the user loads the same
   // profile in different builds the component registry must be
@@ -4317,26 +4363,16 @@ XREMain::XRE_mainRun()
   nsresult rv = NS_OK;
   NS_ASSERTION(mScopedXPCOM, "Scoped xpcom not initialized.");
 
 #if defined(XP_WIN)
   RefPtr<mozilla::DllServices> dllServices(mozilla::DllServices::Get());
   auto dllServicesDisable = MakeScopeExit([&dllServices]() {
     dllServices->Disable();
   });
-
-#if defined(NIGHTLY_BUILD)
-  if (!IsWin8OrLater()) {
-    // On Windows 7, ensure that the COM MTA remains alive for the life of the
-    // process. We have seen multiple crashes on that OS when the MTA is
-    // repeatedly set up and torn down, so maintaining at least one consistent
-    // reference should prevent those.
-    mscom::EnsureMTA();
-  }
-#endif // defined(NIGHTLY_BUILD)
 #endif // defined(XP_WIN)
 
 #ifdef NS_FUNCTION_TIMER
   // initialize some common services, so we don't pay the cost for these at odd times later on;
   // SetWindowCreator -> ChromeRegistry -> IOService -> SocketTransportService -> (nspr wspm init), Prefs
   {
     nsCOMPtr<nsISupports> comp;
 
@@ -4802,16 +4838,26 @@ XREMain::XRE_main(int argc, char* argv[]
 #endif
 
   // init
   bool exit = false;
   int result = XRE_mainInit(&exit);
   if (result != 0 || exit)
     return result;
 
+  // If we exit gracefully, remove the startup crash canary file.
+  auto cleanup = MakeScopeExit([&] () -> nsresult {
+    if (mProfLD) {
+      nsCOMPtr<nsIFile> crashFile;
+      MOZ_TRY_VAR(crashFile, GetIncompleteStartupFile(mProfLD));
+      crashFile->Remove(false);
+    }
+    return NS_OK;
+  });
+
   // startup
   result = XRE_mainStartup(&exit);
   if (result != 0 || exit)
     return result;
 
   bool appInitiatedRestart = false;
 
   // Start the real application
--- a/toolkit/xre/nsAppRunner.h
+++ b/toolkit/xre/nsAppRunner.h
@@ -111,16 +111,18 @@ WinLaunchChild(const wchar_t *exePath, i
                char **argv, HANDLE userToken = nullptr,
                HANDLE *hProcess = nullptr);
 #endif
 
 #define NS_NATIVEAPPSUPPORT_CONTRACTID "@mozilla.org/toolkit/native-app-support;1"
 
 namespace mozilla {
 namespace startup {
+Result<nsCOMPtr<nsIFile>, nsresult> GetIncompleteStartupFile(nsIFile* aProfLD);
+
 extern GeckoProcessType sChildProcessType;
 } // namespace startup
 
 const char* PlatformBuildID();
 
 } // namespace mozilla
 
 /**
--- a/xpcom/io/nsLocalFileCommon.cpp
+++ b/xpcom/io/nsLocalFileCommon.cpp
@@ -56,21 +56,29 @@ nsLocalFile::CreateUnique(uint32_t aType
 #else
   nsAutoCString pathName, leafName, rootName, suffix;
   rv = GetNativePath(pathName);
 #endif
   if (NS_FAILED(rv)) {
     return rv;
   }
 
+  auto FailedBecauseExists = [&] (nsresult aRv) {
+    if (aRv == NS_ERROR_FILE_ACCESS_DENIED) {
+      bool exists;
+      return NS_SUCCEEDED(Exists(&exists)) && exists;
+    }
+    return aRv == NS_ERROR_FILE_ALREADY_EXISTS;
+  };
+
   longName = (pathName.Length() + kMaxSequenceNumberLength >
               kMaxFilenameLength);
   if (!longName) {
     rv = Create(aType, aAttributes);
-    if (rv != NS_ERROR_FILE_ALREADY_EXISTS) {
+    if (!FailedBecauseExists(rv)) {
       return rv;
     }
   }
 
 #ifdef XP_WIN
   rv = GetLeafName(leafName);
   if (NS_FAILED(rv)) {
     return rv;
@@ -123,32 +131,32 @@ nsLocalFile::CreateUnique(uint32_t aType
         return NS_ERROR_FILE_UNRECOGNIZED_PATH;
       }
     }
 
     rootName.SetLength(maxRootLength);
     SetNativeLeafName(rootName + suffix);
 #endif
     nsresult rvCreate = Create(aType, aAttributes);
-    if (rvCreate != NS_ERROR_FILE_ALREADY_EXISTS) {
+    if (!FailedBecauseExists(rvCreate)) {
       return rvCreate;
     }
   }
 
   for (int indx = 1; indx < 10000; ++indx) {
     // start with "Picture-1.jpg" after "Picture.jpg" exists
 #ifdef XP_WIN
     SetLeafName(rootName +
                 NS_ConvertASCIItoUTF16(nsPrintfCString("-%d", indx)) +
                 suffix);
 #else
     SetNativeLeafName(rootName + nsPrintfCString("-%d", indx) + suffix);
 #endif
     rv = Create(aType, aAttributes);
-    if (NS_SUCCEEDED(rv) || rv != NS_ERROR_FILE_ALREADY_EXISTS) {
+    if (NS_SUCCEEDED(rv) || !FailedBecauseExists(rv)) {
       return rv;
     }
   }
 
   // The disk is full, sort of
   return NS_ERROR_FILE_TOO_BIG;
 }