Merge autoland to mozilla-central. a=merge
authorshindli <shindli@mozilla.com>
Sat, 15 Feb 2020 11:54:55 +0200
changeset 514170 02b1aa498dd2269ea45fb47e7e50f3d45d26bd3c
parent 513901 f8ec5d8b4718a6456446b56c40ea32c50c631d48 (current diff)
parent 514169 3ff6c746997a57a551c5e40d5d28ca069328e716 (diff)
child 514177 b9257fb3e5ac5d24e4ef92f5c4747ca0395183df
push id37125
push usershindli@mozilla.com
push dateSat, 15 Feb 2020 09:56:17 +0000
treeherdermozilla-central@02b1aa498dd2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone75.0a1
first release with
nightly linux32
02b1aa498dd2 / 75.0a1 / 20200215095617 / files
nightly linux64
02b1aa498dd2 / 75.0a1 / 20200215095617 / files
nightly mac
02b1aa498dd2 / 75.0a1 / 20200215095617 / files
nightly win32
02b1aa498dd2 / 75.0a1 / 20200215095617 / files
nightly win64
02b1aa498dd2 / 75.0a1 / 20200215095617 / 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
testing/firefox-ui/tests/functional/keyboard_shortcuts/manifest.ini
testing/firefox-ui/tests/functional/keyboard_shortcuts/test_browser_window.py
testing/firefox-ui/tests/puppeteer/manifest.ini
testing/firefox-ui/tests/puppeteer/test_about_window.py
testing/firefox-ui/tests/puppeteer/test_appinfo.py
testing/firefox-ui/tests/puppeteer/test_menubar.py
testing/firefox-ui/tests/puppeteer/test_notifications.py
testing/firefox-ui/tests/puppeteer/test_page_info_window.py
testing/firefox-ui/tests/puppeteer/test_places.py
testing/firefox-ui/tests/puppeteer/test_security.py
testing/firefox-ui/tests/puppeteer/test_tabbar.py
testing/firefox-ui/tests/puppeteer/test_toolbars.py
testing/firefox-ui/tests/puppeteer/test_utils.py
testing/firefox-ui/tests/puppeteer/test_windows.py
testing/marionette/puppeteer/firefox/MANIFEST.in
testing/marionette/puppeteer/firefox/docs/Makefile
testing/marionette/puppeteer/firefox/docs/api/appinfo.rst
testing/marionette/puppeteer/firefox/docs/api/keys.rst
testing/marionette/puppeteer/firefox/docs/api/l10n.rst
testing/marionette/puppeteer/firefox/docs/api/places.rst
testing/marionette/puppeteer/firefox/docs/api/security.rst
testing/marionette/puppeteer/firefox/docs/api/utils.rst
testing/marionette/puppeteer/firefox/docs/conf.py
testing/marionette/puppeteer/firefox/docs/index.rst
testing/marionette/puppeteer/firefox/docs/make.bat
testing/marionette/puppeteer/firefox/docs/ui/about_window/window.rst
testing/marionette/puppeteer/firefox/docs/ui/browser/notifications.rst
testing/marionette/puppeteer/firefox/docs/ui/browser/tabbar.rst
testing/marionette/puppeteer/firefox/docs/ui/browser/toolbars.rst
testing/marionette/puppeteer/firefox/docs/ui/browser/window.rst
testing/marionette/puppeteer/firefox/docs/ui/deck.rst
testing/marionette/puppeteer/firefox/docs/ui/menu.rst
testing/marionette/puppeteer/firefox/docs/ui/pageinfo/window.rst
testing/marionette/puppeteer/firefox/docs/ui/windows.rst
testing/marionette/puppeteer/firefox/firefox_puppeteer/__init__.py
testing/marionette/puppeteer/firefox/firefox_puppeteer/api/__init__.py
testing/marionette/puppeteer/firefox/firefox_puppeteer/api/appinfo.py
testing/marionette/puppeteer/firefox/firefox_puppeteer/api/keys.py
testing/marionette/puppeteer/firefox/firefox_puppeteer/api/places.py
testing/marionette/puppeteer/firefox/firefox_puppeteer/api/security.py
testing/marionette/puppeteer/firefox/firefox_puppeteer/api/utils.py
testing/marionette/puppeteer/firefox/firefox_puppeteer/base.py
testing/marionette/puppeteer/firefox/firefox_puppeteer/decorators.py
testing/marionette/puppeteer/firefox/firefox_puppeteer/errors.py
testing/marionette/puppeteer/firefox/firefox_puppeteer/mixins.py
testing/marionette/puppeteer/firefox/firefox_puppeteer/puppeteer.py
testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/__init__.py
testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/about_window/__init__.py
testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/about_window/deck.py
testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/about_window/window.py
testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/base.py
testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/browser/__init__.py
testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/browser/notifications.py
testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/browser/tabbar.py
testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/browser/toolbars.py
testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/browser/window.py
testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/deck.py
testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/menu.py
testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/pageinfo/__init__.py
testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/pageinfo/deck.py
testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/pageinfo/window.py
testing/marionette/puppeteer/firefox/firefox_puppeteer/ui/windows.py
testing/marionette/puppeteer/firefox/requirements-docs.txt
testing/marionette/puppeteer/firefox/requirements.txt
testing/marionette/puppeteer/firefox/setup.py
testing/web-platform/meta/content-security-policy/generic/eval-typecheck-callout-order.tentative.html.ini
testing/web-platform/meta/css/css-fonts/font-variant-position-01.html.ini
testing/web-platform/meta/css/css-text/line-breaking/line-breaking-atomic-003.html.ini
testing/web-platform/meta/css/css-text/line-breaking/line-breaking-atomic-004.html.ini
testing/web-platform/meta/css/css-text/line-breaking/line-breaking-atomic-006.html.ini
testing/web-platform/meta/css/css-text/line-breaking/line-breaking-atomic-008.html.ini
testing/web-platform/meta/css/css-text/line-breaking/line-breaking-replaced-002.html.ini
testing/web-platform/meta/css/css-text/line-breaking/line-breaking-replaced-003.html.ini
testing/web-platform/meta/css/css-text/line-breaking/line-breaking-replaced-005.html.ini
testing/web-platform/meta/css/css-text/white-space/trailing-other-space-separators-break-spaces-001.html.ini
testing/web-platform/meta/css/css-text/white-space/trailing-other-space-separators-break-spaces-002.html.ini
testing/web-platform/meta/css/css-text/white-space/trailing-other-space-separators-break-spaces-003.html.ini
testing/web-platform/meta/css/css-text/white-space/trailing-other-space-separators-break-spaces-004.html.ini
testing/web-platform/meta/css/css-text/white-space/trailing-other-space-separators-break-spaces-005.html.ini
testing/web-platform/meta/css/css-text/white-space/trailing-other-space-separators-break-spaces-006.html.ini
testing/web-platform/meta/css/css-text/white-space/trailing-other-space-separators-break-spaces-007.html.ini
testing/web-platform/meta/css/css-text/white-space/trailing-other-space-separators-break-spaces-008.html.ini
testing/web-platform/meta/css/css-text/white-space/trailing-other-space-separators-break-spaces-010.html.ini
testing/web-platform/meta/css/css-text/white-space/trailing-other-space-separators-break-spaces-011.html.ini
testing/web-platform/meta/css/css-text/white-space/trailing-other-space-separators-break-spaces-012.html.ini
testing/web-platform/meta/css/css-text/white-space/trailing-other-space-separators-break-spaces-014.html.ini
testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/masking/mask-position-3a.html.ini
testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/masking/mask-position-4c.html.ini
testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/masking/mask-position-6.html.ini
testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/masking/mask-position-7.html.ini
testing/web-platform/meta/forced-colors-mode/backplate/forced-colors-mode-backplate-01.html.ini
testing/web-platform/meta/forced-colors-mode/backplate/forced-colors-mode-backplate-02.html.ini
testing/web-platform/meta/forced-colors-mode/backplate/forced-colors-mode-backplate-03.html.ini
testing/web-platform/meta/forced-colors-mode/backplate/forced-colors-mode-backplate-04.html.ini
testing/web-platform/meta/forced-colors-mode/backplate/forced-colors-mode-backplate-05.html.ini
testing/web-platform/meta/forced-colors-mode/backplate/forced-colors-mode-backplate-06.html.ini
testing/web-platform/meta/forced-colors-mode/backplate/forced-colors-mode-backplate-08.html.ini
testing/web-platform/meta/forced-colors-mode/backplate/forced-colors-mode-backplate-09.html.ini
testing/web-platform/meta/forced-colors-mode/backplate/forced-colors-mode-backplate-10.html.ini
testing/web-platform/meta/forced-colors-mode/forced-colors-mode-18.html.ini
testing/web-platform/meta/forced-colors-mode/forced-colors-mode-26.html.ini
testing/web-platform/meta/html/semantics/selectors/pseudo-classes/dir.html.ini
testing/web-platform/meta/native-file-system/sandboxed_FileSystemBaseHandle-postMessage-MessagePort.tentative.https.window.js.ini
testing/web-platform/meta/native-file-system/sandboxed_FileSystemBaseHandle-postMessage.tentative.https.window.js.ini
testing/web-platform/meta/scroll-to-text-fragment/scroll-to-text-fragment-security.html.ini
testing/web-platform/meta/service-workers/service-worker/getregistrations.https.html.ini
testing/web-platform/tests/css/css-text/line-breaking/reference/line-breaking-atomic-003-ref.html
testing/web-platform/tests/css/css-text/line-breaking/reference/line-breaking-replaced-005-ref.html
testing/web-platform/tests/css/css-text/white-space/reference/trailing-other-space-separators-break-spaces-003-ref.html
testing/web-platform/tests/css/css-text/white-space/reference/trailing-other-space-separators-break-spaces-004-ref.html
testing/web-platform/tests/css/css-text/white-space/reference/trailing-other-space-separators-break-spaces-005-ref.html
testing/web-platform/tests/css/css-text/white-space/reference/trailing-other-space-separators-break-spaces-006-ref.html
testing/web-platform/tests/css/css-text/white-space/reference/trailing-other-space-separators-break-spaces-007-ref.html
testing/web-platform/tests/css/css-text/white-space/reference/trailing-other-space-separators-break-spaces-008-ref.html
testing/web-platform/tests/css/css-text/white-space/reference/trailing-other-space-separators-break-spaces-010-ref.html
testing/web-platform/tests/css/css-text/white-space/reference/trailing-other-space-separators-break-spaces-011-ref.html
testing/web-platform/tests/css/css-text/white-space/reference/trailing-other-space-separators-break-spaces-012-ref.html
testing/web-platform/tests/css/css-text/white-space/reference/trailing-other-space-separators-break-spaces-014-ref.html
testing/web-platform/tests/css/css-text/white-space/reference/trailing-other-space-separators-break-spaces-015-ref.html
testing/web-platform/tests/forced-colors-mode/forced-colors-mode-18-ref.html
testing/web-platform/tests/forced-colors-mode/forced-colors-mode-18.html
testing/web-platform/tests/forced-colors-mode/forced-colors-mode-26-ref.html
testing/web-platform/tests/forced-colors-mode/forced-colors-mode-26.html
testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/structured_clone_blob.window.js
testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/structured_clone_blob_array.window.js
testing/web-platform/tests/interfaces/webrtc-dscp.idl
testing/web-platform/tests/native-file-system/native_FileSystemBaseHandle-postMessage-MessagePort-manual.https.tentative.html
testing/web-platform/tests/native-file-system/native_FileSystemBaseHandle-postMessage-manual.https.tentative.html
testing/web-platform/tests/native-file-system/sandboxed_FileSystemBaseHandle-postMessage-MessagePort.tentative.https.window.js
testing/web-platform/tests/native-file-system/sandboxed_FileSystemBaseHandle-postMessage.tentative.https.window.js
testing/web-platform/tests/native-file-system/script-tests/FileSystemBaseHandle-postMessage-MessagePort.js
testing/web-platform/tests/native-file-system/script-tests/FileSystemBaseHandle-postMessage.js
testing/web-platform/tests/scroll-to-text-fragment/scroll-to-text-fragment-security.html
testing/web-platform/tests/tools/.gitmodules
testing/web-platform/tests/workers/modules/resources/credentials.py
testing/web-platform/tests/workers/modules/resources/referrer-checker.py
toolkit/components/places/PageIconProtocolHandler.jsm
--- a/accessible/html/HTMLListAccessible.cpp
+++ b/accessible/html/HTMLListAccessible.cpp
@@ -33,22 +33,25 @@ uint64_t HTMLListAccessible::NativeState
 ////////////////////////////////////////////////////////////////////////////////
 // HTMLLIAccessible
 ////////////////////////////////////////////////////////////////////////////////
 
 HTMLLIAccessible::HTMLLIAccessible(nsIContent* aContent, DocAccessible* aDoc)
     : HyperTextAccessibleWrap(aContent, aDoc), mBullet(nullptr) {
   mType = eHTMLLiType;
 
-  const nsStyleList* styleList = GetFrame()->StyleList();
-  if (nsLayoutUtils::GetMarkerFrame(aContent) &&
-      (styleList->GetListStyleImage() || !styleList->mCounterStyle.IsNone())) {
-    mBullet = new HTMLListBulletAccessible(mContent, mDoc);
-    Document()->BindToDocument(mBullet, nullptr);
-    AppendChild(mBullet);
+  if (nsIFrame* bulletFrame = nsLayoutUtils::GetMarkerFrame(aContent)) {
+    if (const nsStyleList* styleList = bulletFrame->StyleList()) {
+      if (styleList->GetListStyleImage() ||
+          !styleList->mCounterStyle.IsNone()) {
+        mBullet = new HTMLListBulletAccessible(mContent, mDoc);
+        Document()->BindToDocument(mBullet, nullptr);
+        AppendChild(mBullet);
+      }
+    }
   }
 }
 
 void HTMLLIAccessible::Shutdown() {
   mBullet = nullptr;
 
   HyperTextAccessibleWrap::Shutdown();
 }
--- a/accessible/ipc/ProxyAccessibleShared.h
+++ b/accessible/ipc/ProxyAccessibleShared.h
@@ -19,18 +19,19 @@ uint64_t State() const;
 
 /*
  * Return the native states for the proxied accessible.
  */
 uint64_t NativeState() const;
 
 /*
  * Set aName to the name of the proxied accessible.
+ * Return the ENameValueFlag passed from Accessible::Name
  */
-void Name(nsString& aName) const;
+uint32_t Name(nsString& aName) const;
 
 /*
  * Set aValue to the value of the proxied accessible.
  */
 void Value(nsString& aValue) const;
 
 /*
  * Set aHelp to the help string of the proxied accessible.
--- a/accessible/ipc/other/DocAccessibleChild.cpp
+++ b/accessible/ipc/other/DocAccessibleChild.cpp
@@ -97,21 +97,22 @@ mozilla::ipc::IPCResult DocAccessibleChi
   }
 
   *aState = acc->NativeState();
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult DocAccessibleChild::RecvName(const uint64_t& aID,
-                                                     nsString* aName) {
+                                                     nsString* aName,
+                                                     uint32_t* aFlag) {
   Accessible* acc = IdToAccessible(aID);
   if (!acc) return IPC_OK();
 
-  acc->Name(*aName);
+  *aFlag = acc->Name(*aName);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult DocAccessibleChild::RecvValue(const uint64_t& aID,
                                                       nsString* aValue) {
   Accessible* acc = IdToAccessible(aID);
   if (!acc) {
     return IPC_OK();
--- a/accessible/ipc/other/DocAccessibleChild.h
+++ b/accessible/ipc/other/DocAccessibleChild.h
@@ -50,18 +50,18 @@ class DocAccessibleChild : public DocAcc
    * Return the native state for the accessible with given ID.
    */
   virtual mozilla::ipc::IPCResult RecvNativeState(const uint64_t& aID,
                                                   uint64_t* aState) override;
 
   /*
    * Get the name for the accessible with given id.
    */
-  virtual mozilla::ipc::IPCResult RecvName(const uint64_t& aID,
-                                           nsString* aName) override;
+  virtual mozilla::ipc::IPCResult RecvName(const uint64_t& aID, nsString* aName,
+                                           uint32_t* aFlag) override;
 
   virtual mozilla::ipc::IPCResult RecvValue(const uint64_t& aID,
                                             nsString* aValue) override;
 
   virtual mozilla::ipc::IPCResult RecvHelp(const uint64_t& aID,
                                            nsString* aHelp) override;
 
   /*
--- a/accessible/ipc/other/PDocAccessible.ipdl
+++ b/accessible/ipc/other/PDocAccessible.ipdl
@@ -121,17 +121,17 @@ child:
    * Called as a result of focus shifting from chrome to content
    * elements through keyboard navigation.
    */
   async RestoreFocus();
 
   // Accessible
   nested(inside_sync) sync State(uint64_t aID) returns(uint64_t states);
   nested(inside_sync) sync NativeState(uint64_t aID) returns(uint64_t states);
-  nested(inside_sync) sync Name(uint64_t aID) returns(nsString name);
+  nested(inside_sync) sync Name(uint64_t aID) returns(nsString name, uint32_t flag);
   nested(inside_sync) sync Value(uint64_t aID) returns(nsString value);
   nested(inside_sync) sync Help(uint64_t aID) returns(nsString help);
   nested(inside_sync) sync Description(uint64_t aID) returns(nsString desc);
   nested(inside_sync) sync Attributes(uint64_t aID) returns(Attribute[] attributes);
   nested(inside_sync) sync RelationByType(uint64_t aID, uint32_t aRelationType)
     returns(uint64_t[] targets);
   nested(inside_sync) sync Relations(uint64_t aID) returns(RelationTargets[] relations);
   nested(inside_sync) sync IsSearchbox(uint64_t aID) returns(bool retval);
--- a/accessible/ipc/other/ProxyAccessible.cpp
+++ b/accessible/ipc/other/ProxyAccessible.cpp
@@ -25,18 +25,20 @@ uint64_t ProxyAccessible::State() const 
 }
 
 uint64_t ProxyAccessible::NativeState() const {
   uint64_t state = 0;
   Unused << mDoc->SendNativeState(mID, &state);
   return state;
 }
 
-void ProxyAccessible::Name(nsString& aName) const {
-  Unused << mDoc->SendName(mID, &aName);
+uint32_t ProxyAccessible::Name(nsString& aName) const {
+  uint32_t flag;
+  Unused << mDoc->SendName(mID, &aName, &flag);
+  return flag;
 }
 
 void ProxyAccessible::Value(nsString& aValue) const {
   Unused << mDoc->SendValue(mID, &aValue);
 }
 
 void ProxyAccessible::Help(nsString& aHelp) const {
   Unused << mDoc->SendHelp(mID, &aHelp);
--- a/accessible/ipc/win/ProxyAccessible.cpp
+++ b/accessible/ipc/win/ProxyAccessible.cpp
@@ -113,30 +113,36 @@ static ProxyAccessible* GetProxyFor(DocA
   uint64_t id;
   if (FAILED(custom->get_ID(&id))) {
     return nullptr;
   }
 
   return aDoc->GetAccessible(id);
 }
 
-void ProxyAccessible::Name(nsString& aName) const {
+uint32_t ProxyAccessible::Name(nsString& aName) const {
+  /* The return values here exist only to match behvaiour required
+   * by the header declaration of this function. On Mac, we'd like
+   * to return the associated ENameValueFlag, but we don't have
+   * access to that here, so we return a dummy eNameOK value instead.
+   */
   aName.Truncate();
   RefPtr<IAccessible> acc;
   if (!GetCOMInterface((void**)getter_AddRefs(acc))) {
-    return;
+    return eNameOK;
   }
 
   BSTR result;
   HRESULT hr = acc->get_accName(kChildIdSelf, &result);
   _bstr_t resultWrap(result, false);
   if (FAILED(hr)) {
-    return;
+    return eNameOK;
   }
   aName = (wchar_t*)resultWrap;
+  return eNameOK;
 }
 
 void ProxyAccessible::Value(nsString& aValue) const {
   aValue.Truncate();
   RefPtr<IAccessible> acc;
   if (!GetCOMInterface((void**)getter_AddRefs(acc))) {
     return;
   }
--- a/accessible/mac/mozAccessible.mm
+++ b/accessible/mac/mozAccessible.mm
@@ -269,24 +269,35 @@ static inline NSMutableArray* ConvertToN
     return [self roleDescription];
   if ([attribute isEqualToString:NSAccessibilityFocusedAttribute])
     return [NSNumber numberWithBool:[self isFocused]];
   if ([attribute isEqualToString:NSAccessibilitySizeAttribute]) return [self size];
   if ([attribute isEqualToString:NSAccessibilityWindowAttribute]) return [self window];
   if ([attribute isEqualToString:NSAccessibilityTopLevelUIElementAttribute]) return [self window];
   if ([attribute isEqualToString:NSAccessibilityTitleAttribute]) return [self title];
   if ([attribute isEqualToString:NSAccessibilityTitleUIElementAttribute]) {
+    /* If our accessible is labelled by more than one item, its label
+     * should be set by accessibilityLabel instead of here, so we return nil.
+     */
     if (accWrap) {
       Relation rel = accWrap->RelationByType(RelationType::LABELLED_BY);
       Accessible* tempAcc = rel.Next();
-      return tempAcc ? GetNativeFromGeckoAccessible(tempAcc) : nil;
+      if (tempAcc && !rel.Next()) {
+        return GetNativeFromGeckoAccessible(tempAcc);
+      } else {
+        return nil;
+      }
     }
     nsTArray<ProxyAccessible*> rel = proxy->RelationByType(RelationType::LABELLED_BY);
     ProxyAccessible* tempProxy = rel.SafeElementAt(0);
-    return tempProxy ? GetNativeFromProxy(tempProxy) : nil;
+    if (tempProxy && rel.Length() <= 1) {
+      return GetNativeFromProxy(tempProxy);
+    } else {
+      return nil;
+    }
   }
   if ([attribute isEqualToString:NSAccessibilityHelpAttribute]) return [self help];
   if ([attribute isEqualToString:NSAccessibilityOrientationAttribute]) return [self orientation];
 
   if ([attribute isEqualToString:NSAccessibilityDOMIdentifierAttribute]) {
     nsAutoString id;
     if (accWrap)
       nsCoreUtils::GetID(accWrap->GetContent(), id);
@@ -469,16 +480,53 @@ static inline NSMutableArray* ConvertToN
 }
 
 - (NSString*)accessibilityActionDescription:(NSString*)action {
   // by default we return whatever the MacOS API know about.
   // if you have custom actions, override.
   return NSAccessibilityActionDescription(action);
 }
 
+- (NSString*)accessibilityLabel {
+  AccessibleWrap* accWrap = [self getGeckoAccessible];
+  ProxyAccessible* proxy = [self getProxyAccessible];
+  if (!accWrap && !proxy) {
+    return nil;
+  }
+
+  /* If our accessible is labelled by exactly one item, or if its
+   * name is obtained from a subtree, we should let
+   * NSAccessibilityTitleUIElementAttribute determine its label. */
+  if (accWrap) {
+    nsAutoString name;
+    ENameValueFlag flag = accWrap->Name(name);
+    if (flag == eNameFromSubtree) {
+      return nil;
+    }
+
+    Relation rel = accWrap->RelationByType(RelationType::LABELLED_BY);
+    if (rel.Next() && !rel.Next()) {
+      return nil;
+    }
+  } else if (proxy) {
+    nsAutoString name;
+    uint32_t flag = proxy->Name(name);
+    if (flag == eNameFromSubtree) {
+      return nil;
+    }
+
+    nsTArray<ProxyAccessible*> rels = proxy->RelationByType(RelationType::LABELLED_BY);
+    if (rels.Length() == 1) {
+      return nil;
+    }
+  }
+
+  return [self title];
+}
+
 - (void)accessibilityPerformAction:(NSString*)action {
 }
 
 - (id)accessibilityFocusedUIElement {
   AccessibleWrap* accWrap = [self getGeckoAccessible];
   ProxyAccessible* proxy = [self getProxyAccessible];
   if (!accWrap && !proxy) return nil;
 
--- a/accessible/tests/mochitest/events/test_focus_listcontrols.xhtml
+++ b/accessible/tests/mochitest/events/test_focus_listcontrols.xhtml
@@ -33,34 +33,29 @@
       gQueue.push(new synthFocus("multiselrichlistbox", new focusChecker("msrlb_item1")));
       gQueue.push(new synthDownKey("msrlb_item1", new focusChecker("msrlb_item2"), { shiftKey: true }));
       gQueue.push(new synthFocus("emptyrichlistbox", new focusChecker("emptyrichlistbox")));
 
       gQueue.push(new synthFocus("menulist"));
       gQueue.push(new synthClick("menulist", new focusChecker("ml_tangerine")));
       gQueue.push(new synthDownKey("ml_tangerine", new focusChecker("ml_marmalade")));
       gQueue.push(new synthEscapeKey("ml_marmalade", new focusChecker("menulist")));
-if (!MAC) {
+
       // On Windows, items get selected during navigation.
       let expectedItem = WIN ? "ml_strawberry" : "ml_marmalade";
       gQueue.push(new synthDownKey("menulist", new nofocusChecker(expectedItem)));
       gQueue.push(new synthOpenComboboxKey("menulist", new focusChecker(expectedItem)));
       gQueue.push(new synthEnterKey(expectedItem, new focusChecker("menulist")));
-} else {
-      todo(false, "Bug 746531 - timeouts of last three menulist tests on OS X");
-}
 
       // no focus events for unfocused list controls when current item is
       // changed.
       gQueue.push(new synthFocus("emptyrichlistbox"));
 
       gQueue.push(new changeCurrentItem("richlistbox", "rlb_item1"));
-if (!MAC) {
       gQueue.push(new changeCurrentItem("menulist", WIN ? "ml_marmalade" : "ml_tangerine"));
-}
 
       gQueue.invoke(); // Will call SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTests);
   </script>
 
--- a/browser/actors/ContextMenuChild.jsm
+++ b/browser/actors/ContextMenuChild.jsm
@@ -965,16 +965,23 @@ class ContextMenuChild extends JSWindowA
       context.onImage = true;
 
       context.imageInfo = {
         currentSrc: context.target.currentSrc,
         width: context.target.width,
         height: context.target.height,
         imageText: context.target.title || context.target.alt,
       };
+      const { SVGAnimatedLength } = context.target.ownerGlobal;
+      if (context.imageInfo.height instanceof SVGAnimatedLength) {
+        context.imageInfo.height = context.imageInfo.height.animVal.value;
+      }
+      if (context.imageInfo.width instanceof SVGAnimatedLength) {
+        context.imageInfo.width = context.imageInfo.width.animVal.value;
+      }
 
       const request = context.target.getRequest(
         Ci.nsIImageLoadingContent.CURRENT_REQUEST
       );
 
       if (request && request.imageStatus & request.STATUS_SIZE_AVAILABLE) {
         context.onLoadedImage = true;
       }
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1287,19 +1287,16 @@ pref("browser.newtabpage.activity-stream
 // ASRouter provider configuration
 pref("browser.newtabpage.activity-stream.asrouter.providers.cfr", "{\"id\":\"cfr\",\"enabled\":true,\"type\":\"remote-settings\",\"bucket\":\"cfr\",\"frequency\":{\"custom\":[{\"period\":\"daily\",\"cap\":1}]},\"categories\":[\"cfrAddons\",\"cfrFeatures\"],\"updateCycleInMs\":3600000}");
 pref("browser.newtabpage.activity-stream.asrouter.providers.whats-new-panel", "{\"id\":\"whats-new-panel\",\"enabled\":true,\"type\":\"remote-settings\",\"bucket\":\"whats-new-panel\",\"updateCycleInMs\":3600000}");
 pref("browser.newtabpage.activity-stream.asrouter.providers.message-groups", "{\"id\":\"message-groups\",\"enabled\":true,\"type\":\"remote-settings\",\"bucket\":\"message-groups\",\"updateCycleInMs\":3600000}");
 // This url, if changed, MUST continue to point to an https url. Pulling arbitrary content to inject into
 // this page over http opens us up to a man-in-the-middle attack that we'd rather not face. If you are a downstream
 // repackager of this code using an alternate snippet url, please keep your users safe
 pref("browser.newtabpage.activity-stream.asrouter.providers.snippets", "{\"id\":\"snippets\",\"enabled\":true,\"type\":\"remote\",\"url\":\"https://snippets.cdn.mozilla.net/%STARTPAGE_VERSION%/%NAME%/%VERSION%/%APPBUILDID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/\",\"updateCycleInMs\":14400000}");
-#ifdef NIGHTLY_BUILD
-  pref("browser.newtabpage.activity-stream.asrouter.useReleaseSnippets", true);
-#endif
 
 // The pref that controls if ASRouter uses the remote fluent files.
 // It's enabled by default, but could be disabled to force ASRouter to use the local files.
 pref("browser.newtabpage.activity-stream.asrouter.useRemoteL10n", true);
 
 // These prefs control if Discovery Stream is enabled.
 pref("browser.newtabpage.activity-stream.discoverystream.enabled", true);
 pref("browser.newtabpage.activity-stream.discoverystream.hardcoded-basic-layout", false);
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -736,16 +736,21 @@ toolbar:not(#TabsToolbar) > #personal-bo
     min-width: 280px;
   }
   :root[customizing] #urlbar-container {
     min-width: 245px;
   }
   #identity-icon-labels {
     max-width: 50px;
   }
+  /* Contenxtual identity labels are user-customizable and can be very long,
+     so we only show the colored icon when the window gets small. */
+  #userContext-label {
+    display: none;
+  }
 }
 /* 680px is just below half of popular 1366px wide screens, so when putting two
    browser windows next to each other on such a screen, they'll be above this
    threshold. */
 @media (max-width: 680px) {
   /* Page action buttons are duplicated in the page action menu so we can
      safely hide them in small windows. */
   #pageActionSeparator,
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -5093,21 +5093,21 @@ function updateUserContextUIIndicator() 
   if (!identity) {
     replaceContainerClass("color", hbox, "");
     hbox.hidden = true;
     return;
   }
 
   replaceContainerClass("color", hbox, identity.color);
 
-  let label = document.getElementById("userContext-label");
-  label.setAttribute(
-    "value",
-    ContextualIdentityService.getUserContextLabel(userContextId)
-  );
+  let label = ContextualIdentityService.getUserContextLabel(userContextId);
+  document.getElementById("userContext-label").setAttribute("value", label);
+  // Also set the container label as the tooltip so we can only show the icon
+  // in small windows.
+  hbox.setAttribute("tooltiptext", label);
 
   let indicator = document.getElementById("userContext-indicator");
   replaceContainerClass("icon", indicator, identity.icon);
 
   hbox.hidden = false;
 }
 
 /**
--- a/browser/base/content/test/contextMenu/browser_contextmenu.js
+++ b/browser/base/content/test/contextMenu/browser_contextmenu.js
@@ -267,34 +267,53 @@ add_task(async function test_mailto() {
     "context-searchselect",
     true,
     "context-searchselect-private",
     true,
   ]);
 });
 
 add_task(async function test_image() {
-  await test_contextmenu("#test-image", [
-    "context-viewimage",
-    true,
-    "context-copyimage-contents",
-    true,
-    "context-copyimage",
-    true,
-    "---",
-    null,
-    "context-saveimage",
-    true,
-    "context-sendimage",
-    true,
-    "context-setDesktopBackground",
-    true,
-    "context-viewimageinfo",
-    true,
-  ]);
+  for (let selector of ["#test-image", "#test-svg-image"]) {
+    await test_contextmenu(
+      selector,
+      [
+        "context-viewimage",
+        true,
+        "context-copyimage-contents",
+        true,
+        "context-copyimage",
+        true,
+        "---",
+        null,
+        "context-saveimage",
+        true,
+        "context-sendimage",
+        true,
+        "context-setDesktopBackground",
+        true,
+        "context-viewimageinfo",
+        true,
+      ],
+      {
+        onContextMenuShown() {
+          is(
+            typeof gContextMenu.imageInfo.height,
+            "number",
+            "Should have height"
+          );
+          is(
+            typeof gContextMenu.imageInfo.width,
+            "number",
+            "Should have width"
+          );
+        },
+      }
+    );
+  }
 });
 
 add_task(async function test_canvas() {
   await test_contextmenu(
     "#test-canvas",
     [
       "context-viewimage",
       true,
--- a/browser/base/content/test/contextMenu/subtst_contextmenu.html
+++ b/browser/base/content/test/contextMenu/subtst_contextmenu.html
@@ -14,16 +14,19 @@ Browser context menu subtest.
 if ("ShadowRoot" in this) {
   var sr = document.getElementById("shadow-host").attachShadow({mode: "closed"});
   sr.innerHTML = "<a href='http://mozilla.com'>Click the monkey!</a>";
 }
 </script>
 <a id="test-mailto" href="mailto:codemonkey@mozilla.com">Mail the monkey!</a><br>
 <input id="test-input"><br>
 <img id="test-image" src="ctxmenu-image.png">
+<svg>
+  <image id="test-svg-image" href="ctxmenu-image.png"/>
+</svg>
 <canvas id="test-canvas" width="100" height="100" style="background-color: blue"></canvas>
 <video controls id="test-video-ok"  src="video.ogg" width="100" height="100" style="background-color: green"></video>
 <video id="test-audio-in-video" src="audio.ogg" width="100" height="100" style="background-color: red"></video>
 <video controls id="test-video-bad" src="bogus.duh" width="100" height="100" style="background-color: orange"></video>
 <video controls id="test-video-bad2" width="100" height="100" style="background-color: yellow">
   <source src="bogus.duh" type="video/durrrr;">
 </video>
 <iframe id="test-iframe" width="98"  height="98" style="border: 1px solid black"></iframe>
--- a/browser/components/enterprisepolicies/Policies.jsm
+++ b/browser/components/enterprisepolicies/Policies.jsm
@@ -1203,57 +1203,110 @@ var Policies = {
           setAndLockPref("privacy.clearOnShutdown.downloads", true);
           setAndLockPref("privacy.clearOnShutdown.formdata", true);
           setAndLockPref("privacy.clearOnShutdown.history", true);
           setAndLockPref("privacy.clearOnShutdown.sessions", true);
           setAndLockPref("privacy.clearOnShutdown.siteSettings", true);
           setAndLockPref("privacy.clearOnShutdown.offlineApps", true);
         }
       } else {
-        setAndLockPref("privacy.sanitize.sanitizeOnShutdown", true);
+        let locked = true;
+        // Needed to preserve original behavior in perpetuity.
+        let lockDefaultPrefs = true;
+        if ("Locked" in param) {
+          locked = param.Locked;
+          lockDefaultPrefs = false;
+        }
+        setDefaultPref("privacy.sanitize.sanitizeOnShutdown", true, locked);
         if ("Cache" in param) {
-          setAndLockPref("privacy.clearOnShutdown.cache", param.Cache);
+          setDefaultPref("privacy.clearOnShutdown.cache", param.Cache, locked);
         } else {
-          setAndLockPref("privacy.clearOnShutdown.cache", false);
+          setDefaultPref(
+            "privacy.clearOnShutdown.cache",
+            false,
+            lockDefaultPrefs
+          );
         }
         if ("Cookies" in param) {
-          setAndLockPref("privacy.clearOnShutdown.cookies", param.Cookies);
+          setDefaultPref(
+            "privacy.clearOnShutdown.cookies",
+            param.Cookies,
+            locked
+          );
         } else {
-          setAndLockPref("privacy.clearOnShutdown.cookies", false);
+          setDefaultPref(
+            "privacy.clearOnShutdown.cookies",
+            false,
+            lockDefaultPrefs
+          );
         }
         if ("Downloads" in param) {
-          setAndLockPref("privacy.clearOnShutdown.downloads", param.Downloads);
+          setDefaultPref(
+            "privacy.clearOnShutdown.downloads",
+            param.Downloads,
+            locked
+          );
         } else {
-          setAndLockPref("privacy.clearOnShutdown.downloads", false);
+          setDefaultPref(
+            "privacy.clearOnShutdown.downloads",
+            false,
+            lockDefaultPrefs
+          );
         }
         if ("FormData" in param) {
-          setAndLockPref("privacy.clearOnShutdown.formdata", param.FormData);
+          setDefaultPref(
+            "privacy.clearOnShutdown.formdata",
+            param.FormData,
+            locked
+          );
         } else {
-          setAndLockPref("privacy.clearOnShutdown.formdata", false);
+          setDefaultPref(
+            "privacy.clearOnShutdown.formdata",
+            false,
+            lockDefaultPrefs
+          );
         }
         if ("History" in param) {
-          setAndLockPref("privacy.clearOnShutdown.history", param.History);
+          setDefaultPref(
+            "privacy.clearOnShutdown.history",
+            param.History,
+            locked
+          );
         } else {
-          setAndLockPref("privacy.clearOnShutdown.history", false);
+          setDefaultPref(
+            "privacy.clearOnShutdown.history",
+            false,
+            lockDefaultPrefs
+          );
         }
         if ("Sessions" in param) {
-          setAndLockPref("privacy.clearOnShutdown.sessions", param.Sessions);
+          setDefaultPref(
+            "privacy.clearOnShutdown.sessions",
+            param.Sessions,
+            locked
+          );
         } else {
-          setAndLockPref("privacy.clearOnShutdown.sessions", false);
+          setDefaultPref(
+            "privacy.clearOnShutdown.sessions",
+            false,
+            lockDefaultPrefs
+          );
         }
         if ("SiteSettings" in param) {
-          setAndLockPref(
+          setDefaultPref(
             "privacy.clearOnShutdown.siteSettings",
-            param.SiteSettings
+            param.SiteSettings,
+            locked
           );
         }
         if ("OfflineApps" in param) {
-          setAndLockPref(
+          setDefaultPref(
             "privacy.clearOnShutdown.offlineApps",
-            param.OfflineApps
+            param.OfflineApps,
+            locked
           );
         }
       }
     },
   },
 
   SearchBar: {
     onAllWindowsRestored(manager, param) {
--- a/browser/components/enterprisepolicies/schemas/policies-schema.json
+++ b/browser/components/enterprisepolicies/schemas/policies-schema.json
@@ -945,16 +945,19 @@
         "Sessions": {
           "type": "boolean"
         },
         "SiteSettings": {
           "type": "boolean"
         },
         "OfflineApps": {
           "type": "boolean"
+        },
+        "Locked": {
+          "type": "boolean"
         }
       }
     },
 
     "SearchBar": {
       "type": "string",
       "enum": ["unified", "separate"]
     },
--- a/browser/components/enterprisepolicies/tests/xpcshell/test_simple_pref_policies.js
+++ b/browser/components/enterprisepolicies/tests/xpcshell/test_simple_pref_policies.js
@@ -356,16 +356,76 @@ const POLICIES_TESTS = [
       "privacy.clearOnShutdown.downloads": false,
       "privacy.clearOnShutdown.formdata": false,
       "privacy.clearOnShutdown.history": false,
       "privacy.clearOnShutdown.sessions": false,
       "privacy.clearOnShutdown.offlineApps": true,
     },
   },
 
+  // POLICY: SanitizeOnShutdown using Locked
+  {
+    policies: {
+      SanitizeOnShutdown: {
+        Cache: true,
+        Locked: true,
+      },
+    },
+    lockedPrefs: {
+      "privacy.sanitize.sanitizeOnShutdown": true,
+      "privacy.clearOnShutdown.cache": true,
+    },
+    unlockedPrefs: {
+      "privacy.clearOnShutdown.cookies": false,
+      "privacy.clearOnShutdown.downloads": false,
+      "privacy.clearOnShutdown.formdata": false,
+      "privacy.clearOnShutdown.history": false,
+      "privacy.clearOnShutdown.sessions": false,
+    },
+  },
+
+  {
+    policies: {
+      SanitizeOnShutdown: {
+        Cache: true,
+        Cookies: false,
+        Locked: true,
+      },
+    },
+    lockedPrefs: {
+      "privacy.sanitize.sanitizeOnShutdown": true,
+      "privacy.clearOnShutdown.cache": true,
+      "privacy.clearOnShutdown.cookies": false,
+    },
+    unlockedPrefs: {
+      "privacy.clearOnShutdown.downloads": false,
+      "privacy.clearOnShutdown.formdata": false,
+      "privacy.clearOnShutdown.history": false,
+      "privacy.clearOnShutdown.sessions": false,
+    },
+  },
+
+  {
+    policies: {
+      SanitizeOnShutdown: {
+        Cache: true,
+        Locked: false,
+      },
+    },
+    unlockedPrefs: {
+      "privacy.sanitize.sanitizeOnShutdown": true,
+      "privacy.clearOnShutdown.cache": true,
+      "privacy.clearOnShutdown.cookies": false,
+      "privacy.clearOnShutdown.downloads": false,
+      "privacy.clearOnShutdown.formdata": false,
+      "privacy.clearOnShutdown.history": false,
+      "privacy.clearOnShutdown.sessions": false,
+    },
+  },
+
   // POLICY: DNSOverHTTPS Locked
   {
     policies: {
       DNSOverHTTPS: {
         Enabled: true,
         ProviderURL: "http://example.com/provider",
         Locked: true,
       },
--- a/browser/components/extensions/test/browser/browser_ext_urlbar.js
+++ b/browser/components/extensions/test/browser/browser_ext_urlbar.js
@@ -100,19 +100,17 @@ add_task(async function tip_onResultPick
 // Loads a tip extension without a main button URL and clicks the main button.
 add_task(async function tip_onResultPicked_mainButton_noURL_mouse() {
   let ext = await loadTipExtension();
   await UrlbarTestUtils.promiseAutocompleteResultPopup({
     window,
     waitForFocus,
     value: "test",
   });
-  let mainButton = document.querySelector(
-    "#urlbarView-row-0 .urlbarView-tip-button"
-  );
+  let mainButton = gURLBar.querySelector(".urlbarView-tip-button");
   Assert.ok(mainButton);
   EventUtils.synthesizeMouseAtCenter(mainButton, {});
   await ext.awaitMessage("onResultPicked received");
   await ext.unload();
 });
 
 // Loads a tip extension with a main button URL and presses enter on the main
 // button.
@@ -141,19 +139,17 @@ add_task(async function tip_onResultPick
 add_task(async function tip_onResultPicked_mainButton_url_mouse() {
   let ext = await loadTipExtension({ buttonUrl: "http://example.com/" });
   await BrowserTestUtils.withNewTab("about:blank", async () => {
     await UrlbarTestUtils.promiseAutocompleteResultPopup({
       window,
       waitForFocus,
       value: "test",
     });
-    let mainButton = document.querySelector(
-      "#urlbarView-row-0 .urlbarView-tip-button"
-    );
+    let mainButton = gURLBar.querySelector(".urlbarView-tip-button");
     Assert.ok(mainButton);
     let loadedPromise = BrowserTestUtils.browserLoaded(
       gBrowser.selectedBrowser
     );
     ext.onMessage("onResultPicked received", () => {
       Assert.ok(false, "onResultPicked should not be called");
     });
     EventUtils.synthesizeMouseAtCenter(mainButton, {});
@@ -191,19 +187,17 @@ add_task(async function tip_onResultPick
 add_task(async function tip_onResultPicked_helpButton_url_mouse() {
   let ext = await loadTipExtension({ helpUrl: "http://example.com/" });
   await BrowserTestUtils.withNewTab("about:blank", async () => {
     await UrlbarTestUtils.promiseAutocompleteResultPopup({
       window,
       waitForFocus,
       value: "test",
     });
-    let helpButton = document.querySelector(
-      "#urlbarView-row-0 .urlbarView-tip-help"
-    );
+    let helpButton = gURLBar.querySelector(".urlbarView-tip-help");
     Assert.ok(helpButton);
     let loadedPromise = BrowserTestUtils.browserLoaded(
       gBrowser.selectedBrowser
     );
     ext.onMessage("onResultPicked received", () => {
       Assert.ok(false, "onResultPicked should not be called");
     });
     EventUtils.synthesizeMouseAtCenter(helpButton, {});
--- a/browser/components/newtab/content-src/asrouter/templates/OnboardingMessage/WhatsNewMessage.schema.json
+++ b/browser/components/newtab/content-src/asrouter/templates/OnboardingMessage/WhatsNewMessage.schema.json
@@ -74,18 +74,20 @@
       "enum": ["OPEN_URL", "OPEN_ABOUT_PAGE"]
     },
     "icon_url": {
       "description": "(optional) URL for the What's New message icon.",
       "type": "string",
       "format": "uri"
     },
     "icon_alt": {
-      "description": "Alt text for image.",
-      "type": "string"
+      "allOf": [
+        {"$ref": "#/definitions/localizableText"},
+        {"description": "Alt text for image."}
+      ]
     }
   },
   "additionalProperties": false,
   "required": ["published_date", "title", "body", "cta_url", "bucket_id"],
   "dependencies": {
     "layout": ["layout_title_content_variable"]
   }
 }
--- a/browser/components/newtab/lib/ASRouter.jsm
+++ b/browser/components/newtab/lib/ASRouter.jsm
@@ -625,19 +625,16 @@ class _ASRouter {
         const localProvider = this._localProviders[provider.localProvider];
         provider.messages = localProvider ? localProvider.getMessages() : [];
       }
       if (provider.type === "remote" && provider.url) {
         provider.url = provider.url.replace(
           /%STARTPAGE_VERSION%/g,
           STARTPAGE_VERSION
         );
-        if (ASRouterPreferences.useReleaseSnippets) {
-          provider.url = provider.url.replace(/%CHANNEL%/g, "release");
-        }
         provider.url = Services.urlFormatter.formatURL(provider.url);
       }
       this.normalizeItemFrequency(provider);
       // Reset provider update timestamp to force message refresh
       provider.lastUpdated = undefined;
       return provider;
     });
 
--- a/browser/components/newtab/lib/ASRouterPreferences.jsm
+++ b/browser/components/newtab/lib/ASRouterPreferences.jsm
@@ -29,24 +29,16 @@ XPCOMUtils.defineLazyPreferenceGetter(
   this,
   "trailheadPrefs",
   FIRST_RUN_PREF,
   "",
   null,
   getTrailheadConfigFromPref
 );
 
-XPCOMUtils.defineLazyPreferenceGetter(
-  this,
-  "useReleaseSnippets",
-  "browser.newtabpage.activity-stream.asrouter.useReleaseSnippets",
-  false,
-  null
-);
-
 const DEFAULT_STATE = {
   _initialized: false,
   _providers: null,
   _providerPrefBranch: PROVIDER_PREF_BRANCH,
   _devtoolsEnabled: null,
   _devtoolsPref: DEVTOOLS_PREF,
 };
 
@@ -176,20 +168,16 @@ class _ASRouterPreferences {
       this._devtoolsEnabled = Services.prefs.getBoolPref(
         this._devtoolsPref,
         false
       );
     }
     return this._devtoolsEnabled;
   }
 
-  get useReleaseSnippets() {
-    return useReleaseSnippets;
-  }
-
   observe(aSubject, aTopic, aPrefName) {
     if (aPrefName && aPrefName.startsWith(this._providerPrefBranch)) {
       this._providers = null;
     } else if (aPrefName === this._devtoolsPref) {
       this._providers = null;
       this._devtoolsEnabled = null;
     }
     this._callbacks.forEach(cb => cb(aPrefName));
--- a/browser/components/newtab/lib/PanelTestProvider.jsm
+++ b/browser/components/newtab/lib/PanelTestProvider.jsm
@@ -59,16 +59,35 @@ const MESSAGES = () => [
             "https://www.mozilla.org/%LOCALE%/etc/firefox/retention/thank-you-a/",
           expireDelta: TWO_DAYS,
         },
       },
     },
     trigger: { id: "momentsUpdate" },
   },
   {
+    id: "WHATS_NEW_AWESOMEBAR_74",
+    template: "whatsnew_panel_message",
+    order: 1,
+    content: {
+      bucket_id: "WHATS_NEW_AWESOMEBAR_74",
+      published_date: 1581675076835,
+      title: { string_id: "cfr-whatsnew-searchbar-title" },
+      icon_url: "chrome://browser/skin/search-glass.svg",
+      icon_alt: { string_id: "cfr-whatsnew-searchbar-icon-alt-text" },
+      body: { string_id: "cfr-whatsnew-searchbar-body-enginename" },
+      cta_url:
+        "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/address-bar-search",
+      cta_type: "OPEN_URL",
+      link_text: { string_id: "cfr-whatsnew-pip-cta" },
+    },
+    targeting: `firefoxVersion >= 74`,
+    trigger: { id: "whatsNewPanelOpened" },
+  },
+  {
     id: "WHATS_NEW_PIP_72",
     template: "whatsnew_panel_message",
     order: 4,
     content: {
       bucket_id: "WHATS_NEW_72",
       published_date: 1574776601000,
       title: { string_id: "cfr-whatsnew-pip-header" },
       icon_url:
--- a/browser/components/newtab/lib/TelemetryFeed.jsm
+++ b/browser/components/newtab/lib/TelemetryFeed.jsm
@@ -787,25 +787,16 @@ this.TelemetryFeed = class TelemetryFeed
   }
 
   async handleASRouterUserEvent(action) {
     const { ping, pingType } = await this.createASRouterEvent(action);
     if (!pingType) {
       Cu.reportError("Unknown ping type for ASRouter telemetry");
       return;
     }
-    // Don't report snippets telemetry from Nightly channel if using release
-    // snippets endpoint
-    if (
-      pingType === "snippets" &&
-      ASRouterPreferences.useReleaseSnippets &&
-      action.data.action !== "snippets_local_testing_user_event"
-    ) {
-      return;
-    }
     this.sendStructuredIngestionEvent(
       ping,
       STRUCTURED_INGESTION_NAMESPACE_MS,
       pingType,
       "1"
     );
   }
 
--- a/browser/components/newtab/lib/ToolbarPanelHub.jsm
+++ b/browser/components/newtab/lib/ToolbarPanelHub.jsm
@@ -400,16 +400,17 @@ class _ToolbarPanelHub {
     if (options.content) {
       await this._setString(node, options.content);
     }
 
     return node;
   }
 
   async _contentArguments() {
+    const { defaultEngine } = Services.search;
     // Between now and 6 weeks ago
     const dateTo = new Date();
     const dateFrom = new Date(dateTo.getTime() - 42 * 24 * 60 * 60 * 1000);
     const eventsByDate = await TrackingDBService.getEventsByDateRange(
       dateFrom,
       dateTo
     );
     // Make sure we set all types of possible values to 0 because they might
@@ -432,16 +433,20 @@ class _ToolbarPanelHub {
       // Keys need to match variable names used in asrouter.ftl
       // `earliestDate` will be either 6 weeks ago or when tracking recording
       // started. Whichever is more recent.
       earliestDate: Math.max(
         new Date(await TrackingDBService.getEarliestRecordedDate()),
         dateFrom
       ),
       ...totalEvents,
+      // Passing in `undefined` as string for the Fluent variable name
+      // in order to match and select the message that does not require
+      // the variable.
+      searchEngineName: defaultEngine ? defaultEngine.name : "undefined",
     };
   }
 
   // If `string_id` is present it means we are relying on fluent for translations.
   // Otherwise, we have a vanilla string.
   async _setString(el, stringObj) {
     if (stringObj && stringObj.string_id) {
       const [{ value }] = await RemoteL10n.l10n.formatMessages([
--- a/browser/components/newtab/test/unit/asrouter/ASRouter.test.js
+++ b/browser/components/newtab/test/unit/asrouter/ASRouter.test.js
@@ -123,17 +123,16 @@ describe("ASRouter", () => {
 
     sandbox.spy(ASRouterPreferences, "init");
     sandbox.spy(ASRouterPreferences, "uninit");
     sandbox.spy(ASRouterPreferences, "addListener");
     sandbox.spy(ASRouterPreferences, "removeListener");
     sandbox.stub(ASRouterPreferences, "trailhead").get(() => {
       return { trailheadTriplet: "test" };
     });
-    sandbox.stub(ASRouterPreferences, "useReleaseSnippets").get(() => true);
     sandbox.replaceGetter(
       ASRouterPreferences,
       "personalizedCfrScores",
       () => personalizedCfrScores
     );
 
     clock = sandbox.useFakeTimers();
     fetchStub = sandbox
@@ -955,35 +954,16 @@ describe("ASRouter", () => {
         .returns(replacedUrl);
       const provider = { id: "foo", enabled: true, type: "remote", url };
       setMessageProviderPref([provider]);
       Router._updateMessageProviders();
       assert.calledOnce(stub);
       assert.calledWithExactly(stub, url);
       assert.equal(Router.state.providers[0].url, replacedUrl);
     });
-    it("should use release snippets", () => {
-      const url = "https://www.example.com/%CHANNEL%/";
-      const provider = { id: "foo", enabled: true, type: "remote", url };
-      setMessageProviderPref([provider]);
-
-      Router._updateMessageProviders();
-
-      assert.isTrue(Router.state.providers[0].url.includes("release"));
-    });
-    it("should not use release snippets", () => {
-      sandbox.stub(ASRouterPreferences, "useReleaseSnippets").get(() => false);
-      const url = "https://www.example.com/%CHANNEL%/";
-      const provider = { id: "foo", enabled: true, type: "remote", url };
-      setMessageProviderPref([provider]);
-
-      Router._updateMessageProviders();
-
-      assert.isFalse(Router.state.providers[0].url.includes("release"));
-    });
     it("should only add the providers that are enabled", () => {
       const providers = [
         {
           id: "foo",
           enabled: false,
           type: "remote",
           url: "https://www.foo.com/",
         },
--- a/browser/components/newtab/test/unit/asrouter/ASRouterPreferences.test.js
+++ b/browser/components/newtab/test/unit/asrouter/ASRouterPreferences.test.js
@@ -370,27 +370,16 @@ describe("ASRouterPreferences", () => {
       assert.deepEqual(
         ASRouterPreferences._transformPersonalizedCfrScores(
           JSON.stringify(scores)
         ),
         scores
       );
     });
   });
-  describe("#useReleaseSnippets", () => {
-    it("should return the pref value", () => {
-      global.useReleaseSnippets = true;
-
-      assert.isTrue(ASRouterPreferences.useReleaseSnippets);
-
-      global.useReleaseSnippets = false;
-
-      assert.isFalse(ASRouterPreferences.useReleaseSnippets);
-    });
-  });
   describe("#getTrailheadConfigFromPref", () => {
     it("should return trailHeadTriplet and trailHeadInterrupt", () => {
       let result = getTrailheadConfigFromPref("foo-bar");
       assert.propertyVal(result, "trailheadInterrupt", "foo");
       assert.propertyVal(result, "trailheadTriplet", "bar");
     });
     it("should return default values when pref is empty", () => {
       let result = getTrailheadConfigFromPref("");
--- a/browser/components/newtab/test/unit/asrouter/PanelTestProvider.test.js
+++ b/browser/components/newtab/test/unit/asrouter/PanelTestProvider.test.js
@@ -3,17 +3,17 @@ import schema from "content-src/asrouter
 import update_schema from "content-src/asrouter/templates/OnboardingMessage/UpdateAction.schema.json";
 import whats_new_schema from "content-src/asrouter/templates/OnboardingMessage/WhatsNewMessage.schema.json";
 const messages = PanelTestProvider.getMessages();
 
 describe("PanelTestProvider", () => {
   it("should have a message", () => {
     // Careful: when changing this number make sure that new messages also go
     // through schema verifications.
-    assert.lengthOf(messages, 16);
+    assert.lengthOf(messages, 17);
   });
   it("should be a valid message", () => {
     const fxaMessages = messages.filter(
       ({ template }) => template === "fxa_bookmark_panel"
     );
     for (let message of fxaMessages) {
       assert.jsonSchema(message.content, schema);
     }
--- a/browser/components/newtab/test/unit/lib/TelemetryFeed.test.js
+++ b/browser/components/newtab/test/unit/lib/TelemetryFeed.test.js
@@ -1809,52 +1809,10 @@ describe("TelemetryFeed", () => {
       instance = new TelemetryFeed();
       sandbox.spy(instance, "sendStructuredIngestionEvent");
 
       await instance.handleASRouterUserEvent({ data });
 
       assert.calledOnce(global.Cu.reportError);
       assert.notCalled(instance.sendStructuredIngestionEvent);
     });
-    it("should not send telemetry for Nightly release snippets", async () => {
-      sandbox.stub(ASRouterPreferences, "useReleaseSnippets").get(() => true);
-      const data = {
-        action: "snippets_user_event",
-        event: "IMPRESSION",
-        message_id: "12345",
-      };
-      instance = new TelemetryFeed();
-      sandbox.spy(instance, "sendStructuredIngestionEvent");
-
-      await instance.handleASRouterUserEvent({ data });
-
-      assert.notCalled(instance.sendStructuredIngestionEvent);
-    });
-    it("should send telemetry for regular channel snippets", async () => {
-      sandbox.stub(ASRouterPreferences, "useReleaseSnippets").get(() => false);
-      const data = {
-        action: "snippets_user_event",
-        event: "IMPRESSION",
-        message_id: "12345",
-      };
-      instance = new TelemetryFeed();
-      sandbox.spy(instance, "sendStructuredIngestionEvent");
-
-      await instance.handleASRouterUserEvent({ data });
-
-      assert.calledOnce(instance.sendStructuredIngestionEvent);
-    });
-    it("should send telemetry for test snippets", async () => {
-      sandbox.stub(ASRouterPreferences, "useReleaseSnippets").get(() => true);
-      const data = {
-        action: "snippets_local_testing_user_event",
-        event: "IMPRESSION",
-        message_id: "12345",
-      };
-      instance = new TelemetryFeed();
-      sandbox.spy(instance, "sendStructuredIngestionEvent");
-
-      await instance.handleASRouterUserEvent({ data });
-
-      assert.calledOnce(instance.sendStructuredIngestionEvent);
-    });
   });
 });
--- a/browser/components/newtab/test/unit/lib/ToolbarPanelHub.test.js
+++ b/browser/components/newtab/test/unit/lib/ToolbarPanelHub.test.js
@@ -18,16 +18,17 @@ describe("ToolbarPanelHub", () => {
   let getBoolPrefStub;
   let setBoolPrefStub;
   let waitForInitializedStub;
   let isBrowserPrivateStub;
   let fakeDispatch;
   let getEarliestRecordedDateStub;
   let getEventsByDateRangeStub;
   let handleUserActionStub;
+  let defaultSearchStub;
 
   beforeEach(async () => {
     sandbox = sinon.createSandbox();
     globals = new GlobalOverrider();
     instance = new _ToolbarPanelHub();
     waitForInitializedStub = sandbox.stub().resolves();
     fakeElementById = {
       setAttribute: sandbox.stub(),
@@ -90,26 +91,28 @@ describe("ToolbarPanelHub", () => {
     fakeDispatch = sandbox.stub();
     isBrowserPrivateStub = sandbox.stub();
     getEarliestRecordedDateStub = sandbox.stub().returns(
       // A random date that's not the current timestamp
       new Date() - 500
     );
     getEventsByDateRangeStub = sandbox.stub().returns([]);
     handleUserActionStub = sandbox.stub();
+    defaultSearchStub = { defaultEngine: { name: "DDG" } };
     globals.set({
       EveryWindow: everyWindowStub,
       Services: {
         ...Services,
         prefs: {
           addObserver: addObserverStub,
           removeObserver: removeObserverStub,
           getBoolPref: getBoolPrefStub,
           setBoolPref: setBoolPrefStub,
         },
+        search: defaultSearchStub,
       },
       PrivateBrowsingUtils: {
         isBrowserPrivate: isBrowserPrivateStub,
       },
       TrackingDBService: {
         getEarliestRecordedDate: getEarliestRecordedDateStub,
         getEventsByDateRange: getEventsByDateRangeStub,
       },
@@ -455,16 +458,17 @@ describe("ToolbarPanelHub", () => {
           args: {
             blockedCount: 7,
             earliestDate: getEarliestRecordedDateStub(),
             cookieCount: 7,
             cryptominerCount: 0,
             socialCount: 0,
             trackerCount: 0,
             fingerprinterCount: 0,
+            searchEngineName: Services.search.defaultEngine.name,
           },
         },
       ]);
     });
     it("should correctly compute event counts per type", async () => {
       const messages = (await PanelTestProvider.getMessages()).filter(
         m => m.template === "whatsnew_panel_message"
       );
@@ -488,16 +492,39 @@ describe("ToolbarPanelHub", () => {
           args: {
             blockedCount: 7,
             earliestDate: getEarliestRecordedDateStub(),
             trackerCount: 4,
             fingerprinterCount: 3,
             cookieCount: 0,
             cryptominerCount: 0,
             socialCount: 0,
+            searchEngineName: Services.search.defaultEngine.name,
+          },
+        },
+      ]);
+    });
+    it("should fallback to undefined search engine name", async () => {
+      globals.set("Services", {
+        ...global.Services,
+        search: { defaultEngine: null },
+      });
+      const messages = (await PanelTestProvider.getMessages()).filter(
+        m => m.template === "whatsnew_panel_message"
+      );
+      getMessagesStub.returns(messages);
+
+      await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
+
+      assert.calledWithExactly(global.RemoteL10n.l10n.formatMessages, [
+        {
+          id: sinon.match.string,
+          args: {
+            ...instance.state.contentArguments,
+            searchEngineName: "undefined",
           },
         },
       ]);
     });
     it("should only render unique dates (no duplicates)", async () => {
       const messages = (await PanelTestProvider.getMessages()).filter(
         m => m.template === "whatsnew_panel_message"
       );
--- a/browser/components/search/content/searchbar.js
+++ b/browser/components/search/content/searchbar.js
@@ -672,26 +672,30 @@
 
       this.textbox.addEventListener("contextmenu", event => {
         if (!this._menupopup) {
           this._buildContextMenu();
         }
 
         BrowserSearch.searchBar._textbox.closePopup();
 
-        let controller = document.commandDispatcher.getControllerForCommand(
-          "cmd_paste"
-        );
-        let pasteEnabled = controller.isCommandEnabled("cmd_paste");
-        if (pasteEnabled) {
-          this._pasteAndSearchMenuItem.removeAttribute("disabled");
-        } else {
-          this._pasteAndSearchMenuItem.setAttribute("disabled", "true");
+        // Update disabled state of menu items
+        for (let item of this._menupopup.querySelectorAll("menuitem[cmd]")) {
+          let command = item.getAttribute("cmd");
+          let controller = document.commandDispatcher.getControllerForCommand(
+            command
+          );
+          item.disabled = !controller.isCommandEnabled(command);
         }
 
+        let pasteEnabled = document.commandDispatcher
+          .getControllerForCommand("cmd_paste")
+          .isCommandEnabled("cmd_paste");
+        this._pasteAndSearchMenuItem.disabled = !pasteEnabled;
+
         this._menupopup.openPopupAtScreen(event.screenX, event.screenY, true);
 
         // Make sure the context menu isn't opened via keyboard shortcut.
         if (event.button) {
           this._maybeSelectAll();
         }
         event.preventDefault();
       });
--- a/browser/components/search/test/browser/browser.ini
+++ b/browser/components/search/test/browser/browser.ini
@@ -38,16 +38,17 @@ skip-if = verify && debug && os == 'win'
 [browser_healthreport.js]
 skip-if = (verify && debug && (os == 'win' || os == 'linux'))
 [browser_hiddenOneOffs_cleanup.js]
 [browser_hiddenOneOffs_diacritics.js]
 [browser_oneOffContextMenu.js]
 [browser_oneOffContextMenu_setDefault.js]
 skip-if = (os == "win" && processor == "aarch64") # disabled on aarch64 due to Bug 1584886
 [browser_private_search_perwindowpb.js]
+[browser_searchbar_context.js]
 [browser_searchbar_default.js]
 [browser_searchbar_openpopup.js]
 skip-if = os == "linux" # Linux has different focus behaviours.
 [browser_searchbar_keyboard_navigation.js]
 [browser_searchbar_smallpanel_keyboard_navigation.js]
 [browser_searchEngine_behaviors.js]
 [browser_searchTelemetry.js]
 skip-if = !debug && (os == 'linux') # Bug 1515466
new file mode 100644
--- /dev/null
+++ b/browser/components/search/test/browser/browser_searchbar_context.js
@@ -0,0 +1,105 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests the context menu for the search bar.
+ */
+
+"use strict";
+
+let win;
+
+add_task(async function setup() {
+  await gCUITestUtils.addSearchBar();
+
+  win = await BrowserTestUtils.openNewBrowserWindow();
+
+  // Create an engine to use for the test.
+  await Services.search.addEngineWithDetails("MozSearch1", {
+    alias: "mozalias",
+    method: "GET",
+    template: "https://example.com/?q={searchTerms}",
+  });
+
+  let originalEngine = await Services.search.getDefault();
+  let engineDefault = Services.search.getEngineByName("MozSearch1");
+  await Services.search.setDefault(engineDefault);
+
+  registerCleanupFunction(async function() {
+    gCUITestUtils.removeSearchBar();
+    await Services.search.setDefault(originalEngine);
+    await Services.search.removeEngine(engineDefault);
+    await BrowserTestUtils.closeWindow(win);
+  });
+});
+
+add_task(async function test_emptybar() {
+  const searchbar = win.BrowserSearch.searchBar;
+  searchbar.focus();
+
+  let contextMenu = searchbar.querySelector(".textbox-contextmenu");
+  let contextMenuPromise = BrowserTestUtils.waitForEvent(
+    contextMenu,
+    "popupshown"
+  );
+
+  await EventUtils.synthesizeMouseAtCenter(
+    searchbar,
+    { type: "contextmenu", button: 2 },
+    win
+  );
+  await contextMenuPromise;
+
+  Assert.ok(
+    contextMenu.getElementsByAttribute("cmd", "cmd_cut")[0].disabled,
+    "Should have disabled the cut menuitem"
+  );
+  Assert.ok(
+    contextMenu.getElementsByAttribute("cmd", "cmd_copy")[0].disabled,
+    "Should have disabled the copy menuitem"
+  );
+
+  let popupHiddenPromise = BrowserTestUtils.waitForEvent(
+    contextMenu,
+    "popuphidden"
+  );
+  contextMenu.hidePopup();
+  await popupHiddenPromise;
+});
+
+add_task(async function test_text_in_bar() {
+  const searchbar = win.BrowserSearch.searchBar;
+  searchbar.focus();
+
+  searchbar.value = "Test";
+  searchbar._textbox.editor.selectAll();
+
+  let contextMenu = searchbar.querySelector(".textbox-contextmenu");
+  let contextMenuPromise = BrowserTestUtils.waitForEvent(
+    contextMenu,
+    "popupshown"
+  );
+
+  await EventUtils.synthesizeMouseAtCenter(
+    searchbar,
+    { type: "contextmenu", button: 2 },
+    win
+  );
+  await contextMenuPromise;
+
+  Assert.ok(
+    !contextMenu.getElementsByAttribute("cmd", "cmd_cut")[0].disabled,
+    "Should have enabled the cut menuitem"
+  );
+  Assert.ok(
+    !contextMenu.getElementsByAttribute("cmd", "cmd_copy")[0].disabled,
+    "Should have enabled the copy menuitem"
+  );
+
+  let popupHiddenPromise = BrowserTestUtils.waitForEvent(
+    contextMenu,
+    "popuphidden"
+  );
+  contextMenu.hidePopup();
+  await popupHiddenPromise;
+});
--- a/browser/components/urlbar/UrlbarPrefs.jsm
+++ b/browser/components/urlbar/UrlbarPrefs.jsm
@@ -119,16 +119,21 @@ const PREF_URLBAR_DEFAULTS = new Map([
   ["restyleSearches", false],
 
   // The number of times the user has been shown the onboarding search tip.
   ["searchTips.onboard.shownCount", 0],
 
   // The number of times the user has been shown the redirect search tip.
   ["searchTips.redirect.shownCount", 0],
 
+  // Hidden pref. Disables checks that prevent search tips being shown, thus
+  // showing them every time the newtab page or the default search engine
+  // homepage is opened.
+  ["searchTips.test.ignoreShowLimits", false],
+
   // Whether speculative connections should be enabled.
   ["speculativeConnect.enabled", true],
 
   // Results will include the user's bookmarks when this is true.
   ["suggest.bookmark", true],
 
   // Results will include the user's history when this is true.
   ["suggest.history", true],
--- a/browser/components/urlbar/UrlbarProviderSearchTips.jsm
+++ b/browser/components/urlbar/UrlbarProviderSearchTips.jsm
@@ -275,17 +275,18 @@ class ProviderSearchTips extends UrlbarP
     if (this.showedTipTypeInCurrentEngagement != TIPS.NONE) {
       window.gURLBar.view.close();
     }
 
     // Check if we are supposed to show a tip for the current session.
     if (
       !UrlbarPrefs.get("update1.searchTips") ||
       !cfrFeaturesUserPref ||
-      this.disableTipsForCurrentSession
+      (this.disableTipsForCurrentSession &&
+        !UrlbarPrefs.get("searchTips.test.ignoreShowLimits"))
     ) {
       return;
     }
 
     this._maybeShowTipForUrl(uri.spec).catch(Cu.reportError);
   }
 
   /**
@@ -293,24 +294,26 @@ class ProviderSearchTips extends UrlbarP
    * this.currentTip, and starts a search on an empty string.
    * @param {number} urlStr
    *   The URL of the page being loaded, in string form.
    */
   async _maybeShowTipForUrl(urlStr) {
     let instance = {};
     this._maybeShowTipForUrlInstance = instance;
 
+    let ignoreShowLimits = UrlbarPrefs.get("searchTips.test.ignoreShowLimits");
+
     // Don't show a tip if the browser is already showing some other notification.
-    if (isBrowserShowingNotification()) {
+    if (isBrowserShowingNotification() && !ignoreShowLimits) {
       return;
     }
 
     // Don't show a tip if the browser has been updated recently.
     let date = await lastBrowserUpdateDate();
-    if (Date.now() - date <= LAST_UPDATE_THRESHOLD_MS) {
+    if (Date.now() - date <= LAST_UPDATE_THRESHOLD_MS && !ignoreShowLimits) {
       return;
     }
 
     // Determine which tip we should show for the tab.
     let tip;
     let shownCountPrefName;
     let isNewtab = ["about:newtab", "about:home"].includes(urlStr);
     let isSearchHomepage = !isNewtab && (await isDefaultEngineHomepage(urlStr));
@@ -327,17 +330,17 @@ class ProviderSearchTips extends UrlbarP
 
     if (this._maybeShowTipForUrlInstance != instance) {
       return;
     }
 
     // If we've shown this type of tip the maximum number of times over all
     // sessions, don't show it again.
     let shownCount = UrlbarPrefs.get(shownCountPrefName);
-    if (shownCount >= MAX_SHOWN_COUNT) {
+    if (shownCount >= MAX_SHOWN_COUNT && !ignoreShowLimits) {
       return;
     }
 
     this.currentTip = tip;
 
     // At this point, we're showing a tip.
     this.disableTipsForCurrentSession = true;
 
--- a/browser/components/urlbar/UrlbarView.jsm
+++ b/browser/components/urlbar/UrlbarView.jsm
@@ -17,16 +17,23 @@ XPCOMUtils.defineLazyModuleGetters(this,
 
 // Stale rows are removed on a timer with this timeout.  Tests can override this
 // by setting UrlbarView.removeStaleRowsTimeout.
 const DEFAULT_REMOVE_STALE_ROWS_TIMEOUT = 400;
 
 const getBoundsWithoutFlushing = element =>
   element.ownerGlobal.windowUtils.getBoundsWithoutFlushing(element);
 
+// Used to get a unique id to use for row elements, it wraps at 9999, that
+// should be plenty for our needs.
+let gUniqueIdSerial = 1;
+function getUniqueId(prefix) {
+  return prefix + (gUniqueIdSerial++ % 9999);
+}
+
 /**
  * Receives and displays address bar autocomplete results.
  */
 class UrlbarView {
   /**
    * @param {UrlbarInput} input
    *   The UrlbarInput instance belonging to this UrlbarView instance.
    */
@@ -896,16 +903,17 @@ class UrlbarView {
     // the focus goes straight to the tip button.)
     item.addEventListener("focus", () => this.input.focus(), true);
   }
 
   _updateRow(item, result) {
     let oldResultType = item.result && item.result.type;
     item.result = result;
     item.removeAttribute("stale");
+    item.id = getUniqueId("urlbarView-row-");
 
     let needsNewContent =
       oldResultType === undefined ||
       (oldResultType == UrlbarUtils.RESULT_TYPE.TIP) !=
         (result.type == UrlbarUtils.RESULT_TYPE.TIP);
 
     if (needsNewContent) {
       if (item._content) {
@@ -1059,64 +1067,55 @@ class UrlbarView {
     }
 
     item._elements.get("titleSeparator").hidden = !action && !setURL;
   }
 
   _updateRowForTip(item, result) {
     let favicon = item._elements.get("favicon");
     favicon.src = result.payload.icon || UrlbarUtils.ICON.TIP;
+    favicon.id = item.id + "-icon";
 
     let title = item._elements.get("title");
+    title.id = item.id + "-title";
     // Add-ons will provide text, rather than l10n ids.
     if (result.payload.textData) {
       this.document.l10n.setAttributes(
         title,
         result.payload.textData.id,
         result.payload.textData.args
       );
     } else {
       title.textContent = result.payload.text;
     }
 
+    item._content.setAttribute("aria-labelledby", `${favicon.id} ${title.id}`);
+
     let tipButton = item._elements.get("tipButton");
+    tipButton.id = item.id + "-tip-button";
     // Add-ons will provide buttonText, rather than l10n ids.
     if (result.payload.buttonTextData) {
       this.document.l10n.setAttributes(
         tipButton,
         result.payload.buttonTextData.id,
         result.payload.buttonTextData.args
       );
     } else {
       tipButton.textContent = result.payload.buttonText;
     }
 
     let helpIcon = item._elements.get("helpButton");
+    helpIcon.id = item.id + "-tip-help";
     helpIcon.style.display = result.payload.helpUrl ? "" : "none";
   }
 
   _updateIndices() {
     for (let i = 0; i < this._rows.children.length; i++) {
       let item = this._rows.children[i];
       item.result.rowIndex = i;
-      item.id = "urlbarView-row-" + i;
-      if (item.result.type == UrlbarUtils.RESULT_TYPE.TIP) {
-        let favicon = item._elements.get("favicon");
-        favicon.id = item.id + "-icon";
-        let title = item._elements.get("title");
-        title.id = item.id + "-title";
-        item._content.setAttribute(
-          "aria-labelledby",
-          `${favicon.id} ${title.id}`
-        );
-        let tipButton = item._elements.get("tipButton");
-        tipButton.id = item.id + "-tip-button";
-        let helpButton = item._elements.get("helpButton");
-        helpButton.id = item.id + "-tip-help";
-      }
     }
     let selectableElement = this._getFirstSelectableElement();
     let uiIndex = 0;
     while (selectableElement) {
       selectableElement.elementIndex = uiIndex++;
       selectableElement = this._getNextSelectableElement(selectableElement);
     }
   }
--- a/browser/locales/en-US/browser/newtab/asrouter.ftl
+++ b/browser/locales/en-US/browser/newtab/asrouter.ftl
@@ -136,16 +136,25 @@ cfr-whatsnew-lockwise-backup-body =
 cfr-whatsnew-lockwise-backup-link-text = Turn on backups
 
 cfr-whatsnew-lockwise-take-title = Take your passwords with you
 cfr-whatsnew-lockwise-take-body =
    The { -lockwise-brand-short-name } mobile app lets you securely access your
    backed up passwords from anywhere.
 cfr-whatsnew-lockwise-take-link-text = Get the app
 
+## Search Bar
+
+cfr-whatsnew-searchbar-title = Type less, find more with the address bar
+# Variables:
+#   $searchEngineName - Name of the current default search engine as also shown in the urlbar.
+cfr-whatsnew-searchbar-body-enginename = Get to the sites you use most with a single click into the address bar. Find things faster with results from { $searchEngineName } and your browsing history.
+cfr-whatsnew-searchbar-body-generic = Get to the sites you use most with a single click into the address bar. Find things faster with search results from your browsing history.
+cfr-whatsnew-searchbar-icon-alt-text = Magnifying glass icon
+
 ## Picture-in-Picture
 
 cfr-whatsnew-pip-header = Watch videos while you browse
 cfr-whatsnew-pip-body = Picture-in-picture pops video into a floating window so you can watch while working in other tabs.
 cfr-whatsnew-pip-cta = Learn more
 
 ## Permission Prompt
 
--- a/browser/locales/l10n-onchange-changesets.json
+++ b/browser/locales/l10n-onchange-changesets.json
@@ -1,9 +1,26 @@
 {
+    "ar": {
+        "platforms": [
+            "linux",
+            "linux-devedition",
+            "linux64",
+            "linux64-devedition",
+            "macosx64",
+            "macosx64-devedition",
+            "win32",
+            "win32-devedition",
+            "win64",
+            "win64-aarch64",
+            "win64-aarch64-devedition",
+            "win64-devedition"
+        ],
+        "revision": "default"
+    },
     "en-CA": {
         "platforms": [
             "linux",
             "linux-devedition",
             "linux64",
             "linux64-devedition",
             "macosx64",
             "macosx64-devedition",
--- a/browser/modules/TabsList.jsm
+++ b/browser/modules/TabsList.jsm
@@ -42,16 +42,24 @@ class TabsListBase {
   handleEvent(event) {
     switch (event.type) {
       case "TabAttrModified":
         this._tabAttrModified(event.target);
         break;
       case "TabClose":
         this._tabClose(event.target);
         break;
+      case "TabMove":
+        this._moveTab(event.target);
+        break;
+      case "TabPinned":
+        if (!this.filterFn(event.target)) {
+          this._tabClose(event.target);
+        }
+        break;
       case "command":
         this._selectTab(event.target.tab);
         break;
     }
   }
 
   _selectTab(tab) {
     if (this.gBrowser.selectedTab != tab) {
@@ -91,21 +99,25 @@ class TabsListBase {
     this.tabToElement = new Map();
     this._cleanupListeners();
   }
 
   _setupListeners() {
     this.listenersRegistered = true;
     this.gBrowser.tabContainer.addEventListener("TabAttrModified", this);
     this.gBrowser.tabContainer.addEventListener("TabClose", this);
+    this.gBrowser.tabContainer.addEventListener("TabMove", this);
+    this.gBrowser.tabContainer.addEventListener("TabPinned", this);
   }
 
   _cleanupListeners() {
     this.gBrowser.tabContainer.removeEventListener("TabAttrModified", this);
     this.gBrowser.tabContainer.removeEventListener("TabClose", this);
+    this.gBrowser.tabContainer.removeEventListener("TabMove", this);
+    this.gBrowser.tabContainer.removeEventListener("TabPinned", this);
     this.listenersRegistered = false;
   }
 
   _tabAttrModified(tab) {
     let item = this.tabToElement.get(tab);
     if (item) {
       if (!this.filterFn(tab)) {
         // The tab no longer matches our criteria, remove it.
@@ -114,34 +126,43 @@ class TabsListBase {
         this._setRowAttributes(item, tab);
       }
     } else if (this.filterFn(tab)) {
       // The tab now matches our criteria, add a row for it.
       this._addTab(tab);
     }
   }
 
+  _moveTab(tab) {
+    let item = this.tabToElement.get(tab);
+    if (item) {
+      this._removeItem(item, tab);
+      this._addTab(tab);
+    }
+  }
   _addTab(newTab) {
+    if (!this.filterFn(newTab)) {
+      return;
+    }
     let newRow = this._createRow(newTab);
     let nextTab = newTab.nextElementSibling;
 
     while (nextTab && !this.filterFn(nextTab)) {
       nextTab = nextTab.nextElementSibling;
     }
 
-    if (nextTab) {
-      // If we found a tab after this one in the list, insert the new row before it.
-      let nextRow = this.tabToElement.get(nextTab);
+    // If we found a tab after this one in the list, insert the new row before it.
+    let nextRow = this.tabToElement.get(nextTab);
+    if (nextRow) {
       nextRow.parentNode.insertBefore(newRow, nextRow);
     } else {
       // If there's no next tab then insert it as usual.
       this._addElement(newRow);
     }
   }
-
   _tabClose(tab) {
     let item = this.tabToElement.get(tab);
     if (item) {
       this._removeItem(item, tab);
     }
   }
 
   _removeItem(item, tab) {
--- a/browser/themes/shared/incontentprefs/search.css
+++ b/browser/themes/shared/incontentprefs/search.css
@@ -31,16 +31,17 @@
 #defaultEngine,
 #defaultPrivateEngine {
   margin-inline-start: 0;
 }
 
 #defaultEngine > .menulist-label-box > .menulist-icon,
 #defaultPrivateEngine > .menulist-label-box > .menulist-icon {
   height: 16px;
+  width: 16px;
 }
 
 /* work around a display: none in Linux's menu.css, see bug 1112310 */
 .searchengine-menuitem > .menu-iconic-left {
   display: -moz-box;
 }
 
 #engineList {
--- a/build/moz.configure/windows.configure
+++ b/build/moz.configure/windows.configure
@@ -47,24 +47,24 @@ def windows_sdk_dir(value, host):
 # The $SDK/lib directories always have version subdirectories, but while the
 # versions match the one in $SDK/include for SDK 10, it's "winv6.3" for SDK
 # 8.1.
 
 
 @imports('os')
 @imports('re')
 @imports(_from='__builtin__', _import='sorted')
-@imports(_from='__builtin__', _import='WindowsError')
+@imports(_from='__builtin__', _import='Exception')
 def get_sdk_dirs(sdk, subdir):
     def get_dirs_containing(sdk, stem, subdir):
         base = os.path.join(sdk, stem)
         try:
             subdirs = [d for d in os.listdir(base)
                        if os.path.isdir(os.path.join(base, d))]
-        except WindowsError:
+        except Exception:
             subdirs = []
         if not subdirs:
             return ()
         if subdir in subdirs:
             return (base,)
         # At this point, either we have an incomplete or invalid SDK directory,
         # or we exclusively have version numbers in subdirs.
         return tuple(os.path.join(base, s) for s in subdirs
@@ -186,21 +186,21 @@ def valid_ucrt_sdk_dir(windows_sdk_dir, 
             if version != 'include':
                 sdks[d] = Version(version), sdk
                 continue
         if d == windows_sdk_dir_env:
             # When WINDOWSSDKDIR is set in the environment and we can't find the
             # Universal CRT SDK, chances are this is a start-shell-msvc*.bat
             # setup, where INCLUDE and LIB already contain the UCRT paths.
             ucrt_includes = [
-                p for p in os.environ.get('INCLUDE', '').split(os.pathsep)
+                p for p in os.environ.get('INCLUDE', '').split(';')
                 if os.path.basename(p).lower() == 'ucrt'
             ]
             ucrt_libs = [
-                p for p in os.environ.get('LIB', '').split(os.pathsep)
+                p for p in os.environ.get('LIB', '').split(';')
                 if os.path.basename(os.path.dirname(p)).lower() == 'ucrt'
             ]
             if ucrt_includes and ucrt_libs:
                 # Pick the first of each, since they are the ones that the
                 # compiler would look first. Assume they contain the SDK files.
                 include = os.path.dirname(ucrt_includes[0])
                 lib = os.path.dirname(os.path.dirname(ucrt_libs[0]))
                 path = os.path.dirname(os.path.dirname(include))
@@ -271,17 +271,17 @@ option(env='DIA_SDK_PATH', nargs=1,
 def dia_sdk_dir(vc_path, dia_sdk_path):
     if dia_sdk_path:
         path = os.path.normpath(dia_sdk_path[0])
 
     elif vc_path:
         # This would be easier if we had the installationPath that
         # get_vc_paths works with, since 'DIA SDK' is relative to that.
         path = os.path.normpath(os.path.join(
-            vc_path, r'..\..\..\..\DIA SDK'))
+            vc_path, '..', '..', '..', '..', 'DIA SDK'))
     else:
         return
 
     if os.path.exists(os.path.join(path, 'include', 'dia2.h')):
         return path
 
 
 @depends(vc_path, valid_windows_sdk_dir, valid_ucrt_sdk_dir, dia_sdk_dir)
@@ -309,17 +309,17 @@ def include_path(vc_path, windows_sdk_di
         os.path.join(windows_sdk_dir.include, 'shared'),
         os.path.join(windows_sdk_dir.include, 'um'),
         winrt_dir,
         os.path.join(ucrt_sdk_dir.include, 'ucrt'),
     ))
     if dia_sdk_dir:
         includes.append(os.path.join(dia_sdk_dir, 'include'))
     # Set in the environment for old-configure
-    includes = os.pathsep.join(includes)
+    includes = ';'.join(includes)
     os.environ['INCLUDE'] = includes
     return includes
 
 
 set_config('INCLUDE', include_path)
 
 
 @template
@@ -370,17 +370,17 @@ def lib_path_for(host_or_target):
         atlmfc_dir = os.path.join(vc_path, 'atlmfc', 'lib', sdk_target)
         if not os.path.isdir(atlmfc_dir):
             die('Cannot find the ATL/MFC libraries in the Visual C++ directory '
                 '(%s). Please install them.' % vc_path)
 
         libs = []
         lib_env = os.environ.get('LIB')
         if lib_env and not is_host:
-            libs.extend(lib_env.split(os.pathsep))
+            libs.extend(lib_env.split(';'))
         libs.extend((
             os.path.join(vc_path, 'lib', sdk_target),
             atlmfc_dir,
             os.path.join(windows_sdk_dir.lib, 'um', sdk_target),
             os.path.join(ucrt_sdk_dir.lib, 'ucrt', sdk_target),
         ))
         if dia_sdk_lib_dir:
             libs.append(dia_sdk_lib_dir)
@@ -388,17 +388,17 @@ def lib_path_for(host_or_target):
 
     return lib_path
 
 
 @depends_if(lib_path_for(target))
 @imports('os')
 def lib_path(libs):
     # Set in the environment for old-configure
-    libs = os.pathsep.join(libs)
+    libs = ';'.join(libs)
     os.environ['LIB'] = libs
     return libs
 
 
 set_config('LIB', lib_path)
 
 
 lib_path_for_host = lib_path_for(host)
@@ -427,27 +427,26 @@ set_config('HOST_LINKER_LIBPATHS_BAT', h
 
 # The when is technically wrong and should be removed and the code that
 # @depends on the option will need to be adapted when actual support for
 # clang-cl cross-builds emerge.
 option(env='MT', nargs=1, help='Path to the Microsoft Manifest Tool',
        when=host_is_windows)
 
 
-@depends(valid_windows_sdk_dir, valid_ucrt_sdk_dir)
+@depends(valid_windows_sdk_dir, valid_ucrt_sdk_dir, host)
 @imports(_from='os', _import='environ')
-@imports('platform')
-def sdk_bin_path(valid_windows_sdk_dir, valid_ucrt_sdk_dir):
+def sdk_bin_path(valid_windows_sdk_dir, valid_ucrt_sdk_dir, host):
     if not valid_windows_sdk_dir:
         return
 
     vc_host = {
         'x86': 'x86',
-        'AMD64': 'x64',
-    }.get(platform.machine())
+        'x86_64': 'x64',
+    }.get(host.cpu)
 
     # From version 10.0.15063.0 onwards the bin path contains the version number.
     versioned_bin = ('bin' if valid_ucrt_sdk_dir.version < '10.0.15063.0'
                      else os.path.join('bin', str(valid_ucrt_sdk_dir.version)))
     result = [
         environ['PATH'],
         os.path.join(valid_windows_sdk_dir.path, versioned_bin, vc_host)
     ]
--- a/caps/nsScriptSecurityManager.cpp
+++ b/caps/nsScriptSecurityManager.cpp
@@ -393,17 +393,17 @@ NS_IMPL_ISUPPORTS(nsScriptSecurityManage
 
 ///////////////////////////////////////////////////
 // Methods implementing nsIScriptSecurityManager //
 ///////////////////////////////////////////////////
 
 ///////////////// Security Checks /////////////////
 
 bool nsScriptSecurityManager::ContentSecurityPolicyPermitsJSAction(
-    JSContext* cx, JS::HandleValue aValue) {
+    JSContext* cx, JS::HandleString aCode) {
   MOZ_ASSERT(cx == nsContentUtils::GetCurrentJSContext());
 
   // Get the window, if any, corresponding to the current global
   nsCOMPtr<nsIContentSecurityPolicy> csp;
   if (nsGlobalWindowInner* win = xpc::CurrentWindowOrNull(cx)) {
     csp = win->GetCsp();
   }
 
@@ -440,23 +440,17 @@ bool nsScriptSecurityManager::ContentSec
   nsresult rv = csp->GetAllowsEval(&reportViolation, &evalOK);
 
   // A little convoluted. We want the scriptSample for a) reporting a violation
   // or b) passing it to AssertEvalNotUsingSystemPrincipal or c) we're in the
   // parent process. So do the work to get it if either of those cases is true.
   nsAutoJSString scriptSample;
   if (reportViolation || subjectPrincipal->IsSystemPrincipal() ||
       XRE_IsE10sParentProcess()) {
-    JS::Rooted<JSString*> jsString(cx, JS::ToString(cx, aValue));
-    if (NS_WARN_IF(!jsString)) {
-      JS_ClearPendingException(cx);
-      return false;
-    }
-
-    if (NS_WARN_IF(!scriptSample.init(cx, jsString))) {
+    if (NS_WARN_IF(!scriptSample.init(cx, aCode))) {
       JS_ClearPendingException(cx);
       return false;
     }
   }
 
 #if !defined(ANDROID)
   if (!nsContentSecurityUtils::IsEvalAllowed(
           cx, subjectPrincipal->IsSystemPrincipal(), scriptSample)) {
--- a/caps/nsScriptSecurityManager.h
+++ b/caps/nsScriptSecurityManager.h
@@ -85,17 +85,17 @@ class nsScriptSecurityManager final : pu
 
  private:
   // GetScriptSecurityManager is the only call that can make one
   nsScriptSecurityManager();
   virtual ~nsScriptSecurityManager();
 
   // Decides, based on CSP, whether or not eval() and stuff can be executed.
   static bool ContentSecurityPolicyPermitsJSAction(JSContext* cx,
-                                                   JS::HandleValue aValue);
+                                                   JS::HandleString aCode);
 
   static bool JSPrincipalsSubsume(JSPrincipals* first, JSPrincipals* second);
 
   nsresult Init();
 
   nsresult InitPrefs();
 
   static void ScriptSecurityPrefChanged(const char* aPref, void* aSelf);
--- a/config/external/rlbox/rlbox_config.h
+++ b/config/external/rlbox/rlbox_config.h
@@ -10,26 +10,25 @@
 
 // RLBox uses c++17's shared_locks by default, even for the noop_sandbox
 // However c++17 shared_lock is not supported on macOS 10.9 to 10.11
 // Thus we use Firefox's shared lock implementation
 // This can be removed if macOS 10.9 to 10.11 support is dropped
 #  include "mozilla/RWLock.h"
 namespace rlbox {
 struct rlbox_shared_lock {
-  mozilla::RWLock rwlock;
-  rlbox_shared_lock() : rwlock("rlbox") {}
+  mozilla::detail::StaticRWLock rwlock;
 };
 }  // namespace rlbox
 #  define RLBOX_USE_CUSTOM_SHARED_LOCK
 #  define RLBOX_SHARED_LOCK(name) rlbox::rlbox_shared_lock name
 #  define RLBOX_ACQUIRE_SHARED_GUARD(name, ...) \
-    mozilla::AutoReadLock name((__VA_ARGS__).rwlock)
+    mozilla::detail::StaticAutoReadLock name((__VA_ARGS__).rwlock)
 #  define RLBOX_ACQUIRE_UNIQUE_GUARD(name, ...) \
-    mozilla::AutoWriteLock name((__VA_ARGS__).rwlock)
+    mozilla::detail::StaticAutoWriteLock name((__VA_ARGS__).rwlock)
 
 #endif
 
 // All uses are on the main thread right now, disable rlbox thread checks for
 // performance
 #define RLBOX_SINGLE_THREADED_INVOCATIONS
 
 // The MingW compiler does not correctly handle static thread_local inline
--- a/devtools/client/netmonitor/src/assets/styles/NetworkDetailsPanel.css
+++ b/devtools/client/netmonitor/src/assets/styles/NetworkDetailsPanel.css
@@ -19,16 +19,17 @@
   flex-direction: column;
   overflow-x: hidden;
   overflow-y: auto;
 }
 
 .network-monitor .panel-container .tree-container .objectBox {
   white-space: normal;
   word-wrap: break-word;
+  unicode-bidi: plaintext;
 }
 
 .network-monitor .properties-view {
   display: flex;
   flex-direction: column;
   flex-grow: 1;
 }
 
@@ -104,17 +105,17 @@
 }
 
 /* Make right td fill available horizontal space */
 .network-monitor .tree-container .treeTable td:last-child {
   width: 100%;
 }
 
 .network-monitor .tree-container .treeTable .tree-section,
-.network-monitor .properties-view .raw-headers-container  {
+.network-monitor .properties-view .raw-headers-container {
   width: 100%;
   background-color: var(--theme-toolbar-background);
 }
 
 .network-monitor .tree-container .treeTable tr.tree-section:not(:first-child) td:not([class=""]) {
   border-top: 1px solid var(--theme-splitter-color);
 }
 
@@ -198,18 +199,17 @@
 
 .network-monitor .tabpanel-summary-container {
   flex-wrap: wrap;
   padding: 1px;
   padding-inline-start: 4px;
 }
 
 .network-monitor .tabpanel-summary-container .tracking-resource {
-  margin-inline-start: -2px;
-  margin-inline-end: 2px;
+  margin-inline: -2px 2px;
   vertical-align: text-bottom;
   fill: var(--theme-icon-color);
 }
 
 .network-monitor .tabpanel-summary-labelvalue {
   white-space: pre-wrap;
 }
 
@@ -228,17 +228,17 @@
 }
 
 .network-monitor .tracking-protection {
   background-color: var(--toolbarbutton-background);
   color: inherit;
   margin-block: 2px;
 }
 
-.theme-dark .network-monitor .edit-and-resend-button  {
+.theme-dark .network-monitor .edit-and-resend-button {
   background-color: var(--toolbarbutton-background);
   color: var(--theme-selection-color);
 }
 
 .summary-edit-and-resend {
   display: flex;
   align-items: center;
   flex-wrap: wrap;
@@ -422,18 +422,17 @@
 
 .network-monitor .timings-overview-item {
   display: inline-flex;
 }
 
 .network-monitor .timings-overview-item:not(:first-of-type)::before {
   content: "";
   display: inline-flex;
-  margin-inline-end: 10px;
-  margin-inline-start: 10px;
+  margin-inline: 10px;
   width: 1px;
   background: var(--theme-splitter-color);
 }
 
 .network-monitor .timings-label {
   width: 10em;
 }
 
@@ -449,19 +448,19 @@
 
 .network-monitor .requests-list-timings-box {
   border: none;
   min-width: 1px;
   transition: width 0.2s ease-out;
 }
 
 .network-monitor .label-separator {
+  margin-block: 5px;
   margin-inline-start: 4px;
   font-weight: 600;
-  margin-block: 5px;
   color: var(--theme-comment);
 }
 
 .theme-light .network-monitor .requests-list-timings-box {
   --timing-server-color-1: rgba(104, 195, 179, 0.8); /* teal */
   --timing-server-color-2: rgba(123, 102, 167, 0.8); /* purple */
   --timing-server-color-3: rgba(233, 236, 130, 0.8); /* yellow */
   --timing-server-color-total: rgba(186, 90, 140, 0.8); /* pink */
--- a/devtools/client/netmonitor/src/assets/styles/RequestList.css
+++ b/devtools/client/netmonitor/src/assets/styles/RequestList.css
@@ -330,16 +330,17 @@
   margin-inline-end: 3px;
 }
 
 /* Waterfall column */
 
 .requests-list-waterfall {
   background-repeat: repeat-y;
   background-position: left center;
+  overflow: visible;
   /* Background created on a <canvas> in js. */
   /* @see devtools/client/netmonitor/src/widgets/WaterfallBackground.js */
   background-image: -moz-element(#waterfall-background);
 }
 
 .requests-list-waterfall:dir(rtl) {
   background-position: right center;
 }
@@ -390,30 +391,35 @@
 }
 
 .requests-list-timings-division[data-division-scale=second],
 .requests-list-timings-division[data-division-scale=minute] {
   font-weight: 600;
 }
 
 .requests-list-timings {
-  transform: scaleX(var(--timings-scale));
+  display: flex;
+  align-items: center;
 }
 
 .requests-list-timings:dir(ltr) {
   transform-origin: left center;
 }
 
 .requests-list-timings:dir(rtl) {
   transform-origin: right center;
 }
 
 .requests-list-timings-box {
   display: inline-block;
-  height: 9px;
+  height: 12px;
+}
+
+.requests-list-timings-box.filler {
+  background-color: var(--theme-splitter-color);
 }
 
 .requests-list-timings-box.blocked {
   background-color: var(--timing-blocked-color);
 }
 
 .requests-list-timings-box.dns {
   background-color: var(--timing-dns-color);
@@ -437,21 +443,19 @@
 
 .requests-list-timings-box.receive {
   background-color: var(--timing-receive-color);
 }
 
 .requests-list-timings-total {
   display: inline-block;
   padding-inline-start: 4px;
-  font-size: 85%;
+  font-size: 80%;
   font-weight: 600;
   white-space: nowrap;
-  /* This node should not be scaled - apply a reversed transformation */
-  transform: scaleX(var(--timings-rev-scale));
   text-align: left;
 }
 
 .requests-list-timings-total:dir(ltr) {
   transform-origin: left center;
 }
 
 .requests-list-timings-total:dir(rtl) {
--- a/devtools/client/netmonitor/src/components/request-list/RequestListColumnWaterfall.js
+++ b/devtools/client/netmonitor/src/components/request-list/RequestListColumnWaterfall.js
@@ -2,78 +2,93 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { Component } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const {
+  connect,
+} = require("devtools/client/shared/redux/visibility-handler-connect");
+const {
+  getWaterfallScale,
+} = require("devtools/client/netmonitor/src/selectors/index");
 
 const { L10N } = require("devtools/client/netmonitor/src/utils/l10n");
 const {
   fetchNetworkUpdatePacket,
   propertiesEqual,
 } = require("devtools/client/netmonitor/src/utils/request-utils");
 
 // List of properties of the timing info we want to create boxes for
 const { TIMING_KEYS } = require("devtools/client/netmonitor/src/constants");
 
 const { div } = dom;
 
+const UPDATED_WATERFALL_ITEM_PROPS = ["eventTimings", "totalTime"];
 const UPDATED_WATERFALL_PROPS = [
-  "eventTimings",
-  "fromCache",
-  "fromServiceWorker",
-  "totalTime",
+  "item",
+  "firstRequestStartedMs",
+  "scale",
+  "isVisible",
 ];
 
 class RequestListColumnWaterfall extends Component {
   static get propTypes() {
     return {
       connector: PropTypes.object.isRequired,
       firstRequestStartedMs: PropTypes.number.isRequired,
       item: PropTypes.object.isRequired,
       onWaterfallMouseDown: PropTypes.func.isRequired,
+      scale: PropTypes.number,
+      isVisible: PropTypes.bool.isRequired,
     };
   }
 
+  constructor() {
+    super();
+    this.handleMouseOver = this.handleMouseOver.bind(this);
+  }
+
   componentDidMount() {
     const { connector, item } = this.props;
     fetchNetworkUpdatePacket(connector.requestData, item, ["eventTimings"]);
   }
 
   componentWillReceiveProps(nextProps) {
-    const { connector, item } = nextProps;
-    fetchNetworkUpdatePacket(connector.requestData, item, ["eventTimings"]);
+    if (nextProps.isVisible && nextProps.item.totalTime) {
+      const { connector, item } = nextProps;
+      fetchNetworkUpdatePacket(connector.requestData, item, ["eventTimings"]);
+    }
   }
 
   shouldComponentUpdate(nextProps) {
     return (
-      !propertiesEqual(
-        UPDATED_WATERFALL_PROPS,
-        this.props.item,
-        nextProps.item
-      ) || this.props.firstRequestStartedMs !== nextProps.firstRequestStartedMs
+      nextProps.isVisible &&
+      (!propertiesEqual(UPDATED_WATERFALL_PROPS, this.props, nextProps) ||
+        !propertiesEqual(
+          UPDATED_WATERFALL_ITEM_PROPS,
+          this.props.item,
+          nextProps.item
+        ))
     );
   }
 
+  handleMouseOver({ target }) {
+    if (!target.title) {
+      target.title = this.timingTooltip();
+    }
+  }
+
   timingTooltip() {
-    const {
-      eventTimings,
-      fromCache,
-      fromServiceWorker,
-      totalTime,
-    } = this.props.item;
+    const { eventTimings, totalTime } = this.props.item;
     const tooltip = [];
 
-    if (fromCache || fromServiceWorker) {
-      return tooltip;
-    }
-
     if (eventTimings) {
       for (const key of TIMING_KEYS) {
         const width = eventTimings.timings[key];
 
         if (width > 0) {
           tooltip.push(
             L10N.getFormatStr("netmonitor.waterfall.tooltip." + key, width)
           );
@@ -87,82 +102,100 @@ class RequestListColumnWaterfall extends
       );
     }
 
     return tooltip.join(L10N.getStr("netmonitor.waterfall.tooltip.separator"));
   }
 
   timingBoxes() {
     const {
-      eventTimings,
-      fromCache,
-      fromServiceWorker,
-      totalTime,
-    } = this.props.item;
+      scale,
+      item: { eventTimings, totalTime },
+    } = this.props;
     const boxes = [];
 
-    if (fromCache || fromServiceWorker) {
-      return boxes;
-    }
-
-    if (eventTimings) {
-      // Add a set of boxes representing timing information.
-      for (const key of TIMING_KEYS) {
-        const width = eventTimings.timings[key];
+    // Physical pixel as minimum size
+    const minPixel = 1 / window.devicePixelRatio;
 
-        // Don't render anything if it surely won't be visible.
-        // One millisecond == one unscaled pixel.
-        if (width > 0) {
-          boxes.push(
-            div({
-              key,
-              className: `requests-list-timings-box ${key}`,
-              style: { width },
-            })
-          );
+    if (typeof totalTime === "number") {
+      if (eventTimings) {
+        // Add a set of boxes representing timing information.
+        for (const key of TIMING_KEYS) {
+          if (eventTimings.timings[key] > 0) {
+            boxes.push(
+              div({
+                key,
+                className: `requests-list-timings-box ${key}`,
+                style: {
+                  width: Math.max(eventTimings.timings[key] * scale, minPixel),
+                },
+              })
+            );
+          }
         }
       }
-    }
+      // Minimal box to at least show start and total time
+      if (!boxes.length) {
+        boxes.push(
+          div({
+            className: "requests-list-timings-box filler",
+            key: "filler",
+            style: { width: Math.max(totalTime * scale, minPixel) },
+          })
+        );
+      }
 
-    if (typeof totalTime === "number") {
       const title = L10N.getFormatStr("networkMenu.totalMS2", totalTime);
       boxes.push(
         div(
           {
             key: "total",
             className: "requests-list-timings-total",
             title,
           },
           title
         )
       );
+    } else {
+      // Pending requests are marked for start time
+      boxes.push(
+        div({
+          className: "requests-list-timings-box filler",
+          key: "pending",
+          style: { width: minPixel },
+        })
+      );
     }
 
     return boxes;
   }
 
   render() {
-    const { firstRequestStartedMs, item, onWaterfallMouseDown } = this.props;
+    const {
+      firstRequestStartedMs,
+      item: { startedMs },
+      scale,
+      onWaterfallMouseDown,
+    } = this.props;
 
     return dom.td(
       {
         className: "requests-list-column requests-list-waterfall",
-        onMouseOver: ({ target }) => {
-          if (!target.title) {
-            target.title = this.timingTooltip();
-          }
-        },
+        onMouseOver: this.handeMouseOver,
       },
       div(
         {
           className: "requests-list-timings",
           style: {
-            paddingInlineStart: `${item.startedMs - firstRequestStartedMs}px`,
+            paddingInlineStart: `${(startedMs - firstRequestStartedMs) *
+              scale}px`,
           },
           onMouseDown: onWaterfallMouseDown,
         },
         this.timingBoxes()
       )
     );
   }
 }
 
-module.exports = RequestListColumnWaterfall;
+module.exports = connect(state => ({
+  scale: getWaterfallScale(state),
+}))(RequestListColumnWaterfall);
--- a/devtools/client/netmonitor/src/components/request-list/RequestListContent.js
+++ b/devtools/client/netmonitor/src/components/request-list/RequestListContent.js
@@ -20,17 +20,16 @@ const {
 const Actions = require("devtools/client/netmonitor/src/actions/index");
 const {
   formDataURI,
 } = require("devtools/client/netmonitor/src/utils/request-utils");
 const {
   getDisplayedRequests,
   getColumns,
   getSelectedRequest,
-  getWaterfallScale,
 } = require("devtools/client/netmonitor/src/selectors/index");
 
 loader.lazyRequireGetter(
   this,
   "openRequestInTab",
   "devtools/client/netmonitor/src/utils/firefox/open-request-in-tab",
   true
 );
@@ -86,17 +85,16 @@ class RequestListContent extends Compone
       onItemMouseDown: PropTypes.func.isRequired,
       onSecurityIconMouseDown: PropTypes.func.isRequired,
       onSelectDelta: PropTypes.func.isRequired,
       onWaterfallMouseDown: PropTypes.func.isRequired,
       openStatistics: PropTypes.func.isRequired,
       openRequestBlockingAndAddUrl: PropTypes.func.isRequired,
       openRequestBlockingAndDisableUrls: PropTypes.func.isRequired,
       removeBlockedUrl: PropTypes.func.isRequired,
-      scale: PropTypes.number,
       selectRequest: PropTypes.func.isRequired,
       selectedRequest: PropTypes.object,
       requestFilterTypes: PropTypes.object.isRequired,
     };
   }
 
   constructor(props) {
     super(props);
@@ -104,32 +102,48 @@ class RequestListContent extends Compone
     this.onScroll = this.onScroll.bind(this);
     this.onResize = this.onResize.bind(this);
     this.onKeyDown = this.onKeyDown.bind(this);
     this.openRequestInTab = this.openRequestInTab.bind(this);
     this.onDoubleClick = this.onDoubleClick.bind(this);
     this.onContextMenu = this.onContextMenu.bind(this);
     this.onMouseDown = this.onMouseDown.bind(this);
     this.hasOverflow = false;
+    this.onIntersect = this.onIntersect.bind(this);
+    this.intersectionObserver = null;
+    this.state = {
+      onscreenItems: new Set(),
+    };
   }
 
   componentWillMount() {
     this.tooltip = new HTMLTooltip(window.parent.document, { type: "arrow" });
     window.addEventListener("resize", this.onResize);
   }
 
   componentDidMount() {
     // Install event handler for displaying a tooltip
     this.tooltip.startTogglingOnHover(this.refs.scrollEl, this.onHover, {
       toggleDelay: REQUESTS_TOOLTIP_TOGGLE_DELAY,
       interactive: true,
     });
     // Install event handler to hide the tooltip on scroll
     this.refs.scrollEl.addEventListener("scroll", this.onScroll, true);
     this.onResize();
+    this.intersectionObserver = new IntersectionObserver(this.onIntersect, {
+      root: this.refs.scrollEl,
+      // Render 10% more columns for a scrolling headstart
+      rootMargin: "10%",
+    });
+    // Prime IntersectionObserver with existing entries
+    for (const item of this.refs.scrollEl.querySelectorAll(
+      ".request-list-item"
+    )) {
+      this.intersectionObserver.observe(item);
+    }
   }
 
   componentDidUpdate(prevProps) {
     const output = this.refs.scrollEl;
     if (!this.hasOverflow && output.scrollHeight > output.clientHeight) {
       output.scrollTop = output.scrollHeight;
       this.hasOverflow = true;
     }
@@ -143,28 +157,56 @@ class RequestListContent extends Compone
   }
 
   componentWillUnmount() {
     this.refs.scrollEl.removeEventListener("scroll", this.onScroll, true);
 
     // Uninstall the tooltip event handler
     this.tooltip.stopTogglingOnHover();
     window.removeEventListener("resize", this.onResize);
+    this.intersectionObserver.disconnect();
+    this.intersectionObserver = null;
   }
 
   /*
    * Removing onResize() method causes perf regression - too many repaints of the panel.
    * So it is needed in ComponentDidMount and ComponentDidUpdate. See Bug 1532914.
    */
   onResize() {
     const parent = this.refs.scrollEl.parentNode;
     this.refs.scrollEl.style.width = parent.offsetWidth + "px";
     this.refs.scrollEl.style.height = parent.offsetHeight + "px";
   }
 
+  onIntersect(entries) {
+    // Track when off screen elements moved on screen to ensure updates
+    let onscreenDidChange = false;
+    const onscreenItems = new Set(this.state.onscreenItems);
+    for (const { target, isIntersecting } of entries) {
+      const id = target.dataset.id;
+      if (isIntersecting) {
+        if (onscreenItems.add(id)) {
+          onscreenDidChange = true;
+        }
+      } else {
+        onscreenItems.delete(id);
+      }
+    }
+    if (onscreenDidChange) {
+      // Remove ids that are no longer displayed
+      const itemIds = new Set(this.props.displayedRequests.map(({ id }) => id));
+      for (const id of onscreenItems) {
+        if (!itemIds.has(id)) {
+          onscreenItems.delete(id);
+        }
+      }
+      this.setState({ onscreenItems });
+    }
+  }
+
   /**
    * The predicate used when deciding whether a popup should be shown
    * over a request item or not.
    *
    * @param Node target
    *        The element node currently being hovered.
    * @param object tooltip
    *        The current tooltip instance.
@@ -316,20 +358,20 @@ class RequestListContent extends Compone
       connector,
       columns,
       displayedRequests,
       firstRequestStartedMs,
       onCauseBadgeMouseDown,
       onSecurityIconMouseDown,
       onWaterfallMouseDown,
       requestFilterTypes,
-      scale,
       selectedRequest,
       openRequestBlockingAndAddUrl,
       openRequestBlockingAndDisableUrls,
+      networkDetailsOpen,
     } = this.props;
 
     return div(
       {
         ref: "scrollEl",
         className: "requests-list-scroll",
       },
       [
@@ -340,50 +382,46 @@ class RequestListContent extends Compone
           },
           RequestListHeader(),
           dom.tbody(
             {
               ref: "rowGroupEl",
               className: "requests-list-row-group",
               tabIndex: 0,
               onKeyDown: this.onKeyDown,
-              style: {
-                "--timings-scale": scale,
-                "--timings-rev-scale": 1 / scale,
-              },
             },
-            displayedRequests.map((item, index) =>
-              RequestListItem({
+            displayedRequests.map((item, index) => {
+              return RequestListItem({
                 blocked: !!item.blockedReason,
                 firstRequestStartedMs,
                 fromCache: item.status === "304" || item.fromCache,
-                networkDetailsOpen: this.props.networkDetailsOpen,
+                networkDetailsOpen,
                 connector,
                 columns,
                 item,
                 index,
                 isSelected: item.id === (selectedRequest && selectedRequest.id),
+                isVisible: this.state.onscreenItems.has(item.id),
                 key: item.id,
+                intersectionObserver: this.intersectionObserver,
                 onContextMenu: this.onContextMenu,
                 onDoubleClick: () => this.onDoubleClick(item),
                 onMouseDown: evt =>
                   this.onMouseDown(evt, item.id, item.channelId),
                 onCauseBadgeMouseDown: () => onCauseBadgeMouseDown(item.cause),
                 onSecurityIconMouseDown: () =>
                   onSecurityIconMouseDown(item.securityState),
-                onWaterfallMouseDown: () => onWaterfallMouseDown(),
+                onWaterfallMouseDown: onWaterfallMouseDown,
                 requestFilterTypes,
-                openRequestBlockingAndAddUrl: url =>
-                  openRequestBlockingAndAddUrl(url),
-                openRequestBlockingAndDisableUrls: url =>
-                  openRequestBlockingAndDisableUrls(url),
-              })
-            )
-          ) // end of requests-list-row-group">
-        ),
+                openRequestBlockingAndAddUrl: openRequestBlockingAndAddUrl,
+                openRequestBlockingAndDisableUrls: openRequestBlockingAndDisableUrls,
+              });
+            })
+          )
+        ), // end of requests-list-row-group">
         dom.div({
           className: "requests-list-anchor",
           key: "anchor",
         }),
       ]
     );
   }
 }
@@ -396,17 +434,16 @@ module.exports = connect(
     columns: getColumns(state),
     networkDetailsOpen: state.ui.networkDetailsOpen,
     networkDetailsWidth: state.ui.networkDetailsWidth,
     networkDetailsHeight: state.ui.networkDetailsHeight,
     clickedRequest: state.requests.clickedRequest,
     displayedRequests: getDisplayedRequests(state),
     firstRequestStartedMs: state.requests.firstStartedMs,
     selectedRequest: getSelectedRequest(state),
-    scale: getWaterfallScale(state),
     requestFilterTypes: state.filters.requestFilterTypes,
   }),
   (dispatch, props) => ({
     cloneRequest: id => dispatch(Actions.cloneRequest(id)),
     openDetailsPanelTab: () => dispatch(Actions.openNetworkDetails(true)),
     sendCustomRequest: () =>
       dispatch(Actions.sendCustomRequest(props.connector)),
     openStatistics: open =>
--- a/devtools/client/netmonitor/src/components/request-list/RequestListItem.js
+++ b/devtools/client/netmonitor/src/components/request-list/RequestListItem.js
@@ -154,18 +154,18 @@ const UPDATED_REQ_ITEM_PROPS = [
   "responseHeaders",
 ];
 
 const UPDATED_REQ_PROPS = [
   "firstRequestStartedMs",
   "index",
   "networkDetailsOpen",
   "isSelected",
+  "isVisible",
   "requestFilterTypes",
-  "waterfallWidth",
 ];
 
 /**
  * Used by render: renders the given ColumnComponent if the flag for this column
  * is set in the columns prop. The list of props are used to determine which of
  * RequestListItem's need to be passed to the ColumnComponent. Any objects contained
  * in that list are passed as props verbatim.
  */
@@ -238,35 +238,39 @@ class RequestListItem extends Component 
   static get propTypes() {
     return {
       blocked: PropTypes.bool,
       connector: PropTypes.object.isRequired,
       columns: PropTypes.object.isRequired,
       item: PropTypes.object.isRequired,
       index: PropTypes.number.isRequired,
       isSelected: PropTypes.bool.isRequired,
+      isVisible: PropTypes.bool.isRequired,
       firstRequestStartedMs: PropTypes.number.isRequired,
       fromCache: PropTypes.bool,
       networkDetailsOpen: PropTypes.bool,
       onCauseBadgeMouseDown: PropTypes.func.isRequired,
       onDoubleClick: PropTypes.func.isRequired,
       onContextMenu: PropTypes.func.isRequired,
       onFocusedNodeChange: PropTypes.func,
       onMouseDown: PropTypes.func.isRequired,
       onSecurityIconMouseDown: PropTypes.func.isRequired,
       onWaterfallMouseDown: PropTypes.func.isRequired,
       requestFilterTypes: PropTypes.object.isRequired,
-      waterfallWidth: PropTypes.number,
+      intersectionObserver: PropTypes.object,
     };
   }
 
   componentDidMount() {
     if (this.props.isSelected) {
       this.refs.listItem.focus();
     }
+    if (this.props.intersectionObserver) {
+      this.props.intersectionObserver.observe(this.refs.listItem);
+    }
 
     const { connector, item, requestFilterTypes } = this.props;
     // Filtering XHR & WS require to lazily fetch requestHeaders & responseHeaders
     if (requestFilterTypes.xhr || requestFilterTypes.ws) {
       fetchNetworkUpdatePacket(connector.requestData, item, [
         "requestHeaders",
         "responseHeaders",
       ]);
@@ -300,24 +304,31 @@ class RequestListItem extends Component 
     if (!prevProps.isSelected && this.props.isSelected) {
       this.refs.listItem.focus();
       if (this.props.onFocusedNodeChange) {
         this.props.onFocusedNodeChange();
       }
     }
   }
 
+  componentWillUnmount() {
+    if (this.props.intersectionObserver) {
+      this.props.intersectionObserver.unobserve(this.refs.listItem);
+    }
+  }
+
   render() {
     const {
       blocked,
       connector,
       columns,
       item,
       index,
       isSelected,
+      isVisible,
       firstRequestStartedMs,
       fromCache,
       onDoubleClick,
       onContextMenu,
       onMouseDown,
       onWaterfallMouseDown,
     } = this.props;
 
@@ -332,45 +343,44 @@ class RequestListItem extends Component 
         className: classList.join(" "),
         "data-id": item.id,
         tabIndex: 0,
         onContextMenu,
         onMouseDown,
         onDoubleClick,
       },
       ...COLUMN_COMPONENTS.filter(({ column }) => columns[column]).map(
-        ({ column, ColumnComponent, props: columnProps }) =>
-          column &&
-          ColumnComponent({
+        ({ column, ColumnComponent, props: columnProps }) => {
+          return ColumnComponent({
+            key: column,
             item,
-            ...(columnProps || []).reduce(
-              (acc, keyOrObject) => {
-                if (typeof keyOrObject == "string") {
-                  acc[keyOrObject] = this.props[keyOrObject];
-                } else {
-                  Object.assign(acc, keyOrObject);
-                }
-                return acc;
-              },
-              { item }
-            ),
-          })
+            ...(columnProps || []).reduce((acc, keyOrObject) => {
+              if (typeof keyOrObject == "string") {
+                acc[keyOrObject] = this.props[keyOrObject];
+              } else {
+                Object.assign(acc, keyOrObject);
+              }
+              return acc;
+            }, {}),
+          });
+        }
       ),
       ...RESPONSE_HEADERS.filter(header => columns[header]).map(header =>
         RequestListColumnResponseHeader({
           connector,
           item,
           header,
         })
       ),
       // The last column is Waterfall (aka Timeline)
       columns.waterfall &&
         RequestListColumnWaterfall({
           connector,
           firstRequestStartedMs,
           item,
           onWaterfallMouseDown,
+          isVisible,
         })
     );
   }
 }
 
 module.exports = RequestListItem;
--- a/devtools/client/netmonitor/src/selectors/ui.js
+++ b/devtools/client/netmonitor/src/selectors/ui.js
@@ -7,36 +7,43 @@
 const { createSelector } = require("devtools/client/shared/vendor/reselect");
 const {
   REQUESTS_WATERFALL,
 } = require("devtools/client/netmonitor/src/constants");
 
 const EPSILON = 0.001;
 
 const getWaterfallScale = createSelector(
-  state => state.requests,
-  state => state.timingMarkers,
-  state => state.ui,
-  (requests, timingMarkers, ui) => {
-    if (requests.firstStartedMs === +Infinity || ui.waterfallWidth === null) {
+  state => state.requests.firstStartedMs,
+  state => state.requests.lastEndedMs,
+  state => state.timingMarkers.firstDocumentDOMContentLoadedTimestamp,
+  state => state.timingMarkers.firstDocumentLoadTimestamp,
+  state => state.ui.waterfallWidth,
+  (
+    firstStartedMs,
+    lastEndedMs,
+    firstDocumentDOMContentLoadedTimestamp,
+    firstDocumentLoadTimestamp,
+    waterfallWidth
+  ) => {
+    if (firstStartedMs === +Infinity || waterfallWidth === null) {
       return null;
     }
 
     const lastEventMs = Math.max(
-      requests.lastEndedMs,
-      timingMarkers.firstDocumentDOMContentLoadedTimestamp,
-      timingMarkers.firstDocumentLoadTimestamp
+      lastEndedMs,
+      firstDocumentDOMContentLoadedTimestamp,
+      firstDocumentLoadTimestamp
     );
-    const longestWidth = lastEventMs - requests.firstStartedMs;
+    const longestWidth = lastEventMs - firstStartedMs;
 
     // Reduce 20px for the last request's requests-list-timings-total
     return Math.min(
       Math.max(
-        (ui.waterfallWidth - REQUESTS_WATERFALL.LABEL_WIDTH - 20) /
-          longestWidth,
+        (waterfallWidth - REQUESTS_WATERFALL.LABEL_WIDTH - 20) / longestWidth,
         EPSILON
       ),
       1
     );
   }
 );
 
 function getVisibleColumns(columns) {
--- a/devtools/client/netmonitor/test/browser_net_brotli.js
+++ b/devtools/client/netmonitor/test/browser_net_brotli.js
@@ -23,19 +23,21 @@ add_task(async function() {
   );
 
   store.dispatch(Actions.batchEnable(false));
 
   // Execute requests.
   await performRequests(monitor, tab, BROTLI_REQUESTS);
 
   const requestItem = document.querySelector(".request-list-item");
+  // Status code title is generated on hover
   const requestsListStatus = requestItem.querySelector(".status-code");
   EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
   await waitUntil(() => requestsListStatus.title);
+  await waitForDOMIfNeeded(requestItem, ".requests-list-timings-total");
 
   verifyRequestItemTarget(
     document,
     getDisplayedRequests(store.getState()),
     getSortedRequests(store.getState())[0],
     "GET",
     HTTPS_CONTENT_TYPE_SJS + "?fmt=br",
     {
--- a/devtools/client/netmonitor/test/browser_net_cached-status.js
+++ b/devtools/client/netmonitor/test/browser_net_cached-status.js
@@ -95,16 +95,17 @@ add_task(async function() {
 
   let index = 0;
   for (const request of REQUEST_DATA) {
     const requestItem = document.querySelectorAll(".request-list-item")[index];
     requestItem.scrollIntoView();
     const requestsListStatus = requestItem.querySelector(".status-code");
     EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
     await waitUntil(() => requestsListStatus.title);
+    await waitForDOMIfNeeded(requestItem, ".requests-list-timings-total");
 
     info("Verifying request #" + index);
     await verifyRequestItemTarget(
       document,
       getDisplayedRequests(store.getState()),
       getSortedRequests(store.getState())[index],
       request.method,
       request.uri,
--- a/devtools/client/netmonitor/test/browser_net_content-type.js
+++ b/devtools/client/netmonitor/test/browser_net_content-type.js
@@ -23,16 +23,17 @@ add_task(async function() {
   // Execute requests.
   await performRequests(monitor, tab, CONTENT_TYPE_WITHOUT_CACHE_REQUESTS);
 
   for (const requestItem of document.querySelectorAll(".request-list-item")) {
     const requestsListStatus = requestItem.querySelector(".status-code");
     requestItem.scrollIntoView();
     EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
     await waitUntil(() => requestsListStatus.title);
+    await waitForDOMIfNeeded(requestItem, ".requests-list-timings-total");
   }
 
   verifyRequestItemTarget(
     document,
     getDisplayedRequests(store.getState()),
     getSortedRequests(store.getState())[0],
     "GET",
     CONTENT_TYPE_SJS + "?fmt=xml",
--- a/devtools/client/netmonitor/test/browser_net_cyrillic-01.js
+++ b/devtools/client/netmonitor/test/browser_net_cyrillic-01.js
@@ -22,16 +22,17 @@ add_task(async function() {
   // Execute requests.
   await performRequests(monitor, tab, 1);
 
   const requestItem = document.querySelectorAll(".request-list-item")[0];
   const requestsListStatus = requestItem.querySelector(".status-code");
   requestItem.scrollIntoView();
   EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
   await waitUntil(() => requestsListStatus.title);
+  await waitForDOMIfNeeded(requestItem, ".requests-list-timings-total");
 
   verifyRequestItemTarget(
     document,
     getDisplayedRequests(store.getState()),
     getSortedRequests(store.getState())[0],
     "GET",
     CONTENT_TYPE_SJS + "?fmt=txt",
     {
--- a/devtools/client/netmonitor/test/browser_net_cyrillic-02.js
+++ b/devtools/client/netmonitor/test/browser_net_cyrillic-02.js
@@ -21,16 +21,17 @@ add_task(async function() {
   tab.linkedBrowser.reload();
   await wait;
 
   const requestItem = document.querySelectorAll(".request-list-item")[0];
   const requestsListStatus = requestItem.querySelector(".status-code");
   requestItem.scrollIntoView();
   EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
   await waitUntil(() => requestsListStatus.title);
+  await waitForDOMIfNeeded(requestItem, ".requests-list-timings-total");
 
   verifyRequestItemTarget(
     document,
     getDisplayedRequests(store.getState()),
     getSortedRequests(store.getState())[0],
     "GET",
     CYRILLIC_URL,
     {
--- a/devtools/client/netmonitor/test/browser_net_filter-flags.js
+++ b/devtools/client/netmonitor/test/browser_net_filter-flags.js
@@ -415,16 +415,17 @@ add_task(async function() {
 
     // Fake mouse over the status column only after the list is fully updated
     const requestItems = document.querySelectorAll(".request-list-item");
     for (const requestItem of requestItems) {
       requestItem.scrollIntoView();
       const requestsListStatus = requestItem.querySelector(".status-code");
       EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
       await waitUntil(() => requestsListStatus.title);
+      await waitForDOMIfNeeded(requestItem, ".requests-list-timings-total");
     }
 
     for (let i = 0; i < visibility.length; i++) {
       const shouldBeVisible = !!visibility[i];
 
       if (shouldBeVisible) {
         const { method, url, data } = EXPECTED_REQUESTS[i];
         verifyRequestItemTarget(
--- a/devtools/client/netmonitor/test/browser_net_json-long.js
+++ b/devtools/client/netmonitor/test/browser_net_json-long.js
@@ -27,16 +27,17 @@ add_task(async function() {
 
   // Execute requests.
   await performRequests(monitor, tab, 1);
 
   const requestItem = document.querySelector(".request-list-item");
   const requestsListStatus = requestItem.querySelector(".status-code");
   EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
   await waitUntil(() => requestsListStatus.title);
+  await waitForDOMIfNeeded(requestItem, ".requests-list-timings-total");
 
   verifyRequestItemTarget(
     document,
     getDisplayedRequests(store.getState()),
     getSortedRequests(store.getState())[0],
     "GET",
     CONTENT_TYPE_SJS + "?fmt=json-long",
     {
--- a/devtools/client/netmonitor/test/browser_net_json-malformed.js
+++ b/devtools/client/netmonitor/test/browser_net_json-malformed.js
@@ -22,16 +22,17 @@ add_task(async function() {
 
   // Execute requests.
   await performRequests(monitor, tab, 1);
 
   const requestItem = document.querySelector(".request-list-item");
   const requestsListStatus = requestItem.querySelector(".status-code");
   EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
   await waitUntil(() => requestsListStatus.title);
+  await waitForDOMIfNeeded(requestItem, ".requests-list-timings-total");
 
   verifyRequestItemTarget(
     document,
     getDisplayedRequests(store.getState()),
     getSortedRequests(store.getState())[0],
     "GET",
     CONTENT_TYPE_SJS + "?fmt=json-malformed",
     {
--- a/devtools/client/netmonitor/test/browser_net_json_custom_mime.js
+++ b/devtools/client/netmonitor/test/browser_net_json_custom_mime.js
@@ -22,16 +22,17 @@ add_task(async function() {
 
   // Execute requests.
   await performRequests(monitor, tab, 1);
 
   const requestItem = document.querySelector(".request-list-item");
   const requestsListStatus = requestItem.querySelector(".status-code");
   EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
   await waitUntil(() => requestsListStatus.title);
+  await waitForDOMIfNeeded(requestItem, ".requests-list-timings-total");
 
   verifyRequestItemTarget(
     document,
     getDisplayedRequests(store.getState()),
     getSortedRequests(store.getState())[0],
     "GET",
     CONTENT_TYPE_SJS + "?fmt=json-custom-mime",
     {
--- a/devtools/client/netmonitor/test/browser_net_json_text_mime.js
+++ b/devtools/client/netmonitor/test/browser_net_json_text_mime.js
@@ -23,16 +23,17 @@ add_task(async function() {
 
   // Execute requests.
   await performRequests(monitor, tab, 1);
 
   const requestItem = document.querySelector(".request-list-item");
   const requestsListStatus = requestItem.querySelector(".status-code");
   EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
   await waitUntil(() => requestsListStatus.title);
+  await waitForDOMIfNeeded(requestItem, ".requests-list-timings-total");
 
   verifyRequestItemTarget(
     document,
     getDisplayedRequests(store.getState()),
     getSortedRequests(store.getState())[0],
     "GET",
     CONTENT_TYPE_SJS + "?fmt=json-text-mime",
     {
--- a/devtools/client/netmonitor/test/browser_net_jsonp.js
+++ b/devtools/client/netmonitor/test/browser_net_jsonp.js
@@ -25,16 +25,17 @@ add_task(async function() {
   await performRequests(monitor, tab, 2);
 
   const requestItems = document.querySelectorAll(".request-list-item");
   for (const requestItem of requestItems) {
     requestItem.scrollIntoView();
     const requestsListStatus = requestItem.querySelector(".status-code");
     EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
     await waitUntil(() => requestsListStatus.title);
+    await waitForDOMIfNeeded(requestItem, ".requests-list-timings-total");
   }
 
   verifyRequestItemTarget(
     document,
     getDisplayedRequests(store.getState()),
     getSortedRequests(store.getState())[0],
     "GET",
     CONTENT_TYPE_SJS + "?fmt=jsonp&jsonp=$_0123Fun",
--- a/devtools/client/netmonitor/test/browser_net_large-response.js
+++ b/devtools/client/netmonitor/test/browser_net_large-response.js
@@ -33,16 +33,17 @@ add_task(async function() {
   });
   await wait;
 
   const requestItem = document.querySelector(".request-list-item");
   requestItem.scrollIntoView();
   const requestsListStatus = requestItem.querySelector(".status-code");
   EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
   await waitUntil(() => requestsListStatus.title);
+  await waitForDOMIfNeeded(requestItem, ".requests-list-timings-total");
 
   verifyRequestItemTarget(
     document,
     getDisplayedRequests(store.getState()),
     getSortedRequests(store.getState())[0],
     "GET",
     CONTENT_TYPE_SJS + "?fmt=html-long",
     {
--- a/devtools/client/netmonitor/test/browser_net_post-data-01.js
+++ b/devtools/client/netmonitor/test/browser_net_post-data-01.js
@@ -27,16 +27,17 @@ add_task(async function() {
   await performRequests(monitor, tab, 2);
 
   const requestItems = document.querySelectorAll(".request-list-item");
   for (const requestItem of requestItems) {
     requestItem.scrollIntoView();
     const requestsListStatus = requestItem.querySelector(".status-code");
     EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
     await waitUntil(() => requestsListStatus.title);
+    await waitForDOMIfNeeded(requestItem, ".requests-list-timings-total");
   }
 
   verifyRequestItemTarget(
     document,
     getDisplayedRequests(store.getState()),
     getSortedRequests(store.getState())[0],
     "POST",
     SIMPLE_SJS + "?foo=bar&baz=42&type=urlencoded",
--- a/devtools/client/netmonitor/test/browser_net_req-resp-bodies.js
+++ b/devtools/client/netmonitor/test/browser_net_req-resp-bodies.js
@@ -50,16 +50,17 @@ add_task(async function() {
 
   async function verifyRequest(index) {
     const requestItems = document.querySelectorAll(".request-list-item");
     for (const requestItem of requestItems) {
       requestItem.scrollIntoView();
       const requestsListStatus = requestItem.querySelector(".status-code");
       EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
       await waitUntil(() => requestsListStatus.title);
+      await waitForDOMIfNeeded(requestItem, ".requests-list-timings-total");
     }
     verifyRequestItemTarget(
       document,
       getDisplayedRequests(store.getState()),
       getSortedRequests(store.getState())[index],
       "GET",
       CONTENT_TYPE_SJS + "?fmt=json-long",
       {
--- a/devtools/client/netmonitor/test/browser_net_service-worker-status.js
+++ b/devtools/client/netmonitor/test/browser_net_service-worker-status.js
@@ -58,16 +58,17 @@ add_task(async function() {
   );
 
   const requestItems = document.querySelectorAll(".request-list-item");
   for (const requestItem of requestItems) {
     requestItem.scrollIntoView();
     const requestsListStatus = requestItem.querySelector(".status-code");
     EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
     await waitUntil(() => requestsListStatus.title);
+    await waitForDOMIfNeeded(requestItem, ".requests-list-timings-total");
   }
 
   let index = 0;
   for (const request of REQUEST_DATA) {
     const item = getSortedRequests(store.getState())[index];
 
     info(`Verifying request #${index}`);
     await verifyRequestItemTarget(
--- a/devtools/client/netmonitor/test/browser_net_simple-request-data.js
+++ b/devtools/client/netmonitor/test/browser_net_simple-request-data.js
@@ -303,16 +303,17 @@ function test() {
         "The headersSize data has an incorrect value."
       );
 
       const requestListItem = document.querySelector(".request-list-item");
       requestListItem.scrollIntoView();
       const requestsListStatus = requestListItem.querySelector(".status-code");
       EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
       await waitUntil(() => requestsListStatus.title);
+      await waitForDOMIfNeeded(requestListItem, ".requests-list-timings-total");
 
       verifyRequestItemTarget(
         document,
         getDisplayedRequests(store.getState()),
         requestItem,
         "GET",
         SIMPLE_SJS,
         {
--- a/devtools/client/netmonitor/test/browser_net_status-codes.js
+++ b/devtools/client/netmonitor/test/browser_net_status-codes.js
@@ -113,16 +113,17 @@ add_task(async function() {
    */
   async function verifyRequests() {
     const requestListItems = document.querySelectorAll(".request-list-item");
     for (const requestItem of requestListItems) {
       requestItem.scrollIntoView();
       const requestsListStatus = requestItem.querySelector(".status-code");
       EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
       await waitUntil(() => requestsListStatus.title);
+      await waitForDOMIfNeeded(requestItem, ".requests-list-timings-total");
     }
 
     info("Verifying requests contain correct information.");
     let index = 0;
     for (const request of REQUEST_DATA) {
       const item = getSortedRequests(store.getState())[index];
       requestItems[index] = item;
 
--- a/devtools/client/netmonitor/test/browser_net_streaming-response.js
+++ b/devtools/client/netmonitor/test/browser_net_streaming-response.js
@@ -34,16 +34,17 @@ add_task(async function() {
   await wait;
 
   const requestItems = document.querySelectorAll(".request-list-item");
   for (const requestItem of requestItems) {
     requestItem.scrollIntoView();
     const requestsListStatus = requestItem.querySelector(".status-code");
     EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
     await waitUntil(() => requestsListStatus.title);
+    await waitForDOMIfNeeded(requestItem, ".requests-list-timings-total");
   }
 
   REQUESTS.forEach(([fmt], i) => {
     verifyRequestItemTarget(
       document,
       getDisplayedRequests(store.getState()),
       getSortedRequests(store.getState())[i],
       "GET",
--- a/devtools/client/netmonitor/test/head.js
+++ b/devtools/client/netmonitor/test/head.js
@@ -482,34 +482,38 @@ function verifyRequestItemTarget(
   requestList,
   requestItem,
   method,
   url,
   data = {}
 ) {
   info("> Verifying: " + method + " " + url + " " + data.toSource());
 
-  const visibleIndex = requestList.indexOf(requestItem);
+  const visibleIndex = requestList.findIndex(
+    needle => needle.id === requestItem.id
+  );
 
+  isnot(visibleIndex, -1, "The requestItem exists");
   info("Visible index of item: " + visibleIndex);
 
   const {
     fuzzyUrl,
     status,
     statusText,
     cause,
     type,
     fullMimeType,
     transferred,
     size,
     time,
     displayedStatus,
   } = data;
 
   const target = document.querySelectorAll(".request-list-item")[visibleIndex];
+
   // Bug 1414981 - Request URL should not show #hash
   const unicodeUrl = getUnicodeUrl(url.split("#")[0]);
   const ORIGINAL_FILE_URL = L10N.getFormatStr(
     "netRequest.originalFileURL.tooltip",
     url
   );
   const DECODED_FILE_URL = L10N.getFormatStr(
     "netRequest.decodedFileURL.tooltip",
--- a/devtools/client/performance-new/initializer.js
+++ b/devtools/client/performance-new/initializer.js
@@ -85,16 +85,19 @@ async function gInit(perfFront, preferen
     // Get the supported features from the debuggee. If the debuggee is before
     // Firefox 72, then return null, as the actor does not support that feature.
     // We can't use `target.actorHasMethod`, because the target is not provided
     // when remote debugging. Unfortunately, this approach means that if this
     // function throws a real error, it will get swallowed here.
     Promise.resolve(perfFront.getSupportedFeatures()).catch(() => null),
   ]);
 
+  // This panel doesn't support presets yet, make sure it's always set to custom.
+  recordingPreferences.presetName = "custom";
+
   // Do some initialization, especially with privileged things that are part of the
   // the browser.
   store.dispatch(
     actions.initializeStore({
       perfFront,
       receiveProfile,
       recordingPreferences,
       supportedFeatures,
--- a/devtools/client/shared/components/tree/TreeView.css
+++ b/devtools/client/shared/components/tree/TreeView.css
@@ -17,36 +17,35 @@
 
 .treeTable {
   color: var(--theme-highlight-blue);
 }
 
 .treeTable .treeLabelCell,
 .treeTable .treeValueCell {
   padding: 2px 0;
+  padding-inline-start: 4px;
   line-height: 16px; /* make rows 20px tall */
-  padding-inline-start: 4px;
   vertical-align: top;
   overflow: hidden;
 }
 
 .treeTable .treeLabelCell {
   white-space: nowrap;
   cursor: default;
   padding-inline-start: var(--tree-label-cell-indent);
 }
 
 .treeTable .treeLabelCell::after {
   content: ":";
   color: var(--object-color);
 }
 
 .treeTable .treeValueCell.inputEnabled {
-  padding-top: 0;
-  padding-bottom: 0;
+  padding-block: 0;
 }
 
 .treeTable .treeValueCell.inputEnabled input {
   width: 100%;
   height: 20px;
   margin: 0;
   margin-inline-start: -2px;
   border: solid 1px transparent;
@@ -60,16 +59,17 @@
 .treeTable .treeValueCell.inputEnabled input:focus {
   border-color: var(--theme-textbox-box-shadow);
   transition: all 150ms ease-in-out;
 }
 
 .treeTable .treeValueCell > [aria-labelledby],
 .treeTable .treeLabelCell > .treeLabel {
   unicode-bidi: plaintext;
+  text-align: match-parent;
 }
 
 /* No padding if there is actually no label */
 .treeTable .treeLabel:empty {
   padding-inline-start: 0;
 }
 
 .treeTable .treeRow.hasChildren > .treeLabelCell > .treeLabel:hover {
@@ -131,22 +131,21 @@
   width: 14px;
   padding: 1px;
   /* Set the size of loading spinner (see .devtools-throbber) */
   font-size: 10px;
   line-height: 14px;
   display: inline-block;
   vertical-align: bottom;
   /* Use a total width of 20px (margins + padding + width) */
-  margin-inline-start: 3px;
-  margin-inline-end: 1px;
+  margin-inline: 3px 1px;
 }
 
 /* All expanded/collapsed styles need to apply on immediate children
-  since there might be nested trees within a tree. */
+   since there might be nested trees within a tree. */
 .treeTable .treeRow.hasChildren > .treeLabelCell > .treeIcon {
   cursor: pointer;
   background-repeat: no-repeat;
 }
 
 /******************************************************************************/
 /* Header */
 
@@ -165,19 +164,18 @@
       radial-gradient(1px 60% at right,
           rgba(0, 0, 0, 0.8) 0%,
           transparent 80%) repeat-x var(--tree-header-background);
   color: var(--theme-body-color);
   white-space: nowrap;
 }
 
 .treeTable .treeHeaderCellBox {
-  padding: 2px 0;
-  padding-inline-start: 10px;
-  padding-inline-end: 14px;
+  padding-block: 2px;
+  padding-inline: 10px 14px;
 }
 
 .treeTable .treeHeaderRow > .treeHeaderCell:first-child > .treeHeaderCellBox {
   padding: 0;
 }
 
 .treeTable .treeHeaderSorted {
   background-color: var(--tree-header-sorted-background);
--- a/devtools/client/themes/computed.css
+++ b/devtools/client/themes/computed.css
@@ -1,12 +1,20 @@
 /* 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/. */
 
+ .theme-dark {
+  --row-striped-background: rgba(255, 255, 255, 0.05);
+ }
+
+.theme-light {
+  --row-striped-background: rgba(247, 247, 247, 0.8);
+}
+
 #sidebar-panel-computedview {
   margin: 0;
   display: flex;
   flex-direction: column;
   width: 100%;
   height: 100%;
 }
 
@@ -66,17 +74,17 @@
   overflow-y: auto;
   overflow-x: hidden;
   flex: auto;
   border-top: 1px solid var(--theme-splitter-color);
   margin-top: -1px;
 }
 
 .row-striped {
-  background: var(--theme-body-background);
+  background: var(--row-striped-background);
 }
 
 .computed-property-hidden {
   display: none;
 }
 
 .computed-property-view {
   padding: 2px 0px;
--- a/devtools/client/webconsole/test/browser/browser_webconsole_network_messages_openinnet.js
+++ b/devtools/client/webconsole/test/browser/browser_webconsole_network_messages_openinnet.js
@@ -52,18 +52,19 @@ add_task(async function task() {
   );
   await waitForRequestData(netmonitor.panelWin.store, ["eventTimings"], 0);
 
   // Go back to console.
   await toolbox.selectTool("webconsole");
   info("console panel open again.");
 
   // Fire an XHR request.
-  await SpecialPowers.spawn(gBrowser.selectedBrowser, [], function() {
-    content.wrappedJSObject.testXhrGet();
+  await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function() {
+    // Ensure XHR request is completed
+    await new Promise(resolve => content.wrappedJSObject.testXhrGet(resolve));
   });
 
   const jsonUrl = TEST_PATH + JSON_TEST_URL;
   await openMessageInNetmonitor(toolbox, hud, jsonUrl);
 
   info(
     "Wait for the netmonitor headers panel to appear as it spawn RDP requests"
   );
--- a/devtools/docs/README.md
+++ b/devtools/docs/README.md
@@ -1,17 +1,13 @@
 # Firefox Developer Tools: Contributor Docs
 
 Note: This is a guide to working on code contributions to DevTools. If you're looking for help with using the tools, view the [user docs](https://developer.mozilla.org/en-US/docs/Tools) instead. For other ways to get involved, see our [community site](https://firefox-dev.tools).
 
 ---
 
-**Hello,** and thanks for your interest in contributing to Firefox DevTools! 
+**Hello,** and thanks for your interest in contributing to Firefox DevTools!
 
 DevTools is a complex web app, but if you're familiar with either HTML/CSS or JavaScript, you can create a patch!
 
-The initial setup process of building Firefox can take a while. If you get stuck at any point, you can ask for help on the [DevTools Slack](https://devtools-html-slack.herokuapp.com/). 
+The initial setup process of building Firefox can take a while. If you get stuck at any point, you can ask for help on the [DevTools Slack](https://devtools-html-slack.herokuapp.com/).
 
 We all make lots of errors when we get started, so don't worry about that—there are safeguards in place to ensure that any commits that land in the Firefox repository are peer-reviewed, can be built, and pass the tests in multiple configurations and operating systems.
-
-(Note: You can navigate these docs with the arrow keys.)
-
-[Let's get started!](./getting-started/)
new file mode 100644
--- /dev/null
+++ b/devtools/docs/index.rst
@@ -0,0 +1,129 @@
+========
+DevTools
+========
+
+This collection of linked pages contain design and architectural documents
+for the DevTools implementation in Firefox. DevTools code base lives under
+`devtools` directory.
+
+This `MDN page <https://developer.mozilla.org/en-US/docs/Tools>`__ contains
+information about DevTools features.
+
+This `MDN page <https://www.mozilla.org/en-US/firefox/developer>`__ contains
+information about Developer Edition.
+
+
+Getting Started
+===============
+.. toctree::
+   :maxdepth: 1
+
+   Introduction <README.md>
+   Getting Started <getting-started/README.md>
+   Get a Bugzilla account <getting-started/bugzilla.md>
+   Get and build the code <getting-started/build.md>
+   Create a development profile <getting-started/development-profiles.md>
+
+
+Contributing
+============
+.. toctree::
+   :maxdepth: 1
+
+   Contributing <contributing.md>
+   Find bugs to work on <contributing/find-bugs.md>
+   How to fix a bug <contributing/fixing-bugs.md>
+   Code reviews <contributing/code-reviews.md>
+   Landing code <contributing/landing-code.md>
+   Leveling up <contributing/levelling-up.md>
+   Coding standards <contributing/coding-standards.md>
+   Filing good bugs <contributing/filing-good-bugs.md>
+   Investigating performance issues <contributing/performance.md>
+   Writing efficient React code <contributing/react-performance-tips.md>
+
+
+Automated tests
+===============
+.. toctree::
+   :maxdepth: 1
+
+   Automated tests <tests/README.md>
+   xpcshell <tests/xpcshell.md>
+   Chrome mochitests <tests/mochitest-chrome.md>
+   DevTools mochitests <tests/mochitest-devtools.md>
+   Node tests <tests/node-tests.md>
+   Writing tests <tests/writing-tests.md>
+   Debugging intermittent failures <tests/debugging-intermittents.md>
+   Performance tests (DAMP) <tests/performance-tests.md>
+   Writing a new test <tests/writing-perf-tests.md>
+   Example <tests/writing-perf-tests-example.md>
+   Advanced tips <tests/writing-perf-tests-tips.md>
+
+Files and directories
+=====================
+.. toctree::
+   :maxdepth: 1
+
+   Files and directories <files/README.md>
+   Adding New Files <files/adding-files.md>
+
+
+Tool Architectures
+==================
+.. toctree::
+   :maxdepth: 1
+
+   Tool Architectures <tools/tools.md>
+   Inspector Panel Architecture <tools/inspector-panel.md>
+   Inspector Highlighters <tools/highlighters.md>
+   Memory <tools/memory-panel.md>
+   Debugger <tools/debugger-panel.md>
+   Responsive Design Mode <tools/responsive-design-mode.md>
+   Console <tools/console-panel.md>
+
+
+Frontend
+========
+.. toctree::
+   :maxdepth: 1
+
+  Frontend <frontend/frontend.md>
+  Panel SVGs <frontend/svgs.md>
+  React <frontend/react.md>
+  React Guidelines <frontend/react-guidelines.md>
+  Redux <frontend/redux.md>
+  Redux Guidelines <frontend/redux-guidelines.md>
+  Telemetry <frontend/telemetry.md>
+  Content Security Policy <frontend/csp.md>
+
+
+Backend
+=======
+.. toctree::
+   :maxdepth: 1
+
+   Backend <backend/backend.md>
+   Remote Debugging Protocol <backend/protocol.md>
+   Client API <backend/client-api.md>
+   Debugger API <backend/debugger-api.md>
+   Backward Compatibility <backend/backward-compatibility.md>
+   Actors Organization <backend/actor-hierarchy.md>
+   Handling Multi-Processes in Actors <backend/actor-e10s-handling.md>
+   Writing Actors With protocol.js <backend/protocol.js.md>
+   Registering A New Actor <backend/actor-registration.md>
+   Actor Best Practices <backend/actor-best-practices.md>
+
+Preferences
+===========
+.. toctree::
+   :maxdepth: 1
+
+   Preferences <preferences.md>
+
+
+About this documentation
+========================
+.. toctree::
+   :maxdepth: 1
+
+   About this documentation <about-documentation.md>
--- a/docshell/base/BrowsingContext.h
+++ b/docshell/base/BrowsingContext.h
@@ -261,16 +261,17 @@ class BrowsingContext : public nsISuppor
   // Check that this browsing context is targetable for navigations (i.e. that
   // it is neither closed, cached, nor discarded).
   bool IsTargetable();
 
   const nsString& Name() const { return GetName(); }
   void GetName(nsAString& aName) { aName = GetName(); }
   bool NameEquals(const nsAString& aName) { return GetName().Equals(aName); }
 
+  Type GetType() const { return mType; }
   bool IsContent() const { return mType == Type::Content; }
   bool IsChrome() const { return !IsContent(); }
 
   bool IsTop() const { return !GetParent(); }
 
   bool IsTopContent() const { return IsContent() && !GetParent(); }
 
   bool IsContentSubframe() const { return IsContent() && GetParent(); }
@@ -519,18 +520,16 @@ class BrowsingContext : public nsISuppor
 
   // The runnable will be called once there is idle time, or the top level
   // page has been loaded or if a timeout has fired.
   // Must be called only on the top level BrowsingContext.
   void AddDeprioritizedLoadRunner(nsIRunnable* aRunner);
 
   RefPtr<SessionStorageManager> GetSessionStorageManager();
 
-  Type GetType() const { return mType; }
-
   bool PendingInitialization() const { return mPendingInitialization; };
   void SetPendingInitialization(bool aVal) { mPendingInitialization = aVal; };
 
  protected:
   virtual ~BrowsingContext();
   BrowsingContext(BrowsingContext* aParent, BrowsingContextGroup* aGroup,
                   uint64_t aBrowsingContextId, Type aType,
                   FieldTuple&& aFields);
--- a/dom/base/AbstractRange.cpp
+++ b/dom/base/AbstractRange.cpp
@@ -11,16 +11,17 @@
 #include "mozilla/Attributes.h"
 #include "mozilla/RangeUtils.h"
 #include "mozilla/dom/StaticRange.h"
 #include "nsContentUtils.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsGkAtoms.h"
 #include "nsINode.h"
 #include "nsRange.h"
+#include "nsTArray.h"
 
 namespace mozilla {
 namespace dom {
 
 template nsresult AbstractRange::SetStartAndEndInternal(
     const RangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary,
     nsRange* aRange);
 template nsresult AbstractRange::SetStartAndEndInternal(
@@ -39,16 +40,20 @@ template nsresult AbstractRange::SetStar
     const RangeBoundary& aStartBoundary, const RawRangeBoundary& aEndBoundary,
     StaticRange* aRange);
 template nsresult AbstractRange::SetStartAndEndInternal(
     const RawRangeBoundary& aStartBoundary, const RangeBoundary& aEndBoundary,
     StaticRange* aRange);
 template nsresult AbstractRange::SetStartAndEndInternal(
     const RawRangeBoundary& aStartBoundary,
     const RawRangeBoundary& aEndBoundary, StaticRange* aRange);
+template bool AbstractRange::MaybeCacheToReuse(nsRange& aInstance);
+template bool AbstractRange::MaybeCacheToReuse(StaticRange& aInstance);
+
+bool AbstractRange::sHasShutDown = false;
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(AbstractRange)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(AbstractRange)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AbstractRange)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
@@ -66,23 +71,65 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStart)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEnd)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(AbstractRange)
 
 // NOTE: If you need to change default value of members of AbstractRange,
-//       update nsRange::Create(nsINode* aNode) too.
+//       update nsRange::Create(nsINode* aNode) and ClearForReuse() too.
 AbstractRange::AbstractRange(nsINode* aNode)
     : mIsPositioned(false), mIsGenerated(false), mCalledByJS(false) {
+  Init(aNode);
+}
+
+void AbstractRange::Init(nsINode* aNode) {
   MOZ_ASSERT(aNode, "range isn't in a document!");
   mOwner = aNode->OwnerDoc();
 }
 
+// static
+void AbstractRange::Shutdown() {
+  sHasShutDown = true;
+  if (nsTArray<RefPtr<nsRange>>* cachedRanges = nsRange::sCachedRanges) {
+    nsRange::sCachedRanges = nullptr;
+    cachedRanges->Clear();
+    delete cachedRanges;
+  }
+  if (nsTArray<RefPtr<StaticRange>>* cachedRanges =
+          StaticRange::sCachedRanges) {
+    StaticRange::sCachedRanges = nullptr;
+    cachedRanges->Clear();
+    delete cachedRanges;
+  }
+}
+
+// static
+template <class RangeType>
+bool AbstractRange::MaybeCacheToReuse(RangeType& aInstance) {
+  static const size_t kMaxRangeCache = 64;
+
+  // If the instance is not used by JS and the cache is not yet full, we
+  // should reuse it.  Otherwise, delete it.
+  if (sHasShutDown || aInstance.GetWrapperMaybeDead() || aInstance.GetFlags() ||
+      (RangeType::sCachedRanges &&
+       RangeType::sCachedRanges->Length() == kMaxRangeCache)) {
+    return false;
+  }
+
+  aInstance.ClearForReuse();
+
+  if (!RangeType::sCachedRanges) {
+    RangeType::sCachedRanges = new nsTArray<RefPtr<RangeType>>(16);
+  }
+  RangeType::sCachedRanges->AppendElement(&aInstance);
+  return true;
+}
+
 nsINode* AbstractRange::GetClosestCommonInclusiveAncestor() const {
   return mIsPositioned ? nsContentUtils::GetClosestCommonInclusiveAncestor(
                              mStart.Container(), mEnd.Container())
                        : nullptr;
 }
 
 // static
 template <typename SPT, typename SRT, typename EPT, typename ERT,
--- a/dom/base/AbstractRange.h
+++ b/dom/base/AbstractRange.h
@@ -20,16 +20,21 @@ class AbstractRange : public nsISupports
  protected:
   explicit AbstractRange(nsINode* aNode);
   virtual ~AbstractRange() = default;
 
  public:
   AbstractRange() = delete;
   explicit AbstractRange(const AbstractRange& aOther) = delete;
 
+  /**
+   * Called when the process is shutting down.
+   */
+  static void Shutdown();
+
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AbstractRange)
 
   const RangeBoundary& StartRef() const { return mStart; }
   const RangeBoundary& EndRef() const { return mEnd; }
 
   nsIContent* GetChildAtStartOffset() const {
     return mStart.GetChildAtOffset();
@@ -80,34 +85,43 @@ class AbstractRange : public nsISupports
 
  protected:
   template <typename SPT, typename SRT, typename EPT, typename ERT,
             typename RangeType>
   static nsresult SetStartAndEndInternal(
       const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
       const RangeBoundaryBase<EPT, ERT>& aEndBoundary, RangeType* aRange);
 
+  template <class RangeType>
+  static bool MaybeCacheToReuse(RangeType& aInstance);
+
+  void Init(nsINode* aNode);
+
+ private:
   void ClearForReuse() {
     mOwner = nullptr;
     mStart = RangeBoundary();
     mEnd = RangeBoundary();
     mIsPositioned = false;
     mIsGenerated = false;
     mCalledByJS = false;
   }
 
+ protected:
   RefPtr<Document> mOwner;
   RangeBoundary mStart;
   RangeBoundary mEnd;
   // `true` if `mStart` and `mEnd` are set for StaticRange or set and valid
   // for nsRange.
   bool mIsPositioned;
 
   // Used by nsRange, but this should have this for minimizing the size.
   bool mIsGenerated;
   // Used by nsRange, but this should have this for minimizing the size.
   bool mCalledByJS;
+
+  static bool sHasShutDown;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // #ifndef mozilla_dom_AbstractRange_h
--- a/dom/base/BodyConsumer.cpp
+++ b/dom/base/BodyConsumer.cpp
@@ -731,17 +731,17 @@ void BodyConsumer::ContinueConsumeBody(n
       break;
     }
     default:
       MOZ_ASSERT_UNREACHABLE("Unexpected consume body type");
   }
 
   error.WouldReportJSException();
   if (error.Failed()) {
-    localPromise->MaybeReject(error);
+    localPromise->MaybeReject(std::move(error));
   }
 }
 
 void BodyConsumer::ContinueConsumeBlobBody(BlobImpl* aBlobImpl,
                                            bool aShuttingDown) {
   AssertIsOnTargetThread();
   MOZ_ASSERT(mConsumeType == CONSUME_BLOB);
 
--- a/dom/base/BodyStream.cpp
+++ b/dom/base/BodyStream.cpp
@@ -331,20 +331,26 @@ void BodyStream::ErrorPropagation(JSCont
 
   // Let's close the stream.
   if (aError == NS_BASE_STREAM_CLOSED) {
     CloseAndReleaseObjects(aCx, aProofOfLock, aStream);
     return;
   }
 
   // Let's use a generic error.
-  RefPtr<DOMException> error = DOMException::Create(NS_ERROR_DOM_TYPE_ERR);
+  ErrorResult rv;
+  // XXXbz can we come up with a better error message here to tell the
+  // consumer what went wrong?
+  rv.ThrowTypeError(u"Error in body stream");
 
   JS::Rooted<JS::Value> errorValue(aCx);
-  if (ToJSValue(aCx, error, &errorValue)) {
+  bool ok = ToJSValue(aCx, std::move(rv), &errorValue);
+  MOZ_RELEASE_ASSERT(ok, "ToJSValue never fails for ErrorResult");
+
+  {
     MutexAutoUnlock unlock(mMutex);
     JS::ReadableStreamError(aCx, aStream, errorValue);
   }
 
   ReleaseObjects(aProofOfLock);
 }
 
 NS_IMETHODIMP
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -2675,25 +2675,27 @@ already_AddRefed<nsIPrincipal> Document:
   nsCOMPtr<nsIPrincipal> principal(aPrincipal);
   return principal.forget();
 }
 
 size_t Document::FindDocStyleSheetInsertionPoint(const StyleSheet& aSheet) {
   nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
 
   // lowest index first
-  int32_t newDocIndex = IndexOfSheet(aSheet);
+  int32_t newDocIndex = StyleOrderIndexOfSheet(aSheet);
 
   size_t count = mStyleSet->SheetCount(StyleOrigin::Author);
   size_t index = 0;
   for (; index < count; index++) {
     auto* sheet = mStyleSet->SheetAt(StyleOrigin::Author, index);
     MOZ_ASSERT(sheet);
-    int32_t sheetDocIndex = IndexOfSheet(*sheet);
-    if (sheetDocIndex > newDocIndex) break;
+    int32_t sheetDocIndex = StyleOrderIndexOfSheet(*sheet);
+    if (sheetDocIndex > newDocIndex) {
+      break;
+    }
 
     // If the sheet is not owned by the document it can be an author
     // sheet registered at nsStyleSheetService or an additional author
     // sheet on the document, which means the new
     // doc sheet should end up before it.
     if (sheetDocIndex < 0) {
       if (sheetService) {
         auto& authorSheets = *sheetService->AuthorStyleSheets();
@@ -2705,16 +2707,24 @@ size_t Document::FindDocStyleSheetInsert
         break;
       }
     }
   }
 
   return index;
 }
 
+void Document::AppendAdoptedStyleSheet(StyleSheet& aSheet) {
+  DocumentOrShadowRoot::InsertAdoptedSheetAt(mAdoptedStyleSheets.Length(),
+                                             aSheet);
+  if (aSheet.IsApplicable()) {
+    AddStyleSheetToStyleSets(&aSheet);
+  }
+}
+
 void Document::RemoveDocStyleSheetsFromStyleSets() {
   MOZ_ASSERT(mStyleSetFilled);
   // The stylesheets should forget us
   for (StyleSheet* sheet : Reversed(mStyleSheets)) {
     sheet->ClearAssociatedDocumentOrShadowRoot();
     if (sheet->IsApplicable()) {
       mStyleSet->RemoveDocStyleSheet(sheet);
     }
@@ -2923,22 +2933,30 @@ void Document::AddContentEditableStyleSh
     ApplicableStylesChanged();
   }
 }
 
 void Document::FillStyleSetDocumentSheets() {
   MOZ_ASSERT(mStyleSet->SheetCount(StyleOrigin::Author) == 0,
              "Style set already has document sheets?");
 
+  // Sheets are added in reverse order to avoid worst-case
+  // time complexity when looking up the index of a sheet
   for (StyleSheet* sheet : Reversed(mStyleSheets)) {
     if (sheet->IsApplicable()) {
       mStyleSet->AddDocStyleSheet(sheet);
     }
   }
 
+  for (StyleSheet* sheet : Reversed(mAdoptedStyleSheets)) {
+    if (sheet->IsApplicable()) {
+      mStyleSet->AddDocStyleSheet(sheet);
+    }
+  }
+
   nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
   for (StyleSheet* sheet : *sheetService->AuthorStyleSheets()) {
     mStyleSet->AppendStyleSheet(StyleOrigin::Author, sheet);
   }
 
   AppendSheetsToStyleSet(mStyleSet.get(), mAdditionalSheets[eAgentSheet],
                          StyleOrigin::UserAgent);
   AppendSheetsToStyleSet(mStyleSet.get(), mAdditionalSheets[eUserSheet],
@@ -6541,19 +6559,18 @@ void Document::ApplicableStylesChanged()
 
 void Document::RemoveStyleSheetFromStyleSets(StyleSheet* aSheet) {
   if (mStyleSetFilled) {
     mStyleSet->RemoveDocStyleSheet(aSheet);
     ApplicableStylesChanged();
   }
 }
 
-void Document::RemoveStyleSheet(StyleSheet* aSheet) {
-  MOZ_ASSERT(aSheet);
-  RefPtr<StyleSheet> sheet = DocumentOrShadowRoot::RemoveSheet(*aSheet);
+void Document::RemoveStyleSheet(StyleSheet& aSheet) {
+  RefPtr<StyleSheet> sheet = DocumentOrShadowRoot::RemoveSheet(aSheet);
 
   if (!sheet) {
     NS_ASSERTION(mInUnlinkOrDeletion, "stylesheet not found");
     return;
   }
 
   if (!mIsGoingAway && sheet->IsApplicable()) {
     RemoveStyleSheetFromStyleSets(sheet);
@@ -6568,17 +6585,17 @@ void Document::InsertSheetAt(size_t aInd
   if (aSheet.IsApplicable()) {
     AddStyleSheetToStyleSets(&aSheet);
   }
 }
 
 void Document::StyleSheetApplicableStateChanged(StyleSheet& aSheet) {
   const bool applicable = aSheet.IsApplicable();
   // If we're actually in the document style sheet list
-  if (mStyleSheets.IndexOf(&aSheet) != mStyleSheets.NoIndex) {
+  if (StyleOrderIndexOfSheet(aSheet) >= 0) {
     if (applicable) {
       AddStyleSheetToStyleSets(&aSheet);
     } else {
       RemoveStyleSheetFromStyleSets(&aSheet);
     }
   }
 
   if (StyleSheetChangeEventsEnabled()) {
@@ -16125,10 +16142,39 @@ Document::RecomputeContentBlockingAllowL
   AntiTrackingCommon::RecomputeContentBlockingAllowListPrincipal(
       aURIBeingLoaded, aAttrs,
       getter_AddRefs(mContentBlockingAllowListPrincipal));
 
   nsCOMPtr<nsIPrincipal> copy = mContentBlockingAllowListPrincipal;
   return copy.forget();
 }
 
+// https://wicg.github.io/construct-stylesheets/#dom-documentorshadowroot-adoptedstylesheets
+void Document::SetAdoptedStyleSheets(
+    const Sequence<OwningNonNull<StyleSheet>>& aAdoptedStyleSheets,
+    ErrorResult& aRv) {
+  // Step 1 is a variable declaration
+
+  // 2.1 Check if all sheets are constructed, else throw NotAllowedError
+  // 2.2 Check if all sheets' constructor documents match the
+  // DocumentOrShadowRoot's node document, else throw NotAlloweError
+  EnsureAdoptedSheetsAreValid(aAdoptedStyleSheets, aRv);
+  if (aRv.Failed()) {
+    return;
+  }
+
+  // 3. Set the adopted style sheets to the new sheets
+  for (const RefPtr<StyleSheet>& sheet : mAdoptedStyleSheets) {
+    if (sheet->IsApplicable()) {
+      RemoveStyleSheetFromStyleSets(sheet);
+    }
+    sheet->RemoveAdopter(*this);
+  }
+  mAdoptedStyleSheets.ClearAndRetainStorage();
+  mAdoptedStyleSheets.SetCapacity(aAdoptedStyleSheets.Length());
+  for (const OwningNonNull<StyleSheet>& sheet : aAdoptedStyleSheets) {
+    sheet->AddAdopter(*this);
+    AppendAdoptedStyleSheet(*sheet);
+  }
+}
+
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/base/Document.h
+++ b/dom/base/Document.h
@@ -1695,17 +1695,17 @@ class Document : public nsINode,
   void AddStyleSheet(StyleSheet* aSheet) {
     MOZ_ASSERT(aSheet);
     InsertSheetAt(SheetCount(), *aSheet);
   }
 
   /**
    * Remove a stylesheet from the document
    */
-  void RemoveStyleSheet(StyleSheet*);
+  void RemoveStyleSheet(StyleSheet&);
 
   /**
    * Notify the document that the applicable state of the sheet changed
    * and that observers should be notified and style sets updated
    */
   void StyleSheetApplicableStateChanged(StyleSheet&);
 
   enum additionalSheetType {
@@ -1720,16 +1720,18 @@ class Document : public nsINode,
   nsresult AddAdditionalStyleSheet(additionalSheetType aType,
                                    StyleSheet* aSheet);
   void RemoveAdditionalStyleSheet(additionalSheetType aType, nsIURI* sheetURI);
 
   StyleSheet* GetFirstAdditionalAuthorSheet() {
     return mAdditionalSheets[eAuthorSheet].SafeElementAt(0);
   }
 
+  void AppendAdoptedStyleSheet(StyleSheet& aSheet);
+
   /**
    * Returns the index that aSheet should be inserted at to maintain document
    * ordering.
    */
   size_t FindDocStyleSheetInsertionPoint(const StyleSheet& aSheet);
 
   /**
    * Get this document's CSSLoader.  This is guaranteed to not return null.
@@ -3874,16 +3876,20 @@ class Document : public nsINode,
   static bool UseOverlayScrollbars(const Document* aDocument);
 
   static bool HasRecentlyStartedForegroundLoads();
 
   static bool AutomaticStorageAccessCanBeGranted(nsIPrincipal* aPrincipal);
 
   already_AddRefed<Promise> AddCertException(bool aIsTemporary);
 
+  void SetAdoptedStyleSheets(
+      const Sequence<OwningNonNull<StyleSheet>>& aAdoptedStyleSheets,
+      ErrorResult& aRv);
+
  protected:
   void DoUpdateSVGUseElementShadowTrees();
 
   already_AddRefed<nsIPrincipal> MaybeDowngradePrincipal(
       nsIPrincipal* aPrincipal);
 
   void EnsureOnloadBlocker();
 
@@ -4942,16 +4948,18 @@ class Document : public nsINode,
   CSSCoord mMaxWidth;
   CSSCoord mMinHeight;
   CSSCoord mMaxHeight;
 
   RefPtr<EventListenerManager> mListenerManager;
 
   nsCOMPtr<nsIRequest> mOnloadBlocker;
 
+  // Gecko-internal sheets used for extensions and such.
+  // Exposed to privileged script via nsIDOMWindowUtils.loadSheet.
   nsTArray<RefPtr<StyleSheet>> mAdditionalSheets[AdditionalSheetTypeCount];
 
   // Member to store out last-selected stylesheet set.
   nsString mLastStyleSheetSet;
   nsString mPreferredStyleSheetSet;
 
   RefPtr<DOMStyleSheetSetList> mStyleSheetSetList;
 
--- a/dom/base/DocumentOrShadowRoot.cpp
+++ b/dom/base/DocumentOrShadowRoot.cpp
@@ -72,58 +72,52 @@ StyleSheetList* DocumentOrShadowRoot::St
 }
 
 void DocumentOrShadowRoot::InsertSheetAt(size_t aIndex, StyleSheet& aSheet) {
   aSheet.SetAssociatedDocumentOrShadowRoot(
       this, StyleSheet::OwnedByDocumentOrShadowRoot);
   mStyleSheets.InsertElementAt(aIndex, &aSheet);
 }
 
+void DocumentOrShadowRoot::InsertAdoptedSheetAt(size_t aIndex,
+                                                StyleSheet& aSheet) {
+  mAdoptedStyleSheets.InsertElementAt(aIndex, &aSheet);
+}
+
 already_AddRefed<StyleSheet> DocumentOrShadowRoot::RemoveSheet(
     StyleSheet& aSheet) {
   auto index = mStyleSheets.IndexOf(&aSheet);
   if (index == mStyleSheets.NoIndex) {
     return nullptr;
   }
   RefPtr<StyleSheet> sheet = std::move(mStyleSheets[index]);
   mStyleSheets.RemoveElementAt(index);
   sheet->ClearAssociatedDocumentOrShadowRoot();
   return sheet.forget();
 }
 
 // https://wicg.github.io/construct-stylesheets/#dom-documentorshadowroot-adoptedstylesheets
-void DocumentOrShadowRoot::SetAdoptedStyleSheets(
+void DocumentOrShadowRoot::EnsureAdoptedSheetsAreValid(
     const Sequence<OwningNonNull<StyleSheet>>& aAdoptedStyleSheets,
     ErrorResult& aRv) {
-  // TODO(nordzilla): This is just a minimal-implementation stub to land
-  // the WebIDL attribute (Bug 1608489).
-
-  // Step 1 is a variable declaration
-
   for (const OwningNonNull<StyleSheet>& sheet : aAdoptedStyleSheets) {
     // 2.1 Check if all sheets are constructed, else throw NotAllowedError
     if (!sheet->IsConstructed()) {
       return aRv.ThrowNotAllowedError(
           "Each adopted style sheet must be created through the Constructable "
           "StyleSheets API");
     }
     // 2.2 Check if all sheets' constructor documents match the
     // DocumentOrShadowRoot's node document, else throw NotAlloweError
     if (!sheet->ConstructorDocumentMatches(AsNode().OwnerDoc())) {
       return aRv.ThrowNotAllowedError(
           "Each adopted style sheet's constructor document must match the "
           "document or shadow root's node document");
     }
   }
-  // 3. Set the adopted style sheets to the new sheets
-  mAdoptedStyleSheets.ClearAndRetainStorage();
-  mAdoptedStyleSheets.SetCapacity(aAdoptedStyleSheets.Length());
-  for (const OwningNonNull<StyleSheet>& sheet : aAdoptedStyleSheets) {
-    mAdoptedStyleSheets.AppendElement(sheet.get());
-  }
 }
 
 Element* DocumentOrShadowRoot::GetElementById(const nsAString& aElementId) {
   if (MOZ_UNLIKELY(aElementId.IsEmpty())) {
     nsContentUtils::ReportEmptyGetElementByIdArg(AsNode().OwnerDoc());
     return nullptr;
   }
 
@@ -647,64 +641,80 @@ nsRadioGroupStruct* DocumentOrShadowRoot
 
 nsRadioGroupStruct* DocumentOrShadowRoot::GetOrCreateRadioGroup(
     const nsAString& aName) {
   return mRadioGroups.LookupForAdd(aName)
       .OrInsert([]() { return new nsRadioGroupStruct(); })
       .get();
 }
 
+int32_t DocumentOrShadowRoot::StyleOrderIndexOfSheet(
+    const StyleSheet& aSheet) const {
+  if (aSheet.IsConstructed()) {
+    int32_t index = mAdoptedStyleSheets.IndexOf(&aSheet);
+    return (index < 0) ? index : index + SheetCount();
+  }
+  return mStyleSheets.IndexOf(&aSheet);
+}
+
 void DocumentOrShadowRoot::GetAdoptedStyleSheets(
     nsTArray<RefPtr<StyleSheet>>& aAdoptedStyleSheets) const {
   aAdoptedStyleSheets = mAdoptedStyleSheets;
 }
 
 void DocumentOrShadowRoot::Traverse(DocumentOrShadowRoot* tmp,
                                     nsCycleCollectionTraversalCallback& cb) {
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheets)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMStyleSheets)
-  // TODO(nordzilla): This may get more involved once the sheets are applied.
-  // This currently exists only to land the WebIDL attribute (Bug 1608489)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAdoptedStyleSheets)
-  for (StyleSheet* sheet : tmp->mStyleSheets) {
-    if (!sheet->IsApplicable()) {
-      continue;
+
+  auto NoteSheets = [tmp, &cb = cb](nsTArray<RefPtr<StyleSheet>>& sheetList) {
+    for (StyleSheet* sheet : sheetList) {
+      if (!sheet->IsApplicable()) {
+        continue;
+      }
+      // The style set or mServoStyles keep more references to it if the sheet
+      // is applicable.
+      if (tmp->mKind == Kind::ShadowRoot) {
+        NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mServoStyles->sheets[i]");
+        cb.NoteXPCOMChild(sheet);
+      } else if (tmp->AsNode().AsDocument()->StyleSetFilled()) {
+        NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
+            cb, "mStyleSet->mRawSet.stylist.stylesheets.author[i]");
+        cb.NoteXPCOMChild(sheet);
+      }
     }
-    // The style set or mServoStyles keep more references to it if the sheet is
-    // applicable.
-    if (tmp->mKind == Kind::ShadowRoot) {
-      NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mServoStyles->sheets[i]");
-      cb.NoteXPCOMChild(sheet);
-    } else if (tmp->AsNode().AsDocument()->StyleSetFilled()) {
-      NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
-          cb, "mStyleSet->mRawSet.stylist.stylesheets.author[i]");
-      cb.NoteXPCOMChild(sheet);
-    }
-  }
+  };
+
+  NoteSheets(tmp->mStyleSheets);
+  NoteSheets(tmp->mAdoptedStyleSheets);
+
   for (auto iter = tmp->mIdentifierMap.ConstIter(); !iter.Done(); iter.Next()) {
     iter.Get()->Traverse(&cb);
   }
+
   for (auto iter = tmp->mRadioGroups.Iter(); !iter.Done(); iter.Next()) {
     nsRadioGroupStruct* radioGroup = iter.UserData();
     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
         cb, "mRadioGroups entry->mSelectedRadioButton");
     cb.NoteXPCOMChild(ToSupports(radioGroup->mSelectedRadioButton));
 
     uint32_t i, count = radioGroup->mRadioButtons.Count();
     for (i = 0; i < count; ++i) {
       NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
           cb, "mRadioGroups entry->mRadioButtons[i]");
       cb.NoteXPCOMChild(radioGroup->mRadioButtons[i]);
     }
   }
 }
 
 void DocumentOrShadowRoot::Unlink(DocumentOrShadowRoot* tmp) {
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMStyleSheets)
-  // TODO(nordzilla): This may get more involved once the sheets are applied.
-  // This currently exists only to land the WebIDL attribute (Bug 1608489)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mAdoptedStyleSheets)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMStyleSheets);
+  for (RefPtr<StyleSheet>& sheet : tmp->mAdoptedStyleSheets) {
+    sheet->RemoveAdopter(*tmp);
+  }
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mAdoptedStyleSheets);
   tmp->mIdentifierMap.Clear();
   tmp->mRadioGroups.Clear();
 }
 
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/base/DocumentOrShadowRoot.h
+++ b/dom/base/DocumentOrShadowRoot.h
@@ -68,28 +68,31 @@ class DocumentOrShadowRoot {
   const nsINode& AsNode() const { return *mAsNode; }
 
   StyleSheet* SheetAt(size_t aIndex) const {
     return mStyleSheets.SafeElementAt(aIndex);
   }
 
   size_t SheetCount() const { return mStyleSheets.Length(); }
 
-  int32_t IndexOfSheet(const StyleSheet& aSheet) const {
-    return mStyleSheets.IndexOf(&aSheet);
-  }
+  size_t AdoptedSheetCount() const { return mAdoptedStyleSheets.Length(); }
+
+  /**
+   * Returns an index for the sheet in relative style order.
+   * If there are non-applicable sheets, then this index may
+   * not match 1:1 with the sheet's actual index in the style set.
+   *
+   * Handles sheets from both mStyleSheets and mAdoptedStyleSheets
+   */
+  int32_t StyleOrderIndexOfSheet(const StyleSheet& aSheet) const;
 
   StyleSheetList* StyleSheets();
 
   void GetAdoptedStyleSheets(nsTArray<RefPtr<StyleSheet>>&) const;
 
-  void SetAdoptedStyleSheets(
-      const Sequence<OwningNonNull<StyleSheet>>& aAdoptedStyleSheets,
-      ErrorResult& aRv);
-
   Element* GetElementById(const nsAString& aElementId);
 
   /**
    * This method returns _all_ the elements in this scope which have id
    * aElementId, if there are any.  Otherwise it returns null.
    *
    * This is useful for stuff like QuerySelector optimization and such.
    */
@@ -217,16 +220,21 @@ class DocumentOrShadowRoot {
   nsRadioGroupStruct* GetOrCreateRadioGroup(const nsAString& aName);
 
   nsIContent* Retarget(nsIContent* aContent) const;
 
  protected:
   // Returns the reference to the sheet, if found in mStyleSheets.
   already_AddRefed<StyleSheet> RemoveSheet(StyleSheet& aSheet);
   void InsertSheetAt(size_t aIndex, StyleSheet& aSheet);
+  void InsertAdoptedSheetAt(size_t aIndex, StyleSheet& aSheet);
+
+  void EnsureAdoptedSheetsAreValid(
+      const Sequence<OwningNonNull<StyleSheet>>& aAdoptedStyleSheets,
+      ErrorResult& aRv);
 
   void AddSizeOfExcludingThis(nsWindowSizes&) const;
   void AddSizeOfOwnedSheetArrayExcludingThis(
       nsWindowSizes&, const nsTArray<RefPtr<StyleSheet>>&) const;
 
   /**
    * If focused element's subtree root is this document or shadow root, return
    * focused element, otherwise, get the shadow host recursively until the
@@ -234,18 +242,16 @@ class DocumentOrShadowRoot {
    */
   Element* GetRetargetedFocusedElement();
 
   nsTArray<RefPtr<StyleSheet>> mStyleSheets;
   RefPtr<StyleSheetList> mDOMStyleSheets;
 
   // Style sheets that are adopted by assinging to the `adoptedStyleSheets`
   // WebIDL atribute. These can only be constructed stylesheets.
-  // TODO(nordzilla): These sheets are not yet applied. These currently
-  // exist only to land the WebIDL attribute (Bug 1608489).
   nsTArray<RefPtr<StyleSheet>> mAdoptedStyleSheets;
 
   /*
    * mIdentifierMap works as follows for IDs:
    * 1) Attribute changes affect the table immediately (removing and adding
    *    entries as needed).
    * 2) Removals from the DOM affect the table immediately
    * 3) Additions to the DOM always update existing entries for names, and add
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -4234,25 +4234,16 @@ double Element::FirstLineBoxBSize() cons
   }
   nsBlockFrame::ConstLineIterator line = frame->LinesBegin();
   nsBlockFrame::ConstLineIterator lineEnd = frame->LinesEnd();
   return line != lineEnd
              ? nsPresContext::AppUnitsToDoubleCSSPixels(line->BSize())
              : 0.0;
 }
 
-#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
-void Element::AssertInvariantsOnNodeInfoChange() {
-  MOZ_DIAGNOSTIC_ASSERT(!IsInComposedDoc());
-  if (nsCOMPtr<Link> link = do_QueryInterface(this)) {
-    MOZ_DIAGNOSTIC_ASSERT(!link->HasPendingLinkUpdate());
-  }
-}
-#endif
-
 // static
 nsAtom* Element::GetEventNameForAttr(nsAtom* aAttr) {
   if (aAttr == nsGkAtoms::onwebkitanimationend) {
     return nsGkAtoms::onwebkitAnimationEnd;
   }
   if (aAttr == nsGkAtoms::onwebkitanimationiteration) {
     return nsGkAtoms::onwebkitAnimationIteration;
   }
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -1439,33 +1439,16 @@ class Element : public FragmentOrElement
     if (index < 0) {
       return BorrowedAttrInfo(nullptr, nullptr);
     }
 
     return mAttrs.AttrInfoAt(index);
   }
 
   /**
-   * Called when we have been adopted, and the information of the
-   * node has been changed.
-   *
-   * The new document can be reached via OwnerDoc().
-   *
-   * If you override this method,
-   * please call up to the parent NodeInfoChanged.
-   *
-   * If you change this, change also the similar method in Link.
-   */
-  virtual void NodeInfoChanged(Document* aOldDoc) {
-#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
-    AssertInvariantsOnNodeInfoChange();
-#endif
-  }
-
-  /**
    * Parse a string into an nsAttrValue for a CORS attribute.  This
    * never fails.  The resulting value is an enumerated value whose
    * GetEnumValue() returns one of the above constants.
    */
   static void ParseCORSValue(const nsAString& aValue, nsAttrValue& aResult);
 
   /**
    * Return the CORS mode for a given string
--- a/dom/base/Link.h
+++ b/dom/base/Link.h
@@ -125,17 +125,17 @@ class Link : public nsISupports {
 
   bool HasPendingLinkUpdate() const { return mHasPendingLinkUpdate; }
   void SetHasPendingLinkUpdate() { mHasPendingLinkUpdate = true; }
   void ClearHasPendingLinkUpdate() { mHasPendingLinkUpdate = false; }
 
   // To ensure correct mHasPendingLinkUpdate handling, we have this method
   // similar to the one in Element. Overriders must call
   // ClearHasPendingLinkUpdate().
-  // If you change this, change also the method in Element.
+  // If you change this, change also the method in nsINode.
   virtual void NodeInfoChanged(Document* aOldDoc) = 0;
 
   bool IsInDNSPrefetch() { return mInDNSPrefetch; }
   void SetIsInDNSPrefetch() { mInDNSPrefetch = true; }
   void ClearIsInDNSPrefetch() { mInDNSPrefetch = false; }
 
  protected:
   virtual ~Link();
--- a/dom/base/ShadowRoot.cpp
+++ b/dom/base/ShadowRoot.cpp
@@ -367,88 +367,115 @@ void ShadowRoot::ApplicableRulesChanged(
   if (Document* doc = GetComposedDoc()) {
     doc->RecordShadowStyleChange(*this);
   }
 }
 
 void ShadowRoot::InsertSheetAt(size_t aIndex, StyleSheet& aSheet) {
   DocumentOrShadowRoot::InsertSheetAt(aIndex, aSheet);
   if (aSheet.IsApplicable()) {
-    InsertSheetIntoAuthorData(aIndex, aSheet);
+    InsertSheetIntoAuthorData(aIndex, aSheet, mStyleSheets);
   }
 }
 
-void ShadowRoot::InsertSheetIntoAuthorData(size_t aIndex, StyleSheet& aSheet) {
-  MOZ_ASSERT(SheetAt(aIndex) == &aSheet);
+void ShadowRoot::InsertAdoptedSheetAt(size_t aIndex, StyleSheet& aSheet) {
+  DocumentOrShadowRoot::InsertAdoptedSheetAt(aIndex, aSheet);
+  if (aSheet.IsApplicable()) {
+    InsertSheetIntoAuthorData(aIndex, aSheet, mAdoptedStyleSheets);
+  }
+}
+
+void ShadowRoot::InsertSheetIntoAuthorData(
+    size_t aIndex, StyleSheet& aSheet,
+    const nsTArray<RefPtr<StyleSheet>>& aList) {
   MOZ_ASSERT(aSheet.IsApplicable());
+  MOZ_ASSERT(aList[aIndex] == &aSheet);
+  MOZ_ASSERT(&aList == &mAdoptedStyleSheets || &aList == &mStyleSheets);
 
   if (!mServoStyles) {
     mServoStyles = Servo_AuthorStyles_Create().Consume();
   }
 
   if (mStyleRuleMap) {
     mStyleRuleMap->SheetAdded(aSheet);
   }
 
-  for (size_t i = aIndex + 1; i < SheetCount(); ++i) {
-    StyleSheet* beforeSheet = SheetAt(i);
+  for (size_t i = aIndex + 1; i < aList.Length(); ++i) {
+    StyleSheet* beforeSheet = aList.ElementAt(i);
     if (!beforeSheet->IsApplicable()) {
       continue;
     }
 
     Servo_AuthorStyles_InsertStyleSheetBefore(mServoStyles.get(), &aSheet,
                                               beforeSheet);
     ApplicableRulesChanged();
     return;
   }
 
-  Servo_AuthorStyles_AppendStyleSheet(mServoStyles.get(), &aSheet);
+  if (mAdoptedStyleSheets.IsEmpty() || &aList == &mAdoptedStyleSheets) {
+    Servo_AuthorStyles_AppendStyleSheet(mServoStyles.get(), &aSheet);
+  } else {
+    Servo_AuthorStyles_InsertStyleSheetBefore(mServoStyles.get(), &aSheet,
+                                              mAdoptedStyleSheets.ElementAt(0));
+  }
   ApplicableRulesChanged();
 }
 
 // FIXME(emilio): This needs to notify document observers and such,
 // presumably.
 void ShadowRoot::StyleSheetApplicableStateChanged(StyleSheet& aSheet) {
-  int32_t index = IndexOfSheet(aSheet);
+  auto& sheetList = aSheet.IsConstructed() ? mAdoptedStyleSheets : mStyleSheets;
+  int32_t index = sheetList.IndexOf(&aSheet);
   if (index < 0) {
     // NOTE(emilio): @import sheets are handled in the relevant RuleAdded
     // notification, which only notifies after the sheet is loaded.
     //
     // This setup causes weirdness in other places, we may want to fix this in
     // bug 1465031.
     MOZ_DIAGNOSTIC_ASSERT(aSheet.GetParentSheet(),
                           "It'd better be an @import sheet");
     return;
   }
   if (aSheet.IsApplicable()) {
-    InsertSheetIntoAuthorData(size_t(index), aSheet);
+    InsertSheetIntoAuthorData(size_t(index), aSheet, sheetList);
   } else {
     MOZ_ASSERT(mServoStyles);
     if (mStyleRuleMap) {
       mStyleRuleMap->SheetRemoved(aSheet);
     }
     Servo_AuthorStyles_RemoveStyleSheet(mServoStyles.get(), &aSheet);
     ApplicableRulesChanged();
   }
 }
 
-void ShadowRoot::RemoveSheet(StyleSheet* aSheet) {
-  MOZ_ASSERT(aSheet);
-  RefPtr<StyleSheet> sheet = DocumentOrShadowRoot::RemoveSheet(*aSheet);
-  MOZ_ASSERT(sheet);
-  if (sheet->IsApplicable()) {
+void ShadowRoot::ClearAdoptedStyleSheets() {
+  for (const RefPtr<StyleSheet>& sheet : mAdoptedStyleSheets) {
+    RemoveSheetFromStyles(*sheet);
+    sheet->RemoveAdopter(*this);
+  }
+  mAdoptedStyleSheets.Clear();
+}
+
+void ShadowRoot::RemoveSheetFromStyles(StyleSheet& aSheet) {
+  if (aSheet.IsApplicable()) {
     MOZ_ASSERT(mServoStyles);
     if (mStyleRuleMap) {
-      mStyleRuleMap->SheetRemoved(*sheet);
+      mStyleRuleMap->SheetRemoved(aSheet);
     }
-    Servo_AuthorStyles_RemoveStyleSheet(mServoStyles.get(), sheet);
+    Servo_AuthorStyles_RemoveStyleSheet(mServoStyles.get(), &aSheet);
     ApplicableRulesChanged();
   }
 }
 
+void ShadowRoot::RemoveSheet(StyleSheet& aSheet) {
+  RefPtr<StyleSheet> sheet = DocumentOrShadowRoot::RemoveSheet(aSheet);
+  MOZ_ASSERT(sheet);
+  RemoveSheetFromStyles(*sheet);
+}
+
 void ShadowRoot::AddToIdTable(Element* aElement, nsAtom* aId) {
   IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aId);
   if (entry) {
     entry->AddIdElement(aElement);
   }
 }
 
 void ShadowRoot::RemoveFromIdTable(Element* aElement, nsAtom* aId) {
@@ -682,8 +709,34 @@ ServoStyleRuleMap& ShadowRoot::ServoStyl
   mStyleRuleMap->EnsureTable(*this);
   return *mStyleRuleMap;
 }
 
 nsresult ShadowRoot::Clone(dom::NodeInfo* aNodeInfo, nsINode** aResult) const {
   *aResult = nullptr;
   return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
 }
+
+// https://wicg.github.io/construct-stylesheets/#dom-documentorshadowroot-adoptedstylesheets
+void ShadowRoot::SetAdoptedStyleSheets(
+    const Sequence<OwningNonNull<StyleSheet>>& aAdoptedStyleSheets,
+    ErrorResult& aRv) {
+  // Step 1 is a variable declaration
+
+  // 2.1 Check if all sheets are constructed, else throw NotAllowedError
+  // 2.2 Check if all sheets' constructor documents match the
+  // DocumentOrShadowRoot's node document, else throw NotAlloweError
+  EnsureAdoptedSheetsAreValid(aAdoptedStyleSheets, aRv);
+  if (aRv.Failed()) {
+    return;
+  }
+
+  // 3. Set the adopted style sheets to the new sheets
+  // TODO(nordzilla): There are optimizations that can be made here
+  // in the case of only appending new sheets.
+  // See https://bugzilla.mozilla.org/show_bug.cgi?id=1611236
+  ClearAdoptedStyleSheets();
+  mAdoptedStyleSheets.SetCapacity(aAdoptedStyleSheets.Length());
+  for (const OwningNonNull<StyleSheet>& sheet : aAdoptedStyleSheets) {
+    sheet->AddAdopter(*this);
+    AppendAdoptedStyleSheet(*sheet);
+  }
+}
--- a/dom/base/ShadowRoot.h
+++ b/dom/base/ShadowRoot.h
@@ -67,51 +67,58 @@ class ShadowRoot final : public Document
                "ShadowRoot always has a host, how did we create "
                "this ShadowRoot?");
     return GetHost();
   }
 
   ShadowRootMode Mode() const { return mMode; }
   bool IsClosed() const { return mMode == ShadowRootMode::Closed; }
 
-  void RemoveSheet(StyleSheet* aSheet);
+  void RemoveSheet(StyleSheet&);
+  void RemoveSheetFromStyles(StyleSheet&);
   void RuleAdded(StyleSheet&, css::Rule&);
   void RuleRemoved(StyleSheet&, css::Rule&);
   void RuleChanged(StyleSheet&, css::Rule*);
   void ImportRuleLoaded(CSSImportRule&, StyleSheet&);
   void SheetCloned(StyleSheet&);
   void StyleSheetApplicableStateChanged(StyleSheet&);
 
   /**
    * Clones internal state, for example stylesheets, of aOther to 'this'.
    */
   void CloneInternalDataFrom(ShadowRoot* aOther);
   void InsertSheetAt(size_t aIndex, StyleSheet&);
+  void InsertAdoptedSheetAt(size_t aIndex, StyleSheet&);
 
   // Calls UnbindFromTree for each of our kids, and also flags us as no longer
   // being connected.
   void Unbind();
 
   // Only intended for UA widgets / special shadow roots, or for handling
   // failure cases when adopting (see BlastSubtreeToPieces).
   //
   // Forgets our shadow host and unbinds all our kids.
   void Unattach();
 
   // Calls BindToTree on each of our kids, and also maybe flags us as being
   // connected.
   nsresult Bind();
 
  private:
-  void InsertSheetIntoAuthorData(size_t aIndex, StyleSheet&);
+  void InsertSheetIntoAuthorData(size_t aIndex, StyleSheet&,
+                                 const nsTArray<RefPtr<StyleSheet>>&);
 
   void AppendStyleSheet(StyleSheet& aSheet) {
     InsertSheetAt(SheetCount(), aSheet);
   }
 
+  void AppendAdoptedStyleSheet(StyleSheet& aSheet) {
+    InsertAdoptedSheetAt(AdoptedSheetCount(), aSheet);
+  }
+
   /**
    * Represents the insertion point in a slot for a given node.
    */
   struct SlotAssignment {
     HTMLSlotElement* mSlot = nullptr;
     Maybe<uint32_t> mIndex;
 
     SlotAssignment() = default;
@@ -163,16 +170,21 @@ class ShadowRoot final : public Document
   }
 
   RawServoAuthorStyles* GetServoStyles() { return mServoStyles.get(); }
 
   mozilla::ServoStyleRuleMap& ServoStyleRuleMap();
 
   JSObject* WrapNode(JSContext*, JS::Handle<JSObject*> aGivenProto) final;
 
+  void NodeInfoChanged(Document* aOldDoc) override {
+    DocumentFragment::NodeInfoChanged(aOldDoc);
+    ClearAdoptedStyleSheets();
+  }
+
   void AddToIdTable(Element* aElement, nsAtom* aId);
   void RemoveFromIdTable(Element* aElement, nsAtom* aId);
 
   // WebIDL methods.
   using mozilla::dom::DocumentOrShadowRoot::GetElementById;
 
   Element* GetActiveElement();
   void GetInnerHTML(nsAString& aInnerHTML);
@@ -239,16 +251,22 @@ class ShadowRoot final : public Document
   virtual bool GetValueMissingState(const nsAString& aName) const override {
     return DocumentOrShadowRoot::GetValueMissingState(aName);
   }
   virtual void SetValueMissingState(const nsAString& aName,
                                     bool aValue) override {
     return DocumentOrShadowRoot::SetValueMissingState(aName, aValue);
   }
 
+  void SetAdoptedStyleSheets(
+      const Sequence<OwningNonNull<StyleSheet>>& aAdoptedStyleSheets,
+      ErrorResult& aRv);
+
+  void ClearAdoptedStyleSheets();
+
  protected:
   // FIXME(emilio): This will need to become more fine-grained.
   void ApplicableRulesChanged();
 
   virtual ~ShadowRoot();
 
   const ShadowRootMode mMode;
 
--- a/dom/base/StaticRange.cpp
+++ b/dom/base/StaticRange.cpp
@@ -40,19 +40,22 @@ template void StaticRange::DoSetRange(co
                                       nsINode* aRootNode);
 template void StaticRange::DoSetRange(const RawRangeBoundary& aStartBoundary,
                                       const RangeBoundary& aEndBoundary,
                                       nsINode* aRootNode);
 template void StaticRange::DoSetRange(const RawRangeBoundary& aStartBoundary,
                                       const RawRangeBoundary& aEndBoundary,
                                       nsINode* aRootNode);
 
+nsTArray<RefPtr<StaticRange>>* StaticRange::sCachedRanges = nullptr;
+
 NS_IMPL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_ADDREF(StaticRange)
-NS_IMPL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(
-    StaticRange, DoSetRange(RawRangeBoundary(), RawRangeBoundary(), nullptr))
+NS_IMPL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_RELEASE_WITH_INTERRUPTABLE_LAST_RELEASE(
+    StaticRange, DoSetRange(RawRangeBoundary(), RawRangeBoundary(), nullptr),
+    AbstractRange::MaybeCacheToReuse(*this))
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(StaticRange)
 NS_INTERFACE_MAP_END_INHERITING(AbstractRange)
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(StaticRange)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(StaticRange, AbstractRange)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mStart)
@@ -61,21 +64,33 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(StaticRange, AbstractRange)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(StaticRange, AbstractRange)
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 // static
+already_AddRefed<StaticRange> StaticRange::Create(nsINode* aNode) {
+  MOZ_ASSERT(aNode);
+  if (!sCachedRanges || sCachedRanges->IsEmpty()) {
+    return do_AddRef(new StaticRange(aNode));
+  }
+  RefPtr<StaticRange> staticRange = sCachedRanges->PopLastElement().forget();
+  staticRange->Init(aNode);
+  return staticRange.forget();
+}
+
+// static
 template <typename SPT, typename SRT, typename EPT, typename ERT>
 already_AddRefed<StaticRange> StaticRange::Create(
     const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
     const RangeBoundaryBase<EPT, ERT>& aEndBoundary, ErrorResult& aRv) {
-  RefPtr<StaticRange> staticRange = new StaticRange(aStartBoundary.Container());
+  RefPtr<StaticRange> staticRange =
+      StaticRange::Create(aStartBoundary.Container());
   staticRange->DoSetRange(aStartBoundary, aEndBoundary, nullptr);
 
   return staticRange.forget();
 }
 
 template <typename SPT, typename SRT, typename EPT, typename ERT>
 void StaticRange::DoSetRange(const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
                              const RangeBoundaryBase<EPT, ERT>& aEndBoundary,
--- a/dom/base/StaticRange.h
+++ b/dom/base/StaticRange.h
@@ -6,32 +6,38 @@
 
 #ifndef mozilla_dom_StaticRange_h
 #define mozilla_dom_StaticRange_h
 
 #include "mozilla/ErrorResult.h"
 #include "mozilla/RangeBoundary.h"
 #include "mozilla/dom/AbstractRange.h"
 #include "mozilla/dom/StaticRangeBinding.h"
+#include "nsTArray.h"
 #include "nsWrapperCache.h"
 
 namespace mozilla {
 namespace dom {
 
 class StaticRange final : public AbstractRange {
  public:
-  explicit StaticRange(nsINode* aNode) : AbstractRange(aNode) {}
   StaticRange() = delete;
   explicit StaticRange(const StaticRange& aOther) = delete;
 
   static already_AddRefed<StaticRange> Constructor(const GlobalObject& global,
                                                    const StaticRangeInit& init,
                                                    ErrorResult& aRv);
 
   /**
+   * The following Create() returns `nsRange` instance which is initialized
+   * only with aNode.  The result is never positioned.
+   */
+  static already_AddRefed<StaticRange> Create(nsINode* aNode);
+
+  /**
    * Create() may return `StaticRange` instance which is initialized with
    * given range or points.  If it fails initializing new range with the
    * arguments, returns `nullptr`.  `ErrorResult` is set to an error only
    * when this returns `nullptr`.  The error code indicates the reason why
    * it couldn't initialize the instance.
    */
   static already_AddRefed<StaticRange> Create(
       const AbstractRange* aAbstractRange, ErrorResult& aRv) {
@@ -49,22 +55,21 @@ class StaticRange final : public Abstrac
                                aRv);
   }
   template <typename SPT, typename SRT, typename EPT, typename ERT>
   static already_AddRefed<StaticRange> Create(
       const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
       const RangeBoundaryBase<EPT, ERT>& aEndBoundary, ErrorResult& aRv);
 
  protected:
+  explicit StaticRange(nsINode* aNode) : AbstractRange(aNode) {}
   virtual ~StaticRange() = default;
 
  public:
   NS_DECL_ISUPPORTS_INHERITED
-  // Need to override this for calling `DoSetRange()` from
-  // `NS_IMPL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE()`.
   NS_IMETHODIMP_(void) DeleteCycleCollectable(void) override;
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(StaticRange,
                                                          AbstractRange)
 
   JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final;
 
   /**
    * SetStartAndEnd() works similar to call both SetStart() and SetEnd().
@@ -96,15 +101,17 @@ class StaticRange final : public Abstrac
    * @param aEndBoundary    Computed end point.
    * @param aRootNode       The root node.
    */
   template <typename SPT, typename SRT, typename EPT, typename ERT>
   void DoSetRange(const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
                   const RangeBoundaryBase<EPT, ERT>& aEndBoundary,
                   nsINode* aRootNode);
 
+  static nsTArray<RefPtr<StaticRange>>* sCachedRanges;
+
   friend class AbstractRange;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // #ifndef mozilla_dom_StaticRange_h
--- a/dom/base/nsGlobalWindowInner.cpp
+++ b/dom/base/nsGlobalWindowInner.cpp
@@ -804,17 +804,17 @@ class PromiseDocumentFlushedResolver fin
 
   void Call() {
     nsMutationGuard guard;
     ErrorResult error;
     JS::Rooted<JS::Value> returnVal(RootingCx());
     mCallback->Call(&returnVal, error);
 
     if (error.Failed()) {
-      mPromise->MaybeReject(error);
+      mPromise->MaybeReject(std::move(error));
     } else if (guard.Mutated(0)) {
       // Something within the callback mutated the DOM.
       mPromise->MaybeReject(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
     } else {
       mPromise->MaybeResolve(returnVal);
     }
   }
 
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -6977,45 +6977,45 @@ void nsGlobalWindowOuter::MaybeAllowStor
   Unused << AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(
       principal, inner, AntiTrackingCommon::eOpener);
 }
 
 //*****************************************************************************
 // nsGlobalWindowOuter: Helper Functions
 //*****************************************************************************
 
-already_AddRefed<nsIDocShellTreeOwner> nsGlobalWindowOuter::GetTreeOwner() {
+already_AddRefed<nsIDocShellTreeOwner> nsPIDOMWindowOuter::GetTreeOwner() {
   // If there's no docShellAsItem, this window must have been closed,
   // in that case there is no tree owner.
 
   if (!mDocShell) {
     return nullptr;
   }
 
   nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
   mDocShell->GetTreeOwner(getter_AddRefs(treeOwner));
   return treeOwner.forget();
 }
 
-already_AddRefed<nsIBaseWindow> nsGlobalWindowOuter::GetTreeOwnerWindow() {
+already_AddRefed<nsIBaseWindow> nsPIDOMWindowOuter::GetTreeOwnerWindow() {
   nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
 
   // If there's no mDocShell, this window must have been closed,
   // in that case there is no tree owner.
 
   if (mDocShell) {
     mDocShell->GetTreeOwner(getter_AddRefs(treeOwner));
   }
 
   nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(treeOwner);
   return baseWindow.forget();
 }
 
 already_AddRefed<nsIWebBrowserChrome>
-nsGlobalWindowOuter::GetWebBrowserChrome() {
+nsPIDOMWindowOuter::GetWebBrowserChrome() {
   nsCOMPtr<nsIDocShellTreeOwner> treeOwner = GetTreeOwner();
 
   nsCOMPtr<nsIWebBrowserChrome> browserChrome = do_GetInterface(treeOwner);
   return browserChrome.forget();
 }
 
 nsIScrollableFrame* nsGlobalWindowOuter::GetScrollFrame() {
   if (!mDocShell) {
--- a/dom/base/nsGlobalWindowOuter.h
+++ b/dom/base/nsGlobalWindowOuter.h
@@ -805,20 +805,16 @@ class nsGlobalWindowOuter final : public
                         const nsAString& aOptions, bool aDialog,
                         bool aContentModal, bool aCalledNoScript,
                         bool aDoJSFixups, bool aNavigate, nsIArray* argv,
                         nsISupports* aExtraArgument,
                         nsDocShellLoadState* aLoadState, bool aForceNoOpener,
                         mozilla::dom::BrowsingContext** aReturn);
 
  public:
-  // Helper Functions
-  already_AddRefed<nsIDocShellTreeOwner> GetTreeOwner();
-  already_AddRefed<nsIBaseWindow> GetTreeOwnerWindow();
-  already_AddRefed<nsIWebBrowserChrome> GetWebBrowserChrome();
   nsresult SecurityCheckURL(const char* aURL, nsIURI** aURI);
 
   bool PopupWhitelisted();
   mozilla::dom::PopupBlocker::PopupControlState RevisePopupAbuseLevel(
       mozilla::dom::PopupBlocker::PopupControlState aState);
   void FireAbuseEvents(const nsAString& aPopupURL,
                        const nsAString& aPopupWindowName,
                        const nsAString& aPopupWindowFeatures);
--- a/dom/base/nsINode.cpp
+++ b/dom/base/nsINode.cpp
@@ -28,16 +28,17 @@
 #include "mozilla/Telemetry.h"
 #include "mozilla/TextEditor.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/dom/BindContext.h"
 #include "mozilla/dom/CharacterData.h"
 #include "mozilla/dom/DocumentType.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/Event.h"
+#include "mozilla/dom/Link.h"
 #include "mozilla/dom/HTMLImageElement.h"
 #include "mozilla/dom/HTMLMediaElement.h"
 #include "mozilla/dom/HTMLTemplateElement.h"
 #include "mozilla/dom/MutationObservers.h"
 #include "mozilla/dom/Selection.h"
 #include "mozilla/dom/ShadowRoot.h"
 #include "mozilla/dom/SVGUseElement.h"
 #include "mozilla/dom/ScriptSettings.h"
@@ -188,16 +189,25 @@ nsINode::nsINode(already_AddRefed<mozill
 }
 #endif
 
 nsINode::~nsINode() {
   MOZ_ASSERT(!HasSlots(), "LastRelease was not called?");
   MOZ_ASSERT(mSubtreeRoot == this, "Didn't restore state properly?");
 }
 
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+void nsINode::AssertInvariantsOnNodeInfoChange() {
+  MOZ_DIAGNOSTIC_ASSERT(!IsInComposedDoc());
+  if (nsCOMPtr<Link> link = do_QueryInterface(this)) {
+    MOZ_DIAGNOSTIC_ASSERT(!link->HasPendingLinkUpdate());
+  }
+}
+#endif
+
 void* nsINode::GetProperty(const nsAtom* aPropertyName,
                            nsresult* aStatus) const {
   if (!HasProperties()) {  // a fast HasFlag() test
     if (aStatus) {
       *aStatus = NS_PROPTABLE_PROP_NOT_THERE;
     }
     return nullptr;
   }
@@ -3062,19 +3072,17 @@ already_AddRefed<nsINode> nsINode::Clone
       //
       // When this fails, it removes all properties for the node anyway, so no
       // extra error handling needed.
       Unused << oldDoc->PropertyTable().TransferOrRemoveAllPropertiesFor(
           aNode, newDoc->PropertyTable());
     }
 
     aNode->mNodeInfo.swap(newNodeInfo);
-    if (elem) {
-      elem->NodeInfoChanged(oldDoc);
-    }
+    aNode->NodeInfoChanged(oldDoc);
 
     MOZ_ASSERT(newDoc != oldDoc);
     if (elem) {
       // Adopted callback must be enqueued whenever a node’s
       // shadow-including inclusive descendants that is custom.
       CustomElementData* data = elem->GetCustomElementData();
       if (data && data->mState == CustomElementData::State::eCustom) {
         LifecycleAdoptedCallbackArgs args = {oldDoc, newDoc};
@@ -3159,19 +3167,17 @@ already_AddRefed<nsINode> nsINode::Clone
           }
           if (hadProperties) {
             // NOTE: When it fails it removes all properties for the node
             // anyway, so no extra error handling needed.
             Unused << newDoc->PropertyTable().TransferOrRemoveAllPropertiesFor(
                 aNode, oldDoc->PropertyTable());
           }
           aNode->mNodeInfo.swap(newNodeInfo);
-          if (elem) {
-            elem->NodeInfoChanged(newDoc);
-          }
+          aNode->NodeInfoChanged(newDoc);
           if (wasRegistered) {
             oldDoc->RegisterActivityObserver(aNode->AsElement());
           }
           return nullptr;
         }
       }
     }
   }
--- a/dom/base/nsINode.h
+++ b/dom/base/nsINode.h
@@ -263,16 +263,19 @@ class nsNodeWeakReference final : public
   }
 
 /**
  * An internal interface that abstracts some DOMNode-related parts that both
  * nsIContent and Document share.  An instance of this interface has a list
  * of nsIContent children and provides access to them.
  */
 class nsINode : public mozilla::dom::EventTarget {
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+  void AssertInvariantsOnNodeInfoChange();
+#endif
  public:
   typedef mozilla::dom::BoxQuadOptions BoxQuadOptions;
   typedef mozilla::dom::ConvertCoordinateOptions ConvertCoordinateOptions;
   typedef mozilla::dom::DocGroup DocGroup;
   typedef mozilla::dom::Document Document;
   typedef mozilla::dom::DOMPoint DOMPoint;
   typedef mozilla::dom::DOMPointInit DOMPointInit;
   typedef mozilla::dom::DOMQuad DOMQuad;
@@ -675,16 +678,33 @@ class nsINode : public mozilla::dom::Eve
   const nsString& LocalName() const { return mNodeInfo->LocalName(); }
 
   /**
    * Get the NodeInfo for this element
    * @return the nodes node info
    */
   inline mozilla::dom::NodeInfo* NodeInfo() const { return mNodeInfo; }
 
+  /**
+   * Called when we have been adopted, and the information of the
+   * node has been changed.
+   *
+   * The new document can be reached via OwnerDoc().
+   *
+   * If you override this method,
+   * please call up to the parent NodeInfoChanged.
+   *
+   * If you change this, change also the similar method in Link.
+   */
+  virtual void NodeInfoChanged(Document* aOldDoc) {
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+    AssertInvariantsOnNodeInfoChange();
+#endif
+  }
+
   inline bool IsInNamespace(int32_t aNamespace) const {
     return mNodeInfo->NamespaceID() == aNamespace;
   }
 
   /**
    * Returns the DocGroup of the "node document" of this node.
    */
   DocGroup* GetDocGroup() const;
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -23,27 +23,30 @@
 #define DOM_WINDOW_DESTROYED_TOPIC "dom-window-destroyed"
 #define DOM_WINDOW_FROZEN_TOPIC "dom-window-frozen"
 #define DOM_WINDOW_THAWED_TOPIC "dom-window-thawed"
 
 class nsDOMOfflineResourceList;
 class nsGlobalWindowInner;
 class nsGlobalWindowOuter;
 class nsIArray;
+class nsIBaseWindow;
 class nsIChannel;
 class nsIContent;
 class nsIContentSecurityPolicy;
 class nsICSSDeclaration;
 class nsIDocShell;
+class nsIDocShellTreeOwner;
 class nsDocShellLoadState;
 class nsIPrincipal;
 class nsIRunnable;
 class nsIScriptTimeoutHandler;
 class nsISerialEventTarget;
 class nsIURI;
+class nsIWebBrowserChrome;
 class nsPIDOMWindowInner;
 class nsPIDOMWindowOuter;
 class nsPIWindowRoot;
 
 typedef uint32_t SuspendTypes;
 
 namespace mozilla {
 namespace dom {
@@ -1057,16 +1060,20 @@ class nsPIDOMWindowOuter : public mozIDO
    * and TakeOpenerForInitialContentBrowser is used by nsXULElement in order to
    * take the value set earlier, and null out the value in the window.
    */
   void SetOpenerForInitialContentBrowser(
       mozilla::dom::BrowsingContext* aOpener);
   already_AddRefed<mozilla::dom::BrowsingContext>
   TakeOpenerForInitialContentBrowser();
 
+  already_AddRefed<nsIDocShellTreeOwner> GetTreeOwner();
+  already_AddRefed<nsIBaseWindow> GetTreeOwnerWindow();
+  already_AddRefed<nsIWebBrowserChrome> GetWebBrowserChrome();
+
  protected:
   // Lazily instantiate an about:blank document if necessary, and if
   // we have what it takes to do so.
   void MaybeCreateDoc();
 
   void SetChromeEventHandlerInternal(
       mozilla::dom::EventTarget* aChromeEventHandler);
 
--- a/dom/base/nsRange.cpp
+++ b/dom/base/nsRange.cpp
@@ -119,17 +119,16 @@ static void InvalidateAllFrames(nsINode*
   }
 }
 
 /******************************************************
  * constructor/destructor
  ******************************************************/
 
 nsTArray<RefPtr<nsRange>>* nsRange::sCachedRanges = nullptr;
-bool nsRange::sHasShutDown = false;
 
 nsRange::~nsRange() {
   NS_ASSERTION(!IsInSelection(), "deleting nsRange that is in use");
 
   // we want the side effects (releases and list removals)
   DoSetRange(RawRangeBoundary(), RawRangeBoundary(), nullptr);
 }
 
@@ -145,53 +144,20 @@ nsRange::nsRange(nsINode* aNode)
 
 /* static */
 already_AddRefed<nsRange> nsRange::Create(nsINode* aNode) {
   MOZ_ASSERT(aNode);
   if (!sCachedRanges || sCachedRanges->IsEmpty()) {
     return do_AddRef(new nsRange(aNode));
   }
   RefPtr<nsRange> range = sCachedRanges->PopLastElement().forget();
-  range->mOwner = aNode->OwnerDoc();
+  range->Init(aNode);
   return range.forget();
 }
 
-bool nsRange::MaybeCacheToReuse() {
-  static const size_t kMaxRangeCache = 64;
-
-  // If we are not used by JS and the cache is not yet full, we should reuse
-  // this instance.  Otherwise, delete it.
-  if (sHasShutDown || GetWrapperMaybeDead() || GetFlags() ||
-      (sCachedRanges && sCachedRanges->Length() == kMaxRangeCache)) {
-    return false;
-  }
-
-  ClearForReuse();
-  MOZ_ASSERT(!mRoot);
-  MOZ_ASSERT(!mRegisteredClosestCommonInclusiveAncestor);
-  MOZ_ASSERT(!mNextStartRef);
-  MOZ_ASSERT(!mNextEndRef);
-
-  if (!sCachedRanges) {
-    sCachedRanges = new nsTArray<RefPtr<nsRange>>(16);
-  }
-  sCachedRanges->AppendElement(this);
-  return true;
-}
-
-/* static */
-void nsRange::Shutdown() {
-  sHasShutDown = true;
-  if (nsTArray<RefPtr<nsRange>>* cachedRanges = sCachedRanges) {
-    sCachedRanges = nullptr;
-    cachedRanges->Clear();
-    delete cachedRanges;
-  }
-}
-
 /* static */
 template <typename SPT, typename SRT, typename EPT, typename ERT>
 already_AddRefed<nsRange> nsRange::Create(
     const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
     const RangeBoundaryBase<EPT, ERT>& aEndBoundary, ErrorResult& aRv) {
   // If we fail to initialize the range a lot, nsRange should have a static
   // initializer since the allocation cost is not cheap in hot path.
   RefPtr<nsRange> range = nsRange::Create(aStartBoundary.Container());
@@ -202,45 +168,19 @@ already_AddRefed<nsRange> nsRange::Creat
   return range.forget();
 }
 
 /******************************************************
  * nsISupports
  ******************************************************/
 
 NS_IMPL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_ADDREF(nsRange)
-NS_IMETHODIMP_(MozExternalRefCountType) nsRange::Release() {
-  MOZ_ASSERT(0 != mRefCnt, "dup release");
-  NS_ASSERT_OWNINGTHREAD(nsRange);
-  nsISupports* base = NS_CYCLE_COLLECTION_CLASSNAME(nsRange)::Upcast(this);
-  bool shouldDelete = false;
-  nsrefcnt count =
-      mRefCnt.decr<NS_CycleCollectorSuspectUsingNursery>(base, &shouldDelete);
-  NS_LOG_RELEASE(this, count, "nsRange");
-  if (count == 0) {
-    // Now, release any objects which may be owned by this instance and
-    // stop observing the DOM tree mutation, etc, before deletion.
-    mRefCnt.incr<NS_CycleCollectorSuspectUsingNursery>(base);
-    DoSetRange(RawRangeBoundary(), RawRangeBoundary(), nullptr);
-    mRefCnt.decr<NS_CycleCollectorSuspectUsingNursery>(base);
-    // If we can be cached, we'll be grabbed by sCachedRanges so that
-    // we should stop deleting the instance.
-    if (MaybeCacheToReuse()) {
-      MOZ_ASSERT(mRefCnt.get() > 0);
-      return mRefCnt.get();
-    }
-    if (shouldDelete) {
-      mRefCnt.stabilizeForDeletion();
-      DeleteCycleCollectable();
-    }
-  }
-  return count;
-}
-
-NS_IMETHODIMP_(void) nsRange::DeleteCycleCollectable() { delete this; }
+NS_IMPL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_RELEASE_WITH_INTERRUPTABLE_LAST_RELEASE(
+    nsRange, DoSetRange(RawRangeBoundary(), RawRangeBoundary(), nullptr),
+    MaybeInterruptLastRelease())
 
 // QueryInterface implementation for nsRange
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsRange)
   NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
 NS_INTERFACE_MAP_END_INHERITING(AbstractRange)
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsRange)
 
@@ -261,16 +201,22 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsRange, AbstractRange)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRoot)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(nsRange, AbstractRange)
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
+bool nsRange::MaybeInterruptLastRelease() {
+  bool interrupt = AbstractRange::MaybeCacheToReuse(*this);
+  MOZ_ASSERT(!interrupt || IsCleared());
+  return interrupt;
+}
+
 static void MarkDescendants(nsINode* aNode) {
   // Set NodeIsDescendantOfClosestCommonInclusiveAncestorForRangeInSelection on
   // aNode's descendants unless aNode is already marked as a range common
   // ancestor or a descendant of one, in which case all of our descendants have
   // the bit set already.
   if (!aNode->IsSelectionDescendant()) {
     // don't set the Descendant bit on |aNode| itself
     nsINode* node = aNode->GetNextNode(aNode);
--- a/dom/base/nsRange.h
+++ b/dom/base/nsRange.h
@@ -45,25 +45,18 @@ class nsRange final : public mozilla::do
   typedef mozilla::dom::DOMRect DOMRect;
   typedef mozilla::dom::DOMRectList DOMRectList;
   typedef mozilla::RangeBoundary RangeBoundary;
   typedef mozilla::RawRangeBoundary RawRangeBoundary;
 
   virtual ~nsRange();
   explicit nsRange(nsINode* aNode);
 
-  bool MaybeCacheToReuse();
-
  public:
   /**
-   * Called when the process is shutting down.
-   */
-  static void Shutdown();
-
-  /**
    * The following Create() returns `nsRange` instance which is initialized
    * only with aNode.  The result is never positioned.
    */
   static already_AddRefed<nsRange> Create(nsINode* aNode);
 
   /**
    * The following Create() may return `nsRange` instance which is initialized
    * with given range or points.  If it fails initializing new range with the
@@ -427,30 +420,38 @@ class nsRange final : public mozilla::do
       mCommonAncestor = mRange->GetRegisteredClosestCommonInclusiveAncestor();
     }
     ~AutoInvalidateSelection();
     nsRange* mRange;
     RefPtr<nsINode> mCommonAncestor;
     static bool sIsNested;
   };
 
+  bool MaybeInterruptLastRelease();
+
+#ifdef DEBUG
+  bool IsCleared() const {
+    return !mRoot && !mRegisteredClosestCommonInclusiveAncestor &&
+           !mSelection && !mNextStartRef && !mNextEndRef;
+  }
+#endif  // #ifdef DEBUG
+
   nsCOMPtr<nsINode> mRoot;
   // mRegisteredClosestCommonInclusiveAncestor is only non-null when the range
   // IsInSelection().  It's kept alive via mStart/mEnd,
   // because we update it any time those could become disconnected from it.
   nsINode* MOZ_NON_OWNING_REF mRegisteredClosestCommonInclusiveAncestor;
   mozilla::WeakPtr<mozilla::dom::Selection> mSelection;
 
   // These raw pointers are used to remember a child that is about
   // to be inserted between a CharacterData call and a subsequent
   // ContentInserted or ContentAppended call. It is safe to store
   // these refs because the caller is guaranteed to trigger both
   // notifications while holding a strong reference to the new child.
   nsIContent* MOZ_NON_OWNING_REF mNextStartRef;
   nsIContent* MOZ_NON_OWNING_REF mNextEndRef;
 
   static nsTArray<RefPtr<nsRange>>* sCachedRanges;
-  static bool sHasShutDown;
 
   friend class mozilla::dom::AbstractRange;
 };
 
 #endif /* nsRange_h___ */
--- a/dom/base/nsStyleLinkElement.cpp
+++ b/dom/base/nsStyleLinkElement.cpp
@@ -255,19 +255,19 @@ nsStyleLinkElement::DoUpdateStyleSheet(D
                "there should not be a old document and old "
                "ShadowRoot simultaneously.");
 
     // We're removing the link element from the document or shadow tree,
     // unload the stylesheet.  We want to do this even if updates are
     // disabled, since otherwise a sheet with a stale linking element pointer
     // will be hanging around -- not good!
     if (aOldShadowRoot) {
-      aOldShadowRoot->RemoveSheet(mStyleSheet);
+      aOldShadowRoot->RemoveSheet(*mStyleSheet);
     } else {
-      aOldDocument->RemoveStyleSheet(mStyleSheet);
+      aOldDocument->RemoveStyleSheet(*mStyleSheet);
     }
 
     SetStyleSheet(nullptr);
   }
 
   Document* doc = thisContent->GetComposedDoc();
 
   // Loader could be null during unlink, see bug 1425866.
@@ -292,20 +292,20 @@ nsStyleLinkElement::DoUpdateStyleSheet(D
     }
   }
 
   if (mStyleSheet) {
     if (thisContent->IsInShadowTree()) {
       ShadowRoot* containingShadow = thisContent->GetContainingShadow();
       // Could be null only during unlink.
       if (MOZ_LIKELY(containingShadow)) {
-        containingShadow->RemoveSheet(mStyleSheet);
+        containingShadow->RemoveSheet(*mStyleSheet);
       }
     } else {
-      doc->RemoveStyleSheet(mStyleSheet);
+      doc->RemoveStyleSheet(*mStyleSheet);
     }
 
     nsStyleLinkElement::SetStyleSheet(nullptr);
   }
 
   if (!info) {
     return Update{};
   }
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -1864,26 +1864,17 @@ class CGClassConstructor(CGAbstractStati
         rawConditions = getRawConditionList(self._ctor, "cx", "obj", alreadySecureContext)
         if len(rawConditions) > 0:
             notConditions = " ||\n".join("!" + cond for cond in rawConditions)
             failedCheckAction = CGGeneric("return ThrowingConstructor(cx, argc, vp);\n")
             conditionsCheck = CGIfWrapper(failedCheckAction, notConditions).define() + "\n"
 
         # Additionally, we want to throw if a caller does a bareword invocation
         # of a constructor without |new|.
-        #
-        # Figure out the name of our constructor for error reporting purposes.
-        # For unnamed webidl constructors, identifier.name is "constructor" but
-        # the name JS sees is the interface name; for named constructors
-        # identifier.name is the actual name.
-        name = self._ctor.identifier.name
-        if name != "constructor":
-            ctorName = name
-        else:
-            ctorName = self.descriptor.interface.identifier.name
+        ctorName = GetConstructorNameForReporting(self.descriptor, self._ctor)
 
         preamble = fill(
             """
             JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
             JS::Rooted<JSObject*> obj(cx, &args.callee());
             $*{conditionsCheck}
             if (!args.isConstructing()) {
               return ThrowConstructorWithoutNew(cx, "${ctorName}");
@@ -1899,33 +1890,27 @@ class CGClassConstructor(CGAbstractStati
             """,
             conditionsCheck=conditionsCheck,
             ctorName=ctorName,
             name=self.descriptor.name)
 
         name = self._ctor.identifier.name
         nativeName = MakeNativeName(self.descriptor.binaryNameFor(name))
         callGenerator = CGMethodCall(nativeName, True, self.descriptor,
-                                     self._ctor, isConstructor=True,
-                                     constructorName=ctorName)
+                                     self._ctor, isConstructor=True)
         return preamble + "\n" + callGenerator.define()
 
     def auto_profiler_label(self):
-        name = self._ctor.identifier.name
-        if name != "constructor":
-            ctorName = name
-        else:
-            ctorName = self.descriptor.interface.identifier.name
         return fill(
             """
             AUTO_PROFILER_LABEL_DYNAMIC_FAST(
               "${ctorName}", "constructor", DOM, cx,
               uint32_t(js::ProfilingStackFrame::Flags::RELEVANT_FOR_JS));
             """,
-            ctorName=ctorName)
+            ctorName=GetConstructorNameForReporting(self.descriptor, self._ctor))
 
 
 def NamedConstructorName(m):
     return '_' + m.identifier.name
 
 
 class CGNamedConstructors(CGThing):
     def __init__(self, descriptor):
@@ -7956,21 +7941,17 @@ class CGPerSignatureCall(CGThing):
         if idlNode.isMethod() and idlNode.isLegacycaller():
             # If we can have legacycaller with identifier, we can't
             # just use the idlNode to determine whether we're
             # generating code for the legacycaller or not.
             assert idlNode.isIdentifierLess()
             # Pass in our thisVal
             argsPre.append("args.thisv()")
 
-        if isConstructor:
-            ourName = "%s constructor" % descriptor.interface.identifier.name
-        else:
-            ourName = "%s.%s" % (descriptor.interface.identifier.name,
-                                 GetWebExposedName(idlNode, descriptor))
+        ourName = GetLabelForErrorReporting(descriptor, idlNode, isConstructor)
 
         if idlNode.isMethod():
             argDescription = "argument %(index)d of " + ourName
         elif setter:
             argDescription = "value being assigned to %s" % ourName
         else:
             assert self.argCount == 0
 
@@ -8352,24 +8333,20 @@ class CGCase(CGList):
 
 
 class CGMethodCall(CGThing):
     """
     A class to generate selection of a method signature from a set of
     signatures and generation of a call to that signature.
     """
     def __init__(self, nativeMethodName, static, descriptor, method,
-                 isConstructor=False, constructorName=None):
+                 isConstructor=False):
         CGThing.__init__(self)
 
-        if isConstructor:
-            assert constructorName is not None
-            methodName = constructorName
-        else:
-            methodName = "%s.%s" % (descriptor.interface.identifier.name, method.identifier.name)
+        methodName = GetLabelForErrorReporting(descriptor, method, isConstructor)
         argDesc = "argument %d of " + methodName
 
         if method.getExtendedAttribute("UseCounter"):
             useCounterName = methodName.replace(".", "_")
         else:
             useCounterName = None
 
         if method.isStatic():
@@ -8941,16 +8918,44 @@ def MakeNativeName(name):
 
 
 def GetWebExposedName(idlObject, descriptor):
     if idlObject == descriptor.operations['Stringifier']:
         return "toString"
     return idlObject.identifier.name
 
 
+def GetConstructorNameForReporting(descriptor, ctor):
+    # Figure out the name of our constructor for reporting purposes.
+    # For unnamed webidl constructors, identifier.name is "constructor" but
+    # the name JS sees is the interface name; for named constructors
+    # identifier.name is the actual name.
+    ctorName = ctor.identifier.name
+    if ctorName == "constructor":
+        return descriptor.interface.identifier.name
+    return ctorName
+
+
+def GetLabelForErrorReporting(descriptor, idlObject, isConstructor):
+    """
+    descriptor is the descriptor for the interface involved
+
+    idlObject is the method (regular or static), attribute (regular or
+    static), or constructor (named or not) involved.
+
+    isConstructor is true if idlObject is a constructor and false otherwise.
+    """
+    if isConstructor:
+        return "%s constructor" % GetConstructorNameForReporting(descriptor, idlObject)
+
+    namePrefix = descriptor.interface.identifier.name
+
+    return "%s.%s" % (namePrefix, GetWebExposedName(idlObject, descriptor))
+
+
 class CGSpecializedMethod(CGAbstractStaticMethod):
     """
     A class for generating the C++ code for a specialized method that the JIT
     can call with lower overhead.
     """
     def __init__(self, descriptor, method):
         self.method = method
         name = CppKeywords.checkMethodName(IDLToCIdentifier(method.identifier.name))
--- a/dom/bindings/ErrorIPCUtils.h
+++ b/dom/bindings/ErrorIPCUtils.h
@@ -90,16 +90,18 @@ struct ParamTraits<mozilla::CopyableErro
   typedef mozilla::CopyableErrorResult paramType;
 
   static void Write(Message* aMsg, const paramType& aParam) {
     ParamTraits<mozilla::ErrorResult>::Write(aMsg, aParam);
   }
 
   static bool Read(const Message* aMsg, PickleIterator* aIter,
                    paramType* aResult) {
-    mozilla::ErrorResult& ref = static_cast<mozilla::ErrorResult&>(*aResult);
-    return ParamTraits<mozilla::ErrorResult>::Read(aMsg, aIter, &ref);
+    // We can't cast *aResult to ErrorResult&, so cheat and just cast
+    // to ErrorResult*.
+    return ParamTraits<mozilla::ErrorResult>::Read(
+        aMsg, aIter, reinterpret_cast<mozilla::ErrorResult*>(aResult));
   }
 };
 
 }  // namespace IPC
 
 #endif
--- a/dom/bindings/ErrorResult.h
+++ b/dom/bindings/ErrorResult.h
@@ -128,16 +128,17 @@ struct StringArrayAppender {
     Append(aArgs, aCount - 1, std::forward<Ts>(aOtherArgs)...);
   }
 };
 
 }  // namespace dom
 
 class ErrorResult;
 class OOMReporter;
+class CopyableErrorResult;
 
 namespace binding_danger {
 
 /**
  * Templated implementation class for various ErrorResult-like things.  The
  * instantiations differ only in terms of their cleanup policies (used in the
  * destructor), which they can specify via the template argument.  Note that
  * this means it's safe to reinterpret_cast between the instantiations unless
@@ -666,16 +667,20 @@ class ErrorResult : public binding_dange
   typedef binding_danger::TErrorResult<
       binding_danger::AssertAndSuppressCleanupPolicy>
       BaseErrorResult;
 
  public:
   ErrorResult() : BaseErrorResult() {}
 
   ErrorResult(ErrorResult&& aRHS) : BaseErrorResult(std::move(aRHS)) {}
+  // Explicitly allow moving out of a CopyableErrorResult into an ErrorResult.
+  // This is implemented below so it can see the definition of
+  // CopyableErrorResult.
+  inline explicit ErrorResult(CopyableErrorResult&& aRHS);
 
   explicit ErrorResult(nsresult aRv) : BaseErrorResult(aRv) {}
 
   // This operator is deprecated and ideally shouldn't be used.
   void operator=(nsresult rv) { BaseErrorResult::operator=(rv); }
 
   ErrorResult& operator=(ErrorResult&& aRHS) {
     BaseErrorResult::operator=(std::move(aRHS));
@@ -781,18 +786,33 @@ class CopyableErrorResult
     if (aRight.IsJSException()) {
       SuppressException();
       Throw(NS_ERROR_FAILURE);
     } else {
       aRight.CloneTo(*this);
     }
     return *this;
   }
+
+  // Disallow implicit converstion to non-const ErrorResult&, because that would
+  // allow people to throw exceptions on us while bypassing our checks for JS
+  // exceptions.
+  operator ErrorResult&() = delete;
+
+  // Allow conversion to ErrorResult&& so we can move out of ourselves into
+  // an ErrorResult.
+  operator ErrorResult &&() && {
+    auto* val = reinterpret_cast<ErrorResult*>(this);
+    return std::move(*val);
+  }
 };
 
+inline ErrorResult::ErrorResult(CopyableErrorResult&& aRHS)
+    : ErrorResult(reinterpret_cast<ErrorResult&&>(aRHS)) {}
+
 namespace dom {
 namespace binding_detail {
 class FastErrorResult : public mozilla::binding_danger::TErrorResult<
                             mozilla::binding_danger::JustAssertCleanupPolicy> {
 };
 }  // namespace binding_detail
 }  // namespace dom
 
--- a/dom/bindings/ToJSValue.cpp
+++ b/dom/bindings/ToJSValue.cpp
@@ -38,17 +38,17 @@ bool ToJSValue(JSContext* aCx, const nsA
 }
 
 bool ToJSValue(JSContext* aCx, nsresult aArgument,
                JS::MutableHandle<JS::Value> aValue) {
   RefPtr<Exception> exception = CreateException(aArgument);
   return ToJSValue(aCx, exception, aValue);
 }
 
-bool ToJSValue(JSContext* aCx, ErrorResult& aArgument,
+bool ToJSValue(JSContext* aCx, ErrorResult&& aArgument,
                JS::MutableHandle<JS::Value> aValue) {
   MOZ_ASSERT(aArgument.Failed());
   MOZ_ASSERT(
       !aArgument.IsUncatchableException(),
       "Doesn't make sense to convert uncatchable exception to a JS value!");
   MOZ_ALWAYS_TRUE(aArgument.MaybeSetPendingException(aCx));
   MOZ_ALWAYS_TRUE(JS_GetPendingException(aCx, aValue));
   JS_ClearPendingException(aCx);
--- a/dom/bindings/ToJSValue.h
+++ b/dom/bindings/ToJSValue.h
@@ -298,17 +298,17 @@ MOZ_MUST_USE inline bool ToJSValue(JSCon
 // Accept nsresult, for use in rejections, and create an XPCOM
 // exception object representing that nsresult.
 MOZ_MUST_USE bool ToJSValue(JSContext* aCx, nsresult aArgument,
                             JS::MutableHandle<JS::Value> aValue);
 
 // Accept ErrorResult, for use in rejections, and create an exception
 // representing the failure.  Note, the ErrorResult must indicate a failure
 // with aArgument.Failure() returning true.
-MOZ_MUST_USE bool ToJSValue(JSContext* aCx, ErrorResult& aArgument,
+MOZ_MUST_USE bool ToJSValue(JSContext* aCx, ErrorResult&& aArgument,
                             JS::MutableHandle<JS::Value> aValue);
 
 // Accept owning WebIDL unions.
 template <typename T>
 MOZ_MUST_USE
     std::enable_if_t<std::is_base_of<AllOwningUnionBase, T>::value, bool>
     ToJSValue(JSContext* aCx, const T& aArgument,
               JS::MutableHandle<JS::Value> aValue) {
--- a/dom/cache/Cache.cpp
+++ b/dom/cache/Cache.cpp
@@ -171,17 +171,17 @@ class Cache::FetchHandler final : public
       // Do not allow the convenience methods .add()/.addAll() to store failed
       // or invalid responses.  A consequence of this is that these methods
       // cannot be used to store opaque or opaqueredirect responses since they
       // always expose a 0 status value.
       ErrorResult errorResult;
       if (!IsValidPutResponseStatus(*response, PutStatusPolicy::RequireOK,
                                     errorResult)) {
         // TODO: abort the fetch requests we have running (bug 1157434)
-        mPromise->MaybeReject(errorResult);
+        mPromise->MaybeReject(std::move(errorResult));
         return;
       }
 
       responseList.AppendElement(std::move(response));
     }
 
     MOZ_DIAGNOSTIC_ASSERT(mRequestList.Length() == responseList.Length());
 
@@ -190,17 +190,17 @@ class Cache::FetchHandler final : public
     // TODO: Here we use the JSContext as received by the ResolvedCallback, and
     // its state could be the wrong one. The spec doesn't say anything
     // about it, yet (bug 1384006)
     RefPtr<Promise> put =
         mCache->PutAll(aCx, mRequestList, responseList, result);
     result.WouldReportJSException();
     if (NS_WARN_IF(result.Failed())) {
       // TODO: abort the fetch requests we have running (bug 1157434)
-      mPromise->MaybeReject(result);
+      mPromise->MaybeReject(std::move(result));
       return;
     }
 
     // Chain the Cache::Put() promise to the original promise returned to
     // the content script.
     mPromise->MaybeResolve(put);
   }
 
--- a/dom/cache/CacheOpChild.cpp
+++ b/dom/cache/CacheOpChild.cpp
@@ -90,17 +90,17 @@ void CacheOpChild::ActorDestroy(ActorDes
 }
 
 mozilla::ipc::IPCResult CacheOpChild::Recv__delete__(
     ErrorResult&& aRv, const CacheOpResult& aResult) {
   NS_ASSERT_OWNINGTHREAD(CacheOpChild);
 
   if (NS_WARN_IF(aRv.Failed())) {
     MOZ_DIAGNOSTIC_ASSERT(aResult.type() == CacheOpResult::Tvoid_t);
-    mPromise->MaybeReject(aRv);
+    mPromise->MaybeReject(std::move(aRv));
     mPromise = nullptr;
     return IPC_OK();
   }
 
   switch (aResult.type()) {
     case CacheOpResult::TCacheMatchResult: {
       HandleResponse(aResult.get_CacheMatchResult().maybeResponse());
       break;
--- a/dom/cache/CacheStorage.cpp
+++ b/dom/cache/CacheStorage.cpp
@@ -546,17 +546,17 @@ void CacheStorage::RunRequest(nsAutoPtr<
   nsAutoPtr<Entry> entry(std::move(aEntry));
 
   AutoChildOpArgs args(this, entry->mArgs, 1);
 
   if (entry->mRequest) {
     ErrorResult rv;
     args.Add(entry->mRequest, IgnoreBody, IgnoreInvalidScheme, rv);
     if (NS_WARN_IF(rv.Failed())) {
-      entry->mPromise->MaybeReject(rv);
+      entry->mPromise->MaybeReject(std::move(rv));
       return;
     }
   }
 
   mActor->ExecuteOp(mGlobal, entry->mPromise, this, args.SendAsOpArgs());
 }
 
 OpenMode CacheStorage::GetOpenMode() const {
--- a/dom/canvas/ImageBitmap.cpp
+++ b/dom/canvas/ImageBitmap.cpp
@@ -1669,17 +1669,17 @@ void CreateImageBitmapFromBlob::
   RefPtr<ImageBitmap> imageBitmap =
       new ImageBitmap(mGlobalObject, aImage, false /* write-only */);
 
   if (mCropRect.isSome()) {
     ErrorResult rv;
     imageBitmap->SetPictureRect(mCropRect.ref(), rv);
 
     if (rv.Failed()) {
-      mPromise->MaybeReject(rv);
+      mPromise->MaybeReject(std::move(rv));
       return;
     }
   }
 
   imageBitmap->mAllocatedImageData = true;
 
   mPromise->MaybeResolve(imageBitmap);
 }
--- a/dom/clients/api/Client.cpp
+++ b/dom/clients/api/Client.cpp
@@ -158,19 +158,18 @@ already_AddRefed<Promise> Client::Focus(
             NS_ENSURE_TRUE_VOID(holder->GetParentObject());
             RefPtr<Client> newClient =
                 new Client(holder->GetParentObject(),
                            ClientInfoAndState(ipcClientInfo, aResult.ToIPC()));
             outerPromise->MaybeResolve(newClient);
           },
           [holder, outerPromise](const CopyableErrorResult& aResult) {
             holder->Complete();
-            // MaybeReject needs a non-const result, so make a copy.
-            CopyableErrorResult result(aResult);
-            outerPromise->MaybeReject(result);
+            // MaybeReject needs a non-const-ref result, so make a copy.
+            outerPromise->MaybeReject(CopyableErrorResult(aResult));
           })
       ->Track(*holder);
 
   return outerPromise.forget();
 }
 
 already_AddRefed<Promise> Client::Navigate(const nsAString& aURL,
                                            ErrorResult& aRv) {
@@ -196,17 +195,17 @@ already_AddRefed<Promise> Client::Naviga
           outerPromise->MaybeResolve(JS::NullHandleValue);
           return;
         }
         RefPtr<Client> newClient =
             new Client(self->mGlobal, aResult.get_ClientInfoAndState());
         outerPromise->MaybeResolve(newClient);
       },
       [self, outerPromise](const CopyableErrorResult& aResult) {
-        CopyableErrorResult result(aResult);
-        outerPromise->MaybeReject(result);
+        // MaybeReject needs a non-const-ref result, so make a copy.
+        outerPromise->MaybeReject(CopyableErrorResult(aResult));
       });
 
   return outerPromise.forget();
 }
 
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/clients/api/Clients.cpp
+++ b/dom/clients/api/Clients.cpp
@@ -184,19 +184,18 @@ already_AddRefed<Promise> Clients::Match
                     nsTArray<nsString>());
               });
           SystemGroup::Dispatch(TaskCategory::Other, r.forget());
         }
         clientList.Sort(MatchAllComparator());
         outerPromise->MaybeResolve(clientList);
       },
       [outerPromise](const CopyableErrorResult& aResult) {
-        // MaybeReject needs a non-const result, so make a copy.
-        CopyableErrorResult result(aResult);
-        outerPromise->MaybeReject(result);
+        // MaybeReject needs a non-const-ref result, so make a copy.
+        outerPromise->MaybeReject(CopyableErrorResult(aResult));
       });
 
   return outerPromise.forget();
 }
 
 already_AddRefed<Promise> Clients::OpenWindow(const nsAString& aURL,
                                               ErrorResult& aRv) {
   MOZ_ASSERT(!NS_IsMainThread());
@@ -209,17 +208,17 @@ already_AddRefed<Promise> Clients::OpenW
   if (aRv.Failed()) {
     return outerPromise.forget();
   }
 
   if (aURL.EqualsLiteral("about:blank")) {
     CopyableErrorResult rv;
     rv.ThrowTypeError(
         u"Passing \"about:blank\" to Clients.openWindow is not allowed");
-    outerPromise->MaybeReject(rv);
+    outerPromise->MaybeReject(std::move(rv));
     return outerPromise.forget();
   }
 
   if (!workerPrivate->GlobalScope()->WindowInteractionAllowed()) {
     outerPromise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR);
     return outerPromise.forget();
   }
 
@@ -239,19 +238,18 @@ already_AddRefed<Promise> Clients::OpenW
           outerPromise->MaybeResolve(JS::NullHandleValue);
           return;
         }
         RefPtr<Client> client =
             new Client(global, aResult.get_ClientInfoAndState());
         outerPromise->MaybeResolve(client);
       },
       [outerPromise](const CopyableErrorResult& aResult) {
-        // MaybeReject needs a non-const result, so make a copy.
-        CopyableErrorResult result(aResult);
-        outerPromise->MaybeReject(result);
+        // MaybeReject needs a non-const-ref result, so make a copy.
+        outerPromise->MaybeReject(CopyableErrorResult(aResult));
       });
 
   return outerPromise.forget();
 }
 
 already_AddRefed<Promise> Clients::Claim(ErrorResult& aRv) {
   MOZ_ASSERT(!NS_IsMainThread());
   WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
@@ -274,18 +272,17 @@ already_AddRefed<Promise> Clients::Claim
   }
 
   StartClientManagerOp(
       &ClientManager::Claim, ClientClaimArgs(serviceWorker.ToIPC()), mGlobal,
       [outerPromise](const ClientOpResult& aResult) {
         outerPromise->MaybeResolveWithUndefined();
       },
       [outerPromise](const CopyableErrorResult& aResult) {
-        // MaybeReject needs a non-const result, so make a copy.
-        CopyableErrorResult result(aResult);
-        outerPromise->MaybeReject(result);
+        // MaybeReject needs a non-const-ref result, so make a copy.
+        outerPromise->MaybeReject(CopyableErrorResult(aResult));
       });
 
   return outerPromise.forget();
 }
 
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/clients/manager/ClientOpenWindowUtils.cpp
+++ b/dom/clients/manager/ClientOpenWindowUtils.cpp
@@ -448,17 +448,17 @@ RefPtr<ClientOpPromise> ClientOpenWindow
 
 #ifdef MOZ_WIDGET_ANDROID
   // If we are on Android we are GeckoView.
   GeckoViewOpenWindow(aArgs, promise);
   return promise.forget();
 #endif  // MOZ_WIDGET_ANDROID
 
   RefPtr<BrowsingContext> bc;
-  CopyableErrorResult rv;
+  ErrorResult rv;
   OpenWindow(aArgs, getter_AddRefs(bc), rv);
 
   nsCOMPtr<nsPIDOMWindowOuter> outerWindow(bc->GetDOMWindow());
 
   if (NS_WARN_IF(rv.Failed())) {
     promise->Reject(rv, __func__);
     return promise;
   }
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -1626,17 +1626,17 @@ mozilla::ipc::IPCResult ContentChild::Re
   }
   if (!CompositorManagerChild::CreateContentCompositorBridge(namespaces[1])) {
     return GetResultForRenderingInitFailure(aCompositor.OtherPid());
   }
   if (!ImageBridgeChild::ReinitForContent(std::move(aImageBridge),
                                           namespaces[2])) {
     return GetResultForRenderingInitFailure(aImageBridge.OtherPid());
   }
-  if (!gfx::VRManagerChild::ReinitForContent(std::move(aVRBridge))) {
+  if (!gfx::VRManagerChild::InitForContent(std::move(aVRBridge))) {
     return GetResultForRenderingInitFailure(aVRBridge.OtherPid());
   }
   gfxPlatform::GetPlatform()->CompositorUpdated();
 
   // Establish new PLayerTransactions.
   for (const auto& browserChild : tabs) {
     if (browserChild->GetLayersId().IsValid()) {
       browserChild->ReinitRendering();
--- a/dom/ipc/JSWindowActor.cpp
+++ b/dom/ipc/JSWindowActor.cpp
@@ -331,17 +331,17 @@ void JSWindowActor::ReceiveMessageOrQuer
   // If we have a promise, resolve or reject it respectively.
   if (promise) {
     if (aRv.Failed()) {
       if (aRv.IsUncatchableException()) {
         aRv.SuppressException();
         promise->MaybeRejectWithTimeoutError(
             "Message handler threw uncatchable exception");
       } else {
-        promise->MaybeReject(aRv);
+        promise->MaybeReject(std::move(aRv));
       }
     } else {
       promise->MaybeResolve(retval);
     }
   }
 }
 
 void JSWindowActor::ReceiveQueryReply(JSContext* aCx,
--- a/dom/media/eme/DetailedPromise.cpp
+++ b/dom/media/eme/DetailedPromise.cpp
@@ -47,20 +47,20 @@ void DetailedPromise::LogRejectionReason
 }
 
 void DetailedPromise::MaybeReject(nsresult aArg, const nsACString& aReason) {
   LogRejectionReason(static_cast<uint32_t>(aArg), aReason);
 
   Promise::MaybeRejectWithDOMException(aArg, aReason);
 }
 
-void DetailedPromise::MaybeReject(ErrorResult& aArg,
+void DetailedPromise::MaybeReject(ErrorResult&& aArg,
                                   const nsACString& aReason) {
   LogRejectionReason(aArg.ErrorCodeAsInt(), aReason);
-  Promise::MaybeReject(aArg);
+  Promise::MaybeReject(std::move(aArg));
 }
 
 /* static */
 already_AddRefed<DetailedPromise> DetailedPromise::Create(
     nsIGlobalObject* aGlobal, ErrorResult& aRv, const nsACString& aName) {
   RefPtr<DetailedPromise> promise = new DetailedPromise(aGlobal, aName);
   promise->CreateWrapper(aRv);
   return aRv.Failed() ? nullptr : promise.forget();
--- a/dom/media/eme/DetailedPromise.h
+++ b/dom/media/eme/DetailedPromise.h
@@ -35,18 +35,18 @@ class DetailedPromise : public Promise {
     EME_LOG("%s promise resolved", mName.get());
     MaybeReportTelemetry(eStatus::kSucceeded);
     Promise::MaybeResolve(std::forward<T>(aArg));
   }
 
   void MaybeReject(nsresult aArg) = delete;
   void MaybeReject(nsresult aArg, const nsACString& aReason);
 
-  void MaybeReject(ErrorResult& aArg) = delete;
-  void MaybeReject(ErrorResult& aArg, const nsACString& aReason);
+  void MaybeReject(ErrorResult&& aArg) = delete;
+  void MaybeReject(ErrorResult&& aArg, const nsACString& aReason);
 
   // Facilities for rejecting with various spec-defined exception values.
 #define DOMEXCEPTION(name, err)                                   \
   inline void MaybeRejectWith##name(const nsACString& aMessage) { \
     LogRejectionReason(static_cast<uint32_t>(err), aMessage);     \
     Promise::MaybeRejectWith##name(aMessage);                     \
   }                                                               \
   template <int N>                                                \
@@ -59,31 +59,31 @@ class DetailedPromise : public Promise {
 #undef DOMEXCEPTION
 
   template <ErrNum errorNumber, typename... Ts>
   void MaybeRejectWithTypeError(Ts&&... aMessageArgs) = delete;
 
   inline void MaybeRejectWithTypeError(const nsAString& aMessage) {
     ErrorResult res;
     res.ThrowTypeError(aMessage);
-    MaybeReject(res, NS_ConvertUTF16toUTF8(aMessage));
+    MaybeReject(std::move(res), NS_ConvertUTF16toUTF8(aMessage));
   }
 
   template <int N>
   void MaybeRejectWithTypeError(const char16_t (&aMessage)[N]) {
     MaybeRejectWithTypeError(nsLiteralString(aMessage));
   }
 
   template <ErrNum errorNumber, typename... Ts>
   void MaybeRejectWithRangeError(Ts&&... aMessageArgs) = delete;
 
   inline void MaybeRejectWithRangeError(const nsAString& aMessage) {
     ErrorResult res;
     res.ThrowRangeError(aMessage);
-    MaybeReject(res, NS_ConvertUTF16toUTF8(aMessage));
+    MaybeReject(std::move(res), NS_ConvertUTF16toUTF8(aMessage));
   }
 
   template <int N>
   void MaybeRejectWithRangeError(const char16_t (&aMessage)[N]) {
     MaybeRejectWithRangeError(nsLiteralString(aMessage));
   }
 
  private:
--- a/dom/midi/MIDIAccess.cpp
+++ b/dom/midi/MIDIAccess.cpp
@@ -186,25 +186,25 @@ void MIDIAccess::MaybeCreateMIDIPort(con
   }
 }
 
 // For the MIDIAccess object, only worry about new connections, where we create
 // MIDIPort objects. When a port is removed and the MIDIPortRemove event is
 // received, that will be handled by the MIDIPort object itself, and it will
 // request removal from MIDIAccess's maps.
 void MIDIAccess::Notify(const MIDIPortList& aEvent) {
-  ErrorResult rv;
   for (auto& port : aEvent.ports()) {
     // Something went very wrong. Warn and return.
+    ErrorResult rv;
     MaybeCreateMIDIPort(port, rv);
     if (rv.Failed()) {
       if (!mAccessPromise) {
         return;
       }
-      mAccessPromise->MaybeReject(rv);
+      mAccessPromise->MaybeReject(std::move(rv));
       mAccessPromise = nullptr;
     }
   }
   if (!mAccessPromise) {
     return;
   }
   mAccessPromise->MaybeResolve(this);
   mAccessPromise = nullptr;
--- a/dom/payments/PaymentRequest.cpp
+++ b/dom/payments/PaymentRequest.cpp
@@ -754,40 +754,40 @@ already_AddRefed<Promise> PaymentRequest
     return nullptr;
   }
 
   mAcceptPromise = promise;
   mState = eInteractive;
   return promise.forget();
 }
 
-void PaymentRequest::RejectShowPayment(ErrorResult& aRejectReason) {
+void PaymentRequest::RejectShowPayment(ErrorResult&& aRejectReason) {
   MOZ_ASSERT(mAcceptPromise || mResponse);
   MOZ_ASSERT(mState == eInteractive);
 
   if (mResponse) {
-    mResponse->RejectRetry(aRejectReason);
+    mResponse->RejectRetry(std::move(aRejectReason));
   } else {
-    mAcceptPromise->MaybeReject(aRejectReason);
+    mAcceptPromise->MaybeReject(std::move(aRejectReason));
   }
   mState = eClosed;
   mAcceptPromise = nullptr;
 }
 
 void PaymentRequest::RespondShowPayment(const nsAString& aMethodName,
                                         const ResponseData& aDetails,
                                         const nsAString& aPayerName,
                                         const nsAString& aPayerEmail,
                                         const nsAString& aPayerPhone,
-                                        ErrorResult& aResult) {
+                                        ErrorResult&& aResult) {
   MOZ_ASSERT(mAcceptPromise || mResponse);
   MOZ_ASSERT(mState == eInteractive);
 
   if (aResult.Failed()) {
-    RejectShowPayment(aResult);
+    RejectShowPayment(std::move(aResult));
     return;
   }
 
   // https://github.com/w3c/payment-request/issues/692
   mShippingAddress.swap(mFullShippingAddress);
   mFullShippingAddress = nullptr;
 
   if (mResponse) {
@@ -854,30 +854,29 @@ void PaymentRequest::RespondAbortPayment
   //      the action, regardless of |aSuccess|.
   //
   // - Otherwise, we are handling |Abort| method call from merchant.
   //   => Resolve/Reject |mAbortPromise| based on |aSuccess|.
   if (mUpdateError.Failed()) {
     // Respond show with mUpdateError, set mUpdating to false.
     mUpdating = false;
     RespondShowPayment(EmptyString(), ResponseData(), EmptyString(),
-                       EmptyString(), EmptyString(), mUpdateError);
-    mUpdateError.SuppressException();
+                       EmptyString(), EmptyString(), std::move(mUpdateError));
     return;
   }
 
   MOZ_ASSERT(mAbortPromise);
   MOZ_ASSERT(mState == eInteractive);
 
   if (aSuccess) {
     mAbortPromise->MaybeResolve(JS::UndefinedHandleValue);
     mAbortPromise = nullptr;
     ErrorResult abortResult;
     abortResult.ThrowAbortError("The PaymentRequest is aborted");
-    RejectShowPayment(abortResult);
+    RejectShowPayment(std::move(abortResult));
   } else {
     mAbortPromise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
     mAbortPromise = nullptr;
   }
 }
 
 void PaymentRequest::UpdatePayment(JSContext* aCx,
                                    const PaymentDetailsUpdate& aDetails,
@@ -1192,17 +1191,17 @@ void PaymentRequest::NotifyOwnerDocument
     if (mState == eInteractive) {
       if (mAcceptPromise) {
         mAcceptPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
         mAcceptPromise = nullptr;
       }
       if (mResponse) {
         ErrorResult rejectReason;
         rejectReason.ThrowAbortError("The owner documnet is not fully active");
-        mResponse->RejectRetry(rejectReason);
+        mResponse->RejectRetry(std::move(rejectReason));
       }
       if (mAbortPromise) {
         mAbortPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
         mAbortPromise = nullptr;
       }
     }
     if (mState == eCreated) {
       if (mResultPromise) {
--- a/dom/payments/PaymentRequest.h
+++ b/dom/payments/PaymentRequest.h
@@ -146,18 +146,18 @@ class PaymentRequest final : public DOME
   void RespondCanMakePayment(bool aResult);
 
   already_AddRefed<Promise> Show(
       const Optional<OwningNonNull<Promise>>& detailsPromise, ErrorResult& aRv);
   void RespondShowPayment(const nsAString& aMethodName,
                           const ResponseData& aData,
                           const nsAString& aPayerName,
                           const nsAString& aPayerEmail,
-                          const nsAString& aPayerPhone, ErrorResult& aResult);
-  void RejectShowPayment(ErrorResult& aRejectReason);
+                          const nsAString& aPayerPhone, ErrorResult&& aResult);
+  void RejectShowPayment(ErrorResult&& aRejectReason);
   void RespondComplete();
 
   already_AddRefed<Promise> Abort(ErrorResult& aRv);
   void RespondAbortPayment(bool aResult);
 
   void RetryPayment(JSContext* aCx, const PaymentValidationErrors& aErrors,
                     ErrorResult& aRv);
 
--- a/dom/payments/PaymentRequestManager.cpp
+++ b/dom/payments/PaymentRequestManager.cpp
@@ -680,17 +680,18 @@ nsresult PaymentRequestManager::RespondP
         }
         default: {
           rejectedReason.ThrowUnknownError("Unknown response for the payment");
           break;
         }
       }
       aRequest->RespondShowPayment(response.methodName(), responseData,
                                    response.payerName(), response.payerEmail(),
-                                   response.payerPhone(), rejectedReason);
+                                   response.payerPhone(),
+                                   std::move(rejectedReason));
       if (rejectedReason.Failed()) {
         NotifyRequestDone(aRequest);
       }
       break;
     }
     case IPCPaymentActionResponse::TIPCPaymentAbortActionResponse: {
       const IPCPaymentAbortActionResponse& response = aResponse;
       aRequest->RespondAbortPayment(response.isSucceeded());
--- a/dom/payments/PaymentResponse.cpp
+++ b/dom/payments/PaymentResponse.cpp
@@ -295,19 +295,19 @@ void PaymentResponse::RespondRetry(const
                           StaticPrefs::dom_payments_response_timeout(),
                           nsITimer::TYPE_ONE_SHOT,
                           GetOwner()->EventTargetFor(TaskCategory::Other));
   MOZ_ASSERT(mRetryPromise);
   mRetryPromise->MaybeResolve(JS::UndefinedHandleValue);
   mRetryPromise = nullptr;
 }
 
-void PaymentResponse::RejectRetry(ErrorResult& aRejectReason) {
+void PaymentResponse::RejectRetry(ErrorResult&& aRejectReason) {
   MOZ_ASSERT(mRetryPromise);
-  mRetryPromise->MaybeReject(aRejectReason);
+  mRetryPromise->MaybeReject(std::move(aRejectReason));
   mRetryPromise = nullptr;
 }
 
 void PaymentResponse::ConvertPaymentMethodErrors(
     JSContext* aCx, const PaymentValidationErrors& aErrors,
     ErrorResult& aRv) const {
   MOZ_ASSERT(aCx);
   if (!aErrors.mPaymentMethod.WasPassed()) {
--- a/dom/payments/PaymentResponse.h
+++ b/dom/payments/PaymentResponse.h
@@ -136,17 +136,17 @@ class PaymentResponse final : public DOM
                                   const PaymentValidationErrors& errorField,
                                   ErrorResult& aRv);
 
   void RespondRetry(const nsAString& aMethodName,
                     const nsAString& aShippingOption,
                     PaymentAddress* aShippingAddress,
                     const ResponseData& aDetails, const nsAString& aPayerName,
                     const nsAString& aPayerEmail, const nsAString& aPayerPhone);
-  void RejectRetry(ErrorResult& aRejectReason);
+  void RejectRetry(ErrorResult&& aRejectReason);
 
  protected:
   ~PaymentResponse();
 
   void ValidatePaymentValidationErrors(const PaymentValidationErrors& aErrors,
                                        ErrorResult& aRv);
 
   void ConvertPaymentMethodErrors(JSContext* aCx,
--- a/dom/promise/Promise.h
+++ b/dom/promise/Promise.h
@@ -97,73 +97,75 @@ class Promise : public nsISupports, publ
   // or may not correspond to a DOMException type, they should consider using an
   // appropriate DOMException-type nsresult with an informative message and
   // calling MaybeRejectWithDOMException.
   inline void MaybeReject(nsresult aArg) {
     MOZ_ASSERT(NS_FAILED(aArg));
     MaybeSomething(aArg, &Promise::MaybeReject);
   }
 
-  inline void MaybeReject(ErrorResult& aArg) {
+  inline void MaybeReject(ErrorResult&& aArg) {
     MOZ_ASSERT(aArg.Failed());
-    MaybeSomething(aArg, &Promise::MaybeReject);
+    MaybeSomething(std::move(aArg), &Promise::MaybeReject);
+    // That should have consumed aArg.
+    MOZ_ASSERT(!aArg.Failed());
   }
 
   void MaybeReject(const RefPtr<MediaStreamError>& aArg);
 
   void MaybeRejectWithUndefined();
 
   void MaybeResolveWithClone(JSContext* aCx, JS::Handle<JS::Value> aValue);
   void MaybeRejectWithClone(JSContext* aCx, JS::Handle<JS::Value> aValue);
 
   // Facilities for rejecting with various spec-defined exception values.
 #define DOMEXCEPTION(name, err)                                   \
   inline void MaybeRejectWith##name(const nsACString& aMessage) { \
     ErrorResult res;                                              \
     res.Throw##name(aMessage);                                    \
-    MaybeReject(res);                                             \
+    MaybeReject(std::move(res));                                  \
   }                                                               \
   template <int N>                                                \
   void MaybeRejectWith##name(const char(&aMessage)[N]) {          \
     MaybeRejectWith##name(nsLiteralCString(aMessage));            \
   }
 
 #include "mozilla/dom/DOMExceptionNames.h"
 
 #undef DOMEXCEPTION
 
   template <ErrNum errorNumber, typename... Ts>
   void MaybeRejectWithTypeError(Ts&&... aMessageArgs) {
     ErrorResult res;
     res.ThrowTypeError<errorNumber>(std::forward<Ts>(aMessageArgs)...);
-    MaybeReject(res);
+    MaybeReject(std::move(res));
   }
 
   inline void MaybeRejectWithTypeError(const nsAString& aMessage) {
     ErrorResult res;
     res.ThrowTypeError(aMessage);
-    MaybeReject(res);
+    MaybeReject(std::move(res));
   }
 
   template <int N>
   void MaybeRejectWithTypeError(const char16_t (&aMessage)[N]) {
     MaybeRejectWithTypeError(nsLiteralString(aMessage));
   }
 
   template <ErrNum errorNumber, typename... Ts>
   void MaybeRejectWithRangeError(Ts&&... aMessageArgs) {
     ErrorResult res;
     res.ThrowRangeError<errorNumber>(std::forward<Ts>(aMessageArgs)...);
-    MaybeReject(res);
+    MaybeReject(std::move(res));
   }
 
   inline void MaybeRejectWithRangeError(const nsAString& aMessage) {
     ErrorResult res;
     res.ThrowRangeError(aMessage);
-    MaybeReject(res);
+    MaybeReject(std::move(res));
   }
 
   template <int N>
   void MaybeRejectWithRangeError(const char16_t (&aMessage)[N]) {
     MaybeRejectWithRangeError(nsLiteralString(aMessage));
   }
 
   // DO NOT USE MaybeRejectBrokenly with in new code.  Promises should be
@@ -279,17 +281,17 @@ class Promise : public nsISupports, publ
  protected:
   // Legacy method for throwing DOMExceptions.  Only used by media code at this
   // point, via DetailedPromise.  Do NOT add new uses!  When this is removed,
   // remove the friend declaration in ErrorResult.h.
   inline void MaybeRejectWithDOMException(nsresult rv,
                                           const nsACString& aMessage) {
     ErrorResult res;
     res.ThrowDOMException(rv, aMessage);
-    MaybeReject(res);
+    MaybeReject(std::move(res));
   }
 
   struct PromiseCapability;
 
   // Do NOT call this unless you're Promise::Create or
   // Promise::CreateFromExisting.  I wish we could enforce that from inside this
   // class too, somehow.
   explicit Promise(nsIGlobalObject* aGlobal);
--- a/dom/serviceworkers/RemoteServiceWorkerContainerImpl.cpp
+++ b/dom/serviceworkers/RemoteServiceWorkerContainerImpl.cpp
@@ -52,17 +52,17 @@ void RemoteServiceWorkerContainerImpl::R
 void RemoteServiceWorkerContainerImpl::Register(
     const ClientInfo& aClientInfo, const nsACString& aScopeURL,
     const nsACString& aScriptURL, ServiceWorkerUpdateViaCache aUpdateViaCache,
     ServiceWorkerRegistrationCallback&& aSuccessCB,
     ServiceWorkerFailureCallback&& aFailureCB) const {
   if (!mActor) {
     CopyableErrorResult rv;
     rv.ThrowInvalidStateError("Can't register service worker");
-    aFailureCB(rv);
+    aFailureCB(std::move(rv));
     return;
   }
 
   mActor->SendRegister(
       aClientInfo.ToIPC(), nsCString(aScopeURL), nsCString(aScriptURL),
       aUpdateViaCache,
       [successCB = std::move(aSuccessCB), aFailureCB](
           const IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult&
@@ -79,17 +79,17 @@ void RemoteServiceWorkerContainerImpl::R
         // success
         auto& ipcDesc = aResult.get_IPCServiceWorkerRegistrationDescriptor();
         successCB(ServiceWorkerRegistrationDescriptor(ipcDesc));
       },
       [aFailureCB](ResponseRejectReason&& aReason) {
         // IPC layer error
         CopyableErrorResult rv;
         rv.ThrowInvalidStateError("Failed to register service worker");
-        aFailureCB(rv);
+        aFailureCB(std::move(rv));
       });
 }
 
 void RemoteServiceWorkerContainerImpl::GetRegistration(
     const ClientInfo& aClientInfo, const nsACString& aURL,
     ServiceWorkerRegistrationCallback&& aSuccessCB,
     ServiceWorkerFailureCallback&& aFailureCB) const {
   if (!mActor) {
--- a/dom/serviceworkers/RemoteServiceWorkerRegistrationImpl.cpp
+++ b/dom/serviceworkers/RemoteServiceWorkerRegistrationImpl.cpp
@@ -89,17 +89,17 @@ void RemoteServiceWorkerRegistrationImpl
     return;
   }
 
   mActor->SendUnregister(
       [successCB = std::move(aSuccessCB),
        aFailureCB](Tuple<bool, CopyableErrorResult>&& aResult) {
         if (Get<1>(aResult).Failed()) {
           // application layer error
-          aFailureCB(Get<1>(aResult));
+          aFailureCB(std::move(Get<1>(aResult)));
           return;
         }
         // success
         successCB(Get<0>(aResult));
       },
       [aFailureCB](ResponseRejectReason&& aReason) {
         // IPC layer error
         aFailureCB(CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR));
@@ -141,24 +141,26 @@ void RemoteServiceWorkerRegistrationImpl
   MOZ_DIAGNOSTIC_ASSERT(mActor);
   MOZ_DIAGNOSTIC_ASSERT(mActor == aActor);
   mActor->RevokeOwner(this);
   mActor = nullptr;
 
   mShutdown = true;
 
   if (mOuter) {
-    mOuter->RegistrationCleared();
+    RefPtr<ServiceWorkerRegistration> outer = mOuter;
+    outer->RegistrationCleared();
   }
 }
 
 void RemoteServiceWorkerRegistrationImpl::UpdateState(
     const ServiceWorkerRegistrationDescriptor& aDescriptor) {
   if (mOuter) {
-    mOuter->UpdateState(aDescriptor);
+    RefPtr<ServiceWorkerRegistration> outer = mOuter;
+    outer->UpdateState(aDescriptor);
   }
 }
 
 void RemoteServiceWorkerRegistrationImpl::FireUpdateFound() {
   if (mOuter) {
     mOuter->MaybeDispatchUpdateFoundRunnable();
   }
 }
--- a/dom/serviceworkers/ServiceWorker.cpp
+++ b/dom/serviceworkers/ServiceWorker.cpp
@@ -106,17 +106,17 @@ ServiceWorker::ServiceWorker(nsIGlobalOb
         [self = std::move(self)](
             const ServiceWorkerRegistrationDescriptor& aDescriptor) {
           nsIGlobalObject* global = self->GetParentObject();
           NS_ENSURE_TRUE_VOID(global);
           RefPtr<ServiceWorkerRegistration> reg =
               global->GetOrCreateServiceWorkerRegistration(aDescriptor);
           self->MaybeAttachToRegistration(reg);
         },
-        [](ErrorResult& aRv) {
+        [](ErrorResult&& aRv) {
           // do nothing
           aRv.SuppressException();
         });
   }
 }
 
 ServiceWorker::~ServiceWorker() {
   MOZ_ASSERT(NS_IsMainThread());
--- a/dom/serviceworkers/ServiceWorkerContainer.cpp
+++ b/dom/serviceworkers/ServiceWorkerContainer.cpp
@@ -378,24 +378,24 @@ already_AddRefed<Promise> ServiceWorkerC
 
   mInner->Register(
       clientInfo.ref(), cleanedScopeURL, cleanedScriptURL,
       aOptions.mUpdateViaCache,
       [self, outer](const ServiceWorkerRegistrationDescriptor& aDesc) {
         ErrorResult rv;
         nsIGlobalObject* global = self->GetGlobalIfValid(rv);
         if (rv.Failed()) {
-          outer->MaybeReject(rv);
+          outer->MaybeReject(std::move(rv));
           return;
         }
         RefPtr<ServiceWorkerRegistration> reg =
             global->GetOrCreateServiceWorkerRegistration(aDesc);
         outer->MaybeResolve(reg);
       },
-      [outer](ErrorResult& aRv) { outer->MaybeReject(aRv); });
+      [outer](ErrorResult&& aRv) { outer->MaybeReject(std::move(aRv)); });
 
   return outer.forget();
 }
 
 already_AddRefed<ServiceWorker> ServiceWorkerContainer::GetController() {
   RefPtr<ServiceWorker> ref = mControllerWorker;
   return ref.forget();
 }
@@ -428,30 +428,30 @@ already_AddRefed<Promise> ServiceWorkerC
 
   mInner->GetRegistrations(
       clientInfo.ref(),
       [self,
        outer](const nsTArray<ServiceWorkerRegistrationDescriptor>& aDescList) {
         ErrorResult rv;
         nsIGlobalObject* global = self->GetGlobalIfValid(rv);
         if (rv.Failed()) {
-          outer->MaybeReject(rv);
+          outer->MaybeReject(std::move(rv));
           return;
         }
         nsTArray<RefPtr<ServiceWorkerRegistration>> regList;
         for (auto& desc : aDescList) {
           RefPtr<ServiceWorkerRegistration> reg =
               global->GetOrCreateServiceWorkerRegistration(desc);
           if (reg) {
             regList.AppendElement(std::move(reg));
           }
         }
         outer->MaybeResolve(regList);
       },
-      [self, outer](ErrorResult& aRv) { outer->MaybeReject(aRv); });
+      [self, outer](ErrorResult&& aRv) { outer->MaybeReject(std::move(aRv)); });
 
   return outer.forget();
 }
 
 void ServiceWorkerContainer::StartMessages() {
   while (!mPendingMessages.IsEmpty()) {
     EnqueueReceivedMessageDispatch(mPendingMessages.ElementAt(0));
     mPendingMessages.RemoveElementAt(0);
@@ -503,32 +503,32 @@ already_AddRefed<Promise> ServiceWorkerC
   RefPtr<ServiceWorkerContainer> self = this;
 
   mInner->GetRegistration(
       clientInfo.ref(), spec,
       [self, outer](const ServiceWorkerRegistrationDescriptor& aDescriptor) {
         ErrorResult rv;
         nsIGlobalObject* global = self->GetGlobalIfValid(rv);
         if (rv.Failed()) {
-          outer->MaybeReject(rv);
+          outer->MaybeReject(std::move(rv));
           return;
         }
         RefPtr<ServiceWorkerRegistration> reg =
             global->GetOrCreateServiceWorkerRegistration(aDescriptor);
         outer->MaybeResolve(reg);
       },
-      [self, outer](ErrorResult& aRv) {
+      [self, outer](ErrorResult&& aRv) {
         if (!aRv.Failed()) {
           Unused << self->GetGlobalIfValid(aRv);
           if (!aRv.Failed()) {
             outer->MaybeResolveWithUndefined();
             return;
           }
         }
-        outer->MaybeReject(aRv);
+        outer->MaybeReject(std::move(aRv));
       });
 
   return outer.forget();
 }
 
 Promise* ServiceWorkerContainer::GetReady(ErrorResult& aRv) {
   if (mReadyPromise) {
     return mReadyPromise;
@@ -556,31 +556,31 @@ Promise* ServiceWorkerContainer::GetRead
   RefPtr<Promise> outer = mReadyPromise;
 
   mInner->GetReady(
       clientInfo.ref(),
       [self, outer](const ServiceWorkerRegistrationDescriptor& aDescriptor) {
         ErrorResult rv;
         nsIGlobalObject* global = self->GetGlobalIfValid(rv);
         if (rv.Failed()) {
-          outer->MaybeReject(rv);
+          outer->MaybeReject(std::move(rv));
           return;
         }
         RefPtr<ServiceWorkerRegistration> reg =
             global->GetOrCreateServiceWorkerRegistration(aDescriptor);
         NS_ENSURE_TRUE_VOID(reg);
 
         // Don't resolve the ready promise until the registration has
         // reached the right version.  This ensures that the active
         // worker property is set correctly on the registration.
         reg->WhenVersionReached(
             aDescriptor.Version(),
             [outer, reg](bool aResult) { outer->MaybeResolve(reg); });
       },
-      [self, outer](ErrorResult& aRv) { outer->MaybeReject(aRv); });
+      [self, outer](ErrorResult&& aRv) { outer->MaybeReject(std::move(aRv)); });
 
   return mReadyPromise;
 }
 
 // Testing only.
 void ServiceWorkerContainer::GetScopeForUrl(const nsAString& aUrl,
                                             nsString& aScope,
                                             ErrorResult& aRv) {
--- a/dom/serviceworkers/ServiceWorkerManager.cpp
+++ b/dom/serviceworkers/ServiceWorkerManager.cpp
@@ -1,16 +1,18 @@
 /* -*- 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 "ServiceWorkerManager.h"
 
+#include <algorithm>
+
 #include "nsAutoPtr.h"
 #include "nsIEffectiveTLDService.h"
 #include "nsIHttpChannel.h"
 #include "nsIHttpChannelInternal.h"
 #include "nsINamed.h"
 #include "nsINetworkInterceptController.h"
 #include "nsIMutableArray.h"
 #include "nsITimer.h"
@@ -161,26 +163,111 @@ static_assert(static_cast<uint16_t>(Serv
 static_assert(static_cast<uint16_t>(ServiceWorkerUpdateViaCache::None) ==
                   nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_NONE,
               "nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_*"
               " should match ServiceWorkerUpdateViaCache enumeration.");
 
 static StaticRefPtr<ServiceWorkerManager> gInstance;
 
 struct ServiceWorkerManager::RegistrationDataPerPrincipal final {
-  // Ordered list of scopes for glob matching.
-  // Each entry is an absolute URL representing the scope.
-  // Each value of the hash table is an array of an absolute URLs representing
-  // the scopes.
+  // Implements a container of keys for the "scope to registration map":
+  // https://w3c.github.io/ServiceWorker/#dfn-scope-to-registration-map
+  //
+  // where each key is an absolute URL.
+  //
+  // The properties of this map that the spec uses are
+  // 1) insertion,
+  // 2) removal,
+  // 3) iteration of scopes in FIFO order (excluding removed scopes),
+  // 4) and finding, for a given path, the maximal length scope which is a
+  //    prefix of the path.
+  //
+  // Additionally, because this is a container of keys for a map, there
+  // shouldn't be duplicate scopes.
+  //
+  // The current implementation uses a dynamic array as the underlying
+  // container, which is not optimal for unbounded container sizes (all
+  // supported operations are in linear time) but may be superior for small
+  // container sizes.
   //
-  // An array is used for now since the number of controlled scopes per
-  // domain is expected to be relatively low. If that assumption was proved
-  // wrong this should be replaced with a better structure to avoid the
-  // memmoves associated with inserting stuff in the middle of the array.
-  nsTArray<nsCString> mOrderedScopes;
+  // If this is proven to be too slow, the underlying storage should be replaced
+  // with a linked list of scopes in combination with an ordered map that maps
+  // scopes to linked list elements/iterators. This would reduce all of the
+  // above operations besides iteration (necessarily linear) to logarithmic
+  // time.
+  class ScopeContainer final : private nsTArray<nsCString> {
+    using Base = nsTArray<nsCString>;
+
+   public:
+    using Base::Contains;
+    using Base::IsEmpty;
+    using Base::Length;
+
+    // No using-declaration to avoid importing the non-const overload.
+    decltype(auto) operator[](Base::index_type aIndex) const {
+      return Base::operator[](aIndex);
+    }
+
+    void InsertScope(const nsACString& aScope) {
+      MOZ_DIAGNOSTIC_ASSERT(nsContentUtils::IsAbsoluteURL(aScope));
+
+      if (Contains(aScope)) {
+        return;
+      }
+
+      AppendElement(aScope);
+    }
+
+    void RemoveScope(const nsACString& aScope) {
+      MOZ_DIAGNOSTIC_ALWAYS_TRUE(RemoveElement(aScope));
+    }
+
+    // Implements most of "Match Service Worker Registration":
+    // https://w3c.github.io/ServiceWorker/#scope-match-algorithm
+    Maybe<nsCString> MatchScope(const nsACString& aClientUrl) const {
+      Maybe<nsCString> match;
+
+      for (const nsCString& scope : *this) {
+        if (StringBeginsWith(aClientUrl, scope)) {
+          if (!match || scope.Length() > match->Length()) {
+            match = Some(scope);
+          }
+        }
+      }
+
+      // Step 7.2:
+      // "Assert: matchingScope’s origin and clientURL’s origin are same
+      // origin."
+      MOZ_DIAGNOSTIC_ASSERT_IF(match, IsSameOrigin(*match, aClientUrl));
+
+      return match;
+    }
+
+   private:
+    bool IsSameOrigin(const nsACString& aMatchingScope,
+                      const nsACString& aClientUrl) const {
+      RefPtr<BasePrincipal> scopePrincipal =
+          BasePrincipal::CreateContentPrincipal(aMatchingScope);
+
+      if (NS_WARN_IF(!scopePrincipal)) {
+        return false;
+      }
+
+      RefPtr<BasePrincipal> clientPrincipal =
+          BasePrincipal::CreateContentPrincipal(aClientUrl);
+
+      if (NS_WARN_IF(!clientPrincipal)) {
+        return false;
+      }
+
+      return scopePrincipal->FastEquals(clientPrincipal);
+    }
+  };
+
+  ScopeContainer mScopeContainer;
 
   // Scope to registration.
   // The scope should be a fully qualified valid URL.
   nsRefPtrHashtable<nsCStringHashKey, ServiceWorkerRegistrationInfo> mInfos;
 
   // Maps scopes to job queues.
   nsRefPtrHashtable<nsCStringHashKey, ServiceWorkerJobQueue> mJobQueues;
 
@@ -921,21 +1008,21 @@ class GetRegistrationsRunnable final : p
 
     ServiceWorkerManager::RegistrationDataPerPrincipal* data;
     if (!swm->mRegistrationInfos.Get(scopeKey, &data)) {
       scopeExit.release();
       mPromise->Resolve(array, __func__);
       return NS_OK;
     }
 
-    for (uint32_t i = 0; i < data->mOrderedScopes.Length(); ++i) {
+    for (uint32_t i = 0; i < data->mScopeContainer.Length(); ++i) {
       RefPtr<ServiceWorkerRegistrationInfo> info =
-          data->mInfos.GetWeak(data->mOrderedScopes[i]);
-
-      NS_ConvertUTF8toUTF16 scope(data->mOrderedScopes[i]);
+          data->mInfos.GetWeak(data->mScopeContainer[i]);
+
+      NS_ConvertUTF8toUTF16 scope(data->mScopeContainer[i]);
 
       nsCOMPtr<nsIURI> scopeURI;
       nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), scope);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         break;
       }
 
       // Unfortunately we don't seem to have an obvious window id here; in
@@ -1701,63 +1788,42 @@ void ServiceWorkerManager::AddScopeAndRe
     return;
   }
 
   MOZ_ASSERT(!scopeKey.IsEmpty());
 
   const auto& data = swm->mRegistrationInfos.LookupForAdd(scopeKey).OrInsert(
       []() { return new RegistrationDataPerPrincipal(); });
 
-  for (uint32_t i = 0; i < data->mOrderedScopes.Length(); ++i) {
-    const nsCString& current = data->mOrderedScopes[i];
-
-    // Perfect match!
-    if (aScope.Equals(current)) {
-      data->mInfos.Put(aScope, aInfo);
-      swm->NotifyListenersOnRegister(aInfo);
-      return;
-    }
-
-    // Sort by length, with longest match first.
-    // /foo/bar should be before /foo/
-    // Similarly /foo/b is between the two.
-    if (StringBeginsWith(aScope, current)) {
-      data->mOrderedScopes.InsertElementAt(i, aScope);
-      data->mInfos.Put(aScope, aInfo);
-      swm->NotifyListenersOnRegister(aInfo);
-      return;
-    }
-  }
-
-  data->mOrderedScopes.AppendElement(aScope);
+  data->mScopeContainer.InsertScope(aScope);
   data->mInfos.Put(aScope, aInfo);
   swm->NotifyListenersOnRegister(aInfo);
 }
 
 /* static */
 bool ServiceWorkerManager::FindScopeForPath(
     const nsACString& aScopeKey, const nsACString& aPath,
     RegistrationDataPerPrincipal** aData, nsACString& aMatch) {
   MOZ_ASSERT(aData);
 
   RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
 
   if (!swm || !swm->mRegistrationInfos.Get(aScopeKey, aData)) {
     return false;
   }
 
-  for (uint32_t i = 0; i < (*aData)->mOrderedScopes.Length(); ++i) {
-    const nsCString& current = (*aData)->mOrderedScopes[i];
-    if (StringBeginsWith(aPath, current)) {
-      aMatch = current;
-      return true;
-    }
+  Maybe<nsCString> scope = (*aData)->mScopeContainer.MatchScope(aPath);
+
+  if (scope) {
+    // scope.isSome() will still truen true after this; we are just moving the
+    // string inside the Maybe, so the Maybe will contain an empty string.
+    aMatch = std::move(*scope);
   }
 
-  return false;
+  return scope.isSome();
 }
 
 /* static */
 bool ServiceWorkerManager::HasScope(nsIPrincipal* aPrincipal,
                                     const nsACString& aScope) {
   RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
   if (!swm) {
     return false;
@@ -1769,17 +1835,17 @@ bool ServiceWorkerManager::HasScope(nsIP
     return false;
   }
 
   RegistrationDataPerPrincipal* data;
   if (!swm->mRegistrationInfos.Get(scopeKey, &data)) {
     return false;
   }
 
-  return data->mOrderedScopes.Contains(aScope);
+  return data->mScopeContainer.Contains(aScope);
 }
 
 /* static */
 void ServiceWorkerManager::RemoveScopeAndRegistration(
     ServiceWorkerRegistrationInfo* aRegistration) {
   RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
   if (!swm) {
     return;
@@ -1809,26 +1875,26 @@ void ServiceWorkerManager::RemoveScopeAn
         reg->IsCorrupt()) {
       iter.Remove();
     }
   }
 
   RefPtr<ServiceWorkerRegistrationInfo> info;
   data->mInfos.Remove(aRegistration->Scope(), getter_AddRefs(info));
   aRegistration->SetUnregistered();
-  data->mOrderedScopes.RemoveElement(aRegistration->Scope());
+  data->mScopeContainer.RemoveScope(aRegistration->Scope());
   swm->NotifyListenersOnUnregister(info);
 
   swm->MaybeRemoveRegistrationInfo(scopeKey);
 }
 
 void ServiceWorkerManager::MaybeRemoveRegistrationInfo(
     const nsACString& aScopeKey) {
   if (auto entry = mRegistrationInfos.Lookup(aScopeKey)) {
-    if (entry.Data()->mOrderedScopes.IsEmpty() &&
+    if (entry.Data()->mScopeContainer.IsEmpty() &&
         entry.Data()->mJobQueues.Count() == 0) {
       entry.Remove();
     }
   }
 }
 
 bool ServiceWorkerManager::StartControlling(
     const ClientInfo& aClientInfo,
--- a/dom/serviceworkers/ServiceWorkerRegistration.cpp
+++ b/dom/serviceworkers/ServiceWorkerRegistration.cpp
@@ -255,17 +255,17 @@ already_AddRefed<Promise> ServiceWorkerR
         RefPtr<ServiceWorkerRegistration> ref =
             global->GetOrCreateServiceWorkerRegistration(aDesc);
         if (!ref) {
           outer->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
           return;
         }
         outer->MaybeResolve(ref);
       },
-      [outer, self](ErrorResult& aRv) { outer->MaybeReject(aRv); });
+      [outer, self](ErrorResult&& aRv) { outer->MaybeReject(std::move(aRv)); });
 
   return outer.forget();
 }
 
 already_AddRefed<Promise> ServiceWorkerRegistration::Unregister(
     ErrorResult& aRv) {
   nsIGlobalObject* global = GetParentObject();
   if (NS_WARN_IF(!global)) {
@@ -279,19 +279,20 @@ already_AddRefed<Promise> ServiceWorkerR
   }
 
   if (!mInner) {
     outer->MaybeResolve(false);
     return outer.forget();
   }
 
   mInner->Unregister([outer](bool aSuccess) { outer->MaybeResolve(aSuccess); },
-                     [outer](ErrorResult& aRv) {
+                     [outer](ErrorResult&& aRv) {
                        // register() should be resilient and resolve false
                        // instead of rejecting in most cases.
+                       aRv.SuppressException();
                        outer->MaybeResolve(false);
                      });
 
   return outer.forget();
 }
 
 already_AddRefed<PushManager> ServiceWorkerRegistration::GetPushManager(
     JSContext* aCx, ErrorResult& aRv) {
--- a/dom/serviceworkers/ServiceWorkerUtils.h
+++ b/dom/serviceworkers/ServiceWorkerUtils.h
@@ -34,17 +34,17 @@ typedef std::function<void(const Service
     ServiceWorkerRegistrationCallback;
 
 typedef std::function<void(
     const nsTArray<ServiceWorkerRegistrationDescriptor>&)>
     ServiceWorkerRegistrationListCallback;
 
 typedef std::function<void(bool)> ServiceWorkerBoolCallback;
 
-typedef std::function<void(ErrorResult&)> ServiceWorkerFailureCallback;
+typedef std::function<void(ErrorResult&&)> ServiceWorkerFailureCallback;
 
 bool ServiceWorkerParentInterceptEnabled();
 
 bool ServiceWorkerRegistrationDataIsValid(
     const ServiceWorkerRegistrationData& aData);
 
 void ServiceWorkerScopeAndScriptAreValid(const ClientInfo& aClientInfo,
                                          nsIURI* aScopeURI, nsIURI* aScriptURI,
--- a/dom/webauthn/WebAuthnManager.cpp
+++ b/dom/webauthn/WebAuthnManager.cpp
@@ -242,17 +242,17 @@ already_AddRefed<Promise> WebAuthnManage
     promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
     return promise.forget();
   }
 
   nsString origin;
   nsCString rpId;
   rv = GetOrigin(mParent, origin, rpId);
   if (NS_WARN_IF(rv.Failed())) {
-    promise->MaybeReject(rv);
+    promise->MaybeReject(std::move(rv));
     return promise.forget();
   }
 
   // Enforce 5.4.3 User Account Parameters for Credential Generation
   // When we add UX, we'll want to do more with this value, but for now
   // we just have to verify its correctness.
 
   CryptoBuffer userId;
@@ -461,17 +461,17 @@ already_AddRefed<Promise> WebAuthnManage
     promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
     return promise.forget();
   }
 
   nsString origin;
   nsCString rpId;
   rv = GetOrigin(mParent, origin, rpId);
   if (NS_WARN_IF(rv.Failed())) {
-    promise->MaybeReject(rv);
+    promise->MaybeReject(std::move(rv));
     return promise.forget();
   }
 
   // If timeoutSeconds was specified, check if its value lies within a
   // reasonable range as defined by the platform and if not, correct it to the
   // closest value lying within that range.
 
   uint32_t adjustedTimeout = 30000;
--- a/dom/webgpu/Buffer.cpp
+++ b/dom/webgpu/Buffer.cpp
@@ -96,17 +96,17 @@ already_AddRefed<dom::Promise> Buffer::M
           return;
         }
         JS::Rooted<JSObject*> arrayBuffer(
             jsapi.cx(),
             Device::CreateExternalArrayBuffer(jsapi.cx(), size, aShmem));
         if (!arrayBuffer) {
           ErrorResult rv;
           rv.StealExceptionFromJSContext(jsapi.cx());
-          promise->MaybeReject(rv);
+          promise->MaybeReject(std::move(rv));
           return;
         }
         JS::Rooted<JS::Value> val(jsapi.cx(), JS::ObjectValue(*arrayBuffer));
         self->mMapping.emplace(std::move(aShmem), arrayBuffer);
         promise->MaybeResolve(val);
       },
       [promise](const ipc::ResponseRejectReason&) {
         promise->MaybeRejectWithAbortError("Internal communication error!");
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -576,28 +576,22 @@ class LogViolationDetailsRunnable final 
   }
 
   virtual bool MainThreadRun() override;
 
  private:
   ~LogViolationDetailsRunnable() = default;
 };
 
-bool ContentSecurityPolicyAllows(JSContext* aCx, JS::HandleValue aValue) {
+bool ContentSecurityPolicyAllows(JSContext* aCx, JS::HandleString aCode) {
   WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx);
   worker->AssertIsOnWorkerThread();
 
-  JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, aValue));
-  if (NS_WARN_IF(!jsString)) {
-    JS_ClearPendingException(aCx);
-    return false;
-  }
-
   nsAutoJSString scriptSample;
-  if (NS_WARN_IF(!scriptSample.init(aCx, jsString))) {
+  if (NS_WARN_IF(!scriptSample.init(aCx, aCode))) {
     JS_ClearPendingException(aCx);
     return false;
   }
 
   if (!nsContentSecurityUtils::IsEvalAllowed(aCx, worker->UsesSystemPrincipal(),
                                              scriptSample)) {
     return false;
   }
--- a/dom/workers/ScriptLoader.cpp
+++ b/dom/workers/ScriptLoader.cpp
@@ -2166,17 +2166,17 @@ void ScriptExecutorRunnable::ShutdownScr
 
 void ScriptExecutorRunnable::LogExceptionToConsole(
     JSContext* aCx, WorkerPrivate* aWorkerPrivate) {
   aWorkerPrivate->AssertIsOnWorkerThread();
 
   MOZ_ASSERT(mScriptLoader.mRv.IsJSException());
 
   JS::Rooted<JS::Value> exn(aCx);
-  if (!ToJSValue(aCx, mScriptLoader.mRv, &exn)) {
+  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);
--- a/dom/worklet/Worklet.cpp
+++ b/dom/worklet/Worklet.cpp
@@ -125,18 +125,19 @@ class WorkletFetchHandler final : public
     request.SetAsUSVString().ShareOrDependUpon(aModuleURL);
 
     RequestInit init;
     init.mCredentials.Construct(aOptions.mCredentials);
 
     RefPtr<Promise> fetchPromise =
         FetchRequest(global, request, init, aCallerType, aRv);
     if (NS_WARN_IF(aRv.Failed())) {
-      promise->MaybeReject(aRv);
-      return promise.forget();
+      // OK to just return null, since caller will ignore return value
+      // anyway if aRv is a failure.
+      return nullptr;
     }
 
     RefPtr<WorkletFetchHandler> handler =
         new WorkletFetchHandler(aWorklet, spec, promise);
     fetchPromise->AppendNativeHandler(handler);
 
     aWorklet->AddImportFetchHandler(spec, handler);
     return promise.forget();
--- a/editor/libeditor/HTMLEditor.h
+++ b/editor/libeditor/HTMLEditor.h
@@ -3958,24 +3958,28 @@ class HTMLEditor final : public TextEdit
    * CollectListAndTableRelatedElementsAt() collects list elements and
    * table related elements from aNode (meaning aNode may be in the first of
    * the result) to the root element.
    */
   static void CollectListAndTableRelatedElementsAt(
       nsINode& aNode,
       nsTArray<OwningNonNull<Element>>& aOutArrayOfListAndTableElements);
 
-  int32_t DiscoverPartialListsAndTables(
-      nsTArray<OwningNonNull<nsINode>>& aPasteNodes,
-      nsTArray<OwningNonNull<Element>>& aListsAndTables);
+  /**
+   * TODO: Document what this does.
+   */
+  static Element* DiscoverPartialListsAndTables(
+      const nsTArray<OwningNonNull<nsINode>>& aArrayOfNodes,
+      const nsTArray<OwningNonNull<Element>>&
+          aArrayOfListAndTableRelatedElements);
+
   enum class StartOrEnd { start, end };
-  void ReplaceOrphanedStructure(
-      StartOrEnd aStartOrEnd, nsTArray<OwningNonNull<nsINode>>& aNodeArray,
-      nsTArray<OwningNonNull<Element>>& aListAndTableArray,
-      int32_t aHighWaterMark);
+  void ReplaceOrphanedStructure(StartOrEnd aStartOrEnd,
+                                nsTArray<OwningNonNull<nsINode>>& aNodeArray,
+                                Element& aListOrTableElement);
 
   /**
    * FindReplaceableTableElement() is a helper method of
    * ReplaceOrphanedStructure().  If aNodeMaybeInTableElement is a descendant
    * of aTableElement, returns aNodeMaybeInTableElement or its nearest ancestor
    * whose tag name is `<td>`, `<th>`, `<tr>`, `<thead>`, `<tfoot>`, `<tbody>`
    * or `<caption>`.
    *
--- a/editor/libeditor/HTMLEditorDataTransfer.cpp
+++ b/editor/libeditor/HTMLEditorDataTransfer.cpp
@@ -398,40 +398,37 @@ nsresult HTMLEditor::DoInsertHTMLWithCon
 
   // build up list of parents of first node in list that are either
   // lists or tables.  First examine front of paste node list.
   AutoTArray<OwningNonNull<Element>, 4>
       arrayOfListAndTableRelatedElementsAtStart;
   HTMLEditor::CollectListAndTableRelatedElementsAt(
       nodeList[0], arrayOfListAndTableRelatedElementsAtStart);
   if (!arrayOfListAndTableRelatedElementsAtStart.IsEmpty()) {
-    int32_t highWaterMark = DiscoverPartialListsAndTables(
+    Element* listOrTableElement = HTMLEditor::DiscoverPartialListsAndTables(
         nodeList, arrayOfListAndTableRelatedElementsAtStart);
     // if we have pieces of tables or lists to be inserted, let's force the
     // paste to deal with table elements right away, so that it doesn't orphan
     // some table or list contents outside the table or list.
-    if (highWaterMark >= 0) {
+    if (listOrTableElement) {
       ReplaceOrphanedStructure(StartOrEnd::start, nodeList,
-                               arrayOfListAndTableRelatedElementsAtStart,
-                               highWaterMark);
+                               *listOrTableElement);
     }
   }
 
   // Now go through the same process again for the end of the paste node list.
   AutoTArray<OwningNonNull<Element>, 4> arrayOfListAndTableRelatedElementsAtEnd;
   HTMLEditor::CollectListAndTableRelatedElementsAt(
       nodeList.LastElement(), arrayOfListAndTableRelatedElementsAtEnd);
   if (!arrayOfListAndTableRelatedElementsAtEnd.IsEmpty()) {
-    int32_t highWaterMark = DiscoverPartialListsAndTables(
+    Element* listOrTableElement = HTMLEditor::DiscoverPartialListsAndTables(
         nodeList, arrayOfListAndTableRelatedElementsAtEnd);
     // don't orphan partial list or table structure
-    if (highWaterMark >= 0) {
-      ReplaceOrphanedStructure(StartOrEnd::end, nodeList,
-                               arrayOfListAndTableRelatedElementsAtEnd,
-                               highWaterMark);
+    if (listOrTableElement) {
+      ReplaceOrphanedStructure(StartOrEnd::end, nodeList, *listOrTableElement);
     }
   }
 
   MOZ_ASSERT(pointToInsert.GetContainer()->GetChildAt_Deprecated(
                  pointToInsert.Offset()) == pointToInsert.GetChild());
 
   // Loop over the node list and paste the nodes:
   nsCOMPtr<nsINode> parentBlock =
@@ -2749,59 +2746,93 @@ void HTMLEditor::CollectListAndTableRela
   for (nsIContent* content = nsIContent::FromNode(&aNode); content;
        content = content->GetParentElement()) {
     if (HTMLEditUtils::IsList(content) || HTMLEditUtils::IsTable(content)) {
       aOutArrayOfListAndTableElements.AppendElement(*content->AsElement());
     }
   }
 }
 
-int32_t HTMLEditor::DiscoverPartialListsAndTables(
-    nsTArray<OwningNonNull<nsINode>>& aPasteNodes,
-    nsTArray<OwningNonNull<Element>>& aListsAndTables) {
-  int32_t ret = -1;
-  int32_t listAndTableParents = aListsAndTables.Length();
-
-  // Scan insertion list for table elements (other than table).
-  for (auto& curNode : aPasteNodes) {
-    if (HTMLEditUtils::IsTableElement(curNode) &&
-        !curNode->IsHTMLElement(nsGkAtoms::table)) {
-      nsCOMPtr<Element> table = curNode->GetParentElement();
-      while (table && !table->IsHTMLElement(nsGkAtoms::table)) {
-        table = table->GetParentElement();
-      }
-      if (table) {
-        int32_t idx = aListsAndTables.IndexOf(table);
-        if (idx == -1) {
-          return ret;
-        }
-        ret = idx;
-        if (ret == listAndTableParents - 1) {
-          return ret;
+// static
+Element* HTMLEditor::DiscoverPartialListsAndTables(
+    const nsTArray<OwningNonNull<nsINode>>& aArrayOfNodes,
+    const nsTArray<OwningNonNull<Element>>&
+        aArrayOfListAndTableRelatedElements) {
+  Element* lastFoundAncestorListOrTableElement = nullptr;
+  for (auto& node : aArrayOfNodes) {
+    if (HTMLEditUtils::IsTableElement(node) &&
+        !node->IsHTMLElement(nsGkAtoms::table)) {
+      Element* tableElement = nullptr;
+      for (Element* maybeTableElement = node->GetParentElement();
+           maybeTableElement;
+           maybeTableElement = maybeTableElement->GetParentElement()) {
+        if (maybeTableElement->IsHTMLElement(nsGkAtoms::table)) {
+          tableElement = maybeTableElement;
+          break;
         }
       }
-    }
-    if (HTMLEditUtils::IsListItem(curNode)) {
-      nsCOMPtr<Element> list = curNode->GetParentElement();
-      while (list && !HTMLEditUtils::IsList(list)) {
-        list = list->GetParentElement();
+      if (!tableElement) {
+        continue;
+      }
+      // If we find a `<table>` element which is an ancestor of a table
+      // related element and is not an acestor of first nor last of
+      // aArrayOfNodes, return the last found list or `<table>` element.
+      // XXX Is that really expected that this returns a list element in this
+      //     case?
+      if (!aArrayOfListAndTableRelatedElements.Contains(tableElement)) {
+        return lastFoundAncestorListOrTableElement;
       }
-      if (list) {
-        int32_t idx = aListsAndTables.IndexOf(list);
-        if (idx == -1) {
-          return ret;
-        }
-        ret = idx;
-        if (ret == listAndTableParents - 1) {
-          return ret;
-        }
+      // If we find a `<table>` element which is topmost list or `<table>`
+      // element at first or last of aArrayOfNodes, return it.
+      if (aArrayOfListAndTableRelatedElements.LastElement().get() ==
+          tableElement) {
+        return tableElement;
+      }
+      // Otherwise, store the `<table>` element which is an ancestor but
+      // not topmost ancestor of first or last of aArrayOfNodes.
+      lastFoundAncestorListOrTableElement = tableElement;
+      continue;
+    }
+
+    if (!HTMLEditUtils::IsListItem(node)) {
+      continue;
+    }
+    Element* listElement = nullptr;
+    for (Element* maybeListElement = node->GetParentElement(); maybeListElement;
+         maybeListElement = maybeListElement->GetParentElement()) {
+      if (HTMLEditUtils::IsList(maybeListElement)) {
+        listElement = maybeListElement;
+        break;
       }
     }
+    if (!listElement) {
+      continue;
+    }
+    // If we find a list element which is ancestor of a list item element and
+    // is not an acestor of first nor last of aArrayOfNodes, return the last
+    // found list or `<table>` element.
+    // XXX Is that really expected that this returns a `<table>` element in
+    //     this case?
+    if (!aArrayOfListAndTableRelatedElements.Contains(listElement)) {
+      return lastFoundAncestorListOrTableElement;
+    }
+    // If we find a list element which is topmost list or `<table>` element at
+    // first or last of aArrayOfNodes, return it.
+    if (aArrayOfListAndTableRelatedElements.LastElement().get() ==
+        listElement) {
+      return listElement;
+    }
+    // Otherwise, store the list element which is an ancestor but not topmost
+    // ancestor of first or last of aArrayOfNodes.
+    lastFoundAncestorListOrTableElement = listElement;
   }
-  return ret;
+
+  // If we find only non-topmost list or `<table>` element, returns the last
+  // found one (meaning bottommost one).  Otherwise, nullptr.
+  return lastFoundAncestorListOrTableElement;
 }
 
 // static
 Element* HTMLEditor::FindReplaceableTableElement(
     Element& aTableElement, nsINode& aNodeMaybeInTableElement) {
   MOZ_ASSERT(aTableElement.IsHTMLElement(nsGkAtoms::table));
   // Perhaps, this is designed for climbing up the DOM tree from
   // aNodeMaybeInTableElement to aTableElement and making sure that
@@ -2878,38 +2909,36 @@ bool HTMLEditor::IsReplaceableListElemen
     // XXX If we find another list element, why don't we keep searching
     //     from its parent?
   }
   return false;
 }
 
 void HTMLEditor::ReplaceOrphanedStructure(
     StartOrEnd aStartOrEnd, nsTArray<OwningNonNull<nsINode>>& aNodeArray,
-    nsTArray<OwningNonNull<Element>>& aListAndTableArray,
-    int32_t aHighWaterMark) {
+    Element& aListOrTableElement) {
   MOZ_ASSERT(!aNodeArray.IsEmpty());
 
   OwningNonNull<nsINode>& edgeNode =
       aStartOrEnd == StartOrEnd::end ? aNodeArray.LastElement() : aNodeArray[0];
-  OwningNonNull<Element> curNode = aListAndTableArray[aHighWaterMark];
 
   // Find substructure of list or table that must be included in paste.
   Element* replaceElement;
-  if (HTMLEditUtils::IsList(curNode)) {
-    if (!HTMLEditor::IsReplaceableListElement(curNode, edgeNode)) {
+  if (HTMLEditUtils::IsList(&aListOrTableElement)) {
+    if (!HTMLEditor::IsReplaceableListElement(aListOrTableElement, edgeNode)) {
       return;
     }
-    replaceElement = curNode;
-  } else if (curNode->IsHTMLElement(nsGkAtoms::table)) {
-    replaceElement = HTMLEditor::FindReplaceableTableElement(curNode, edgeNode);
+    replaceElement = &aListOrTableElement;
+  } else {
+    MOZ_ASSERT(aListOrTableElement.IsHTMLElement(nsGkAtoms::table));
+    replaceElement =
+        HTMLEditor::FindReplaceableTableElement(aListOrTableElement, edgeNode);
     if (!replaceElement) {
       return;
     }
-  } else {
-    return;
   }
 
   // If we found substructure, paste it instead of its descendants.
   // Postprocess list to remove any descendants of this node so that we don't
   // insert them twice.
   uint32_t removedCount = 0;
   uint32_t originalLength = aNodeArray.Length();
   for (uint32_t i = 0; i < originalLength; i++) {
--- a/gfx/ipc/CrossProcessPaint.cpp
+++ b/gfx/ipc/CrossProcessPaint.cpp
@@ -350,17 +350,17 @@ void CrossProcessPaint::MaybeResolve() {
   RefPtr<dom::ImageBitmap> bitmap = dom::ImageBitmap::CreateFromSourceSurface(
       mPromise->GetParentObject(), root, rv);
 
   if (!rv.Failed()) {
     CPP_LOG("Success, fulfilling promise.\n");
     mPromise->MaybeResolve(bitmap);
   } else {
     CPP_LOG("Couldn't create ImageBitmap for SourceSurface.\n");
-    mPromise->MaybeReject(rv);
+    mPromise->MaybeReject(std::move(rv));
   }
   Clear();
 }
 
 nsresult CrossProcessPaint::ResolveInternal(dom::TabId aTabId,
                                             ResolvedSurfaceMap* aResolved) {
   // We should not have resolved this paint already
   MOZ_ASSERT(!aResolved->GetWeak(aTabId));
--- a/gfx/layers/ipc/CompositorBridgeChild.cpp
+++ b/gfx/layers/ipc/CompositorBridgeChild.cpp
@@ -40,16 +40,17 @@
 #include "nsTArray.h"         // for nsTArray, nsTArray_Impl
 #include "nsXULAppAPI.h"      // for XRE_GetIOMessageLoop, etc
 #include "FrameLayerBuilder.h"
 #include "mozilla/dom/BrowserChild.h"
 #include "mozilla/dom/BrowserParent.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/Unused.h"
 #include "mozilla/DebugOnly.h"
+#include "nsThreadUtils.h"
 #if defined(XP_WIN)
 #  include "WinUtils.h"
 #endif
 #include "mozilla/widget/CompositorWidget.h"
 #ifdef MOZ_WIDGET_SUPPORTS_OOP_COMPOSITING
 #  include "mozilla/widget/CompositorWidgetChild.h"
 #endif
 #include "VsyncSource.h"
@@ -104,16 +105,27 @@ CompositorBridgeChild::~CompositorBridge
     gfxCriticalError() << "CompositorBridgeChild was not deinitialized";
   }
 }
 
 bool CompositorBridgeChild::IsSameProcess() const {
   return OtherPid() == base::GetCurrentProcId();
 }
 
+void CompositorBridgeChild::PrepareFinalDestroy() {
+  // Because of medium high priority DidComposite, we need to repost to
+  // medium high priority queue to ensure the actor is destroyed after possible
+  // pending DidComposite message.
+  nsCOMPtr<nsIRunnable> runnable =
+      NewRunnableMethod("CompositorBridgeChild::AfterDestroy", this,
+                        &CompositorBridgeChild::AfterDestroy);
+  NS_DispatchToCurrentThreadQueue(runnable.forget(),
+                                  EventQueuePriority::MediumHigh);
+}
+
 void CompositorBridgeChild::AfterDestroy() {
   // Note that we cannot rely upon mCanSend here because we already set that to
   // false to prevent normal IPDL calls from being made after SendWillClose.
   // The only time we should not issue Send__delete__ is if the actor is already
   // destroyed, e.g. the compositor process crashed.
   if (!mActorDestroyed) {
     Send__delete__(this);
     mActorDestroyed = true;
@@ -154,18 +166,18 @@ void CompositorBridgeChild::Destroy() {
   // Flush async paints before we destroy texture data.
   FlushAsyncPaints();
 
   if (!mCanSend) {
     // We may have already called destroy but still have lingering references
     // or CompositorBridgeChild::ActorDestroy was called. Ensure that we do our
     // post destroy clean up no matter what. It is safe to call multiple times.
     MessageLoop::current()->PostTask(
-        NewRunnableMethod("CompositorBridgeChild::AfterDestroy", selfRef,
-                          &CompositorBridgeChild::AfterDestroy));
+        NewRunnableMethod("CompositorBridgeChild::PrepareFinalDestroy", selfRef,
+                          &CompositorBridgeChild::PrepareFinalDestroy));
     return;
   }
 
   AutoTArray<PLayerTransactionChild*, 16> transactions;
   ManagedPLayerTransactionChild(transactions);
   for (int i = transactions.Length() - 1; i >= 0; --i) {
     RefPtr<LayerTransactionChild> layers =
         static_cast<LayerTransactionChild*>(transactions[i]);
@@ -216,18 +228,18 @@ void CompositorBridgeChild::Destroy() {
   // destruction of shared memory). We need to ensure this gets processed by the
   // CompositorBridgeChild before it gets destroyed. It suffices to ensure that
   // events already in the MessageLoop get processed before the
   // CompositorBridgeChild is destroyed, so we add a task to the MessageLoop to
   // handle compositor destruction.
 
   // From now on we can't send any message message.
   MessageLoop::current()->PostTask(
-      NewRunnableMethod("CompositorBridgeChild::AfterDestroy", selfRef,
-                        &CompositorBridgeChild::AfterDestroy));
+      NewRunnableMethod("CompositorBridgeChild::PrepareFinalDestroy", selfRef,
+                        &CompositorBridgeChild::PrepareFinalDestroy));
 }
 
 // static
 void CompositorBridgeChild::ShutDown() {
   if (sCompositorBridge) {
     sCompositorBridge->Destroy();
     SpinEventLoopUntil([&]() { return !sCompositorBridge; });
   }
--- a/gfx/layers/ipc/CompositorBridgeChild.h
+++ b/gfx/layers/ipc/CompositorBridgeChild.h
@@ -270,16 +270,17 @@ class CompositorBridgeChild final : publ
   // Private destructor, to discourage deletion outside of Release():
   virtual ~CompositorBridgeChild();
 
   // Must only be called from the paint thread. If the main thread is delaying
   // IPC messages, this forwards all such delayed IPC messages to the I/O thread
   // and resumes IPC.
   void ResumeIPCAfterAsyncPaint();
 
+  void PrepareFinalDestroy();
   void AfterDestroy();
 
   PLayerTransactionChild* AllocPLayerTransactionChild(
       const nsTArray<LayersBackend>& aBackendHints, const LayersId& aId);
 
   bool DeallocPLayerTransactionChild(PLayerTransactionChild* aChild);
 
   void ActorDestroy(ActorDestroyReason aWhy) override;
--- a/gfx/layers/ipc/PCompositorBridge.ipdl
+++ b/gfx/layers/ipc/PCompositorBridge.ipdl
@@ -115,18 +115,19 @@ child:
   // TextureSources are recreated.
   async InvalidateLayers(LayersId layersId);
 
   // The compositor completed a layers transaction. id is the layers id
   // of the child layer tree that was composited (or 0 when notifying
   // the root layer tree).
   // transactionId is the id of the transaction before this composite, or 0
   // if there was no transaction since the last composite.
-  async DidComposite(LayersId id, TransactionId transactionId,
-                     TimeStamp compositeStart, TimeStamp compositeEnd);
+  prio(mediumhigh) async DidComposite(LayersId id, TransactionId transactionId,
+                                      TimeStamp compositeStart,
+                                      TimeStamp compositeEnd);
 
   async NotifyFrameStats(FrameStats[] aFrameStats);
 
   /**
    * Parent informs the child that the graphics objects are ready for
    * compositing.  This usually means that the graphics objects (textures
    * and the like) are available on the GPU.  This is used for chrome UI.
    * @see RequestNotifyAfterRemotePaint
--- a/gfx/thebes/VsyncSource.h
+++ b/gfx/thebes/VsyncSource.h
@@ -15,22 +15,17 @@
 
 namespace mozilla {
 class RefreshTimerVsyncDispatcher;
 class CompositorVsyncDispatcher;
 
 class VsyncIdType {};
 typedef layers::BaseTransactionId<VsyncIdType> VsyncId;
 
-namespace layout {
-class PVsyncChild;
-}
-
 namespace gfx {
-class PVsyncBridgeParent;
 
 // Controls how and when to enable/disable vsync. Lives as long as the
 // gfxPlatform does on the parent process
 class VsyncSource {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VsyncSource)
 
   typedef mozilla::RefreshTimerVsyncDispatcher RefreshTimerVsyncDispatcher;
   typedef mozilla::CompositorVsyncDispatcher CompositorVsyncDispatcher;
@@ -91,31 +86,20 @@ class VsyncSource {
   void Shutdown();
 
  protected:
   virtual ~VsyncSource() = default;
 };
 
 }  // namespace gfx
 
-namespace recordreplay {
-namespace child {
-void NotifyVsyncObserver();
-}
-}  // namespace recordreplay
-
 struct VsyncEvent {
   VsyncId mId;
   TimeStamp mTime;
 
- private:
   VsyncEvent(const VsyncId& aId, const TimeStamp& aTime)
       : mId(aId), mTime(aTime) {}
   VsyncEvent() {}
-  friend class gfx::VsyncSource::Display;
-  friend class gfx::PVsyncBridgeParent;
-  friend class layout::PVsyncChild;
-  friend void recordreplay::child::NotifyVsyncObserver();
 };
 
 }  // namespace mozilla
 
 #endif /* GFX_VSYNCSOURCE_H */
--- a/gfx/thebes/gfxTextRun.cpp
+++ b/gfx/thebes/gfxTextRun.cpp
@@ -533,17 +533,17 @@ struct MOZ_STACK_CLASS BufferAlphaColor 
     // pop the text, using the color alpha as the opacity
     mContext->PopGroupAndBlend();
     mContext->Restore();
   }
 
   gfxContext* mContext;
 };
 
-void gfxTextRun::Draw(Range aRange, gfx::Point aPt,
+void gfxTextRun::Draw(const Range aRange, const gfx::Point aPt,
                       const DrawParams& aParams) const {
   NS_ASSERTION(aRange.end <= GetLength(), "Substring out of range");
   NS_ASSERTION(aParams.drawMode == DrawMode::GLYPH_PATH ||
                    !(aParams.drawMode & DrawMode::GLYPH_PATH),
                "GLYPH_PATH cannot be used with GLYPH_FILL, GLYPH_STROKE or "
                "GLYPH_STROKE_UNDERNEATH");
   NS_ASSERTION(aParams.drawMode == DrawMode::GLYPH_PATH || !aParams.callbacks,
                "callback must not be specified unless using GLYPH_PATH");
@@ -577,16 +577,25 @@ void gfxTextRun::Draw(Range aRange, gfx:
   // correctly unless first drawn without alpha
   BufferAlphaColor syntheticBoldBuffer(aParams.context);
   Color currentColor;
   bool mayNeedBuffering =
       aParams.drawMode & DrawMode::GLYPH_FILL &&
       aParams.context->HasNonOpaqueNonTransparentColor(currentColor) &&
       !aParams.context->GetTextDrawer();
 
+  // If we need to double-buffer, we'll need to measure the text first to
+  // get the bounds of the area of interest. Ideally we'd do that just for
+  // the specific glyph run(s) that need buffering, but because of bug
+  // 1612610 we currently use the extent of the entire range even when
+  // just buffering a subrange. So we'll measure the full range once and
+  // keep the metrics on hand for any subsequent subranges.
+  gfxTextRun::Metrics metrics;
+  bool gotMetrics = false;
+
   // Set up parameters that will be constant across all glyph runs we need
   // to draw, regardless of the font used.
   TextRunDrawParams params;
   params.context = aParams.context;
   params.devPerApp = 1.0 / double(GetAppUnitsPerDevUnit());
   params.isVerticalRun = IsVertical();
   params.isRTL = IsRightToLeft();
   params.direction = direction;
@@ -598,66 +607,75 @@ void gfxTextRun::Draw(Range aRange, gfx:
   params.callbacks = aParams.callbacks;
   params.runContextPaint = aParams.contextPaint;
   params.paintSVGGlyphs =
       !aParams.callbacks || aParams.callbacks->mShouldPaintSVGGlyphs;
   params.dt = aParams.context->GetDrawTarget();
 
   GlyphRunIterator iter(this, aRange);
   gfxFloat advance = 0.0;
+  gfx::Point pt = aPt;
 
   while (iter.NextRun()) {
     gfxFont* font = iter.GetGlyphRun()->mFont;
     Range runRange(iter.GetStringStart(), iter.GetStringEnd());
 
     bool needToRestore = false;
     if (mayNeedBuffering && HasSyntheticBoldOrColor(font)) {
       needToRestore = true;
-      // Measure text; use the bounding box to determine the area we need
-      // to buffer.
-      gfxTextRun::Metrics metrics =
-          MeasureText(runRange, gfxFont::LOOSE_INK_EXTENTS,
-                      aParams.context->GetDrawTarget(), aParams.provider);
-      if (IsRightToLeft()) {
-        metrics.mBoundingBox.MoveBy(
-            gfxPoint(aPt.x - metrics.mAdvanceWidth, aPt.y));
-      } else {
-        metrics.mBoundingBox.MoveBy(gfxPoint(aPt.x, aPt.y));
+      if (!gotMetrics) {
+        // Measure text; use the bounding box to determine the area we need
+        // to buffer. We measure the entire range, rather than just the glyph
+        // run that we're actually handling, because of bug 1612610: if the
+        // bounding box passed to PushSolidColor does not intersect the
+        // drawTarget's current clip, the skia backend fails to clip properly.
+        // This means we may use a larger buffer than actually needed, but is
+        // otherwise harmless.
+        metrics =
+            MeasureText(aRange, gfxFont::LOOSE_INK_EXTENTS,
+                        aParams.context->GetDrawTarget(), aParams.provider);
+        if (IsRightToLeft()) {
+          metrics.mBoundingBox.MoveBy(
+              gfxPoint(aPt.x - metrics.mAdvanceWidth, aPt.y));
+        } else {
+          metrics.mBoundingBox.MoveBy(gfxPoint(aPt.x, aPt.y));
+        }
+        gotMetrics = true;
       }
       syntheticBoldBuffer.PushSolidColor(metrics.mBoundingBox, currentColor,
                                          GetAppUnitsPerDevUnit());
     }
 
     Range ligatureRange(runRange);
     ShrinkToLigatureBoundaries(&ligatureRange);
 
     bool drawPartial =
         (aParams.drawMode & (DrawMode::GLYPH_FILL | DrawMode::GLYPH_STROKE)) ||
         (aParams.drawMode == DrawMode::GLYPH_PATH && aParams.callbacks);
-    gfx::Point origPt = aPt;
+    gfx::Point origPt = pt;
 
     if (drawPartial) {
-      DrawPartialLigature(font, Range(runRange.start, ligatureRange.start),
-                          &aPt, aParams.provider, params,
+      DrawPartialLigature(font, Range(runRange.start, ligatureRange.start), &pt,
+                          aParams.provider, params,
                           iter.GetGlyphRun()->mOrientation);
     }
 
-    DrawGlyphs(font, ligatureRange, &aPt, aParams.provider, ligatureRange,
+    DrawGlyphs(font, ligatureRange, &pt, aParams.provider, ligatureRange,
                params, iter.GetGlyphRun()->mOrientation);
 
     if (drawPartial) {
-      DrawPartialLigature(font, Range(ligatureRange.end, runRange.end), &aPt,
+      DrawPartialLigature(font, Range(ligatureRange.end, runRange.end), &pt,
                           aParams.provider, params,
                           iter.GetGlyphRun()->mOrientation);
     }
 
     if (params.isVerticalRun) {
-      advance += (aPt.y - origPt.y) * params.direction;
+      advance += (pt.y - origPt.y) * params.direction;
     } else {
-      advance += (aPt.x - origPt.x) * params.direction;
+      advance += (pt.x - origPt.x) * params.direction;
     }
 
     // composite result when synthetic bolding used
     if (needToRestore) {
       syntheticBoldBuffer.PopAlpha();
     }
   }
 
--- a/gfx/thebes/gfxTextRun.h
+++ b/gfx/thebes/gfxTextRun.h
@@ -269,17 +269,17 @@ class gfxTextRun : public gfxShapedText 
    *   Draw(Range(middle, end), pt, ...) followed by
    *   Draw(Range(start, middle), gfxPoint(pt.x + advance, pt.y), ...)
    * should have the same effect as
    *   Draw(Range(start, end), pt, ...)
    *
    * Glyphs should be drawn in logical content order, which can be significant
    * if they overlap (perhaps due to negative spacing).
    */
-  void Draw(Range aRange, mozilla::gfx::Point aPt,
+  void Draw(const Range aRange, const mozilla::gfx::Point aPt,
             const DrawParams& aParams) const;
 
   /**
    * Draws the emphasis marks for this text run. Uses only GetSpacing
    * from aProvider. The provided point is the baseline origin of the
    * line of emphasis marks.
    */
   void DrawEmphasisMarks(gfxContext* aContext, gfxTextRun* aMark,
--- a/gfx/vr/ipc/VRManagerChild.cpp
+++ b/gfx/vr/ipc/VRManagerChild.cpp
@@ -38,16 +38,17 @@ VRManagerChild::VRManagerChild()
     : mRuntimeCapabilities(VRDisplayCapabilityFlags::Cap_None),
       mMessageLoop(MessageLoop::current()),
       mFrameRequestCallbackCounter(0),
       mWaitingForEnumeration(false),
       mBackend(layers::LayersBackend::LAYERS_NONE) {
   MOZ_ASSERT(NS_IsMainThread());
 
   mStartTimeStamp = TimeStamp::Now();
+  AddRef();
 }
 
 VRManagerChild::~VRManagerChild() { MOZ_ASSERT(NS_IsMainThread()); }
 
 /*static*/
 void VRManagerChild::IdentifyTextureHost(
     const TextureFactoryIdentifier& aIdentifier) {
   if (sVRManagerChildSingleton) {
@@ -85,35 +86,25 @@ bool VRManagerChild::IsPresenting() {
     result |= display->IsPresenting();
   }
   return result;
 }
 
 /* static */
 bool VRManagerChild::InitForContent(Endpoint<PVRManagerChild>&& aEndpoint) {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(!sVRManagerChildSingleton);
 
   RefPtr<VRManagerChild> child(new VRManagerChild());
   if (!aEndpoint.Bind(child)) {
     return false;
   }
   sVRManagerChildSingleton = child;
   return true;
 }
 
-/* static */
-bool VRManagerChild::ReinitForContent(Endpoint<PVRManagerChild>&& aEndpoint) {
-  MOZ_ASSERT(NS_IsMainThread());
-
-  ShutDown();
-
-  return InitForContent(std::move(aEndpoint));
-}
-
 /*static*/
 void VRManagerChild::InitSameProcess() {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!sVRManagerChildSingleton);
 
   sVRManagerChildSingleton = new VRManagerChild();
   sVRManagerParentSingleton = VRManagerParent::CreateSameProcess();
   sVRManagerChildSingleton->Open(sVRManagerParentSingleton->GetIPCChannel(),
@@ -130,31 +121,26 @@ void VRManagerChild::InitWithGPUProcess(
   if (!aEndpoint.Bind(sVRManagerChildSingleton)) {
     MOZ_CRASH("Couldn't Open() Compositor channel.");
   }
 }
 
 /*static*/
 void VRManagerChild::ShutDown() {
   MOZ_ASSERT(NS_IsMainThread());
-  if (sVRManagerChildSingleton) {
-    sVRManagerChildSingleton->Destroy();
-    SpinEventLoopUntil([&]() { return !sVRManagerChildSingleton; });
+  if (!sVRManagerChildSingleton) {
+    return;
   }
+  sVRManagerChildSingleton->Close();
+  sVRManagerChildSingleton = nullptr;
 }
 
-void VRManagerChild::Destroy() {
-  // Keep ourselves alive until everything has been shut down
-  RefPtr<VRManagerChild> selfRef = this;
-  MessageLoop::current()->PostTask(NewRunnableMethod(
-      "VRManagerChild::AfterDestroy", selfRef, &VRManagerChild::AfterDestroy));
-}
+void VRManagerChild::ActorDealloc() { Release(); }
 
-void VRManagerChild::AfterDestroy() {
-  Close();
+void VRManagerChild::ActorDestroy(ActorDestroyReason aReason) {
   if (sVRManagerChildSingleton == this) {
     sVRManagerChildSingleton = nullptr;
   }
 }
 
 PVRLayerChild* VRManagerChild::AllocPVRLayerChild(const uint32_t& aDisplayID,
                                                   const uint32_t& aGroup) {
   return VRLayerChild::CreateIPDLActor();
--- a/gfx/vr/ipc/VRManagerChild.h
+++ b/gfx/vr/ipc/VRManagerChild.h
@@ -68,17 +68,16 @@ class VRManagerChild : public PVRManager
   bool RefreshVRDisplaysWithCallback(uint64_t aWindowId);
   bool EnumerateVRDisplays();
   void DetectRuntimes();
   void AddPromise(const uint32_t& aID, dom::Promise* aPromise);
 
   static void InitSameProcess();
   static void InitWithGPUProcess(Endpoint<PVRManagerChild>&& aEndpoint);
   static bool InitForContent(Endpoint<PVRManagerChild>&& aEndpoint);
-  static bool ReinitForContent(Endpoint<PVRManagerChild>&& aEndpoint);
   static void ShutDown();
 
   static bool IsCreated();
   static bool IsPresenting();
 
   PVRLayerChild* CreateVRLayer(uint32_t aDisplayID, nsIEventTarget* aTarget,
                                uint32_t aGroup);
 
@@ -98,32 +97,33 @@ class VRManagerChild : public PVRManager
   void UpdateDisplayInfo(const VRDisplayInfo& aDisplayInfo);
   void FireDOMVRDisplayMountedEvent(uint32_t aDisplayID);
   void FireDOMVRDisplayUnmountedEvent(uint32_t aDisplayID);
   void FireDOMVRDisplayConnectEvent(uint32_t aDisplayID);
   void FireDOMVRDisplayDisconnectEvent(uint32_t aDisplayID);
   void FireDOMVRDisplayPresentChangeEvent(uint32_t aDisplayID);
   void FireDOMVRDisplayConnectEventsForLoad(VRManagerEventObserver* aObserver);
 
-  virtual void HandleFatalError(const char* aMsg) const override;
+  void HandleFatalError(const char* aMsg) const override;
+  void ActorDestroy(ActorDestroyReason aReason) override;
 
   void RunPuppet(const nsTArray<uint64_t>& aBuffer, dom::Promise* aPromise,
                  ErrorResult& aRv);
   void ResetPuppet(dom::Promise* aPromise, ErrorResult& aRv);
 
  protected:
   explicit VRManagerChild();
   ~VRManagerChild();
-  void Destroy();
-  void AfterDestroy();
 
   PVRLayerChild* AllocPVRLayerChild(const uint32_t& aDisplayID,
                                     const uint32_t& aGroup);
   bool DeallocPVRLayerChild(PVRLayerChild* actor);
 
+  void ActorDealloc() override;
+
   // MOZ_CAN_RUN_SCRIPT_BOUNDARY until we can mark ipdl-generated things as
   // MOZ_CAN_RUN_SCRIPT.
   MOZ_CAN_RUN_SCRIPT_BOUNDARY
   mozilla::ipc::IPCResult RecvUpdateDisplayInfo(
       const VRDisplayInfo& aDisplayInfo);
   mozilla::ipc::IPCResult RecvUpdateRuntimeCapabilities(
       const VRDisplayCapabilityFlags& aCapabilities);
   mozilla::ipc::IPCResult RecvReplyGamepadVibrateHaptic(
--- a/image/imgLoader.cpp
+++ b/image/imgLoader.cpp
@@ -487,17 +487,19 @@ class imgMemoryReporter final : public n
                 "Decoded image data which is stored on the heap.",
                 aCounter.DecodedHeap());
 
     ReportValue(aHandleReport, aData, KIND_NONHEAP, aPathPrefix,
                 "decoded-nonheap",
                 "Decoded image data which isn't stored on the heap.",
                 aCounter.DecodedNonHeap());
 
-    ReportValue(aHandleReport, aData, KIND_OTHER, aPathPrefix,
+    // We don't know for certain whether or not it is on the heap, so let's
+    // just report it as non-heap for reporting purposes.
+    ReportValue(aHandleReport, aData, KIND_NONHEAP, aPathPrefix,
                 "decoded-unknown",
                 "Decoded image data which is unknown to be on the heap or not.",
                 aCounter.DecodedUnknown());
   }
 
   static void ReportSourceValue(nsIHandleReportCallback* aHandleReport,
                                 nsISupports* aData,
                                 const nsACString& aPathPrefix,
--- a/ipc/glue/ForkServiceChild.cpp
+++ b/ipc/glue/ForkServiceChild.cpp
@@ -121,31 +121,35 @@ already_AddRefed<ForkServerLauncher> For
   RefPtr<ForkServerLauncher> launcher = mSingleton;
   return launcher.forget();
 }
 
 NS_IMETHODIMP
 ForkServerLauncher::Observe(nsISupports* aSubject, const char* aTopic,
                             const char16_t* aData) {
   if (!mHaveStartedClient &&
-      strcmp(aTopic, NS_XPCOM_STARTUP_CATEGORY) == 0 &&
-      StaticPrefs::dom_ipc_forkserver_enable_AtStartup()) {
-    mHaveStartedClient = true;
-    ForkServiceChild::StartForkServer();
+      strcmp(aTopic, "final-ui-startup") == 0) {
+    if (StaticPrefs::dom_ipc_forkserver_enable_AtStartup()) {
+      mHaveStartedClient = true;
+      ForkServiceChild::StartForkServer();
 
-    nsCOMPtr<nsIObserverService> obsSvc =
+      nsCOMPtr<nsIObserverService> obsSvc =
         mozilla::services::GetObserverService();
-    MOZ_ASSERT(obsSvc != nullptr);
-    obsSvc->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+      MOZ_ASSERT(obsSvc != nullptr);
+      obsSvc->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+    } else {
+      mSingleton = nullptr;
+    }
   }
 
-  if (mHaveStartedClient &&
-      strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
-    mHaveStartedClient = false;
-    ForkServiceChild::StopForkServer();
+  if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
+    if (mHaveStartedClient) {
+      mHaveStartedClient = false;
+      ForkServiceChild::StopForkServer();
+    }
 
     // To make leak checker happy!
     mSingleton = nullptr;
   }
   return NS_OK;
 }
 
 }  // namespace ipc
--- a/ipc/glue/components.conf
+++ b/ipc/glue/components.conf
@@ -12,11 +12,11 @@ Classes = [
     {
         'cid': '{cdb4757f-f51b-40c0-8b38-66d12c3bff7b}',
         'contract_ids': ['@mozilla.org/fork-server-launcher;1'],
         'singleton': True,
         'type': 'mozilla::ipc::ForkServerLauncher',
         'headers': ['mozilla/ipc/ForkServiceChild.h'],
         'constructor': 'mozilla::ipc::ForkServerLauncher::Create',
         'processes': ProcessSelector.MAIN_PROCESS_ONLY,
-        'categories': {'xpcom-startup': 'Fork Server Launcher'},
+        'categories': {'final-ui-startup': 'Fork Server Launcher'},
     },
 ]
--- a/js/public/Principals.h
+++ b/js/public/Principals.h
@@ -65,17 +65,17 @@ extern JS_PUBLIC_API void JS_DropPrincip
 // 'subsumes' is left up to the browser. Subsumption is checked inside the JS
 // engine when determining, e.g., which stack frames to display in a backtrace.
 typedef bool (*JSSubsumesOp)(JSPrincipals* first, JSPrincipals* second);
 
 /*
  * Used to check if a CSP instance wants to disable eval() and friends.
  * See GlobalObject::isRuntimeCodeGenEnabled() in vm/GlobalObject.cpp.
  */
-typedef bool (*JSCSPEvalChecker)(JSContext* cx, JS::HandleValue value);
+typedef bool (*JSCSPEvalChecker)(JSContext* cx, JS::HandleString code);
 
 struct JSSecurityCallbacks {
   JSCSPEvalChecker contentSecurityPolicyAllows;
   JSSubsumesOp subsumes;
 };
 
 extern JS_PUBLIC_API void JS_SetSecurityCallbacks(
     JSContext* cx, const JSSecurityCallbacks* callbacks);
--- a/js/src/builtin/Eval.cpp
+++ b/js/src/builtin/Eval.cpp
@@ -204,46 +204,49 @@ static EvalJSONResult TryEvalJSON(JSCont
 
   return linearChars.isLatin1()
              ? ParseEvalStringAsJSON(cx, linearChars.latin1Range(), rval)
              : ParseEvalStringAsJSON(cx, linearChars.twoByteRange(), rval);
 }
 
 enum EvalType { DIRECT_EVAL, INDIRECT_EVAL };
 
+// 18.2.1.1 PerformEval
+//
 // Common code implementing direct and indirect eval.
 //
 // Evaluate call.argv[2], if it is a string, in the context of the given calling
 // frame, with the provided scope chain, with the semantics of either a direct
 // or indirect eval (see ES5 10.4.2).  If this is an indirect eval, env
 // must be a global object.
 //
 // On success, store the completion value in call.rval and return true.
 static bool EvalKernel(JSContext* cx, HandleValue v, EvalType evalType,
                        AbstractFramePtr caller, HandleObject env,
                        jsbytecode* pc, MutableHandleValue vp) {
   MOZ_ASSERT((evalType == INDIRECT_EVAL) == !caller);
   MOZ_ASSERT((evalType == INDIRECT_EVAL) == !pc);
   MOZ_ASSERT_IF(evalType == INDIRECT_EVAL, IsGlobalLexicalEnvironment(env));
   AssertInnerizedEnvironmentChain(cx, *env);
 
-  if (!GlobalObject::isRuntimeCodeGenEnabled(cx, v, cx->global())) {
+  // Step 2.
+  if (!v.isString()) {
+    vp.set(v);
+    return true;
+  }
+
+  // Steps 3-4.
+  RootedString str(cx, v.toString());
+  if (!GlobalObject::isRuntimeCodeGenEnabled(cx, str, cx->global())) {
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                               JSMSG_CSP_BLOCKED_EVAL);
     return false;
   }
 
-  // ES5 15.1.2.1 step 1.
-  if (!v.isString()) {
-    vp.set(v);
-    return true;
-  }
-  RootedString str(cx, v.toString());
-
-  // ES5 15.1.2.1 steps 2-8.
+  // Step 5 ff.
 
   // Per ES5, indirect eval runs in the global scope. (eval is specified this
   // way so that the compiler can make assumptions about what bindings may or
   // may not exist in the current frame if it doesn't see 'eval'.)
   MOZ_ASSERT_IF(evalType != DIRECT_EVAL,
                 cx->global() == &env->as<LexicalEnvironmentObject>().global());
 
   RootedLinearString linearStr(cx, str->ensureLinear(cx));
@@ -349,18 +352,17 @@ static bool EvalKernel(JSContext* cx, Ha
 }
 
 bool js::DirectEvalStringFromIon(JSContext* cx, HandleObject env,
                                  HandleScript callerScript,
                                  HandleValue newTargetValue, HandleString str,
                                  jsbytecode* pc, MutableHandleValue vp) {
   AssertInnerizedEnvironmentChain(cx, *env);
 
-  RootedValue v(cx, StringValue(str));
-  if (!GlobalObject::isRuntimeCodeGenEnabled(cx, v, cx->global())) {
+  if (!GlobalObject::isRuntimeCodeGenEnabled(cx, str, cx->global())) {
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                               JSMSG_CSP_BLOCKED_EVAL);
     return false;
   }
 
   // ES5 15.1.2.1 steps 2-8.
 
   RootedLinearString linearStr(cx, str->ensureLinear(cx));
--- a/js/src/ds/InlineTable.h
+++ b/js/src/ds/InlineTable.h
@@ -13,18 +13,47 @@
 
 #include "js/AllocPolicy.h"
 #include "js/HashTable.h"
 
 namespace js {
 
 namespace detail {
 
+// The InlineTable below needs an abstract way of testing keys for
+// tombstone values, and to set a key in an entry to a tombstone.
+// This is provided by the KeyPolicy generic type argument, which
+// has a default implementation for pointers provided below.
+
+// A default implementation of a KeyPolicy for some types (only pointer
+// types for now).
+//
+// The `KeyPolicy` type parameter informs an InlineTable of how to
+// check for tombstone values and to set tombstone values within
+// the domain of key (entry).
+//
+// A `KeyPolicy` for some key type `K` must provide two static methods:
+//   static bool isTombstone(const K& key);
+//   static void setToTombstone(K& key);
+template <typename K>
+class DefaultKeyPolicy;
+
+template <typename T>
+class DefaultKeyPolicy<T*> {
+  DefaultKeyPolicy() = delete;
+  DefaultKeyPolicy(const T*&) = delete;
+
+ public:
+  static bool isTombstone(T* const& ptr) { return ptr == nullptr; }
+  static void setToTombstone(T*& ptr) { ptr = nullptr; }
+};
+
 template <typename InlineEntry, typename Entry, typename Table,
-          typename HashPolicy, typename AllocPolicy, size_t InlineEntries>
+          typename HashPolicy, typename AllocPolicy, typename KeyPolicy,
+          size_t InlineEntries>
 class InlineTable : private AllocPolicy {
  private:
   using TablePtr = typename Table::Ptr;
   using TableAddPtr = typename Table::AddPtr;
   using TableRange = typename Table::Range;
   using Lookup = typename HashPolicy::Lookup;
 
   size_t inlNext_;
@@ -293,18 +322,18 @@ class InlineTable : private AllocPolicy 
     return table_.add(p.tableAddPtr_, std::forward<KeyInput>(key),
                       std::forward<Args>(args)...);
   }
 
   void remove(Ptr& p) {
     MOZ_ASSERT(p);
     if (p.isInlinePtr_) {
       MOZ_ASSERT(inlCount_ > 0);
-      MOZ_ASSERT(p.inlPtr_->key != nullptr);
-      p.inlPtr_->key = nullptr;
+      MOZ_ASSERT(!KeyPolicy::isTombstone(p.inlPtr_->key));
+      KeyPolicy::setToTombstone(p.inlPtr_->key);
       --inlCount_;
       return;
     }
     MOZ_ASSERT(usingTable());
     table_.remove(p.tablePtr_);
   }
 
   void remove(const Lookup& l) {
@@ -336,28 +365,28 @@ class InlineTable : private AllocPolicy 
           end_(const_cast<InlineEntry*>(end)),
           isInline_(true) {
       advancePastNulls(cur_);
       MOZ_ASSERT(isInlineRange());
     }
 
     bool assertInlineRangeInvariants() const {
       MOZ_ASSERT(uintptr_t(cur_) <= uintptr_t(end_));
-      MOZ_ASSERT_IF(cur_ != end_, cur_->key != nullptr);
+      MOZ_ASSERT_IF(cur_ != end_, !KeyPolicy::isTombstone(cur_->key));
       return true;
     }
 
     bool isInlineRange() const {
       MOZ_ASSERT_IF(isInline_, assertInlineRangeInvariants());
       return isInline_;
     }
 
     void advancePastNulls(InlineEntry* begin) {
       InlineEntry* newCur = begin;
-      while (newCur < end_ && nullptr == newCur->key) {
+      while (newCur < end_ && KeyPolicy::isTombstone(newCur->key)) {
         ++newCur;
       }
       MOZ_ASSERT(uintptr_t(newCur) <= uintptr_t(end_));
       cur_ = newCur;
     }
 
     void bumpCurPtr() {
       MOZ_ASSERT(isInlineRange());
@@ -398,17 +427,18 @@ class InlineTable : private AllocPolicy 
 // A map with InlineEntries number of entries kept inline in an array.
 //
 // The Key type must be zeroable as zeros are used as tombstone keys.
 // The Value type must have a default constructor.
 //
 // The API is very much like HashMap's.
 template <typename Key, typename Value, size_t InlineEntries,
           typename HashPolicy = DefaultHasher<Key>,
-          typename AllocPolicy = TempAllocPolicy>
+          typename AllocPolicy = TempAllocPolicy,
+          typename KeyPolicy = detail::DefaultKeyPolicy<Key>>
 class InlineMap {
   using Map = HashMap<Key, Value, HashPolicy, AllocPolicy>;
 
   struct InlineEntry {
     Key key;
     Value value;
 
     template <typename KeyInput, typename ValueInput>
@@ -450,17 +480,17 @@ class InlineMap {
       if (mapEntry_) {
         return mapEntry_->value();
       }
       return inlineEntry_->value;
     }
   };
 
   using Impl = detail::InlineTable<InlineEntry, Entry, Map, HashPolicy,
-                                   AllocPolicy, InlineEntries>;
+                                   AllocPolicy, KeyPolicy, InlineEntries>;
 
   Impl impl_;
 
  public:
   using Table = Map;
   using Ptr = typename Impl::Ptr;
   using AddPtr = typename Impl::AddPtr;
   using Range = typename Impl::Range;
@@ -514,17 +544,18 @@ class InlineMap {
 // A set with InlineEntries number of entries kept inline in an array.
 //
 // The T type must be zeroable as zeros are used as tombstone keys.
 // The T type must have a default constructor.
 //
 // The API is very much like HashMap's.
 template <typename T, size_t InlineEntries,
           typename HashPolicy = DefaultHasher<T>,
-          typename AllocPolicy = TempAllocPolicy>
+          typename AllocPolicy = TempAllocPolicy,
+          typename KeyPolicy = detail::DefaultKeyPolicy<T>>
 class InlineSet {
   using Set = HashSet<T, HashPolicy, AllocPolicy>;
 
   struct InlineEntry {
     T key;
 
     template <typename TInput>
     void update(TInput&& key) {
@@ -554,17 +585,17 @@ class InlineSet {
       if (setEntry_) {
         return *setEntry_;
       }
       return inlineEntry_->key;
     }
   };
 
   using Impl = detail::InlineTable<InlineEntry, Entry, Set, HashPolicy,
-                                   AllocPolicy, InlineEntries>;
+                                   AllocPolicy, KeyPolicy, InlineEntries>;
 
   Impl impl_;
 
  public:
   using Table = Set;
   using Ptr = typename Impl::Ptr;
   using AddPtr = typename Impl::AddPtr;
   using Range = typename Impl::Range;
--- a/js/src/vm/GlobalObject.cpp
+++ b/js/src/vm/GlobalObject.cpp
@@ -807,17 +807,17 @@ bool GlobalObject::initSelfHostingBuilti
          InitBareBuiltinCtor(cx, global, JSProto_TypedArray) &&
          InitBareBuiltinCtor(cx, global, JSProto_Uint8Array) &&
          InitBareBuiltinCtor(cx, global, JSProto_Int32Array) &&
          InitBareBuiltinCtor(cx, global, JSProto_Symbol) &&
          DefineFunctions(cx, global, builtins, AsIntrinsic);
 }
 
 /* static */
-bool GlobalObject::isRuntimeCodeGenEnabled(JSContext* cx, HandleValue code,
+bool GlobalObject::isRuntimeCodeGenEnabled(JSContext* cx, HandleString code,
                                            Handle<GlobalObject*> global) {
   HeapSlot& v = global->getSlotRef(RUNTIME_CODEGEN_ENABLED);
   if (v.isUndefined()) {
     /*
      * If there are callbacks, make sure that the CSP callback is installed
      * and that it permits runtime code generation.
      */
     JSCSPEvalChecker allows =
--- a/js/src/vm/GlobalObject.h
+++ b/js/src/vm/GlobalObject.h
@@ -801,17 +801,17 @@ class GlobalObject : public NativeObject
                                     MutableHandleValue funVal);
 
   static RegExpStatics* getRegExpStatics(JSContext* cx,
                                          Handle<GlobalObject*> global);
 
   static JSObject* getOrCreateThrowTypeError(JSContext* cx,
                                              Handle<GlobalObject*> global);
 
-  static bool isRuntimeCodeGenEnabled(JSContext* cx, HandleValue code,
+  static bool isRuntimeCodeGenEnabled(JSContext* cx, HandleString code,
                                       Handle<GlobalObject*> global);
 
   static bool getOrCreateEval(JSContext* cx, Handle<GlobalObject*> global,
                               MutableHandleObject eval);
 
   // Infallibly test whether the given value is the eval function for this
   // global.
   bool valueIsEval(const Value& val);
--- a/js/src/vm/JSFunction.cpp
+++ b/js/src/vm/JSFunction.cpp
@@ -1901,18 +1901,17 @@ static bool CreateDynamicFunction(JSCont
 
   RootedString functionText(cx, sb.finishString());
   if (!functionText) {
     return false;
   }
 
   // Block this call if security callbacks forbid it.
   Handle<GlobalObject*> global = cx->global();
-  RootedValue v(cx, StringValue(functionText));
-  if (!GlobalObject::isRuntimeCodeGenEnabled(cx, v, global)) {
+  if (!GlobalObject::isRuntimeCodeGenEnabled(cx, functionText, global)) {
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                               JSMSG_CSP_BLOCKED_FUNCTION);
     return false;
   }
 
   /*
    * NB: (new Function) is not lexically closed by its caller, it's just an
    * anonymous function in the top-level scope that its constructor inhabits.
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -1346,16 +1346,17 @@ void PresShell::Destroy() {
 
   // Revoke any pending events.  We need to do this and cancel pending reflows
   // before we destroy the frame manager, since apparently frame destruction
   // sometimes spins the event queue when plug-ins are involved(!).
   StopObservingRefreshDriver();
 
   if (rd->GetPresContext() == GetPresContext()) {
     rd->RevokeViewManagerFlush();
+    rd->ClearHasScheduleFlush();
   }
 
   CancelAllPendingReflows();
   CancelPostedReflowCallbacks();
 
   // Destroy the frame manager. This will destroy the frame hierarchy
   mFrameConstructor->WillDestroyFrameTree();
 
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -214,16 +214,18 @@ nsContainerFrame* NS_NewRootBoxFrame(Pre
 
 nsContainerFrame* NS_NewDocElementBoxFrame(PresShell* aPresShell,
                                            ComputedStyle* aStyle);
 
 nsIFrame* NS_NewDeckFrame(PresShell* aPresShell, ComputedStyle* aStyle);
 
 nsIFrame* NS_NewLeafBoxFrame(PresShell* aPresShell, ComputedStyle* aStyle);
 
+nsIFrame* NS_NewStackFrame(PresShell* aPresShell, ComputedStyle* aStyle);
+
 nsIFrame* NS_NewRangeFrame(PresShell* aPresShell, ComputedStyle* aStyle);
 
 nsIFrame* NS_NewImageBoxFrame(PresShell* aPresShell, ComputedStyle* aStyle);
 
 nsIFrame* NS_NewTextBoxFrame(PresShell* aPresShell, ComputedStyle* aStyle);
 
 nsIFrame* NS_NewGroupBoxFrame(PresShell* aPresShell, ComputedStyle* aStyle);
 
@@ -3932,16 +3934,17 @@ static bool IsXULDisplayType(const nsSty
   // -moz-{inline-}box is XUL, unless we're emulating it with flexbox.
   if (!StaticPrefs::layout_css_emulate_moz_box_with_flex() &&
       aDisplay->DisplayInside() == StyleDisplayInside::MozBox) {
     return true;
   }
 
 #ifdef MOZ_XUL
   return (aDisplay->mDisplay == StyleDisplay::MozGrid ||
+          aDisplay->mDisplay == StyleDisplay::MozStack ||
           aDisplay->mDisplay == StyleDisplay::MozGridGroup ||
           aDisplay->mDisplay == StyleDisplay::MozGridLine ||
           aDisplay->mDisplay == StyleDisplay::MozDeck ||
           aDisplay->mDisplay == StyleDisplay::MozPopup);
 #else
   return false;
 #endif
 }
@@ -4128,18 +4131,19 @@ already_AddRefed<ComputedStyle> nsCSSFra
 
   if (!gfxScrollFrame) {
     // Build a XULScrollFrame when the child is a box, otherwise an
     // HTMLScrollFrame
     // XXXbz this is the lone remaining consumer of IsXULDisplayType.
     // I wonder whether we can eliminate that somehow.
     const nsStyleDisplay* displayStyle = aContentStyle->StyleDisplay();
     if (IsXULDisplayType(displayStyle)) {
-      gfxScrollFrame =
-          NS_NewXULScrollFrame(mPresShell, contentStyle, aIsRoot, false);
+      gfxScrollFrame = NS_NewXULScrollFrame(
+          mPresShell, contentStyle, aIsRoot,
+          displayStyle->mDisplay == StyleDisplay::MozStack);
     } else {
       gfxScrollFrame = NS_NewHTMLScrollFrame(mPresShell, contentStyle, aIsRoot);
     }
 
     InitAndRestoreFrame(aState, aContent, aParentFrame, gfxScrollFrame);
   }
 
   MOZ_ASSERT(gfxScrollFrame);
@@ -4461,16 +4465,21 @@ nsCSSFrameConstructor::FindDisplayData(c
           SCROLLABLE_XUL_FCDATA(NS_NewGridRowGroupFrame);
       return &data;
     }
     case StyleDisplayInside::MozGridLine: {
       static const FrameConstructionData data =
           SCROLLABLE_XUL_FCDATA(NS_NewGridRowLeafFrame);
       return &data;
     }
+    case StyleDisplayInside::MozStack: {
+      static const FrameConstructionData data =
+          SCROLLABLE_XUL_FCDATA(NS_NewStackFrame);
+      return &data;
+    }
     case StyleDisplayInside::MozDeck: {
       static const FrameConstructionData data =
           SIMPLE_XUL_FCDATA(NS_NewDeckFrame);
       return &data;
     }
     case StyleDisplayInside::MozPopup: {
       static const FrameConstructionData data =
           FCDATA_DECL(FCDATA_DISALLOW_OUT_OF_FLOW | FCDATA_IS_POPUP |
--- a/layout/base/nsRefreshDriver.cpp
+++ b/layout/base/nsRefreshDriver.cpp
@@ -504,82 +504,89 @@ class VsyncRefreshDriverTimer : public R
   // Since VsyncObservers are refCounted, but the RefreshDriverTimer are
   // explicitly shutdown. We create an inner class that has the VsyncObserver
   // and is shutdown when the RefreshDriverTimer is deleted.
   class RefreshDriverVsyncObserver final : public VsyncObserver {
    public:
     explicit RefreshDriverVsyncObserver(
         VsyncRefreshDriverTimer* aVsyncRefreshDriverTimer)
         : mVsyncRefreshDriverTimer(aVsyncRefreshDriverTimer),
-          mRefreshTickLock("RefreshTickLock"),
+          mParentProcessRefreshTickLock("RefreshTickLock"),
+          mPendingParentProcessVsync(false),
           mRecentVsync(TimeStamp::Now()),
-          mLastChildTick(TimeStamp::Now()),
+          mLastTick(TimeStamp::Now()),
           mVsyncRate(TimeDuration::Forever()),
           mProcessedVsync(true) {
       MOZ_ASSERT(NS_IsMainThread());
     }
 
     class ParentProcessVsyncNotifier final : public Runnable,
                                              public nsIRunnablePriority {
      public:
-      ParentProcessVsyncNotifier(RefreshDriverVsyncObserver* aObserver,
-                                 VsyncId aId, TimeStamp aVsyncTimestamp)
+      explicit ParentProcessVsyncNotifier(RefreshDriverVsyncObserver* aObserver)
           : Runnable(
                 "VsyncRefreshDriverTimer::RefreshDriverVsyncObserver::"
                 "ParentProcessVsyncNotifier"),
-            mObserver(aObserver),
-            mId(aId),
-            mVsyncTimestamp(aVsyncTimestamp) {}
+            mObserver(aObserver) {}
 
       NS_DECL_ISUPPORTS_INHERITED
 
       NS_IMETHOD Run() override {
         MOZ_ASSERT(NS_IsMainThread());
         sHighPriorityEnabled = mozilla::BrowserTabsRemoteAutostart();
 
-        mObserver->TickRefreshDriver(mId, mVsyncTimestamp);
+        mObserver->NotifyParentProcessVsync();
         return NS_OK;
       }
 
       NS_IMETHOD GetPriority(uint32_t* aPriority) override {
         *aPriority = sHighPriorityEnabled
                          ? nsIRunnablePriority::PRIORITY_HIGH
                          : nsIRunnablePriority::PRIORITY_NORMAL;
         return NS_OK;
       }
 
      private:
       ~ParentProcessVsyncNotifier() {}
       RefPtr<RefreshDriverVsyncObserver> mObserver;
-      VsyncId mId;
-      TimeStamp mVsyncTimestamp;
       static mozilla::Atomic<bool> sHighPriorityEnabled;
     };
 
+    void NotifyParentProcessVsync() {
+      MOZ_ASSERT(NS_IsMainThread());
+      MOZ_ASSERT(XRE_IsParentProcess());
+
+      VsyncEvent vsync;
+      {
+        MonitorAutoLock lock(mParentProcessRefreshTickLock);
+        vsync = mRecentParentProcessVsync;
+        mPendingParentProcessVsync = false;
+      }
+      NotifyVsync(vsync);
+    }
+
     bool NotifyVsync(const VsyncEvent& aVsync) override {
       // IMPORTANT: All paths through this method MUST hold a strong ref on
       // |this| for the duration of the TickRefreshDriver callback.
 
       if (!NS_IsMainThread()) {
         MOZ_ASSERT(XRE_IsParentProcess());
         // Compress vsync notifications such that only 1 may run at a time
         // This is so that we don't flood the refresh driver with vsync messages
         // if the main thread is blocked for long periods of time
         {  // scope lock
-          MonitorAutoLock lock(mRefreshTickLock);
-          mRecentVsync = aVsync.mTime;
-          mRecentVsyncId = aVsync.mId;
-          if (!mProcessedVsync) {
+          MonitorAutoLock lock(mParentProcessRefreshTickLock);
+          mRecentParentProcessVsync = aVsync;
+          if (mPendingParentProcessVsync) {
             return true;
           }
-          mProcessedVsync = false;
+          mPendingParentProcessVsync = true;
         }
-
         nsCOMPtr<nsIRunnable> vsyncEvent =
-            new ParentProcessVsyncNotifier(this, aVsync.mId, aVsync.mTime);
+            new ParentProcessVsyncNotifier(this);
         NS_DispatchToMainThread(vsyncEvent);
       } else {
         mRecentVsync = aVsync.mTime;
         mRecentVsyncId = aVsync.mId;
         if (!mBlockUntil.IsNull() && mBlockUntil > aVsync.mTime) {
           if (mProcessedVsync) {
             // Re-post vsync update as a normal priority runnable. This way
             // runnables already in normal priority queue get processed.
@@ -588,17 +595,18 @@ class VsyncRefreshDriverTimer : public R
                 "RefreshDriverVsyncObserver::NormalPriorityNotify", this,
                 &RefreshDriverVsyncObserver::NormalPriorityNotify);
             NS_DispatchToMainThread(vsyncEvent);
           }
 
           return true;
         }
 
-        if (StaticPrefs::layout_lower_priority_refresh_driver_during_load()) {
+        if (StaticPrefs::layout_lower_priority_refresh_driver_during_load() &&
+            mVsyncRefreshDriverTimer) {
           nsPresContext* pctx =
               mVsyncRefreshDriverTimer->GetPresContextForOnlyRefreshDriver();
           if (pctx && pctx->HadContentfulPaint() && pctx->Document() &&
               pctx->Document()->GetReadyStateEnum() <
                   Document::READYSTATE_COMPLETE) {
             nsPIDOMWindowInner* win = pctx->Document()->GetInnerWindow();
             uint32_t frameRateMultiplier = pctx->GetNextFrameRateMultiplier();
             if (!frameRateMultiplier) {
@@ -639,24 +647,22 @@ class VsyncRefreshDriverTimer : public R
     }
 
     void Shutdown() {
       MOZ_ASSERT(NS_IsMainThread());
       mVsyncRefreshDriverTimer = nullptr;
     }
 
     void OnTimerStart() {
-      if (!XRE_IsParentProcess()) {
-        mLastChildTick = TimeStamp::Now();
-      }
+      mLastTick = TimeStamp::Now();
     }
 
     void NormalPriorityNotify() {
-      if (mLastProcessedTickInChildProcess.IsNull() ||
-          mRecentVsync > mLastProcessedTickInChildProcess) {
+      if (mLastProcessedTick.IsNull() ||
+          mRecentVsync > mLastProcessedTick) {
         // mBlockUntil is for high priority vsync notifications only.
         mBlockUntil = TimeStamp();
         TickRefreshDriver(mRecentVsyncId, mRecentVsync);
       }
 
       mProcessedVsync = true;
     }
 
@@ -671,17 +677,17 @@ class VsyncRefreshDriverTimer : public R
         uint32_t sample = (uint32_t)vsyncLatency.ToMilliseconds();
         Telemetry::Accumulate(
             Telemetry::FX_REFRESH_DRIVER_CHROME_FRAME_DELAY_MS, sample);
         Telemetry::Accumulate(
             Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS, sample);
         RecordJank(sample);
       } else if (mVsyncRate != TimeDuration::Forever()) {
         TimeDuration contentDelay =
-            (TimeStamp::Now() - mLastChildTick) - mVsyncRate;
+            (TimeStamp::Now() - mLastTick) - mVsyncRate;
         if (contentDelay.ToMilliseconds() < 0) {
           // Vsyncs are noisy and some can come at a rate quicker than
           // the reported hardware rate. In those cases, consider that we have 0
           // delay.
           contentDelay = TimeDuration::FromMilliseconds(0);
         }
         uint32_t sample = (uint32_t)contentDelay.ToMilliseconds();
         Telemetry::Accumulate(
@@ -707,24 +713,18 @@ class VsyncRefreshDriverTimer : public R
         sJankLevels[i]++;
       }
     }
 
     void TickRefreshDriver(VsyncId aId, TimeStamp aVsyncTimestamp) {
       MOZ_ASSERT(NS_IsMainThread());
 
       RecordTelemetryProbes(aVsyncTimestamp);
-      if (XRE_IsParentProcess()) {
-        MonitorAutoLock lock(mRefreshTickLock);
-        aVsyncTimestamp = mRecentVsync;
-        mProcessedVsync = true;
-      } else {
-        mLastChildTick = TimeStamp::Now();
-        mLastProcessedTickInChildProcess = aVsyncTimestamp;
-      }
+      mLastTick = TimeStamp::Now();
+      mLastProcessedTick = aVsyncTimestamp;
 
       // On 32-bit Windows we sometimes get times where TimeStamp::Now() is not
       // monotonic because the underlying system apis produce non-monontonic
       // results. (bug 1306896)
 #if !defined(_WIN32)
       // Do not compare timestamps unless they are both canonical or fuzzy
       DebugOnly<TimeStamp> rightnow = TimeStamp::Now();
       MOZ_ASSERT_IF(
@@ -736,31 +736,33 @@ class VsyncRefreshDriverTimer : public R
       // the scheduled TickRefreshDriver() runs. Check mVsyncRefreshDriverTimer
       // before use.
       if (mVsyncRefreshDriverTimer) {
         RefPtr<VsyncRefreshDriverTimer> timer = mVsyncRefreshDriverTimer;
         timer->RunRefreshDrivers(aId, aVsyncTimestamp);
         // Note: mVsyncRefreshDriverTimer might be null now.
       }
 
-      if (!XRE_IsParentProcess()) {
-        TimeDuration tickDuration = TimeStamp::Now() - mLastChildTick;
-        mBlockUntil = aVsyncTimestamp + tickDuration;
-      }
+      TimeDuration tickDuration = TimeStamp::Now() - mLastTick;
+      mBlockUntil = aVsyncTimestamp + tickDuration;
     }
 
     // VsyncRefreshDriverTimer holds this RefreshDriverVsyncObserver and it will
     // be always available before Shutdown(). We can just use the raw pointer
     // here.
     VsyncRefreshDriverTimer* mVsyncRefreshDriverTimer;
-    Monitor mRefreshTickLock;
+
+    Monitor mParentProcessRefreshTickLock;
+    VsyncEvent mRecentParentProcessVsync;
+    bool mPendingParentProcessVsync;
+
     TimeStamp mRecentVsync;
     VsyncId mRecentVsyncId;
-    TimeStamp mLastChildTick;
-    TimeStamp mLastProcessedTickInChildProcess;
+    TimeStamp mLastTick;
+    TimeStamp mLastProcessedTick;
     TimeStamp mBlockUntil;
     TimeDuration mVsyncRate;
     bool mProcessedVsync;
   };  // RefreshDriverVsyncObserver
 
   ~VsyncRefreshDriverTimer() override {
     if (XRE_IsParentProcess()) {
       mVsyncDispatcher->RemoveChildRefreshTimer(mVsyncObserver);
--- a/layout/base/nsRefreshDriver.h
+++ b/layout/base/nsRefreshDriver.h
@@ -247,16 +247,17 @@ class nsRefreshDriver final : public moz
 
   /**
    * Remember whether our presshell's view manager needs a flush
    */
   void ScheduleViewManagerFlush();
   void RevokeViewManagerFlush() { mViewManagerFlushIsPending = false; }
   bool ViewManagerFlushIsPending() { return mViewManagerFlushIsPending; }
   bool HasScheduleFlush() { return mHasScheduleFlush; }
+  void ClearHasScheduleFlush() { mHasScheduleFlush = false; }
 
   /**
    * Add a document for which we have FrameRequestCallbacks
    */
   void ScheduleFrameRequestCallbacks(Document* aDocument);
 
   /**
    * Remove a document for which we have FrameRequestCallbacks
@@ -525,20 +526,24 @@ class nsRefreshDriver final : public moz
   // visibility information. This is a minimum because, regardless of this
   // interval, we only recompute visibility when we've seen a layout or style
   // flush since the last time we did it.
   const mozilla::TimeDuration mMinRecomputeVisibilityInterval;
 
   bool mThrottled : 1;
   bool mNeedToRecomputeVisibility : 1;
   bool mTestControllingRefreshes : 1;
+
+  // These two fields are almost the same, the only difference is that
+  // mViewManagerFlushIsPending gets cleared right before calling
+  // ProcessPendingUpdates, and mHasScheduleFlush gets cleared right after
+  // calling ProcessPendingUpdates. It is important that mHasScheduleFlush
+  // only gets cleared after, but it's not clear if mViewManagerFlushIsPending
+  // needs to be cleared before.
   bool mViewManagerFlushIsPending : 1;
-
-  // True if the view manager needs a flush. Layers-free mode uses this value
-  // to know when to notify invalidation.
   bool mHasScheduleFlush : 1;
 
   bool mInRefresh : 1;
 
   // True if the refresh driver is suspended waiting for transaction
   // id's to be returned and shouldn't do any work during Tick().
   bool mWaitingForTransaction : 1;
   // True if Tick() was skipped because of mWaitingForTransaction and
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -27,17 +27,16 @@
 #include "mozilla/dom/Attr.h"
 #include "mozilla/dom/PopupBlocker.h"
 #include "nsFrame.h"
 #include "nsFrameState.h"
 #include "nsGlobalWindow.h"
 #include "nsGkAtoms.h"
 #include "nsImageFrame.h"
 #include "mozilla/GlobalStyleSheetCache.h"
-#include "nsRange.h"
 #include "nsRegion.h"
 #include "nsRepeatService.h"
 #include "nsFloatManager.h"
 #include "nsSprocketLayout.h"
 #include "nsStackLayout.h"
 #include "nsTextControlFrame.h"
 #include "txMozillaXSLTProcessor.h"
 #include "nsTreeSanitizer.h"
@@ -108,16 +107,17 @@
 #include "MediaDecoder.h"
 #include "mozilla/ClearSiteData.h"
 #include "mozilla/dom/DOMSecurityManager.h"
 #include "mozilla/EditorController.h"
 #include "mozilla/Fuzzyfox.h"
 #include "mozilla/HTMLEditorController.h"
 #include "mozilla/ServoBindings.h"
 #include "mozilla/StaticPresData.h"
+#include "mozilla/dom/AbstractRange.h"
 #include "mozilla/dom/Document.h"
 #include "mozilla/dom/IPCBlobInputStreamStorage.h"
 #include "mozilla/dom/WebIDLGlobalNameHash.h"
 #include "mozilla/dom/U2FTokenManager.h"
 #ifdef OS_WIN
 #  include "mozilla/dom/WinWebAuthnManager.h"
 #endif
 #include "mozilla/dom/PointerEventHandler.h"
@@ -315,17 +315,17 @@ void nsLayoutStatics::Shutdown() {
   // Don't need to shutdown nsWindowMemoryReporter, that will be done by the
   // memory reporter manager.
 
   if (XRE_IsParentProcess() || XRE_IsContentProcess()) {
     ShutdownServo();
     URLExtraData::ReleaseDummy();
   }
 
-  nsRange::Shutdown();
+  mozilla::dom::AbstractRange::Shutdown();
   Document::Shutdown();
   nsMessageManagerScriptExecutor::Shutdown();
   nsFocusManager::Shutdown();
 #ifdef MOZ_XUL
   nsXULPopupManager::Shutdown();
 #endif
   UIDirectionManager::Shutdown();
   StorageObserver::Shutdown();
--- a/layout/generic/FrameClasses.py
+++ b/layout/generic/FrameClasses.py
@@ -92,16 +92,17 @@ FRAME_CLASSES = [
     Frame("nsRubyTextContainerFrame", "RubyTextContainer", NOT_LEAF),
     Frame("nsRubyTextFrame", "RubyText", NOT_LEAF),
     Frame("nsScrollbarButtonFrame", "Box", NOT_LEAF),
     Frame("nsScrollbarFrame", "Scrollbar", NOT_LEAF),
     Frame("nsSelectsAreaFrame", "Block", NOT_LEAF),
     Frame("nsPageSequenceFrame", "Sequence", NOT_LEAF),
     Frame("nsSliderFrame", "Slider", NOT_LEAF),
     Frame("nsSplitterFrame", "Box", NOT_LEAF),
+    Frame("nsStackFrame", "Box", NOT_LEAF),
     Frame("nsSubDocumentFrame", "SubDocument", LEAF),
     Frame("nsSVGAFrame", "SVGA", NOT_LEAF),
     Frame("nsSVGClipPathFrame", "SVGClipPath", NOT_LEAF),
     Frame("nsSVGContainerFrame", "None", NOT_LEAF),
     Frame("SVGFEContainerFrame", "SVGFEContainer", NOT_LEAF),
     Frame("SVGFEImageFrame", "SVGFEImage", LEAF),
     Frame("SVGFELeafFrame", "SVGFELeaf", LEAF),
     Frame("SVGFEUnstyledLeafFrame", "SVGFEUnstyledLeaf", LEAF),
--- a/layout/generic/nsGridContainerFrame.cpp
+++ b/layout/generic/nsGridContainerFrame.cpp
@@ -4361,18 +4361,18 @@ void nsGridContainerFrame::Grid::PlaceGr
       SetSubgridChildEdgeBits(item);
     }
   }
 
   // http://dev.w3.org/csswg/css-grid/#auto-placement-algo
   // Step 1, place 'auto' items that have one definite position -
   // definite row (column) for grid-auto-flow:row (column).
   auto flowStyle = gridStyle->mGridAutoFlow;
-  const bool isRowOrder = (flowStyle & NS_STYLE_GRID_AUTO_FLOW_ROW);
-  const bool isSparse = !(flowStyle & NS_STYLE_GRID_AUTO_FLOW_DENSE);
+  const bool isRowOrder = bool(flowStyle & StyleGridAutoFlow::ROW);
+  const bool isSparse = !(flowStyle & StyleGridAutoFlow::DENSE);
   uint32_t clampMaxColLine = colLineNameMap.mClampMaxLine + offsetToColZero;
   uint32_t clampMaxRowLine = rowLineNameMap.mClampMaxLine + offsetToRowZero;
   // We need 1 cursor per row (or column) if placement is sparse.
   {
     Maybe<nsDataHashtable<nsUint32HashKey, uint32_t>> cursors;
     if (isSparse) {
       cursors.emplace();
     }
@@ -7957,17 +7957,17 @@ nscoord nsGridContainerFrame::IntrinsicI
   // https://drafts.csswg.org/css-grid/#auto-repeat
   // They're only used for auto-repeat so we skip computing them otherwise.
   RepeatTrackSizingInput repeatSizing(state.mWM);
   if (!IsColSubgrid() && state.mColFunctions.mHasRepeatAuto) {
     repeatSizing.InitFromStyle(eLogicalAxisInline, state.mWM,
                                state.mFrame->Style());
   }
   if (!IsRowSubgrid() && state.mRowFunctions.mHasRepeatAuto &&
-      !(state.mGridStyle->mGridAutoFlow & NS_STYLE_GRID_AUTO_FLOW_ROW)) {
+      !(state.mGridStyle->mGridAutoFlow & StyleGridAutoFlow::ROW)) {
     // Only 'grid-auto-flow:column' can create new implicit columns, so that's
     // the only case where our block-size can affect the number of columns.
     repeatSizing.InitFromStyle(eLogicalAxisBlock, state.mWM,
                                state.mFrame->Style());
   }
 
   Grid grid;
   if (MOZ_LIKELY(!IsSubgrid())) {
--- a/layout/printing/nsPrintJob.cpp
+++ b/layout/printing/nsPrintJob.cpp
@@ -11,16 +11,17 @@
 #include "nsCRT.h"
 
 #include "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/ComputedStyleInlines.h"
 #include "mozilla/dom/BrowsingContext.h"
 #include "mozilla/dom/Selection.h"
 #include "mozilla/dom/CustomEvent.h"
 #include "mozilla/dom/ScriptSettings.h"
+#include "nsIBrowserChild.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsPIDOMWindow.h"
 #include "nsIDocShell.h"
 #include "nsIURI.h"
 #include "nsITextToSubURI.h"
 #include "nsError.h"
 
 #include "nsView.h"
@@ -81,17 +82,16 @@ static const char kPrintingPromptService
 #include "nsWidgetsCID.h"
 #include "nsIDeviceContextSpec.h"
 #include "nsDeviceContextSpecProxy.h"
 #include "nsViewManager.h"
 
 #include "nsPageSequenceFrame.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIInterfaceRequestorUtils.h"
-#include "nsIDocShellTreeOwner.h"
 #include "nsIWebBrowserChrome.h"
 #include "nsFrameManager.h"
 #include "mozilla/ReflowInput.h"
 #include "nsIContentViewer.h"
 #include "nsIDocumentViewerPrint.h"
 
 #include "nsFocusManager.h"
 #include "nsRange.h"
@@ -604,21 +604,23 @@ nsresult nsPrintJob::Initialize(nsIDocum
   // here, since the document that the user selected to print may mutate
   // across consecutive PrintPreview() calls.
 
   Element* root = aOriginalDoc->GetRootElement();
   mDisallowSelectionPrint =
       root &&
       root->HasAttr(kNameSpaceID_None, nsGkAtoms::mozdisallowselectionprint);
 
-  nsCOMPtr<nsIDocShellTreeOwner> owner;
-  aDocShell->GetTreeOwner(getter_AddRefs(owner));
-  nsCOMPtr<nsIWebBrowserChrome> browserChrome = do_GetInterface(owner);
-  if (browserChrome) {
-    browserChrome->IsWindowModal(&mIsForModalWindow);
+  if (nsPIDOMWindowOuter* window = aOriginalDoc->GetWindow()) {
+    if (nsCOMPtr<nsIWebBrowserChrome> wbc = window->GetWebBrowserChrome()) {
+      // We only get this in order to skip opening the progress dialog when
+      // the window is modal.  Once the platform code stops opening the
+      // progress dialog (bug 1558907), we can get rid of this.
+      wbc->IsWindowModal(&mIsForModalWindow);
+    }
   }
 
   bool hasMozPrintCallback = false;
   DocHasPrintCallbackCanvas(*aOriginalDoc,
                             static_cast<void*>(&hasMozPrintCallback));
   mHasMozPrintCallback =
       hasMozPrintCallback || AnySubdocHasPrintCallbackCanvas(*aOriginalDoc);
 
@@ -3222,22 +3224,19 @@ static void DumpViews(nsIDocShell* aDocS
         }
       }
     } else {
       fputs("null pres shell\n", out);
     }
 
     // dump the views of the sub documents
     int32_t i, n;
-    aDocShell->GetChildCount(&n);
-    for (i = 0; i < n; i++) {
-      nsCOMPtr<nsIDocShellTreeItem> child;
-      aDocShell->GetChildAt(i, getter_AddRefs(child));
-      nsCOMPtr<nsIDocShell> childAsShell(do_QueryInterface(child));
-      if (childAsShell) {
+    BrowsingContext* bc = nsDocShell::Cast(aDocShell)->GetBrowsingContext();
+    for (auto& child : bc->GetChildren()) {
+      if (auto childDS = child->GetDocShell()) {
         DumpViews(childAsShell, out);
       }
     }
   }
 }
 
 /** ---------------------------------------------------
  *  Dumps the Views and Frames
--- a/layout/printing/nsPrintObject.cpp
+++ b/layout/printing/nsPrintObject.cpp
@@ -8,17 +8,16 @@
 
 #include "nsIContentViewer.h"
 #include "nsContentUtils.h"  // for nsAutoScriptBlocker
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsPIDOMWindow.h"
 #include "nsPresContext.h"
 #include "nsGkAtoms.h"
 #include "nsComponentManagerUtils.h"
-#include "nsIDocShellTreeItem.h"
 #include "nsIBaseWindow.h"
 #include "nsDocShell.h"
 
 #include "mozilla/PresShell.h"
 #include "mozilla/dom/BrowsingContext.h"
 #include "mozilla/dom/Document.h"
 #include "mozilla/dom/Element.h"
 
@@ -75,19 +74,17 @@ nsresult nsPrintObject::InitAsRootObject
   } else {
     // When doing an actual print, we create a BrowsingContext/nsDocShell that
     // is detached from any browser window or tab.
 
     // Create a new BrowsingContext to create our DocShell in.
     RefPtr<BrowsingContext> bc = BrowsingContext::CreateWindowless(
         /* aParent */ nullptr,
         /* aOpener */ nullptr, EmptyString(),
-        aDocShell->ItemType() == nsIDocShellTreeItem::typeContent
-            ? BrowsingContext::Type::Content
-            : BrowsingContext::Type::Chrome);
+        nsDocShell::Cast(aDocShell)->GetBrowsingContext()->GetType());
 
     // Create a container docshell for printing.
     mDocShell = nsDocShell::Create(bc);
     NS_ENSURE_TRUE(mDocShell, NS_ERROR_OUT_OF_MEMORY);
 
     mDidCreateDocShell = true;
     MOZ_ASSERT(mDocShell->ItemType() == aDocShell->ItemType());
 
--- a/layout/style/Loader.cpp
+++ b/layout/style/Loader.cpp
@@ -355,16 +355,19 @@ SheetLoadData::SheetLoadData(Loader* aLo
       mRequestingNode(aRequestingNode),
       mPreloadEncoding(aPreloadEncoding) {
   MOZ_ASSERT(mLoader, "Must have a loader!");
   MOZ_ASSERT(!mUseSystemPrincipal || mSyncLoad,
              "Shouldn't use system principal for async loads");
 }
 
 SheetLoadData::~SheetLoadData() {
+  MOZ_DIAGNOSTIC_ASSERT(mSheetCompleteCalled,
+                        "Should always call SheetComplete");
+
   // Do this iteratively to avoid blowing up the stack.
   RefPtr<SheetLoadData> next = std::move(mNext);
   while (next) {
     next = std::move(next->mNext);
   }
 }
 
 NS_IMETHODIMP
@@ -1610,27 +1613,26 @@ nsresult Loader::LoadSheet(SheetLoadData
 
   // Now tell the channel we expect text/css data back....  We do
   // this before opening it, so it's only treated as a hint.
   channel->SetContentType(NS_LITERAL_CSTRING("text/css"));
 
   // We don't have to hold on to the stream loader.  The ownership
   // model is: Necko owns the stream loader, which owns the load data,
   // which owns us
-  nsCOMPtr<nsIStreamListener> streamLoader = new StreamLoader(aLoadData);
-
+  auto streamLoader = MakeRefPtr<StreamLoader>(aLoadData);
   if (mDocument) {
     net::PredictorLearn(aLoadData.mURI, mDocument->GetDocumentURI(),
                         nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE, mDocument);
   }
 
   rv = channel->AsyncOpen(streamLoader);
-
   if (NS_FAILED(rv)) {
     LOG_ERROR(("  Failed to create stream loader"));
+    streamLoader->AsyncOpenFailed();
     SheetComplete(aLoadData, rv);
     return rv;
   }
 
   mSheets->mLoadingDatas.Put(&key, &aLoadData);
   aLoadData.mIsLoading = true;
 
   return NS_OK;
@@ -1767,31 +1769,32 @@ void Loader::DoSheetComplete(SheetLoadDa
                "mLoadingDatas should be initialized by now.");
 
   // Twiddle the hashtables
   if (aLoadData.mURI) {
     LOG_URI("  Finished loading: '%s'", aLoadData.mURI);
     // Remove the data from the list of loading datas
     if (aLoadData.mIsLoading) {
       SheetLoadDataHashKey key(aLoadData);
-#ifdef DEBUG
-      SheetLoadData* loadingData;
-      NS_ASSERTION(mSheets->mLoadingDatas.Get(&key, &loadingData) &&
-                       loadingData == &aLoadData,
-                   "Bad loading table");
-#endif
-
-      mSheets->mLoadingDatas.Remove(&key);
+      Maybe<SheetLoadData*> loadingData =
+          mSheets->mLoadingDatas.GetAndRemove(&key);
+      MOZ_DIAGNOSTIC_ASSERT(loadingData && loadingData.value() == &aLoadData);
+      Unused << loadingData;
       aLoadData.mIsLoading = false;
     }
   }
 
   // Go through and deal with the whole linked list.
   SheetLoadData* data = &aLoadData;
   do {
+    MOZ_DIAGNOSTIC_ASSERT(!data->mSheetCompleteCalled);
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+    data->mSheetCompleteCalled = true;
+#endif
+
     if (!data->mSheetAlreadyComplete) {
       // If mSheetAlreadyComplete, then the sheet could well be modified between
       // when we posted the async call to SheetComplete and now, since the sheet
       // was page-accessible during that whole time.
       MOZ_ASSERT(!data->mSheet->HasForcedUniqueInner(),
                  "should not get a forced unique inner during parsing");
       data->mSheet->SetComplete();
       data->ScheduleLoadEventIfNeeded();
--- a/layout/style/ServoBindings.toml
+++ b/layout/style/ServoBindings.toml
@@ -502,17 +502,16 @@ cbindgen-types = [
     { gecko = "StyleGenericFontFamily", servo = "values::computed::font::GenericFontFamily" },
     { gecko = "StyleFontFamilyNameSyntax", servo = "values::computed::font::FontFamilyNameSyntax" },
     { gecko = "StyleGenericColor", servo = "values::generics::color::Color" },
     { gecko = "StyleSystemColor", servo = "values::specified::color::SystemColor" },
     { gecko = "StyleGenericColorOrAuto", servo = "values::generics::color::ColorOrAuto" },
     { gecko = "StyleGenericScrollbarColor", servo = "values::generics::ui::ScrollbarColor" },
     { gecko = "StyleRGBA", servo = "cssparser::RGBA" },
     { gecko = "StyleOrigin", servo = "stylesheets::Origin" },
-    { gecko = "StyleGenericGradientItem", servo = "values::generics::image::GradientItem" },
     { gecko = "StyleGenericVerticalAlign", servo = "values::generics::box_::VerticalAlign" },
     { gecko = "StyleVerticalAlignKeyword", servo = "values::generics::box_::VerticalAlignKeyword" },
     { gecko = "StyleGenericBasicShape", servo = "values::generics::basic_shape::BasicShape" },
     { gecko = "StyleArcSlice", servo = "style_traits::arc_slice::ArcSlice" },
     { gecko = "StyleForgottenArcSlicePtr", servo = "style_traits::arc_slice::ForgottenArcSlicePtr" },
     { gecko = "StyleOwnedSlice", servo = "style_traits::owned_slice::OwnedSlice" },
     { gecko = "StyleMozContextProperties", servo = "values::specified::svg::MozContextProperties" },
     { gecko = "StyleQuotes", servo = "values::specified::list::Quotes" },
@@ -562,16 +561,17 @@ cbindgen-types = [
     { gecko = "StyleJustifySelf", servo = "values::computed::JustifySelf" },
     { gecko = "StyleAlignSelf", servo = "values::computed::AlignSelf" },
     { gecko = "StyleAlignContent", servo = "values::computed::align::AlignContent" },
     { gecko = "StyleJustifyContent", servo = "values::computed::align::JustifyContent" },
     { gecko = "StyleComputedValueFlags", servo = "computed_value_flags::ComputedValueFlags" },
     { gecko = "StyleImage", servo = "values::computed::Image" },
     { gecko = "StyleShapeOutside", servo = "values::computed::basic_shape::ShapeOutside" },
     { gecko = "StyleClipPath", servo = "values::computed::basic_shape::ClipPath" },
+    { gecko = "StyleGridAutoFlow", servo = "values::computed::GridAutoFlow" },
 ]
 
 mapped-generic-types = [
     { generic = true, gecko = "mozilla::RustCell", servo = "::std::cell::Cell" },
     { generic = false, gecko = "ServoNodeData", servo = "AtomicRefCell<ElementData>" },
     { generic = false, gecko = "mozilla::ServoWritingMode", servo = "::logical_geometry::WritingMode" },
     { generic = false, gecko = "mozilla::ServoCustomPropertiesMap", servo = "Option<::servo_arc::Arc<::custom_properties::CustomPropertiesMap>>" },
     { generic = false, gecko = "mozilla::ServoRuleNode", servo = "Option<::rule_tree::StrongRuleNode>" },
--- a/layout/style/SheetLoadData.h
+++ b/layout/style/SheetLoadData.h
@@ -191,16 +191,21 @@ class SheetLoadData final : public nsIRu
 
   // The node that identifies who started loading us.
   nsCOMPtr<nsINode> mRequestingNode;
 
   // The encoding to use for preloading Must be empty if mOwningElement
   // is non-null.
   const Encoding* mPreloadEncoding;
 
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+  // Whether SheetComplete was called.
+  bool mSheetCompleteCalled = false;
+#endif
+
   bool ShouldDefer() const { return mWasAlternate || !mMediaMatched; }
 
  private:
   void FireLoadEvent(nsIThreadInternal* aThread);
 };
 
 typedef nsMainThreadPtrHolder<SheetLoadData> SheetLoadDataHolder;
 
--- a/layout/style/StreamLoader.cpp
+++ b/layout/style/StreamLoader.cpp
@@ -15,45 +15,52 @@
 using namespace mozilla;
 
 namespace mozilla {
 namespace css {
 
 StreamLoader::StreamLoader(SheetLoadData& aSheetLoadData)
     : mSheetLoadData(&aSheetLoadData), mStatus(NS_OK) {}
 
-StreamLoader::~StreamLoader() {}
+StreamLoader::~StreamLoader() {
+  MOZ_DIAGNOSTIC_ASSERT(mOnStopRequestCalled || mAsyncOpenFailed);
+}
 
 NS_IMPL_ISUPPORTS(StreamLoader, nsIStreamListener)
 
 /* nsIRequestObserver implementation */
 NS_IMETHODIMP
 StreamLoader::OnStartRequest(nsIRequest* aRequest) {
+
   // It's kinda bad to let Web content send a number that results
   // in a potentially large allocation directly, but efficiency of
   // compression bombs is so great that it doesn't make much sense
   // to require a site to send one before going ahead and allocating.
-  nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
-  if (channel) {
+  if (nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest)) {
     int64_t length;
     nsresult rv = channel->GetContentLength(&length);
     if (NS_SUCCEEDED(rv) && length > 0) {
       if (length > std::numeric_limits<nsACString::size_type>::max()) {
         return (mStatus = NS_ERROR_OUT_OF_MEMORY);
       }
       if (!mBytes.SetCapacity(length, fallible)) {
         return (mStatus = NS_ERROR_OUT_OF_MEMORY);
       }
     }
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 StreamLoader::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
+  MOZ_DIAGNOSTIC_ASSERT(!mOnStopRequestCalled);
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+  mOnStopRequestCalled = true;
+#endif
+
   // Decoded data
   nsCString utf8String;
   {
     // Hold the nsStringBuffer for the bytes from the stack to ensure release
     // no matter which return branch is taken.
     nsCString bytes(mBytes);
     mBytes.Truncate();
 
--- a/layout/style/StreamLoader.h
+++ b/layout/style/StreamLoader.h
@@ -5,30 +5,37 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_css_StreamLoader_h
 #define mozilla_css_StreamLoader_h
 
 #include "nsIStreamListener.h"
 #include "nsString.h"
 #include "mozilla/css/SheetLoadData.h"
+#include "mozilla/Assertions.h"
 
 class nsIInputStream;
 
 namespace mozilla {
 namespace css {
 
 class StreamLoader : public nsIStreamListener {
  public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIREQUESTOBSERVER
   NS_DECL_NSISTREAMLISTENER
 
   explicit StreamLoader(SheetLoadData&);
 
+  void AsyncOpenFailed() {
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+    mAsyncOpenFailed = true;
+#endif
+  }
+
  private:
   virtual ~StreamLoader();
 
   /**
    * callback method used for ReadSegments
    */
   static nsresult WriteSegmentFun(nsIInputStream*, void*, const char*, uint32_t,
                                   uint32_t, uint32_t*);
@@ -39,14 +46,19 @@ class StreamLoader : public nsIStreamLis
   nsresult mStatus;
   Maybe<const Encoding*> mEncodingFromBOM;
 
   // We store the initial three bytes of the stream into mBOMBytes, and then
   // use that buffer to detect a BOM. We then shift any non-BOM bytes into
   // mBytes, and store all subsequent data in that buffer.
   nsCString mBytes;
   nsAutoCStringN<3> mBOMBytes;
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+  bool mAsyncOpenFailed = false;
+  bool mOnStopRequestCalled = false;
+#endif
 };
 
 }  // namespace css
 }  // namespace mozilla
 
 #endif  // mozilla_css_StreamLoader_h
--- a/layout/style/StyleSheet.cpp
+++ b/layout/style/StyleSheet.cpp
@@ -137,18 +137,25 @@ StyleSheet::~StyleSheet() {
   MOZ_ASSERT(!mInner, "Inner should have been dropped in LastRelease");
 }
 
 bool StyleSheet::HasRules() const {
   return Servo_StyleSheet_HasRules(Inner().mContents);
 }
 
 Document* StyleSheet::GetAssociatedDocument() const {
-  return mDocumentOrShadowRoot ? mDocumentOrShadowRoot->AsNode().OwnerDoc()
-                               : nullptr;
+  auto* associated = GetAssociatedDocumentOrShadowRoot();
+  return associated ? associated->AsNode().OwnerDoc() : nullptr;
+}
+
+dom::DocumentOrShadowRoot* StyleSheet::GetAssociatedDocumentOrShadowRoot()
+    const {
+  // FIXME(nordzilla) This will not work for children of adtoped sheets.
+  // https://bugzilla.mozilla.org/show_bug.cgi?id=1613748
+  return IsConstructed() ? mConstructorDocument : mDocumentOrShadowRoot;
 }
 
 Document* StyleSheet::GetComposedDoc() const {
   return mDocumentOrShadowRoot
              ? mDocumentOrShadowRoot->AsNode().GetComposedDoc()
              : nullptr;
 }
 
@@ -158,16 +165,18 @@ bool StyleSheet::IsKeptAliveByDocument()
   }
 
   return !!GetComposedDoc();
 }
 
 void StyleSheet::LastRelease() {
   MOZ_ASSERT(mInner, "Should have an mInner at time of destruction.");
   MOZ_ASSERT(mInner->mSheets.Contains(this), "Our mInner should include us.");
+  MOZ_DIAGNOSTIC_ASSERT(mAdopters.IsEmpty(),
+                        "Should have no adopters at time of destruction.");
 
   UnparentChildren();
 
   mInner->RemoveSheet(this);
   mInner = nullptr;
 
   DropMedia();
   DropRuleList();
@@ -262,25 +271,32 @@ void StyleSheet::SetComplete() {
   MOZ_ASSERT(!IsComplete(), "Already complete?");
   mState |= State::Complete;
   if (!Disabled()) {
     ApplicableStateChanged(true);
   }
 }
 
 void StyleSheet::ApplicableStateChanged(bool aApplicable) {
-  if (!mDocumentOrShadowRoot) {
-    return;
+  auto Notify = [this](DocumentOrShadowRoot& target) {
+    nsINode& node = target.AsNode();
+    if (ShadowRoot* shadow = ShadowRoot::FromNode(node)) {
+      shadow->StyleSheetApplicableStateChanged(*this);
+    } else {
+      node.AsDocument()->StyleSheetApplicableStateChanged(*this);
+    }
+  };
+
+  if (mDocumentOrShadowRoot) {
+    Notify(*mDocumentOrShadowRoot);
   }
 
-  nsINode& node = mDocumentOrShadowRoot->AsNode();
-  if (auto* shadow = ShadowRoot::FromNode(node)) {
-    shadow->StyleSheetApplicableStateChanged(*this);
-  } else {
-    node.AsDocument()->StyleSheetApplicableStateChanged(*this);
+  for (DocumentOrShadowRoot* adopter : mAdopters) {
+    MOZ_ASSERT(adopter, "adopters should never be null");
+    Notify(*adopter);
   }
 }
 
 void StyleSheet::SetDisabled(bool aDisabled) {
   if (IsReadOnly()) {
     return;
   }
 
@@ -441,31 +457,38 @@ void StyleSheet::DropStyleSet(ServoStyle
   MOZ_DIAGNOSTIC_ASSERT(found, "didn't find style set");
 #ifndef MOZ_DIAGNOSTIC_ASSERT_ENABLED
   Unused << found;
 #endif
 }
 
 // NOTE(emilio): Composed doc and containing shadow root are set in child sheets
 // too, so no need to do it for each ancestor.
-#define NOTIFY(function_, args_)                        \
-  do {                                                  \
-    if (auto* shadow = GetContainingShadow()) {         \
-      shadow->function_ args_;                          \
-    }                                                   \
-    if (auto* doc = GetComposedDoc()) {                 \
-      doc->function_ args_;                             \
-    }                                                   \
-    StyleSheet* current = this;                         \
-    do {                                                \
-      for (ServoStyleSet * set : current->mStyleSets) { \
-        set->function_ args_;                           \
-      }                                                 \
-      current = current->mParent;                       \
-    } while (current);                                  \
+#define NOTIFY(function_, args_)                                      \
+  do {                                                                \
+    if (auto* shadow = GetContainingShadow()) {                       \
+      shadow->function_ args_;                                        \
+    }                                                                 \
+    if (auto* doc = GetComposedDoc()) {                               \
+      doc->function_ args_;                                           \
+    }                                                                 \
+    StyleSheet* current = this;                                       \
+    do {                                                              \
+      for (ServoStyleSet * set : current->mStyleSets) {               \
+        set->function_ args_;                                         \
+      }                                                               \
+      for (auto* adopter : mAdopters) {                               \
+        if (auto* shadow = ShadowRoot::FromNode(adopter->AsNode())) { \
+          shadow->function_ args_;                                    \
+        } else {                                                      \
+          adopter->AsNode().AsDocument()->function_ args_;            \
+        }                                                             \
+      }                                                               \
+      current = current->mParent;                                     \
+    } while (current);                                                \
   } while (0)
 
 void StyleSheet::EnsureUniqueInner() {
   MOZ_ASSERT(mInner->mSheets.Length() != 0, "unexpected number of outers");
 
   if (IsReadOnly()) {
     // Sheets that can't be modified don't need a unique inner.
     return;
@@ -648,16 +671,17 @@ void StyleSheet::ReplaceSync(const nsACS
         "@import rules are not allowed. Use the async replace() method "
         "instead.");
   }
 
   // 5. Set sheet's rules to the new rules.
   DropRuleList();
   Inner().mContents = std::move(rawContent);
   FinishParse();
+  RuleChanged(nullptr);
 }
 
 nsresult StyleSheet::DeleteRuleFromGroup(css::GroupRule* aGroup,
                                          uint32_t aIndex) {
   NS_ENSURE_ARG_POINTER(aGroup);
   NS_ASSERTION(IsComplete(), "No deleting from an incomplete sheet!");
   RefPtr<css::Rule> rule = aGroup->GetStyleRuleAt(aIndex);
   NS_ENSURE_TRUE(rule, NS_ERROR_ILLEGAL_VALUE);
--- a/layout/style/StyleSheet.h
+++ b/layout/style/StyleSheet.h
@@ -241,29 +241,29 @@ class StyleSheet final : public nsICSSLo
   enum AssociationMode : uint8_t {
     // OwnedByDocumentOrShadowRoot means mDocumentOrShadowRoot owns us (possibly
     // via a chain of other stylesheets).
     OwnedByDocumentOrShadowRoot,
     // NotOwnedByDocument means we're owned by something that might have a
     // different lifetime than mDocument.
     NotOwnedByDocumentOrShadowRoot
   };
-  dom::DocumentOrShadowRoot* GetAssociatedDocumentOrShadowRoot() const {
-    return mDocumentOrShadowRoot;
-  }
+  dom::DocumentOrShadowRoot* GetAssociatedDocumentOrShadowRoot() const;
 
   // Whether this stylesheet is kept alive by the associated document or
   // associated shadow root's document somehow, and thus at least has the same
   // lifetime as GetAssociatedDocument().
   bool IsKeptAliveByDocument() const;
 
   // Returns the document whose styles this sheet is affecting.