Merge autoland to mozilla-central. a=merge
authorBogdan Tara <btara@mozilla.com>
Thu, 14 May 2020 12:37:13 +0300
changeset 529840 8af03d77567d3104c00efc5e6161e4910d835504
parent 529839 045d696faa87fc06c8040b75dec7cd6358c34067 (current diff)
parent 529801 96df7fe7497f00497167c036cc05c347abdcf25c (diff)
child 529841 175bb2f682a37f23fbe8e82485b5c4f5a2825e5b
push id115934
push userbtara@mozilla.com
push dateThu, 14 May 2020 10:00:49 +0000
treeherderautoland@175bb2f682a3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone78.0a1
first release with
nightly linux32
8af03d77567d / 78.0a1 / 20200514094044 / files
nightly linux64
8af03d77567d / 78.0a1 / 20200514094044 / files
nightly mac
8af03d77567d / 78.0a1 / 20200514094044 / files
nightly win32
8af03d77567d / 78.0a1 / 20200514094044 / files
nightly win64
8af03d77567d / 78.0a1 / 20200514094044 / 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 autoland to mozilla-central. a=merge
--- a/accessible/base/RoleMap.h
+++ b/accessible/base/RoleMap.h
@@ -222,17 +222,17 @@ ROLE(TOOLBAR,
      ROLE_SYSTEM_TOOLBAR,
      ROLE_SYSTEM_TOOLBAR,
      java::SessionAccessibility::CLASSNAME_VIEW,
      eNoNameRule)
 
 ROLE(STATUSBAR,
      "statusbar",
      ATK_ROLE_STATUSBAR,
-     NSAccessibilityUnknownRole,  //Doesn't exist on OS X (a status bar is its parts; a progressbar, a label, etc.)
+     NSAccessibilityGroupRole,
      ROLE_SYSTEM_STATUSBAR,
      ROLE_SYSTEM_STATUSBAR,
      java::SessionAccessibility::CLASSNAME_VIEW,
      eNoNameRule)
 
 ROLE(TABLE,
      "table",
      ATK_ROLE_TABLE,
@@ -1060,17 +1060,17 @@ ROLE(COMBOBOX_OPTION,
      ROLE_SYSTEM_LISTITEM,
      ROLE_SYSTEM_LISTITEM,
      java::SessionAccessibility::CLASSNAME_MENUITEM,
      eNameFromSubtreeRule)
 
 ROLE(IMAGE_MAP,
      "image map",
      ATK_ROLE_IMAGE,
-     NSAccessibilityUnknownRole,
+     @"AXImageMap",
      ROLE_SYSTEM_GRAPHIC,
      ROLE_SYSTEM_GRAPHIC,
      java::SessionAccessibility::CLASSNAME_IMAGE,
      eNoNameRule)
 
 ROLE(OPTION,
      "listbox option",
      ATK_ROLE_LIST_ITEM,
--- a/accessible/ipc/DocAccessibleParent.cpp
+++ b/accessible/ipc/DocAccessibleParent.cpp
@@ -599,17 +599,17 @@ ipc::IPCResult DocAccessibleParent::AddC
       aChildDoc->GetCOMInterface((void**)getter_AddRefs(docAcc));
       RefPtr<IDispatch> docWrapped(
           mscom::PassthruProxy::Wrap<IDispatch>(WrapNotNull(docAcc)));
       IDispatchHolder::COMPtrType docPtr(
           mscom::ToProxyUniquePtr(std::move(docWrapped)));
       IDispatchHolder docHolder(std::move(docPtr));
       if (bridge->SendSetEmbeddedDocAccessibleCOMProxy(docHolder)) {
 #  if defined(MOZ_SANDBOX)
-        mDocProxyStream = docHolder.GetPreservedStream();
+        aChildDoc->mDocProxyStream = docHolder.GetPreservedStream();
 #  endif  // defined(MOZ_SANDBOX)
       }
       // Send a COM proxy for the embedder OuterDocAccessible to the embedded
       // document process. This will be returned as the parent of the
       // embedded document.
       aChildDoc->SendParentCOMProxy(WrapperFor(outerDoc));
       if (nsWinUtils::IsWindowEmulationStarted()) {
         // The embedded document should use the same emulated window handle as
--- a/accessible/tests/browser/mac/browser_roles_elements.js
+++ b/accessible/tests/browser/mac/browser_roles_elements.js
@@ -123,17 +123,17 @@ addAccessibleTask(
     testRoleAndSubRole(accDoc, "code", "AXGroup", "AXCodeStyleGroup");
     testRoleAndSubRole(accDoc, "dialog", null, "AXApplicationDialog", "dialog");
     testRoleAndSubRole(accDoc, "ariaDocument", null, "AXDocument");
     testRoleAndSubRole(accDoc, "log", null, "AXApplicationLog");
     testRoleAndSubRole(accDoc, "marquee", null, "AXApplicationMarquee");
     testRoleAndSubRole(accDoc, "ariaMath", null, "AXDocumentMath");
     testRoleAndSubRole(accDoc, "note", null, "AXDocumentNote");
     testRoleAndSubRole(accDoc, "ariaRegion", null, "AXLandmarkRegion");
-    testRoleAndSubRole(accDoc, "ariaStatus", null, "AXApplicationStatus");
+    testRoleAndSubRole(accDoc, "ariaStatus", "AXGroup", "AXApplicationStatus");
     testRoleAndSubRole(accDoc, "switch", "AXCheckBox", "AXSwitch");
     testRoleAndSubRole(accDoc, "timer", null, "AXApplicationTimer");
     testRoleAndSubRole(accDoc, "tooltip", null, "AXUserInterfaceTooltip");
 
     // True HTML5 search field
     testRoleAndSubRole(accDoc, "htmlSearch", "AXTextField", "AXSearchField");
 
     // A button morphed into a toggle by ARIA
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1781,18 +1781,19 @@ var gBrowserInit = {
     window.browserDOMWindow = new nsBrowserAccess();
 
     gBrowser = window._gBrowser;
     delete window._gBrowser;
     gBrowser.init();
 
     BrowserWindowTracker.track(window);
 
-    gNavToolbox.palette = document.getElementById("BrowserToolbarPalette");
-    gNavToolbox.palette.remove();
+    gNavToolbox.palette = document.getElementById(
+      "BrowserToolbarPalette"
+    ).content;
     let areas = CustomizableUI.areas;
     areas.splice(areas.indexOf(CustomizableUI.AREA_FIXED_OVERFLOW_PANEL), 1);
     for (let area of areas) {
       let node = document.getElementById(area);
       CustomizableUI.registerToolbarNode(node);
     }
     BrowserSearch.initPlaceHolder();
 
--- a/browser/base/content/browser.xhtml
+++ b/browser/base/content/browser.xhtml
@@ -1219,17 +1219,17 @@
                          tooltip="bhTooltip" popupsinherittooltip="true"
                          context="placesContext"/>
             </toolbarbutton>
           </hbox>
         </hbox>
       </toolbaritem>
     </toolbar>
 
-    <toolbarpalette id="BrowserToolbarPalette">
+    <html:template id="BrowserToolbarPalette">
 
       <toolbarbutton id="print-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
 #ifdef XP_MACOSX
                      command="cmd_print"
                      tooltip="dynamic-shortcut-tooltip"
 #else
                      command="cmd_printPreview"
                      tooltiptext="&printButton.tooltip;"
@@ -1351,17 +1351,17 @@
                    title="&searchItem.title;"
                    align="center"
                    flex="175"
                    persist="width">
         <toolbartabstop/>
         <searchbar id="searchbar" flex="1"/>
         <toolbartabstop/>
       </toolbaritem>
-    </toolbarpalette>
+    </html:template>
   </toolbox>
   </box>
 
   <hbox id="fullscr-toggler" hidden="true"/>
 
   <hbox flex="1" id="browser">
     <vbox id="browser-border-start" hidden="true" layer="true"/>
     <vbox id="sidebar-box" hidden="true" class="chromeclass-extrachrome">
--- a/browser/components/customizableui/CustomizableUI.jsm
+++ b/browser/components/customizableui/CustomizableUI.jsm
@@ -1459,17 +1459,20 @@ var CustomizableUIInternal = {
     }
 
     let container = this.getCustomizationTarget(aAreaNode);
     let placements = gPlacements.get(areaId);
     let nodeIndex = placements.indexOf(aNode.id);
 
     while (++nodeIndex < placements.length) {
       let nextNodeId = placements[nodeIndex];
-      let nextNode = aNode.ownerDocument.getElementById(nextNodeId);
+      // We use aAreaNode here, because if aNode is in a template, its
+      // `ownerDocument` is *not* going to be the browser.xhtml document,
+      // so we cannot rely on it.
+      let nextNode = aAreaNode.ownerDocument.getElementById(nextNodeId);
       // If the next placed widget exists, and is a direct child of the
       // container, or wrapped in a customize mode wrapper (toolbarpaletteitem)
       // inside the container, insert beside it.
       // We have to check the parent to avoid errors when the placement ids
       // are for nodes that are no longer customizable.
       if (
         nextNode &&
         (nextNode.parentNode == container ||
@@ -1856,17 +1859,19 @@ var CustomizableUIInternal = {
   },
 
   addShortcut(aShortcutNode, aTargetNode = aShortcutNode) {
     // Detect if we've already been here before.
     if (aTargetNode.hasAttribute("shortcut")) {
       return;
     }
 
-    let document = aShortcutNode.ownerDocument;
+    // Use ownerGlobal.document to ensure we get the right doc even for
+    // elements in template tags.
+    let { document } = aShortcutNode.ownerGlobal;
     let shortcutId = aShortcutNode.getAttribute("key");
     let shortcut;
     if (shortcutId) {
       shortcut = document.getElementById(shortcutId);
     } else {
       let commandId = aShortcutNode.getAttribute("command");
       if (commandId) {
         shortcut = ShortcutUtils.findShortcut(
@@ -4349,17 +4354,19 @@ var CustomizableUI = {
       "hidden",
       "class",
       "origin",
       "image",
       "checked",
       "style",
     ];
 
-    let doc = aSubview.ownerDocument;
+    // Use ownerGlobal.document to ensure we get the right doc even for
+    // elements in template tags.
+    let doc = aSubview.ownerGlobal.document;
     let fragment = doc.createDocumentFragment();
     for (let menuChild of aMenuItems) {
       if (menuChild.hidden) {
         continue;
       }
 
       let subviewItem;
       if (menuChild.localName == "menuseparator") {
@@ -4657,17 +4664,17 @@ function XULWidgetSingleWrapper(aWidgetI
   this.__defineGetter__("node", function() {
     // If we've set this to null (further down), we're sure there's nothing to
     // be gotten here, so bail out early:
     if (!weakDoc) {
       return null;
     }
     if (aNode) {
       // Return the last known node if it's still in the DOM...
-      if (aNode.ownerDocument.contains(aNode)) {
+      if (aNode.isConnected) {
         return aNode;
       }
       // ... or the toolbox
       let toolbox = aNode.ownerGlobal.gNavToolbox;
       if (toolbox && toolbox.palette && aNode.parentNode == toolbox.palette) {
         return aNode;
       }
       // If it isn't, clear the cached value and fall through to the "slow" case:
@@ -5288,17 +5295,19 @@ OverflowableToolbar.prototype = {
     // before the desired location for the new node). Once we pass
     // the desired location of the widget, we look for placement ids
     // that actually have DOM equivalents to insert before. If all
     // else fails, we insert at the end of either the overflow list
     // or the toolbar target.
     while (++loopIndex < placements.length) {
       let nextNodeId = placements[loopIndex];
       if (loopIndex > nodeIndex) {
-        let nextNode = aNode.ownerDocument.getElementById(nextNodeId);
+        // Note that if aNode is in a template, its `ownerDocument` is *not*
+        // going to be the browser.xhtml document, so we cannot rely on it.
+        let nextNode = this._toolbar.ownerDocument.getElementById(nextNodeId);
         // If the node we're inserting can overflow, and the next node
         // in the toolbar is overflown, we should insert this node
         // in the overflow panel before it.
         if (
           newNodeCanOverflow &&
           this._collapsed.has(nextNodeId) &&
           nextNode &&
           nextNode.parentNode == this._list
--- a/browser/components/customizableui/CustomizeMode.jsm
+++ b/browser/components/customizableui/CustomizeMode.jsm
@@ -642,17 +642,17 @@ CustomizeMode.prototype = {
         animationNode.removeEventListener(
           "animationend",
           cleanupWidgetAnimationEnd
         );
         animationNode.removeEventListener(
           "customizationending",
           cleanupCustomizationExit
         );
-        resolve();
+        resolve(animationNode);
       }
 
       // Wait until the next frame before setting the class to ensure
       // we do start the animation.
       this.window.requestAnimationFrame(() => {
         this.window.requestAnimationFrame(() => {
           animationNode.classList.add("animate-out");
           animationNode.ownerGlobal.gNavToolbox.addEventListener(
@@ -669,18 +669,19 @@ CustomizeMode.prototype = {
   },
 
   async addToToolbar(aNode) {
     aNode = this._getCustomizableChildForNode(aNode);
     if (aNode.localName == "toolbarpaletteitem" && aNode.firstElementChild) {
       aNode = aNode.firstElementChild;
     }
     let widgetAnimationPromise = this._promiseWidgetAnimationOut(aNode);
+    let animationNode;
     if (widgetAnimationPromise) {
-      await widgetAnimationPromise;
+      animationNode = await widgetAnimationPromise;
     }
 
     let widgetToAdd = aNode.id;
     if (
       CustomizableUI.isSpecialWidget(widgetToAdd) &&
       aNode.closest("#customization-palette")
     ) {
       widgetToAdd = widgetToAdd.match(
@@ -696,55 +697,48 @@ CustomizeMode.prototype = {
     // If the user explicitly moves this item, turn off autohide.
     if (aNode.id == "downloads-button") {
       Services.prefs.setBoolPref(kDownloadAutoHidePref, false);
       if (this._customizing) {
         this._showDownloadsAutoHidePanel();
       }
     }
 
-    if (widgetAnimationPromise) {
-      if (aNode.parentNode && aNode.parentNode.id.startsWith("wrapper-")) {
-        aNode.parentNode.classList.remove("animate-out");
-      } else {
-        aNode.classList.remove("animate-out");
-      }
+    if (animationNode) {
+      animationNode.classList.remove("animate-out");
     }
   },
 
   async addToPanel(aNode) {
     aNode = this._getCustomizableChildForNode(aNode);
     if (aNode.localName == "toolbarpaletteitem" && aNode.firstElementChild) {
       aNode = aNode.firstElementChild;
     }
     let widgetAnimationPromise = this._promiseWidgetAnimationOut(aNode);
+    let animationNode;
     if (widgetAnimationPromise) {
-      await widgetAnimationPromise;
+      animationNode = await widgetAnimationPromise;
     }
 
     let panel = CustomizableUI.AREA_FIXED_OVERFLOW_PANEL;
     CustomizableUI.addWidgetToArea(aNode.id, panel);
     if (!this._customizing) {
       CustomizableUI.dispatchToolboxEvent("customizationchange");
     }
 
     // If the user explicitly moves this item, turn off autohide.
     if (aNode.id == "downloads-button") {
       Services.prefs.setBoolPref(kDownloadAutoHidePref, false);
       if (this._customizing) {
         this._showDownloadsAutoHidePanel();
       }
     }
 
-    if (widgetAnimationPromise) {
-      if (aNode.parentNode && aNode.parentNode.id.startsWith("wrapper-")) {
-        aNode.parentNode.classList.remove("animate-out");
-      } else {
-        aNode.classList.remove("animate-out");
-      }
+    if (animationNode) {
+      animationNode.classList.remove("animate-out");
     }
     if (!this.window.gReduceMotion) {
       let overflowButton = this.$("nav-bar-overflow-button");
       BrowserUtils.setToolbarButtonHeightProperty(overflowButton).then(() => {
         overflowButton.setAttribute("animate", "true");
         overflowButton.addEventListener("animationend", function onAnimationEnd(
           event
         ) {
@@ -761,38 +755,35 @@ CustomizeMode.prototype = {
   },
 
   async removeFromArea(aNode) {
     aNode = this._getCustomizableChildForNode(aNode);
     if (aNode.localName == "toolbarpaletteitem" && aNode.firstElementChild) {
       aNode = aNode.firstElementChild;
     }
     let widgetAnimationPromise = this._promiseWidgetAnimationOut(aNode);
+    let animationNode;
     if (widgetAnimationPromise) {
-      await widgetAnimationPromise;
+      animationNode = await widgetAnimationPromise;
     }
 
     CustomizableUI.removeWidgetFromArea(aNode.id);
     if (!this._customizing) {
       CustomizableUI.dispatchToolboxEvent("customizationchange");
     }
 
     // If the user explicitly removes this item, turn off autohide.
     if (aNode.id == "downloads-button") {
       Services.prefs.setBoolPref(kDownloadAutoHidePref, false);
       if (this._customizing) {
         this._showDownloadsAutoHidePanel();
       }
     }
-    if (widgetAnimationPromise) {
-      if (aNode.parentNode && aNode.parentNode.id.startsWith("wrapper-")) {
-        aNode.parentNode.classList.remove("animate-out");
-      } else {
-        aNode.classList.remove("animate-out");
-      }
+    if (animationNode) {
+      animationNode.classList.remove("animate-out");
     }
   },
 
   populatePalette() {
     let fragment = this.document.createDocumentFragment();
     let toolboxPalette = this.window.gNavToolbox.palette;
 
     try {
--- a/browser/components/customizableui/SearchWidgetTracker.jsm
+++ b/browser/components/customizableui/SearchWidgetTracker.jsm
@@ -107,18 +107,18 @@ const SearchWidgetTracker = {
       win.document
         .getElementById("nav-bar")
         .querySelectorAll("toolbarspring")
         .forEach(n => n.removeAttribute("width"));
       win.PanelUI.overflowPanel
         .querySelectorAll("toolbarspring")
         .forEach(n => n.removeAttribute("width"));
       let searchbar =
-        win.document.getElementById(this.WIDGET_ID) ||
-        win.gNavToolbox.palette.querySelector("#" + this.WIDGET_ID);
+        win.document.getElementById(WIDGET_ID) ||
+        win.gNavToolbox.palette.querySelector("#" + WIDGET_ID);
       searchbar.removeAttribute("width");
     }
   },
 
   get widgetIsInNavBar() {
     let placement = CustomizableUI.getPlacementOfWidget(WIDGET_ID);
     return placement ? placement.area == CustomizableUI.AREA_NAVBAR : false;
   },
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -1246,20 +1246,16 @@ Document::Document(const char* aContentT
       mDocURISchemeIsChrome(false),
       mInChromeDocShell(false),
       mIsDevToolsDocument(false),
       mIsSyntheticDocument(false),
       mHasLinksToUpdateRunnable(false),
       mFlushingPendingLinkUpdates(false),
       mMayHaveDOMMutationObservers(false),
       mMayHaveAnimationObservers(false),
-      mHasMixedActiveContentLoaded(false),
-      mHasMixedActiveContentBlocked(false),
-      mHasMixedDisplayContentLoaded(false),
-      mHasMixedDisplayContentBlocked(false),
       mHasCSP(false),
       mHasUnsafeEvalCSP(false),
       mHasUnsafeInlineCSP(false),
       mHasCSPDeliveredThroughHeader(false),
       mBFCacheDisallowed(false),
       mHasHadDefaultView(false),
       mStyleSheetChangeEventsEnabled(false),
       mIsSrcdocDocument(false),
--- a/dom/base/Document.h
+++ b/dom/base/Document.h
@@ -1043,65 +1043,101 @@ class Document : public nsINode,
    * change to actually change anything immediately.
    * @see nsBidiUtils.h
    */
   void SetBidiOptions(uint32_t aBidiOptions) { mBidiOptions = aBidiOptions; }
 
   /**
    * Get the has mixed active content loaded flag for this document.
    */
-  bool GetHasMixedActiveContentLoaded() { return mHasMixedActiveContentLoaded; }
+  bool GetHasMixedActiveContentLoaded() {
+    return mMixedContentFlags &
+           nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT;
+  }
 
   /**
    * Set the has mixed active content loaded flag for this document.
    */
   void SetHasMixedActiveContentLoaded(bool aHasMixedActiveContentLoaded) {
-    mHasMixedActiveContentLoaded = aHasMixedActiveContentLoaded;
+    if (aHasMixedActiveContentLoaded) {
+      mMixedContentFlags |=
+          nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT;
+    } else {
+      mMixedContentFlags &=
+          ~nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT;
+    }
   }
 
   /**
    * Get mixed active content blocked flag for this document.
    */
   bool GetHasMixedActiveContentBlocked() {
-    return mHasMixedActiveContentBlocked;
+    return mMixedContentFlags &
+           nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT;
   }
 
   /**
    * Set the mixed active content blocked flag for this document.
    */
   void SetHasMixedActiveContentBlocked(bool aHasMixedActiveContentBlocked) {
-    mHasMixedActiveContentBlocked = aHasMixedActiveContentBlocked;
+    if (aHasMixedActiveContentBlocked) {
+      mMixedContentFlags |=
+          nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT;
+    } else {
+      mMixedContentFlags &=
+          ~nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT;
+    }
   }
 
   /**
    * Get the has mixed display content loaded flag for this document.
    */
   bool GetHasMixedDisplayContentLoaded() {
-    return mHasMixedDisplayContentLoaded;
+    return mMixedContentFlags &
+           nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT;
   }
 
   /**
    * Set the has mixed display content loaded flag for this document.
    */
   void SetHasMixedDisplayContentLoaded(bool aHasMixedDisplayContentLoaded) {
-    mHasMixedDisplayContentLoaded = aHasMixedDisplayContentLoaded;
+    if (aHasMixedDisplayContentLoaded) {
+      mMixedContentFlags |=
+          nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT;
+    } else {
+      mMixedContentFlags &=
+          ~nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT;
+    }
   }
 
   /**
    * Get mixed display content blocked flag for this document.
    */
   bool GetHasMixedDisplayContentBlocked() {
-    return mHasMixedDisplayContentBlocked;
+    return mMixedContentFlags &
+           nsIWebProgressListener::STATE_BLOCKED_MIXED_DISPLAY_CONTENT;
   }
 
   /**
    * Set the mixed display content blocked flag for this document.
    */
   void SetHasMixedDisplayContentBlocked(bool aHasMixedDisplayContentBlocked) {
-    mHasMixedDisplayContentBlocked = aHasMixedDisplayContentBlocked;
+    if (aHasMixedDisplayContentBlocked) {
+      mMixedContentFlags |=
+          nsIWebProgressListener::STATE_BLOCKED_MIXED_DISPLAY_CONTENT;
+    } else {
+      mMixedContentFlags &=
+          ~nsIWebProgressListener::STATE_BLOCKED_MIXED_DISPLAY_CONTENT;
+    }
+  }
+
+  uint32_t GetMixedContentFlags() const { return mMixedContentFlags; }
+
+  void AddMixedContentFlags(uint32_t aMixedContentFlags) {
+    mMixedContentFlags |= aMixedContentFlags;
   }
 
   /**
    * Set CSP flag for this document.
    */
   void SetHasCSP(bool aHasCSP) { mHasCSP = aHasCSP; }
 
   /**
@@ -4339,16 +4375,18 @@ class Document : public nsINode,
   RefPtr<mozilla::dom::FeaturePolicy> mFeaturePolicy;
 
   UniquePtr<ResizeObserverController> mResizeObserverController;
 
   // Permission Delegate Handler, lazily-initialized in
   // GetPermissionDelegateHandler
   RefPtr<PermissionDelegateHandler> mPermissionDelegateHandler;
 
+  uint32_t mMixedContentFlags = 0;
+
   // True if BIDI is enabled.
   bool mBidiEnabled : 1;
   // True if we may need to recompute the language prefs for this document.
   bool mMayNeedFontPrefsUpdate : 1;
   // True if a MathML element has ever been owned by this document.
   bool mMathMLEnabled : 1;
 
   // True if this document is the initial document for a window.  This should
@@ -4432,32 +4470,16 @@ class Document : public nsINode,
   // True if a DOMMutationObserver is perhaps attached to a node in the
   // document.
   bool mMayHaveDOMMutationObservers : 1;
 
   // True if an nsIAnimationObserver is perhaps attached to a node in the
   // document.
   bool mMayHaveAnimationObservers : 1;
 
-  // True if a document has loaded Mixed Active Script (see
-  // nsMixedContentBlocker.cpp)
-  bool mHasMixedActiveContentLoaded : 1;
-
-  // True if a document has blocked Mixed Active Script (see
-  // nsMixedContentBlocker.cpp)
-  bool mHasMixedActiveContentBlocked : 1;
-
-  // True if a document has loaded Mixed Display/Passive Content (see
-  // nsMixedContentBlocker.cpp)
-  bool mHasMixedDisplayContentLoaded : 1;
-
-  // True if a document has blocked Mixed Display/Passive Content (see
-  // nsMixedContentBlocker.cpp)
-  bool mHasMixedDisplayContentBlocked : 1;
-
   // True if a document load has a CSP attached.
   bool mHasCSP : 1;
 
   // True if a document load has a CSP with unsafe-eval attached.
   bool mHasUnsafeEvalCSP : 1;
 
   // True if a document load has a CSP with unsafe-inline attached.
   bool mHasUnsafeInlineCSP : 1;
--- a/dom/indexedDB/IDBFileHandle.cpp
+++ b/dom/indexedDB/IDBFileHandle.cpp
@@ -35,17 +35,18 @@ RefPtr<IDBFileRequest> GenerateFileReque
   aFileHandle->AssertIsOnOwningThread();
 
   return IDBFileRequest::Create(aFileHandle, /* aWrapAsDOMRequest */ false);
 }
 
 }  // namespace
 
 IDBFileHandle::IDBFileHandle(IDBMutableFile* aMutableFile, FileMode aMode)
-    : mMutableFile(aMutableFile),
+    : DOMEventTargetHelper(aMutableFile),
+      mMutableFile(aMutableFile),
       mBackgroundActor(nullptr),
       mLocation(0),
       mPendingRequestCount(0),
       mReadyState(INITIAL),
       mMode(aMode),
       mAborted(false),
       mCreating(false)
 #ifdef DEBUG
@@ -78,18 +79,16 @@ IDBFileHandle::~IDBFileHandle() {
 RefPtr<IDBFileHandle> IDBFileHandle::Create(IDBMutableFile* aMutableFile,
                                             FileMode aMode) {
   MOZ_ASSERT(aMutableFile);
   aMutableFile->AssertIsOnOwningThread();
   MOZ_ASSERT(aMode == FileMode::Readonly || aMode == FileMode::Readwrite);
 
   RefPtr<IDBFileHandle> fileHandle = new IDBFileHandle(aMutableFile, aMode);
 
-  fileHandle->BindToOwner(aMutableFile);
-
   // XXX Fix!
   MOZ_ASSERT(NS_IsMainThread(), "This won't work on non-main threads!");
 
   nsCOMPtr<nsIRunnable> runnable = do_QueryObject(fileHandle);
   nsContentUtils::AddPendingIDBTransaction(runnable.forget());
 
   fileHandle->mCreating = true;
 
--- a/dom/media/webaudio/AudioWorkletNode.cpp
+++ b/dom/media/webaudio/AudioWorkletNode.cpp
@@ -202,18 +202,19 @@ void WorkletNodeEngine::SendProcessorErr
     ProcessorErrorDetails details;
     details.mMessage.Assign(u"Unknown processor error");
     SendErrorToMainThread(aTrack, details);
     return;
   }
 
   JS::ExceptionStack exnStack(aCx);
   if (JS::StealPendingExceptionStack(aCx, &exnStack)) {
-    js::ErrorReport jsReport(aCx);
-    if (!jsReport.init(aCx, exnStack, js::ErrorReport::WithSideEffects)) {
+    JS::ErrorReportBuilder jsReport(aCx);
+    if (!jsReport.init(aCx, exnStack,
+                       JS::ErrorReportBuilder::WithSideEffects)) {
       ProcessorErrorDetails details;
       details.mMessage.Assign(u"Unknown processor error");
       SendErrorToMainThread(aTrack, details);
       // Set the exception and stack back to have it in the console with a stack
       // trace.
       JS::SetPendingExceptionStack(aCx, exnStack);
       return;
     }
--- a/dom/promise/Promise.cpp
+++ b/dom/promise/Promise.cpp
@@ -26,16 +26,17 @@
 #include "mozilla/dom/UserActivation.h"
 #include "mozilla/dom/WorkerPrivate.h"
 #include "mozilla/dom/WorkerRunnable.h"
 #include "mozilla/dom/WorkerRef.h"
 #include "mozilla/dom/WorkletImpl.h"
 #include "mozilla/dom/WorkletGlobalScope.h"
 
 #include "jsfriendapi.h"
+#include "js/Exception.h"  // JS::ExceptionStack
 #include "js/StructuredClone.h"
 #include "nsContentUtils.h"
 #include "nsGlobalWindow.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "nsJSEnvironment.h"
 #include "nsJSPrincipals.h"
 #include "nsJSUtils.h"
 #include "nsPIDOMWindow.h"
@@ -544,28 +545,31 @@ void Promise::ReportRejectedPromise(JSCo
   {
     Maybe<JSAutoRealm> ar;
     JS::Rooted<JS::Value> unwrapped(aCx, result);
     if (unwrapped.isObject()) {
       unwrapped.setObject(*js::UncheckedUnwrap(&unwrapped.toObject()));
       ar.emplace(aCx, &unwrapped.toObject());
     }
 
-    js::ErrorReport report(aCx);
+    JS::ErrorReportBuilder report(aCx);
     RefPtr<Exception> exn;
     if (unwrapped.isObject() &&
         (NS_SUCCEEDED(UNWRAP_OBJECT(DOMException, &unwrapped, exn)) ||
          NS_SUCCEEDED(UNWRAP_OBJECT(Exception, &unwrapped, exn)))) {
       xpcReport->Init(aCx, exn, isChrome, innerWindowID);
-    } else if (report.init(aCx, unwrapped, js::ErrorReport::NoSideEffects)) {
+    } else {
+      JS::ExceptionStack exnStack(aCx, unwrapped, nullptr);
+      if (!report.init(aCx, exnStack, JS::ErrorReportBuilder::NoSideEffects)) {
+        JS_ClearPendingException(aCx);
+        return;
+      }
+
       xpcReport->Init(report.report(), report.toStringResult().c_str(),
                       isChrome, innerWindowID);
-    } else {
-      JS_ClearPendingException(aCx);
-      return;
     }
   }
 
   // Now post an event to do the real reporting async
   RefPtr<AsyncErrorReporter> event = new AsyncErrorReporter(xpcReport);
   if (win) {
     if (!win->IsDying()) {
       // Exceptions from a dying window will cause the window to leak.
--- a/dom/script/ScriptSettings.cpp
+++ b/dom/script/ScriptSettings.cpp
@@ -497,19 +497,19 @@ void AutoJSAPI::ReportException() {
           return;
         }
       }
     }
   }
   MOZ_ASSERT(JS_IsGlobalObject(errorGlobal));
   JSAutoRealm ar(cx(), errorGlobal);
   JS::ExceptionStack exnStack(cx());
-  js::ErrorReport jsReport(cx());
+  JS::ErrorReportBuilder jsReport(cx());
   if (StealExceptionAndStack(&exnStack) &&
-      jsReport.init(cx(), exnStack, js::ErrorReport::WithSideEffects)) {
+      jsReport.init(cx(), exnStack, JS::ErrorReportBuilder::WithSideEffects)) {
     if (mIsMainThread) {
       RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
 
       RefPtr<nsGlobalWindowInner> inner = xpc::WindowOrNull(errorGlobal);
       bool isChrome =
           nsContentUtils::ObjectPrincipal(errorGlobal)->IsSystemPrincipal();
       xpcReport->Init(jsReport.report(), jsReport.toStringResult().c_str(),
                       isChrome, inner ? inner->WindowID() : 0);
--- a/dom/security/nsMixedContentBlocker.cpp
+++ b/dom/security/nsMixedContentBlocker.cpp
@@ -57,161 +57,16 @@ bool nsMixedContentBlocker::sSecureconte
 
 enum MixedContentHSTSState {
   MCB_HSTS_PASSIVE_NO_HSTS = 0,
   MCB_HSTS_PASSIVE_WITH_HSTS = 1,
   MCB_HSTS_ACTIVE_NO_HSTS = 2,
   MCB_HSTS_ACTIVE_WITH_HSTS = 3
 };
 
-// Fired at the document that attempted to load mixed content.  The UI could
-// handle this event, for example, by displaying an info bar that offers the
-// choice to reload the page with mixed content permitted.
-class nsMixedContentEvent : public Runnable {
- public:
-  nsMixedContentEvent(nsISupports* aContext, MixedContentTypes aType,
-                      bool aRootHasSecureConnection)
-      : mozilla::Runnable("nsMixedContentEvent"),
-        mContext(aContext),
-        mType(aType),
-        mRootHasSecureConnection(aRootHasSecureConnection) {}
-
-  NS_IMETHOD Run() override {
-    NS_ASSERTION(mContext,
-                 "You can't call this runnable without a requesting context");
-
-    // To update the security UI in the tab with the blocked mixed content, call
-    // nsDocLoader::OnSecurityChange.
-
-    // Mixed content was allowed and is about to load; get the document and
-    // set the approriate flag to true if we are about to load Mixed Active
-    // Content.
-    nsCOMPtr<nsIDocShell> docShell = NS_CP_GetDocShellFromContext(mContext);
-    if (!docShell) {
-      return NS_OK;
-    }
-
-    nsCOMPtr<nsIDocShell> rootShell =
-        docShell->GetBrowsingContext()->Top()->GetDocShell();
-    if (!rootShell) {
-      return NS_OK;
-    }
-
-    // now get the document from sameTypeRoot
-    nsCOMPtr<Document> rootDoc = rootShell->GetDocument();
-    NS_ASSERTION(rootDoc,
-                 "No root document from document shell root tree item.");
-
-    nsDocShell* nativeDocShell = nsDocShell::Cast(docShell);
-    uint32_t state = nsIWebProgressListener::STATE_IS_BROKEN;
-    nsCOMPtr<nsISecureBrowserUI> securityUI;
-    rootShell->GetSecurityUI(getter_AddRefs(securityUI));
-    // If there is no securityUI, document doesn't have a security state to
-    // update.  But we still want to set the document flags, so we don't return
-    // early.
-    nsresult stateRV = NS_ERROR_FAILURE;
-    if (securityUI) {
-      stateRV = securityUI->GetState(&state);
-    }
-
-    if (mType == eMixedScript) {
-      // See if the pref will change here. If it will, only then do we need to
-      // call OnSecurityChange() to update the UI.
-      if (rootDoc->GetHasMixedActiveContentLoaded()) {
-        return NS_OK;
-      }
-      rootDoc->SetHasMixedActiveContentLoaded(true);
-
-      // Update the security UI in the tab with the allowed mixed active content
-      if (securityUI) {
-        // Bug 1182551 - before changing the security state to broken, check
-        // that the root is actually secure.
-        if (mRootHasSecureConnection) {
-          // reset state security flag
-          state = state >> 4 << 4;
-          // set state security flag to broken, since there is mixed content
-          state |= nsIWebProgressListener::STATE_IS_BROKEN;
-
-          // If mixed display content is loaded, make sure to include that in
-          // the state.
-          if (rootDoc->GetHasMixedDisplayContentLoaded()) {
-            state |= nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT;
-          }
-
-          nativeDocShell->nsDocLoader::OnSecurityChange(
-              mContext,
-              (state |
-               nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
-        } else {
-          // root not secure, mixed active content loaded in an https subframe
-          if (NS_SUCCEEDED(stateRV)) {
-            nativeDocShell->nsDocLoader::OnSecurityChange(
-                mContext,
-                (state |
-                 nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
-          }
-        }
-      }
-
-    } else if (mType == eMixedDisplay) {
-      // See if the pref will change here. If it will, only then do we need to
-      // call OnSecurityChange() to update the UI.
-      if (rootDoc->GetHasMixedDisplayContentLoaded()) {
-        return NS_OK;
-      }
-      rootDoc->SetHasMixedDisplayContentLoaded(true);
-
-      // Update the security UI in the tab with the allowed mixed display
-      // content.
-      if (securityUI) {
-        // Bug 1182551 - before changing the security state to broken, check
-        // that the root is actually secure.
-        if (mRootHasSecureConnection) {
-          // reset state security flag
-          state = state >> 4 << 4;
-          // set state security flag to broken, since there is mixed content
-          state |= nsIWebProgressListener::STATE_IS_BROKEN;
-
-          // If mixed active content is loaded, make sure to include that in the
-          // state.
-          if (rootDoc->GetHasMixedActiveContentLoaded()) {
-            state |= nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT;
-          }
-
-          nativeDocShell->nsDocLoader::OnSecurityChange(
-              mContext,
-              (state |
-               nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
-        } else {
-          // root not secure, mixed display content loaded in an https subframe
-          if (NS_SUCCEEDED(stateRV)) {
-            nativeDocShell->nsDocLoader::OnSecurityChange(
-                mContext,
-                (state |
-                 nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
-          }
-        }
-      }
-    }
-
-    return NS_OK;
-  }
-
- private:
-  // The requesting context for the content load. Generally, a DOM node from
-  // the document that caused the load.
-  nsCOMPtr<nsISupports> mContext;
-
-  // The type of mixed content detected, e.g. active or display
-  const MixedContentTypes mType;
-
-  // Indicates whether the top level load is https or not.
-  bool mRootHasSecureConnection;
-};
-
 nsMixedContentBlocker::~nsMixedContentBlocker() = default;
 
 NS_IMPL_ISUPPORTS(nsMixedContentBlocker, nsIContentPolicy, nsIChannelEventSink)
 
 static void LogMixedContentMessage(
     MixedContentTypes aClassification, nsIURI* aContentLocation,
     uint64_t aInnerWindowID, nsMixedContentBlockerMessageType aMessageType,
     nsIURI* aRequestingLocation,
@@ -889,30 +744,24 @@ nsresult nsMixedContentBlocker::ShouldLo
         *aDecision = nsIContentPolicy::ACCEPT;
         return NS_OK;
       }
       *aDecision = nsIContentPolicy::REJECT_REQUEST;
       return NS_OK;
     }
   }
 
-  uint64_t topInnerWindowID =
-      docShell->GetBrowsingContext()->GetTopWindowContext()->Id();
-  nsDocShell* nativeDocShell = nsDocShell::Cast(docShell);
-
-  uint32_t state = nsIWebProgressListener::STATE_IS_BROKEN;
   nsCOMPtr<nsISecureBrowserUI> securityUI;
   rootShell->GetSecurityUI(getter_AddRefs(securityUI));
   // If there is no securityUI, document doesn't have a security state.
   // Allow load and return early.
   if (!securityUI) {
     *aDecision = nsIContentPolicy::ACCEPT;
     return NS_OK;
   }
-  nsresult stateRV = securityUI->GetState(&state);
 
   OriginAttributes originAttributes;
   if (loadingPrincipal) {
     originAttributes = loadingPrincipal->OriginAttributesRef();
   } else if (triggeringPrincipal) {
     originAttributes = triggeringPrincipal->OriginAttributesRef();
   }
 
@@ -944,160 +793,95 @@ nsresult nsMixedContentBlocker::ShouldLo
     }
   }
 
   // set hasMixedContentObjectSubrequest on this object if necessary
   if (contentType == TYPE_OBJECT_SUBREQUEST) {
     if (!StaticPrefs::security_mixed_content_block_object_subrequest()) {
       nsAutoCString messageLookUpKey(
           "LoadingMixedDisplayObjectSubrequestDeprecation");
-      LogMixedContentMessage(classification, aContentLocation, topInnerWindowID,
+      LogMixedContentMessage(classification, aContentLocation, topWC->Id(),
                              eUserOverride, requestingLocation,
                              messageLookUpKey);
     }
   }
 
+  uint32_t newState = 0;
   // If the content is display content, and the pref says display content should
   // be blocked, block it.
-  if (StaticPrefs::security_mixed_content_block_display_content() &&
-      classification == eMixedDisplay) {
-    if (allowMixedContent) {
-      LogMixedContentMessage(classification, aContentLocation, topInnerWindowID,
-                             eUserOverride, requestingLocation);
+  if (classification == eMixedDisplay) {
+    if (!StaticPrefs::security_mixed_content_block_display_content() ||
+        allowMixedContent) {
       *aDecision = nsIContentPolicy::ACCEPT;
-      // See if mixed display content has already loaded on the page or if the
-      // state needs to be updated here. If mixed display hasn't loaded
-      // previously, then we need to call OnSecurityChange() to update the UI.
-      if (rootDoc->GetHasMixedDisplayContentLoaded()) {
-        return NS_OK;
-      }
-      rootDoc->SetHasMixedDisplayContentLoaded(true);
-
-      if (rootHasSecureConnection) {
-        // reset state security flag
-        state = state >> 4 << 4;
-        // set state security flag to broken, since there is mixed content
-        state |= nsIWebProgressListener::STATE_IS_BROKEN;
-
-        // If mixed active content is loaded, make sure to include that in the
-        // state.
-        if (rootDoc->GetHasMixedActiveContentLoaded()) {
-          state |= nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT;
-        }
-
-        nativeDocShell->nsDocLoader::OnSecurityChange(
-            requestingContext,
-            (state |
-             nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
-      } else {
-        // User has overriden the pref and the root is not https;
-        // mixed display content was allowed on an https subframe.
-        if (NS_SUCCEEDED(stateRV)) {
-          nativeDocShell->nsDocLoader::OnSecurityChange(
-              requestingContext,
-              (state |
-               nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
-        }
-      }
+      // User has overriden the pref and the root is not https;
+      // mixed display content was allowed on an https subframe.
+      newState |= nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT;
     } else {
       *aDecision = nsIContentPolicy::REJECT_REQUEST;
-      LogMixedContentMessage(classification, aContentLocation, topInnerWindowID,
-                             eBlocked, requestingLocation);
-      if (!rootDoc->GetHasMixedDisplayContentBlocked() &&
-          NS_SUCCEEDED(stateRV)) {
-        rootDoc->SetHasMixedDisplayContentBlocked(true);
-        nativeDocShell->nsDocLoader::OnSecurityChange(
-            requestingContext,
-            (state |
-             nsIWebProgressListener::STATE_BLOCKED_MIXED_DISPLAY_CONTENT));
-      }
+      newState |= nsIWebProgressListener::STATE_BLOCKED_MIXED_DISPLAY_CONTENT;
     }
-    return NS_OK;
-
-  } else if (StaticPrefs::security_mixed_content_block_active_content() &&
-             classification == eMixedScript) {
+  } else {
+    MOZ_ASSERT(classification == eMixedScript);
     // If the content is active content, and the pref says active content should
     // be blocked, block it unless the user has choosen to override the pref
-    if (allowMixedContent) {
-      LogMixedContentMessage(classification, aContentLocation, topInnerWindowID,
-                             eUserOverride, requestingLocation);
+    if (!StaticPrefs::security_mixed_content_block_active_content() ||
+        allowMixedContent) {
       *aDecision = nsIContentPolicy::ACCEPT;
-      // See if the state will change here. If it will, only then do we need to
-      // call OnSecurityChange() to update the UI.
-      if (rootDoc->GetHasMixedActiveContentLoaded()) {
-        return NS_OK;
-      }
-      rootDoc->SetHasMixedActiveContentLoaded(true);
-
-      if (rootHasSecureConnection) {
-        // reset state security flag
-        state = state >> 4 << 4;
-        // set state security flag to broken, since there is mixed content
-        state |= nsIWebProgressListener::STATE_IS_BROKEN;
-
-        // If mixed display content is loaded, make sure to include that in the
-        // state.
-        if (rootDoc->GetHasMixedDisplayContentLoaded()) {
-          state |= nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT;
-        }
-
-        nativeDocShell->nsDocLoader::OnSecurityChange(
-            requestingContext,
-            (state |
-             nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
-
-        return NS_OK;
-      } else {
-        // User has already overriden the pref and the root is not https;
-        // mixed active content was allowed on an https subframe.
-        if (NS_SUCCEEDED(stateRV)) {
-          nativeDocShell->nsDocLoader::OnSecurityChange(
-              requestingContext,
-              (state |
-               nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
-        }
-        return NS_OK;
-      }
+      // User has already overriden the pref and the root is not https;
+      // mixed active content was allowed on an https subframe.
+      newState |= nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT;
     } else {
       // User has not overriden the pref by Disabling protection. Reject the
       // request and update the security state.
       *aDecision = nsIContentPolicy::REJECT_REQUEST;
-      LogMixedContentMessage(classification, aContentLocation, topInnerWindowID,
-                             eBlocked, requestingLocation);
-      // See if the pref will change here. If it will, only then do we need to
-      // call OnSecurityChange() to update the UI.
-      if (rootDoc->GetHasMixedActiveContentBlocked()) {
-        return NS_OK;
-      }
-      rootDoc->SetHasMixedActiveContentBlocked(true);
-
       // The user has not overriden the pref, so make sure they still have an
       // option by calling nativeDocShell which will invoke the doorhanger
-      if (NS_SUCCEEDED(stateRV)) {
-        nativeDocShell->nsDocLoader::OnSecurityChange(
-            requestingContext,
-            (state |
-             nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT));
-      }
-      return NS_OK;
+      newState |= nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT;
     }
-  } else {
-    // The content is not blocked by the mixed content prefs.
+  }
 
-    // Log a message that we are loading mixed content.
-    LogMixedContentMessage(classification, aContentLocation, topInnerWindowID,
-                           eUserOverride, requestingLocation);
+  LogMixedContentMessage(classification, aContentLocation, topWC->Id(),
+                         (*aDecision == nsIContentPolicy::REJECT_REQUEST)
+                             ? eBlocked
+                             : eUserOverride,
+                         requestingLocation);
 
-    // Fire the event from a script runner as it is unsafe to run script
-    // from within ShouldLoad
-    nsContentUtils::AddScriptRunner(new nsMixedContentEvent(
-        requestingContext, classification, rootHasSecureConnection));
-    *aDecision = ACCEPT;
+  if (rootDoc->GetMixedContentFlags() == newState) {
     return NS_OK;
   }
+
+  // Copy the new state onto the Document flags.
+  rootDoc->AddMixedContentFlags(newState);
+
+  uint32_t state = nsIWebProgressListener::STATE_IS_BROKEN;
+  MOZ_ALWAYS_SUCCEEDS(securityUI->GetState(&state));
+
+  if (*aDecision == nsIContentPolicy::ACCEPT && rootHasSecureConnection) {
+    // reset state security flag
+    state = state >> 4 << 4;
+    // set state security flag to broken, since there is mixed content
+    state |= nsIWebProgressListener::STATE_IS_BROKEN;
+
+    // If mixed display content is loaded, make sure to include that in the
+    // state.
+    if (rootDoc->GetHasMixedDisplayContentLoaded()) {
+      state |= nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT;
+    }
+
+    // If mixed active content is loaded, make sure to include that in the
+    // state.
+    if (rootDoc->GetHasMixedActiveContentLoaded()) {
+      state |= nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT;
+    }
+  }
+
+  state |= newState;
+  nsDocShell* nativeDocShell = nsDocShell::Cast(docShell);
+  nativeDocShell->nsDocLoader::OnSecurityChange(requestingContext, state);
+  return NS_OK;
 }
 
 bool nsMixedContentBlocker::URISafeToBeLoadedInSecureContext(nsIURI* aURI) {
   /* Returns a bool if the URI can be loaded as a sub resource safely.
    *
    * Check Protocol Flags to determine if scheme is safe to load:
    * URI_DOES_NOT_RETURN_DATA - e.g.
    *   "mailto"
--- a/dom/serviceworkers/ServiceWorkerEvents.cpp
+++ b/dom/serviceworkers/ServiceWorkerEvents.cpp
@@ -6,16 +6,17 @@
 
 #include "ServiceWorkerEvents.h"
 
 #include <utility>
 
 #include "ServiceWorker.h"
 #include "ServiceWorkerManager.h"
 #include "js/Conversions.h"
+#include "js/Exception.h"  // JS::ExceptionStack, JS::StealPendingExceptionStack
 #include "js/TypeDecls.h"
 #include "mozilla/Encoding.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/LoadInfo.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/BodyUtil.h"
 #include "mozilla/dom/Client.h"
 #include "mozilla/dom/EventBinding.h"
@@ -495,26 +496,24 @@ class MOZ_STACK_CLASS AutoCancel {
     // Storing the error as exception in the JSContext.
     if (!aRv.MaybeSetPendingException(aCx)) {
       return;
     }
 
     MOZ_ASSERT(!aRv.Failed());
 
     // Let's take the pending exception.
-    JS::Rooted<JS::Value> exn(aCx);
-    if (!JS_GetPendingException(aCx, &exn)) {
+    JS::ExceptionStack exnStack(aCx);
+    if (!JS::StealPendingExceptionStack(aCx, &exnStack)) {
       return;
     }
 
-    JS_ClearPendingException(aCx);
-
-    // Converting the exception in a js::ErrorReport.
-    js::ErrorReport report(aCx);
-    if (!report.init(aCx, exn, js::ErrorReport::WithSideEffects)) {
+    // Converting the exception in a JS::ErrorReportBuilder.
+    JS::ErrorReportBuilder report(aCx);
+    if (!report.init(aCx, exnStack, JS::ErrorReportBuilder::WithSideEffects)) {
       JS_ClearPendingException(aCx);
       return;
     }
 
     MOZ_ASSERT(mOwner);
     MOZ_ASSERT(mMessageName.EqualsLiteral("InterceptionFailedWithURL"));
     MOZ_ASSERT(mParams.Length() == 1);
 
--- a/dom/serviceworkers/ServiceWorkerOp.cpp
+++ b/dom/serviceworkers/ServiceWorkerOp.cpp
@@ -3,16 +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 "ServiceWorkerOp.h"
 
 #include <utility>
 
+#include "js/Exception.h"  // JS::ExceptionStack, JS::StealPendingExceptionStack
 #include "jsapi.h"
 
 #include "nsCOMPtr.h"
 #include "nsContentUtils.h"
 #include "nsDebug.h"
 #include "nsError.h"
 #include "nsINamed.h"
 #include "nsIPushErrorReporter.h"
@@ -1046,26 +1047,24 @@ class MOZ_STACK_CLASS FetchEventOp::Auto
     // Storing the error as exception in the JSContext.
     if (!aRv.MaybeSetPendingException(aCx)) {
       return;
     }
 
     MOZ_ASSERT(!aRv.Failed());
 
     // Let's take the pending exception.
-    JS::Rooted<JS::Value> exn(aCx);
-    if (!JS_GetPendingException(aCx, &exn)) {
+    JS::ExceptionStack exnStack(aCx);
+    if (!JS::StealPendingExceptionStack(aCx, &exnStack)) {
       return;
     }
 
-    JS_ClearPendingException(aCx);
-
-    // Converting the exception in a js::ErrorReport.
-    js::ErrorReport report(aCx);
-    if (!report.init(aCx, exn, js::ErrorReport::WithSideEffects)) {
+    // Converting the exception in a JS::ErrorReportBuilder.
+    JS::ErrorReportBuilder report(aCx);
+    if (!report.init(aCx, exnStack, JS::ErrorReportBuilder::WithSideEffects)) {
       JS_ClearPendingException(aCx);
       return;
     }
 
     MOZ_ASSERT(mOwner);
     MOZ_ASSERT(mMessageName.EqualsLiteral("InterceptionFailedWithURL"));
     MOZ_ASSERT(mParams.Length() == 1);
 
--- a/dom/workers/ScriptLoader.cpp
+++ b/dom/workers/ScriptLoader.cpp
@@ -26,16 +26,17 @@
 #include "nsIStreamListenerTee.h"
 #include "nsIThreadRetargetableRequest.h"
 #include "nsIURI.h"
 #include "nsIXPConnect.h"
 
 #include "jsapi.h"
 #include "jsfriendapi.h"
 #include "js/CompilationAndEvaluation.h"
+#include "js/Exception.h"
 #include "js/SourceText.h"
 #include "nsError.h"
 #include "nsContentPolicyUtils.h"
 #include "nsContentUtils.h"
 #include "nsDocShellCID.h"
 #include "nsNetUtil.h"
 #include "nsIPipe.h"
 #include "nsIOutputStream.h"
@@ -2245,18 +2246,19 @@ void ScriptExecutorRunnable::LogExceptio
   if (!ToJSValue(aCx, std::move(mScriptLoader.mRv), &exn)) {
     return;
   }
 
   // Now the exception state should all be in exn.
   MOZ_ASSERT(!JS_IsExceptionPending(aCx));
   MOZ_ASSERT(!mScriptLoader.mRv.Failed());
 
-  js::ErrorReport report(aCx);
-  if (!report.init(aCx, exn, js::ErrorReport::WithSideEffects)) {
+  JS::ExceptionStack exnStack(aCx, exn, nullptr);
+  JS::ErrorReportBuilder report(aCx);
+  if (!report.init(aCx, exnStack, JS::ErrorReportBuilder::WithSideEffects)) {
     JS_ClearPendingException(aCx);
     return;
   }
 
   RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
   xpcReport->Init(report.report(), report.toStringResult().c_str(),
                   aWorkerPrivate->IsChromeWorker(), aWorkerPrivate->WindowID());
 
--- a/dom/xhr/XMLHttpRequest.cpp
+++ b/dom/xhr/XMLHttpRequest.cpp
@@ -37,18 +37,18 @@ already_AddRefed<XMLHttpRequest> XMLHttp
       }
 
       cookieJarSettings = document->CookieJarSettings();
     } else {
       // We are here because this is a sandbox.
       cookieJarSettings = net::CookieJarSettings::Create();
     }
 
-    RefPtr<XMLHttpRequestMainThread> req = new XMLHttpRequestMainThread();
-    req->Construct(principal->GetPrincipal(), global, cookieJarSettings, false);
+    RefPtr<XMLHttpRequestMainThread> req = new XMLHttpRequestMainThread(global);
+    req->Construct(principal->GetPrincipal(), cookieJarSettings, false);
     req->InitParameters(aParams.mMozAnon, aParams.mMozSystem);
     return req.forget();
   }
 
   return XMLHttpRequestWorker::Construct(aGlobal, aParams, aRv);
 }
 
 }  // namespace dom
--- a/dom/xhr/XMLHttpRequest.h
+++ b/dom/xhr/XMLHttpRequest.h
@@ -122,14 +122,18 @@ class XMLHttpRequest : public XMLHttpReq
   virtual bool MozAnon() const = 0;
 
   virtual bool MozSystem() const = 0;
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override {
     return mozilla::dom::XMLHttpRequest_Binding::Wrap(aCx, this, aGivenProto);
   }
+
+ protected:
+  explicit XMLHttpRequest(nsIGlobalObject* aGlobalObject)
+      : XMLHttpRequestEventTarget(aGlobalObject) {}
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_XMLHttpRequest_h
--- a/dom/xhr/XMLHttpRequestEventTarget.h
+++ b/dom/xhr/XMLHttpRequestEventTarget.h
@@ -12,17 +12,18 @@
 namespace mozilla {
 namespace dom {
 
 class XMLHttpRequestEventTarget : public DOMEventTargetHelper {
  protected:
   explicit XMLHttpRequestEventTarget(DOMEventTargetHelper* aOwner)
       : DOMEventTargetHelper(aOwner) {}
 
-  XMLHttpRequestEventTarget() = default;
+  explicit XMLHttpRequestEventTarget(nsIGlobalObject* aGlobalObject)
+      : DOMEventTargetHelper(aGlobalObject) {}
 
   virtual ~XMLHttpRequestEventTarget() = default;
 
  public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(XMLHttpRequestEventTarget,
                                            DOMEventTargetHelper)
 
--- a/dom/xhr/XMLHttpRequestMainThread.cpp
+++ b/dom/xhr/XMLHttpRequestMainThread.cpp
@@ -181,18 +181,20 @@ static void AddLoadFlags(nsIRequest* req
 
 /////////////////////////////////////////////
 //
 //
 /////////////////////////////////////////////
 
 bool XMLHttpRequestMainThread::sDontWarnAboutSyncXHR = false;
 
-XMLHttpRequestMainThread::XMLHttpRequestMainThread()
-    : mResponseBodyDecodedPos(0),
+XMLHttpRequestMainThread::XMLHttpRequestMainThread(
+    nsIGlobalObject* aGlobalObject)
+    : XMLHttpRequest(aGlobalObject),
+      mResponseBodyDecodedPos(0),
       mResponseType(XMLHttpRequestResponseType::_empty),
       mRequestObserver(nullptr),
       mState(XMLHttpRequest_Binding::UNSENT),
       mFlagSynchronous(false),
       mFlagAborted(false),
       mFlagParseBody(false),
       mFlagSyncLooping(false),
       mFlagBackgroundRequest(false),
--- a/dom/xhr/XMLHttpRequestMainThread.h
+++ b/dom/xhr/XMLHttpRequestMainThread.h
@@ -205,26 +205,25 @@ class XMLHttpRequestMainThread final : p
     eRequest,
     eUnreachable,
     eChannelOpen,
     eRedirect,
     eTerminated,
     ENUM_MAX
   };
 
-  XMLHttpRequestMainThread();
+  explicit XMLHttpRequestMainThread(nsIGlobalObject* aGlobalObject);
 
-  void Construct(nsIPrincipal* aPrincipal, nsIGlobalObject* aGlobalObject,
+  void Construct(nsIPrincipal* aPrincipal,
                  nsICookieJarSettings* aCookieJarSettings, bool aForWorker,
                  nsIURI* aBaseURI = nullptr, nsILoadGroup* aLoadGroup = nullptr,
                  PerformanceStorage* aPerformanceStorage = nullptr,
                  nsICSPEventListener* aCSPEventListener = nullptr) {
     MOZ_ASSERT(aPrincipal);
     mPrincipal = aPrincipal;
-    BindToOwner(aGlobalObject);
     mBaseURI = aBaseURI;
     mLoadGroup = aLoadGroup;
     mCookieJarSettings = aCookieJarSettings;
     mForWorker = aForWorker;
     mPerformanceStorage = aPerformanceStorage;
     mCSPEventListener = aCSPEventListener;
   }
 
--- a/dom/xhr/XMLHttpRequestWorker.cpp
+++ b/dom/xhr/XMLHttpRequestWorker.cpp
@@ -760,19 +760,19 @@ bool Proxy::Init() {
   }
 
   nsPIDOMWindowInner* ownerWindow = mWorkerPrivate->GetWindow();
   if (ownerWindow && !ownerWindow->IsCurrentInnerWindow()) {
     NS_WARNING("Window has navigated, cannot create XHR here.");
     return false;
   }
 
-  mXHR = new XMLHttpRequestMainThread();
+  mXHR = new XMLHttpRequestMainThread(ownerWindow ? ownerWindow->AsGlobal()
+                                                  : nullptr);
   mXHR->Construct(mWorkerPrivate->GetPrincipal(),
-                  ownerWindow ? ownerWindow->AsGlobal() : nullptr,
                   mWorkerPrivate->CookieJarSettings(), true,
                   mWorkerPrivate->GetBaseURI(), mWorkerPrivate->GetLoadGroup(),
                   mWorkerPrivate->GetPerformanceStorage(),
                   mWorkerPrivate->CSPEventListener());
 
   mXHR->SetParameters(mMozAnon, mMozSystem);
   mXHR->SetClientInfoAndController(mClientInfo, mController);
 
@@ -1350,18 +1350,20 @@ void SendRunnable::RunOnMainThread(Error
       if (!mProxy->mUploadEventListenersAttached &&
           !mProxy->AddRemoveEventListeners(true, true)) {
         MOZ_ASSERT(false, "This should never fail!");
       }
     }
   }
 }
 
-XMLHttpRequestWorker::XMLHttpRequestWorker(WorkerPrivate* aWorkerPrivate)
-    : mWorkerPrivate(aWorkerPrivate),
+XMLHttpRequestWorker::XMLHttpRequestWorker(WorkerPrivate* aWorkerPrivate,
+                                           nsIGlobalObject* aGlobalObject)
+    : XMLHttpRequest(aGlobalObject),
+      mWorkerPrivate(aWorkerPrivate),
       mResponseType(XMLHttpRequestResponseType::_empty),
       mStateData(new StateData()),
       mResponseData(new ResponseData()),
       mResponseArrayBufferValue(nullptr),
       mResponseJSONValue(JS::UndefinedValue()),
       mTimeout(0),
       mBackgroundRequest(false),
       mWithCredentials(false),
@@ -1423,18 +1425,18 @@ already_AddRefed<XMLHttpRequest> XMLHttp
   MOZ_ASSERT(workerPrivate);
 
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
   if (NS_WARN_IF(!global)) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
-  RefPtr<XMLHttpRequestWorker> xhr = new XMLHttpRequestWorker(workerPrivate);
-  xhr->BindToOwner(global);
+  RefPtr<XMLHttpRequestWorker> xhr =
+      new XMLHttpRequestWorker(workerPrivate, global);
 
   if (workerPrivate->XHRParamsAllowed()) {
     if (aParams.mMozSystem)
       xhr->mMozAnon = true;
     else
       xhr->mMozAnon = aParams.mMozAnon;
     xhr->mMozSystem = aParams.mMozSystem;
   }
--- a/dom/xhr/XMLHttpRequestWorker.h
+++ b/dom/xhr/XMLHttpRequestWorker.h
@@ -218,17 +218,18 @@ class XMLHttpRequestWorker final : publi
 
   virtual bool MozAnon() const override { return mMozAnon; }
 
   virtual bool MozSystem() const override { return mMozSystem; }
 
   bool SendInProgress() const { return !!mWorkerRef; }
 
  private:
-  explicit XMLHttpRequestWorker(WorkerPrivate* aWorkerPrivate);
+  XMLHttpRequestWorker(WorkerPrivate* aWorkerPrivate,
+                       nsIGlobalObject* aGlobalObject);
   ~XMLHttpRequestWorker();
 
   enum ReleaseType { Default, XHRIsGoingAway, WorkerIsGoingAway };
 
   void ReleaseProxy(ReleaseType aType = Default);
 
   void MaybePin(ErrorResult& aRv);
 
--- a/gfx/layers/AnimationHelper.cpp
+++ b/gfx/layers/AnimationHelper.cpp
@@ -309,20 +309,21 @@ AnimationHelper::SampleResult AnimationH
   MOZ_ASSERT(aAnimationValues.IsEmpty(),
              "Should be called with empty aAnimationValues");
 
   nsTArray<RefPtr<RawServoAnimationValue>> nonAnimatingValues;
   for (PropertyAnimationGroup& group : aPropertyAnimationGroups) {
     // Initialize animation value with base style.
     RefPtr<RawServoAnimationValue> currValue = group.mBaseStyle;
 
-    CanSkipCompose canSkipCompose = aPropertyAnimationGroups.Length() == 1 &&
-                                            group.mAnimations.Length() == 1
-                                        ? CanSkipCompose::IfPossible
-                                        : CanSkipCompose::No;
+    CanSkipCompose canSkipCompose =
+        aPreviousValue && aPropertyAnimationGroups.Length() == 1 &&
+                group.mAnimations.Length() == 1
+            ? CanSkipCompose::IfPossible
+            : CanSkipCompose::No;
 
     MOZ_ASSERT(
         !group.mAnimations.IsEmpty() ||
             nsCSSPropertyIDSet::TransformLikeProperties().HasProperty(
                 group.mProperty),
         "Only transform-like properties can have empty PropertyAnimation list");
 
     // For properties which are not animating (i.e. their values are always the
--- a/js/public/ErrorReport.h
+++ b/js/public/ErrorReport.h
@@ -23,16 +23,18 @@
 #include <stddef.h>  // size_t
 #include <stdint.h>  // int16_t, uint16_t
 #include <string.h>  // strlen
 
 #include "jstypes.h"  // JS_PUBLIC_API
 
 #include "js/AllocPolicy.h"        // js::SystemAllocPolicy
 #include "js/CharacterEncoding.h"  // JS::ConstUTF8CharsZ
+#include "js/Exception.h"          // JS::ExceptionStack
+#include "js/RootingAPI.h"         // JS::HandleObject, JS::RootedObject
 #include "js/UniquePtr.h"          // js::UniquePtr
 #include "js/Vector.h"             // js::Vector
 
 struct JS_PUBLIC_API JSContext;
 class JS_PUBLIC_API JSString;
 
 /**
  * Possible exception types. These types are part of a JSErrorFormatString
@@ -260,9 +262,99 @@ class JSErrorReport : public JSErrorBase
                            size_t tokenOffsetArg);
 
   bool isWarning() const { return isWarning_; }
 
  private:
   void freeLinebuf();
 };
 
+namespace JS {
+
+struct MOZ_STACK_CLASS JS_PUBLIC_API ErrorReportBuilder {
+  explicit ErrorReportBuilder(JSContext* cx);
+  ~ErrorReportBuilder();
+
+  enum SniffingBehavior { WithSideEffects, NoSideEffects };
+
+  /**
+   * Generate a JSErrorReport from the provided thrown value.
+   *
+   * If the value is a (possibly wrapped) Error object, the JSErrorReport will
+   * be exactly initialized from the Error object's information, without
+   * observable side effects. (The Error object's JSErrorReport is reused, if
+   * it has one.)
+   *
+   * Otherwise various attempts are made to derive JSErrorReport information
+   * from |exnStack| and from the current execution state.  This process is
+   * *definitely* inconsistent with any standard, and particulars of the
+   * behavior implemented here generally shouldn't be relied upon.
+   *
+   * If the value of |sniffingBehavior| is |WithSideEffects|, some of these
+   * attempts *may* invoke user-configurable behavior when the exception is an
+   * object: converting it to a string, detecting and getting its properties,
+   * accessing its prototype chain, and others are possible.  Users *must*
+   * tolerate |ErrorReportBuilder::init| potentially having arbitrary effects.
+   * Any exceptions thrown by these operations will be caught and silently
+   * ignored, and "default" values will be substituted into the JSErrorReport.
+   *
+   * But if the value of |sniffingBehavior| is |NoSideEffects|, these attempts
+   * *will not* invoke any observable side effects.  The JSErrorReport will
+   * simply contain fewer, less precise details.
+   *
+   * Unlike some functions involved in error handling, this function adheres
+   * to the usual JSAPI return value error behavior.
+   */
+  bool init(JSContext* cx, const JS::ExceptionStack& exnStack,
+            SniffingBehavior sniffingBehavior);
+
+  JSErrorReport* report() const { return reportp; }
+
+  const JS::ConstUTF8CharsZ toStringResult() const { return toStringResult_; }
+
+ private:
+  // More or less an equivalent of JS_ReportErrorNumber/js::ReportErrorNumberVA
+  // but fills in an ErrorReport instead of reporting it.  Uses varargs to
+  // make it simpler to call js::ExpandErrorArgumentsVA.
+  //
+  // Returns false if we fail to actually populate the ErrorReport
+  // for some reason (probably out of memory).
+  bool populateUncaughtExceptionReportUTF8(JSContext* cx,
+                                           JS::HandleObject stack, ...);
+  bool populateUncaughtExceptionReportUTF8VA(JSContext* cx,
+                                             JS::HandleObject stack,
+                                             va_list ap);
+
+  // Reports exceptions from add-on scopes to telemetry.
+  void ReportAddonExceptionToTelemetry(JSContext* cx);
+
+  // We may have a provided JSErrorReport, so need a way to represent that.
+  JSErrorReport* reportp;
+
+  // Or we may need to synthesize a JSErrorReport one of our own.
+  JSErrorReport ownedReport;
+
+  // Root our exception value to keep a possibly borrowed |reportp| alive.
+  JS::RootedObject exnObject;
+
+  // And for our filename.
+  JS::UniqueChars filename;
+
+  // We may have a result of error.toString().
+  // FIXME: We should not call error.toString(), since it could have side
+  //        effect (see bug 633623).
+  JS::ConstUTF8CharsZ toStringResult_;
+  JS::UniqueChars toStringResultBytesStorage;
+};
+
+// Writes a full report to a file descriptor. Does nothing for JSErrorReports
+// which are warnings, unless reportWarnings is set.
+extern JS_PUBLIC_API void PrintError(JSContext* cx, FILE* file,
+                                     JSErrorReport* report,
+                                     bool reportWarnings);
+
+extern JS_PUBLIC_API void PrintError(JSContext* cx, FILE* file,
+                                     const JS::ErrorReportBuilder& builder,
+                                     bool reportWarnings);
+
+}  // namespace JS
+
 #endif /* js_ErrorReport_h */
--- a/js/src/jsapi-tests/moz.build
+++ b/js/src/jsapi-tests/moz.build
@@ -158,16 +158,22 @@ if CONFIG['JS_BUILD_BINAST'] and CONFIG[
     # Otherwise, in the current state of the build system,
     # we can't have data files in js/src tests.
     # Also, fuzzing builds modify the const matching in the
     # token reader and hence affect the correctness of the tests.
     UNIFIED_SOURCES += [
         'testBinASTReader.cpp',
     ]
 
+if CONFIG['OS_ARCH'] not in ('WINNT', 'Darwin') and CONFIG['OS_TARGET'] != 'Android':
+    # open_memstream() not available on Windows, macOS, or Android
+    UNIFIED_SOURCES += [
+        'testPrintError.cpp',
+    ]
+
 
 DEFINES['EXPORT_JS_API'] = True
 
 LOCAL_INCLUDES += [
     '!..',
     '..',
 ]
 
--- a/js/src/jsapi-tests/testArrayBuffer.cpp
+++ b/js/src/jsapi-tests/testArrayBuffer.cpp
@@ -272,18 +272,18 @@ BEGIN_TEST(testArrayBuffer_serializeExte
   // serialize(externalBuffer, [externalBuffer]) should throw for an unhandled
   // BufferContents kind.
   CHECK(!JS::Call(cx, JS::UndefinedHandleValue, serializeValue,
                   JS::HandleValueArray(args), &v));
 
   JS::ExceptionStack exnStack(cx);
   CHECK(JS::StealPendingExceptionStack(cx, &exnStack));
 
-  js::ErrorReport report(cx);
-  CHECK(report.init(cx, exnStack, js::ErrorReport::NoSideEffects));
+  JS::ErrorReportBuilder report(cx);
+  CHECK(report.init(cx, exnStack, JS::ErrorReportBuilder::NoSideEffects));
 
   CHECK_EQUAL(report.report()->errorNumber,
               static_cast<unsigned int>(JSMSG_SC_NOT_TRANSFERABLE));
 
   // Data should have been left alone.
   CHECK(!data.wasFreed());
 
   v.setNull();
--- a/js/src/jsapi-tests/testBinASTReader.cpp
+++ b/js/src/jsapi-tests/testBinASTReader.cpp
@@ -389,36 +389,34 @@ void runTestFromPath(JSContext* cx, cons
         MOZ_CRASH("Couldn't clear binExn");
       }
     }
 
     // The binary parser should accept the file iff the text parser has.
     if (binParsed.isOk() && !txtParsed) {
       fprintf(stderr, "Text file parsing failed: ");
 
-      js::ErrorReport report(cx);
-      if (!report.init(cx, txtExn, js::ErrorReport::WithSideEffects)) {
+      JS::ErrorReportBuilder report(cx);
+      if (!report.init(cx, txtExn, JS::ErrorReportBuilder::WithSideEffects)) {
         MOZ_CRASH("Couldn't report txtExn");
       }
 
-      PrintError(cx, stderr, report.toStringResult(), report.report(),
-                 /* reportWarnings */ true);
+      PrintError(cx, stderr, report, /* reportWarnings */ true);
       MOZ_CRASH("Binary parser accepted a file that text parser rejected");
     }
 
     if (binParsed.isErr() && txtParsed) {
       fprintf(stderr, "Binary file parsing failed: ");
 
-      js::ErrorReport report(cx);
-      if (!report.init(cx, binExn, js::ErrorReport::WithSideEffects)) {
+      JS::ErrorReportBuilder report(cx);
+      if (!report.init(cx, binExn, JS::ErrorReportBuilder::WithSideEffects)) {
         MOZ_CRASH("Couldn't report binExn");
       }
 
-      PrintError(cx, stderr, report.toStringResult(), report.report(),
-                 /* reportWarnings */ true);
+      PrintError(cx, stderr, report, /* reportWarnings */ true);
       MOZ_CRASH("Binary parser rejected a file that text parser accepted");
     }
 
     if (binParsed.isErr()) {
       fprintf(stderr,
               "Binary parser and text parser agree that %s is invalid\n",
               txtPath.get());
       continue;
--- a/js/src/jsapi-tests/testCompileUtf8.cpp
+++ b/js/src/jsapi-tests/testCompileUtf8.cpp
@@ -193,18 +193,18 @@ bool testBadUtf8(const char (&chars)[N],
 
     script = JS::Compile(cx, options, srcBuf);
     CHECK(!script);
   }
 
   JS::ExceptionStack exnStack(cx);
   CHECK(JS::StealPendingExceptionStack(cx, &exnStack));
 
-  js::ErrorReport report(cx);
-  CHECK(report.init(cx, exnStack, js::ErrorReport::WithSideEffects));
+  JS::ErrorReportBuilder report(cx);
+  CHECK(report.init(cx, exnStack, JS::ErrorReportBuilder::WithSideEffects));
 
   const auto* errorReport = report.report();
 
   CHECK(errorReport->errorNumber == errorNumber);
 
   CHECK(testMessage(errorReport->message()));
 
   {
@@ -276,18 +276,18 @@ bool testContext(const char (&chars)[N],
 
     script = JS::Compile(cx, options, srcBuf);
     CHECK(!script);
   }
 
   JS::ExceptionStack exnStack(cx);
   CHECK(JS::StealPendingExceptionStack(cx, &exnStack));
 
-  js::ErrorReport report(cx);
-  CHECK(report.init(cx, exnStack, js::ErrorReport::WithSideEffects));
+  JS::ErrorReportBuilder report(cx);
+  CHECK(report.init(cx, exnStack, JS::ErrorReportBuilder::WithSideEffects));
 
   const auto* errorReport = report.report();
 
   CHECK(errorReport->errorNumber == JSMSG_ILLEGAL_CHARACTER);
 
   const char16_t* lineOfContext = errorReport->linebuf();
   size_t lineOfContextLength = errorReport->linebufLength();
 
--- a/js/src/jsapi-tests/testEmptyWindowIsOmitted.cpp
+++ b/js/src/jsapi-tests/testEmptyWindowIsOmitted.cpp
@@ -108,18 +108,18 @@ template <typename CharT, size_t N>
 bool testOmittedWindow(const CharT (&chars)[N], unsigned expectedErrorNumber,
                        const char* badCodeUnits = nullptr) {
   JS::Rooted<JSScript*> script(cx, compile(chars, N - 1));
   CHECK(!script);
 
   JS::ExceptionStack exnStack(cx);
   CHECK(JS::StealPendingExceptionStack(cx, &exnStack));
 
-  js::ErrorReport report(cx);
-  CHECK(report.init(cx, exnStack, js::ErrorReport::WithSideEffects));
+  JS::ErrorReportBuilder report(cx);
+  CHECK(report.init(cx, exnStack, JS::ErrorReportBuilder::WithSideEffects));
 
   const auto* errorReport = report.report();
 
   CHECK(errorReport->errorNumber == expectedErrorNumber);
 
   if (const auto& notes = errorReport->notes) {
     CHECK(sizeof(CharT) == 1);
     CHECK(badCodeUnits != nullptr);
--- a/js/src/jsapi-tests/testErrorCopying.cpp
+++ b/js/src/jsapi-tests/testErrorCopying.cpp
@@ -17,16 +17,16 @@ BEGIN_TEST(testErrorCopying_columnCopied
   EXEC("function check() { Object; foo; }");
 
   JS::RootedValue rval(cx);
   CHECK(!JS_CallFunctionName(cx, global, "check", JS::HandleValueArray::empty(),
                              &rval));
   JS::ExceptionStack exnStack(cx);
   CHECK(JS::StealPendingExceptionStack(cx, &exnStack));
 
-  js::ErrorReport report(cx);
-  CHECK(report.init(cx, exnStack, js::ErrorReport::WithSideEffects));
+  JS::ErrorReportBuilder report(cx);
+  CHECK(report.init(cx, exnStack, JS::ErrorReportBuilder::WithSideEffects));
 
   CHECK_EQUAL(report.report()->column, 28u);
   return true;
 }
 
 END_TEST(testErrorCopying_columnCopied)
--- a/js/src/jsapi-tests/testErrorLineOfContext.cpp
+++ b/js/src/jsapi-tests/testErrorLineOfContext.cpp
@@ -53,18 +53,18 @@ template <size_t N>
 bool testLineOfContextHasNoLineTerminator(const char16_t (&chars)[N],
                                           char16_t expectedLast) {
   JS::RootedValue rval(cx);
   CHECK(!eval(chars, N - 1, &rval));
 
   JS::ExceptionStack exnStack(cx);
   CHECK(JS::StealPendingExceptionStack(cx, &exnStack));
 
-  js::ErrorReport report(cx);
-  CHECK(report.init(cx, exnStack, js::ErrorReport::WithSideEffects));
+  JS::ErrorReportBuilder report(cx);
+  CHECK(report.init(cx, exnStack, JS::ErrorReportBuilder::WithSideEffects));
 
   const auto* errorReport = report.report();
 
   const char16_t* lineOfContext = errorReport->linebuf();
   size_t lineOfContextLength = errorReport->linebufLength();
 
   CHECK(lineOfContext[lineOfContextLength] == '\0');
   char16_t last = lineOfContext[lineOfContextLength - 1];
--- a/js/src/jsapi-tests/testMutedErrors.cpp
+++ b/js/src/jsapi-tests/testMutedErrors.cpp
@@ -85,14 +85,14 @@ bool testInner(const char* asciiChars, b
 
 bool testError(const char* asciiChars) {
   JS::RootedValue rval(cx);
   CHECK(!eval(asciiChars, true, &rval));
 
   JS::ExceptionStack exnStack(cx);
   CHECK(JS::StealPendingExceptionStack(cx, &exnStack));
 
-  js::ErrorReport report(cx);
-  CHECK(report.init(cx, exnStack, js::ErrorReport::WithSideEffects));
+  JS::ErrorReportBuilder report(cx);
+  CHECK(report.init(cx, exnStack, JS::ErrorReportBuilder::WithSideEffects));
   CHECK(report.report()->isMuted == true);
   return true;
 }
 END_TEST(testMutedErrors)
--- a/js/src/jsapi-tests/testParseJSON.cpp
+++ b/js/src/jsapi-tests/testParseJSON.cpp
@@ -296,18 +296,18 @@ inline bool Error(JSContext* cx, const c
   str = input;
 
   bool ok = JS_ParseJSON(cx, str.chars(), str.length(), &dummy);
   CHECK(!ok);
 
   JS::ExceptionStack exnStack(cx);
   CHECK(StealPendingExceptionStack(cx, &exnStack));
 
-  js::ErrorReport report(cx);
-  CHECK(report.init(cx, exnStack, js::ErrorReport::WithSideEffects));
+  JS::ErrorReportBuilder report(cx);
+  CHECK(report.init(cx, exnStack, JS::ErrorReportBuilder::WithSideEffects));
   CHECK(report.report()->errorNumber == JSMSG_JSON_BAD_PARSE);
 
   UniqueChars lineAndColumnASCII =
       JS_smprintf("line %d column %d", expectedLine, expectedColumn);
   CHECK(strstr(report.toStringResult().c_str(), lineAndColumnASCII.get()) !=
         nullptr);
 
   /* We do not execute JS, so there should be no exception thrown. */
new file mode 100644
--- /dev/null
+++ b/js/src/jsapi-tests/testPrintError.cpp
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <cstdio>  // fclose, fflush, open_memstream
+
+#include "jsapi.h"           // JS_{Clear,Get}PendingException
+#include "jsfriendapi.h"     // js::ErrorReport
+#include "js/ErrorReport.h"  // JS::PrintError
+#include "js/Warnings.h"     // JS::SetWarningReporter, JS::WarnUTF8
+
+#include "jsapi-tests/tests.h"
+
+class AutoStreamBuffer {
+  char* buffer;
+  size_t size;
+  FILE* fp;
+
+ public:
+  AutoStreamBuffer() { fp = open_memstream(&buffer, &size); }
+
+  ~AutoStreamBuffer() {
+    fclose(fp);
+    free(buffer);
+  }
+
+  FILE* stream() { return fp; }
+
+  bool contains(const char* str) {
+    if (fflush(fp) != 0) {
+      fprintf(stderr, "Error flushing stream\n");
+      return false;
+    }
+    if (strcmp(buffer, str) != 0) {
+      fprintf(stderr, "Expected |%s|, got |%s|\n", str, buffer);
+      return false;
+    }
+    return true;
+  }
+};
+
+BEGIN_TEST(testPrintError_Works) {
+  AutoStreamBuffer buf;
+
+  CHECK(!execDontReport("throw null;", "testPrintError_Works.js", 3));
+
+  JS::ExceptionStack exnStack(cx);
+  CHECK(JS::StealPendingExceptionStack(cx, &exnStack));
+
+  JS::ErrorReportBuilder builder(cx);
+  CHECK(builder.init(cx, exnStack, JS::ErrorReportBuilder::NoSideEffects));
+  JS::PrintError(cx, buf.stream(), builder, false);
+
+  CHECK(buf.contains("testPrintError_Works.js:3:1 uncaught exception: null\n"));
+
+  return true;
+}
+END_TEST(testPrintError_Works)
+
+BEGIN_TEST(testPrintError_SkipWarning) {
+  JS::SetWarningReporter(cx, warningReporter);
+  CHECK(JS::WarnUTF8(cx, "warning message"));
+  CHECK(warningSuccess);
+  return true;
+}
+
+static bool warningSuccess;
+
+static void warningReporter(JSContext* cx, JSErrorReport* report) {
+  AutoStreamBuffer buf;
+  JS::PrintError(cx, buf.stream(), report, false);
+  warningSuccess = buf.contains("");
+}
+END_TEST(testPrintError_SkipWarning)
+
+bool cls_testPrintError_SkipWarning::warningSuccess = false;
+
+BEGIN_TEST(testPrintError_PrintWarning) {
+  JS::SetWarningReporter(cx, warningReporter);
+  CHECK(JS::WarnUTF8(cx, "warning message"));
+  CHECK(warningSuccess);
+  return true;
+}
+
+static bool warningSuccess;
+
+static void warningReporter(JSContext* cx, JSErrorReport* report) {
+  AutoStreamBuffer buf;
+  JS::PrintError(cx, buf.stream(), report, true);
+  warningSuccess = buf.contains("warning: warning message\n");
+}
+END_TEST(testPrintError_PrintWarning)
+
+bool cls_testPrintError_PrintWarning::warningSuccess = false;
+
+#define BURRITO "\xF0\x9F\x8C\xAF"
+
+BEGIN_TEST(testPrintError_UTF16CodePoints) {
+  AutoStreamBuffer buf;
+
+  static const char utf8code[] =
+      "function f() {\n  var x = `\n" BURRITO "`; " BURRITO "; } f();";
+
+  CHECK(!execDontReport(utf8code, "testPrintError_UTF16CodePoints.js", 1));
+
+  JS::ExceptionStack exnStack(cx);
+  CHECK(JS::StealPendingExceptionStack(cx, &exnStack));
+
+  JS::ErrorReportBuilder builder(cx);
+  CHECK(builder.init(cx, exnStack, JS::ErrorReportBuilder::NoSideEffects));
+  JS::PrintError(cx, buf.stream(), builder, false);
+
+  CHECK(buf.contains(
+      "testPrintError_UTF16CodePoints.js:3:4 SyntaxError: illegal character:\n"
+      "testPrintError_UTF16CodePoints.js:3:4 " BURRITO "`; " BURRITO
+      "; } f();\n"
+      "testPrintError_UTF16CodePoints.js:3:4 .....^\n"));
+
+  return true;
+}
+END_TEST(testPrintError_UTF16CodePoints)
+
+#undef BURRITO
--- a/js/src/jsapi-tests/testUncaughtSymbol.cpp
+++ b/js/src/jsapi-tests/testUncaughtSymbol.cpp
@@ -30,19 +30,19 @@ BEGIN_TEST(testUncaughtSymbol) {
   return true;
 }
 
 static SymbolExceptionType GetSymbolExceptionType(JSContext* cx) {
   JS::ExceptionStack exnStack(cx);
   MOZ_RELEASE_ASSERT(JS::StealPendingExceptionStack(cx, &exnStack));
   MOZ_RELEASE_ASSERT(exnStack.exception().isSymbol());
 
-  js::ErrorReport report(cx);
+  JS::ErrorReportBuilder report(cx);
   MOZ_RELEASE_ASSERT(
-      report.init(cx, exnStack, js::ErrorReport::WithSideEffects));
+      report.init(cx, exnStack, JS::ErrorReportBuilder::WithSideEffects));
 
   if (strcmp(report.toStringResult().c_str(),
              "uncaught exception: Symbol(Symbol.iterator)") == 0) {
     return SYMBOL_ITERATOR;
   }
   if (strcmp(report.toStringResult().c_str(),
              "uncaught exception: Symbol(foo)") == 0) {
     return SYMBOL_FOO;
--- a/js/src/jsexn.cpp
+++ b/js/src/jsexn.cpp
@@ -23,17 +23,18 @@
 #include "jsapi.h"
 #include "jsfriendapi.h"
 #include "jstypes.h"
 
 #include "gc/Rooting.h"
 #include "js/CharacterEncoding.h"
 #include "js/Class.h"
 #include "js/Conversions.h"
-#include "js/Exception.h"  // JS::ExceptionStack
+#include "js/ErrorReport.h"  // JS::PrintError
+#include "js/Exception.h"    // JS::ExceptionStack
 #include "js/SavedFrameAPI.h"
 #include "js/UniquePtr.h"
 #include "js/Value.h"
 #include "js/Warnings.h"  // JS::{,Set}WarningReporter
 #include "js/Wrapper.h"
 #include "util/Memory.h"
 #include "util/StringBuffer.h"
 #include "vm/Compartment.h"
@@ -282,17 +283,17 @@ JS_FRIEND_API JSLinearString* js::GetErr
 void js::ErrorToException(JSContext* cx, JSErrorReport* reportp,
                           JSErrorCallback callback, void* userRef) {
   MOZ_ASSERT(!reportp->isWarning());
 
   // We cannot throw a proper object inside the self-hosting realm, as we
   // cannot construct the Error constructor without self-hosted code. Just
   // print the error to stderr to help debugging.
   if (cx->realm()->isSelfHostingRealm()) {
-    PrintError(cx, stderr, JS::ConstUTF8CharsZ(), reportp, true);
+    JS::PrintError(cx, stderr, reportp, true);
     return;
   }
 
   // Find the exception index associated with this error.
   JSErrNum errorNumber = static_cast<JSErrNum>(reportp->errorNumber);
   if (!callback) {
     callback = GetErrorMessage;
   }
@@ -345,17 +346,17 @@ void js::ErrorToException(JSContext* cx,
   RootedValue errValue(cx, ObjectValue(*errObject));
   RootedSavedFrame nstack(cx);
   if (stack) {
     nstack = &stack->as<SavedFrame>();
   }
   cx->setPendingException(errValue, nstack);
 }
 
-using SniffingBehavior = js::ErrorReport::SniffingBehavior;
+using SniffingBehavior = JS::ErrorReportBuilder::SniffingBehavior;
 
 static bool IsDuckTypedErrorObject(JSContext* cx, HandleObject exnObject,
                                    const char** filename_strp) {
   /*
    * This function is called from ErrorReport::init and so should not generate
    * any new exceptions.
    */
   AutoClearPendingException acpe(cx);
@@ -459,55 +460,52 @@ static JSString* ErrorReportToString(JSC
     if (!message) {
       return nullptr;
     }
   }
 
   return FormatErrorMessage(cx, name, message);
 }
 
-ErrorReport::ErrorReport(JSContext* cx) : reportp(nullptr), exnObject(cx) {}
-
-ErrorReport::~ErrorReport() = default;
+JS::ErrorReportBuilder::ErrorReportBuilder(JSContext* cx)
+    : reportp(nullptr), exnObject(cx) {}
 
-bool ErrorReport::init(JSContext* cx, const JS::ExceptionStack& exnStack,
-                       SniffingBehavior sniffingBehavior) {
-  return init(cx, exnStack.exception(), sniffingBehavior, exnStack.stack());
-}
+JS::ErrorReportBuilder::~ErrorReportBuilder() = default;
 
-bool ErrorReport::init(JSContext* cx, HandleValue exn,
-                       SniffingBehavior sniffingBehavior,
-                       HandleObject fallbackStack) {
+bool JS::ErrorReportBuilder::init(JSContext* cx,
+                                  const JS::ExceptionStack& exnStack,
+                                  SniffingBehavior sniffingBehavior) {
   MOZ_ASSERT(!cx->isExceptionPending());
   MOZ_ASSERT(!reportp);
 
-  if (exn.isObject()) {
+  if (exnStack.exception().isObject()) {
     // Because ToString below could error and an exception object could become
     // unrooted, we must root our exception object, if any.
-    exnObject = &exn.toObject();
+    exnObject = &exnStack.exception().toObject();
     reportp = ErrorFromException(cx, exnObject);
   }
 
   // Be careful not to invoke ToString if we've already successfully extracted
   // an error report, since the exception might be wrapped in a security
   // wrapper, and ToString-ing it might throw.
   RootedString str(cx);
   if (reportp) {
     str = ErrorReportToString(cx, exnObject, reportp, sniffingBehavior);
-  } else if (exn.isSymbol()) {
+  } else if (exnStack.exception().isSymbol()) {
     RootedValue strVal(cx);
-    if (js::SymbolDescriptiveString(cx, exn.toSymbol(), &strVal)) {
+    if (js::SymbolDescriptiveString(cx, exnStack.exception().toSymbol(),
+                                    &strVal)) {
       str = strVal.toString();
     } else {
       str = nullptr;
     }
   } else if (exnObject && sniffingBehavior == NoSideEffects) {
     str = cx->names().Object;
   } else {
-    str = ToString<CanGC>(cx, exn);
+    str = js::ToString<CanGC>(cx, exnStack.exception());
   }
 
   if (!str) {
     cx->clearPendingException();
   }
 
   // If ErrorFromException didn't get us a JSErrorReport, then the object
   // was not an ErrorObject, security-wrapped or otherwise. However, it might
@@ -540,17 +538,17 @@ bool ErrorReport::init(JSContext* cx, Ha
     // If we have the right fields, override the ToString we performed on
     // the exception object above with something built out of its quacks
     // (i.e. as much of |NameQuack: MessageQuack| as we can make).
     str = FormatErrorMessage(cx, name, msg);
 
     {
       AutoClearPendingException acpe(cx);
       if (JS_GetProperty(cx, exnObject, filename_str, &val)) {
-        RootedString tmp(cx, ToString<CanGC>(cx, val));
+        RootedString tmp(cx, js::ToString<CanGC>(cx, val));
         if (tmp) {
           filename = JS_EncodeStringToUTF8(cx, tmp);
         }
       }
     }
     if (!filename) {
       filename = DuplicateString("");
       if (!filename) {
@@ -609,46 +607,47 @@ bool ErrorReport::init(JSContext* cx, Ha
   if (!reportp) {
     // This is basically an inlined version of
     //
     //   JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
     //                            JSMSG_UNCAUGHT_EXCEPTION, utf8Message);
     //
     // but without the reporting bits.  Instead it just puts all
     // the stuff we care about in our ownedReport and message_.
-    if (!populateUncaughtExceptionReportUTF8(cx, fallbackStack, utf8Message)) {
+    if (!populateUncaughtExceptionReportUTF8(cx, exnStack.stack(),
+                                             utf8Message)) {
       // Just give up.  We're out of memory or something; not much we can
       // do here.
       return false;
     }
   } else {
     toStringResult_ = JS::ConstUTF8CharsZ(utf8Message, strlen(utf8Message));
   }
 
   return true;
 }
 
-bool ErrorReport::populateUncaughtExceptionReportUTF8(
-    JSContext* cx, HandleObject fallbackStack, ...) {
+bool JS::ErrorReportBuilder::populateUncaughtExceptionReportUTF8(
+    JSContext* cx, HandleObject stack, ...) {
   va_list ap;
-  va_start(ap, fallbackStack);
-  bool ok = populateUncaughtExceptionReportUTF8VA(cx, fallbackStack, ap);
+  va_start(ap, stack);
+  bool ok = populateUncaughtExceptionReportUTF8VA(cx, stack, ap);
   va_end(ap);
   return ok;
 }
 
-bool ErrorReport::populateUncaughtExceptionReportUTF8VA(
-    JSContext* cx, HandleObject fallbackStack, va_list ap) {
+bool JS::ErrorReportBuilder::populateUncaughtExceptionReportUTF8VA(
+    JSContext* cx, HandleObject stack, va_list ap) {
   new (&ownedReport) JSErrorReport();
   ownedReport.isWarning_ = false;
   ownedReport.errorNumber = JSMSG_UNCAUGHT_EXCEPTION;
 
   bool skippedAsync;
   RootedSavedFrame frame(
-      cx, UnwrapSavedFrame(cx, cx->realm()->principals(), fallbackStack,
+      cx, UnwrapSavedFrame(cx, cx->realm()->principals(), stack,
                            SavedFrameSelfHosted::Exclude, skippedAsync));
   if (frame) {
     filename = StringToNewUTF8CharsZ(cx, *frame->getSource());
     if (!filename) {
       return false;
     }
 
     // |ownedReport.filename| inherits the lifetime of |ErrorReport::filename|.
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -1284,100 +1284,16 @@ typedef enum JSErrNum {
 
 namespace js {
 
 /* Implemented in vm/JSContext.cpp. */
 
 extern JS_FRIEND_API const JSErrorFormatString* GetErrorMessage(
     void* userRef, const unsigned errorNumber);
 
-struct MOZ_STACK_CLASS JS_FRIEND_API ErrorReport {
-  explicit ErrorReport(JSContext* cx);
-  ~ErrorReport();
-
-  enum SniffingBehavior { WithSideEffects, NoSideEffects };
-
-  /**
-   * Generate a JSErrorReport from the provided thrown value.
-   *
-   * If the value is a (possibly wrapped) Error object, the JSErrorReport will
-   * be exactly initialized from the Error object's information, without
-   * observable side effects. (The Error object's JSErrorReport is reused, if
-   * it has one.)
-   *
-   * Otherwise various attempts are made to derive JSErrorReport information
-   * from |exn| and from the current execution state.  This process is
-   * *definitely* inconsistent with any standard, and particulars of the
-   * behavior implemented here generally shouldn't be relied upon.
-   *
-   * To fill in information such as file-name or line number the (optional)
-   * stack for the pending exception can be used. For this first SavedFrame
-   * is used.
-   *
-   * If the value of |sniffingBehavior| is |WithSideEffects|, some of these
-   * attempts *may* invoke user-configurable behavior when |exn| is an object:
-   * converting |exn| to a string, detecting and getting properties on |exn|,
-   * accessing |exn|'s prototype chain, and others are possible.  Users *must*
-   * tolerate |ErrorReport::init| potentially having arbitrary effects.  Any
-   * exceptions thrown by these operations will be caught and silently
-   * ignored, and "default" values will be substituted into the JSErrorReport.
-   *
-   * But if the value of |sniffingBehavior| is |NoSideEffects|, these attempts
-   * *will not* invoke any observable side effects.  The JSErrorReport will
-   * simply contain fewer, less precise details.
-   *
-   * Unlike some functions involved in error handling, this function adheres
-   * to the usual JSAPI return value error behavior.
-   */
-  bool init(JSContext* cx, JS::HandleValue exn,
-            SniffingBehavior sniffingBehavior,
-            JS::HandleObject fallbackStack = nullptr);
-
-  bool init(JSContext* cx, const JS::ExceptionStack& exnStack,
-            SniffingBehavior sniffingBehavior);
-
-  JSErrorReport* report() { return reportp; }
-
-  const JS::ConstUTF8CharsZ toStringResult() { return toStringResult_; }
-
- private:
-  // More or less an equivalent of JS_ReportErrorNumber/js::ReportErrorNumberVA
-  // but fills in an ErrorReport instead of reporting it.  Uses varargs to
-  // make it simpler to call js::ExpandErrorArgumentsVA.
-  //
-  // Returns false if we fail to actually populate the ErrorReport
-  // for some reason (probably out of memory).
-  bool populateUncaughtExceptionReportUTF8(JSContext* cx,
-                                           JS::HandleObject fallbackStack, ...);
-  bool populateUncaughtExceptionReportUTF8VA(JSContext* cx,
-                                             JS::HandleObject fallbackStack,
-                                             va_list ap);
-
-  // Reports exceptions from add-on scopes to telemetry.
-  void ReportAddonExceptionToTelemetry(JSContext* cx);
-
-  // We may have a provided JSErrorReport, so need a way to represent that.
-  JSErrorReport* reportp;
-
-  // Or we may need to synthesize a JSErrorReport one of our own.
-  JSErrorReport ownedReport;
-
-  // Root our exception value to keep a possibly borrowed |reportp| alive.
-  JS::RootedObject exnObject;
-
-  // And for our filename.
-  JS::UniqueChars filename;
-
-  // We may have a result of error.toString().
-  // FIXME: We should not call error.toString(), since it could have side
-  //        effect (see bug 633623).
-  JS::ConstUTF8CharsZ toStringResult_;
-  JS::UniqueChars toStringResultBytesStorage;
-};
-
 /* Implemented in vm/StructuredClone.cpp. */
 extern JS_FRIEND_API uint64_t GetSCOffset(JSStructuredCloneWriter* writer);
 
 namespace Scalar {
 
 // Scalar types that can appear in typed arrays and typed objects.
 // The enum values must be kept in sync with:
 //  * the JS_SCALARTYPEREPR constants
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -101,16 +101,17 @@
 #include "js/ArrayBuffer.h"  // JS::{CreateMappedArrayBufferContents,NewMappedArrayBufferWithContents,IsArrayBufferObject,GetArrayBufferLengthAndData}
 #include "js/BuildId.h"      // JS::BuildIdCharVector, JS::SetProcessBuildIdOp
 #include "js/CharacterEncoding.h"
 #include "js/CompilationAndEvaluation.h"
 #include "js/CompileOptions.h"
 #include "js/ContextOptions.h"  // JS::ContextOptions{,Ref}
 #include "js/Debug.h"
 #include "js/Equality.h"                 // JS::SameValue
+#include "js/ErrorReport.h"              // JS::PrintError
 #include "js/Exception.h"                // JS::StealPendingExceptionStack
 #include "js/experimental/SourceHook.h"  // js::{Set,Forget,}SourceHook
 #include "js/GCVector.h"
 #include "js/Initialization.h"
 #include "js/JSON.h"
 #include "js/MemoryFunctions.h"
 #include "js/Modules.h"  // JS::GetModulePrivate, JS::SetModule{DynamicImport,Metadata,Resolve}Hook, JS::SetModulePrivate
 #include "js/Printf.h"
@@ -3837,18 +3838,18 @@ static bool CopyErrorReportToObject(JSCo
   return DefineDataProperty(cx, obj, cx->names().notes, notesArrayVal);
 }
 
 static bool CreateErrorReport(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
 
   // We don't have a stack here, so just initialize with null.
   JS::ExceptionStack exnStack(cx, args.get(0), nullptr);
-  js::ErrorReport report(cx);
-  if (!report.init(cx, exnStack, js::ErrorReport::WithSideEffects)) {
+  JS::ErrorReportBuilder report(cx);
+  if (!report.init(cx, exnStack, JS::ErrorReportBuilder::WithSideEffects)) {
     return false;
   }
 
   MOZ_ASSERT(!report.report()->isWarning());
 
   RootedObject obj(cx, JS_NewPlainObject(cx));
   if (!obj) {
     return false;
@@ -8807,17 +8808,17 @@ static const JSFunctionSpecWithHelp shel
 "  Stop accounting time to mutator vs GC and dump the results."),
 
     JS_FN_HELP("throwError", ThrowError, 0, 0,
 "throwError()",
 "  Throw an error from JS_ReportError."),
 
     JS_FN_HELP("createErrorReport", CreateErrorReport, 1, 0,
 "createErrorReport(value)",
-"  Create an js::ErrorReport object from the given value and serialize\n"
+"  Create an JS::ErrorReportBuilder object from the given value and serialize\n"
 "  to an object."),
 
 #if defined(DEBUG) || defined(JS_JITSPEW)
     JS_FN_HELP("disassemble", DisassembleToString, 1, 0,
 "disassemble([fun/code])",
 "  Return the disassembly for the given function or code.\n"
 "  All disassembly functions take these options as leading string arguments:\n"
 "    \"-r\" (disassemble recursively)\n"
@@ -9725,28 +9726,28 @@ js::shell::AutoReportException::~AutoRep
   if (!JS::StealPendingExceptionStack(cx, &exnStack)) {
     fprintf(stderr, "out of memory while stealing exception\n");
     fflush(stderr);
     JS_ClearPendingException(cx);
     return;
   }
 
   ShellContext* sc = GetShellContext(cx);
-  js::ErrorReport report(cx);
-  if (!report.init(cx, exnStack, js::ErrorReport::WithSideEffects)) {
-    fprintf(stderr, "out of memory initializing ErrorReport\n");
+  JS::ErrorReportBuilder report(cx);
+  if (!report.init(cx, exnStack, JS::ErrorReportBuilder::WithSideEffects)) {
+    fprintf(stderr, "out of memory initializing JS::ErrorReportBuilder\n");
     fflush(stderr);
     JS_ClearPendingException(cx);
     return;
   }
 
   MOZ_ASSERT(!report.report()->isWarning());
 
   FILE* fp = ErrorFilePointer();
-  PrintError(cx, fp, report.toStringResult(), report.report(), reportWarnings);
+  JS::PrintError(cx, fp, report, reportWarnings);
   JS_ClearPendingException(cx);
 
   if (!PrintStackTrace(cx, exnStack.stack())) {
     fputs("(Unable to print stack trace)\n", fp);
     JS_ClearPendingException(cx);
   }
 
 #if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
@@ -9776,17 +9777,17 @@ void js::shell::WarningReporter(JSContex
       fputs("Unhandled error happened while creating last warning object.\n",
             fp);
       fflush(fp);
     }
     savedExc.restore();
   }
 
   // Print the warning.
-  PrintError(cx, fp, JS::ConstUTF8CharsZ(), report, reportWarnings);
+  JS::PrintError(cx, fp, report, reportWarnings);
 }
 
 static bool global_enumerate(JSContext* cx, JS::HandleObject obj,
                              JS::MutableHandleIdVector properties,
                              bool enumerableOnly) {
 #ifdef LAZY_STANDARD_CLASSES
   return JS_NewEnumerateStandardClasses(cx, obj, properties, enumerableOnly);
 #else
--- a/js/src/shell/jsrtfuzzing/jsrtfuzzing.cpp
+++ b/js/src/shell/jsrtfuzzing/jsrtfuzzing.cpp
@@ -13,43 +13,42 @@
 #include <stdio.h>  // fflush, fprintf, fputs
 
 #include "FuzzerDefs.h"
 #include "FuzzingInterface.h"
 #include "jsapi.h"  // JS_ClearPendingException, JS_IsExceptionPending, JS_SetProperty
 
 #include "js/CompilationAndEvaluation.h"  // JS::Evaluate
 #include "js/CompileOptions.h"            // JS::CompileOptions
+#include "js/ErrorReport.h"               // JS::PrintError
 #include "js/Exception.h"                 // JS::StealPendingExceptionStack
 #include "js/RootingAPI.h"                // JS::Rooted
 #include "js/SourceText.h"                // JS::Source{Ownership,Text}
 #include "js/Value.h"                     // JS::Value
 #include "shell/jsshell.h"  // js::shell::{reportWarnings,PrintStackTrace,sArg{c,v}}
 #include "vm/Interpreter.h"
-#include "vm/JSContext.h"  // js::PrintError
 #include "vm/TypedArrayObject.h"
 
 #include "vm/ArrayBufferObject-inl.h"
 #include "vm/JSContext-inl.h"
 
 static JSContext* gCx = nullptr;
 static std::string gFuzzModuleName;
 
 static void CrashOnPendingException() {
   if (JS_IsExceptionPending(gCx)) {
     JS::ExceptionStack exnStack(gCx);
     (void)JS::StealPendingExceptionStack(gCx, &exnStack);
 
-    js::ErrorReport report(gCx);
-    if (!report.init(gCx, exnStack, js::ErrorReport::WithSideEffects)) {
-      fprintf(stderr, "out of memory initializing ErrorReport\n");
+    JS::ErrorReportBuilder report(gCx);
+    if (!report.init(gCx, exnStack, JS::ErrorReportBuilder::WithSideEffects)) {
+      fprintf(stderr, "out of memory initializing JS::ErrorReportBuilder\n");
       fflush(stderr);
     } else {
-      js::PrintError(gCx, stderr, report.toStringResult(), report.report(),
-                     js::shell::reportWarnings);
+      JS::PrintError(gCx, stderr, report, js::shell::reportWarnings);
       if (!js::shell::PrintStackTrace(gCx, exnStack.stack())) {
         fputs("(Unable to print stack trace)\n", stderr);
       }
     }
 
     MOZ_CRASH("Unhandled exception from JS runtime!");
   }
 }
--- a/js/src/vm/JSContext.cpp
+++ b/js/src/vm/JSContext.cpp
@@ -6,21 +6,23 @@
 
 /*
  * JS execution context.
  */
 
 #include "vm/JSContext-inl.h"
 
 #include "mozilla/ArrayUtils.h"
+#include "mozilla/CheckedInt.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Sprintf.h"
 #include "mozilla/TextUtils.h"
 #include "mozilla/Unused.h"
+#include "mozilla/Utf8.h"  // mozilla::ConvertUtf16ToUtf8
 
 #include <stdarg.h>
 #include <string.h>
 #ifdef ANDROID
 #  include <android/log.h>
 #  include <fstream>
 #  include <string>
 #endif  // ANDROID
@@ -349,39 +351,65 @@ void js::ReportUsageErrorASCII(JSContext
   }
 }
 
 enum class PrintErrorKind { Error, Warning, Note };
 
 static void PrintErrorLine(FILE* file, const char* prefix,
                            JSErrorReport* report) {
   if (const char16_t* linebuf = report->linebuf()) {
-    size_t n = report->linebufLength();
+    UniqueChars line;
+    size_t n;
+    {
+      size_t linebufLen = report->linebufLength();
+
+      // This function is only used for shell command-line sorts of stuff where
+      // performance doesn't really matter, so just encode into max-sized
+      // memory.
+      mozilla::CheckedInt<size_t> utf8Len(linebufLen);
+      utf8Len *= 3;
+      if (utf8Len.isValid()) {
+        line = UniqueChars(js_pod_malloc<char>(utf8Len.value()));
+        if (line) {
+          n = mozilla::ConvertUtf16toUtf8({linebuf, linebufLen},
+                                          {line.get(), utf8Len.value()});
+        }
+      }
+    }
+
+    const char* utf8buf;
+    if (line) {
+      utf8buf = line.get();
+    } else {
+      static const char unavailableStr[] = "<context unavailable>";
+      utf8buf = unavailableStr;
+      n = mozilla::ArrayLength(unavailableStr) - 1;
+    }
 
     fputs(":\n", file);
     if (prefix) {
       fputs(prefix, file);
     }
 
     for (size_t i = 0; i < n; i++) {
-      fputc(static_cast<char>(linebuf[i]), file);
+      fputc(utf8buf[i], file);
     }
 
-    // linebuf usually ends with a newline. If not, add one here.
-    if (n == 0 || linebuf[n - 1] != '\n') {
+    // linebuf/utf8buf usually ends with a newline. If not, add one here.
+    if (n == 0 || utf8buf[n - 1] != '\n') {
       fputc('\n', file);
     }
 
     if (prefix) {
       fputs(prefix, file);
     }
 
     n = report->tokenOffset();
     for (size_t i = 0, j = 0; i < n; i++) {
-      if (linebuf[i] == '\t') {
+      if (utf8buf[i] == '\t') {
         for (size_t k = (j + 8) & ~7; j < k; j++) {
           fputc('.', file);
         }
         continue;
       }
       fputc('.', file);
       j++;
     }
@@ -443,19 +471,19 @@ static void PrintSingleError(JSContext* 
   fputs(message, file);
 
   PrintErrorLine(file, prefix.get(), report);
   fputc('\n', file);
 
   fflush(file);
 }
 
-void js::PrintError(JSContext* cx, FILE* file,
-                    JS::ConstUTF8CharsZ toStringResult, JSErrorReport* report,
-                    bool reportWarnings) {
+static void PrintErrorImpl(JSContext* cx, FILE* file,
+                           JS::ConstUTF8CharsZ toStringResult,
+                           JSErrorReport* report, bool reportWarnings) {
   MOZ_ASSERT(report);
 
   /* Conditionally ignore reported warnings. */
   if (report->isWarning() && !reportWarnings) {
     return;
   }
 
   PrintErrorKind kind = PrintErrorKind::Error;
@@ -467,16 +495,28 @@ void js::PrintError(JSContext* cx, FILE*
   if (report->notes) {
     for (auto&& note : *report->notes) {
       PrintSingleError(cx, file, JS::ConstUTF8CharsZ(), note.get(),
                        PrintErrorKind::Note);
     }
   }
 }
 
+JS_PUBLIC_API void JS::PrintError(JSContext* cx, FILE* file,
+                                  JSErrorReport* report, bool reportWarnings) {
+  PrintErrorImpl(cx, file, JS::ConstUTF8CharsZ(), report, reportWarnings);
+}
+
+JS_PUBLIC_API void JS::PrintError(JSContext* cx, FILE* file,
+                                  const JS::ErrorReportBuilder& builder,
+                                  bool reportWarnings) {
+  PrintErrorImpl(cx, file, builder.toStringResult(), builder.report(),
+                 reportWarnings);
+}
+
 void js::ReportIsNotDefined(JSContext* cx, HandleId id) {
   if (UniqueChars printable =
           IdToPrintableUTF8(cx, id, IdToPrintableBehavior::IdIsIdentifier)) {
     JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_NOT_DEFINED,
                              printable.get());
   }
 }
 
--- a/js/src/vm/JSContext.h
+++ b/js/src/vm/JSContext.h
@@ -1031,23 +1031,16 @@ struct MOZ_RAII AutoResolving {
 extern JSContext* NewContext(uint32_t maxBytes, JSRuntime* parentRuntime);
 
 extern void DestroyContext(JSContext* cx);
 
 /* |callee| requires a usage string provided by JS_DefineFunctionsWithHelp. */
 extern void ReportUsageErrorASCII(JSContext* cx, HandleObject callee,
                                   const char* msg);
 
-// Writes a full report to a file descriptor.
-// Does nothing for JSErrorReport which are warnings, unless
-// reportWarnings is set.
-extern void PrintError(JSContext* cx, FILE* file,
-                       JS::ConstUTF8CharsZ toStringResult,
-                       JSErrorReport* report, bool reportWarnings);
-
 extern void ReportIsNotDefined(JSContext* cx, HandlePropertyName name);
 
 extern void ReportIsNotDefined(JSContext* cx, HandleId id);
 
 /*
  * Report an attempt to access the property of a null or undefined value (v).
  */
 extern void ReportIsNullOrUndefinedForPropertyAccess(JSContext* cx,
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -44,16 +44,17 @@
 #include "frontend/BytecodeCompiler.h"  // js::frontend::CreateScriptSourceObject
 #include "gc/Marking.h"
 #include "gc/Policy.h"
 #include "jit/AtomicOperations.h"
 #include "jit/InlinableNatives.h"
 #include "js/CharacterEncoding.h"
 #include "js/CompilationAndEvaluation.h"
 #include "js/Date.h"
+#include "js/ErrorReport.h"  // JS::PrintError
 #include "js/Exception.h"
 #include "js/Modules.h"  // JS::GetModulePrivate
 #include "js/PropertySpec.h"
 #include "js/SourceText.h"  // JS::SourceText
 #include "js/StableStringChars.h"
 #include "js/Warnings.h"  // JS::{,Set}WarningReporter
 #include "js/Wrapper.h"
 #include "util/StringBuffer.h"
@@ -103,17 +104,17 @@ using namespace js::selfhosted;
 using JS::AutoCheckCannotGC;
 using JS::AutoStableStringChars;
 using JS::CompileOptions;
 using mozilla::Maybe;
 
 static void selfHosting_WarningReporter(JSContext* cx, JSErrorReport* report) {
   MOZ_ASSERT(report->isWarning());
 
-  PrintError(cx, stderr, JS::ConstUTF8CharsZ(), report, true);
+  JS::PrintError(cx, stderr, report, true);
 }
 
 static bool intrinsic_ToObject(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
   JSObject* obj = ToObject(cx, args[0]);
   if (!obj) {
     return false;
   }
@@ -2589,24 +2590,24 @@ static void MaybePrintAndClearPendingExc
   AutoClearPendingException acpe(cx);
 
   JS::ExceptionStack exnStack(cx);
   if (!JS::StealPendingExceptionStack(cx, &exnStack)) {
     fprintf(file, "error getting pending exception\n");
     return;
   }
 
-  ErrorReport report(cx);
-  if (!report.init(cx, exnStack, js::ErrorReport::WithSideEffects)) {
-    fprintf(file, "out of memory initializing ErrorReport\n");
+  JS::ErrorReportBuilder report(cx);
+  if (!report.init(cx, exnStack, JS::ErrorReportBuilder::WithSideEffects)) {
+    fprintf(file, "out of memory initializing JS::ErrorReportBuilder\n");
     return;
   }
 
   MOZ_ASSERT(!report.report()->isWarning());
-  PrintError(cx, file, report.toStringResult(), report.report(), true);
+  JS::PrintError(cx, file, report, true);
 }
 
 class MOZ_STACK_CLASS AutoSelfHostingErrorReporter {
   JSContext* cx_;
   JS::WarningReporter oldReporter_;
 
  public:
   explicit AutoSelfHostingErrorReporter(JSContext* cx) : cx_(cx) {
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -3335,17 +3335,16 @@ void nsIFrame::BuildDisplayListForStacki
         allowAsyncAnimation = true;
         visibleRect = dirtyRect;
         break;
       case nsDisplayTransform::PrerenderDecision::No: {
         // If we didn't prerender an animated frame in a preserve-3d context,
         // then we want disable async animations for the rest of the preserve-3d
         // (especially ancestors).
         if ((extend3DContext || combines3DTransformWithAncestors) &&
-            decision.mDecision == nsDisplayTransform::PrerenderDecision::No &&
             decision.mHasAnimations) {
           aBuilder->SavePreserves3DAllowAsyncAnimation(false);
         }
 
         const nsRect overflow = GetVisualOverflowRectRelativeToSelf();
         if (overflow.IsEmpty() && !extend3DContext) {
           return;
         }
--- a/layout/painting/nsDisplayList.h
+++ b/layout/painting/nsDisplayList.h
@@ -7086,25 +7086,25 @@ class nsDisplayTransform : public nsDisp
    * and child 3D rendering context.
    * \see nsIFrame::BuildDisplayListForStackingContext().
    */
   bool IsTransformSeparator() const { return mIsTransformSeparator; }
   /**
    * This item is the boundary between parent and child 3D rendering
    * context.
    */
-  bool IsLeafOf3DContext() {
+  bool IsLeafOf3DContext() const {
     return (IsTransformSeparator() ||
             (!mFrame->Extend3DContext() && Combines3DTransformWithAncestors()));
   }
   /**
    * The backing frame of this item participates a 3D rendering
    * context.
    */
-  bool IsParticipating3DContext() {
+  bool IsParticipating3DContext() const {
     return mFrame->Extend3DContext() || Combines3DTransformWithAncestors();
   }
 
   void AddSizeOfExcludingThis(nsWindowSizes&) const override;
 
  private:
   void ComputeBounds(nsDisplayListBuilder* aBuilder);
   nsRect TransformUntransformedBounds(nsDisplayListBuilder* aBuilder,
--- a/netwerk/base/ProxyAutoConfig.cpp
+++ b/netwerk/base/ProxyAutoConfig.cpp
@@ -392,24 +392,23 @@ class MOZ_STACK_CLASS AutoPACErrorReport
   JSContext* mCx;
 
  public:
   explicit AutoPACErrorReporter(JSContext* aCx) : mCx(aCx) {}
   ~AutoPACErrorReporter() {
     if (!JS_IsExceptionPending(mCx)) {
       return;
     }
-    JS::RootedValue exn(mCx);
-    if (!JS_GetPendingException(mCx, &exn)) {
+    JS::ExceptionStack exnStack(mCx);
+    if (!JS::StealPendingExceptionStack(mCx, &exnStack)) {
       return;
     }
-    JS_ClearPendingException(mCx);
 
-    js::ErrorReport report(mCx);
-    if (!report.init(mCx, exn, js::ErrorReport::WithSideEffects)) {
+    JS::ErrorReportBuilder report(mCx);
+    if (!report.init(mCx, exnStack, JS::ErrorReportBuilder::WithSideEffects)) {
       JS_ClearPendingException(mCx);
       return;
     }
 
     PACLogErrorOrWarning(NS_LITERAL_STRING("Error"), report.report());
   }
 };
 
--- a/services/sync/golden_gate/src/task.rs
+++ b/services/sync/golden_gate/src/task.rs
@@ -1,143 +1,140 @@
 /* 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 std::{
     fmt::Write,
-    mem,
+    mem, result,
     sync::{Arc, Weak},
 };
 
 use atomic_refcell::AtomicRefCell;
 use moz_task::{DispatchOptions, Task, TaskRunnable, ThreadPtrHandle, ThreadPtrHolder};
 use nserror::nsresult;
 use nsstring::{nsACString, nsCString};
 use sync15_traits::{ApplyResults, BridgedEngine, Guid};
 use thin_vec::ThinVec;
 use xpcom::{
     interfaces::{
         mozIBridgedSyncEngineApplyCallback, mozIBridgedSyncEngineCallback, nsIEventTarget,
     },
     RefPtr,
 };
 
-use crate::error::{self, BridgedError, Error};
+use crate::error::{BridgedError, Error, Result};
 use crate::ferry::{Ferry, FerryResult};
 
 /// A ferry task sends (or ferries) an operation to a bridged engine on a
 /// background thread or task queue, and ferries back an optional result to
 /// a callback.
 pub struct FerryTask<N: ?Sized + BridgedEngine> {
     /// A ferry task holds a weak reference to the bridged engine, and upgrades
     /// it to a strong reference when run on a background thread. This avoids
     /// scheduled ferries blocking finalization: if the main thread holds the
     /// only strong reference to the engine, it can be unwrapped (using
     /// `Arc::try_unwrap`) and dropped, either on the main thread, or as part of
     /// a teardown task.
     engine: Weak<N>,
     ferry: Ferry,
     callback: ThreadPtrHandle<mozIBridgedSyncEngineCallback>,
-    result: AtomicRefCell<Result<FerryResult, N::Error>>,
+    result: AtomicRefCell<result::Result<FerryResult, N::Error>>,
 }
 
 impl<N> FerryTask<N>
 where
     N: ?Sized + BridgedEngine + Send + Sync + 'static,
     N::Error: BridgedError,
 {
     /// Creates a task to fetch the engine's last sync time, in milliseconds.
     #[inline]
     pub fn for_last_sync(
         engine: &Arc<N>,
         callback: &mozIBridgedSyncEngineCallback,
-    ) -> error::Result<FerryTask<N>> {
+    ) -> Result<FerryTask<N>> {
         Self::with_ferry(engine, Ferry::LastSync, callback)
     }
 
     /// Creates a task to set the engine's last sync time, in milliseconds.
     #[inline]
     pub fn for_set_last_sync(
         engine: &Arc<N>,
         last_sync_millis: i64,
         callback: &mozIBridgedSyncEngineCallback,
-    ) -> error::Result<FerryTask<N>> {
+    ) -> Result<FerryTask<N>> {
         Self::with_ferry(engine, Ferry::SetLastSync(last_sync_millis), callback)
     }
 
     /// Creates a task to fetch the engine's sync ID.
     #[inline]
     pub fn for_sync_id(
         engine: &Arc<N>,
         callback: &mozIBridgedSyncEngineCallback,
-    ) -> error::Result<FerryTask<N>> {
+    ) -> Result<FerryTask<N>> {
         Self::with_ferry(engine, Ferry::SyncId, callback)
     }
 
     /// Creates a task to reset the engine's sync ID and all its local Sync
     /// metadata.
     #[inline]
     pub fn for_reset_sync_id(
         engine: &Arc<N>,
         callback: &mozIBridgedSyncEngineCallback,
-    ) -> error::Result<FerryTask<N>> {
+    ) -> Result<FerryTask<N>> {
         Self::with_ferry(engine, Ferry::ResetSyncId, callback)
     }
 
     /// Creates a task to compare the bridged engine's local sync ID with
     /// the `new_sync_id` from `meta/global`, and ferry back the final sync ID
     /// to use.
     #[inline]
     pub fn for_ensure_current_sync_id(
         engine: &Arc<N>,
         new_sync_id: &nsACString,
         callback: &mozIBridgedSyncEngineCallback,
-    ) -> error::Result<FerryTask<N>> {
+    ) -> Result<FerryTask<N>> {
         Self::with_ferry(
             engine,
             Ferry::EnsureCurrentSyncId(std::str::from_utf8(new_sync_id)?.into()),
             callback,
         )
     }
 
     /// Creates a task to signal that the engine is about to sync.
     #[inline]
     pub fn for_sync_started(
         engine: &Arc<N>,
         callback: &mozIBridgedSyncEngineCallback,
-    ) -> error::Result<FerryTask<N>> {
+    ) -> Result<FerryTask<N>> {
         Self::with_ferry(engine, Ferry::SyncStarted, callback)
     }
 
     /// Creates a task to store incoming records.
     pub fn for_store_incoming(
         engine: &Arc<N>,
         incoming_envelopes_json: &[nsCString],
         callback: &mozIBridgedSyncEngineCallback,
-    ) -> error::Result<FerryTask<N>> {
-        let incoming_envelopes = incoming_envelopes_json.iter().try_fold(
-            Vec::with_capacity(incoming_envelopes_json.len()),
-            |mut envelopes, envelope| -> error::Result<_> {
-                envelopes.push(serde_json::from_slice(&*envelope)?);
-                Ok(envelopes)
-            },
-        )?;
+    ) -> Result<FerryTask<N>> {
+        let incoming_envelopes = incoming_envelopes_json
+            .iter()
+            .map(|envelope| Ok(serde_json::from_slice(&*envelope)?))
+            .collect::<Result<_>>()?;
         Self::with_ferry(engine, Ferry::StoreIncoming(incoming_envelopes), callback)
     }
 
     /// Creates a task to mark a subset of outgoing records as uploaded. This
     /// may be called multiple times per sync, or not at all if there are no
     /// records to upload.
     pub fn for_set_uploaded(
         engine: &Arc<N>,
         server_modified_millis: i64,
         uploaded_ids: &[nsCString],
         callback: &mozIBridgedSyncEngineCallback,
-    ) -> error::Result<FerryTask<N>> {
+    ) -> Result<FerryTask<N>> {
         let uploaded_ids = uploaded_ids
             .iter()
             .map(|id| Guid::from_slice(&*id))
             .collect();
         Self::with_ferry(
             engine,
             Ferry::SetUploaded(server_modified_millis, uploaded_ids),
             callback,
@@ -146,77 +143,77 @@ where
 
     /// Creates a task to signal that all records have been uploaded, and
     /// the engine has been synced. This is called even if there were no
     /// records uploaded.
     #[inline]
     pub fn for_sync_finished(
         engine: &Arc<N>,
         callback: &mozIBridgedSyncEngineCallback,
-    ) -> error::Result<FerryTask<N>> {
+    ) -> Result<FerryTask<N>> {
         Self::with_ferry(engine, Ferry::SyncFinished, callback)
     }
 
     /// Creates a task to reset all local Sync state for the engine, without
     /// erasing user data.
     #[inline]
     pub fn for_reset(
         engine: &Arc<N>,
         callback: &mozIBridgedSyncEngineCallback,
-    ) -> error::Result<FerryTask<N>> {
+    ) -> Result<FerryTask<N>> {
         Self::with_ferry(engine, Ferry::Reset, callback)
     }
 
     /// Creates a task to erase all local user data for the engine.
     #[inline]
     pub fn for_wipe(
         engine: &Arc<N>,
         callback: &mozIBridgedSyncEngineCallback,
-    ) -> error::Result<FerryTask<N>> {
+    ) -> Result<FerryTask<N>> {
         Self::with_ferry(engine, Ferry::Wipe, callback)
     }
 
     /// Creates a task for a ferry. The `callback` is bound to the current
     /// thread, and will be called once, after the ferry returns from the
     /// background thread.
     fn with_ferry(
         engine: &Arc<N>,
         ferry: Ferry,
         callback: &mozIBridgedSyncEngineCallback,
-    ) -> error::Result<FerryTask<N>> {
+    ) -> Result<FerryTask<N>> {
         let name = ferry.name();
         Ok(FerryTask {
             engine: Arc::downgrade(engine),
             ferry,
             callback: ThreadPtrHolder::new(
                 cstr!("mozIBridgedSyncEngineCallback"),
                 RefPtr::new(callback),
             )?,
             result: AtomicRefCell::new(Err(Error::DidNotRun(name).into())),
         })
     }
 
     /// Dispatches the task to the given thread `target`.
-    pub fn dispatch(self, target: &nsIEventTarget) -> Result<(), Error> {
+    pub fn dispatch(self, target: &nsIEventTarget) -> Result<()> {
         let runnable = TaskRunnable::new(self.ferry.name(), Box::new(self))?;
         // `may_block` schedules the task on the I/O thread pool, since we
         // expect most operations to wait on I/O.
         runnable.dispatch_with_options(target, DispatchOptions::default().may_block(true))?;
         Ok(())
     }
 }
 
 impl<N> FerryTask<N>
 where
     N: ?Sized + BridgedEngine,
     N::Error: BridgedError,
 {
     /// Runs the task on the background thread. This is split out into its own
     /// method to make error handling easier.
-    fn inner_run(&self) -> Result<FerryResult, N::Error> {
+    fn inner_run(&self) -> result::Result<FerryResult, N::Error> {
         let engine = match self.engine.upgrade() {
             Some(outer) => outer,
             None => return Err(Error::DidNotRun(self.ferry.name()).into()),
         };
         Ok(match &self.ferry {
             Ferry::LastSync => FerryResult::LastSync(engine.last_sync()?),
             Ferry::SetLastSync(last_sync_millis) => {
                 engine.set_last_sync(*last_sync_millis)?;
@@ -259,17 +256,17 @@ impl<N> Task for FerryTask<N>
 where
     N: ?Sized + BridgedEngine,
     N::Error: BridgedError,
 {
     fn run(&self) {
         *self.result.borrow_mut() = self.inner_run();
     }
 
-    fn done(&self) -> Result<(), nsresult> {
+    fn done(&self) -> result::Result<(), nsresult> {
         let callback = self.callback.get().unwrap();
         match mem::replace(
             &mut *self.result.borrow_mut(),
             Err(Error::DidNotRun(self.ferry.name()).into()),
         ) {
             Ok(result) => unsafe { callback.HandleSuccess(result.into_variant().coerce()) },
             Err(err) => {
                 let mut message = nsCString::new();
@@ -282,89 +279,86 @@ where
 }
 
 /// An apply task ferries incoming records to an engine on a background
 /// thread, and ferries back records to upload. It's separate from
 /// `FerryTask` because its callback type is different.
 pub struct ApplyTask<N: ?Sized + BridgedEngine> {
     engine: Weak<N>,
     callback: ThreadPtrHandle<mozIBridgedSyncEngineApplyCallback>,
-    result: AtomicRefCell<Result<Vec<String>, N::Error>>,
+    result: AtomicRefCell<result::Result<Vec<String>, N::Error>>,
 }
 
 impl<N> ApplyTask<N>
 where
     N: ?Sized + BridgedEngine,
     N::Error: BridgedError,
 {
     /// Returns the task name for debugging.
     pub fn name() -> &'static str {
         concat!(module_path!(), "apply")
     }
 
     /// Runs the task on the background thread.
-    fn inner_run(&self) -> Result<Vec<String>, N::Error> {
+    fn inner_run(&self) -> result::Result<Vec<String>, N::Error> {
         let engine = match self.engine.upgrade() {
             Some(outer) => outer,
             None => return Err(Error::DidNotRun(Self::name()).into()),
         };
         let ApplyResults {
             envelopes: outgoing_envelopes,
             ..
         } = engine.apply()?;
-        let outgoing_envelopes_json = outgoing_envelopes.iter().try_fold(
-            Vec::with_capacity(outgoing_envelopes.len()),
-            |mut envelopes, envelope| {
-                envelopes.push(serde_json::to_string(envelope)?);
-                Ok(envelopes)
-            },
-        )?;
+        let outgoing_envelopes_json = outgoing_envelopes
+            .iter()
+            .map(|envelope| Ok(serde_json::to_string(envelope)?))
+            .collect::<Result<_>>()?;
         Ok(outgoing_envelopes_json)
     }
 }
 
 impl<N> ApplyTask<N>
 where
     N: ?Sized + BridgedEngine + Send + Sync + 'static,
     N::Error: BridgedError,
 {
     /// Creates a task. The `callback` is bound to the current thread, and will
     /// be called once, after the records are applied on the background thread.
     pub fn new(
         engine: &Arc<N>,
         callback: &mozIBridgedSyncEngineApplyCallback,
-    ) -> error::Result<ApplyTask<N>> {
+    ) -> Result<ApplyTask<N>> {
         Ok(ApplyTask {
             engine: Arc::downgrade(engine),
             callback: ThreadPtrHolder::new(
                 cstr!("mozIBridgedSyncEngineApplyCallback"),
                 RefPtr::new(callback),
             )?,
             result: AtomicRefCell::new(Err(Error::DidNotRun(Self::name()).into())),
         })
     }
 
     /// Dispatches the task to the given thread `target`.
-    pub fn dispatch(self, target: &nsIEventTarget) -> Result<(), Error> {
+    pub fn dispatch(self, target: &nsIEventTarget) -> Result<()> {
         let runnable = TaskRunnable::new(Self::name(), Box::new(self))?;
         runnable.dispatch_with_options(target, DispatchOptions::default().may_block(true))?;
         Ok(())
     }
 }
 
 impl<N> Task for ApplyTask<N>
 where
     N: ?Sized + BridgedEngine,
     N::Error: BridgedError,
 {
     fn run(&self) {
         *self.result.borrow_mut() = self.inner_run();
     }
 
-    fn done(&self) -> Result<(), nsresult> {
+    fn done(&self) -> result::Result<(), nsresult> {
         let callback = self.callback.get().unwrap();
         match mem::replace(
             &mut *self.result.borrow_mut(),
             Err(Error::DidNotRun(Self::name()).into()),
         ) {
             Ok(envelopes) => {
                 let result = envelopes
                     .into_iter()
--- a/taskcluster/taskgraph/util/hash.py
+++ b/taskcluster/taskgraph/util/hash.py
@@ -35,13 +35,15 @@ def hash_paths(base_path, patterns):
     files = {}
     for pattern in patterns:
         found = list(finder.find(pattern))
         if found:
             files.update(found)
         else:
             raise Exception('%s did not match anything' % pattern)
     for path in sorted(files.keys()):
+        if path.endswith(('.pyc', '.pyd', '.pyo')):
+            continue
         h.update(six.ensure_binary('{} {}\n'.format(
             hash_path(mozpath.abspath(mozpath.join(base_path, path))),
             mozpath.normsep(path)
         )))
     return h.hexdigest()
--- a/toolkit/components/extensions/test/xpcshell/test_ext_background_telemetry.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_background_telemetry.js
@@ -1,16 +1,21 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 const HISTOGRAM = "WEBEXT_BACKGROUND_PAGE_LOAD_MS";
 const HISTOGRAM_KEYED = "WEBEXT_BACKGROUND_PAGE_LOAD_MS_BY_ADDONID";
 
 add_task(async function test_telemetry() {
+  Services.prefs.setBoolPref(
+    "toolkit.telemetry.testing.overrideProductsCheck",
+    true
+  );
+
   let extension1 = ExtensionTestUtils.loadExtension({
     background() {
       browser.test.sendMessage("loaded");
     },
   });
 
   let extension2 = ExtensionTestUtils.loadExtension({
     background() {
--- a/toolkit/components/extensions/test/xpcshell/test_ext_experiments.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_experiments.js
@@ -163,17 +163,17 @@ add_task(async function test_bundled_exp
     {
       isPrivileged: false,
       temporarilyInstalled: true,
       shouldHaveExperiments: AddonSettings.EXPERIMENTS_ENABLED,
     },
     {
       isPrivileged: false,
       temporarilyInstalled: false,
-      shouldHaveExperiments: false,
+      shouldHaveExperiments: AppConstants.MOZ_APP_NAME == "thunderbird",
     },
   ];
 
   async function background(shouldHaveExperiments) {
     if (shouldHaveExperiments) {
       await testFooExperiment();
     } else {
       await testFooFailExperiment();
--- a/toolkit/components/extensions/test/xpcshell/test_ext_extension_content_telemetry.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_extension_content_telemetry.js
@@ -10,16 +10,21 @@ server.registerDirectory("/data/", do_ge
 
 const BASE_URL = `http://localhost:${server.identity.primaryPort}/data`;
 
 add_task(async function test_telemetry() {
   function contentScript() {
     browser.test.sendMessage("content-script-run");
   }
 
+  Services.prefs.setBoolPref(
+    "toolkit.telemetry.testing.overrideProductsCheck",
+    true
+  );
+
   let extension1 = ExtensionTestUtils.loadExtension({
     manifest: {
       content_scripts: [
         {
           matches: ["http://*/*/file_sample.html"],
           js: ["content_script.js"],
           run_at: "document_end",
         },
--- a/toolkit/components/extensions/test/xpcshell/test_ext_extension_startup_telemetry.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_extension_startup_telemetry.js
@@ -13,16 +13,21 @@ function processKeyedSnapshot(snapshot) 
   let res = {};
   for (let key of Object.keys(snapshot)) {
     res[key] = snapshot[key].sum > 0;
   }
   return res;
 }
 
 add_task(async function test_telemetry() {
+  Services.prefs.setBoolPref(
+    "toolkit.telemetry.testing.overrideProductsCheck",
+    true
+  );
+
   let extension1 = ExtensionTestUtils.loadExtension({});
   let extension2 = ExtensionTestUtils.loadExtension({});
 
   clearHistograms();
 
   assertHistogramEmpty(HISTOGRAM);
   assertKeyedHistogramEmpty(HISTOGRAM_KEYED);
 
--- a/toolkit/components/extensions/test/xpcshell/test_ext_startup_perf.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_startup_perf.js
@@ -19,16 +19,20 @@ const STARTUP_MODULES = [
 
 if (!Services.prefs.getBoolPref("extensions.webextensions.remote")) {
   STARTUP_MODULES.push(
     "resource://gre/modules/ExtensionChild.jsm",
     "resource://gre/modules/ExtensionPageChild.jsm"
   );
 }
 
+if (AppConstants.MOZ_APP_NAME == "thunderbird") {
+  STARTUP_MODULES.push("resource://gre/modules/ExtensionContent.jsm");
+}
+
 AddonTestUtils.init(this);
 
 // Tests that only the minimal set of API scripts and modules are loaded at
 // startup for a simple extension.
 add_task(async function test_loaded_scripts() {
   await ExtensionTestUtils.startAddonManager();
 
   let extension = ExtensionTestUtils.loadExtension({
--- a/toolkit/components/extensions/test/xpcshell/test_ext_storage_telemetry.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_storage_telemetry.js
@@ -258,16 +258,21 @@ async function test_telemetry_background
       `No data recorded for histogram: ${id}.`
     );
   }
 
   await contentPage.close();
 }
 
 add_task(async function setup() {
+  Services.prefs.setBoolPref(
+    "toolkit.telemetry.testing.overrideProductsCheck",
+    true
+  );
+
   // Telemetry test setup needed to ensure that the builtin events are defined
   // and they can be collected and verified.
   await TelemetryController.testSetup();
 
   // This is actually only needed on Android, because it does not properly support unified telemetry
   // and so, if not enabled explicitly here, it would make these tests to fail when running on a
   // non-Nightly build.
   const oldCanRecordBase = Services.telemetry.canRecordBase;
--- a/toolkit/components/extensions/test/xpcshell/test_ext_userScripts_telemetry.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_userScripts_telemetry.js
@@ -34,16 +34,21 @@ add_task(async function test_userScripts
       scriptMetadata: {
         name: "test-user-script-telemetry",
       },
     });
 
     browser.test.sendMessage("userScript-registered");
   }
 
+  Services.prefs.setBoolPref(
+    "toolkit.telemetry.testing.overrideProductsCheck",
+    true
+  );
+
   let testExtensionDef = {
     manifest: {
       permissions: ["http://*/*/file_sample.html"],
       user_scripts: {
         api_script: "api-script.js",
       },
     },
     background,
--- a/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
@@ -20,24 +20,26 @@ skip-if = os == "android" # Android does
 [test_ext_background_telemetry.js]
 [test_ext_background_window_properties.js]
 skip-if = os == "android"
 [test_ext_browserSettings.js]
 [test_ext_browserSettings_homepage.js]
 skip-if = appname == "thunderbird" || os == "android"
 [test_ext_captivePortal.js]
 # As with test_captive_portal_service.js, we use the same limits here.
-skip-if = os == "android" || (os == "mac" && debug) # CP service is disabled on Android, macosx1014/debug due to 1564534
+skip-if = appname == "thunderbird" || os == "android" || (os == "mac" && debug) # CP service is disabled on Android, macosx1014/debug due to 1564534
 run-sequentially = node server exceptions dont replay well
 [test_ext_captivePortal_url.js]
 # As with test_captive_portal_service.js, we use the same limits here.
-skip-if = os == "android" || (os == "mac" && debug) # CP service is disabled on Android, macosx1014/debug due to 1564534
+skip-if = appname == "thunderbird" || os == "android" || (os == "mac" && debug) # CP service is disabled on Android, macosx1014/debug due to 1564534
 run-sequentially = node server exceptions dont replay well
 [test_ext_cookieBehaviors.js]
+skip-if = appname == "thunderbird"
 [test_ext_cookies_firstParty.js]
+skip-if = appname == "thunderbird"
 [test_ext_cookies_samesite.js]
 [test_ext_content_security_policy.js]
 skip-if = (os == "win" && debug) #Bug 1485567
 [test_ext_contentscript_api_injection.js]
 [test_ext_contentscript_async_loading.js]
 skip-if = os == 'android' && debug # The generated script takes too long to load on Android debug
 [test_ext_contentscript_context.js]
 [test_ext_contentscript_context_isolation.js]
@@ -64,38 +66,39 @@ skip-if = appname == "thunderbird" || os
 skip-if = appname == "thunderbird" || os == "android"
 [test_ext_downloads_search.js]
 skip-if = appname == "thunderbird" || os == "android" || tsan # tsan: bug 1612707
 [test_ext_downloads_urlencoded.js]
 skip-if = appname == "thunderbird" || os == "android"
 [test_ext_error_location.js]
 [test_ext_eventpage_warning.js]
 [test_ext_experiments.js]
-fail-if = appname == "thunderbird"
 [test_ext_extension.js]
 [test_ext_extensionPreferencesManager.js]
 [test_ext_extensionSettingsStore.js]
 [test_ext_extension_content_telemetry.js]
 skip-if = os == "android" # checking for telemetry needs to be updated: 1384923
 [test_ext_extension_startup_telemetry.js]
 [test_ext_file_access.js]
 [test_ext_geckoProfiler_control.js]
 skip-if = os == "android" || tsan # Not shipped on Android. tsan: bug 1612707
 [test_ext_geturl.js]
 [test_ext_idle.js]
 [test_ext_incognito.js]
+skip-if = appname == "thunderbird"
 [test_ext_l10n.js]
 [test_ext_localStorage.js]
 [test_ext_management.js]
 skip-if = (os == "win" && !debug) #Bug 1419183 disable on Windows
 [test_ext_management_uninstall_self.js]
 [test_ext_messaging_startup.js]
 skip-if = appname == "thunderbird" || (os == "android" && debug)
 [test_ext_networkStatus.js]
 [test_ext_notifications_incognito.js]
+skip-if = appname == "thunderbird"
 [test_ext_notifications_unsupported.js]
 [test_ext_onmessage_removelistener.js]
 skip-if = true # This test no longer tests what it is meant to test.
 [test_ext_permission_xhr.js]
 [test_ext_persistent_events.js]
 [test_ext_privacy.js]
 skip-if = appname == "thunderbird" || (os == "android" && debug)
 [test_ext_privacy_disable.js]
@@ -135,26 +138,27 @@ skip-if = ccov && os == 'linux' # bug 16
 [test_ext_startupData.js]
 [test_ext_startup_cache.js]
 skip-if = os == "android"
 [test_ext_startup_perf.js]
 [test_ext_startup_request_handler.js]
 [test_ext_storage.js]
 skip-if = os == "android" && debug
 [test_ext_storage_idb_data_migration.js]
-skip-if = os == "android" && debug
+skip-if = appname == "thunderbird" || (os == "android" && debug)
 [test_ext_storage_content.js]
 skip-if = os == "android" && debug
 [test_ext_storage_quota_exceeded_errors.js]
 skip-if = os == "android" # Bug 1564871
 [test_ext_storage_managed.js]
 skip-if = os == "android"
 [test_ext_storage_managed_policy.js]
-skip-if = os == "android"
+skip-if = appname == "thunderbird" || os == "android"
 [test_ext_storage_sanitizer.js]
+skip-if = appname == "thunderbird"
 [test_ext_storage_sync.js]
 head = head.js head_sync.js
 skip-if = appname == "thunderbird" || os == "android"
 [test_ext_storage_sync_crypto.js]
 skip-if = appname == "thunderbird" || os == "android"
 [test_ext_storage_tab.js]
 [test_ext_storage_telemetry.js]
 skip-if = os == "android" # checking for telemetry needs to be updated: 1384923
@@ -202,16 +206,17 @@ skip-if = os == "android"
 skip-if = appname == "thunderbird" || os == "android" # Bug 1350559
 [test_ext_permissions_api.js]
 skip-if = appname == "thunderbird" || os == "android" # Bug 1350559
 [test_ext_permissions_migrate.js]
 skip-if = appname == "thunderbird" || os == "android" # Bug 1350559
 [test_ext_permissions_uninstall.js]
 skip-if = appname == "thunderbird" || os == "android" # Bug 1350559
 [test_proxy_listener.js]
+skip-if = appname == "thunderbird"
 [test_proxy_incognito.js]
 skip-if = os == "android" # incognito not supported on android
 [test_proxy_info_results.js]
 [test_proxy_userContextId.js]
 [test_webRequest_ancestors.js]
 [test_webRequest_cookies.js]
 [test_webRequest_filtering.js]
 [test_ext_brokenlinks.js]
--- a/toolkit/components/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell.ini
@@ -78,11 +78,11 @@ skip-if = os == 'android' && processor =
 skip-if = os == 'android' && processor == 'x86_64'
 [test_ext_ipcBlob.js]
 skip-if = os == 'android' && processor == 'x86_64'
 
 [test_ext_runtime_sendMessage_args.js]
 skip-if = os == 'android' && processor == 'x86_64'
 
 [include:xpcshell-common.ini]
-skip-if = os != 'android' || (os == 'android' && processor == 'x86_64') # only android is left without e10s
+run-if = appname == "thunderbird" || (os == 'android' && processor != 'x86_64') # Thunderbird and Android have no e10s
 [include:xpcshell-content.ini]
-skip-if = os != 'android' || (os == 'android' && processor == 'x86_64') # only android is left without e10s
+run-if = appname == "thunderbird" || (os == 'android' && processor != 'x86_64') # Thunderbird and Android have no e10s