Merge mozilla-central to inbound. a=merge CLOSED TREE
authorshindli <shindli@mozilla.com>
Tue, 30 Apr 2019 07:10:58 +0300
changeset 530706 b3612d6d6448011aacc51ba580f4358e5869ed99
parent 530705 b96f067ff11790f61449906ecf7f764abfa2a933 (current diff)
parent 530668 da2b564f6df03fd8ce37f2eb394fd48289d43a55 (diff)
child 530707 116b2213c4261383a5e279f65edcca069ca8df81
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to inbound. a=merge CLOSED TREE
mobile/android/geckoview/src/androidTest/assets/www/reflect_local_storage_into_title.html
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/StorageController.java
testing/web-platform/meta/css/css-contain/contain-layout-button-001.html.ini
testing/web-platform/meta/css/css-contain/contain-size-button-001.html.ini
testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-button-001.html.ini
toolkit/modules/ClientID.jsm
toolkit/modules/tests/xpcshell/test_client_id.js
toolkit/mozapps/update/tests/browser/browser_TelemetryUpdatePing.js
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -426,16 +426,17 @@ name = "cc"
 version = "1.0.34"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "cert_storage"
 version = "0.0.1"
 dependencies = [
  "base64 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "crossbeam-utils 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "lmdb-rkv 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "moz_task 0.1.0",
  "nserror 0.1.0",
  "nsstring 0.1.0",
  "rkv 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
--- a/accessible/base/nsCoreUtils.cpp
+++ b/accessible/base/nsCoreUtils.cpp
@@ -206,25 +206,25 @@ bool nsCoreUtils::IsAncestorOf(nsINode *
     if (parentNode == aPossibleAncestorNode) return true;
   }
 
   return false;
 }
 
 nsresult nsCoreUtils::ScrollSubstringTo(nsIFrame *aFrame, nsRange *aRange,
                                         uint32_t aScrollType) {
-  nsIPresShell::ScrollAxis vertical, horizontal;
+  ScrollAxis vertical, horizontal;
   ConvertScrollTypeToPercents(aScrollType, &vertical, &horizontal);
 
   return ScrollSubstringTo(aFrame, aRange, vertical, horizontal);
 }
 
 nsresult nsCoreUtils::ScrollSubstringTo(nsIFrame *aFrame, nsRange *aRange,
-                                        nsIPresShell::ScrollAxis aVertical,
-                                        nsIPresShell::ScrollAxis aHorizontal) {
+                                        ScrollAxis aVertical,
+                                        ScrollAxis aHorizontal) {
   if (!aFrame || !aRange) {
     return NS_ERROR_FAILURE;
   }
 
   nsPresContext *presContext = aFrame->PresContext();
 
   nsCOMPtr<nsISelectionController> selCon;
   aFrame->GetSelectionController(presContext, getter_AddRefs(selCon));
@@ -257,19 +257,19 @@ void nsCoreUtils::ScrollFrameToPoint(nsI
   nsPoint deltaPoint = point - frameRect.TopLeft();
 
   nsPoint scrollPoint = scrollableFrame->GetScrollPosition();
   scrollPoint -= deltaPoint;
 
   scrollableFrame->ScrollTo(scrollPoint, ScrollMode::Instant);
 }
 
-void nsCoreUtils::ConvertScrollTypeToPercents(
-    uint32_t aScrollType, nsIPresShell::ScrollAxis *aVertical,
-    nsIPresShell::ScrollAxis *aHorizontal) {
+void nsCoreUtils::ConvertScrollTypeToPercents(uint32_t aScrollType,
+                                              ScrollAxis *aVertical,
+                                              ScrollAxis *aHorizontal) {
   WhereToScroll whereY, whereX;
   WhenToScroll whenY, whenX;
   switch (aScrollType) {
     case nsIAccessibleScrollType::SCROLL_TYPE_TOP_LEFT:
       whereY = kScrollToTop;
       whenY = WhenToScroll::Always;
       whereX = kScrollToLeft;
       whenX = WhenToScroll::Always;
@@ -305,18 +305,18 @@ void nsCoreUtils::ConvertScrollTypeToPer
       whenX = WhenToScroll::Always;
       break;
     default:
       whereY = kScrollMinimum;
       whenY = WhenToScroll::IfNotFullyVisible;
       whereX = kScrollMinimum;
       whenX = WhenToScroll::IfNotFullyVisible;
   }
-  *aVertical = nsIPresShell::ScrollAxis(whereY, whenY);
-  *aHorizontal = nsIPresShell::ScrollAxis(whereX, whenX);
+  *aVertical = ScrollAxis(whereY, whenY);
+  *aHorizontal = ScrollAxis(whereX, whenX);
 }
 
 nsIntPoint nsCoreUtils::GetScreenCoordsForWindow(nsINode *aNode) {
   nsIntPoint coords(0, 0);
   nsCOMPtr<nsIDocShellTreeItem> treeItem(GetDocShellFor(aNode));
   if (!treeItem) return coords;
 
   nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
@@ -502,17 +502,17 @@ already_AddRefed<nsTreeColumn> nsCoreUti
 bool nsCoreUtils::IsColumnHidden(nsTreeColumn *aColumn) {
   Element *element = aColumn->Element();
   return element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden,
                               nsGkAtoms::_true, eCaseMatters);
 }
 
 void nsCoreUtils::ScrollTo(PresShell *aPresShell, nsIContent *aContent,
                            uint32_t aScrollType) {
-  nsIPresShell::ScrollAxis vertical, horizontal;
+  ScrollAxis vertical, horizontal;
   ConvertScrollTypeToPercents(aScrollType, &vertical, &horizontal);
   aPresShell->ScrollContentIntoView(aContent, vertical, horizontal,
                                     ScrollFlags::ScrollOverflowHidden);
 }
 
 bool nsCoreUtils::IsWhitespaceString(const nsAString &aString) {
   nsAString::const_char_iterator iterBegin, iterEnd;
 
--- a/accessible/base/nsCoreUtils.h
+++ b/accessible/base/nsCoreUtils.h
@@ -152,37 +152,37 @@ class nsCoreUtils {
    * @param aFrame        the frame for accessible the range belongs to.
    * @param aRange    the range to scroll to
    * @param aVertical     how to align vertically, specified in percents, and
    * when.
    * @param aHorizontal     how to align horizontally, specified in percents,
    * and when.
    */
   static nsresult ScrollSubstringTo(nsIFrame *aFrame, nsRange *aRange,
-                                    nsIPresShell::ScrollAxis aVertical,
-                                    nsIPresShell::ScrollAxis aHorizontal);
+                                    mozilla::ScrollAxis aVertical,
+                                    mozilla::ScrollAxis aHorizontal);
 
   /**
    * Scrolls the given frame to the point, used for implememntation of
    * nsIAccessible::scrollToPoint and nsIAccessibleText::scrollSubstringToPoint.
    *
    * @param aScrollableFrame  the scrollable frame
    * @param aFrame            the frame to scroll
    * @param aPoint            the point scroll to
    */
   static void ScrollFrameToPoint(nsIFrame *aScrollableFrame, nsIFrame *aFrame,
                                  const nsIntPoint &aPoint);
 
   /**
    * Converts scroll type constant defined in nsIAccessibleScrollType to
    * vertical and horizontal parameters.
    */
-  static void ConvertScrollTypeToPercents(
-      uint32_t aScrollType, nsIPresShell::ScrollAxis *aVertical,
-      nsIPresShell::ScrollAxis *aHorizontal);
+  static void ConvertScrollTypeToPercents(uint32_t aScrollType,
+                                          mozilla::ScrollAxis *aVertical,
+                                          mozilla::ScrollAxis *aHorizontal);
 
   /**
    * Returns coordinates in device pixels relative screen for the top level
    * window.
    *
    * @param aNode  the DOM node hosted in the window.
    */
   static nsIntPoint GetScreenCoordsForWindow(nsINode *aNode);
--- a/accessible/generic/Accessible.cpp
+++ b/accessible/generic/Accessible.cpp
@@ -1839,18 +1839,17 @@ void Accessible::DoCommand(nsIContent* a
 
 void Accessible::DispatchClickEvent(nsIContent* aContent,
                                     uint32_t aActionIndex) const {
   if (IsDefunct()) return;
 
   RefPtr<PresShell> presShell = mDoc->PresShellPtr();
 
   // Scroll into view.
-  presShell->ScrollContentIntoView(aContent, nsIPresShell::ScrollAxis(),
-                                   nsIPresShell::ScrollAxis(),
+  presShell->ScrollContentIntoView(aContent, ScrollAxis(), ScrollAxis(),
                                    ScrollFlags::ScrollOverflowHidden);
 
   AutoWeakFrame frame = aContent->GetPrimaryFrame();
   if (!frame) return;
 
   // Compute x and y coordinates.
   nsPoint point;
   nsCOMPtr<nsIWidget> widget = frame->GetNearestWidget(point);
--- a/accessible/generic/HyperTextAccessible.cpp
+++ b/accessible/generic/HyperTextAccessible.cpp
@@ -1272,17 +1272,17 @@ nsresult HyperTextAccessible::SetSelecti
 
   // Set up the selection.
   for (int32_t idx = domSel->RangeCount() - 1; idx > 0; idx--)
     domSel->RemoveRange(*domSel->GetRangeAt(idx), IgnoreErrors());
   SetSelectionBoundsAt(0, aStartPos, aEndPos);
 
   // Make sure it is visible
   domSel->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION,
-                         nsIPresShell::ScrollAxis(), nsIPresShell::ScrollAxis(),
+                         ScrollAxis(), ScrollAxis(),
                          dom::Selection::SCROLL_FOR_CARET_MOVE |
                              dom::Selection::SCROLL_OVERFLOW_HIDDEN);
 
   // When selection is done, move the focus to the selection if accessible is
   // not focusable. That happens when selection is set within hypertext
   // accessible.
   if (isFocusable) return NS_OK;
 
@@ -1641,18 +1641,17 @@ void HyperTextAccessible::ScrollSubstrin
         // avoid divide by zero
         size.width = size.width ? size.width : 1;
         size.height = size.height ? size.height : 1;
 
         int16_t hPercent = offsetPointX * 100 / size.width;
         int16_t vPercent = offsetPointY * 100 / size.height;
 
         nsresult rv = nsCoreUtils::ScrollSubstringTo(
-            frame, range, nsIPresShell::ScrollAxis(vPercent),
-            nsIPresShell::ScrollAxis(hPercent));
+            frame, range, ScrollAxis(vPercent), ScrollAxis(hPercent));
         if (NS_FAILED(rv)) return;
 
         initialScrolled = true;
       } else {
         // Substring was scrolled to the given point already inside its closest
         // scrollable area. If there are nested scrollable areas then make
         // sure we scroll lower areas to the given point inside currently
         // traversed scrollable area.
--- a/accessible/tests/browser/events/browser_test_focus_urlbar.js
+++ b/accessible/tests/browser/events/browser_test_focus_urlbar.js
@@ -114,16 +114,19 @@ async function runTests() {
   event = await focused;
   testStates(event.accessible, STATE_FOCUSED);
 
   info("Ensuring text box focus on left arrow");
   focused = waitForEvent(EVENT_FOCUS, textBox);
   EventUtils.synthesizeKey("KEY_ArrowLeft");
   await focused;
   testStates(textBox, STATE_FOCUSED);
+  if (UrlbarPrefs.get("quantumbar")) {
+    gURLBar.view.close();
+  }
   // On Mac, down arrow when not at the end of the field moves to the end.
   // Move back to the end so the next press of down arrow opens the popup.
   EventUtils.synthesizeKey("KEY_ArrowRight");
 
   info("Ensuring autocomplete focus on down arrow");
   focused = waitForEvent(EVENT_FOCUS, isEventForAutocompleteItem);
   EventUtils.synthesizeKey("KEY_ArrowDown");
   event = await focused;
--- a/browser/base/content/browser-addons.js
+++ b/browser/base/content/browser-addons.js
@@ -409,38 +409,46 @@ var gXPInstallObserver = {
       let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI");
       secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_ADDON_ASKING_PREVENTED);
       let popup = PopupNotifications.show(browser, notificationID,
                                           messageString, anchorID,
                                           null, null, options);
       removeNotificationOnEnd(popup, installInfo.installs);
       break; }
     case "addon-install-blocked": {
-      messageString = gNavigatorBundle.getFormattedString("xpinstallPromptMessage.header", ["<>"]);
-      let unknown = gNavigatorBundle.getString("xpinstallPromptMessage.unknown");
-      options.name = options.displayURI && options.displayURI.displayHost || unknown;
+      let hasHost = !!options.displayURI;
+      if (hasHost) {
+        messageString = gNavigatorBundle.getFormattedString("xpinstallPromptMessage.header", ["<>"]);
+        options.name = options.displayURI.displayHost;
+      } else {
+        messageString = gNavigatorBundle.getString("xpinstallPromptMessage.header.unknown");
+      }
       // displayURI becomes it's own label, so we unset it for this panel. It will become part of the
       // messageString above.
       options.displayURI = undefined;
 
       options.eventCallback = (topic) => {
         if (topic !== "showing") {
           return;
         }
         let doc = browser.ownerDocument;
         let message = doc.getElementById("addon-install-blocked-message");
         // We must remove any prior use of this panel message in this window.
         while (message.firstChild) {
           message.firstChild.remove();
         }
-        let text = gNavigatorBundle.getString("xpinstallPromptMessage.message");
-        let b = doc.createElementNS("http://www.w3.org/1999/xhtml", "b");
-        b.textContent = options.name;
-        let fragment = BrowserUtils.getLocalizedFragment(doc, text, b);
-        message.appendChild(fragment);
+        if (hasHost) {
+          let text = gNavigatorBundle.getString("xpinstallPromptMessage.message");
+          let b = doc.createElementNS("http://www.w3.org/1999/xhtml", "b");
+          b.textContent = options.name;
+          let fragment = BrowserUtils.getLocalizedFragment(doc, text, b);
+          message.appendChild(fragment);
+        } else {
+          message.textContent = gNavigatorBundle.getString("xpinstallPromptMessage.message.unknown");
+        }
         let learnMore = doc.getElementById("addon-install-blocked-info");
         learnMore.textContent = gNavigatorBundle.getString("xpinstallPromptMessage.learnMore");
         learnMore.setAttribute("href", Services.urlFormatter.formatURLPref("app.support.baseURL") + "unlisted-extensions-risks");
       };
 
       let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI");
       action = {
         label: gNavigatorBundle.getString("xpinstallPromptMessage.install"),
--- a/browser/base/content/pageinfo/pageInfo.js
+++ b/browser/base/content/pageinfo/pageInfo.js
@@ -314,30 +314,16 @@ async function onLoadPageInfo() {
 }
 
 function loadPageInfo(frameOuterWindowID, imageElement, browser) {
   browser = browser || window.opener.gBrowser.selectedBrowser;
   let mm = browser.messageManager;
 
   let imageInfo = imageElement;
 
-  // Generate security information if it is a security error page.
-  let documentURI = browser.documentURI.spec;
-  if (documentURI.startsWith("about:certerror")) {
-    let hostName = null;
-    try {
-      hostName = browser.currentURI.displayHost;
-    } catch (exception) { }
-    let info = {
-      isTopWindow: !!frameOuterWindowID,
-      hostName,
-    };
-    securityOnLoad(documentURI, info);
-  }
-
   // Look for pageInfoListener in content.js. Sends message to listener with arguments.
   mm.sendAsyncMessage("PageInfo:getData", {strings: gStrings, frameOuterWindowID});
 
   let pageInfoData;
 
   // Get initial pageInfoData needed to display the general, permission and security tabs.
   mm.addMessageListener("PageInfo:data", async function onmessage(message) {
     mm.removeMessageListener("PageInfo:data", onmessage);
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -1722,16 +1722,19 @@ window._gBrowser = {
     if (!Services.prefs.getBoolPref("fission.rebuild_frameloaders_on_remoteness_change", false)) {
       parent.appendChild(aBrowser);
     } else {
       // This call actually switches out our frameloaders. Do this as late as
       // possible before rebuilding the browser, as we'll need the new browser
       // state set up completely first.
       aBrowser.changeRemoteness({ remoteType });
       // Once we have new frameloaders, this call sets the browser back up.
+      //
+      // FIXME(emilio): Shouldn't we call destroy() first? What hides the
+      // select pop-ups and such otherwise?
       aBrowser.construct();
     }
 
     aBrowser.userTypedValue = oldUserTypedValue;
     if (hadStartedLoad) {
       aBrowser.urlbarChangeTracker.startedLoad();
     }
 
--- a/browser/base/content/test/caps/browser_principalSerialization_version1.js
+++ b/browser/base/content/test/caps/browser_principalSerialization_version1.js
@@ -113,92 +113,86 @@ add_task(async function test_realHistory
       "output": {
         "URI": false,
         "originAttributes": {
           "appId": 0,
           "firstPartyDomain": "",
           "inIsolatedMozBrowser": false,
           "privateBrowsingId": 0,
           "userContextId": 0,
-          "geckoViewSessionContextId": "",
         },
         "cspJSON": "{}",
       },
     },
     {
       "input": "ZT4OTT7kRfqycpfCC8AeuAAAAAAAAAAAwAAAAAAAAEYB3pRy0IA0EdOTmQAQS6D9QJIHOlRteE8wkTq4cYEyCMYAAAAC/////wAAAbsBAAAAHmh0dHBzOi8vd3d3Lm1vemlsbGEub3JnL2VuLVVTLwAAAAAAAAAFAAAACAAAAA8AAAAA/////wAAAAD/////AAAACAAAAA8AAAAXAAAABwAAABcAAAAHAAAAFwAAAAcAAAAeAAAAAAAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AQAAAAAAAAAAAAAAAQnZ7Rrl1EAEv+Anzrkj2ayzxMCuvV5MrYfgjSENuz+fAd6UctCANBHTk5kAEEug/UCSBzpUbXhPMJE6uHGBMgjGAAAAAv////8AAAG7AQAAAB5odHRwczovL3d3dy5tb3ppbGxhLm9yZy9lbi1VUy8AAAAAAAAABQAAAAgAAAAPAAAAAP////8AAAAA/////wAAAAgAAAAPAAAAFwAAAAcAAAAXAAAABwAAABcAAAAHAAAAHgAAAAAAAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wEAAAAAAAAAAAABAAAFtgBzAGMAcgBpAHAAdAAtAHMAcgBjACAAJwBzAGUAbABmACcAIABoAHQAdABwAHMAOgAvAC8AKgAuAG0AbwB6AGkAbABsAGEALgBuAGUAdAAgAGgAdAB0AHAAcwA6AC8ALwAqAC4AbQBvAHoAaQBsAGwAYQAuAG8AcgBnACAAaAB0AHQAcABzADoALwAvACoALgBtAG8AegBpAGwAbABhAC4AYwBvAG0AIAAnAHUAbgBzAGEAZgBlAC0AaQBuAGwAaQBuAGUAJwAgACcAdQBuAHMAYQBmAGUALQBlAHYAYQBsACcAIABoAHQAdABwAHMAOgAvAC8AdwB3AHcALgBnAG8AbwBnAGwAZQB0AGEAZwBtAGEAbgBhAGcAZQByAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AdwB3AHcALgBnAG8AbwBnAGwAZQAtAGEAbgBhAGwAeQB0AGkAYwBzAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AdABhAGcAbQBhAG4AYQBnAGUAcgAuAGcAbwBvAGcAbABlAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AdwB3AHcALgB5AG8AdQB0AHUAYgBlAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AcwAuAHkAdABpAG0AZwAuAGMAbwBtADsAIABpAG0AZwAtAHMAcgBjACAAJwBzAGUAbABmACcAIABoAHQAdABwAHMAOgAvAC8AKgAuAG0AbwB6AGkAbABsAGEALgBuAGUAdAAgAGgAdAB0AHAAcwA6AC8ALwAqAC4AbQBvAHoAaQBsAGwAYQAuAG8AcgBnACAAaAB0AHQAcABzADoALwAvACoALgBtAG8AegBpAGwAbABhAC4AYwBvAG0AIABkAGEAdABhADoAIABoAHQAdABwAHMAOgAvAC8AbQBvAHoAaQBsAGwAYQAuAG8AcgBnACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AZwBvAG8AZwBsAGUAdABhAGcAbQBhAG4AYQBnAGUAcgAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AZwBvAG8AZwBsAGUALQBhAG4AYQBsAHkAdABpAGMAcwAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAGEAZABzAGUAcgB2AGkAYwBlAC4AZwBvAG8AZwBsAGUALgBjAG8AbQAgAGgAdAB0AHAAcwA6AC8ALwBhAGQAcwBlAHIAdgBpAGMAZQAuAGcAbwBvAGcAbABlAC4AZABlACAAaAB0AHQAcABzADoALwAvAGEAZABzAGUAcgB2AGkAYwBlAC4AZwBvAG8AZwBsAGUALgBkAGsAIABoAHQAdABwAHMAOgAvAC8AYwByAGUAYQB0AGkAdgBlAGMAbwBtAG0AbwBuAHMALgBvAHIAZwAgAGgAdAB0AHAAcwA6AC8ALwBhAGQALgBkAG8AdQBiAGwAZQBjAGwAaQBjAGsALgBuAGUAdAA7ACAAZABlAGYAYQB1AGwAdAAtAHMAcgBjACAAJwBzAGUAbABmACcAIABoAHQAdABwAHMAOgAvAC8AKgAuAG0AbwB6AGkAbABsAGEALgBuAGUAdAAgAGgAdAB0AHAAcwA6AC8ALwAqAC4AbQBvAHoAaQBsAGwAYQAuAG8AcgBnACAAaAB0AHQAcABzADoALwAvACoALgBtAG8AegBpAGwAbABhAC4AYwBvAG0AOwAgAGYAcgBhAG0AZQAtAHMAcgBjACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AZwBvAG8AZwBsAGUAdABhAGcAbQBhAG4AYQBnAGUAcgAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AZwBvAG8AZwBsAGUALQBhAG4AYQBsAHkAdABpAGMAcwAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AeQBvAHUAdAB1AGIAZQAtAG4AbwBjAG8AbwBrAGkAZQAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAHQAcgBhAGMAawBlAHIAdABlAHMAdAAuAG8AcgBnACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AcwB1AHIAdgBlAHkAZwBpAHoAbQBvAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AYQBjAGMAbwB1AG4AdABzAC4AZgBpAHIAZQBmAG8AeAAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAGEAYwBjAG8AdQBuAHQAcwAuAGYAaQByAGUAZgBvAHgALgBjAG8AbQAuAGMAbgAgAGgAdAB0AHAAcwA6AC8ALwB3AHcAdwAuAHkAbwB1AHQAdQBiAGUALgBjAG8AbQA7ACAAcwB0AHkAbABlAC0AcwByAGMAIAAnAHMAZQBsAGYAJwAgAGgAdAB0AHAAcwA6AC8ALwAqAC4AbQBvAHoAaQBsAGwAYQAuAG4AZQB0ACAAaAB0AHQAcABzADoALwAvACoALgBtAG8AegBpAGwAbABhAC4AbwByAGcAIABoAHQAdABwAHMAOgAvAC8AKgAuAG0AbwB6AGkAbABsAGEALgBjAG8AbQAgACcAdQBuAHMAYQBmAGUALQBpAG4AbABpAG4AZQAnADsAIABjAG8AbgBuAGUAYwB0AC0AcwByAGMAIAAnAHMAZQBsAGYAJwAgAGgAdAB0AHAAcwA6AC8ALwAqAC4AbQBvAHoAaQBsAGwAYQAuAG4AZQB0ACAAaAB0AHQAcABzADoALwAvACoALgBtAG8AegBpAGwAbABhAC4AbwByAGcAIABoAHQAdABwAHMAOgAvAC8AKgAuAG0AbwB6AGkAbABsAGEALgBjAG8AbQAgAGgAdAB0AHAAcwA6AC8ALwB3AHcAdwAuAGcAbwBvAGcAbABlAHQAYQBnAG0AYQBuAGEAZwBlAHIALgBjAG8AbQAgAGgAdAB0AHAAcwA6AC8ALwB3AHcAdwAuAGcAbwBvAGcAbABlAC0AYQBuAGEAbAB5AHQAaQBjAHMALgBjAG8AbQAgAGgAdAB0AHAAcwA6AC8ALwBhAGMAYwBvAHUAbgB0AHMALgBmAGkAcgBlAGYAbwB4AC4AYwBvAG0ALwAgAGgAdAB0AHAAcwA6AC8ALwBhAGMAYwBvAHUAbgB0AHMALgBmAGkAcgBlAGYAbwB4AC4AYwBvAG0ALgBjAG4ALwA7ACAAYwBoAGkAbABkAC0AcwByAGMAIABoAHQAdABwAHMAOgAvAC8AdwB3AHcALgBnAG8AbwBnAGwAZQB0AGEAZwBtAGEAbgBhAGcAZQByAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AdwB3AHcALgBnAG8AbwBnAGwAZQAtAGEAbgBhAGwAeQB0AGkAYwBzAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AdwB3AHcALgB5AG8AdQB0AHUAYgBlAC0AbgBvAGMAbwBvAGsAaQBlAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AdAByAGEAYwBrAGUAcgB0AGUAcwB0AC4AbwByAGcAIABoAHQAdABwAHMAOgAvAC8AdwB3AHcALgBzAHUAcgB2AGUAeQBnAGkAegBtAG8ALgBjAG8AbQAgAGgAdAB0AHAAcwA6AC8ALwBhAGMAYwBvAHUAbgB0AHMALgBmAGkAcgBlAGYAbwB4AC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AYQBjAGMAbwB1AG4AdABzAC4AZgBpAHIAZQBmAG8AeAAuAGMAbwBtAC4AYwBuACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AeQBvAHUAdAB1AGIAZQAuAGMAbwBtAAA=",
       "output": {
         "cspJSON": "{\"csp-policies\":[{\"child-src\":[\"https://www.googletagmanager.com\",\"https://www.google-analytics.com\",\"https://www.youtube-nocookie.com\",\"https://trackertest.org\",\"https://www.surveygizmo.com\",\"https://accounts.firefox.com\",\"https://accounts.firefox.com.cn\",\"https://www.youtube.com\"],\"connect-src\":[\"'self'\",\"https://*.mozilla.net\",\"https://*.mozilla.org\",\"https://*.mozilla.com\",\"https://www.googletagmanager.com\",\"https://www.google-analytics.com\",\"https://accounts.firefox.com/\",\"https://accounts.firefox.com.cn/\"],\"default-src\":[\"'self'\",\"https://*.mozilla.net\",\"https://*.mozilla.org\",\"https://*.mozilla.com\"],\"frame-src\":[\"https://www.googletagmanager.com\",\"https://www.google-analytics.com\",\"https://www.youtube-nocookie.com\",\"https://trackertest.org\",\"https://www.surveygizmo.com\",\"https://accounts.firefox.com\",\"https://accounts.firefox.com.cn\",\"https://www.youtube.com\"],\"img-src\":[\"'self'\",\"https://*.mozilla.net\",\"https://*.mozilla.org\",\"https://*.mozilla.com\",\"data:\",\"https://mozilla.org\",\"https://www.googletagmanager.com\",\"https://www.google-analytics.com\",\"https://adservice.google.com\",\"https://adservice.google.de\",\"https://adservice.google.dk\",\"https://creativecommons.org\",\"https://ad.doubleclick.net\"],\"report-only\":false,\"script-src\":[\"'self'\",\"https://*.mozilla.net\",\"https://*.mozilla.org\",\"https://*.mozilla.com\",\"'unsafe-inline'\",\"'unsafe-eval'\",\"https://www.googletagmanager.com\",\"https://www.google-analytics.com\",\"https://tagmanager.google.com\",\"https://www.youtube.com\",\"https://s.ytimg.com\"],\"style-src\":[\"'self'\",\"https://*.mozilla.net\",\"https://*.mozilla.org\",\"https://*.mozilla.com\",\"'unsafe-inline'\"]}]}",
         "URISpec": "https://www.mozilla.org/en-US/",
         "originAttributes": {
           "appId": 0,
           "firstPartyDomain": "",
           "inIsolatedMozBrowser": false,
           "privateBrowsingId": 0,
           "userContextId": 0,
-          "geckoViewSessionContextId": "",
         },
       },
     },
     {
       "input": "ZT4OTT7kRfqycpfCC8AeuAAAAAAAAAAAwAAAAAAAAEYB3pRy0IA0EdOTmQAQS6D9QJIHOlRteE8wkTq4cYEyCMYAAAAC/////wAAAbsBAAAAL2h0dHBzOi8vd3d3Lm1vemlsbGEub3JnL2VuLVVTL2ZpcmVmb3gvYWNjb3VudHMvAAAAAAAAAAUAAAAIAAAADwAAAAj/////AAAACP////8AAAAIAAAADwAAABcAAAAYAAAAFwAAABgAAAAXAAAAGAAAAC8AAAAAAAAAL/////8AAAAA/////wAAABf/////AAAAF/////8BAAAAAAAAAAAAAAABCdntGuXUQAS/4CfOuSPZrLPEwK69Xkyth+CNIQ27P58B3pRy0IA0EdOTmQAQS6D9QJIHOlRteE8wkTq4cYEyCMYAAAAC/////wAAAbsBAAAAL2h0dHBzOi8vd3d3Lm1vemlsbGEub3JnL2VuLVVTL2ZpcmVmb3gvYWNjb3VudHMvAAAAAAAAAAUAAAAIAAAADwAAAAj/////AAAACP////8AAAAIAAAADwAAABcAAAAYAAAAFwAAABgAAAAXAAAAGAAAAC8AAAAAAAAAL/////8AAAAA/////wAAABf/////AAAAF/////8BAAAAAAAAAAAAAQAABbYAcwBjAHIAaQBwAHQALQBzAHIAYwAgACcAcwBlAGwAZgAnACAAaAB0AHQAcABzADoALwAvACoALgBtAG8AegBpAGwAbABhAC4AbgBlAHQAIABoAHQAdABwAHMAOgAvAC8AKgAuAG0AbwB6AGkAbABsAGEALgBvAHIAZwAgAGgAdAB0AHAAcwA6AC8ALwAqAC4AbQBvAHoAaQBsAGwAYQAuAGMAbwBtACAAJwB1AG4AcwBhAGYAZQAtAGkAbgBsAGkAbgBlACcAIAAnAHUAbgBzAGEAZgBlAC0AZQB2AGEAbAAnACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AZwBvAG8AZwBsAGUAdABhAGcAbQBhAG4AYQBnAGUAcgAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AZwBvAG8AZwBsAGUALQBhAG4AYQBsAHkAdABpAGMAcwAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAHQAYQBnAG0AYQBuAGEAZwBlAHIALgBnAG8AbwBnAGwAZQAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AeQBvAHUAdAB1AGIAZQAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAHMALgB5AHQAaQBtAGcALgBjAG8AbQA7ACAAaQBtAGcALQBzAHIAYwAgACcAcwBlAGwAZgAnACAAaAB0AHQAcABzADoALwAvACoALgBtAG8AegBpAGwAbABhAC4AbgBlAHQAIABoAHQAdABwAHMAOgAvAC8AKgAuAG0AbwB6AGkAbABsAGEALgBvAHIAZwAgAGgAdAB0AHAAcwA6AC8ALwAqAC4AbQBvAHoAaQBsAGwAYQAuAGMAbwBtACAAZABhAHQAYQA6ACAAaAB0AHQAcABzADoALwAvAG0AbwB6AGkAbABsAGEALgBvAHIAZwAgAGgAdAB0AHAAcwA6AC8ALwB3AHcAdwAuAGcAbwBvAGcAbABlAHQAYQBnAG0AYQBuAGEAZwBlAHIALgBjAG8AbQAgAGgAdAB0AHAAcwA6AC8ALwB3AHcAdwAuAGcAbwBvAGcAbABlAC0AYQBuAGEAbAB5AHQAaQBjAHMALgBjAG8AbQAgAGgAdAB0AHAAcwA6AC8ALwBhAGQAcwBlAHIAdgBpAGMAZQAuAGcAbwBvAGcAbABlAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AYQBkAHMAZQByAHYAaQBjAGUALgBnAG8AbwBnAGwAZQAuAGQAZQAgAGgAdAB0AHAAcwA6AC8ALwBhAGQAcwBlAHIAdgBpAGMAZQAuAGcAbwBvAGcAbABlAC4AZABrACAAaAB0AHQAcABzADoALwAvAGMAcgBlAGEAdABpAHYAZQBjAG8AbQBtAG8AbgBzAC4AbwByAGcAIABoAHQAdABwAHMAOgAvAC8AYQBkAC4AZABvAHUAYgBsAGUAYwBsAGkAYwBrAC4AbgBlAHQAOwAgAGQAZQBmAGEAdQBsAHQALQBzAHIAYwAgACcAcwBlAGwAZgAnACAAaAB0AHQAcABzADoALwAvACoALgBtAG8AegBpAGwAbABhAC4AbgBlAHQAIABoAHQAdABwAHMAOgAvAC8AKgAuAG0AbwB6AGkAbABsAGEALgBvAHIAZwAgAGgAdAB0AHAAcwA6AC8ALwAqAC4AbQBvAHoAaQBsAGwAYQAuAGMAbwBtADsAIABmAHIAYQBtAGUALQBzAHIAYwAgAGgAdAB0AHAAcwA6AC8ALwB3AHcAdwAuAGcAbwBvAGcAbABlAHQAYQBnAG0AYQBuAGEAZwBlAHIALgBjAG8AbQAgAGgAdAB0AHAAcwA6AC8ALwB3AHcAdwAuAGcAbwBvAGcAbABlAC0AYQBuAGEAbAB5AHQAaQBjAHMALgBjAG8AbQAgAGgAdAB0AHAAcwA6AC8ALwB3AHcAdwAuAHkAbwB1AHQAdQBiAGUALQBuAG8AYwBvAG8AawBpAGUALgBjAG8AbQAgAGgAdAB0AHAAcwA6AC8ALwB0AHIAYQBjAGsAZQByAHQAZQBzAHQALgBvAHIAZwAgAGgAdAB0AHAAcwA6AC8ALwB3AHcAdwAuAHMAdQByAHYAZQB5AGcAaQB6AG0AbwAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAGEAYwBjAG8AdQBuAHQAcwAuAGYAaQByAGUAZgBvAHgALgBjAG8AbQAgAGgAdAB0AHAAcwA6AC8ALwBhAGMAYwBvAHUAbgB0AHMALgBmAGkAcgBlAGYAbwB4AC4AYwBvAG0ALgBjAG4AIABoAHQAdABwAHMAOgAvAC8AdwB3AHcALgB5AG8AdQB0AHUAYgBlAC4AYwBvAG0AOwAgAHMAdAB5AGwAZQAtAHMAcgBjACAAJwBzAGUAbABmACcAIABoAHQAdABwAHMAOgAvAC8AKgAuAG0AbwB6AGkAbABsAGEALgBuAGUAdAAgAGgAdAB0AHAAcwA6AC8ALwAqAC4AbQBvAHoAaQBsAGwAYQAuAG8AcgBnACAAaAB0AHQAcABzADoALwAvACoALgBtAG8AegBpAGwAbABhAC4AYwBvAG0AIAAnAHUAbgBzAGEAZgBlAC0AaQBuAGwAaQBuAGUAJwA7ACAAYwBvAG4AbgBlAGMAdAAtAHMAcgBjACAAJwBzAGUAbABmACcAIABoAHQAdABwAHMAOgAvAC8AKgAuAG0AbwB6AGkAbABsAGEALgBuAGUAdAAgAGgAdAB0AHAAcwA6AC8ALwAqAC4AbQBvAHoAaQBsAGwAYQAuAG8AcgBnACAAaAB0AHQAcABzADoALwAvACoALgBtAG8AegBpAGwAbABhAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AdwB3AHcALgBnAG8AbwBnAGwAZQB0AGEAZwBtAGEAbgBhAGcAZQByAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AdwB3AHcALgBnAG8AbwBnAGwAZQAtAGEAbgBhAGwAeQB0AGkAYwBzAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AYQBjAGMAbwB1AG4AdABzAC4AZgBpAHIAZQBmAG8AeAAuAGMAbwBtAC8AIABoAHQAdABwAHMAOgAvAC8AYQBjAGMAbwB1AG4AdABzAC4AZgBpAHIAZQBmAG8AeAAuAGMAbwBtAC4AYwBuAC8AOwAgAGMAaABpAGwAZAAtAHMAcgBjACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AZwBvAG8AZwBsAGUAdABhAGcAbQBhAG4AYQBnAGUAcgAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AZwBvAG8AZwBsAGUALQBhAG4AYQBsAHkAdABpAGMAcwAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AeQBvAHUAdAB1AGIAZQAtAG4AbwBjAG8AbwBrAGkAZQAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAHQAcgBhAGMAawBlAHIAdABlAHMAdAAuAG8AcgBnACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AcwB1AHIAdgBlAHkAZwBpAHoAbQBvAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AYQBjAGMAbwB1AG4AdABzAC4AZgBpAHIAZQBmAG8AeAAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAGEAYwBjAG8AdQBuAHQAcwAuAGYAaQByAGUAZgBvAHgALgBjAG8AbQAuAGMAbgAgAGgAdAB0AHAAcwA6AC8ALwB3AHcAdwAuAHkAbwB1AHQAdQBiAGUALgBjAG8AbQAA",
       "output": {
         "URISpec": "https://www.mozilla.org/en-US/firefox/accounts/",
         "originAttributes": {
           "appId": 0,
           "firstPartyDomain": "",
           "inIsolatedMozBrowser": false,
           "privateBrowsingId": 0,
           "userContextId": 0,
-          "geckoViewSessionContextId": "",
         },
         "cspJSON": "{\"csp-policies\":[{\"child-src\":[\"https://www.googletagmanager.com\",\"https://www.google-analytics.com\",\"https://www.youtube-nocookie.com\",\"https://trackertest.org\",\"https://www.surveygizmo.com\",\"https://accounts.firefox.com\",\"https://accounts.firefox.com.cn\",\"https://www.youtube.com\"],\"connect-src\":[\"'self'\",\"https://*.mozilla.net\",\"https://*.mozilla.org\",\"https://*.mozilla.com\",\"https://www.googletagmanager.com\",\"https://www.google-analytics.com\",\"https://accounts.firefox.com/\",\"https://accounts.firefox.com.cn/\"],\"default-src\":[\"'self'\",\"https://*.mozilla.net\",\"https://*.mozilla.org\",\"https://*.mozilla.com\"],\"frame-src\":[\"https://www.googletagmanager.com\",\"https://www.google-analytics.com\",\"https://www.youtube-nocookie.com\",\"https://trackertest.org\",\"https://www.surveygizmo.com\",\"https://accounts.firefox.com\",\"https://accounts.firefox.com.cn\",\"https://www.youtube.com\"],\"img-src\":[\"'self'\",\"https://*.mozilla.net\",\"https://*.mozilla.org\",\"https://*.mozilla.com\",\"data:\",\"https://mozilla.org\",\"https://www.googletagmanager.com\",\"https://www.google-analytics.com\",\"https://adservice.google.com\",\"https://adservice.google.de\",\"https://adservice.google.dk\",\"https://creativecommons.org\",\"https://ad.doubleclick.net\"],\"report-only\":false,\"script-src\":[\"'self'\",\"https://*.mozilla.net\",\"https://*.mozilla.org\",\"https://*.mozilla.com\",\"'unsafe-inline'\",\"'unsafe-eval'\",\"https://www.googletagmanager.com\",\"https://www.google-analytics.com\",\"https://tagmanager.google.com\",\"https://www.youtube.com\",\"https://s.ytimg.com\"],\"style-src\":[\"'self'\",\"https://*.mozilla.net\",\"https://*.mozilla.org\",\"https://*.mozilla.com\",\"'unsafe-inline'\"]}]}",
       },
     },
     {
       "input": "ZT4OTT7kRfqycpfCC8AeuAAAAAAAAAAAwAAAAAAAAEYB3pRy0IA0EdOTmQAQS6D9QJIHOlRteE8wkTq4cYEyCMYAAAAC/////wAAAbsBAAAAe2h0dHBzOi8vZGV2ZWxvcGVyLm1vemlsbGEub3JnL2VuLVVTLz91dG1fc291cmNlPXd3dy5tb3ppbGxhLm9yZyZ1dG1fbWVkaXVtPXJlZmVycmFsJnV0bV9jYW1wYWlnbj1uYXYmdXRtX2NvbnRlbnQ9ZGV2ZWxvcGVycwAAAAAAAAAFAAAACAAAABUAAAAA/////wAAAAD/////AAAACAAAABUAAAAdAAAAXgAAAB0AAAAHAAAAHQAAAAcAAAAkAAAAAAAAAAD/////AAAAAP////8AAAAlAAAAVgAAAAD/////AQAAAAAAAAAAAAAAAA==",
       "output": {
         "URISpec": "https://developer.mozilla.org/en-US/?utm_source=www.mozilla.org&utm_medium=referral&utm_campaign=nav&utm_content=developers",
         "originAttributes": {
           "appId": 0,
           "firstPartyDomain": "",
           "inIsolatedMozBrowser": false,
           "privateBrowsingId": 0,
           "userContextId": 0,
-          "geckoViewSessionContextId": "",
         },
         "cspJSON": "{}",
       },
     },
     {
       "input": "SmIS26zLEdO3ZQBgsLbOywAAAAAAAAAAwAAAAAAAAEY=",
       "output": {
         "URI": false,
         "originAttributes": {
           "appId": 0,
           "firstPartyDomain": "",
           "inIsolatedMozBrowser": false,
           "privateBrowsingId": 0,
           "userContextId": 0,
-          "geckoViewSessionContextId": "",
         },
         "cspJSON": "{}",
       },
     },
     {
       "input": "vQZuXxRvRHKDMXv9BbHtkAAAAAAAAAAAwAAAAAAAAEYAAAA4bW96LW51bGxwcmluY2lwYWw6ezA0NWNhMThkLTQzNmMtNDc0NC1iYmI2LWIxYTE1MzY2ZGY3OX0AAAAA",
       "output": {
         "URISpec": "moz-nullprincipal:{045ca18d-436c-4744-bbb6-b1a15366df79}",
         "originAttributes": {
           "appId": 0,
           "firstPartyDomain": "",
           "inIsolatedMozBrowser": false,
           "privateBrowsingId": 0,
           "userContextId": 0,
-          "geckoViewSessionContextId": "",
         },
         "cspJSON": "{}",
       },
     },
   ];
 
   for (let test of serializedPrincipalsFromFirefox) {
     let principal = E10SUtils.deserializePrincipal(test.input);
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -20,20 +20,18 @@ contextMenuSearch.accesskey=S
 
 bookmarkAllTabsDefault=[Folder Name]
 
 xpinstallPromptMessage=%S prevented this site from asking you to install software on your computer.
 # LOCALIZATION NOTE (xpinstallPromptMessage.header)
 # The string contains the hostname of the site the add-on is being installed from.
 xpinstallPromptMessage.header=Allow %S to install an add-on?
 xpinstallPromptMessage.message=You are attempting to install an add-on from %S. Make sure you trust this site before continuing.
-# LOCALIZATION NOTE (xpinstallPromptMessage.unknown)
-# This string is used in xpinstallPromptMessage.header and xpinstallPromptMessage.message when the domain is not available,
-# for example in case of local installs with drag and drop.
-xpinstallPromptMessage.unknown=an unknown site
+xpinstallPromptMessage.header.unknown=Allow an unknown site to install an add-on?
+xpinstallPromptMessage.message.unknown=You are attempting to install an add-on from an unknown site. Make sure you trust this site before continuing.
 xpinstallPromptMessage.learnMore=Learn more about installing add-ons safely
 xpinstallPromptMessage.dontAllow=Don’t Allow
 xpinstallPromptMessage.dontAllow.accesskey=D
 # Accessibility Note:
 # Be sure you do not choose an accesskey that is used elsewhere in the active context (e.g. main menu bar, submenu of the warning popup button)
 # See https://website-archive.mozilla.org/www.mozilla.org/access/access/keyboard/ for details
 xpinstallPromptMessage.install=Continue to Installation
 xpinstallPromptMessage.install.accesskey=C
--- a/caps/OriginAttributes.cpp
+++ b/caps/OriginAttributes.cpp
@@ -158,25 +158,16 @@ void OriginAttributes::CreateSuffix(nsAC
     nsAutoString sanitizedFirstPartyDomain(mFirstPartyDomain);
     sanitizedFirstPartyDomain.ReplaceChar(
         dom::quota::QuotaManager::kReplaceChars, '+');
 
     params.Set(NS_LITERAL_STRING("firstPartyDomain"),
                sanitizedFirstPartyDomain);
   }
 
-  if (!mGeckoViewSessionContextId.IsEmpty()) {
-    nsAutoString sanitizedGeckoViewUserContextId(mGeckoViewSessionContextId);
-    sanitizedGeckoViewUserContextId.ReplaceChar(
-        dom::quota::QuotaManager::kReplaceChars, '+');
-
-    params.Set(NS_LITERAL_STRING("geckoViewUserContextId"),
-               sanitizedGeckoViewUserContextId);
-  }
-
   aStr.Truncate();
 
   params.Serialize(value);
   if (!value.IsEmpty()) {
     aStr.AppendLiteral("^");
     aStr.Append(NS_ConvertUTF16toUTF8(value));
   }
 
@@ -262,23 +253,16 @@ class MOZ_STACK_CLASS PopulateFromSuffix
     }
 
     if (aName.EqualsLiteral("firstPartyDomain")) {
       MOZ_RELEASE_ASSERT(mOriginAttributes->mFirstPartyDomain.IsEmpty());
       mOriginAttributes->mFirstPartyDomain.Assign(aValue);
       return true;
     }
 
-    if (aName.EqualsLiteral("geckoViewUserContextId")) {
-      MOZ_RELEASE_ASSERT(
-          mOriginAttributes->mGeckoViewSessionContextId.IsEmpty());
-      mOriginAttributes->mGeckoViewSessionContextId.Assign(aValue);
-      return true;
-    }
-
     // No other attributes are supported.
     return false;
   }
 
  private:
   OriginAttributes* mOriginAttributes;
 };
 
--- a/caps/OriginAttributes.h
+++ b/caps/OriginAttributes.h
@@ -45,30 +45,28 @@ class OriginAttributes : public dom::Ori
     }
   }
 
   bool operator==(const OriginAttributes& aOther) const {
     return mAppId == aOther.mAppId &&
            mInIsolatedMozBrowser == aOther.mInIsolatedMozBrowser &&
            mUserContextId == aOther.mUserContextId &&
            mPrivateBrowsingId == aOther.mPrivateBrowsingId &&
-           mFirstPartyDomain == aOther.mFirstPartyDomain &&
-           mGeckoViewSessionContextId == aOther.mGeckoViewSessionContextId;
+           mFirstPartyDomain == aOther.mFirstPartyDomain;
   }
 
   bool operator!=(const OriginAttributes& aOther) const {
     return !(*this == aOther);
   }
 
   MOZ_MUST_USE bool EqualsIgnoringFPD(const OriginAttributes& aOther) const {
     return mAppId == aOther.mAppId &&
            mInIsolatedMozBrowser == aOther.mInIsolatedMozBrowser &&
            mUserContextId == aOther.mUserContextId &&
-           mPrivateBrowsingId == aOther.mPrivateBrowsingId &&
-           mGeckoViewSessionContextId == aOther.mGeckoViewSessionContextId;
+           mPrivateBrowsingId == aOther.mPrivateBrowsingId;
   }
 
   // Serializes/Deserializes non-default values into the suffix format, i.e.
   // |!key1=value1&key2=value2|. If there are no non-default attributes, this
   // returns an empty string.
   void CreateSuffix(nsACString& aStr) const;
 
   // Don't use this method for anything else than debugging!
@@ -150,22 +148,16 @@ class OriginAttributesPattern : public d
       return false;
     }
 
     if (mFirstPartyDomain.WasPassed() &&
         mFirstPartyDomain.Value() != aAttrs.mFirstPartyDomain) {
       return false;
     }
 
-    if (mGeckoViewSessionContextId.WasPassed() &&
-        mGeckoViewSessionContextId.Value() !=
-            aAttrs.mGeckoViewSessionContextId) {
-      return false;
-    }
-
     return true;
   }
 
   bool Overlaps(const OriginAttributesPattern& aOther) const {
     if (mAppId.WasPassed() && aOther.mAppId.WasPassed() &&
         mAppId.Value() != aOther.mAppId.Value()) {
       return false;
     }
@@ -187,22 +179,15 @@ class OriginAttributesPattern : public d
       return false;
     }
 
     if (mFirstPartyDomain.WasPassed() && aOther.mFirstPartyDomain.WasPassed() &&
         mFirstPartyDomain.Value() != aOther.mFirstPartyDomain.Value()) {
       return false;
     }
 
-    if (mGeckoViewSessionContextId.WasPassed() &&
-        aOther.mGeckoViewSessionContextId.WasPassed() &&
-        mGeckoViewSessionContextId.Value() !=
-            aOther.mGeckoViewSessionContextId.Value()) {
-      return false;
-    }
-
     return true;
   }
 };
 
 }  // namespace mozilla
 
 #endif /* mozilla_OriginAttributes_h */
--- a/devtools/client/aboutdebugging-new/aboutdebugging.js
+++ b/devtools/client/aboutdebugging-new/aboutdebugging.js
@@ -30,16 +30,17 @@ const {
   removeNetworkLocationsObserver,
 } = require("./src/modules/network-locations");
 const {
   addUSBRuntimesObserver,
   getUSBRuntimes,
   removeUSBRuntimesObserver,
 } = require("./src/modules/usb-runtimes");
 
+loader.lazyRequireGetter(this, "adb", "devtools/shared/adb/adb", true);
 loader.lazyRequireGetter(this, "adbAddon", "devtools/shared/adb/adb-addon", true);
 loader.lazyRequireGetter(this, "adbProcess", "devtools/shared/adb/adb-process", true);
 
 const Router = createFactory(require("devtools/client/shared/vendor/react-router-dom").HashRouter);
 const App = createFactory(require("./src/components/App"));
 
 const AboutDebugging = {
   async init() {
@@ -59,16 +60,27 @@ const AboutDebugging = {
 
     const width = this.getRoundedViewportWidth();
     this.actions.recordTelemetryEvent("open_adbg", { width });
 
     await l10n.init();
 
     this.actions.createThisFirefoxRuntime();
 
+    // Listen to Network locations updates and retrieve the initial list of locations.
+    addNetworkLocationsObserver(this.onNetworkLocationsUpdated);
+    await this.onNetworkLocationsUpdated();
+
+    // Listen to USB runtime updates and retrieve the initial list of runtimes.
+    const onAdbRuntimesReady = adb.once("runtime-list-ready");
+    addUSBRuntimesObserver(this.onUSBRuntimesUpdated);
+    await onAdbRuntimesReady;
+
+    await this.onUSBRuntimesUpdated();
+
     render(
       Provider(
         {
           store: this.store,
         },
         LocalizationProvider(
           { messages: l10n.getBundles() },
           Router(
@@ -77,23 +89,16 @@ const AboutDebugging = {
               {}
             )
           )
         )
       ),
       this.mount
     );
 
-    this.onNetworkLocationsUpdated();
-    addNetworkLocationsObserver(this.onNetworkLocationsUpdated);
-
-    // Listen to USB runtime updates and retrieve the initial list of runtimes.
-    this.onUSBRuntimesUpdated();
-    addUSBRuntimesObserver(this.onUSBRuntimesUpdated);
-
     adbAddon.on("update", this.onAdbAddonUpdated);
     this.onAdbAddonUpdated();
     adbProcess.on("adb-ready", this.onAdbProcessReady);
     // get the initial status of adb process, in case it's already started
     this.onAdbProcessReady();
 
     // Remove deprecated remote debugging extensions.
     await adbAddon.uninstallUnsupportedExtensions();
@@ -103,22 +108,22 @@ const AboutDebugging = {
     this.actions.updateAdbAddonStatus(adbAddon.status);
   },
 
   onAdbProcessReady() {
     this.actions.updateAdbReady(adbProcess.ready);
   },
 
   onNetworkLocationsUpdated() {
-    this.actions.updateNetworkLocations(getNetworkLocations());
+    return this.actions.updateNetworkLocations(getNetworkLocations());
   },
 
   async onUSBRuntimesUpdated() {
     const runtimes = await getUSBRuntimes();
-    this.actions.updateUSBRuntimes(runtimes);
+    return this.actions.updateUSBRuntimes(runtimes);
   },
 
   async destroy() {
     const width = this.getRoundedViewportWidth();
     this.actions.recordTelemetryEvent("close_adbg", { width });
 
     const state = this.store.getState();
     const currentRuntimeId = state.runtimes.selectedRuntimeId;
--- a/devtools/client/aboutdebugging-new/src/actions/ui.js
+++ b/devtools/client/aboutdebugging-new/src/actions/ui.js
@@ -10,17 +10,19 @@ const {
   ADB_ADDON_INSTALL_FAILURE,
   ADB_ADDON_UNINSTALL_START,
   ADB_ADDON_UNINSTALL_SUCCESS,
   ADB_ADDON_UNINSTALL_FAILURE,
   ADB_ADDON_STATUS_UPDATED,
   ADB_READY_UPDATED,
   DEBUG_TARGET_COLLAPSIBILITY_UPDATED,
   HIDE_PROFILER_DIALOG,
-  NETWORK_LOCATIONS_UPDATED,
+  NETWORK_LOCATIONS_UPDATE_FAILURE,
+  NETWORK_LOCATIONS_UPDATE_START,
+  NETWORK_LOCATIONS_UPDATE_SUCCESS,
   PAGE_TYPES,
   SELECT_PAGE_FAILURE,
   SELECT_PAGE_START,
   SELECT_PAGE_SUCCESS,
   SELECTED_RUNTIME_ID_UPDATED,
   SHOW_PROFILER_DIALOG,
   USB_RUNTIMES_SCAN_START,
   USB_RUNTIMES_SCAN_SUCCESS,
@@ -113,19 +115,24 @@ function updateAdbAddonStatus(adbAddonSt
   return { type: ADB_ADDON_STATUS_UPDATED, adbAddonStatus };
 }
 
 function updateAdbReady(isAdbReady) {
   return { type: ADB_READY_UPDATED, isAdbReady };
 }
 
 function updateNetworkLocations(locations) {
-  return (dispatch, getState) => {
-    dispatch(Actions.updateNetworkRuntimes(locations));
-    dispatch({ type: NETWORK_LOCATIONS_UPDATED, locations });
+  return async (dispatch, getState) => {
+    dispatch({ type: NETWORK_LOCATIONS_UPDATE_START });
+    try {
+      await dispatch(Actions.updateNetworkRuntimes(locations));
+      dispatch({ type: NETWORK_LOCATIONS_UPDATE_SUCCESS, locations });
+    } catch (e) {
+      dispatch({ type: NETWORK_LOCATIONS_UPDATE_FAILURE, error: e });
+    }
   };
 }
 
 function installAdbAddon() {
   return async (dispatch, getState) => {
     dispatch({ type: ADB_ADDON_INSTALL_START });
 
     try {
--- a/devtools/client/aboutdebugging-new/src/components/App.js
+++ b/devtools/client/aboutdebugging-new/src/components/App.js
@@ -71,28 +71,16 @@ class App extends PureComponent {
     });
   }
 
   // The `match` object here is passed automatically by the Route object.
   // We are using it to read the route path.
   // See react-router docs:
   // https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/match.md
   renderRuntime({ match }) {
-    // Redirect to This Firefox in these cases:
-    // - If the runtimepage for a device is the first page shown (since we can't
-    //   keep connections open between page reloads).
-    // - If no runtimeId is given.
-    // - If runtime is not in the runtimes list or disconnected (this is handled later)
-    const isDeviceFirstPage =
-      !this.props.selectedPage &&
-      match.params.runtimeId !== RUNTIMES.THIS_FIREFOX;
-    if (!match.params.runtimeId || isDeviceFirstPage) {
-      return Redirect({ to: `/runtime/${RUNTIMES.THIS_FIREFOX}` });
-    }
-
     const isRuntimeAvailable = id => {
       const runtimes = [
         ...this.props.networkRuntimes,
         ...this.props.usbRuntimes,
       ];
       const runtime = runtimes.find(x => x.id === id);
       return runtime && runtime.runtimeDetails;
     };
--- a/devtools/client/aboutdebugging-new/src/components/ProfilerDialog.js
+++ b/devtools/client/aboutdebugging-new/src/components/ProfilerDialog.js
@@ -43,23 +43,23 @@ class ProfilerDialog extends PureCompone
           onClick: e => e.stopPropagation(),
         },
         dom.header(
           {
             className: "profiler-dialog__header",
           },
           Localized(
             {
-              id: "about-debugging-profiler-dialog-title",
+              id: "about-debugging-profiler-dialog-title2",
             },
             dom.h1(
               {
                 className: "profiler-dialog__header__title",
               },
-              "Performance Profiler",
+              "about-debugging-profiler-dialog-title2",
             )
           ),
           dom.button(
             {
               className: "ghost-button js-profiler-dialog-close",
               onClick: () => this.hide(),
             },
             dom.img(
--- a/devtools/client/aboutdebugging-new/src/components/RuntimeActions.js
+++ b/devtools/client/aboutdebugging-new/src/components/RuntimeActions.js
@@ -57,24 +57,24 @@ class RuntimeActions extends PureCompone
   }
 
   renderProfileButton() {
     const { runtimeId } = this.props;
 
     return runtimeId !== RUNTIMES.THIS_FIREFOX
          ? Localized(
            {
-             id: "about-debugging-runtime-profile-button",
+             id: "about-debugging-runtime-profile-button2",
            },
            dom.button(
              {
                className: "default-button js-profile-runtime-button",
                onClick: () => this.onProfilerButtonClick(),
              },
-             "Profile Runtime"
+             "about-debugging-runtime-profile-button2"
            ),
          )
          : null;
   }
 
   render() {
     return dom.div(
       {},
--- a/devtools/client/aboutdebugging-new/src/constants.js
+++ b/devtools/client/aboutdebugging-new/src/constants.js
@@ -21,17 +21,19 @@ const actionTypes = {
   CONNECT_RUNTIME_NOT_RESPONDING: "CONNECT_RUNTIME_NOT_RESPONDING",
   CONNECT_RUNTIME_START: "CONNECT_RUNTIME_START",
   CONNECT_RUNTIME_SUCCESS: "CONNECT_RUNTIME_SUCCESS",
   DEBUG_TARGET_COLLAPSIBILITY_UPDATED: "DEBUG_TARGET_COLLAPSIBILITY_UPDATED",
   DISCONNECT_RUNTIME_FAILURE: "DISCONNECT_RUNTIME_FAILURE",
   DISCONNECT_RUNTIME_START: "DISCONNECT_RUNTIME_START",
   DISCONNECT_RUNTIME_SUCCESS: "DISCONNECT_RUNTIME_SUCCESS",
   HIDE_PROFILER_DIALOG: "HIDE_PROFILER_DIALOG",
-  NETWORK_LOCATIONS_UPDATED: "NETWORK_LOCATIONS_UPDATED",
+  NETWORK_LOCATIONS_UPDATE_FAILURE: "NETWORK_LOCATIONS_UPDATE_FAILURE",
+  NETWORK_LOCATIONS_UPDATE_START: "NETWORK_LOCATIONS_UPDATE_START",
+  NETWORK_LOCATIONS_UPDATE_SUCCESS: "NETWORK_LOCATIONS_UPDATE_SUCCESS",
   REMOTE_RUNTIMES_UPDATED: "REMOTE_RUNTIMES_UPDATED",
   REQUEST_EXTENSIONS_FAILURE: "REQUEST_EXTENSIONS_FAILURE",
   REQUEST_EXTENSIONS_START: "REQUEST_EXTENSIONS_START",
   REQUEST_EXTENSIONS_SUCCESS: "REQUEST_EXTENSIONS_SUCCESS",
   REQUEST_PROCESSES_FAILURE: "REQUEST_PROCESSES_FAILURE",
   REQUEST_PROCESSES_START: "REQUEST_PROCESSES_START",
   REQUEST_PROCESSES_SUCCESS: "REQUEST_PROCESSES_SUCCESS",
   REQUEST_TABS_FAILURE: "REQUEST_TABS_FAILURE",
--- a/devtools/client/aboutdebugging-new/src/reducers/ui-state.js
+++ b/devtools/client/aboutdebugging-new/src/reducers/ui-state.js
@@ -4,17 +4,17 @@
 
 "use strict";
 
 const {
   ADB_ADDON_STATUS_UPDATED,
   ADB_READY_UPDATED,
   DEBUG_TARGET_COLLAPSIBILITY_UPDATED,
   HIDE_PROFILER_DIALOG,
-  NETWORK_LOCATIONS_UPDATED,
+  NETWORK_LOCATIONS_UPDATE_SUCCESS,
   SELECT_PAGE_SUCCESS,
   SHOW_PROFILER_DIALOG,
   TEMPORARY_EXTENSION_INSTALL_FAILURE,
   TEMPORARY_EXTENSION_INSTALL_SUCCESS,
   USB_RUNTIMES_SCAN_START,
   USB_RUNTIMES_SCAN_SUCCESS,
 } = require("../constants");
 
@@ -47,17 +47,17 @@ function uiReducer(state = UiState(), ac
 
     case DEBUG_TARGET_COLLAPSIBILITY_UPDATED: {
       const { isCollapsed, key } = action;
       const debugTargetCollapsibilities = new Map(state.debugTargetCollapsibilities);
       debugTargetCollapsibilities.set(key, isCollapsed);
       return Object.assign({}, state, { debugTargetCollapsibilities });
     }
 
-    case NETWORK_LOCATIONS_UPDATED: {
+    case NETWORK_LOCATIONS_UPDATE_SUCCESS: {
       const { locations } = action;
       return Object.assign({}, state, { networkLocations: locations });
     }
 
     case SELECT_PAGE_SUCCESS: {
       const { page } = action;
       return Object.assign({}, state, { selectedPage: page });
     }
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_persist_connection.js
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_persist_connection.js
@@ -54,16 +54,22 @@ async function testRemoteClientPersistCo
   document = await reloadAboutDebugging(tab);
 
   info("Wait until the remote runtime appears as connected");
   await waitUntil(() => {
     const sidebarItem = findSidebarItemByText(sidebarName, document);
     return sidebarItem && !sidebarItem.querySelector(".js-connect-button");
   });
 
+  info("Wait until the remote runtime page is selected");
+  await waitUntil(() => {
+    const runtimeInfo = document.querySelector(".js-runtime-name");
+    return runtimeInfo && runtimeInfo.textContent.includes(runtimeName);
+  });
+
   // Remove the runtime without emitting an update.
   // This is what happens today when we simply close Firefox for Android.
   info("Remove the runtime from the list of remote runtimes");
   mocks.removeRuntime(id);
 
   info("Emit 'closed' on the client and wait for the sidebar item to disappear");
   client._eventEmitter.emit("closed");
   if (type === "usb") {
--- a/devtools/client/aboutdebugging-new/test/browser/mocks/helper-adb-mock.js
+++ b/devtools/client/aboutdebugging-new/test/browser/mocks/helper-adb-mock.js
@@ -50,16 +50,20 @@ function createAdbMock() {
   adbMock.updateRuntimes = function() {
     console.log("MOCKED METHOD updateRuntimes");
   };
 
   adbMock.unregisterListener = function(listener) {
     console.log("MOCKED METHOD unregisterListener");
   };
 
+  adbMock.once = function() {
+    console.log("MOCKED METHOD once");
+  };
+
   return { adb: adbMock };
 }
 /* exported createAdbMock */
 
 /**
  * The adb module allows to observe runtime updates. To simulate this behaviour
  * the easiest is to use an EventEmitter-decorated object that can accept listeners and
  * can emit events from the test.
--- a/devtools/client/accessibility/actions/audit.js
+++ b/devtools/client/accessibility/actions/audit.js
@@ -8,11 +8,16 @@ const { AUDIT, AUDITING, FILTER_TOGGLE }
 
 exports.filterToggle = filter =>
   dispatch => dispatch({ filter, type: FILTER_TOGGLE });
 
 exports.auditing = filter =>
   dispatch => dispatch({ auditing: filter, type: AUDITING });
 
 exports.audit = (walker, filter) =>
-  dispatch => walker.audit()
-    .then(response => dispatch({ type: AUDIT, response }))
-    .catch(error => dispatch({ type: AUDIT, error }));
+  dispatch => {
+    const onAuditEvent = walker.once("audit-event");
+    walker.startAudit();
+    return onAuditEvent
+      .then(({ ancestries: response, error }) =>
+        dispatch({ type: AUDIT, error, response }))
+      .catch(error => dispatch({ type: AUDIT, error }));
+  };
--- a/devtools/client/debugger/src/components/Editor/EmptyLines.js
+++ b/devtools/client/debugger/src/components/Editor/EmptyLines.js
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
 
 // @flow
 
 import { connect } from "../../utils/connect";
 import { Component } from "react";
-import { getSelectedSource, getBreakableLines } from "../../selectors";
+import { getSelectedSource, getSelectedBreakableLines } from "../../selectors";
 import type { Source } from "../../types";
 import { fromEditorLine } from "../../utils/editor";
 
 type Props = {
   selectedSource: Source,
   editor: Object,
   breakableLines: Set<number>
 };
@@ -59,19 +59,17 @@ class EmptyLines extends Component<Props
   }
 }
 
 const mapStateToProps = state => {
   const selectedSource = getSelectedSource(state);
   if (!selectedSource) {
     throw new Error("no selectedSource");
   }
-  const breakableLines = new Set(
-    getBreakableLines(state, selectedSource.id) || []
-  );
+  const breakableLines = getSelectedBreakableLines(state);
 
   return {
     selectedSource,
     breakableLines
   };
 };
 
 export default connect(mapStateToProps)(EmptyLines);
--- a/devtools/client/debugger/src/reducers/sources.js
+++ b/devtools/client/debugger/src/reducers/sources.js
@@ -876,9 +876,17 @@ export function getBreakpointPositionsFo
 export function getBreakableLines(state: OuterState, sourceId: string) {
   if (!sourceId) {
     return null;
   }
 
   return state.sources.breakableLines[sourceId];
 }
 
+export const getSelectedBreakableLines: Selector<Set<number>> = createSelector(
+  state => {
+    const sourceId = getSelectedSourceId(state);
+    return sourceId && state.sources.breakableLines[sourceId];
+  },
+  breakableLines => new Set(breakableLines || [])
+);
+
 export default update;
--- a/devtools/client/locales/en-US/aboutdebugging.ftl
+++ b/devtools/client/locales/en-US/aboutdebugging.ftl
@@ -198,17 +198,17 @@ about-debugging-runtime-shared-workers =
 about-debugging-runtime-other-workers =
   .name = Other Workers
 # Title of the processes category.
 about-debugging-runtime-processes =
   .name = Processes
 
 # Label of the button opening the performance profiler panel in runtime pages for remote
 # runtimes.
-about-debugging-runtime-profile-button = Profile Runtime
+about-debugging-runtime-profile-button2 = Profile performance
 
 # This string is displayed in the runtime page if the current configuration of the
 # target runtime is incompatible with service workers. "Learn more" points to MDN.
 # https://developer.mozilla.org/en-US/docs/Tools/about%3Adebugging#Service_workers_not_compatible
 about-debugging-runtime-service-workers-not-compatible = Your browser configuration is not compatible with Service Workers. <a>Learn more</a>
 
 # This string is displayed in the runtime page if the remote browser version is too old.
 # "Troubleshooting" link points to https://developer.mozilla.org/docs/Tools/WebIDE/Troubleshooting
@@ -245,17 +245,17 @@ about-debugging-runtime-disconnect-butto
 # "devtools.debugger.prompt-connection" is false on the target runtime.
 about-debugging-connection-prompt-enable-button = Enable connection prompt
 
 # Text of the connection prompt button displayed in Runtime pages, when the preference
 # "devtools.debugger.prompt-connection" is true on the target runtime.
 about-debugging-connection-prompt-disable-button = Disable connection prompt
 
 # Title of a modal dialog displayed on remote runtime pages after clicking on the Profile Runtime button.
-about-debugging-profiler-dialog-title = Performance Profiler
+about-debugging-profiler-dialog-title2 = Profiler
 
 # Label of a checkbox displayed in the runtime page for "This Firefox".
 # This checkbox will toggle preferences that enable local addon debugging.
 # The "Learn more" link points to MDN.
 # https://developer.mozilla.org/docs/Tools/about:debugging#Enabling_add-on_debugging
 about-debugging-extension-debug-setting-label = Enable extension debugging. <a>Learn more</a>
 
 # Clicking on the header of a debug target category will expand or collapse the debug
--- a/devtools/server/actors/accessibility/walker.js
+++ b/devtools/server/actors/accessibility/walker.js
@@ -412,16 +412,37 @@ const AccessibleWalkerActor = ActorClass
             check.score === accessibility.SCORES.FAIL)) {
         ancestries.push(this.getAncestry(acc));
       }
     }
 
     return Promise.all(ancestries);
   },
 
+  /**
+   * Start accessibility audit. The result of this function will not be an audit
+   * report. Instead, an "audit-event" event will be fired when the audit is
+   * completed or fails.
+   */
+  startAudit() {
+    // Audit is already running, wait for the "audit-event" event.
+    if (this._auditing) {
+      return;
+    }
+
+    this._auditing = this.audit()
+      // We do not want to block on audit request, instead fire "audit-event"
+      // event when internal audit is finished or failed.
+      .then(ancestries => this.emit("audit-event", { ancestries }))
+      .catch(() => this.emit("audit-event", { error: true }))
+      .finally(() => {
+        this._auditing = null;
+      });
+  },
+
   onHighlighterEvent: function(data) {
     this.emit("highlighter-event", data);
   },
 
   /**
    * Accessible event observer function.
    *
    * @param {Ci.nsIAccessibleEvent} subject
--- a/devtools/server/tests/browser/browser_accessibility_walker_audit.js
+++ b/devtools/server/tests/browser/browser_accessibility_walker_audit.js
@@ -14,67 +14,72 @@ add_task(async function() {
     role: "document",
     childCount: 2,
     checks: {
       "CONTRAST": null,
     },
   }, {
     name: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " +
           "eiusmod tempor incididunt ut labore et dolore magna aliqua.",
-    role: "heading",
+    role: "paragraph",
     childCount: 1,
     checks: {
       "CONTRAST": null,
     },
   }, {
     name: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " +
            "eiusmod tempor incididunt ut labore et dolore magna aliqua.",
     role: "text leaf",
     childCount: 0,
     checks: {
       "CONTRAST": {
-        "value": 21,
-        "color": [0, 0, 0, 1],
+        "value": 4.00,
+        "color": [255, 0, 0, 1],
         "backgroundColor": [255, 255, 255, 1],
-        "isLargeText": true,
+        "isLargeText": false,
+        "score": "fail",
       },
     },
   }, {
     name: "",
     role: "paragraph",
     childCount: 1,
     checks: {
       "CONTRAST": null,
     },
   }, {
     name: "Accessible Paragraph",
     role: "text leaf",
     childCount: 0,
     checks: {
       "CONTRAST": {
-        "value": 21,
-        "color": [0, 0, 0, 1],
+        "value": 4.00,
+        "color": [255, 0, 0, 1],
         "backgroundColor": [255, 255, 255, 1],
         "isLargeText": false,
+        "score": "fail",
       },
     },
   }];
 
   function findAccessible(name, role) {
     return accessibles.find(accessible =>
       accessible.name === name && accessible.role === role);
   }
 
   const a11yWalker = await accessibility.getWalker();
   ok(a11yWalker, "The AccessibleWalkerFront was returned");
   await accessibility.enable();
 
   info("Checking AccessibleWalker audit functionality");
-  const ancestries = await a11yWalker.audit();
+  const auditEvent = a11yWalker.once("audit-event");
+  a11yWalker.startAudit();
+  const { ancestries } = await auditEvent;
 
+  is(ancestries.length, 2, "The size of ancestries is correct");
   for (const ancestry of ancestries) {
     for (const { accessible, children } of ancestry) {
       checkA11yFront(accessible,
                      findAccessible(accessibles.name, accessibles.role));
       for (const child of children) {
         checkA11yFront(child,
                        findAccessible(child.name, child.role));
       }
--- a/devtools/server/tests/browser/doc_accessibility_audit.html
+++ b/devtools/server/tests/browser/doc_accessibility_audit.html
@@ -1,10 +1,10 @@
 <!DOCTYPE HTML>
 <html>
   <head>
     <meta charset="utf-8">
   </head>
-<body>
-  <h1 id="h1">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</h1>
-  <p id="p">Accessible Paragraph</p>
+<body style="color: red;">
+  <p id="p1">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
+  <p id="p2">Accessible Paragraph</p>
 </body>
 </html>
--- a/devtools/server/tests/browser/head.js
+++ b/devtools/server/tests/browser/head.js
@@ -266,17 +266,27 @@ async function emitA11yEvent(emitter, na
  */
 function checkA11yFront(front, expected, expectedFront) {
   ok(front, "The accessibility front is created");
 
   if (expectedFront) {
     is(front, expectedFront, "Matching accessibility front");
   }
 
+  // Clone the front so we could modify some values for comparison.
+  front = Object.assign(front);
   for (const key in expected) {
+    if (key === "checks") {
+      const { CONTRAST } = front[key];
+      // Contrast values are rounded to two digits after the decimal point.
+      if (CONTRAST && CONTRAST.value) {
+        CONTRAST.value = parseFloat(CONTRAST.value.toFixed(2));
+      }
+    }
+
     if (["actions", "states", "attributes", "checks"].includes(key)) {
       SimpleTest.isDeeply(front[key], expected[key],
         `Accessible Front has correct ${key}`);
     } else {
       is(front[key], expected[key], `accessibility front has correct ${key}`);
     }
   }
 }
--- a/devtools/shared/adb/adb.js
+++ b/devtools/shared/adb/adb.js
@@ -92,22 +92,31 @@ class Adb extends EventEmitter {
     this._runtimes = [];
     this.updateRuntimes();
   }
 
   _shouldTrack() {
     return adbAddon.status === "installed" && this._listeners.size > 0;
   }
 
-  _updateAdbProcess() {
+  /**
+   * This method will emit "runtime-list-ready" to notify the consumer that the list of
+   * runtimes is ready to be retrieved.
+   */
+  async _updateAdbProcess() {
     if (!this._isTrackingDevices && this._shouldTrack()) {
+      const onRuntimesUpdated = this.once("runtime-list-updated");
       this._startTracking();
+      // If we are starting to track runtimes, the list of runtimes will only be ready
+      // once the first "runtime-list-updated" event has been processed.
+      await onRuntimesUpdated;
     } else if (this._isTrackingDevices && !this._shouldTrack()) {
       this._stopTracking();
     }
+    this.emit("runtime-list-ready");
   }
 
   async _onDeviceConnected(deviceId) {
     const adbDevice = new AdbDevice(deviceId);
     await adbDevice.initialize();
     this._devices.set(deviceId, adbDevice);
     this.updateRuntimes();
   }
--- a/devtools/shared/specs/accessibility.js
+++ b/devtools/shared/specs/accessibility.js
@@ -16,16 +16,28 @@ types.addActorType("accessible");
 types.addDictType("accessibleWithChildren", {
   // Accessible
   accessible: "accessible",
   // Accessible's children
   children: "array:accessible",
 });
 
 /**
+ * Data passed via "audit-event" to the client. It may include a list of
+ * ancestries for accessible actors that have failing accessibility checks or
+ * an error flag.
+ */
+types.addDictType("auditEventData", {
+  // List of ancestries (array:accessibleWithChildren)
+  ancestries: "array:array:accessibleWithChildren",
+  // True if the audit failed.
+  error: "boolean",
+});
+
+/**
  * Accessible relation object described by its type that also includes relation targets.
  */
 types.addDictType("accessibleRelation", {
   // Accessible relation type
   type: "string",
   // Accessible relation's targets
   targets: "array:accessible",
 });
@@ -142,16 +154,20 @@ const accessibleWalkerSpec = generateAct
     },
     "picker-accessible-canceled": {
       type: "pickerAccessibleCanceled",
     },
     "highlighter-event": {
       type: "highlighter-event",
       data: Arg(0, "json"),
     },
+    "audit-event": {
+      type: "audit-event",
+      audit: Arg(0, "auditEventData"),
+    },
   },
 
   methods: {
     children: {
       request: {},
       response: {
         children: RetVal("array:accessible"),
       },
@@ -163,22 +179,17 @@ const accessibleWalkerSpec = generateAct
       },
     },
     getAncestry: {
       request: { accessible: Arg(0, "accessible") },
       response: {
         ancestry: RetVal("array:accessibleWithChildren"),
       },
     },
-    audit: {
-      request: {},
-      response: {
-        audit: RetVal("array:array:accessibleWithChildren"),
-      },
-    },
+    startAudit: {},
     highlightAccessible: {
       request: {
         accessible: Arg(0, "accessible"),
         options: Arg(1, "nullable:json"),
       },
       response: {
         value: RetVal("nullable:boolean"),
       },
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -9954,17 +9954,17 @@ nsresult nsDocShell::DoURILoad(nsDocShel
     if (upgradeInsecureRequests) {
       // only upgrade if the navigation is same origin
       nsCOMPtr<nsIPrincipal> resultPrincipal;
       rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
           channel, getter_AddRefs(resultPrincipal));
       NS_ENSURE_SUCCESS(rv, rv);
       if (IsConsideredSameOriginForUIR(aLoadState->TriggeringPrincipal(),
                                        resultPrincipal)) {
-        static_cast<LoadInfo*>(loadInfo.get())->SetUpgradeInsecureRequests();
+        loadInfo->SetUpgradeInsecureRequests();
       }
     }
   }
 
   nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
       do_QueryInterface(channel);
   if (appCacheChannel) {
     // Any document load should not inherit application cache.
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -3144,34 +3144,32 @@ void Document::SetPrincipals(nsIPrincipa
   // check. It's not unsafe to have a document which has a null principal in the
   // same docgroup as another document, so this should not be a problem.
   if (aNewPrincipal) {
     GetDocGroup();
   }
 #endif
 }
 
-mozilla::dom::DocGroup* Document::GetDocGroup() const {
 #ifdef DEBUG
+void Document::AssertDocGroupMatchesKey() const {
   // Sanity check that we have an up-to-date and accurate docgroup
   if (mDocGroup) {
     nsAutoCString docGroupKey;
 
     // GetKey() can fail, e.g. after the TLD service has shut down.
     nsresult rv = mozilla::dom::DocGroup::GetKey(NodePrincipal(), docGroupKey);
     if (NS_SUCCEEDED(rv)) {
       MOZ_ASSERT(mDocGroup->MatchesKey(docGroupKey));
     }
     // XXX: Check that the TabGroup is correct as well!
   }
+}
 #endif
 
-  return mDocGroup;
-}
-
 nsresult Document::Dispatch(TaskCategory aCategory,
                             already_AddRefed<nsIRunnable>&& aRunnable) {
   // Note that this method may be called off the main thread.
   if (mDocGroup) {
     return mDocGroup->Dispatch(aCategory, std::move(aRunnable));
   }
   return DispatcherTrait::Dispatch(aCategory, std::move(aRunnable));
 }
@@ -3673,17 +3671,17 @@ Element* Document::GetActiveElement() {
 Element* Document::GetCurrentScript() {
   nsCOMPtr<Element> el(do_QueryInterface(ScriptLoader()->GetCurrentScript()));
   return el;
 }
 
 void Document::ReleaseCapture() const {
   // only release the capture if the caller can access it. This prevents a
   // page from stopping a scrollbar grab for example.
-  nsCOMPtr<nsINode> node = nsIPresShell::GetCapturingContent();
+  nsCOMPtr<nsINode> node = PresShell::GetCapturingContent();
   if (node && nsContentUtils::CanCallerAccess(node)) {
     PresShell::ReleaseCapturingContent();
   }
 }
 
 already_AddRefed<nsIURI> Document::GetBaseURI(bool aTryUseXHRDocBaseURI) const {
   nsCOMPtr<nsIURI> uri;
   if (aTryUseXHRDocBaseURI && mChromeXHRDocBaseURI) {
@@ -7329,17 +7327,18 @@ void Document::FlushPendingNotifications
   // container is reflowed if its size was changed.  But if it's not safe to
   // flush ourselves, then don't flush the parent, since that can cause things
   // like resizes of our frame's widget, which we can't handle while flushing
   // is unsafe.
   // Since media queries mean that a size change of our container can
   // affect style, we need to promote a style flush on ourself to a
   // layout flush on our parent, since we need our container to be the
   // correct size to determine the correct style.
-  if (mParentDocument && IsSafeToFlush()) {
+  if (StyleOrLayoutObservablyDependsOnParentDocumentLayout() &&
+      IsSafeToFlush()) {
     mozilla::ChangesToFlush parentFlush = aFlush;
     if (flushType >= FlushType::Style) {
       parentFlush.mFlushType = std::max(FlushType::Layout, flushType);
     }
     mParentDocument->FlushPendingNotifications(parentFlush);
   }
 
   if (RefPtr<PresShell> presShell = GetPresShell()) {
--- a/dom/base/Document.h
+++ b/dom/base/Document.h
@@ -3491,17 +3491,40 @@ class Document : public nsINode,
 
   bool HasScriptsBlockedBySandbox();
 
   bool InlineScriptAllowedByCSP();
 
   void ReportHasScrollLinkedEffect();
   bool HasScrollLinkedEffect() const { return mHasScrollLinkedEffect; }
 
-  DocGroup* GetDocGroup() const;
+#ifdef DEBUG
+  void AssertDocGroupMatchesKey() const;
+#endif
+
+  DocGroup* GetDocGroup() const {
+#ifdef DEBUG
+    AssertDocGroupMatchesKey();
+#endif
+    return mDocGroup;
+  }
+
+  /**
+   * If we're a sub-document, the parent document's layout can affect our style
+   * and layout (due to the viewport size, viewport units, media queries...).
+   *
+   * This function returns true if our parent document and our child document
+   * can observe each other. If they cannot, then we don't need to synchronously
+   * update the parent document layout every time the child document may need
+   * up-to-date layout information.
+   */
+  bool StyleOrLayoutObservablyDependsOnParentDocumentLayout() const {
+    return GetParentDocument() &&
+           GetDocGroup() == GetParentDocument()->GetDocGroup();
+  }
 
   void AddIntersectionObserver(DOMIntersectionObserver* aObserver) {
     MOZ_ASSERT(!mIntersectionObservers.Contains(aObserver),
                "Intersection observer already in the list");
     mIntersectionObservers.PutEntry(aObserver);
   }
 
   void RemoveIntersectionObserver(DOMIntersectionObserver* aObserver) {
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -778,20 +778,18 @@ void Element::ScrollIntoView(const Scrol
   } else if (aOptions.mBehavior == ScrollBehavior::Auto) {
     scrollFlags |= ScrollFlags::ScrollSmoothAuto;
   }
   if (StaticPrefs::layout_css_scroll_snap_v1_enabled()) {
     scrollFlags |= ScrollFlags::ScrollSnap;
   }
 
   presShell->ScrollContentIntoView(
-      this,
-      nsIPresShell::ScrollAxis(whereToScrollVertically, WhenToScroll::Always),
-      nsIPresShell::ScrollAxis(whereToScrollHorizontally, WhenToScroll::Always),
-      scrollFlags);
+      this, ScrollAxis(whereToScrollVertically, WhenToScroll::Always),
+      ScrollAxis(whereToScrollHorizontally, WhenToScroll::Always), scrollFlags);
 }
 
 void Element::Scroll(const CSSIntPoint& aScroll,
                      const ScrollOptions& aOptions) {
   nsIScrollableFrame* sf = GetScrollFrame();
   if (sf) {
     ScrollMode scrollMode = ScrollMode::Instant;
     if (aOptions.mBehavior == ScrollBehavior::Smooth) {
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -1170,34 +1170,34 @@ class Element : public FragmentOrElement
       return true;
     }
     return false;
   }
   void SetCapture(bool aRetargetToElement) {
     // If there is already an active capture, ignore this request. This would
     // occur if a splitter, frame resizer, etc had already captured and we don't
     // want to override those.
-    if (!nsIPresShell::GetCapturingContent()) {
+    if (!PresShell::GetCapturingContent()) {
       PresShell::SetCapturingContent(
           this, CaptureFlags::PreventDragStart |
                     (aRetargetToElement ? CaptureFlags::RetargetToElement
                                         : CaptureFlags::None));
     }
   }
 
   void SetCaptureAlways(bool aRetargetToElement) {
     PresShell::SetCapturingContent(
         this, CaptureFlags::PreventDragStart |
                   CaptureFlags::IgnoreAllowedState |
                   (aRetargetToElement ? CaptureFlags::RetargetToElement
                                       : CaptureFlags::None));
   }
 
   void ReleaseCapture() {
-    if (nsIPresShell::GetCapturingContent() == this) {
+    if (PresShell::GetCapturingContent() == this) {
       PresShell::ReleaseCapturingContent();
     }
   }
 
   already_AddRefed<Promise> RequestFullscreen(CallerType, ErrorResult&);
   void RequestPointerLock(CallerType aCallerType);
   Attr* GetAttributeNode(const nsAString& aName);
   already_AddRefed<Attr> SetAttributeNode(Attr& aNewAttr, ErrorResult& aError);
--- a/dom/base/Selection.cpp
+++ b/dom/base/Selection.cpp
@@ -158,17 +158,17 @@ class nsAutoScrollTimer final : public n
   // aPoint is relative to aPresContext's root frame
   nsresult Start(nsPresContext* aPresContext, nsPoint& aPoint) {
     mPoint = aPoint;
 
     // Store the presentation context. The timer will be
     // stopped by the selection if the prescontext is destroyed.
     mPresContext = aPresContext;
 
-    mContent = nsIPresShell::GetCapturingContent();
+    mContent = PresShell::GetCapturingContent();
 
     if (!mTimer) {
       mTimer = NS_NewTimer(
           mPresContext->Document()->EventTargetFor(TaskCategory::Other));
 
       if (!mTimer) {
         return NS_ERROR_OUT_OF_MEMORY;
       }
@@ -1872,18 +1872,18 @@ nsresult Selection::DoAutoScroll(nsIFram
   // Get the point relative to the root most frame because the scroll we are
   // about to do will change the coordinates of aFrame.
   nsPoint globalPoint = aPoint + aFrame->GetOffsetToCrossDoc(rootmostFrame);
 
   bool done = false;
   bool didScroll;
   while (true) {
     didScroll = presShell->ScrollFrameRectIntoView(
-        aFrame, nsRect(aPoint, nsSize(0, 0)), nsIPresShell::ScrollAxis(),
-        nsIPresShell::ScrollAxis(), ScrollFlags::IgnoreMarginAndPadding);
+        aFrame, nsRect(aPoint, nsSize(0, 0)), ScrollAxis(), ScrollAxis(),
+        ScrollFlags::IgnoreMarginAndPadding);
     if (!weakFrame || !weakRootFrame) {
       return NS_OK;
     }
     if (!didScroll && !done) {
       // If aPoint is at the screen edge then try to scroll anyway, once.
       RefPtr<nsDeviceContext> dx =
           presShell->GetViewManager()->GetDeviceContext();
       nsRect screen;
@@ -3001,19 +3001,20 @@ Selection::ScrollSelectionIntoViewEvent:
   Selection* sel = mSelection;  // workaround to satisfy static analysis
   RefPtr<Selection> kungFuDeathGrip(sel);
   mSelection->mScrollEvent.Forget();
   mSelection->ScrollIntoView(mRegion, mVerticalScroll, mHorizontalScroll,
                              mFlags | flags);
   return NS_OK;
 }
 
-nsresult Selection::PostScrollSelectionIntoViewEvent(
-    SelectionRegion aRegion, int32_t aFlags, nsIPresShell::ScrollAxis aVertical,
-    nsIPresShell::ScrollAxis aHorizontal) {
+nsresult Selection::PostScrollSelectionIntoViewEvent(SelectionRegion aRegion,
+                                                     int32_t aFlags,
+                                                     ScrollAxis aVertical,
+                                                     ScrollAxis aHorizontal) {
   // If we've already posted an event, revoke it and place a new one at the
   // end of the queue to make sure that any new pending reflow events are
   // processed before we scroll. This will insure that we scroll to the
   // correct place on screen.
   mScrollEvent.Revoke();
   nsPresContext* presContext = GetPresContext();
   NS_ENSURE_STATE(presContext);
   nsRefreshDriver* refreshDriver = presContext->RefreshDriver();
@@ -3024,26 +3025,25 @@ nsresult Selection::PostScrollSelectionI
   refreshDriver->AddEarlyRunner(mScrollEvent.get());
   return NS_OK;
 }
 
 void Selection::ScrollIntoView(int16_t aRegion, bool aIsSynchronous,
                                WhereToScroll aVPercent, WhereToScroll aHPercent,
                                ErrorResult& aRv) {
   int32_t flags = aIsSynchronous ? Selection::SCROLL_SYNCHRONOUS : 0;
-  nsresult rv = ScrollIntoView(aRegion, nsIPresShell::ScrollAxis(aVPercent),
-                               nsIPresShell::ScrollAxis(aHPercent), flags);
+  nsresult rv = ScrollIntoView(aRegion, ScrollAxis(aVPercent),
+                               ScrollAxis(aHPercent), flags);
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
   }
 }
 
 nsresult Selection::ScrollIntoView(SelectionRegion aRegion,
-                                   nsIPresShell::ScrollAxis aVertical,
-                                   nsIPresShell::ScrollAxis aHorizontal,
+                                   ScrollAxis aVertical, ScrollAxis aHorizontal,
                                    int32_t aFlags) {
   if (!mFrameSelection) {
     return NS_OK;
   }
 
   PresShell* presShell = mFrameSelection->GetPresShell();
   if (!presShell || !presShell->GetDocument()) {
     return NS_OK;
--- a/dom/base/Selection.h
+++ b/dom/base/Selection.h
@@ -6,17 +6,17 @@
 
 #ifndef mozilla_Selection_h__
 #define mozilla_Selection_h__
 
 #include "nsIWeakReference.h"
 
 #include "mozilla/AccessibleCaretEventHub.h"
 #include "mozilla/AutoRestore.h"
-#include "mozilla/PresShell.h"  // For ScrollAxis
+#include "mozilla/PresShell.h"
 #include "mozilla/RangeBoundary.h"
 #include "mozilla/SelectionChangeEventDispatcher.h"
 #include "mozilla/TextRange.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/WeakPtr.h"
 #include "nsDirection.h"
 #include "nsISelectionController.h"
 #include "nsISelectionListener.h"
@@ -131,37 +131,37 @@ class Selection final : public nsSupport
   // region rects.
   nsIFrame* GetSelectionAnchorGeometry(SelectionRegion aRegion, nsRect* aRect);
   // Returns the position of the region (SELECTION_ANCHOR_REGION or
   // SELECTION_FOCUS_REGION only), and frame that that position is relative to.
   // The 'position' is a zero-width rectangle.
   nsIFrame* GetSelectionEndPointGeometry(SelectionRegion aRegion,
                                          nsRect* aRect);
 
-  nsresult PostScrollSelectionIntoViewEvent(
-      SelectionRegion aRegion, int32_t aFlags,
-      nsIPresShell::ScrollAxis aVertical, nsIPresShell::ScrollAxis aHorizontal);
+  nsresult PostScrollSelectionIntoViewEvent(SelectionRegion aRegion,
+                                            int32_t aFlags,
+                                            ScrollAxis aVertical,
+                                            ScrollAxis aHorizontal);
   enum {
     SCROLL_SYNCHRONOUS = 1 << 1,
     SCROLL_FIRST_ANCESTOR_ONLY = 1 << 2,
     SCROLL_DO_FLUSH =
         1 << 3,  // only matters if SCROLL_SYNCHRONOUS is passed too
     SCROLL_OVERFLOW_HIDDEN = 1 << 5,
     SCROLL_FOR_CARET_MOVE = 1 << 6
   };
   // If aFlags doesn't contain SCROLL_SYNCHRONOUS, then we'll flush when
   // the scroll event fires so we make sure to scroll to the right place.
   // Otherwise, if SCROLL_DO_FLUSH is also in aFlags, then this method will
   // flush layout and you MUST hold a strong ref on 'this' for the duration
   // of this call.  This might destroy arbitrary layout objects.
-  nsresult ScrollIntoView(
-      SelectionRegion aRegion,
-      nsIPresShell::ScrollAxis aVertical = nsIPresShell::ScrollAxis(),
-      nsIPresShell::ScrollAxis aHorizontal = nsIPresShell::ScrollAxis(),
-      int32_t aFlags = 0);
+  nsresult ScrollIntoView(SelectionRegion aRegion,
+                          ScrollAxis aVertical = ScrollAxis(),
+                          ScrollAxis aHorizontal = ScrollAxis(),
+                          int32_t aFlags = 0);
   nsresult SubtractRange(RangeData* aRange, nsRange* aSubtract,
                          nsTArray<RangeData>* aOutput);
   /**
    * AddItem adds aRange to this Selection.  If mUserInitiated is true,
    * then aRange is first scanned for -moz-user-select:none nodes and split up
    * into multiple ranges to exclude those before adding the resulting ranges
    * to this Selection.
    */
@@ -659,34 +659,33 @@ class Selection final : public nsSupport
   friend struct mozilla::AutoPrepareFocusRange;
   class ScrollSelectionIntoViewEvent;
   friend class ScrollSelectionIntoViewEvent;
 
   class ScrollSelectionIntoViewEvent : public Runnable {
    public:
     NS_DECL_NSIRUNNABLE
     ScrollSelectionIntoViewEvent(Selection* aSelection, SelectionRegion aRegion,
-                                 nsIPresShell::ScrollAxis aVertical,
-                                 nsIPresShell::ScrollAxis aHorizontal,
+                                 ScrollAxis aVertical, ScrollAxis aHorizontal,
                                  int32_t aFlags)
         : Runnable("dom::Selection::ScrollSelectionIntoViewEvent"),
           mSelection(aSelection),
           mRegion(aRegion),
           mVerticalScroll(aVertical),
           mHorizontalScroll(aHorizontal),
           mFlags(aFlags) {
       NS_ASSERTION(aSelection, "null parameter");
     }
     void Revoke() { mSelection = nullptr; }
 
    private:
     Selection* mSelection;
     SelectionRegion mRegion;
-    nsIPresShell::ScrollAxis mVerticalScroll;
-    nsIPresShell::ScrollAxis mHorizontalScroll;
+    ScrollAxis mVerticalScroll;
+    ScrollAxis mHorizontalScroll;
     int32_t mFlags;
   };
 
   /**
    * Set mAnchorFocusRange to mRanges[aIndex] if aIndex is a valid index.
    * Set mAnchorFocusRange to nullptr if aIndex is negative.
    * Otherwise, i.e., if aIndex is positive but out of bounds of mRanges, do
    * nothing.
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -2511,19 +2511,18 @@ nsDOMWindowUtils::ZoomToFocusedInput() {
       flags |= layers::ONLY_ZOOM_TO_DEFAULT_SCALE;
     }
 
     // The content may be inside a scrollable subframe inside a non-scrollable
     // root content document. In this scenario, we want to ensure that the
     // main-thread side knows to scroll the content into view before we get
     // the bounding content rect and ask APZ to adjust the visual viewport.
     presShell->ScrollContentIntoView(
-        content,
-        nsIPresShell::ScrollAxis(kScrollMinimum, WhenToScroll::IfNotVisible),
-        nsIPresShell::ScrollAxis(kScrollMinimum, WhenToScroll::IfNotVisible),
+        content, ScrollAxis(kScrollMinimum, WhenToScroll::IfNotVisible),
+        ScrollAxis(kScrollMinimum, WhenToScroll::IfNotVisible),
         ScrollFlags::ScrollOverflowHidden);
 
     CSSRect bounds =
         nsLayoutUtils::GetBoundingContentRect(content, rootScrollFrame);
     if (bounds.IsEmpty()) {
       // Do not zoom on empty bounds. Bail out.
       return NS_OK;
     }
--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -1715,16 +1715,30 @@ bool nsFocusManager::Blur(nsPIDOMWindowO
     UpdateCaret(false, true, nullptr);
   }
 
   if (clearFirstBlurEvent) mFirstBlurEvent = nullptr;
 
   return result;
 }
 
+void nsFocusManager::ActivateRemoteFrameIfNeeded(Element& aElement) {
+  MOZ_DIAGNOSTIC_ASSERT(mFocusedElement == &aElement);
+  if (BrowserParent* remote = BrowserParent::GetFrom(&aElement)) {
+    remote->Activate();
+    LOGFOCUS(("Remote browser activated %p", remote));
+  }
+
+  // Same as above but for out-of-process iframes
+  if (BrowserBridgeChild* bbc = BrowserBridgeChild::GetFrom(&aElement)) {
+    bbc->Activate();
+    LOGFOCUS(("Out-of-process iframe activated %p", bbc));
+  }
+}
+
 void nsFocusManager::Focus(nsPIDOMWindowOuter* aWindow, Element* aElement,
                            uint32_t aFlags, bool aIsNewDocument,
                            bool aFocusChanged, bool aWindowRaised,
                            bool aAdjustWidgets, nsIContent* aContentLostFocus) {
   LOGFOCUS(("<<Focus begin>>"));
 
   if (!aWindow) return;
 
@@ -1857,26 +1871,17 @@ void nsFocusManager::Focus(nsPIDOMWindow
       // that we might no longer be in the same document, due to the events we
       // fired above when aIsNewDocument.
       if (presShell->GetDocument() == aElement->GetComposedDoc()) {
         if (aAdjustWidgets && objectFrameWidget && !sTestMode)
           objectFrameWidget->SetFocus(false);
 
         // if the object being focused is a remote browser, activate remote
         // content
-        if (BrowserParent* remote = BrowserParent::GetFrom(aElement)) {
-          remote->Activate();
-          LOGFOCUS(("Remote browser activated %p", remote));
-        }
-
-        // Same as above but for out-of-process iframes
-        if (BrowserBridgeChild* bbc = BrowserBridgeChild::GetFrom(aElement)) {
-          bbc->Activate();
-          LOGFOCUS(("Out-of-process iframe activated %p", bbc));
-        }
+        ActivateRemoteFrameIfNeeded(*aElement);
       }
 
       IMEStateManager::OnChangeFocus(presContext, aElement,
                                      GetFocusMoveActionCause(aFlags));
 
       // as long as this focus wasn't because a window was raised, update the
       // commands
       // XXXndeakin P2 someone could adjust the focus during the update
@@ -2125,20 +2130,18 @@ void nsFocusManager::ScrollIntoView(Pres
                                     uint32_t aFlags) {
   // if the noscroll flag isn't set, scroll the newly focused element into view
   if (!(aFlags & FLAG_NOSCROLL)) {
     ScrollFlags scrollFlags = ScrollFlags::ScrollOverflowHidden;
     if (!(aFlags & FLAG_BYELEMENTFOCUS)) {
       scrollFlags |= ScrollFlags::IgnoreMarginAndPadding;
     }
     aPresShell->ScrollContentIntoView(
-        aContent,
-        nsIPresShell::ScrollAxis(kScrollMinimum, WhenToScroll::IfNotVisible),
-        nsIPresShell::ScrollAxis(kScrollMinimum, WhenToScroll::IfNotVisible),
-        scrollFlags);
+        aContent, ScrollAxis(kScrollMinimum, WhenToScroll::IfNotVisible),
+        ScrollAxis(kScrollMinimum, WhenToScroll::IfNotVisible), scrollFlags);
   }
 }
 
 void nsFocusManager::RaiseWindow(nsPIDOMWindowOuter* aWindow) {
   // don't raise windows that are already raised or are in the process of
   // being lowered
   if (!aWindow || aWindow == mActiveWindow || aWindow == mWindowBeingLowered)
     return;
--- a/dom/base/nsFocusManager.h
+++ b/dom/base/nsFocusManager.h
@@ -164,16 +164,22 @@ class nsFocusManager final : public nsIF
    * navigation is not done to parent documents and iteration returns to the
    * beginning (or end) of the starting document.
    */
   nsresult DetermineElementToMoveFocus(nsPIDOMWindowOuter* aWindow,
                                        nsIContent* aStart, int32_t aType,
                                        bool aNoParentTraversal,
                                        nsIContent** aNextContent);
 
+  /**
+   * Given an element, which must be the focused element, activate the remote
+   * frame it embeds, if any.
+   */
+  void ActivateRemoteFrameIfNeeded(mozilla::dom::Element&);
+
   static uint32_t FocusOptionsToFocusManagerFlags(
       const mozilla::dom::FocusOptions& aOptions);
 
   /**
    * Returns the content node that focus will be redirected to if aContent was
    * focused. This is used for the special case of certain XUL elements such
    * as textboxes or input number which redirect focus to an anonymous child.
    *
--- a/dom/base/nsFrameLoaderOwner.cpp
+++ b/dom/base/nsFrameLoaderOwner.cpp
@@ -1,18 +1,22 @@
 /* -*- 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 "nsFrameLoaderOwner.h"
 #include "nsFrameLoader.h"
+#include "nsFocusManager.h"
+#include "nsSubDocumentFrame.h"
+#include "nsQueryObject.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/dom/BrowsingContext.h"
 #include "mozilla/dom/FrameLoaderBinding.h"
-#include "mozilla/dom/BrowsingContext.h"
 
 already_AddRefed<nsFrameLoader> nsFrameLoaderOwner::GetFrameLoader() {
   return do_AddRef(mFrameLoader);
 }
 
 void nsFrameLoaderOwner::SetFrameLoader(nsFrameLoader* aNewFrameLoader) {
   mFrameLoader = aNewFrameLoader;
 }
@@ -46,21 +50,23 @@ void nsFrameLoaderOwner::ChangeRemotenes
   if (aOptions.mPendingSwitchID.WasPassed()) {
     mFrameLoader->ResumeLoad(aOptions.mPendingSwitchID.Value());
   } else {
     mFrameLoader->LoadFrame(false);
   }
 
   // Now that we've got a new FrameLoader, we need to reset our
   // nsSubDocumentFrame to use the new FrameLoader.
-  nsIFrame* ourFrame = owner->GetPrimaryFrame();
-  if (ourFrame) {
-    nsSubDocumentFrame* ourFrameFrame = do_QueryFrame(ourFrame);
-    if (ourFrameFrame) {
-      ourFrameFrame->ResetFrameLoader();
+  if (nsSubDocumentFrame* ourFrame = do_QueryFrame(owner->GetPrimaryFrame())) {
+    ourFrame->ResetFrameLoader();
+  }
+
+  if (nsFocusManager* fm = nsFocusManager::GetFocusManager()) {
+    if (fm->GetFocusedElement() == owner) {
+      fm->ActivateRemoteFrameIfNeeded(*owner);
     }
   }
 
   // Assuming this element is a XULFrameElement, once we've reset our
   // FrameLoader, fire an event to act like we've recreated ourselves, similar
   // to what XULFrameElement does after rebinding to the tree.
   // ChromeOnlyDispatch is turns on to make sure this isn't fired into content.
   (new AsyncEventDispatcher(owner, NS_LITERAL_STRING("XULFrameLoaderCreated"),
--- a/dom/base/nsFrameLoaderOwner.h
+++ b/dom/base/nsFrameLoaderOwner.h
@@ -2,16 +2,18 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsFrameLoaderOwner_h_
 #define nsFrameLoaderOwner_h_
 
+#include "nsISupports.h"
+
 class nsFrameLoader;
 namespace mozilla {
 class ErrorResult;
 namespace dom {
 class BrowsingContext;
 struct RemotenessOptions;
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -6331,17 +6331,17 @@ void nsGlobalWindowOuter::EnterModalStat
   if (ds) {
     ds->EndDragSession(true, 0);
   }
 
   // Clear the capturing content if it is under topDoc.
   // Usually the activeESM check above does that, but there are cases when
   // we don't have activeESM, or it is for different document.
   Document* topDoc = topWin->GetExtantDoc();
-  nsIContent* capturingContent = nsIPresShell::GetCapturingContent();
+  nsIContent* capturingContent = PresShell::GetCapturingContent();
   if (capturingContent && topDoc &&
       nsContentUtils::ContentIsCrossDocDescendantOf(capturingContent, topDoc)) {
     PresShell::ReleaseCapturingContent();
   }
 
   if (topWin->mModalStateDepth == 0) {
     NS_ASSERTION(!topWin->mSuspendedDoc, "Shouldn't have mSuspendedDoc here!");
 
@@ -7361,26 +7361,22 @@ nsresult nsGlobalWindowOuter::SecurityCh
 
 void nsGlobalWindowOuter::FlushPendingNotifications(FlushType aType) {
   if (mDoc) {
     mDoc->FlushPendingNotifications(aType);
   }
 }
 
 void nsGlobalWindowOuter::EnsureSizeAndPositionUpToDate() {
-  // If we're a subframe, make sure our size is up to date.  It's OK that this
-  // crosses the content/chrome boundary, since chrome can have pending reflows
-  // too.
-  //
-  // Make sure to go through the document chain rather than the window chain to
-  // not flush on detached iframes, see bug 1545516.
-  if (mDoc) {
-    if (RefPtr<Document> parent = mDoc->GetParentDocument()) {
-      parent->FlushPendingNotifications(FlushType::Layout);
-    }
+  // If we're a subframe, make sure our size is up to date.  Make sure to go
+  // through the document chain rather than the window chain to not flush on
+  // detached iframes, see bug 1545516.
+  if (mDoc && mDoc->StyleOrLayoutObservablyDependsOnParentDocumentLayout()) {
+    RefPtr<Document> parent = mDoc->GetParentDocument();
+    parent->FlushPendingNotifications(FlushType::Layout);
   }
 }
 
 already_AddRefed<nsISupports> nsGlobalWindowOuter::SaveWindowState() {
   if (!mContext || !GetWrapperPreserveColor()) {
     // The window may be getting torn down; don't bother saving state.
     return nullptr;
   }
--- a/dom/chrome-webidl/ChromeUtils.webidl
+++ b/dom/chrome-webidl/ChromeUtils.webidl
@@ -520,25 +520,23 @@ dictionary IOActivityDataDictionary {
  * (3) Update the methods on mozilla::OriginAttributesPattern, including matching.
  */
 dictionary OriginAttributesDictionary {
   unsigned long appId = 0;
   unsigned long userContextId = 0;
   boolean inIsolatedMozBrowser = false;
   unsigned long privateBrowsingId = 0;
   DOMString firstPartyDomain = "";
-  DOMString geckoViewSessionContextId = "";
 };
 dictionary OriginAttributesPatternDictionary {
   unsigned long appId;
   unsigned long userContextId;
   boolean inIsolatedMozBrowser;
   unsigned long privateBrowsingId;
   DOMString firstPartyDomain;
-  DOMString geckoViewSessionContextId;
 };
 
 dictionary CompileScriptOptionsDictionary {
   /**
    * The character set from which to decode the script.
    */
   DOMString charset = "utf-8";
 
--- a/dom/events/ContentEventHandler.cpp
+++ b/dom/events/ContentEventHandler.cpp
@@ -2999,18 +2999,17 @@ nsresult ContentEventHandler::OnSelectio
         ->SetBaseAndExtentInLimiter(*startNodeStrong, startNodeOffset,
                                     *endNodeStrong, endNodeOffset, error);
     if (NS_WARN_IF(error.Failed())) {
       return error.StealNSResult();
     }
   }
 
   mSelection->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION,
-                             nsIPresShell::ScrollAxis(),
-                             nsIPresShell::ScrollAxis(), 0);
+                             ScrollAxis(), ScrollAxis(), 0);
   aEvent->mSucceeded = true;
   return NS_OK;
 }
 
 nsRect ContentEventHandler::FrameRelativeRect::RectRelativeTo(
     nsIFrame* aDestFrame) const {
   if (!mBaseFrame || NS_WARN_IF(!aDestFrame)) {
     return nsRect();
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -1752,17 +1752,17 @@ void EventStateManager::GenerateDragGest
       RefPtr<nsFrameSelection> frameSel = mCurrentTarget->GetFrameSelection();
       if (frameSel && frameSel->GetDragState()) {
         StopTrackingDragGesture();
         return;
       }
     }
 
     // If non-native code is capturing the mouse don't start a drag.
-    if (nsIPresShell::IsMouseCapturePreventingDrag()) {
+    if (PresShell::IsMouseCapturePreventingDrag()) {
       StopTrackingDragGesture();
       return;
     }
 
     if (IsEventOutsideDragThreshold(aEvent)) {
       if (Prefs::ClickHoldContextMenu()) {
         // stop the click-hold before we fire off the drag gesture, in case
         // it takes a long time
@@ -3030,17 +3030,17 @@ nsresult EventStateManager::PostHandleEv
         break;
       }
 
       // For remote content, capture the event in the parent process at the
       // <xul:browser remote> element. This will ensure that subsequent
       // mousemove/mouseup events will continue to be dispatched to this element
       // and therefore forwarded to the child.
       if (aEvent->HasBeenPostedToRemoteProcess() &&
-          !nsIPresShell::GetCapturingContent()) {
+          !PresShell::GetCapturingContent()) {
         if (nsIContent* content =
                 mCurrentTarget ? mCurrentTarget->GetContent() : nullptr) {
           PresShell::SetCapturingContent(content, CaptureFlags::None);
         } else {
           PresShell::ReleaseCapturingContent();
         }
       }
 
@@ -6285,17 +6285,17 @@ AutoHandlingUserInputStatePusher::AutoHa
 }
 
 AutoHandlingUserInputStatePusher::~AutoHandlingUserInputStatePusher() {
   if (!mIsHandlingUserInput) {
     return;
   }
   EventStateManager::StopHandlingUserInput(mMessage);
   if (mMessage == eMouseDown) {
-    nsIPresShell::AllowMouseCapture(false);
+    PresShell::AllowMouseCapture(false);
   }
   if (NeedsToResetFocusManagerMouseButtonHandlingState()) {
     nsFocusManager* fm = nsFocusManager::GetFocusManager();
     NS_ENSURE_TRUE_VOID(fm);
     nsCOMPtr<Document> handlingDocument =
         fm->SetMouseButtonHandlingDocument(mMouseButtonEventHandlingDocument);
   }
 }
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -3423,32 +3423,32 @@ void HTMLInputElement::StartRangeThumbDr
   GetValue(mFocusedValue, CallerType::System);
 
   SetValueOfRangeForUserEvent(rangeFrame->GetValueAtEventPoint(aEvent));
 }
 
 void HTMLInputElement::FinishRangeThumbDrag(WidgetGUIEvent* aEvent) {
   MOZ_ASSERT(mIsDraggingRange);
 
-  if (nsIPresShell::GetCapturingContent() == this) {
+  if (PresShell::GetCapturingContent() == this) {
     PresShell::ReleaseCapturingContent();
   }
   if (aEvent) {
     nsRangeFrame* rangeFrame = do_QueryFrame(GetPrimaryFrame());
     SetValueOfRangeForUserEvent(rangeFrame->GetValueAtEventPoint(aEvent));
   }
   mIsDraggingRange = false;
   FireChangeEventIfNeeded();
 }
 
 void HTMLInputElement::CancelRangeThumbDrag(bool aIsForUserEvent) {
   MOZ_ASSERT(mIsDraggingRange);
 
   mIsDraggingRange = false;
-  if (nsIPresShell::GetCapturingContent() == this) {
+  if (PresShell::GetCapturingContent() == this) {
     PresShell::ReleaseCapturingContent();
   }
   if (aIsForUserEvent) {
     SetValueOfRangeForUserEvent(mRangeThumbDragStartValue);
   } else {
     // Don't dispatch an 'input' event - at least not using
     // DispatchTrustedEvent.
     // TODO: decide what we should do here - bug 851782.
@@ -3507,17 +3507,17 @@ void HTMLInputElement::StartNumberContro
   nsNumberControlFrame* numberControlFrame = do_QueryFrame(GetPrimaryFrame());
   if (numberControlFrame) {
     numberControlFrame->SpinnerStateChanged();
   }
 }
 
 void HTMLInputElement::StopNumberControlSpinnerSpin(SpinnerStopState aState) {
   if (mNumberControlSpinnerIsSpinning) {
-    if (nsIPresShell::GetCapturingContent() == this) {
+    if (PresShell::GetCapturingContent() == this) {
       PresShell::ReleaseCapturingContent();
     }
 
     nsRepeatService::GetInstance()->Stop(HandleNumberControlSpin, this);
 
     mNumberControlSpinnerIsSpinning = false;
 
     if (aState == eAllowDispatchingEvents) {
@@ -4178,17 +4178,17 @@ void HTMLInputElement::PostHandleEventFo
   }
 
   switch (aVisitor.mEvent->mMessage) {
     case eMouseDown:
     case eTouchStart: {
       if (mIsDraggingRange) {
         break;
       }
-      if (nsIPresShell::GetCapturingContent()) {
+      if (PresShell::GetCapturingContent()) {
         break;  // don't start drag if someone else is already capturing
       }
       WidgetInputEvent* inputEvent = aVisitor.mEvent->AsInputEvent();
       if (IgnoreInputEventWithModifier(inputEvent, true)) {
         break;  // ignore
       }
       if (aVisitor.mEvent->mMessage == eMouseDown) {
         if (aVisitor.mEvent->AsMouseEvent()->mButtons ==
@@ -4207,17 +4207,17 @@ void HTMLInputElement::PostHandleEventFo
       aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
     } break;
 
     case eMouseMove:
     case eTouchMove:
       if (!mIsDraggingRange) {
         break;
       }
-      if (nsIPresShell::GetCapturingContent() != this) {
+      if (PresShell::GetCapturingContent() != this) {
         // Someone else grabbed capture.
         CancelRangeThumbDrag();
         break;
       }
       SetValueOfRangeForUserEvent(
           rangeFrame->GetValueAtEventPoint(aVisitor.mEvent->AsInputEvent()));
       aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
       break;
--- a/dom/ipc/RemoteWebProgressRequest.cpp
+++ b/dom/ipc/RemoteWebProgressRequest.cpp
@@ -155,16 +155,30 @@ NS_IMETHODIMP RemoteWebProgressRequest::
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP RemoteWebProgressRequest::GetMatchedFullHash(
     nsACString &aMatchedFullHash) {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
+NS_IMETHODIMP RemoteWebProgressRequest::SetMatchedTrackingInfo(
+    const nsTArray<nsCString> &aLists, const nsTArray<nsCString> &aFullHashes) {
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP RemoteWebProgressRequest::GetMatchedTrackingLists(
+    nsTArray<nsCString> &aLists) {
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP RemoteWebProgressRequest::GetMatchedTrackingFullHashes(
+    nsTArray<nsCString> &aFullHashes) {
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
 // nsIRequest methods
 
 NS_IMETHODIMP RemoteWebProgressRequest::GetName(nsACString &aName) {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP RemoteWebProgressRequest::IsPending(bool *_retval) {
   return NS_ERROR_NOT_IMPLEMENTED;
--- a/dom/ipc/URLClassifierParent.cpp
+++ b/dom/ipc/URLClassifierParent.cpp
@@ -90,16 +90,17 @@ class IPCFeature final : public nsIUrlCl
   NS_IMETHOD
   GetSkipHostList(nsACString& aList) override {
     aList = mIPCFeature.skipHostList();
     return NS_OK;
   }
 
   NS_IMETHOD
   ProcessChannel(nsIChannel* aChannel, const nsTArray<nsCString>& aList,
+                 const nsTArray<nsCString>& aHashes,
                  bool* aShouldContinue) override {
     NS_ENSURE_ARG_POINTER(aShouldContinue);
     *aShouldContinue = true;
 
     // Nothing to do here.
     return NS_OK;
   }
 
--- a/dom/plugins/base/nsPluginInstanceOwner.cpp
+++ b/dom/plugins/base/nsPluginInstanceOwner.cpp
@@ -1804,17 +1804,17 @@ static NPCocoaEventType CocoaEventTypeFo
     case eMouseOver:
       return NPCocoaEventMouseEntered;
     case eMouseOut:
       return NPCocoaEventMouseExited;
     case eMouseMove: {
       // We don't know via information on events from the widget code whether or
       // not we're dragging. The widget code just generates mouse move events
       // from native drag events. If anybody is capturing, this is a drag event.
-      if (nsIPresShell::GetCapturingContent()) {
+      if (PresShell::GetCapturingContent()) {
         return NPCocoaEventMouseDragged;
       }
 
       return NPCocoaEventMouseMoved;
     }
     case eMouseDown:
       return NPCocoaEventMouseDown;
     case eMouseUp:
--- a/dom/tests/mochitest/general/test_img_mutations.html
+++ b/dom/tests/mochitest/general/test_img_mutations.html
@@ -11,21 +11,21 @@
     "use strict";
 
     // Tests the relevant mutations part of the spec for img src and srcset
     // and that img.src still behaves by the older spec. (Bug 1076583)
     // https://html.spec.whatwg.org/#relevant-mutations
     SimpleTest.waitForExplicitFinish();
 
     // 50x50 png
-    var testPNG50 = new URL("image_50.png", location).href;
+    var testPNG50 = new URL("image_50.png?noCache=" + Math.random(), location).href;
     // 100x100 png
-    var testPNG100 = new URL("image_100.png", location).href;
+    var testPNG100 = new URL("image_100.png?noCache=" + Math.random(), location).href;
     // 200x200 png
-    var testPNG200 = new URL("image_200.png", location).href;
+    var testPNG200 = new URL("image_200.png?noCache=" + Math.random(), location).href;
 
     var tests = [];
     var img;
     var expectingErrors = 0;
     var expectingLoads = 0;
     var afterExpectCallback;
 
     function onImgLoad() {
--- a/dom/tests/mochitest/general/test_interfaces.js
+++ b/dom/tests/mochitest/general/test_interfaces.js
@@ -829,17 +829,17 @@ var interfaceNamesInGlobalScope =
     {name: "Report", insecureContext: true, nightly: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "ReportBody", insecureContext: true, nightly: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "ReportingObserver", insecureContext: true, nightly: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "Request", insecureContext: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "ResizeObserver", insecureContext: true},
+    {name: "ResizeObserver", insecureContext: true, nightly: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "Response", insecureContext: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "RTCCertificate", insecureContext: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "RTCDataChannel", insecureContext: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "RTCDataChannelEvent", insecureContext: true},
--- a/gfx/wr/webrender/src/batch.rs
+++ b/gfx/wr/webrender/src/batch.rs
@@ -1127,17 +1127,17 @@ impl AlphaBatchBuilder {
                                 }
 
                                 // Construct a local clip rect that ensures we only draw pixels where
                                 // the local bounds of the picture extend to within the edge tiles.
                                 let local_clip_rect = prim_info
                                     .combined_local_clip_rect
                                     .intersection(&picture.local_rect)
                                     .and_then(|rect| {
-                                        rect.intersection(&picture.local_clip_rect)
+                                        rect.intersection(&tile_cache.local_clip_rect)
                                     });
 
                                 if let Some(local_clip_rect) = local_clip_rect {
                                     // Step through each tile in the cache, and draw it with an image
                                     // brush primitive if visible.
 
                                     let kind = BatchKind::Brush(
                                         BrushBatchKind::Image(ImageBufferKind::Texture2DArray)
--- a/gfx/wr/webrender/src/display_list_flattener.rs
+++ b/gfx/wr/webrender/src/display_list_flattener.rs
@@ -519,17 +519,16 @@ impl<'a> DisplayListFlattener<'a> {
             Picture3DContext::Out,
             self.scene.root_pipeline_id.unwrap(),
             None,
             true,
             true,
             RasterSpace::Screen,
             prim_list,
             main_scroll_root,
-            LayoutRect::max_rect(),
             Some(tile_cache),
             PictureOptions::default(),
         ));
 
         let instance = PrimitiveInstance::new(
             LayoutPoint::zero(),
             LayoutRect::max_rect(),
             PrimitiveInstanceKind::Picture {
@@ -1644,24 +1643,16 @@ impl<'a> DisplayListFlattener<'a> {
         };
 
         if stacking_context.create_tile_cache {
             self.setup_picture_caching(
                 &mut stacking_context.primitives,
             );
         }
 
-        // An arbitrary large clip rect. For now, we don't
-        // specify a clip specific to the stacking context.
-        // However, now that they are represented as Picture
-        // primitives, we can apply any kind of clip mask
-        // to them, as for a normal primitive. This is needed
-        // to correctly handle some CSS cases (see #1957).
-        let max_clip = LayoutRect::max_rect();
-
         let (leaf_context_3d, leaf_composite_mode, leaf_output_pipeline_id) = match stacking_context.context_3d {
             // TODO(gw): For now, as soon as this picture is in
             //           a 3D context, we draw it to an intermediate
             //           surface and apply plane splitting. However,
             //           there is a large optimization opportunity here.
             //           During culling, we can check if there is actually
             //           perspective present, and skip the plane splitting
             //           completely when that is not the case.
@@ -1695,17 +1686,16 @@ impl<'a> DisplayListFlattener<'a> {
                 true,
                 stacking_context.is_backface_visible,
                 stacking_context.requested_raster_space,
                 PrimitiveList::new(
                     stacking_context.primitives,
                     &self.interners,
                 ),
                 stacking_context.spatial_node_index,
-                max_clip,
                 None,
                 PictureOptions::default(),
             ))
         );
 
         // Create a chain of pictures based on presence of filters,
         // mix-blend-mode and/or 3d rendering context containers.
 
@@ -1743,17 +1733,16 @@ impl<'a> DisplayListFlattener<'a> {
                     true,
                     stacking_context.is_backface_visible,
                     stacking_context.requested_raster_space,
                     PrimitiveList::new(
                         prims,
                         &self.interners,
                     ),
                     stacking_context.spatial_node_index,
-                    max_clip,
                     None,
                     PictureOptions::default(),
                 ))
             );
 
             cur_instance = create_prim_instance(
                 current_pic_index,
                 PictureCompositeKey::Identity,
@@ -1811,17 +1800,16 @@ impl<'a> DisplayListFlattener<'a> {
                     true,
                     stacking_context.is_backface_visible,
                     stacking_context.requested_raster_space,
                     PrimitiveList::new(
                         vec![cur_instance.clone()],
                         &self.interners,
                     ),
                     stacking_context.spatial_node_index,
-                    max_clip,
                     None,
                     PictureOptions::default(),
                 ))
             );
 
             current_pic_index = filter_pic_index;
             cur_instance = create_prim_instance(
                 current_pic_index,
@@ -1866,17 +1854,16 @@ impl<'a> DisplayListFlattener<'a> {
                     true,
                     stacking_context.is_backface_visible,
                     stacking_context.requested_raster_space,
                     PrimitiveList::new(
                         vec![cur_instance.clone()],
                         &self.interners,
                     ),
                     stacking_context.spatial_node_index,
-                    max_clip,
                     None,
                     PictureOptions::default(),
                 ))
             );
 
             current_pic_index = blend_pic_index;
             cur_instance = create_prim_instance(
                 blend_pic_index,
@@ -2120,17 +2107,16 @@ impl<'a> DisplayListFlattener<'a> {
     }
 
     pub fn pop_all_shadows(
         &mut self,
     ) {
         assert!(!self.pending_shadow_items.is_empty(), "popped shadows, but none were present");
 
         let pipeline_id = self.sc_stack.last().unwrap().pipeline_id;
-        let max_clip = LayoutRect::max_rect();
         let mut items = mem::replace(&mut self.pending_shadow_items, VecDeque::new());
 
         //
         // The pending_shadow_items queue contains a list of shadows and primitives
         // that were pushed during the active shadow context. To process these, we:
         //
         // Iterate the list, popping an item from the front each iteration.
         //
@@ -2237,17 +2223,16 @@ impl<'a> DisplayListFlattener<'a> {
                                 is_passthrough,
                                 is_backface_visible,
                                 raster_space,
                                 PrimitiveList::new(
                                     prims,
                                     &self.interners,
                                 ),
                                 pending_shadow.clip_and_scroll.spatial_node_index,
-                                max_clip,
                                 None,
                                 options,
                             ))
                         );
 
                         let shadow_pic_key = PictureKey::new(
                             true,
                             LayoutSize::zero(),
@@ -3009,17 +2994,16 @@ impl FlattenedStackingContext {
                 true,
                 self.is_backface_visible,
                 self.requested_raster_space,
                 PrimitiveList::new(
                     mem::replace(&mut self.primitives, Vec::new()),
                     interners,
                 ),
                 self.spatial_node_index,
-                LayoutRect::max_rect(),
                 None,
                 PictureOptions::default(),
             ))
         );
 
         let prim_instance = create_prim_instance(
             pic_index,
             PictureCompositeKey::Identity,
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -600,16 +600,18 @@ pub struct TileCache {
     /// List of reference primitive information used for
     /// correlating the position between display lists.
     reference_prims: ReferencePrimitiveList,
     /// The root clip chain for this tile cache.
     root_clip_chain_id: ClipChainId,
     /// If true, this tile cache is enabled. For now, it doesn't
     /// support tile caching if the surface is not the main framebuffer.
     pub is_enabled: bool,
+    /// Local clip rect for this tile cache.
+    pub local_clip_rect: LayoutRect,
 }
 
 /// Stores information about a primitive in the cache that we will
 /// try to use to correlate positions between display lists.
 #[derive(Clone)]
 struct ReferencePrimitive {
     uid: ItemUid,
     local_pos: LayoutPoint,
@@ -724,16 +726,17 @@ impl TileCache {
             tile_count: TileSize::zero(),
             scroll_offset: None,
             pending_blits: Vec::new(),
             world_bounding_rect: WorldRect::zero(),
             root_clip_rect: WorldRect::max_rect(),
             reference_prims,
             root_clip_chain_id,
             is_enabled: true,
+            local_clip_rect: LayoutRect::zero(),
         }
     }
 
     /// Get the tile coordinates for a given rectangle.
     fn get_tile_coords_for_rect(
         &self,
         rect: &WorldRect,
     ) -> (TileOffset, TileOffset) {
@@ -1375,38 +1378,38 @@ impl TileCache {
     /// any late tile invalidations, and sets up the dirty rect and
     /// set of tile blits.
     pub fn post_update(
         &mut self,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         frame_context: &FrameVisibilityContext,
         scratch: &mut PrimitiveScratchBuffer,
-    ) -> LayoutRect {
+    ) {
         self.dirty_region.clear();
         self.pending_blits.clear();
 
         // If the tile cache is disabled, just return a no-op local clip rect.
         if !self.is_enabled {
-            return LayoutRect::max_rect();
+            return;
         }
 
         // Skip all tiles if completely off-screen.
         if !self.world_bounding_rect.intersects(&frame_context.screen_world_rect) {
-            return LayoutRect::zero();
+            return;
         }
 
         let map_surface_to_world: SpaceMapper<LayoutPixel, WorldPixel> = SpaceMapper::new_with_target(
             ROOT_SPATIAL_NODE_INDEX,
             self.spatial_node_index,
             frame_context.screen_world_rect,
             frame_context.clip_scroll_tree,
         );
 
-        let local_clip_rect = map_surface_to_world
+        self.local_clip_rect = map_surface_to_world
             .unmap(&self.world_bounding_rect)
             .expect("bug: unable to map local clip rect");
 
         // Step through each tile and invalidate if the dependencies have changed.
         for (i, tile) in self.tiles.iter_mut().enumerate() {
             // Deal with any potential world clips. Check to see if they are
             // outside the tile cache bounding rect. If they are, they're not
             // relevant and we don't care if they move relative to the content
@@ -1590,18 +1593,16 @@ impl TileCache {
         // If we end up with too many dirty rects, then it's going to be a lot
         // of extra draw calls to submit (since we currently just submit every
         // draw call for every dirty rect). In this case, bail out and work
         // with a single, large dirty rect. In future we can consider improving
         // on this by supporting batching per dirty region.
         if self.dirty_region.dirty_rects.len() > MAX_DIRTY_RECTS {
             self.dirty_region.collapse();
         }
-
-        local_clip_rect
     }
 
     pub fn tile_dimensions(testing: bool) -> DeviceIntSize {
         if testing {
             size2(TILE_SIZE_TESTING, TILE_SIZE_TESTING)
         } else {
             size2(TILE_SIZE_WIDTH, TILE_SIZE_HEIGHT)
         }
@@ -2200,19 +2201,16 @@ pub struct PicturePrimitive {
     pub local_rect: LayoutRect,
 
     /// If false, this picture needs to (re)build segments
     /// if it supports segment rendering. This can occur
     /// if the local rect of the picture changes due to
     /// transform animation and/or scrolling.
     pub segments_are_valid: bool,
 
-    /// Local clip rect for this picture.
-    pub local_clip_rect: LayoutRect,
-
     /// If Some(..) the tile cache that is associated with this picture.
     #[cfg_attr(feature = "capture", serde(skip))] //TODO
     pub tile_cache: Option<TileCache>,
 
     /// The config options for this picture.
     options: PictureOptions,
 }
 
@@ -2221,19 +2219,16 @@ impl PicturePrimitive {
         &self,
         pictures: &[Self],
         self_index: PictureIndex,
         pt: &mut T,
     ) {
         pt.new_level(format!("{:?}", self_index));
         pt.add_item(format!("prim_count: {:?}", self.prim_list.prim_instances.len()));
         pt.add_item(format!("local_rect: {:?}", self.local_rect));
-        if self.apply_local_clip_rect {
-            pt.add_item(format!("local_clip_rect: {:?}", self.local_clip_rect));
-        }
         pt.add_item(format!("spatial_node_index: {:?}", self.spatial_node_index));
         pt.add_item(format!("raster_config: {:?}", self.raster_config));
         pt.add_item(format!("requested_composite_mode: {:?}", self.requested_composite_mode));
 
         for (index, _) in &self.prim_list.pictures {
             pictures[index.0].print(pictures, *index, pt);
         }
 
@@ -2315,17 +2310,16 @@ impl PicturePrimitive {
         context_3d: Picture3DContext<OrderedPictureChild>,
         pipeline_id: PipelineId,
         frame_output_pipeline_id: Option<PipelineId>,
         apply_local_clip_rect: bool,
         is_backface_visible: bool,
         requested_raster_space: RasterSpace,
         prim_list: PrimitiveList,
         spatial_node_index: SpatialNodeIndex,
-        local_clip_rect: LayoutRect,
         tile_cache: Option<TileCache>,
         options: PictureOptions,
     ) -> Self {
         PicturePrimitive {
             prim_list,
             state: None,
             secondary_render_task_id: None,
             requested_composite_mode,
@@ -2334,17 +2328,16 @@ impl PicturePrimitive {
             frame_output_pipeline_id,
             extra_gpu_data_handle: GpuCacheHandle::new(),
             apply_local_clip_rect,
             is_backface_visible,
             pipeline_id,
             requested_raster_space,
             spatial_node_index,
             local_rect: LayoutRect::zero(),
-            local_clip_rect,
             tile_cache,
             options,
             segments_are_valid: false,
         }
     }
 
     pub fn take_context(
         &mut self,
--- a/gfx/wr/webrender/src/prim_store/mod.rs
+++ b/gfx/wr/webrender/src/prim_store/mod.rs
@@ -2015,17 +2015,17 @@ impl PrimitiveStore {
         }
 
         let pic = &mut self.pictures[pic_index.0];
 
         if let Some(RasterConfig { composite_mode: PictureCompositeMode::TileCache { .. }, .. }) = pic.raster_config {
             let mut tile_cache = frame_state.tile_cache.take().unwrap();
 
             // Build the dirty region(s) for this tile cache.
-            pic.local_clip_rect = tile_cache.post_update(
+            tile_cache.post_update(
                 frame_state.resource_cache,
                 frame_state.gpu_cache,
                 frame_context,
                 frame_state.scratch,
             );
 
             pic.tile_cache = Some(tile_cache);
         }
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -207,19 +207,17 @@ using namespace mozilla::css;
 using namespace mozilla::dom;
 using namespace mozilla::gfx;
 using namespace mozilla::layers;
 using namespace mozilla::gfx;
 using namespace mozilla::layout;
 using PaintFrameFlags = nsLayoutUtils::PaintFrameFlags;
 typedef ScrollableLayerGuid::ViewID ViewID;
 
-CapturingContentInfo nsIPresShell::gCaptureInfo = {
-    false /* mAllowed */, false /* mPointerLock */,
-    false /* mRetargetToElement */, false /* mPreventDrag */};
+PresShell::CapturingContentInfo PresShell::sCapturingContentInfo;
 
 // RangePaintInfo is used to paint ranges to offscreen buffers
 struct RangePaintInfo {
   RefPtr<nsRange> mRange;
   nsDisplayListBuilder mBuilder;
   nsDisplayList mList;
 
   // offset of builder's reference frame to the root frame
@@ -2437,17 +2435,18 @@ void PresShell::RestoreRootScrollPositio
   }
 }
 
 void PresShell::MaybeReleaseCapturingContent() {
   RefPtr<nsFrameSelection> frameSelection = FrameSelection();
   if (frameSelection) {
     frameSelection->SetDragState(false);
   }
-  if (gCaptureInfo.mContent && gCaptureInfo.mContent->OwnerDoc() == mDocument) {
+  if (sCapturingContentInfo.mContent &&
+      sCapturingContentInfo.mContent->OwnerDoc() == mDocument) {
     PresShell::ReleaseCapturingContent();
   }
 }
 
 void PresShell::BeginLoad(Document* aDocument) {
   mDocumentLoading = true;
 
   gfxTextPerfMetrics* tp = nullptr;
@@ -3310,20 +3309,18 @@ static nscoord ComputeWhereToScroll(Wher
  * Note that, since we are performing a layout scroll, it's possible that
  * this fnction will sometimes be unsuccessful; the content will move as
  * fast as it can on the screen using layout viewport scrolling, and then
  * stop there, even if it could get closer to the desired position by
  * moving the visual viewport within the layout viewport.
  */
 static void ScrollToShowRect(nsIPresShell* aPresShell,
                              nsIScrollableFrame* aFrameAsScrollable,
-                             const nsRect& aRect,
-                             nsIPresShell::ScrollAxis aVertical,
-                             nsIPresShell::ScrollAxis aHorizontal,
-                             ScrollFlags aScrollFlags) {
+                             const nsRect& aRect, ScrollAxis aVertical,
+                             ScrollAxis aHorizontal, ScrollFlags aScrollFlags) {
   nsPoint scrollPt = aFrameAsScrollable->GetVisualViewportOffset();
   nsRect visibleRect(scrollPt, aFrameAsScrollable->GetVisualViewportSize());
 
   nsSize lineSize;
   // Don't call GetLineScrollAmount unless we actually need it. Not only
   // does this save time, but it's not safe to call GetLineScrollAmount
   // during reflow (because it depends on font size inflation and doesn't
   // use the in-reflow-safe font-size inflation path). If we did call it,
@@ -3394,18 +3391,18 @@ static void ScrollToShowRect(nsIPresShel
         aPresShell->GetPresContext()->IsRootContentDocument()) {
       aPresShell->ScrollToVisual(scrollPt, FrameMetrics::eMainThread,
                                  scrollMode);
     }
   }
 }
 
 nsresult PresShell::ScrollContentIntoView(nsIContent* aContent,
-                                          nsIPresShell::ScrollAxis aVertical,
-                                          nsIPresShell::ScrollAxis aHorizontal,
+                                          ScrollAxis aVertical,
+                                          ScrollAxis aHorizontal,
                                           ScrollFlags aScrollFlags) {
   NS_ENSURE_TRUE(aContent, NS_ERROR_NULL_POINTER);
   RefPtr<Document> composedDoc = aContent->GetComposedDoc();
   NS_ENSURE_STATE(composedDoc);
 
   NS_ASSERTION(mDidInitialize, "should have done initial reflow by now");
 
   if (mContentToScrollTo) {
@@ -3512,18 +3509,18 @@ void PresShell::DoScrollContentIntoView(
 
   ScrollFrameRectIntoView(container, frameBounds, data->mContentScrollVAxis,
                           data->mContentScrollHAxis,
                           data->mContentToScrollToFlags);
 }
 
 bool nsIPresShell::ScrollFrameRectIntoView(nsIFrame* aFrame,
                                            const nsRect& aRect,
-                                           nsIPresShell::ScrollAxis aVertical,
-                                           nsIPresShell::ScrollAxis aHorizontal,
+                                           ScrollAxis aVertical,
+                                           ScrollAxis aHorizontal,
                                            ScrollFlags aScrollFlags) {
   bool didScroll = false;
   // This function needs to work even if rect has a width or height of 0.
   nsRect rect = aRect;
   nsIFrame* container = aFrame;
   // Walk up the frame hierarchy scrolling the rect into view and
   // keeping rect relative to container
   do {
@@ -3707,75 +3704,75 @@ void nsIPresShell::DispatchSynthMouseMov
   nsEventStatus status = nsEventStatus_eIgnore;
   nsView* targetView = nsView::GetViewFor(aEvent->mWidget);
   if (!targetView) return;
   RefPtr<nsViewManager> viewManager = targetView->GetViewManager();
   viewManager->DispatchEvent(aEvent, targetView, &status);
 }
 
 void PresShell::ClearMouseCaptureOnView(nsView* aView) {
-  if (gCaptureInfo.mContent) {
+  if (sCapturingContentInfo.mContent) {
     if (aView) {
       // if a view was specified, ensure that the captured content is within
       // this view.
-      nsIFrame* frame = gCaptureInfo.mContent->GetPrimaryFrame();
+      nsIFrame* frame = sCapturingContentInfo.mContent->GetPrimaryFrame();
       if (frame) {
         nsView* view = frame->GetClosestView();
         // if there is no view, capturing won't be handled any more, so
         // just release the capture.
         if (view) {
           do {
             if (view == aView) {
-              gCaptureInfo.mContent = nullptr;
+              sCapturingContentInfo.mContent = nullptr;
               // the view containing the captured content likely disappeared so
               // disable capture for now.
-              gCaptureInfo.mAllowed = false;
+              sCapturingContentInfo.mAllowed = false;
               break;
             }
 
             view = view->GetParent();
           } while (view);
           // return if the view wasn't found
           return;
         }
       }
     }
 
-    gCaptureInfo.mContent = nullptr;
+    sCapturingContentInfo.mContent = nullptr;
   }
 
   // disable mouse capture until the next mousedown as a dialog has opened
   // or a drag has started. Otherwise, someone could start capture during
   // the modal dialog or drag.
-  gCaptureInfo.mAllowed = false;
-}
-
-void nsIPresShell::ClearMouseCapture(nsIFrame* aFrame) {
-  if (!gCaptureInfo.mContent) {
-    gCaptureInfo.mAllowed = false;
+  sCapturingContentInfo.mAllowed = false;
+}
+
+void PresShell::ClearMouseCapture(nsIFrame* aFrame) {
+  if (!sCapturingContentInfo.mContent) {
+    sCapturingContentInfo.mAllowed = false;
     return;
   }
 
   // null frame argument means clear the capture
   if (!aFrame) {
-    gCaptureInfo.mContent = nullptr;
-    gCaptureInfo.mAllowed = false;
+    sCapturingContentInfo.mContent = nullptr;
+    sCapturingContentInfo.mAllowed = false;
     return;
   }
 
-  nsIFrame* capturingFrame = gCaptureInfo.mContent->GetPrimaryFrame();
+  nsIFrame* capturingFrame = sCapturingContentInfo.mContent->GetPrimaryFrame();
   if (!capturingFrame) {
-    gCaptureInfo.mContent = nullptr;
-    gCaptureInfo.mAllowed = false;
+    sCapturingContentInfo.mContent = nullptr;
+    sCapturingContentInfo.mAllowed = false;
     return;
   }
 
   if (nsLayoutUtils::IsAncestorFrameCrossDoc(aFrame, capturingFrame)) {
-    gCaptureInfo.mContent = nullptr;
-    gCaptureInfo.mAllowed = false;
+    sCapturingContentInfo.mContent = nullptr;
+    sCapturingContentInfo.mAllowed = false;
   }
 }
 
 nsresult PresShell::CaptureHistoryState(nsILayoutHistoryState** aState) {
   MOZ_ASSERT(nullptr != aState, "null state pointer");
 
   // We actually have to mess with the docshell here, since we want to
   // store the state back in it.
@@ -6130,37 +6127,38 @@ void PresShell::Paint(nsView* aViewToPai
                                    ? LayerManager::END_DEFAULT
                                    : LayerManager::END_NO_COMPOSITE);
 }
 
 // static
 void PresShell::SetCapturingContent(nsIContent* aContent, CaptureFlags aFlags) {
   // If capture was set for pointer lock, don't unlock unless we are coming
   // out of pointer lock explicitly.
-  if (!aContent && gCaptureInfo.mPointerLock &&
+  if (!aContent && sCapturingContentInfo.mPointerLock &&
       !(aFlags & CaptureFlags::PointerLock)) {
     return;
   }
 
-  gCaptureInfo.mContent = nullptr;
+  sCapturingContentInfo.mContent = nullptr;
 
   // only set capturing content if allowed or the
   // CaptureFlags::IgnoreAllowedState or CaptureFlags::PointerLock are used.
-  if ((aFlags & CaptureFlags::IgnoreAllowedState) || gCaptureInfo.mAllowed ||
-      (aFlags & CaptureFlags::PointerLock)) {
+  if ((aFlags & CaptureFlags::IgnoreAllowedState) ||
+      sCapturingContentInfo.mAllowed || (aFlags & CaptureFlags::PointerLock)) {
     if (aContent) {
-      gCaptureInfo.mContent = aContent;
+      sCapturingContentInfo.mContent = aContent;
     }
     // CaptureFlags::PointerLock is the same as
     // CaptureFlags::RetargetToElement & CaptureFlags::IgnoreAllowedState.
-    gCaptureInfo.mRetargetToElement =
+    sCapturingContentInfo.mRetargetToElement =
         !!(aFlags & CaptureFlags::RetargetToElement) ||
         !!(aFlags & CaptureFlags::PointerLock);
-    gCaptureInfo.mPreventDrag = !!(aFlags & CaptureFlags::PreventDragStart);
-    gCaptureInfo.mPointerLock = !!(aFlags & CaptureFlags::PointerLock);
+    sCapturingContentInfo.mPreventDrag =
+        !!(aFlags & CaptureFlags::PreventDragStart);
+    sCapturingContentInfo.mPointerLock = !!(aFlags & CaptureFlags::PointerLock);
   }
 }
 
 nsIContent* nsIPresShell::GetCurrentEventContent() {
   if (mCurrentEventContent &&
       mCurrentEventContent->GetComposedDoc() != mDocument) {
     mCurrentEventContent = nullptr;
     mCurrentEventFrame = nullptr;
@@ -6671,17 +6669,17 @@ nsresult PresShell::EventHandler::Handle
     }
   }
 
   // if a node is capturing the mouse, check if the event needs to be
   // retargeted at the capturing content instead. This will be the case when
   // capture retargeting is being used, no frame was found or the frame's
   // content is not a descendant of the capturing content.
   if (capturingContent && !pointerCapturingContent &&
-      (gCaptureInfo.mRetargetToElement ||
+      (PresShell::sCapturingContentInfo.mRetargetToElement ||
        !eventTargetData.mFrame->GetContent() ||
        !nsContentUtils::ContentIsCrossDocDescendantOf(
            eventTargetData.mFrame->GetContent(), capturingContent))) {
     // A check was already done above to ensure that capturingContent is
     // in this presshell.
     NS_ASSERTION(capturingContent->GetComposedDoc() == GetDocument(),
                  "Unexpected document");
     nsIFrame* capturingFrame = capturingContent->GetPrimaryFrame();
@@ -7010,17 +7008,17 @@ bool PresShell::EventHandler::MaybeDisca
 }
 
 // static
 nsIContent* PresShell::EventHandler::GetCapturingContentFor(
     WidgetGUIEvent* aGUIEvent) {
   return (aGUIEvent->mClass == ePointerEventClass ||
           aGUIEvent->mClass == eWheelEventClass ||
           aGUIEvent->HasMouseEventMessage())
-             ? nsIPresShell::GetCapturingContent()
+             ? PresShell::GetCapturingContent()
              : nullptr;
 }
 
 bool PresShell::EventHandler::GetRetargetEventDocument(
     WidgetGUIEvent* aGUIEvent, Document** aRetargetEventDocument) {
   MOZ_ASSERT(aGUIEvent);
   MOZ_ASSERT(aRetargetEventDocument);
 
@@ -7375,17 +7373,17 @@ PresShell::EventHandler::ComputeRootFram
   bool isBaseWindowVisible = false;
   nsresult rv = baseWindow->GetVisibility(&isBaseWindowVisible);
   if (NS_FAILED(rv) || !isBaseWindowVisible) {
     ClearMouseCapture(nullptr);
     *aIsCapturingContentIgnored = true;
     return aRootFrameToHandleEvent;
   }
 
-  if (gCaptureInfo.mRetargetToElement) {
+  if (PresShell::sCapturingContentInfo.mRetargetToElement) {
     *aIsCaptureRetargeted = true;
     return aRootFrameToHandleEvent;
   }
 
   // A check was already done above to ensure that aCapturingContent is
   // in this presshell.
   NS_ASSERTION(aCapturingContent->GetComposedDoc() == GetDocument(),
                "Unexpected document");
@@ -7844,17 +7842,17 @@ bool PresShell::EventHandler::PrepareToD
       *aIsUserInteraction = true;
       return true;
 
     case eMouseMove: {
       bool allowCapture = EventStateManager::GetActiveEventStateManager() &&
                           GetPresContext() &&
                           GetPresContext()->EventStateManager() ==
                               EventStateManager::GetActiveEventStateManager();
-      nsIPresShell::AllowMouseCapture(allowCapture);
+      PresShell::AllowMouseCapture(allowCapture);
       *aIsUserInteraction = false;
       return true;
     }
     case eDrop: {
       nsCOMPtr<nsIDragSession> session = nsContentUtils::GetDragSession();
       if (session) {
         bool onlyChromeDrop = false;
         session->GetOnlyChromeDrop(&onlyChromeDrop);
@@ -7921,17 +7919,17 @@ void PresShell::EventHandler::FinalizeHa
       }
       return;
     }
     case eMouseUp:
       // reset the capturing content now that the mouse button is up
       PresShell::ReleaseCapturingContent();
       return;
     case eMouseMove:
-      nsIPresShell::AllowMouseCapture(false);
+      PresShell::AllowMouseCapture(false);
       return;
     case eDrag:
     case eDragEnd:
     case eDragEnter:
     case eDragExit:
     case eDragLeave:
     case eDragOver:
     case eDrop: {
@@ -8260,17 +8258,17 @@ void PresShell::EventHandler::DispatchTo
 
     nsCOMPtr<EventTarget> targetPtr = touch->mTarget;
     nsCOMPtr<nsIContent> content = do_QueryInterface(targetPtr);
     if (!content) {
       continue;
     }
 
     Document* doc = content->OwnerDoc();
-    nsIContent* capturingContent = nsIPresShell::GetCapturingContent();
+    nsIContent* capturingContent = PresShell::GetCapturingContent();
     if (capturingContent) {
       if (capturingContent->OwnerDoc() != doc) {
         // Wrong document, don't dispatch anything.
         continue;
       }
       content = capturingContent;
     }
     // copy the event
@@ -8508,25 +8506,23 @@ bool PresShell::EventHandler::PrepareToU
     // ScrollContentIntoView, which has a one-pixel disagreement of whether the
     // frame is actually in view. The result is that the frame is aligned with
     // the top of the window, but the menu is still at the bottom.
     //
     // Doing this call first forces the frame to be in view, eliminating the
     // problem. The only difference in the result is that if your cursor is in
     // an edit box below the current view, you'll get the edit box aligned with
     // the top of the window. This is arguably better behavior anyway.
-    rv = MOZ_KnownLive(mPresShell)
-             ->ScrollContentIntoView(
-                 content,
-                 nsIPresShell::ScrollAxis(kScrollMinimum,
-                                          WhenToScroll::IfNotVisible),
-                 nsIPresShell::ScrollAxis(kScrollMinimum,
-                                          WhenToScroll::IfNotVisible),
-                 ScrollFlags::ScrollOverflowHidden |
-                     ScrollFlags::IgnoreMarginAndPadding);
+    rv =
+        MOZ_KnownLive(mPresShell)
+            ->ScrollContentIntoView(
+                content, ScrollAxis(kScrollMinimum, WhenToScroll::IfNotVisible),
+                ScrollAxis(kScrollMinimum, WhenToScroll::IfNotVisible),
+                ScrollFlags::ScrollOverflowHidden |
+                    ScrollFlags::IgnoreMarginAndPadding);
     NS_ENSURE_SUCCESS(rv, false);
     frame = content->GetPrimaryFrame();
     NS_WARNING_ASSERTION(frame, "No frame for focused content?");
   }
 
   // Actually scroll the selection (ie caret) into view. Note that this must
   // be synchronous since we will be checking the caret position on the screen.
   //
--- a/layout/base/PresShell.h
+++ b/layout/base/PresShell.h
@@ -68,16 +68,44 @@ class PresShell final : public nsIPresSh
  public:
   PresShell();
 
   // nsISupports
   NS_DECL_ISUPPORTS
 
   static bool AccessibleCaretEnabled(nsIDocShell* aDocShell);
 
+  /**
+   * Return the active content currently capturing the mouse if any.
+   */
+  static nsIContent* GetCapturingContent() {
+    return sCapturingContentInfo.mContent;
+  }
+
+  /**
+   * Allow or disallow mouse capturing.
+   */
+  static void AllowMouseCapture(bool aAllowed) {
+    sCapturingContentInfo.mAllowed = aAllowed;
+  }
+
+  /**
+   * Returns true if there is an active mouse capture that wants to prevent
+   * drags.
+   */
+  static bool IsMouseCapturePreventingDrag() {
+    return sCapturingContentInfo.mPreventDrag && sCapturingContentInfo.mContent;
+  }
+
+  static void ClearMouseCaptureOnView(nsView* aView);
+
+  // If a frame in the subtree rooted at aFrame is capturing the mouse then
+  // clears that capture.
+  static void ClearMouseCapture(nsIFrame* aFrame);
+
   void Init(Document*, nsPresContext*, nsViewManager*);
   void Destroy() override;
 
   NS_IMETHOD GetSelectionFromScript(RawSelectionType aRawSelectionType,
                                     dom::Selection** aSelection) override;
   dom::Selection* GetSelection(RawSelectionType aRawSelectionType) override;
 
   dom::Selection* GetCurrentSelection(SelectionType aSelectionType) override;
@@ -233,17 +261,16 @@ class PresShell final : public nsIPresSh
    * manager flush on the next tick.
    *
    * @param aType PaintType::DelayedCompress : Schedule a paint to be executed
    * after a delay, and put FrameLayerBuilder in 'compressed' mode that avoids
    * short cut optimizations.
    */
   void ScheduleViewManagerFlush(PaintType aType = PaintType::Default);
 
-  void ClearMouseCaptureOnView(nsView* aView) override;
   bool IsVisible() override;
   void SuppressDisplayport(bool aEnabled) override;
   void RespectDisplayportSuppression(bool aEnabled) override;
   bool IsDisplayportSuppressed() override;
 
   // caret handling
   NS_IMETHOD SetCaretEnabled(bool aInEnable) override;
   NS_IMETHOD SetCaretReadOnly(bool aReadOnly) override;
@@ -1545,16 +1572,18 @@ class PresShell final : public nsIPresSh
 
   // Information needed to properly handle scrolling content into view if the
   // pre-scroll reflow flush can be interrupted.  mContentToScrollTo is non-null
   // between the initial scroll attempt and the first time we finish processing
   // all our dirty roots.  mContentToScrollTo has a content property storing the
   // details for the scroll operation, see ScrollIntoViewData above.
   nsCOMPtr<nsIContent> mContentToScrollTo;
 
+  TimeStamp mLastOSWake;
+
   // The focus sequence number of the last processed input event
   uint64_t mAPZFocusSequenceNumber;
   // The focus information needed for async keyboard scrolling
   FocusTarget mAPZFocusTarget;
 
   nscoord mLastAnchorScrollPositionY = 0;
 
   int32_t mActiveSuppressDisplayport;
@@ -1592,18 +1621,32 @@ class PresShell final : public nsIPresSh
   // mForceUseLegacyKeyCodeAndCharCodeValues are initialized.
   bool mInitializedWithKeyPressEventDispatchingBlacklist : 1;
 
   // Whether we should dispatch click events for non-primary mouse buttons.
   bool mForceUseLegacyNonPrimaryDispatch : 1;
   // Whether mForceUseLegacyNonPrimaryDispatch is initialised.
   bool mInitializedWithClickEventDispatchingBlacklist : 1;
 
-  static bool sDisableNonTestMouseEvents;
+  struct CapturingContentInfo final {
+    CapturingContentInfo()
+        : mAllowed(false),
+          mPointerLock(false),
+          mRetargetToElement(false),
+          mPreventDrag(false) {}
 
-  TimeStamp mLastOSWake;
+    // capture should only be allowed during a mousedown event
+    StaticRefPtr<nsIContent> mContent;
+    bool mAllowed;
+    bool mPointerLock;
+    bool mRetargetToElement;
+    bool mPreventDrag;
+  };
+  static CapturingContentInfo sCapturingContentInfo;
+
+  static bool sDisableNonTestMouseEvents;
 
   static bool sProcessInteractable;
 };
 
 }  // namespace mozilla
 
 #endif  // mozilla_PresShell_h
--- a/layout/base/PresShellForwards.h
+++ b/layout/base/PresShellForwards.h
@@ -72,31 +72,81 @@ enum class ReflowRootHandling {
   InferFromBitToAdd,       // is changing iff (aBitToAdd == NS_FRAME_IS_DIRTY)
 
   // Note:  With eStyleChange, these can also apply to out-of-flows
   // in addition to aFrame.
 };
 
 // WhereToScroll should be 0 ~ 100 or -1.  When it's in 0 ~ 100, it means
 // percentage of scrollTop/scrollLeft in scrollHeight/scrollWidth.
-// See ComputeWhereToScroll() for the detail.
+// See the comment for constructor of ScrollAxis for the detail.
 typedef int16_t WhereToScroll;
 static const WhereToScroll kScrollToTop = 0;
 static const WhereToScroll kScrollToLeft = 0;
 static const WhereToScroll kScrollToCenter = 50;
 static const WhereToScroll kScrollToBottom = 100;
 static const WhereToScroll kScrollToRight = 100;
 static const WhereToScroll kScrollMinimum = -1;
 
+// See the comment for constructor of ScrollAxis for the detail.
 enum class WhenToScroll : uint8_t {
   Always,
   IfNotVisible,
   IfNotFullyVisible,
 };
 
+struct ScrollAxis final {
+  /**
+   * aWhere:
+   *   Either a percentage or a special value. PresShell defines:
+   *   * (Default) kScrollMinimum = -1: The visible area is scrolled the
+   *     minimum amount to show as much as possible of the frame. This won't
+   *     hide any initially visible part of the frame.
+   *   * kScrollToTop = 0: The frame's upper edge is aligned with the top edge
+   *     of the visible area.
+   *   * kScrollToBottom = 100: The frame's bottom edge is aligned with the
+   *     bottom edge of the visible area.
+   *   * kScrollToLeft = 0: The frame's left edge is aligned with the left edge
+   *     of the visible area.
+   *   * kScrollToRight = 100: The frame's right edge is aligned* with the right
+   *     edge of the visible area.
+   *   * kScrollToCenter = 50: The frame is centered along the axis the
+   *     ScrollAxis is used for.
+   *
+   *   Other values are treated as a percentage, and the point*"percent"
+   *   down the frame is placed at the point "percent" down the visible area.
+   *
+   * aWhen:
+   *   * (Default) WhenToScroll::IfNotFullyVisible: Move the frame only if it is
+   *     not fully visible (including if it's not visible at all). Note that
+   *     in this case if the frame is too large to fit in view, it will only
+   *     be scrolled if more of it can fit than is already in view.
+   *   * WhenToScroll::IfNotVisible: Move the frame only if none of it is
+   *     visible.
+   *   * WhenToScroll::Always: Move the frame regardless of its current
+   *     visibility.
+   *
+   * aOnlyIfPerceivedScrollableDirection:
+   *   If the direction is not a perceived scrollable direction (i.e. no
+   *   scrollbar showing and less than one device pixel of scrollable
+   *   distance), don't scroll. Defaults to false.
+   */
+  explicit ScrollAxis(WhereToScroll aWhere = kScrollMinimum,
+                      WhenToScroll aWhen = WhenToScroll::IfNotFullyVisible,
+                      bool aOnlyIfPerceivedScrollableDirection = false)
+      : mWhereToScroll(aWhere),
+        mWhenToScroll(aWhen),
+        mOnlyIfPerceivedScrollableDirection(
+            aOnlyIfPerceivedScrollableDirection) {}
+
+  WhereToScroll mWhereToScroll;
+  WhenToScroll mWhenToScroll;
+  bool mOnlyIfPerceivedScrollableDirection : 1;
+};
+
 enum class ScrollFlags {
   None = 0,
   ScrollFirstAncestorOnly = 1 << 0,
   ScrollOverflowHidden = 1 << 1,
   ScrollNoParentFrames = 1 << 2,
   ScrollSmooth = 1 << 3,
   ScrollSmoothAuto = 1 << 4,
   ScrollSnap = 1 << 5,
--- a/layout/base/nsIPresShell.h
+++ b/layout/base/nsIPresShell.h
@@ -13,17 +13,16 @@
 
 #include "mozilla/ArenaObjectID.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/FlushType.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/ScrollTypes.h"
 #include "mozilla/ServoStyleSet.h"
 #include "mozilla/ServoStyleConsts.h"
-#include "mozilla/StaticPtr.h"
 #include "mozilla/StyleSheet.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/WeakPtr.h"
 #include "FrameMetrics.h"
 #include "GeckoProfiler.h"
 #include "gfxPoint.h"
 #include "nsDOMNavigationTiming.h"
 #include "nsTHashtable.h"
@@ -123,25 +122,16 @@ namespace layers {
 class LayerManager;
 }  // namespace layers
 
 namespace gfx {
 class SourceSurface;
 }  // namespace gfx
 }  // namespace mozilla
 
-struct CapturingContentInfo final {
-  // capture should only be allowed during a mousedown event
-  bool mAllowed;
-  bool mPointerLock;
-  bool mRetargetToElement;
-  bool mPreventDrag;
-  mozilla::StaticRefPtr<nsIContent> mContent;
-};
-
 // b7b89561-4f03-44b3-9afa-b47e7f313ffb
 #define NS_IPRESSHELL_IID                            \
   {                                                  \
     0xb7b89561, 0x4f03, 0x44b3, {                    \
       0x9a, 0xfa, 0xb4, 0x7e, 0x7f, 0x31, 0x3f, 0xfb \
     }                                                \
   }
 
@@ -614,63 +604,16 @@ class nsIPresShell : public nsStubDocume
 
   /**
    * Get a reference rendering context. This is a context that should not
    * be rendered to, but is suitable for measuring text and performing
    * other non-rendering operations. Guaranteed to return non-null.
    */
   already_AddRefed<gfxContext> CreateReferenceRenderingContext();
 
-  typedef struct ScrollAxis {
-    mozilla::WhereToScroll mWhereToScroll;
-    mozilla::WhenToScroll mWhenToScroll;
-    bool mOnlyIfPerceivedScrollableDirection : 1;
-    /**
-     * aWhere:
-     *   Either a percentage or a special value. nsIPresShell defines:
-     *   * (Default) SCROLL_MINIMUM = -1: The visible area is scrolled the
-     *     minimum amount to show as much as possible of the frame. This won't
-     *     hide any initially visible part of the frame.
-     *   * SCROLL_TOP = 0: The frame's upper edge is aligned with the top edge
-     *     of the visible area.
-     *   * SCROLL_BOTTOM = 100: The frame's bottom edge is aligned with the
-     *     bottom edge of the visible area.
-     *   * SCROLL_LEFT = 0: The frame's left edge is aligned with the left edge
-     *     of the visible area.
-     *   * SCROLL_RIGHT = 100: The frame's right edge is aligned* with the right
-     *     edge of the visible area.
-     *   * SCROLL_CENTER = 50: The frame is centered along the axis the
-     *     ScrollAxis is used for.
-     *
-     *   Other values are treated as a percentage, and the point*"percent"
-     *   down the frame is placed at the point "percent" down the visible area.
-     *
-     * aWhen:
-     *   * (Default) SCROLL_IF_NOT_FULLY_VISIBLE: Move the frame only if it is
-     *     not fully visible (including if it's not visible at all). Note that
-     *     in this case if the frame is too large to fit in view, it will only
-     *     be scrolled if more of it can fit than is already in view.
-     *   * SCROLL_IF_NOT_VISIBLE: Move the frame only if none of it is visible.
-     *   * SCROLL_ALWAYS: Move the frame regardless of its current visibility.
-     *
-     * aOnlyIfPerceivedScrollableDirection:
-     *   If the direction is not a perceived scrollable direction (i.e. no
-     *   scrollbar showing and less than one device pixel of scrollable
-     *   distance), don't scroll. Defaults to false.
-     */
-    explicit ScrollAxis(
-        mozilla::WhereToScroll aWhere = mozilla::kScrollMinimum,
-        mozilla::WhenToScroll aWhen = mozilla::WhenToScroll::IfNotFullyVisible,
-        bool aOnlyIfPerceivedScrollableDirection = false)
-        : mWhereToScroll(aWhere),
-          mWhenToScroll(aWhen),
-          mOnlyIfPerceivedScrollableDirection(
-              aOnlyIfPerceivedScrollableDirection) {}
-  } ScrollAxis;
-
   /**
    * Scrolls the view of the document so that the given area of a frame
    * is visible, if possible. Layout is not flushed before scrolling.
    *
    * @param aRect relative to aFrame
    * @param aVertical see ScrollContentIntoView and ScrollAxis
    * @param aHorizontal see ScrollContentIntoView and ScrollAxis
    * @param aScrollFlags if SCROLL_FIRST_ANCESTOR_ONLY is set, only the
@@ -685,17 +628,18 @@ class nsIPresShell : public nsStubDocume
    * contain this document in a iframe or the like.
    * If SCROLL_IGNORE_SCROLL_MARGIN_AND_PADDING is set we ignore scroll-margin
    * value specified for |aFrame| and scroll-padding value for the scroll
    * container. This option is typically used to locate poped-up frames into
    * view.
    * @return true if any scrolling happened, false if no scrolling happened
    */
   bool ScrollFrameRectIntoView(nsIFrame* aFrame, const nsRect& aRect,
-                               ScrollAxis aVertical, ScrollAxis aHorizontal,
+                               mozilla::ScrollAxis aVertical,
+                               mozilla::ScrollAxis aHorizontal,
                                mozilla::ScrollFlags aScrollFlags);
 
   /**
    * Determine if a rectangle specified in the frame's coordinate system
    * intersects "enough" with the viewport to be considered visible. This
    * is not a strict test against the viewport -- it's a test against
    * the intersection of the viewport and the frame's ancestor scrollable
    * frames. If it doesn't intersect enough, return a value indicating
@@ -1098,39 +1042,16 @@ class nsIPresShell : public nsStubDocume
   bool ObservesNativeAnonMutationsForPrint() {
     return mObservesMutationsForPrint;
   }
 
   virtual nsresult SetIsActive(bool aIsActive) = 0;
 
   bool IsActive() { return mIsActive; }
 
-  // mouse capturing
-  static CapturingContentInfo gCaptureInfo;
-
-  /**
-   * Return the active content currently capturing the mouse if any.
-   */
-  static nsIContent* GetCapturingContent() { return gCaptureInfo.mContent; }
-
-  /**
-   * Allow or disallow mouse capturing.
-   */
-  static void AllowMouseCapture(bool aAllowed) {
-    gCaptureInfo.mAllowed = aAllowed;
-  }
-
-  /**
-   * Returns true if there is an active mouse capture that wants to prevent
-   * drags.
-   */
-  static bool IsMouseCapturePreventingDrag() {
-    return gCaptureInfo.mPreventDrag && gCaptureInfo.mContent;
-  }
-
   /**
    * Keep track of how many times this presshell has been rendered to
    * a window.
    */
   uint64_t GetPaintCount() { return mPaintCount; }
   void IncrementPaintCount() { ++mPaintCount; }
 
   /**
@@ -1241,17 +1162,16 @@ class nsIPresShell : public nsStubDocume
   virtual void WillPaintWindow() = 0;
   /**
    * Notify that we called Paint with PaintFlags::PaintComposite.
    * Fires on the presshell for the painted widget.
    * This is issued at a time when it's safe to modify widget geometry.
    */
   virtual void DidPaintWindow() = 0;
 
-  virtual void ClearMouseCaptureOnView(nsView* aView) = 0;
   virtual bool IsVisible() = 0;
   MOZ_CAN_RUN_SCRIPT
   void DispatchSynthMouseMove(mozilla::WidgetGUIEvent* aEvent);
 
   /* Temporarily ignore the Displayport for better paint performance. We
    * trigger a repaint once suppression is disabled. Without that
    * the displayport may get left at the suppressed size for an extended
    * period of time and result in unnecessary checkerboarding (see bug
@@ -1403,20 +1323,16 @@ class nsIPresShell : public nsStubDocume
   bool AddRefreshObserver(nsARefreshObserver* aObserver,
                           mozilla::FlushType aFlushType);
   bool RemoveRefreshObserver(nsARefreshObserver* aObserver,
                              mozilla::FlushType aFlushType);
 
   bool AddPostRefreshObserver(nsAPostRefreshObserver* aObserver);
   bool RemovePostRefreshObserver(nsAPostRefreshObserver* aObserver);
 
-  // If a frame in the subtree rooted at aFrame is capturing the mouse then
-  // clears that capture.
-  static void ClearMouseCapture(nsIFrame* aFrame);
-
   void SetVisualViewportSize(nscoord aWidth, nscoord aHeight);
   void ResetVisualViewportSize();
   bool IsVisualViewportSizeSet() { return mVisualViewportSizeSet; }
   nsSize GetVisualViewportSize() {
     NS_ASSERTION(mVisualViewportSizeSet,
                  "asking for visual viewport size when its not set?");
     return mVisualViewportSize;
   }
@@ -1556,18 +1472,18 @@ class nsIPresShell : public nsStubDocume
   void RemoveSheet(mozilla::StyleOrigin, mozilla::StyleSheet*);
   void RemovePreferenceStyles();
 
   void WillDoReflow();
 
   // This data is stored as a content property (nsGkAtoms::scrolling) on
   // mContentToScrollTo when we have a pending ScrollIntoView.
   struct ScrollIntoViewData {
-    ScrollAxis mContentScrollVAxis;
-    ScrollAxis mContentScrollHAxis;
+    mozilla::ScrollAxis mContentScrollVAxis;
+    mozilla::ScrollAxis mContentScrollHAxis;
     mozilla::ScrollFlags mContentToScrollToFlags;
   };
 
   static mozilla::LazyLogModule gLog;
 
   DOMHighResTimeStamp GetPerformanceNowUnclamped();
 
   // The callback for the mReflowContinueTimer timer.
--- a/layout/forms/nsHTMLButtonControlFrame.cpp
+++ b/layout/forms/nsHTMLButtonControlFrame.cpp
@@ -306,21 +306,26 @@ void nsHTMLButtonControlFrame::ReflowBut
       wm,
       LogicalSize(wm, aButtonReflowInput.ComputedISize() + clbp.IStartEnd(wm),
                   buttonContentBox.BSize(wm) + clbp.BStartEnd(wm)));
 
   //  * Button's ascent is its child's ascent, plus the child's block-offset
   // within our frame... unless it's orthogonal, in which case we'll use the
   // contents inline-size as an approximation for now.
   // XXX is there a better strategy? should we include border-padding?
-  if (aButtonReflowInput.mStyleDisplay->IsContainSize()) {
-    // If we're size-contained, we should pretend our contents had 0 height
-    // (as they would, if we had no children). This case is identical to the
-    // final else case, but uses only our specified button height for ascent
-    // (ie. it ignores the height returned in contentsDesiredSize).
+  if (aButtonReflowInput.mStyleDisplay->IsContainLayout()) {
+    // If we're layout-contained, then for the purposes of computing the
+    // ascent, we should pretend our button-contents frame had 0 height. In
+    // other words, we use the <button> content-rect's central block-axis
+    // position as our baseline.
+    // NOTE: This should be the same ascent that we'd get from the final 'else'
+    // clause here, if we had no DOM children. In that no-children scenario,
+    // the final 'else' clause's BlockStartAscent() term would be 0, and its
+    // childPos.B(wm) term would be equal to the same central offset that we're
+    // independently calculating here.
     nscoord containAscent = (buttonContentBox.BSize(wm) / 2) + clbp.BStart(wm);
     aButtonDesiredSize.SetBlockStartAscent(containAscent);
   } else if (aButtonDesiredSize.GetWritingMode().IsOrthogonalTo(wm)) {
     aButtonDesiredSize.SetBlockStartAscent(contentsDesiredSize.ISize(wm));
   } else {
     aButtonDesiredSize.SetBlockStartAscent(
         contentsDesiredSize.BlockStartAscent() + childPos.B(wm));
   }
--- a/layout/forms/nsListControlFrame.cpp
+++ b/layout/forms/nsListControlFrame.cpp
@@ -819,17 +819,17 @@ void nsListControlFrame::CaptureMouseEve
   // capture requested via other code paths, if any exist).
   if (aGrabMouseEvents && IsInDropDownMode() &&
       nsComboboxControlFrame::ToolkitHasNativePopup())
     return;
 
   if (aGrabMouseEvents) {
     PresShell::SetCapturingContent(mContent, CaptureFlags::IgnoreAllowedState);
   } else {
-    nsIContent* capturingContent = nsIPresShell::GetCapturingContent();
+    nsIContent* capturingContent = PresShell::GetCapturingContent();
 
     bool dropDownIsHidden = false;
     if (IsInDropDownMode()) {
       dropDownIsHidden = !mComboboxFrame->IsDroppedDown();
     }
     if (capturingContent == mContent || dropDownIsHidden) {
       // only clear the capturing content if *we* are the ones doing the
       // capturing (or if the dropdown is hidden, in which case NO-ONE should
@@ -1601,17 +1601,17 @@ void nsListControlFrame::FireMenuItemAct
   FireDOMEvent(NS_LITERAL_STRING("DOMMenuItemActive"), optionContent);
 }
 #endif
 
 nsresult nsListControlFrame::GetIndexFromDOMEvent(dom::Event* aMouseEvent,
                                                   int32_t& aCurIndex) {
   if (IgnoreMouseEventForSelection(aMouseEvent)) return NS_ERROR_FAILURE;
 
-  if (nsIPresShell::GetCapturingContent() != mContent) {
+  if (PresShell::GetCapturingContent() != mContent) {
     // If we're not capturing, then ignore movement in the border
     nsPoint pt =
         nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(aMouseEvent, this);
     nsRect borderInnerEdge = GetScrollPortRect();
     if (!borderInnerEdge.Contains(pt)) {
       return NS_ERROR_FAILURE;
     }
   }
@@ -1808,18 +1808,18 @@ void nsListControlFrame::ScrollToIndex(i
   }
 }
 
 void nsListControlFrame::ScrollToFrame(dom::HTMLOptionElement& aOptElement) {
   // otherwise we find the content's frame and scroll to it
   nsIFrame* childFrame = aOptElement.GetPrimaryFrame();
   if (childFrame) {
     PresShell()->ScrollFrameRectIntoView(
-        childFrame, nsRect(nsPoint(0, 0), childFrame->GetSize()),
-        nsIPresShell::ScrollAxis(), nsIPresShell::ScrollAxis(),
+        childFrame, nsRect(nsPoint(0, 0), childFrame->GetSize()), ScrollAxis(),
+        ScrollAxis(),
         ScrollFlags::ScrollOverflowHidden |
             ScrollFlags::ScrollFirstAncestorOnly |
             ScrollFlags::IgnoreMarginAndPadding);
   }
 }
 
 //---------------------------------------------------------------------
 // Ok, the entire idea of this routine is to move to the next item that
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -2129,17 +2129,17 @@ void nsIFrame::OnVisibilityChange(Visibi
                                   const Maybe<OnNonvisible>& aNonvisibleAction
                                   /* = Nothing() */) {
   // XXX(seth): In bug 1218990 we'll implement visibility tracking for CSS
   // images here.
 }
 
 static nsIFrame* GetActiveSelectionFrame(nsPresContext* aPresContext,
                                          nsIFrame* aFrame) {
-  nsIContent* capturingContent = nsIPresShell::GetCapturingContent();
+  nsIContent* capturingContent = PresShell::GetCapturingContent();
   if (capturingContent) {
     nsIFrame* activeFrame = aPresContext->GetPrimaryFrameFor(capturingContent);
     return activeFrame ? activeFrame : aFrame;
   }
 
   return aFrame;
 }
 
@@ -4263,17 +4263,17 @@ nsFrame::HandlePress(nsPresContext* aPre
 
   bool useFrameSelection = (selectStyle == StyleUserSelect::Text);
 
   // If the mouse is dragged outside the nearest enclosing scrollable area
   // while making a selection, the area will be scrolled. To do this, capture
   // the mouse on the nearest scrollable frame. If there isn't a scrollable
   // frame, or something else is already capturing the mouse, there's no
   // reason to capture.
-  if (!nsIPresShell::GetCapturingContent()) {
+  if (!PresShell::GetCapturingContent()) {
     nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetNearestScrollableFrame(
         this, nsLayoutUtils::SCROLLABLE_SAME_DOC |
                   nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
     if (scrollFrame) {
       nsIFrame* capturingFrame = do_QueryFrame(scrollFrame);
       PresShell::SetCapturingContent(capturingFrame->GetContent(),
                                      CaptureFlags::IgnoreAllowedState);
     }
@@ -4668,17 +4668,17 @@ NS_IMETHODIMP nsFrame::HandleRelease(nsP
                                      WidgetGUIEvent* aEvent,
                                      nsEventStatus* aEventStatus) {
   if (aEvent->mClass != eMouseEventClass) {
     return NS_OK;
   }
 
   nsIFrame* activeFrame = GetActiveSelectionFrame(aPresContext, this);
 
-  nsCOMPtr<nsIContent> captureContent = nsIPresShell::GetCapturingContent();
+  nsCOMPtr<nsIContent> captureContent = PresShell::GetCapturingContent();
 
   // We can unconditionally stop capturing because
   // we should never be capturing when the mouse button is up
   PresShell::ReleaseCapturingContent();
 
   bool selectionOff =
       (DisplaySelection(aPresContext) == nsISelectionController::SELECTION_OFF);
 
--- a/layout/generic/nsFrameSelection.cpp
+++ b/layout/generic/nsFrameSelection.cpp
@@ -441,17 +441,17 @@ nsresult nsFrameSelection::ConstrainFram
 
   if (content) {
     nsIContent* contentRoot = content->GetSelectionRootContent(mPresShell);
     NS_ENSURE_TRUE(contentRoot, NS_ERROR_UNEXPECTED);
 
     if (anchorRoot == contentRoot) {
       // If the aFrame's content isn't the capturing content, it should be
       // a descendant.  At this time, we can return simply.
-      nsIContent* capturedContent = nsIPresShell::GetCapturingContent();
+      nsIContent* capturedContent = PresShell::GetCapturingContent();
       if (capturedContent != content) {
         return NS_OK;
       }
 
       // Find the frame under the mouse cursor with the root frame.
       // At this time, don't use the anchor's frame because it may not have
       // fixed positioned frames.
       nsIFrame* rootFrame = mPresShell->GetRootFrame();
@@ -697,18 +697,17 @@ nsresult nsFrameSelection::MoveCaret(nsD
         offset = anchorFocusRange->StartOffset();
       } else {
         node = anchorFocusRange->GetEndContainer();
         offset = anchorFocusRange->EndOffset();
       }
       sel->Collapse(node, offset);
     }
     sel->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION,
-                        nsIPresShell::ScrollAxis(), nsIPresShell::ScrollAxis(),
-                        scrollFlags);
+                        ScrollAxis(), ScrollAxis(), scrollFlags);
     return NS_OK;
   }
 
   nsIFrame* frame;
   int32_t offsetused = 0;
   nsresult result =
       sel->GetPrimaryFrameForFocusNode(&frame, &offsetused, visualMovement);
 
@@ -827,18 +826,18 @@ nsresult nsFrameSelection::MoveCaret(nsD
     if (!isBRFrame) {
       mHint = CARET_ASSOCIATE_BEFORE;  // We're now at the end of the frame to
                                        // the left.
     }
     result = NS_OK;
   }
   if (NS_SUCCEEDED(result)) {
     result = mDomSelections[index]->ScrollIntoView(
-        nsISelectionController::SELECTION_FOCUS_REGION,
-        nsIPresShell::ScrollAxis(), nsIPresShell::ScrollAxis(), scrollFlags);
+        nsISelectionController::SELECTION_FOCUS_REGION, ScrollAxis(),
+        ScrollAxis(), scrollFlags);
   }
 
   return result;
 }
 
 nsPrevNextBidiLevels nsFrameSelection::GetPrevNextBidiLevels(
     nsIContent* aNode, uint32_t aContentOffset, bool aJumpLines) const {
   return GetPrevNextBidiLevels(aNode, aContentOffset, mHint, aJumpLines);
@@ -1384,39 +1383,38 @@ Selection* nsFrameSelection::GetSelectio
 nsresult nsFrameSelection::ScrollSelectionIntoView(SelectionType aSelectionType,
                                                    SelectionRegion aRegion,
                                                    int16_t aFlags) const {
   int8_t index = GetIndexFromSelectionType(aSelectionType);
   if (index < 0) return NS_ERROR_INVALID_ARG;
 
   if (!mDomSelections[index]) return NS_ERROR_NULL_POINTER;
 
-  nsIPresShell::ScrollAxis verticalScroll = nsIPresShell::ScrollAxis();
+  ScrollAxis verticalScroll = ScrollAxis();
   int32_t flags = Selection::SCROLL_DO_FLUSH;
   if (aFlags & nsISelectionController::SCROLL_SYNCHRONOUS) {
     flags |= Selection::SCROLL_SYNCHRONOUS;
   } else if (aFlags & nsISelectionController::SCROLL_FIRST_ANCESTOR_ONLY) {
     flags |= Selection::SCROLL_FIRST_ANCESTOR_ONLY;
   }
   if (aFlags & nsISelectionController::SCROLL_OVERFLOW_HIDDEN) {
     flags |= Selection::SCROLL_OVERFLOW_HIDDEN;
   }
   if (aFlags & nsISelectionController::SCROLL_CENTER_VERTICALLY) {
-    verticalScroll = nsIPresShell::ScrollAxis(kScrollToCenter,
-                                              WhenToScroll::IfNotFullyVisible);
+    verticalScroll =
+        ScrollAxis(kScrollToCenter, WhenToScroll::IfNotFullyVisible);
   }
   if (aFlags & nsISelectionController::SCROLL_FOR_CARET_MOVE) {
     flags |= Selection::SCROLL_FOR_CARET_MOVE;
   }
 
   // After ScrollSelectionIntoView(), the pending notifications might be
   // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
   RefPtr<Selection> sel = mDomSelections[index];
-  return sel->ScrollIntoView(aRegion, verticalScroll,
-                             nsIPresShell::ScrollAxis(), flags);
+  return sel->ScrollIntoView(aRegion, verticalScroll, ScrollAxis(), flags);
 }
 
 nsresult nsFrameSelection::RepaintSelection(SelectionType aSelectionType) {
   int8_t index = GetIndexFromSelectionType(aSelectionType);
   if (index < 0) return NS_ERROR_INVALID_ARG;
   if (!mDomSelections[index]) return NS_ERROR_NULL_POINTER;
   NS_ENSURE_STATE(mPresShell);
 
--- a/layout/generic/nsFrameSetFrame.cpp
+++ b/layout/generic/nsFrameSetFrame.cpp
@@ -1182,17 +1182,17 @@ void nsHTMLFramesetFrame::StartMouseDrag
   }
 
   gDragInProgress = true;
 }
 
 void nsHTMLFramesetFrame::MouseDrag(nsPresContext* aPresContext,
                                     WidgetGUIEvent* aEvent) {
   // if the capture ended, reset the drag state
-  if (nsIPresShell::GetCapturingContent() != GetContent()) {
+  if (PresShell::GetCapturingContent() != GetContent()) {
     mDragger = nullptr;
     gDragInProgress = false;
     return;
   }
 
   int32_t change;  // measured positive from left-to-right or top-to-bottom
   AutoWeakFrame weakFrame(this);
   if (mDragger->mVertical) {
--- a/layout/generic/nsImageFrame.cpp
+++ b/layout/generic/nsImageFrame.cpp
@@ -2226,17 +2226,17 @@ nsresult nsImageFrame::GetContentForEven
   nsIFrame* f = nsLayoutUtils::GetNonGeneratedAncestor(this);
   if (f != this) {
     return f->GetContentForEvent(aEvent, aContent);
   }
 
   // XXX We need to make this special check for area element's capturing the
   // mouse due to bug 135040. Remove it once that's fixed.
   nsIContent* capturingContent = aEvent->HasMouseEventMessage()
-                                     ? nsIPresShell::GetCapturingContent()
+                                     ? PresShell::GetCapturingContent()
                                      : nullptr;
   if (capturingContent && capturingContent->GetPrimaryFrame() == this) {
     *aContent = capturingContent;
     NS_IF_ADDREF(*aContent);
     return NS_OK;
   }
 
   if (nsImageMap* map = GetImageMap()) {
--- a/layout/generic/nsPluginFrame.cpp
+++ b/layout/generic/nsPluginFrame.cpp
@@ -1509,17 +1509,17 @@ nsresult nsPluginFrame::HandleEvent(nsPr
        anEvent->mMessage == eWheel) &&
       mInstanceOwner->GetEventModel() == NPEventModelCocoa) {
     *anEventStatus = mInstanceOwner->ProcessEvent(*anEvent);
     // Due to plugin code reentering Gecko, this frame may be dead at this
     // point.
     return rv;
   }
 
-  // These two calls to nsIPresShell::SetCapturingContext() (on mouse-down
+  // These two calls to PresShell::SetCapturingContent() (on mouse-down
   // and mouse-up) are needed to make the routing of mouse events while
   // dragging conform to standard OS X practice, and to the Cocoa NPAPI spec.
   // See bug 525078 and bug 909678.
   if (anEvent->mMessage == eMouseDown) {
     PresShell::SetCapturingContent(GetContent(),
                                    CaptureFlags::IgnoreAllowedState);
   }
 #endif
--- a/layout/reftests/w3c-css/submitted/contain/reftest.list
+++ b/layout/reftests/w3c-css/submitted/contain/reftest.list
@@ -13,17 +13,17 @@ pref(layout.css.overflow-clip-box.enable
 == contain-paint-ignored-cases-ib-split-001.html contain-paint-ignored-cases-ib-split-001-ref.html
 == contain-paint-ignored-cases-internal-table-001a.html contain-paint-ignored-cases-internal-table-001-ref.html
 == contain-paint-ignored-cases-internal-table-001b.html contain-paint-ignored-cases-internal-table-001-ref.html
 == contain-paint-ignored-cases-no-principal-box-001.html contain-paint-ignored-cases-no-principal-box-001-ref.html
 == contain-paint-ignored-cases-ruby-containing-block-001.html contain-paint-ignored-cases-ruby-containing-block-001-ref.html
 == contain-paint-ignored-cases-ruby-stacking-and-clipping-001.html contain-paint-ignored-cases-ruby-stacking-and-clipping-001-ref.html
 == contain-paint-stacking-context-001a.html contain-paint-stacking-context-001-ref.html
 == contain-paint-stacking-context-001b.html contain-paint-stacking-context-001-ref.html
-fails == contain-size-button-001.html contain-size-button-001-ref.html # bug 1508441
+== contain-size-button-001.html contain-size-button-001-ref.html
 == contain-size-block-001.html contain-size-block-001-ref.html
 == contain-size-block-002.html contain-size-block-002-ref.html
 == contain-size-block-003.html contain-size-block-003-ref.html
 == contain-size-block-004.html contain-size-block-004-ref.html
 == contain-size-inline-block-001.html contain-size-inline-block-001-ref.html
 == contain-size-inline-block-002.html contain-size-inline-block-002-ref.html
 == contain-size-inline-block-003.html contain-size-inline-block-003-ref.html
 == contain-size-inline-block-004.html contain-size-inline-block-004-ref.html
--- a/layout/style/ServoBindings.toml
+++ b/layout/style/ServoBindings.toml
@@ -207,17 +207,16 @@ whitelist-types = [
     "mozilla::gfx::Float",
     "mozilla::gfx::FontVariation",
     "mozilla::StyleImageLayerAttachment",
     "gfxFontFeature",
     "gfxFontVariation",
     ".*ThreadSafe.*Holder",
     "AnonymousContent",
     "AudioContext",
-    "CapturingContentInfo",
     "DefaultDelete",
     "DOMIntersectionObserverEntry",
     "Element",
     "FontFamilyList",
     "FontFamilyName",
     "mozilla::FontSizePrefs",
     "FragmentOrURL",
     "FrameRequestCallback",
@@ -351,31 +350,31 @@ opaque-types = [
     "mozilla::detail::HashTable", # <- We should be able to remove this and
                                   # HashSet below once
                                   # https://github.com/rust-lang/rust-bindgen/pull/1515
                                   # is available
     "mozilla::detail::PointerType",
     "mozilla::HashSet",
     "mozilla::Pair",
     "mozilla::Pair_Base",
+    "mozilla::ScrollAxis",  # <- For some reason the alignment of this is 4
+                            # for clang.
     "mozilla::SeenPtrs",
     "mozilla::SupportsWeakPtr",
     "mozilla::Tuple",
     "SupportsWeakPtr",
     "mozilla::detail::WeakReference",
     "mozilla::WeakPtr",
     "nsWritingIterator_reference", "nsReadingIterator_reference",
     "nsTObserverArray",  # <- Inherits from nsAutoTObserverArray<T, 0>
     "nsTHashtable",  # <- Inheriting from inner typedefs that clang
                      #    doesn't expose properly.
     "nsBaseHashtable", "nsRefPtrHashtable", "nsDataHashtable", "nsClassHashtable",  # <- Ditto
     "nsInterfaceHashtable",  # <- Ditto
     "mozilla::dom::Document_SelectorCache",  # <- Inherits from nsExpirationTracker<.., 4>
-    "nsIPresShell_ScrollAxis",  # <- For some reason the alignment of this is 4
-                                # for clang.
     "nsPIDOMWindow",  # <- Takes the vtable from a template parameter, and we can't
                       #    generate it conditionally.
     "JS::Rooted",
     "mozilla::Maybe",
     "gfxSize",  # <- union { struct { T width; T height; }; T components[2] };
     "gfxSize_Super",  # Ditto.
     "mozilla::StyleAnimationValue",
     "StyleAnimationValue", # pulls in a whole bunch of stuff we don't need in the bindings
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -780,17 +780,18 @@ bool nsComputedDOMStyle::NeedsToFlush() 
   // We always compute styles from the element's owner document.
   if (ElementNeedsRestyle(mElement, mPseudo)) {
     return true;
   }
 
   Document* doc = mElement->OwnerDoc();
   // If parent document is there, also needs to check if there is some change
   // that needs to flush this document (e.g. size change for iframe).
-  while (Document* parentDocument = doc->GetParentDocument()) {
+  while (doc->StyleOrLayoutObservablyDependsOnParentDocumentLayout()) {
+    Document* parentDocument = doc->GetParentDocument();
     Element* element = parentDocument->FindContentForSubDocument(doc);
     if (ElementNeedsRestyle(element, nullptr)) {
       return true;
     }
     doc = parentDocument;
   }
 
   return false;
--- a/layout/xul/nsDeckFrame.cpp
+++ b/layout/xul/nsDeckFrame.cpp
@@ -72,17 +72,17 @@ void nsDeckFrame::Init(nsIContent* aCont
   nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
 
   mIndex = GetSelectedIndex();
 }
 
 void nsDeckFrame::ShowBox(nsIFrame* aBox) { Animate(aBox, true); }
 
 void nsDeckFrame::HideBox(nsIFrame* aBox) {
-  nsIPresShell::ClearMouseCapture(aBox);
+  mozilla::PresShell::ClearMouseCapture(aBox);
   Animate(aBox, false);
 }
 
 void nsDeckFrame::IndexChanged() {
   // did the index change?
   int32_t index = GetSelectedIndex();
 
   if (index == mIndex) return;
--- a/layout/xul/nsMenuPopupFrame.cpp
+++ b/layout/xul/nsMenuPopupFrame.cpp
@@ -1809,17 +1809,17 @@ nsIScrollableFrame* nsMenuPopupFrame::Ge
 
   return nullptr;
 }
 
 void nsMenuPopupFrame::EnsureMenuItemIsVisible(nsMenuFrame* aMenuItem) {
   if (aMenuItem) {
     aMenuItem->PresShell()->ScrollFrameRectIntoView(
         aMenuItem, nsRect(nsPoint(0, 0), aMenuItem->GetRect().Size()),
-        nsIPresShell::ScrollAxis(), nsIPresShell::ScrollAxis(),
+        ScrollAxis(), ScrollAxis(),
         ScrollFlags::ScrollOverflowHidden |
             ScrollFlags::ScrollFirstAncestorOnly |
             ScrollFlags::IgnoreMarginAndPadding);
   }
 }
 
 void nsMenuPopupFrame::ChangeByPage(bool aIsUp) {
   // Only scroll by page within menulists.
--- a/layout/xul/nsSliderFrame.cpp
+++ b/layout/xul/nsSliderFrame.cpp
@@ -1146,17 +1146,17 @@ void nsSliderFrame::DragThumb(bool aGrab
     PresShell::SetCapturingContent(GetContent(),
                                    CaptureFlags::IgnoreAllowedState);
   } else {
     PresShell::ReleaseCapturingContent();
   }
 }
 
 bool nsSliderFrame::isDraggingThumb() const {
-  return (nsIPresShell::GetCapturingContent() == GetContent());
+  return PresShell::GetCapturingContent() == GetContent();
 }
 
 void nsSliderFrame::AddListener() {
   if (!mMediator) {
     mMediator = new nsSliderMediator(this);
   }
 
   nsIFrame* thumbFrame = mFrames.FirstChild();
--- a/mobile/android/components/geckoview/GeckoViewStartup.js
+++ b/mobile/android/components/geckoview/GeckoViewStartup.js
@@ -129,41 +129,29 @@ GeckoViewStartup.prototype = {
 
         ChromeUtils.import("resource://gre/modules/NotificationDB.jsm");
 
         // Initialize safe browsing module. This is required for content
         // blocking features and manages blocklist downloads and updates.
         SafeBrowsing.init();
 
         // Listen for global EventDispatcher messages
-        EventDispatcher.instance.registerListener(this, [
-          "GeckoView:ClearSessionContextData",
-          "GeckoView:ResetUserPrefs",
-          "GeckoView:SetDefaultPrefs",
-          "GeckoView:SetLocale",
-        ]);
+        EventDispatcher.instance.registerListener(this,
+          ["GeckoView:ResetUserPrefs",
+           "GeckoView:SetDefaultPrefs",
+           "GeckoView:SetLocale"]);
         break;
       }
     }
   },
 
   onEvent(aEvent, aData, aCallback) {
     debug `onEvent ${aEvent}`;
 
     switch (aEvent) {
-      case "GeckoView:ClearSessionContextData": {
-        let pattern = {};
-        if (aData.contextId !== null) {
-          pattern = { geckoViewSessionContextId: aData.contextId };
-        }
-        Services.clearData.deleteDataFromOriginAttributesPattern(pattern);
-        Services.qms.clearStoragesForOriginAttributesPattern(
-          JSON.stringify(pattern));
-        break;
-      }
       case "GeckoView:ResetUserPrefs": {
         const prefs = new Preferences();
         prefs.reset(aData.names);
         break;
       }
       case "GeckoView:SetDefaultPrefs": {
         const prefs = new Preferences({ defaultBranch: true });
         for (const name of Object.keys(aData)) {
--- a/mobile/android/geckoview/api.txt
+++ b/mobile/android/geckoview/api.txt
@@ -72,17 +72,16 @@ import org.mozilla.geckoview.MediaElemen
 import org.mozilla.geckoview.OverscrollEdgeEffect;
 import org.mozilla.geckoview.PanZoomController;
 import org.mozilla.geckoview.RuntimeSettings;
 import org.mozilla.geckoview.RuntimeTelemetry;
 import org.mozilla.geckoview.ScreenLength;
 import org.mozilla.geckoview.SessionAccessibility;
 import org.mozilla.geckoview.SessionFinder;
 import org.mozilla.geckoview.SessionTextInput;
-import org.mozilla.geckoview.StorageController;
 import org.mozilla.geckoview.WebExtension;
 import org.mozilla.geckoview.WebMessage;
 import org.mozilla.geckoview.WebRequest;
 import org.mozilla.geckoview.WebRequestError;
 import org.mozilla.geckoview.WebResponse;
 
 package org.mozilla.geckoview {
 
@@ -262,25 +261,25 @@ package org.mozilla.geckoview {
     method @AnyThread @Nullable public GeckoResult<U> onValue(@Nullable T);
   }
 
   public static final class GeckoResult.UncaughtException extends RuntimeException {
     ctor public UncaughtException(Throwable);
   }
 
   public final class GeckoRuntime implements Parcelable {
+    ctor public GeckoRuntime();
     method @UiThread public void attachTo(@NonNull Context);
     method @UiThread public void configurationChanged(@NonNull Configuration);
     method @UiThread @NonNull public static GeckoRuntime create(@NonNull Context);
     method @UiThread @NonNull public static GeckoRuntime create(@NonNull Context, @NonNull GeckoRuntimeSettings);
     method @UiThread @NonNull public static synchronized GeckoRuntime getDefault(@NonNull Context);
     method @UiThread @Nullable public GeckoRuntime.Delegate getDelegate();
     method @UiThread @Nullable public File getProfileDir();
     method @AnyThread @NonNull public GeckoRuntimeSettings getSettings();
-    method @AnyThread @NonNull public StorageController getStorageController();
     method @UiThread @NonNull public RuntimeTelemetry getTelemetry();
     method @UiThread public void orientationChanged();
     method @UiThread public void orientationChanged(int);
     method @AnyThread public void readFromParcel(@NonNull Parcel);
     method @UiThread @NonNull public GeckoResult<Void> registerWebExtension(@NonNull WebExtension);
     method @UiThread public void setDelegate(@Nullable GeckoRuntime.Delegate);
     method @AnyThread public void shutdown();
     method @UiThread @NonNull public GeckoResult<Void> unregisterWebExtension(@NonNull WebExtension);
@@ -763,17 +762,16 @@ package org.mozilla.geckoview {
     field @NonNull public final String uri;
   }
 
   @AnyThread public final class GeckoSessionSettings implements Parcelable {
     ctor public GeckoSessionSettings();
     ctor public GeckoSessionSettings(@NonNull GeckoSessionSettings);
     method public boolean getAllowJavascript();
     method @Nullable public String getChromeUri();
-    method @Nullable public String getContextId();
     method public int getDisplayMode();
     method public boolean getFullAccessibilityTree();
     method public int getScreenId();
     method public boolean getSuspendMediaWhenInactive();
     method public boolean getUseMultiprocess();
     method public boolean getUsePrivateMode();
     method public boolean getUseTrackingProtection();
     method public int getUserAgentMode();
@@ -801,17 +799,16 @@ package org.mozilla.geckoview {
   }
 
   @AnyThread public static final class GeckoSessionSettings.Builder {
     ctor public Builder();
     ctor public Builder(GeckoSessionSettings);
     method @NonNull public GeckoSessionSettings.Builder allowJavascript(boolean);
     method @NonNull public GeckoSessionSettings build();
     method @NonNull public GeckoSessionSettings.Builder chromeUri(@NonNull String);
-    method @NonNull public GeckoSessionSettings.Builder contextId(@Nullable String);
     method @NonNull public GeckoSessionSettings.Builder displayMode(int);
     method @NonNull public GeckoSessionSettings.Builder fullAccessibilityTree(boolean);
     method @NonNull public GeckoSessionSettings.Builder screenId(int);
     method @NonNull public GeckoSessionSettings.Builder suspendMediaWhenInactive(boolean);
     method @NonNull public GeckoSessionSettings.Builder useMultiprocess(boolean);
     method @NonNull public GeckoSessionSettings.Builder usePrivateMode(boolean);
     method @NonNull public GeckoSessionSettings.Builder useTrackingProtection(boolean);
     method @NonNull public GeckoSessionSettings.Builder userAgentMode(int);
@@ -1010,22 +1007,16 @@ package org.mozilla.geckoview {
     method @UiThread public boolean onKeyMultiple(int, int, @NonNull KeyEvent);
     method @UiThread public boolean onKeyPreIme(int, @NonNull KeyEvent);
     method @UiThread public boolean onKeyUp(int, @NonNull KeyEvent);
     method @UiThread public void onProvideAutofillVirtualStructure(@NonNull ViewStructure, int);
     method @UiThread public void setDelegate(@Nullable GeckoSession.TextInputDelegate);
     method @UiThread public synchronized void setView(@Nullable View);
   }
 
-  public final class StorageController {
-    ctor public StorageController();
-    method @AnyThread public void clearAllSessionContextData();
-    method @AnyThread public void clearSessionContextData(@NonNull String);
-  }
-
   public class WebExtension {
     ctor public WebExtension(@NonNull String, @NonNull String, long);
     ctor public WebExtension(@NonNull String);
     method @UiThread public void setMessageDelegate(@Nullable WebExtension.MessageDelegate, @NonNull String);
     field public final long flags;
     field @NonNull public final String id;
     field @NonNull public final String location;
   }
deleted file mode 100644
--- a/mobile/android/geckoview/src/androidTest/assets/www/reflect_local_storage_into_title.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<html>
-  <head>
-    <meta charset="UTF-8">
-    <title>no title</title>
-    <script>
-      // If we have a query string, save it to the local storage.
-      if (window.location.search.length > 0) {
-        const value = window.location.search.substr(1);
-        localStorage.setItem("ctx", value);
-      }
-
-      // Set the title to reflect the local storage value.
-      document.title = "storage=" + localStorage.getItem("ctx");
-    </script>
-  </head>
-  <body></body>
-</html>
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/BaseSessionTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/BaseSessionTest.kt
@@ -50,17 +50,16 @@ open class BaseSessionTest(noErrorCollec
         const val UNKNOWN_HOST_URI = "http://www.test.invalid/"
         const val FULLSCREEN_PATH = "/assets/www/fullscreen.html"
         const val VIEWPORT_PATH = "/assets/www/viewport.html"
         const val IFRAME_REDIRECT_LOCAL = "/assets/www/iframe_redirect_local.html"
         const val IFRAME_REDIRECT_AUTOMATION = "/assets/www/iframe_redirect_automation.html"
         const val AUTOPLAY_PATH = "/assets/www/autoplay.html"
         const val SCROLL_TEST_PATH = "/assets/www/scroll.html"
         const val COLORS_HTML_PATH = "/assets/www/colors.html"
-        const val STORAGE_TITLE_HTML_PATH = "/assets/www/reflect_local_storage_into_title.html"
         const val FIXED_BOTTOM = "/assets/www/fixedbottom.html"
     }
 
     @get:Rule val sessionRule = GeckoSessionTestRule()
 
     @get:Rule val errors = ErrorCollector()
 
     val mainSession get() = sessionRule.session
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt
@@ -1166,149 +1166,9 @@ class NavigationDelegateTest : BaseSessi
         // This loads in the parent process
         mainSession.loadUri("about:config")
         sessionRule.waitForPageStop()
 
         // This will load a page in the child
         mainSession.loadTestPath(HELLO_HTML_PATH)
         sessionRule.waitForPageStop()
     }
-
-    @WithDevToolsAPI
-    @Test fun sessionContextId() {
-        val session1 = sessionRule.createOpenSession(
-                GeckoSessionSettings.Builder(mainSession.settings)
-                .contextId("1")
-                .build())
-        session1.loadTestPath(STORAGE_TITLE_HTML_PATH + "?ctx1")
-        session1.waitForPageStop()
-
-        session1.forCallbacksDuringWait(object: Callbacks.ContentDelegate {
-            @AssertCalled(count = 1)
-            override fun onTitleChange(session: GeckoSession, title: String?) {
-                assertThat("Title should not be empty", title, not(isEmptyOrNullString()))
-                assertThat("Title should match", title,
-                           equalTo("storage=ctx1"))
-            }
-        })
-
-        session1.loadTestPath(STORAGE_TITLE_HTML_PATH)
-        session1.waitForPageStop()
-
-        session1.forCallbacksDuringWait(object: Callbacks.ContentDelegate {
-            @AssertCalled(count = 1)
-            override fun onTitleChange(session: GeckoSession, title: String?) {
-                assertThat("Title should not be empty", title, not(isEmptyOrNullString()))
-                assertThat("Title should match", title,
-                           equalTo("storage=ctx1"))
-            }
-        })
-
-        val session2 = sessionRule.createOpenSession(
-                GeckoSessionSettings.Builder(mainSession.settings)
-                .contextId("2")
-                .build())
-        session2.loadTestPath(STORAGE_TITLE_HTML_PATH)
-        session2.waitForPageStop()
-
-        session2.forCallbacksDuringWait(object: Callbacks.ContentDelegate {
-            @AssertCalled(count = 1)
-            override fun onTitleChange(session: GeckoSession, title: String?) {
-                assertThat("Title should not be empty", title, not(isEmptyOrNullString()))
-                assertThat("Title should match", title,
-                           equalTo("storage=null"))
-            }
-        })
-
-        session2.loadTestPath(STORAGE_TITLE_HTML_PATH + "?ctx2")
-        session2.waitForPageStop()
-
-        session2.forCallbacksDuringWait(object: Callbacks.ContentDelegate {
-            @AssertCalled(count = 1)
-            override fun onTitleChange(session: GeckoSession, title: String?) {
-                assertThat("Title should not be empty", title, not(isEmptyOrNullString()))
-                assertThat("Title should match", title,
-                           equalTo("storage=ctx2"))
-            }
-        })
-
-        session1.loadTestPath(STORAGE_TITLE_HTML_PATH)
-        session1.waitForPageStop()
-
-        session1.forCallbacksDuringWait(object: Callbacks.ContentDelegate {
-            @AssertCalled(count = 1)
-            override fun onTitleChange(session: GeckoSession, title: String?) {
-                assertThat("Title should not be empty", title, not(isEmptyOrNullString()))
-                assertThat("Title should match", title,
-                           equalTo("storage=ctx1"))
-            }
-        })
-    }
-
-    @WithDevToolsAPI
-    @Test fun clearSessionContextData() {
-        val session1 = sessionRule.createOpenSession(
-                GeckoSessionSettings.Builder(mainSession.settings)
-                .contextId("1")
-                .build())
-        session1.loadTestPath(STORAGE_TITLE_HTML_PATH + "?ctx1")
-        session1.waitForPageStop()
-
-        session1.forCallbacksDuringWait(object: Callbacks.ContentDelegate {
-            @AssertCalled(count = 1)
-            override fun onTitleChange(session: GeckoSession, title: String?) {
-                assertThat("Title should not be empty", title, not(isEmptyOrNullString()))
-                assertThat("Title should match", title,
-                           equalTo("storage=ctx1"))
-            }
-        })
-
-        session1.loadTestPath(STORAGE_TITLE_HTML_PATH)
-        session1.waitForPageStop()
-
-        session1.forCallbacksDuringWait(object: Callbacks.ContentDelegate {
-            @AssertCalled(count = 1)
-            override fun onTitleChange(session: GeckoSession, title: String?) {
-                assertThat("Title should not be empty", title, not(isEmptyOrNullString()))
-                assertThat("Title should match", title,
-                           equalTo("storage=ctx1"))
-            }
-        })
-
-        session1.close()
-
-        val session2 = sessionRule.createOpenSession(
-                GeckoSessionSettings.Builder(mainSession.settings)
-                .contextId("1")
-                .build())
-        session2.loadTestPath(STORAGE_TITLE_HTML_PATH)
-        session2.waitForPageStop()
-
-        session2.forCallbacksDuringWait(object: Callbacks.ContentDelegate {
-            @AssertCalled(count = 1)
-            override fun onTitleChange(session: GeckoSession, title: String?) {
-                assertThat("Title should not be empty", title, not(isEmptyOrNullString()))
-                assertThat("Title should match", title,
-                           equalTo("storage=ctx1"))
-            }
-        })
-
-        session2.close()
-
-        sessionRule.runtime.storageController.clearSessionContextData("1")
-
-        val session3 = sessionRule.createOpenSession(
-                GeckoSessionSettings.Builder(mainSession.settings)
-                .contextId("1")
-                .build())
-        session3.loadTestPath(STORAGE_TITLE_HTML_PATH)
-        session3.waitForPageStop()
-
-        session3.forCallbacksDuringWait(object: Callbacks.ContentDelegate {
-            @AssertCalled(count = 1)
-            override fun onTitleChange(session: GeckoSession, title: String?) {
-                assertThat("Title should not be empty", title, not(isEmptyOrNullString()))
-                assertThat("Title should match", title,
-                           equalTo("storage=null"))
-            }
-        })
-    }
 }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java
@@ -88,22 +88,16 @@ public final class GeckoRuntime implemen
      * This is a key for extra data sent with {@link #ACTION_CRASHED}. The value is
      * a boolean indicating whether or not the crash was fatal or not. If true, the
      * main application process was affected by the crash. If false, only an internal
      * process used by Gecko has crashed and the application may be able to recover.
      * @see GeckoSession.ContentDelegate#onCrash(GeckoSession)
      */
     public static final String EXTRA_CRASH_FATAL = "fatal";
 
-    private final StorageController mStorageController;
-
-    private GeckoRuntime() {
-        mStorageController = new StorageController();
-    }
-
     private static GeckoRuntime sDefaultRuntime;
 
     /**
      * Get the default runtime for the given context.
      * This will create and initialize the runtime with the default settings.
      *
      * Note: Only use this for session-less apps.
      *       For regular apps, use create() instead.
@@ -525,27 +519,16 @@ public final class GeckoRuntime implemen
      *                       {@link android.content.res.Configuration}.
      */
     @UiThread
     public void orientationChanged(final int newOrientation) {
         ThreadUtils.assertOnUiThread();
         GeckoScreenOrientation.getInstance().update(newOrientation);
     }
 
-
-    /**
-     * Get the storage controller for this runtime.
-     *
-     * @return The {@link StorageController} for this instance.
-     */
-    @AnyThread
-    public @NonNull StorageController getStorageController() {
-        return mStorageController;
-    }
-
     @Override // Parcelable
     @AnyThread
     public int describeContents() {
         return 0;
     }
 
     @Override // Parcelable
     @AnyThread
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSessionSettings.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSessionSettings.java
@@ -75,37 +75,16 @@ public final class GeckoSessionSettings 
          * @return This Builder instance.
          */
         public @NonNull Builder usePrivateMode(final boolean flag) {
             mSettings.setUsePrivateMode(flag);
             return this;
         }
 
         /**
-         * Set the session context ID for this instance.
-         * Setting a context ID partitions the cookie jars based on the provided
-         * IDs. This isolates the browser storage like cookies and localStorage
-         * between sessions, only sessions that share the same ID share storage
-         * data.
-         *
-         * Warning: Storage data is collected persistently for each context,
-         * to delete context data, call {@link StorageController#clearSessionContextData}
-         * for the given context.
-         *
-         * @param value The custom context ID.
-         *              The default ID is null, which removes isolation for this
-         *              instance.
-         * @return This Builder instance.
-         */
-        public @NonNull Builder contextId(final @Nullable String value) {
-            mSettings.setContextId(value);
-            return this;
-        }
-
-        /**
          * Set whether multi-process support should be enabled.
          *
          * @param flag A flag determining whether multi-process should be enabled.
          *             Default is false.
          * @return This Builder instance.
          */
         public @NonNull Builder useMultiprocess(final boolean flag) {
             mSettings.setUseMultiprocess(flag);
@@ -229,17 +208,16 @@ public final class GeckoSessionSettings 
 
     // This needs to match GeckoViewSettingsChild.js
     /**
      * Mobile-friendly pages will be rendered using a viewport based on their &lt;meta&gt; viewport
      * tag. All other pages will be rendered using a special desktop mode viewport, which has a
      * width of 980 CSS px.
      */
     public static final int VIEWPORT_MODE_MOBILE = 0;
-
     /**
      * All pages will be rendered using the special desktop mode viewport, which has a width of
      * 980 CSS px, regardless of whether the page has a &lt;meta&gt; viewport tag specified or not.
      */
     public static final int VIEWPORT_MODE_DESKTOP = 1;
 
     public static class Key<T> {
         /* package */ final String name;
@@ -331,22 +309,16 @@ public final class GeckoSessionSettings 
     private static final Key<Boolean> ALLOW_JAVASCRIPT =
             new Key<Boolean>("allowJavascript", /* initOnly */ false, /* values */ null);
     /**
      * Key to specify if entire accessible tree should be exposed with no caching.
      */
     private static final Key<Boolean> FULL_ACCESSIBILITY_TREE =
             new Key<Boolean>("fullAccessibilityTree", /* initOnly */ false, /* values */ null);
 
-    /**
-     * Key to specify the session context ID.
-     */
-    private static final Key<String> CONTEXT_ID =
-        new Key<String>("sessionContextId", /* initOnly */ true, /* values */ null);
-
     private final GeckoSession mSession;
     private final GeckoBundle mBundle;
 
     public GeckoSessionSettings() {
         this(null, null);
     }
 
     public GeckoSessionSettings(final @NonNull GeckoSessionSettings settings) {
@@ -370,17 +342,16 @@ public final class GeckoSessionSettings 
         mBundle.putBoolean(USE_MULTIPROCESS.name, true);
         mBundle.putBoolean(SUSPEND_MEDIA_WHEN_INACTIVE.name, false);
         mBundle.putBoolean(ALLOW_JAVASCRIPT.name, true);
         mBundle.putBoolean(FULL_ACCESSIBILITY_TREE.name, false);
         mBundle.putInt(USER_AGENT_MODE.name, USER_AGENT_MODE_MOBILE);
         mBundle.putString(USER_AGENT_OVERRIDE.name, null);
         mBundle.putInt(VIEWPORT_MODE.name, VIEWPORT_MODE_MOBILE);
         mBundle.putInt(DISPLAY_MODE.name, DISPLAY_MODE_BROWSER);
-        mBundle.putString(CONTEXT_ID.name, null);
     }
 
     /**
      * Set whether tracking protection should be enabled.
      *
      * @param value A flag determining whether tracking protection should be enabled.
      *             Default is false.
      */
@@ -464,25 +435,16 @@ public final class GeckoSessionSettings 
      *
      * @return true if private mode is enabled, false if not.
      */
     public boolean getUsePrivateMode() {
         return getBoolean(USE_PRIVATE_MODE);
     }
 
     /**
-     * The context ID for this session.
-     *
-     * @return The context ID for this session.
-     */
-    public @Nullable String getContextId() {
-        return getString(CONTEXT_ID);
-    }
-
-    /**
      * Whether multiprocess is enabled.
      *
      * @return true if multiprocess is enabled, false if not.
      */
     public boolean getUseMultiprocess() {
         return getBoolean(USE_MULTIPROCESS);
     }
 
@@ -630,20 +592,16 @@ public final class GeckoSessionSettings 
      * Specify the user agent override string.
      * Set value to null to use the user agent specified by USER_AGENT_MODE.
      * @param value The string to override the user agent with.
      */
     public void setUserAgentOverride(final @Nullable String value) {
         setString(USER_AGENT_OVERRIDE, value);
     }
 
-    private void setContextId(final @Nullable String value) {
-        setString(CONTEXT_ID, value);
-    }
-
     private void setString(final Key<String> key, final String value) {
         synchronized (mBundle) {
             if (valueChangedLocked(key, value)) {
                 mBundle.putString(key.name, value);
                 dispatchUpdate();
             }
         }
     }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/PanZoomController.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/PanZoomController.java
@@ -1,20 +1,25 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * 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/. */
 
 package org.mozilla.geckoview;
 
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.PrefsHelper;
 import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.mozglue.JNIObject;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.ThreadUtils;
 
+import android.app.UiModeManager;
+import android.content.Context;
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.SystemClock;
 import android.support.annotation.NonNull;
 import android.support.annotation.UiThread;
 import android.support.annotation.IntDef;
 import android.util.Log;
 import android.util.Pair;
 import android.view.MotionEvent;
@@ -26,22 +31,24 @@ import java.lang.annotation.RetentionPol
 import java.util.ArrayList;
 
 @UiThread
 public class PanZoomController {
     private static final String LOGTAG = "GeckoNPZC";
     private static final int EVENT_SOURCE_SCROLL = 0;
     private static final int EVENT_SOURCE_MOTION = 1;
     private static final int EVENT_SOURCE_MOUSE = 2;
+    private static final String PREF_MOUSE_AS_TOUCH = "ui.android.mouse_as_touch";
 
     private final GeckoSession mSession;
     private final Rect mTempRect = new Rect();
     private boolean mAttached;
     private float mPointerScrollFactor = 64.0f;
     private long mLastDownTime;
+    private boolean mTreatMouseAsTouch;
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({SCROLL_BEHAVIOR_SMOOTH, SCROLL_BEHAVIOR_AUTO})
     /* package */ @interface ScrollBehaviorType {}
 
     /**
      * Specifies smooth scrolling which animates content to the desired scroll position.
      */
@@ -223,16 +230,42 @@ public class PanZoomController {
 
         return mNative.handleMouseEvent(event.getActionMasked(), event.getEventTime(),
                                         event.getMetaState(), x, y, event.getButtonState());
     }
 
     protected PanZoomController(final GeckoSession session) {
         mSession = session;
         enableEventQueue();
+        initMouseAsTouch();
+    }
+
+    private void initMouseAsTouch() {
+        mTreatMouseAsTouch = true;
+
+        PrefsHelper.PrefHandler prefHandler = new PrefsHelper.PrefHandlerBase() {
+            @Override
+            public void prefValue(final String pref, final int value) {
+                if (!PREF_MOUSE_AS_TOUCH.equals(pref)) {
+                    return;
+                }
+                if (value == 0) {
+                    mTreatMouseAsTouch = false;
+                } else if (value == 1) {
+                    mTreatMouseAsTouch = true;
+                } else if (value == 2) {
+                    Context c = GeckoAppShell.getApplicationContext();
+                    UiModeManager m = (UiModeManager)c.getSystemService(Context.UI_MODE_SERVICE);
+                    // on TV devices, treat mouse as touch. everywhere else, don't
+                    mTreatMouseAsTouch = (m.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION);
+                }
+            }
+        };
+        PrefsHelper.addObserver(new String[] { PREF_MOUSE_AS_TOUCH }, prefHandler);
+        PrefsHelper.getPref(PREF_MOUSE_AS_TOUCH, prefHandler);
     }
 
     /**
      * Set the current scroll factor. The scroll factor is the maximum scroll amount that
      * one scroll event may generate, in device pixels.
      *
      * @param factor Scroll factor.
      */
@@ -256,16 +289,20 @@ public class PanZoomController {
      * "touch" rather than as "mouse". Pointer coordinates should be relative to the
      * display surface.
      *
      * @param event MotionEvent to process.
      * @return True if the event was handled.
      */
     public boolean onTouchEvent(final @NonNull MotionEvent event) {
         ThreadUtils.assertOnUiThread();
+
+        if (!mTreatMouseAsTouch && event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
+            return handleMouseEvent(event);
+        }
         return handleMotionEvent(event);
     }
 
     /**
      * Process a touch event through the pan-zoom controller. Treat any mouse events as
      * "mouse" rather than as "touch". Pointer coordinates should be relative to the
      * display surface.
      *
deleted file mode 100644
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/StorageController.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * vim: ts=4 sw=4 expandtab:
- * 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/. */
-
-package org.mozilla.geckoview;
-
-import android.support.annotation.AnyThread;
-import android.support.annotation.NonNull;
-
-import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.util.GeckoBundle;
-
-/**
- * Manage and control runtime storage data.
- *
- * Retrieve an instance via {@link GeckoRuntime#getStorageController}.
- */
-public final class StorageController {
-    /**
-     * Clear all browser storage data like cookies and localStorage for the
-     * given context.
-     *
-     * Note: Any open session may re-accumulate previously cleared data. To
-     * ensure that no persistent data is left behind, you need to close all
-     * sessions for the given context prior to clearing data.
-     *
-     * @param contextId The context ID for the storage data to be deleted.
-     *                  For null, all storage data will be cleared.
-     */
-    @AnyThread
-    public void clearSessionContextData(final @NonNull String contextId) {
-        final GeckoBundle bundle = new GeckoBundle(1);
-        bundle.putString("contextId", contextId);
-
-        EventDispatcher.getInstance().dispatch(
-            "GeckoView:ClearSessionContextData", bundle);
-    }
-
-    /**
-     * Clear all browser storage data like cookies and localStorage.
-     *
-     * Note: Any open session may re-accumulate previously cleared data. To
-     * ensure that no persistent data is left behind, you need to close all
-     * sessions prior to clearing data.
-     */
-    @AnyThread
-    public void clearAllSessionContextData() {
-        final GeckoBundle bundle = new GeckoBundle(1);
-        bundle.putString("contextId", null);
-
-        EventDispatcher.getInstance().dispatch(
-            "GeckoView:ClearSessionContextData", bundle);
-    }
-}
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md
@@ -58,24 +58,16 @@ exclude: true
   [`HistoryDelegate`][68.12] and added `gotoHistoryIndex` to [`GeckoSession`][68.13].
 
 [68.12]: ../GeckoSession.HistoryDelegate.html
 [68.13]: ../GeckoSession.html
 
 - [`GeckoView`][65.5] will not create a [`GeckoSession`][65.9] anymore when
   attached to a window without a session.
 
-- Added API for session context assignment
-  [`GeckoSessionSettings.Builder.contextId`][68.14] and deletion of data
-  related to a session context
-  [`StorageController.clearSessionContextData`][68.15].
-
-[68.14]: ../GeckoSessionSettings.Builder.html#contextId-
-[68.15]: ../StorageController.html#clearSessionContextData-java.lang.String-
-
 - Added [`GeckoRuntimeSettings.Builder#configFilePath`][68.16] to set
   a path to a configuration file from which GeckoView will read
   configuration options such as Gecko process arguments, environment
   variables, and preferences.
 
 [68.16]: ../GeckoRuntimeSettings.Builder.html#configFilePath-java.lang.String-
 
 - Added [`unregisterWebExtension`][68.17] to unregister a web extension.
@@ -309,9 +301,9 @@ exclude: true
 [65.23]: ../GeckoSession.FinderResult.html
 
 - Update [`CrashReporter#sendCrashReport`][65.24] to return the crash ID as a
   [`GeckoResult<String>`][65.25].
 
 [65.24]: ../CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String-
 [65.25]: ../GeckoResult.html
 
-[api-version]: 9fe3ccad7809f393e67b5186b56a90adf82eed60
+[api-version]: dfd66add2059abb3318cfffbfc60001d2e25efb2
--- a/mobile/android/modules/geckoview/GeckoViewNavigation.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewNavigation.jsm
@@ -8,17 +8,16 @@ var EXPORTED_SYMBOLS = ["GeckoViewNaviga
 
 const {GeckoViewModule} = ChromeUtils.import("resource://gre/modules/GeckoViewModule.jsm");
 const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   E10SUtils: "resource://gre/modules/sessionstore/Utils.jsm",
   LoadURIDelegate: "resource://gre/modules/LoadURIDelegate.jsm",
   Services: "resource://gre/modules/Services.jsm",
-  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
 });
 
 // Handles navigation requests between Gecko and a GeckoView.
 // Handles GeckoView:GoBack and :GoForward requests dispatched by
 // GeckoView.goBack and .goForward.
 // Dispatches GeckoView:LocationChange to the GeckoView on location change when
 // active.
 // Implements nsIBrowserDOMWindow.
@@ -28,38 +27,26 @@ class GeckoViewNavigation extends GeckoV
 
     // There may be a GeckoViewNavigation module in another window waiting for
     // us to create a browser so it can call presetOpenerWindow(), so allow them
     // to do that now.
     Services.obs.notifyObservers(this.window, "geckoview-window-created");
   }
 
   onInit() {
-    debug `onInit`;
-
     this.registerListener([
       "GeckoView:GoBack",
       "GeckoView:GoForward",
       "GeckoView:GotoHistoryIndex",
       "GeckoView:LoadUri",
       "GeckoView:Reload",
       "GeckoView:Stop",
     ]);
 
     this.messageManager.addMessageListener("Browser:LoadURI", this);
-
-    debug `sessionContextId=${this.settings.sessionContextId}`;
-
-    if (this.settings.sessionContextId !== null) {
-      this.browser.webNavigation.setOriginAttributesBeforeLoading({
-        geckoViewSessionContextId: this.settings.sessionContextId,
-        privateBrowsingId:
-          PrivateBrowsingUtils.isBrowserPrivate(this.browser) ? 1 : 0,
-      });
-    }
   }
 
   // Bundle event handler.
   onEvent(aEvent, aData, aCallback) {
     debug `onEvent: event=${aEvent}, data=${aData}`;
 
     switch (aEvent) {
       case "GeckoView:GoBack":
--- a/mobile/android/modules/geckoview/GeckoViewSettings.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewSettings.jsm
@@ -38,17 +38,16 @@ const USER_AGENT_MODE_VR = 2;
 // Handles GeckoView settings including:
 // * multiprocess
 // * user agent override
 class GeckoViewSettings extends GeckoViewModule {
   onInit() {
     debug `onInit`;
     this._userAgentMode = USER_AGENT_MODE_MOBILE;
     this._userAgentOverride = null;
-    this._sessionContextId = null;
     // Required for safe browsing and tracking protection.
 
     this.registerListener([
       "GeckoView:GetUserAgent",
     ]);
   }
 
   onEvent(aEvent, aData, aCallback) {
@@ -63,17 +62,16 @@ class GeckoViewSettings extends GeckoVie
 
   onSettingsUpdate() {
     const settings = this.settings;
     debug `onSettingsUpdate: ${settings}`;
 
     this.displayMode = settings.displayMode;
     this.userAgentMode = settings.userAgentMode;
     this.userAgentOverride = settings.userAgentOverride;
-    this.sessionContextId = settings.sessionContextId;
   }
 
   get useMultiprocess() {
     return this.browser.isRemoteBrowser;
   }
 
   get userAgent() {
     if (this.userAgentOverride !== null) {
@@ -109,19 +107,11 @@ class GeckoViewSettings extends GeckoVie
 
   get displayMode() {
     return this.window.docShell.displayMode;
   }
 
   set displayMode(aMode) {
     this.window.docShell.displayMode = aMode;
   }
-
-  set sessionContextId(aAttribute) {
-    this._sessionContextId = aAttribute;
-  }
-
-  get sessionContextId() {
-    return this._sessionContextId;
-  }
 }
 
 const {debug, warn} = GeckoViewSettings.initLogging("GeckoViewSettings"); // eslint-disable-line no-unused-vars
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -1102,21 +1102,27 @@ VARCACHE_PREF(
 // Is steps(jump-*) supported in easing functions?
 VARCACHE_PREF(
   "layout.css.step-position-jump.enabled",
    layout_css_step_position_jump_enabled,
   bool, true
 )
 
 // Are dynamic reflow roots enabled?
+#ifdef EARLY_BETA_OR_EARLIER
+#define PREF_VALUE true
+#else
+#define PREF_VALUE false
+#endif
 VARCACHE_PREF(
    "layout.dynamic-reflow-roots.enabled",
    layout_dynamic_reflow_roots_enabled,
-  bool, true
+  bool, PREF_VALUE
 )
+#undef PREF_VALUE
 
 VARCACHE_PREF(
    "layout.lower_priority_refresh_driver_during_load",
    layout_lower_priority_refresh_driver_during_load,
   bool, true
 )
 
 // Pref to control enabling scroll anchoring.
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -279,16 +279,18 @@ pref("dom.script_loader.binast_encoding.
 pref("dom.window.event.enabled", true);
 
 // Fastback caching - if this pref is negative, then we calculate the number
 // of content viewers to cache based on the amount of available memory.
 pref("browser.sessionhistory.max_total_viewers", -1);
 
 pref("ui.use_native_colors", true);
 pref("ui.click_hold_context_menus", false);
+// 0 = false, 1 = true, 2 = autodetect.
+pref("ui.android.mouse_as_touch", 1);
 
 // Pop up context menu on mouseup instead of mousedown, if that's the OS default.
 // Note: ignored on Windows (context menus always use mouseup)
 pref("ui.context_menus.after_mouseup", false);
 // Duration of timeout of incremental search in menus (ms).  0 means infinite.
 pref("ui.menu.incremental_search.timeout", 1000);
 // If true, all popups won't hide automatically on blur
 pref("ui.popup.disable_autohide", false);
--- a/mozglue/misc/WindowsProcessMitigations.cpp
+++ b/mozglue/misc/WindowsProcessMitigations.cpp
@@ -12,28 +12,51 @@
 #if (_WIN32_WINNT < 0x0602)
 BOOL WINAPI GetProcessMitigationPolicy(
     HANDLE hProcess, PROCESS_MITIGATION_POLICY MitigationPolicy, PVOID lpBuffer,
     SIZE_T dwLength);
 #endif  // (_WIN32_WINNT < 0x0602)
 
 namespace mozilla {
 
-MFBT_API bool IsWin32kLockedDown() {
+static const DynamicallyLinkedFunctionPtr<
+    decltype(&::GetProcessMitigationPolicy)>&
+FetchGetProcessMitigationPolicyFunc() {
   static const DynamicallyLinkedFunctionPtr<decltype(
       &::GetProcessMitigationPolicy)>
       pGetProcessMitigationPolicy(L"kernel32.dll",
                                   "GetProcessMitigationPolicy");
+  return pGetProcessMitigationPolicy;
+}
+
+MFBT_API bool IsWin32kLockedDown() {
+  auto& pGetProcessMitigationPolicy = FetchGetProcessMitigationPolicyFunc();
   if (!pGetProcessMitigationPolicy) {
     return false;
   }
 
   PROCESS_MITIGATION_SYSTEM_CALL_DISABLE_POLICY polInfo;
   if (!pGetProcessMitigationPolicy(::GetCurrentProcess(),
                                    ProcessSystemCallDisablePolicy, &polInfo,
                                    sizeof(polInfo))) {
     return false;
   }
 
   return polInfo.DisallowWin32kSystemCalls;
 }
 
+MFBT_API bool IsDynamicCodeDisabled() {
+  auto& pGetProcessMitigationPolicy = FetchGetProcessMitigationPolicyFunc();
+  if (!pGetProcessMitigationPolicy) {
+    return false;
+  }
+
+  PROCESS_MITIGATION_DYNAMIC_CODE_POLICY polInfo;
+  if (!pGetProcessMitigationPolicy(::GetCurrentProcess(),
+                                   ProcessDynamicCodePolicy, &polInfo,
+                                   sizeof(polInfo))) {
+    return false;
+  }
+
+  return polInfo.ProhibitDynamicCode;
+}
+
 }  // namespace mozilla
--- a/mozglue/misc/WindowsProcessMitigations.h
+++ b/mozglue/misc/WindowsProcessMitigations.h
@@ -7,12 +7,13 @@
 #ifndef mozilla_WindowsProcessMitigations_h
 #define mozilla_WindowsProcessMitigations_h
 
 #include "mozilla/Types.h"
 
 namespace mozilla {
 
 MFBT_API bool IsWin32kLockedDown();
+MFBT_API bool IsDynamicCodeDisabled();
 
 }  // namespace mozilla
 
 #endif  // mozilla_WindowsProcessMitigations_h
--- a/mozglue/misc/interceptor/PatcherDetour.h
+++ b/mozglue/misc/interceptor/PatcherDetour.h
@@ -590,23 +590,23 @@ class WindowsDllDetourPatcher final : pu
       }
 
       // Clear the instance pointer so that we don't try to reset a nonexistent
       // hook.
       tramp.Rewind();
       tramp.WriteEncodedPointer(nullptr);
     });
 
-    if (PatchIfTargetIsRecognizedTrampoline(tramp, origBytes, aDest,
-                                            aOutTramp)) {
+    tramp.WritePointer(origBytes.AsEncodedPtr());
+    if (!tramp) {
       return;
     }
 
-    tramp.WritePointer(origBytes.AsEncodedPtr());
-    if (!tramp) {
+    if (PatchIfTargetIsRecognizedTrampoline(tramp, origBytes, aDest,
+                                            aOutTramp)) {
       return;
     }
 
     tramp.StartExecutableCode();
 
 #if defined(_M_IX86)
     int pJmp32 = -1;
     while (origBytes.GetOffset() < 5) {
--- a/mozglue/misc/interceptor/Trampoline.h
+++ b/mozglue/misc/interceptor/Trampoline.h
@@ -7,16 +7,17 @@
 #ifndef mozilla_interceptor_Trampoline_h
 #define mozilla_interceptor_Trampoline_h
 
 #include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/Types.h"
+#include "mozilla/WindowsProcessMitigations.h"
 
 namespace mozilla {
 namespace interceptor {
 
 template <typename MMPolicy>
 class MOZ_STACK_CLASS Trampoline final {
  public:
   Trampoline(const MMPolicy* aMMPolicy, uint8_t* const aLocalBase,
@@ -340,19 +341,28 @@ class MOZ_STACK_CLASS TrampolineCollecti
         mTrampSize(aTrampSize),
         mNumTramps(aNumTramps),
         mPrevProt(0),
         mCS(nullptr) {
     if (!aNumTramps) {
       return;
     }
 
-    DebugOnly<BOOL> ok = mMMPolicy.Protect(aLocalBase, aNumTramps * aTrampSize,
-                                           PAGE_EXECUTE_READWRITE, &mPrevProt);
-    MOZ_ASSERT(ok);
+    BOOL ok = mMMPolicy.Protect(aLocalBase, aNumTramps * aTrampSize,
+                                PAGE_EXECUTE_READWRITE, &mPrevProt);
+    if (!ok) {
+      // When destroying a sandboxed process that uses
+      // MITIGATION_DYNAMIC_CODE_DISABLE, we won't be allowed to write to our
+      // executable memory so we just do nothing.  If we fail to get access
+      // to memory for any other reason, we still don't want to crash but we
+      // do assert.
+      MOZ_ASSERT(IsDynamicCodeDisabled());
+      mNumTramps = 0;
+      mPrevProt = 0;
+    }
   }
 
   ~TrampolineCollection() {
     if (!mPrevProt) {
       return;
     }
 
     mMMPolicy.Protect(mLocalBase, mNumTramps * mTrampSize, mPrevProt,
@@ -400,17 +410,17 @@ class MOZ_STACK_CLASS TrampolineCollecti
     aOther.mCS = nullptr;
   }
 
  private:
   const MMPolicy& mMMPolicy;
   uint8_t* const mLocalBase;
   const uintptr_t mRemoteBase;
   const uint32_t mTrampSize;
-  const uint32_t mNumTramps;
+  uint32_t mNumTramps;
   uint32_t mPrevProt;
   CRITICAL_SECTION* mCS;
 
   friend class TrampolineIterator;
 };
 
 }  // namespace interceptor
 }  // namespace mozilla
--- a/netwerk/base/SimpleChannelParent.cpp
+++ b/netwerk/base/SimpleChannelParent.cpp
@@ -65,16 +65,23 @@ NS_IMETHODIMP
 SimpleChannelParent::SetClassifierMatchedInfo(const nsACString& aList,
                                               const nsACString& aProvider,
                                               const nsACString& aPrefix) {
   // nothing to do
   return NS_OK;
 }
 
 NS_IMETHODIMP
+SimpleChannelParent::SetClassifierMatchedTrackingInfo(
+    const nsACString& aLists, const nsACString& aFullHashes) {
+  // nothing to do
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 SimpleChannelParent::Delete() {
   // Nothing to do.
   return NS_OK;
 }
 
 void SimpleChannelParent::ActorDestroy(ActorDestroyReason aWhy) {}
 
 NS_IMETHODIMP
--- a/netwerk/base/nsIClassifiedChannel.idl
+++ b/netwerk/base/nsIClassifiedChannel.idl
@@ -39,9 +39,29 @@ interface nsIClassifiedChannel : nsISupp
    */
   readonly attribute ACString matchedProvider;
 
   /**
    * Full hash of URL that matched
    */
   readonly attribute ACString matchedFullHash;
 
+  /**
+   * Sets matched tracking info of the classified channel.
+   *
+   * @param aLists
+   *        Name of the Tracking Protection list that matched (e.g. content-track-digest256).
+   * @param aFullHash
+   *        Full hash of URLs that matched Tracking Protection list.
+   */
+  void setMatchedTrackingInfo(in Array<ACString> aLists,
+                              in Array<ACString> aFullHashes);
+
+  /**
+   * Name of the lists that matched
+   */
+  readonly attribute Array<ACString> matchedTrackingLists;
+
+  /**
+   * Full hash of URLs that matched
+   */
+  readonly attribute Array<ACString> matchedTrackingFullHashes;
 };
--- a/netwerk/base/nsIParentChannel.idl
+++ b/netwerk/base/nsIParentChannel.idl
@@ -60,16 +60,26 @@ interface nsIParentChannel : nsIStreamLi
    *        Name of provider that matched
    * @param aFullHash
    *        String represents full hash that matched
    */
   [noscript] void setClassifierMatchedInfo(in ACString aList,
                                            in ACString aProvider,
                                            in ACString aFullHash);
 
+   /**
+   * Called to set matched tracking information when URL matches tracking annotation list.
+   * @param aList
+   *        Comma-separated list of tables that matched
+   * @param aFullHashes
+   *        Comma-separated list of base64 encoded full hashes that matched
+   */
+  [noscript] void setClassifierMatchedTrackingInfo(in ACString aLists,
+                                                   in ACString aFullHashes);
+
   /**
    * Called to notify the HttpChannelChild that the resource being loaded
    * has been classified.
    * @param aClassificationFlags
    *        What classifier identifies this channel.
    * @param aIsThirdParty
    *        Whether or not the resourced is considered first-party
    *        with the URI of the window.
--- a/netwerk/protocol/data/DataChannelParent.cpp
+++ b/netwerk/protocol/data/DataChannelParent.cpp
@@ -65,16 +65,23 @@ NS_IMETHODIMP
 DataChannelParent::SetClassifierMatchedInfo(const nsACString &aList,
                                             const nsACString &aProvider,
                                             const nsACString &aFullHash) {
   // nothing to do
   return NS_OK;
 }
 
 NS_IMETHODIMP
+DataChannelParent::SetClassifierMatchedTrackingInfo(
+    const nsACString &aLists, const nsACString &aFullHashes) {
+  // nothing to do
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 DataChannelParent::Delete() {
   // Nothing to do.
   return NS_OK;
 }
 
 void DataChannelParent::ActorDestroy(ActorDestroyReason why) {}
 
 NS_IMETHODIMP
--- a/netwerk/protocol/file/FileChannelParent.cpp
+++ b/netwerk/protocol/file/FileChannelParent.cpp
@@ -65,16 +65,23 @@ NS_IMETHODIMP
 FileChannelParent::SetClassifierMatchedInfo(const nsACString &aList,
                                             const nsACString &aProvider,
                                             const nsACString &aFullHash) {
   // nothing to do
   return NS_OK;
 }
 
 NS_IMETHODIMP
+FileChannelParent::SetClassifierMatchedTrackingInfo(
+    const nsACString &aLists, const nsACString &aFullHashes) {
+  // nothing to do
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 FileChannelParent::Delete() {
   // Nothing to do.
   return NS_OK;
 }
 
 void FileChannelParent::ActorDestroy(ActorDestroyReason why) {}
 
 NS_IMETHODIMP
--- a/netwerk/protocol/ftp/FTPChannelParent.cpp
+++ b/netwerk/protocol/ftp/FTPChannelParent.cpp
@@ -532,16 +532,23 @@ NS_IMETHODIMP
 FTPChannelParent::SetClassifierMatchedInfo(const nsACString& aList,
                                            const nsACString& aProvider,
                                            const nsACString& aFullHash) {
   // One day, this should probably be filled in.
   return NS_OK;
 }
 
 NS_IMETHODIMP
+FTPChannelParent::SetClassifierMatchedTrackingInfo(
+    const nsACString& aLists, const nsACString& aFullHashes) {
+  // One day, this should probably be filled in.
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 FTPChannelParent::Delete() {
   if (mIPCClosed || !SendDeleteSelf()) return NS_ERROR_UNEXPECTED;
 
   return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 // FTPChannelParent::nsIInterfaceRequestor
--- a/netwerk/protocol/http/HttpBackgroundChannelChild.cpp
+++ b/netwerk/protocol/http/HttpBackgroundChannelChild.cpp
@@ -396,16 +396,35 @@ IPCResult HttpBackgroundChannelChild::Re
   // SetClassifierMatchedInfo has no order dependency to OnStartRequest.
   // It this be handled as soon as possible
   mChannelChild->ProcessSetClassifierMatchedInfo(info.list(), info.provider(),
                                                  info.fullhash());
 
   return IPC_OK();
 }
 
+IPCResult HttpBackgroundChannelChild::RecvSetClassifierMatchedTrackingInfo(
+    const ClassifierInfo& info) {
+  LOG(
+      ("HttpBackgroundChannelChild::RecvSetClassifierMatchedTrackingInfo "
+       "[this=%p]\n",
+       this));
+  MOZ_ASSERT(OnSocketThread());
+
+  if (NS_WARN_IF(!mChannelChild)) {
+    return IPC_OK();
+  }
+
+  // SetClassifierMatchedTrackingInfo has no order dependency to OnStartRequest.
+  // It this be handled as soon as possible
+  mChannelChild->ProcessSetClassifierMatchedTrackingInfo(info.list(),
+                                                         info.fullhash());
+
+  return IPC_OK();
+}
 void HttpBackgroundChannelChild::ActorDestroy(ActorDestroyReason aWhy) {
   LOG(("HttpBackgroundChannelChild::ActorDestroy[this=%p]\n", this));
   // This function might be called during shutdown phase, so OnSocketThread()
   // might return false even on STS thread. Use IsOnCurrentThreadInfallible()
   // to get correct information.
   MOZ_ASSERT(gSocketTransportService);
   MOZ_ASSERT(gSocketTransportService->IsOnCurrentThreadInfallible());
 
--- a/netwerk/protocol/http/HttpBackgroundChannelChild.h
+++ b/netwerk/protocol/http/HttpBackgroundChannelChild.h
@@ -74,16 +74,18 @@ class HttpBackgroundChannelChild final :
   IPCResult RecvNotifyClassificationFlags(const uint32_t& aClassificationFlags,
                                           const bool& aIsThirdParty);
 
   IPCResult RecvNotifyFlashPluginStateChanged(
       const nsIHttpChannel::FlashPluginState& aState);
 
   IPCResult RecvSetClassifierMatchedInfo(const ClassifierInfo& info);
 
+  IPCResult RecvSetClassifierMatchedTrackingInfo(const ClassifierInfo& info);
+
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
  private:
   virtual ~HttpBackgroundChannelChild();
 
   // Initiate the creation of the PBckground IPC channel.
   // Return false if failed.
   bool CreateBackgroundChannel();
--- a/netwerk/protocol/http/HttpBackgroundChannelParent.cpp
+++ b/netwerk/protocol/http/HttpBackgroundChannelParent.cpp
@@ -493,16 +493,50 @@ bool HttpBackgroundChannelParent::OnSetC
   ClassifierInfo info;
   info.list() = aList;
   info.fullhash() = aFullHash;
   info.provider() = aProvider;
 
   return SendSetClassifierMatchedInfo(info);
 }
 
+bool HttpBackgroundChannelParent::OnSetClassifierMatchedTrackingInfo(
+    const nsACString& aLists, const nsACString& aFullHashes) {
+  LOG(
+      ("HttpBackgroundChannelParent::OnSetClassifierMatchedTrackingInfo "
+       "[this=%p]\n",
+       this));
+  AssertIsInMainProcess();
+
+  if (NS_WARN_IF(!mIPCOpened)) {
+    return false;
+  }
+
+  if (!IsOnBackgroundThread()) {
+    MutexAutoLock lock(mBgThreadMutex);
+    nsresult rv = mBackgroundThread->Dispatch(
+        NewRunnableMethod<const nsCString, const nsCString>(
+            "net::HttpBackgroundChannelParent::"
+            "OnSetClassifierMatchedTrackingInfo",
+            this,
+            &HttpBackgroundChannelParent::OnSetClassifierMatchedTrackingInfo,
+            aLists, aFullHashes),
+        NS_DISPATCH_NORMAL);
+
+    MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+
+    return NS_SUCCEEDED(rv);
+  }
+
+  ClassifierInfo info;
+  info.list() = aLists;
+  info.fullhash() = aFullHashes;
+
+  return SendSetClassifierMatchedTrackingInfo(info);
+}
 void HttpBackgroundChannelParent::ActorDestroy(ActorDestroyReason aWhy) {
   LOG(("HttpBackgroundChannelParent::ActorDestroy [this=%p]\n", this));
   AssertIsInMainProcess();
   AssertIsOnBackgroundThread();
 
   mIPCOpened = false;
 
   RefPtr<HttpBackgroundChannelParent> self = this;
--- a/netwerk/protocol/http/HttpBackgroundChannelParent.h
+++ b/netwerk/protocol/http/HttpBackgroundChannelParent.h
@@ -80,16 +80,20 @@ class HttpBackgroundChannelParent final 
   // To send NotifyFlashPluginStateChanged message over background channel.
   bool OnNotifyFlashPluginStateChanged(nsIHttpChannel::FlashPluginState aState);
 
   // To send SetClassifierMatchedInfo message over background channel.
   bool OnSetClassifierMatchedInfo(const nsACString& aList,
                                   const nsACString& aProvider,
                                   const nsACString& aFullHash);
 
+  // To send SetClassifierMatchedTrackingInfo message over background channel.
+  bool OnSetClassifierMatchedTrackingInfo(const nsACString& aLists,
+                                          const nsACString& aFullHashes);
+
  protected:
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
  private:
   virtual ~HttpBackgroundChannelParent();
 
   Atomic<bool> mIPCOpened;
 
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -3181,20 +3181,16 @@ already_AddRefed<nsILoadInfo> HttpBaseCh
         "docshell and necko should have the same userContextId attribute.");
     MOZ_ASSERT(
         docShellAttrs.mInIsolatedMozBrowser == attrs.mInIsolatedMozBrowser,
         "docshell and necko should have the same inIsolatedMozBrowser "
         "attribute.");
     MOZ_ASSERT(
         docShellAttrs.mPrivateBrowsingId == attrs.mPrivateBrowsingId,
         "docshell and necko should have the same privateBrowsingId attribute.");
-    MOZ_ASSERT(docShellAttrs.mGeckoViewSessionContextId ==
-                   attrs.mGeckoViewSessionContextId,
-               "docshell and necko should have the same "
-               "geckoViewSessionContextId attribute");
 
     attrs = docShellAttrs;
     attrs.SetFirstPartyDomain(true, newURI);
     newLoadInfo->SetOriginAttributes(attrs);
   }
 
   // Leave empty, we want a 'clean ground' when creating the new channel.
   // This will be ensured to be either set by the protocol handler or set
@@ -3776,16 +3772,40 @@ HttpBaseChannel::SetMatchedInfo(const ns
   NS_ENSURE_ARG(!aList.IsEmpty());
 
   mMatchedList = aList;
   mMatchedProvider = aProvider;
   mMatchedFullHash = aFullHash;
   return NS_OK;
 }
 
+NS_IMETHODIMP
+HttpBaseChannel::GetMatchedTrackingLists(nsTArray<nsCString>& aLists) {
+  aLists = mMatchedTrackingLists;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetMatchedTrackingFullHashes(
+    nsTArray<nsCString>& aFullHashes) {
+  aFullHashes = mMatchedTrackingFullHashes;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetMatchedTrackingInfo(
+    const nsTArray<nsCString>& aLists, const nsTArray<nsCString>& aFullHashes) {
+  NS_ENSURE_ARG(!aLists.IsEmpty());
+  // aFullHashes can be empty for non hash-matching algorithm, for example,
+  // host based test entries in preference.
+
+  mMatchedTrackingLists = aLists;
+  mMatchedTrackingFullHashes = aFullHashes;
+  return NS_OK;
+}
 //-----------------------------------------------------------------------------
 // HttpBaseChannel::nsITimedChannel
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 HttpBaseChannel::SetTimingEnabled(bool enabled) {
   mTimingEnabled = enabled;
   return NS_OK;
--- a/netwerk/protocol/http/HttpBaseChannel.h
+++ b/netwerk/protocol/http/HttpBaseChannel.h
@@ -598,16 +598,19 @@ class HttpBaseChannel : public nsHashPro
   nsCString mAvailableCachedAltDataType;
   nsString mIntegrityMetadata;
 
   // Classified channel's matched information
   nsCString mMatchedList;
   nsCString mMatchedProvider;
   nsCString mMatchedFullHash;
 
+  nsTArray<nsCString> mMatchedTrackingLists;
+  nsTArray<nsCString> mMatchedTrackingFullHashes;
+
   nsCOMPtr<nsISupports> mOwner;
 
   nsHttpRequestHead mRequestHead;
   // Upload throttling.
   nsCOMPtr<nsIInputChannelThrottleQueue> mThrottleQueue;
   nsCOMPtr<nsIInputStream> mUploadStream;
   nsCOMPtr<nsIRunnable> mUploadCloneableCallback;
   nsAutoPtr<nsHttpResponseHead> mResponseHead;
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -1927,16 +1927,38 @@ void HttpChannelChild::ProcessSetClassif
   nsCOMPtr<nsIEventTarget> neckoTarget = GetNeckoTarget();
   neckoTarget->Dispatch(
       NewRunnableMethod<const nsCString, const nsCString, const nsCString>(
           "HttpChannelChild::SetMatchedInfo", this,
           &HttpChannelChild::SetMatchedInfo, aList, aProvider, aFullHash),
       NS_DISPATCH_NORMAL);
 }
 
+void HttpChannelChild::ProcessSetClassifierMatchedTrackingInfo(
+    const nsCString& aLists, const nsCString& aFullHashes) {
+  LOG(("HttpChannelChild::ProcessSetClassifierMatchedTrackingInfo [this=%p]\n",
+       this));
+  MOZ_ASSERT(OnSocketThread());
+
+  nsTArray<nsCString> lists, fullhashes;
+  for (const nsACString& token : aLists.Split(',')) {
+    lists.AppendElement(token);
+  }
+  for (const nsACString& token : aFullHashes.Split(',')) {
+    fullhashes.AppendElement(token);
+  }
+
+  nsCOMPtr<nsIEventTarget> neckoTarget = GetNeckoTarget();
+  neckoTarget->Dispatch(
+      NewRunnableMethod<const nsTArray<nsCString>, const nsTArray<nsCString>>(
+          "HttpChannelChild::SetMatchedTrackingInfo", this,
+          &HttpChannelChild::SetMatchedTrackingInfo, lists, fullhashes),
+      NS_DISPATCH_NORMAL);
+}
+
 void HttpChannelChild::ProcessDivertMessages() {
   LOG(("HttpChannelChild::ProcessDivertMessages [this=%p]\n", this));
   MOZ_ASSERT(OnSocketThread());
   MOZ_RELEASE_ASSERT(mDivertingToParent);
 
   // DivertTo() has been called on parent, so we can now start sending queued
   // IPDL messages back to parent listener.
   nsCOMPtr<nsIEventTarget> neckoTarget = GetNeckoTarget();
--- a/netwerk/protocol/http/HttpChannelChild.h
+++ b/netwerk/protocol/http/HttpChannelChild.h
@@ -274,16 +274,18 @@ class HttpChannelChild final : public PH
   void ProcessNotifyCookieBlocked(uint32_t aRejectedReason);
   void ProcessNotifyClassificationFlags(uint32_t aClassificationFlags,
                                         bool aIsThirdParty);
   void ProcessNotifyFlashPluginStateChanged(
       nsIHttpChannel::FlashPluginState aState);
   void ProcessSetClassifierMatchedInfo(const nsCString& aList,
                                        const nsCString& aProvider,
                                        const nsCString& aFullHash);
+  void ProcessSetClassifierMatchedTrackingInfo(const nsCString& aLists,
+                                               const nsCString& aFullHashes);
 
   // Return true if we need to tell the parent the size of unreported received
   // data
   bool NeedToReportBytesRead();
   int32_t mUnreportBytesRead = 0;
 
   void DoOnStartRequest(nsIRequest* aRequest, nsISupports* aContext);
   void DoOnStatus(nsIRequest* aRequest, nsresult status);
--- a/netwerk/protocol/http/HttpChannelParent.cpp
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -1854,16 +1854,29 @@ HttpChannelParent::SetClassifierMatchedI
     MOZ_ASSERT(mBgParent);
     Unused << mBgParent->OnSetClassifierMatchedInfo(aList, aProvider,
                                                     aFullHash);
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
+HttpChannelParent::SetClassifierMatchedTrackingInfo(
+    const nsACString& aLists, const nsACString& aFullHashes) {
+  LOG(("HttpChannelParent::SetClassifierMatchedTrackingInfo [this=%p]\n",
+       this));
+  if (!mIPCClosed) {
+    MOZ_ASSERT(mBgParent);
+    Unused << mBgParent->OnSetClassifierMatchedTrackingInfo(aLists,
+                                                            aFullHashes);
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 HttpChannelParent::NotifyClassificationFlags(uint32_t aClassificationFlags,
                                              bool aIsThirdParty) {
   LOG(
       ("HttpChannelParent::NotifyClassificationFlags "
        "classificationFlags=%" PRIu32 ", thirdparty=%d [this=%p]\n",
        aClassificationFlags, static_cast<int>(aIsThirdParty), this));
   if (!mIPCClosed) {
     MOZ_ASSERT(mBgParent);
--- a/netwerk/protocol/http/PHttpBackgroundChannel.ipdl
+++ b/netwerk/protocol/http/PHttpBackgroundChannel.ipdl
@@ -69,15 +69,18 @@ child:
 
   // Tell the child that the current channel's document is not allowed to load
   // flash content.
   async NotifyFlashPluginStateChanged(FlashPluginState aState);
 
   // Tell the child information of matched URL againts SafeBrowsing list
   async SetClassifierMatchedInfo(ClassifierInfo info);
 
+  // Tell the child information of matched URL againts SafeBrowsing tracking list
+  async SetClassifierMatchedTrackingInfo(ClassifierInfo info);
+
   async __delete__();
 
 };
 
 
 } // namespace net
 } // namespace mozilla
--- a/netwerk/url-classifier/AsyncUrlChannelClassifier.cpp
+++ b/netwerk/url-classifier/AsyncUrlChannelClassifier.cpp
@@ -160,16 +160,18 @@ class TableData {
   };
 
   TableData(URIData* aURIData, const nsACString& aTable);
 
   nsIURI* URI() const;
 
   const nsACString& Table() const;
 
+  const LookupResultArray& Result() const;
+
   State MatchState() const;
 
   bool IsEqual(URIData* aURIData, const nsACString& aTable) const;
 
   // Returns true if the table classifies the URI. This method must be called
   // on hte classifier worker thread.
   bool DoLookup(nsUrlClassifierDBServiceWorker* aWorkerClassifier);
 
@@ -199,16 +201,21 @@ nsIURI* TableData::URI() const {
   return mURIData->URI();
 }
 
 const nsACString& TableData::Table() const {
   MOZ_ASSERT(NS_IsMainThread());
   return mTable;
 }
 
+const LookupResultArray& TableData::Result() const {
+  MOZ_ASSERT(NS_IsMainThread());
+  return mResults;
+}
+
 TableData::State TableData::MatchState() const {
   MOZ_ASSERT(NS_IsMainThread());
   return mState;
 }
 
 bool TableData::IsEqual(URIData* aURIData, const nsACString& aTable) const {
   MOZ_ASSERT(NS_IsMainThread());
   return mURIData == aURIData && mTable == aTable;
@@ -420,29 +427,35 @@ bool FeatureData::MaybeCompleteClassific
       nsContentUtils::IsURIInList(mBlacklistTables[0]->URI(), skipList)) {
     UC_LOG(
         ("FeatureData::MaybeCompleteClassification[%p] - uri found in skiplist",
          this));
     return true;
   }
 
   nsTArray<nsCString> list;
+  nsTArray<nsCString> hashes;
   list.AppendElement(mHostInPrefTables[nsIUrlClassifierFeature::blacklist]);
 
   for (TableData* tableData : mBlacklistTables) {
     if (tableData->MatchState() == TableData::eMatch) {
       list.AppendElement(tableData->Table());
+
+      for (const auto& r : tableData->Result()) {
+        nsCString* hash = hashes.AppendElement();
+        r->hash.complete.ToString(*hash);
+      }
     }
   }
 
   UC_LOG(("FeatureData::MaybeCompleteClassification[%p] - process channel %p",
           this, aChannel));
 
   bool shouldContinue = false;
-  rv = mFeature->ProcessChannel(aChannel, list, &shouldContinue);
+  rv = mFeature->ProcessChannel(aChannel, list, hashes, &shouldContinue);
   Unused << NS_WARN_IF(NS_FAILED(rv));
 
   return shouldContinue;
 }
 
 // CallbackHolder
 // ----------------------------------------------------------------------------
 
--- a/netwerk/url-classifier/UrlClassifierCommon.cpp
+++ b/netwerk/url-classifier/UrlClassifierCommon.cpp
@@ -135,16 +135,48 @@ bool UrlClassifierCommon::ShouldEnableCl
          "channel[%p] with uri %s for toplevel window uri %s",
          aChannel, chanSpec.get(), topWinSpec.get()));
   }
 
   return true;
 }
 
 /* static */
+nsresult UrlClassifierCommon::SetTrackingInfo(
+    nsIChannel* aChannel, const nsTArray<nsCString>& aLists,
+    const nsTArray<nsCString>& aFullHashes) {
+  NS_ENSURE_ARG(!aLists.IsEmpty());
+
+  // Can be called in EITHER the parent or child process.
+  nsCOMPtr<nsIParentChannel> parentChannel;
+  NS_QueryNotificationCallbacks(aChannel, parentChannel);
+  if (parentChannel) {
+    // This channel is a parent-process proxy for a child process request.
+    // Tell the child process channel to do this instead.
+    nsAutoCString strLists, strHashes;
+    TablesToString(aLists, strLists);
+    TablesToString(aFullHashes, strHashes);
+
+    parentChannel->SetClassifierMatchedTrackingInfo(strLists, strHashes);
+    return NS_OK;
+  }
+
+  nsresult rv;
+  nsCOMPtr<nsIClassifiedChannel> classifiedChannel =
+      do_QueryInterface(aChannel, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (classifiedChannel) {
+    classifiedChannel->SetMatchedTrackingInfo(aLists, aFullHashes);
+  }
+
+  return NS_OK;
+}
+
+/* static */
 nsresult UrlClassifierCommon::SetBlockedContent(nsIChannel* channel,
                                                 nsresult aErrorCode,
                                                 const nsACString& aList,
                                                 const nsACString& aProvider,
                                                 const nsACString& aFullHash) {
   NS_ENSURE_ARG(!aList.IsEmpty());
 
   // Can be called in EITHER the parent or child process.
--- a/netwerk/url-classifier/UrlClassifierCommon.h
+++ b/netwerk/url-classifier/UrlClassifierCommon.h
@@ -32,16 +32,20 @@ class UrlClassifierCommon final {
 
   static bool ShouldEnableClassifier(nsIChannel* aChannel);
 
   static nsresult SetBlockedContent(nsIChannel* channel, nsresult aErrorCode,
                                     const nsACString& aList,
                                     const nsACString& aProvider,
                                     const nsACString& aFullHash);
 
+  static nsresult SetTrackingInfo(nsIChannel* channel,
+                                  const nsTArray<nsCString>& aLists,
+                                  const nsTArray<nsCString>& aFullHashes);
+
   // Use this function only when you are looking for a pairwise whitelist uri
   // with the format: http://toplevel.page/?resource=channel.uri.domain
   static nsresult CreatePairwiseWhiteListURI(nsIChannel* aChannel,
                                              nsIURI** aURI);
 
   static void AnnotateChannel(
       nsIChannel* aChannel,
       AntiTrackingCommon::ContentBlockingAllowListPurpose aPurpose,
--- a/netwerk/url-classifier/UrlClassifierFeatureCryptominingAnnotation.cpp
+++ b/netwerk/url-classifier/UrlClassifierFeatureCryptominingAnnotation.cpp
@@ -119,17 +119,17 @@ UrlClassifierFeatureCryptominingAnnotati
   RefPtr<UrlClassifierFeatureCryptominingAnnotation> self =
       gFeatureCryptominingAnnotation;
   return self.forget();
 }
 
 NS_IMETHODIMP
 UrlClassifierFeatureCryptominingAnnotation::ProcessChannel(
     nsIChannel* aChannel, const nsTArray<nsCString>& aList,
-    bool* aShouldContinue) {
+    const nsTArray<nsCString>& aHashes, bool* aShouldContinue) {
   NS_ENSURE_ARG_POINTER(aChannel);
   NS_ENSURE_ARG_POINTER(aShouldContinue);
 
   // This is not a blocking feature.
   *aShouldContinue = true;
 
   UC_LOG(
       ("UrlClassifierFeatureCryptominingAnnotation::ProcessChannel, annotating "
--- a/netwerk/url-classifier/UrlClassifierFeatureCryptominingAnnotation.h
+++ b/netwerk/url-classifier/UrlClassifierFeatureCryptominingAnnotation.h
@@ -24,16 +24,17 @@ class UrlClassifierFeatureCryptominingAn
   static already_AddRefed<UrlClassifierFeatureCryptominingAnnotation>
   MaybeCreate(nsIChannel* aChannel);
 
   static already_AddRefed<nsIUrlClassifierFeature> GetIfNameMatches(
       const nsACString& aName);
 
   NS_IMETHOD ProcessChannel(nsIChannel* aChannel,
                             const nsTArray<nsCString>& aList,
+                            const nsTArray<nsCString>& aHashes,
                             bool* aShouldContinue) override;
 
   NS_IMETHOD GetURIByListType(nsIChannel* aChannel,
                               nsIUrlClassifierFeature::listType aListType,
                               nsIURI** aURI) override;
 
  private:
   UrlClassifierFeatureCryptominingAnnotation();
--- a/netwerk/url-classifier/UrlClassifierFeatureCryptominingProtection.cpp
+++ b/netwerk/url-classifier/UrlClassifierFeatureCryptominingProtection.cpp
@@ -137,17 +137,17 @@ UrlClassifierFeatureCryptominingProtecti
   RefPtr<UrlClassifierFeatureCryptominingProtection> self =
       gFeatureCryptominingProtection;
   return self.forget();
 }
 
 NS_IMETHODIMP
 UrlClassifierFeatureCryptominingProtection::ProcessChannel(
     nsIChannel* aChannel, const nsTArray<nsCString>& aList,
-    bool* aShouldContinue) {
+    const nsTArray<nsCString>& aHashes, bool* aShouldContinue) {
   NS_ENSURE_ARG_POINTER(aChannel);
   NS_ENSURE_ARG_POINTER(aShouldContinue);
 
   bool isAllowListed = UrlClassifierCommon::IsAllowListed(
       aChannel, AntiTrackingCommon::eCryptomining);
 
   // This is a blocking feature.
   *aShouldContinue = isAllowListed;
--- a/netwerk/url-classifier/UrlClassifierFeatureCryptominingProtection.h
+++ b/netwerk/url-classifier/UrlClassifierFeatureCryptominingProtection.h
@@ -24,16 +24,17 @@ class UrlClassifierFeatureCryptominingPr
   static already_AddRefed<UrlClassifierFeatureCryptominingProtection>
   MaybeCreate(nsIChannel* aChannel);
 
   static already_AddRefed<nsIUrlClassifierFeature> GetIfNameMatches(
       const nsACString& aName);
 
   NS_IMETHOD ProcessChannel(nsIChannel* aChannel,
                             const nsTArray<nsCString>& aList,
+                            const nsTArray<nsCString>& aHashes,
                             bool* aShouldContinue) override;
 
   NS_IMETHOD GetURIByListType(nsIChannel* aChannel,
                               nsIUrlClassifierFeature::listType aListType,
                               nsIURI** aURI) override;
 
  private:
   UrlClassifierFeatureCryptominingProtection();
--- a/netwerk/url-classifier/UrlClassifierFeatureCustomTables.cpp
+++ b/netwerk/url-classifier/UrlClassifierFeatureCustomTables.cpp
@@ -78,17 +78,17 @@ UrlClassifierFeatureCustomTables::HasHos
 NS_IMETHODIMP
 UrlClassifierFeatureCustomTables::GetSkipHostList(nsACString& aList) {
   return NS_OK;
 }
 
 NS_IMETHODIMP
 UrlClassifierFeatureCustomTables::ProcessChannel(
     nsIChannel* aChannel, const nsTArray<nsCString>& aList,
-    bool* aShouldContinue) {
+    const nsTArray<nsCString>& aHashes, bool* aShouldContinue) {
   NS_ENSURE_ARG_POINTER(aChannel);
   NS_ENSURE_ARG_POINTER(aShouldContinue);
 
   // This is not a blocking feature.
   *aShouldContinue = true;
 
   return NS_OK;
 }
--- a/netwerk/url-classifier/UrlClassifierFeatureFingerprintingAnnotation.cpp
+++ b/netwerk/url-classifier/UrlClassifierFeatureFingerprintingAnnotation.cpp
@@ -121,17 +121,17 @@ UrlClassifierFeatureFingerprintingAnnota
   RefPtr<UrlClassifierFeatureFingerprintingAnnotation> self =
       gFeatureFingerprintingAnnotation;
   return self.forget();
 }
 
 NS_IMETHODIMP
 UrlClassifierFeatureFingerprintingAnnotation::ProcessChannel(
     nsIChannel* aChannel, const nsTArray<nsCString>& aList,
-    bool* aShouldContinue) {
+    const nsTArray<nsCString>& aHashes, bool* aShouldContinue) {
   NS_ENSURE_ARG_POINTER(aChannel);
   NS_ENSURE_ARG_POINTER(aShouldContinue);
 
   // This is not a blocking feature.
   *aShouldContinue = true;
 
   UC_LOG(
       ("UrlClassifierFeatureFingerprintingAnnotation::ProcessChannel, "
--- a/netwerk/url-classifier/UrlClassifierFeatureFingerprintingAnnotation.h
+++ b/netwerk/url-classifier/UrlClassifierFeatureFingerprintingAnnotation.h
@@ -24,16 +24,17 @@ class UrlClassifierFeatureFingerprinting
   static already_AddRefed<UrlClassifierFeatureFingerprintingAnnotation>
   MaybeCreate(nsIChannel* aChannel);
 
   static already_AddRefed<nsIUrlClassifierFeature> GetIfNameMatches(
       const nsACString& aName);
 
   NS_IMETHOD ProcessChannel(nsIChannel* aChannel,
                             const nsTArray<nsCString>& aList,
+                            const nsTArray<nsCString>& aHashes,
                             bool* aShouldContinue) override;
 
   NS_IMETHOD GetURIByListType(nsIChannel* aChannel,
                               nsIUrlClassifierFeature::listType aListType,
                               nsIURI** aURI) override;
 
  private:
   UrlClassifierFeatureFingerprintingAnnotation();
--- a/netwerk/url-classifier/UrlClassifierFeatureFingerprintingProtection.cpp
+++ b/netwerk/url-classifier/UrlClassifierFeatureFingerprintingProtection.cpp
@@ -141,17 +141,17 @@ UrlClassifierFeatureFingerprintingProtec
   RefPtr<UrlClassifierFeatureFingerprintingProtection> self =
       gFeatureFingerprintingProtection;
   return self.forget();
 }
 
 NS_IMETHODIMP
 UrlClassifierFeatureFingerprintingProtection::ProcessChannel(
     nsIChannel* aChannel, const nsTArray<nsCString>& aList,
-    bool* aShouldContinue) {
+    const nsTArray<nsCString>& aHashes, bool* aShouldContinue) {
   NS_ENSURE_ARG_POINTER(aChannel);
   NS_ENSURE_ARG_POINTER(aShouldContinue);
 
   bool isAllowListed = UrlClassifierCommon::IsAllowListed(
       aChannel, AntiTrackingCommon::eFingerprinting);
 
   // This is a blocking feature.
   *aShouldContinue = isAllowListed;
--- a/netwerk/url-classifier/UrlClassifierFeatureFingerprintingProtection.h
+++ b/netwerk/url-classifier/UrlClassifierFeatureFingerprintingProtection.h
@@ -24,16 +24,17 @@ class UrlClassifierFeatureFingerprinting
   static already_AddRefed<UrlClassifierFeatureFingerprintingProtection>
   MaybeCreate(nsIChannel* aChannel);
 
   static already_AddRefed<nsIUrlClassifierFeature> GetIfNameMatches(
       const nsACString& aName);
 
   NS_IMETHOD ProcessChannel(nsIChannel* aChannel,
                             const nsTArray<nsCString>& aList,
+                            const nsTArray<nsCString>& aHashes,
                             bool* aShouldContinue) override;
 
   NS_IMETHOD GetURIByListType(nsIChannel* aChannel,
                               nsIUrlClassifierFeature::listType aListType,
                               nsIURI** aURI) override;
 
  private:
   UrlClassifierFeatureFingerprintingProtection();
--- a/netwerk/url-classifier/UrlClassifierFeatureFlash.cpp
+++ b/netwerk/url-classifier/UrlClassifierFeatureFlash.cpp
@@ -143,16 +143,17 @@ UrlClassifierFeatureFlash::GetIfNameMatc
   }
 
   return nullptr;
 }
 
 NS_IMETHODIMP
 UrlClassifierFeatureFlash::ProcessChannel(nsIChannel* aChannel,
                                           const nsTArray<nsCString>& aList,
+                                          const nsTArray<nsCString>& aHashes,
                                           bool* aShouldContinue) {
   NS_ENSURE_ARG_POINTER(aChannel);
   NS_ENSURE_ARG_POINTER(aShouldContinue);
 
   // This is not a blocking feature.
   *aShouldContinue = true;
 
   UC_LOG(("UrlClassifierFeatureFlash::ProcessChannel, annotating channel[%p]",
--- a/netwerk/url-classifier/UrlClassifierFeatureFlash.h
+++ b/netwerk/url-classifier/UrlClassifierFeatureFlash.h
@@ -24,16 +24,17 @@ class UrlClassifierFeatureFlash final : 
       nsIChannel* aChannel,
       nsTArray<nsCOMPtr<nsIUrlClassifierFeature>>& aFeatures);
 
   static already_AddRefed<nsIUrlClassifierFeature> GetIfNameMatches(
       const nsACString& aName);
 
   NS_IMETHOD
   ProcessChannel(nsIChannel* aChannel, const nsTArray<nsCString>& aList,
+                 const nsTArray<nsCString>& aHashes,
                  bool* aShouldContinue) override;
 
   NS_IMETHOD GetURIByListType(nsIChannel* aChannel,
                               nsIUrlClassifierFeature::listType aListType,
                               nsIURI** aURI) override;
 
  private:
   explicit UrlClassifierFeatureFlash(const FlashFeature& aFlashFeature);
--- a/netwerk/url-classifier/UrlClassifierFeatureLoginReputation.cpp
+++ b/netwerk/url-classifier/UrlClassifierFeatureLoginReputation.cpp
@@ -73,17 +73,17 @@ UrlClassifierFeatureLoginReputation::Get
 
   nsCOMPtr<nsIUrlClassifierFeature> self = MaybeGetOrCreate();
   return self.forget();
 }
 
 NS_IMETHODIMP
 UrlClassifierFeatureLoginReputation::ProcessChannel(
     nsIChannel* aChannel, const nsTArray<nsCString>& aList,
-    bool* aShouldContinue) {
+    const nsTArray<nsCString>& aHashes, bool* aShouldContinue) {
   MOZ_CRASH(
       "UrlClassifierFeatureLoginReputation::ProcessChannel should never be "
       "called");
   return NS_OK;
 }
 
 NS_IMETHODIMP
 UrlClassifierFeatureLoginReputation::GetURIByListType(
--- a/netwerk/url-classifier/UrlClassifierFeatureLoginReputation.h
+++ b/netwerk/url-classifier/UrlClassifierFeatureLoginReputation.h
@@ -23,16 +23,17 @@ class UrlClassifierFeatureLoginReputatio
 
   static nsIUrlClassifierFeature* MaybeGetOrCreate();
 
   static already_AddRefed<nsIUrlClassifierFeature> GetIfNameMatches(
       const nsACString& aName);
 
   NS_IMETHOD ProcessChannel(nsIChannel* aChannel,
                             const nsTArray<nsCString>& aList,
+                            const nsTArray<nsCString>& aHashes,
                             bool* aShouldContinue) override;
 
   NS_IMETHOD GetURIByListType(nsIChannel* aChannel,
                               nsIUrlClassifierFeature::listType aListType,
                               nsIURI** aURI) override;
 
  private:
   UrlClassifierFeatureLoginReputation();
--- a/netwerk/url-classifier/UrlClassifierFeaturePhishingProtection.cpp
+++ b/netwerk/url-classifier/UrlClassifierFeaturePhishingProtection.cpp
@@ -106,17 +106,17 @@ UrlClassifierFeaturePhishingProtection::
   }
 
   return nullptr;
 }
 
 NS_IMETHODIMP
 UrlClassifierFeaturePhishingProtection::ProcessChannel(
     nsIChannel* aChannel, const nsTArray<nsCString>& aList,
-    bool* aShouldContinue) {
+    const nsTArray<nsCString>& aHashes, bool* aShouldContinue) {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 UrlClassifierFeaturePhishingProtection::GetURIByListType(
     nsIChannel* aChannel, nsIUrlClassifierFeature::listType aListType,
     nsIURI** aURI) {
   return NS_ERROR_NOT_IMPLEMENTED;
--- a/netwerk/url-classifier/UrlClassifierFeaturePhishingProtection.h
+++ b/netwerk/url-classifier/UrlClassifierFeaturePhishingProtection.h
@@ -23,16 +23,17 @@ class UrlClassifierFeaturePhishingProtec
 
   static void MaybeCreate(nsTArray<RefPtr<nsIUrlClassifierFeature>>& aFeatures);
 
   static already_AddRefed<nsIUrlClassifierFeature> GetIfNameMatches(
       const nsACString& aName);
 
   NS_IMETHOD
   ProcessChannel(nsIChannel* aChannel, const nsTArray<nsCString>& aList,
+                 const nsTArray<nsCString>& aHashes,
                  bool* aShouldContinue) override;
 
   NS_IMETHOD GetURIByListType(nsIChannel* aChannel,
                               nsIUrlClassifierFeature::listType aListType,
                               nsIURI** aURI) override;
 
  private:
   explicit UrlClassifierFeaturePhishingProtection(
--- a/netwerk/url-classifier/UrlClassifierFeatureTrackingAnnotation.cpp
+++ b/netwerk/url-classifier/UrlClassifierFeatureTrackingAnnotation.cpp
@@ -112,17 +112,17 @@ UrlClassifierFeatureTrackingAnnotation::
   RefPtr<UrlClassifierFeatureTrackingAnnotation> self =
       gFeatureTrackingAnnotation;
   return self.forget();
 }
 
 NS_IMETHODIMP
 UrlClassifierFeatureTrackingAnnotation::ProcessChannel(
     nsIChannel* aChannel, const nsTArray<nsCString>& aList,
-    bool* aShouldContinue) {
+    const nsTArray<nsCString>& aHashes, bool* aShouldContinue) {
   NS_ENSURE_ARG_POINTER(aChannel);
   NS_ENSURE_ARG_POINTER(aShouldContinue);
 
   // This is not a blocking feature.
   *aShouldContinue = true;
 
   static std::vector<UrlClassifierCommon::ClassificationData>
       sClassificationData = {
@@ -135,16 +135,18 @@ UrlClassifierFeatureTrackingAnnotation::
           {NS_LITERAL_CSTRING("content-track-"),
            nsIHttpChannel::ClassificationFlags::CLASSIFIED_TRACKING_CONTENT},
       };
 
   uint32_t flags = UrlClassifierCommon::TablesToClassificationFlags(
       aList, sClassificationData,
       nsIHttpChannel::ClassificationFlags::CLASSIFIED_TRACKING);
 
+  UrlClassifierCommon::SetTrackingInfo(aChannel, aList, aHashes);
+
   UrlClassifierCommon::AnnotateChannel(
       aChannel, AntiTrackingCommon::eTrackingAnnotations, flags,
       nsIWebProgressListener::STATE_LOADED_TRACKING_CONTENT);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
--- a/netwerk/url-classifier/UrlClassifierFeatureTrackingAnnotation.h
+++ b/netwerk/url-classifier/UrlClassifierFeatureTrackingAnnotation.h
@@ -24,16 +24,17 @@ class UrlClassifierFeatureTrackingAnnota
   static already_AddRefed<UrlClassifierFeatureTrackingAnnotation> MaybeCreate(
       nsIChannel* aChannel);
 
   static already_AddRefed<nsIUrlClassifierFeature> GetIfNameMatches(
       const nsACString& aName);
 
   NS_IMETHOD ProcessChannel(nsIChannel* aChannel,
                             const nsTArray<nsCString>& aList,
+                            const nsTArray<nsCString>& aHashes,
                             bool* aShouldContinue) override;
 
   NS_IMETHOD GetURIByListType(nsIChannel* aChannel,
                               nsIUrlClassifierFeature::listType aListType,
                               nsIURI** aURI) override;
 
  private:
   UrlClassifierFeatureTrackingAnnotation();
--- a/netwerk/url-classifier/UrlClassifierFeatureTrackingProtection.cpp
+++ b/netwerk/url-classifier/UrlClassifierFeatureTrackingProtection.cpp
@@ -133,17 +133,17 @@ UrlClassifierFeatureTrackingProtection::
   RefPtr<UrlClassifierFeatureTrackingProtection> self =
       gFeatureTrackingProtection;
   return self.forget();
 }
 
 NS_IMETHODIMP
 UrlClassifierFeatureTrackingProtection::ProcessChannel(
     nsIChannel* aChannel, const nsTArray<nsCString>& aList,
-    bool* aShouldContinue) {
+    const nsTArray<nsCString>& aHashes, bool* aShouldContinue) {
   NS_ENSURE_ARG_POINTER(aChannel);
   NS_ENSURE_ARG_POINTER(aShouldContinue);
 
   bool isAllowListed = UrlClassifierCommon::IsAllowListed(
       aChannel, AntiTrackingCommon::eTrackingProtection);
 
   // This is a blocking feature.
   *aShouldContinue = isAllowListed;
--- a/netwerk/url-classifier/UrlClassifierFeatureTrackingProtection.h
+++ b/netwerk/url-classifier/UrlClassifierFeatureTrackingProtection.h
@@ -24,16 +24,17 @@ class UrlClassifierFeatureTrackingProtec
   static already_AddRefed<UrlClassifierFeatureTrackingProtection> MaybeCreate(
       nsIChannel* aChannel);
 
   static already_AddRefed<nsIUrlClassifierFeature> GetIfNameMatches(
       const nsACString& aName);
 
   NS_IMETHOD ProcessChannel(nsIChannel* aChannel,
                             const nsTArray<nsCString>& aList,
+                            const nsTArray<nsCString>& aHashes,
                             bool* aShouldContinue) override;
 
   NS_IMETHOD GetURIByListType(nsIChannel* aChannel,
                               nsIUrlClassifierFeature::listType aListType,
                               nsIURI** aURI) override;
 
  private:
   UrlClassifierFeatureTrackingProtection();
--- a/netwerk/url-classifier/nsIUrlClassifierFeature.idl
+++ b/netwerk/url-classifier/nsIUrlClassifierFeature.idl
@@ -59,17 +59,18 @@ interface nsIUrlClassifierFeature : nsIS
    * 'something' on the channel. For instance, a tracking-annotation feature
    * would mark the channel as tracker, a tracking-protection feature would
    * cancel the channel.
    * Returns if we should process other feature results or not. For instance,
    * tracking-protection cancel the channel, and after that we should stop
    * processing other features.
    */
   [noscript] boolean processChannel(in nsIChannel aChannel,
-                                    in ConstStringArrayRef aList);
+                                    in ConstStringArrayRef aList,
+                                    in ConstStringArrayRef aHashes);
 
   /**
    * Features can work with different URLs from a channel (channel url, or
    * top-level, or something else). This method returns what we need to use for
    * the current list.
    */
   [noscript] nsIURI getURIByListType(in nsIChannel channel,
                                      in nsIUrlClassifierFeature_listType listType);
--- a/python/mozboot/mozboot/osx.py
+++ b/python/mozboot/mozboot/osx.py
@@ -17,16 +17,17 @@ except ImportError:
 from distutils.version import StrictVersion
 
 from mozboot.base import BaseBootstrapper
 
 HOMEBREW_BOOTSTRAP = 'https://raw.githubusercontent.com/Homebrew/install/master/install'
 XCODE_APP_STORE = 'macappstore://itunes.apple.com/app/id497799835?mt=12'
 XCODE_LEGACY = ('https://developer.apple.com/downloads/download.action?path=Developer_Tools/'
                 'xcode_3.2.6_and_ios_sdk_4.3__final/xcode_3.2.6_and_ios_sdk_4.3.dmg')
+JAVA_PATH = '/Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home/bin'
 
 MACPORTS_URL = {
     '14': 'https://distfiles.macports.org/MacPorts/MacPorts-2.5.4-10.14-Mojave.pkg',
     '13': 'https://distfiles.macports.org/MacPorts/MacPorts-2.5.4-10.13-HighSierra.pkg',
     '12': 'https://distfiles.macports.org/MacPorts/MacPorts-2.5.4-10.12-Sierra.pkg',
     '11': 'https://distfiles.macports.org/MacPorts/MacPorts-2.5.4-10.11-ElCapitan.pkg',
     '10': 'https://distfiles.macports.org/MacPorts/MacPorts-2.5.4-10.10-Yosemite.pkg',
     '9': 'https://distfiles.macports.org/MacPorts/MacPorts-2.5.4-10.9-Mavericks.pkg',
@@ -162,22 +163,16 @@ Modify your shell's configuration (e.g. 
 ~/.bash_profile) to have %s appear in $PATH before %s. e.g.
 
     export PATH=%s:$PATH
 
 Once this is done, start a new shell (likely Command+T) and run
 this bootstrap again.
 '''
 
-JAVA_LICENSE_NOTICE = '''
-We installed a recent Java toolchain for you. We agreed to the Oracle Java
-license for you by downloading the JDK. If this is unacceptable you should
-uninstall.
-'''
-
 
 class OSXBootstrapper(BaseBootstrapper):
     def __init__(self, version, **kwargs):
         BaseBootstrapper.__init__(self, **kwargs)
 
         self.os_version = StrictVersion(version)
 
         if self.os_version < StrictVersion('10.6'):
@@ -360,42 +355,40 @@ class OSXBootstrapper(BaseBootstrapper):
 
         # 1. System packages.
         packages = [
             'wget',
         ]
         self._ensure_homebrew_packages(packages)
 
         casks = [
-            'java8',
+            'adoptopenjdk8',
         ]
-        installed = self._ensure_homebrew_casks(casks)
-        if installed:
-            print(JAVA_LICENSE_NOTICE)  # We accepted a license agreement for the user.
+        self._ensure_homebrew_casks(casks)
 
         is_64bits = sys.maxsize > 2**32
         if not is_64bits:
             raise Exception('You need a 64-bit version of Mac OS X to build '
                             'GeckoView/Firefox for Android.')
 
         # 2. Android pieces.
         # Prefer homebrew's java binary by putting it on the path first.
         os.environ['PATH'] = \
-            '{}{}{}'.format('/Library/Java/Home/bin', os.pathsep, os.environ['PATH'])
+            '{}{}{}'.format(JAVA_PATH, os.pathsep, os.environ['PATH'])
         self.ensure_java()
         from mozboot import android
 
         android.ensure_android('macosx', artifact_mode=artifact_mode,
                                no_interactive=self.no_interactive)
 
     def suggest_homebrew_mobile_android_mozconfig(self, artifact_mode=False):
         from mozboot import android
-        # Path to java from the homebrew/cask-versions/java8 cask.
+        # Path to java from the homebrew/cask-versions/adoptopenjdk8 cask.
         android.suggest_mozconfig('macosx', artifact_mode=artifact_mode,
-                                  java_bin_path='/Library/Java/Home/bin')
+                                  java_bin_path=JAVA_PATH)
 
     def _ensure_macports_packages(self, packages):
         self.port = self.which('port')
         assert self.port is not None
 
         installed = set(self.check_output([self.port, 'installed']).split())
 
         missing = [package for package in packages if package not in installed]
--- a/python/mozbuild/mozbuild/doctor.py
+++ b/python/mozbuild/mozbuild/doctor.py
@@ -220,17 +220,17 @@ class Doctor(object):
                         else:
                             status = 'BAD, NOT FIXED'
                 else:
                     status = 'BAD, FIXABLE'
                     desc = 'lastaccess enabled'
                     fixable = True
             results.append({'status': status, 'desc': desc, 'fixable': fixable,
                             'denied': denied})
-        elif self.platform in ['darwin', 'freebsd', 'linux', 'openbsd']:
+        elif self.platform in ['freebsd', 'linux', 'openbsd']:
             common_mountpoint = self.srcdir_mount == self.objdir_mount
             for (purpose, path, mount) in self.path_mounts:
                 results.append(self.check_mount_lastaccess(mount))
                 if common_mountpoint:
                     break
         else:
             results.append({'status': 'SKIPPED'})
         return results
--- a/security/certverifier/NSSCertDBTrustDomain.cpp
+++ b/security/certverifier/NSSCertDBTrustDomain.cpp
@@ -83,26 +83,51 @@ NSSCertDBTrustDomain::NSSCertDBTrustDoma
       mDistrustedCAPolicy(distrustedCAPolicy),
       mSawDistrustedCAByPolicyError(false),
       mOriginAttributes(originAttributes),
       mThirdPartyRootInputs(thirdPartyRootInputs),
       mThirdPartyIntermediateInputs(thirdPartyIntermediateInputs),
       mBuiltChain(builtChain),
       mPinningTelemetryInfo(pinningTelemetryInfo),
       mHostname(hostname),
-      mCertBlocklist(do_GetService(NS_CERT_STORAGE_CID)),
+      mCertStorage(do_GetService(NS_CERT_STORAGE_CID)),
       mOCSPStaplingStatus(CertVerifier::OCSP_STAPLING_NEVER_CHECKED),
       mSCTListFromCertificate(),
       mSCTListFromOCSPStapling() {}
 
 Result NSSCertDBTrustDomain::FindIssuer(Input encodedIssuerName,
                                         IssuerChecker& checker, Time) {
   Vector<Input> rootCandidates;
   Vector<Input> intermediateCandidates;
 
+  if (!mCertStorage) {
+    return Result::FATAL_ERROR_LIBRARY_FAILURE;
+  }
+  nsTArray<uint8_t> subject;
+  if (!subject.AppendElements(encodedIssuerName.UnsafeGetData(),
+                              encodedIssuerName.GetLength())) {
+    return Result::FATAL_ERROR_NO_MEMORY;
+  }
+  nsTArray<nsTArray<uint8_t>> certs;
+  nsresult rv = mCertStorage->FindCertsBySubject(subject, certs);
+  if (NS_FAILED(rv)) {
+    return Result::FATAL_ERROR_LIBRARY_FAILURE;
+  }
+  for (auto& cert : certs) {
+    Input certDER;
+    Result rv = certDER.Init(cert.Elements(), cert.Length());
+    if (rv != Success) {
+      continue;  // probably too big
+    }
+    // Currently we're only expecting intermediate certificates in cert storage.
+    if (!intermediateCandidates.append(certDER)) {
+      return Result::FATAL_ERROR_NO_MEMORY;
+    }
+  }
+
   SECItem encodedIssuerNameItem = UnsafeMapInputToSECItem(encodedIssuerName);
 
   // NSS seems not to differentiate between "no potential issuers found" and
   // "there was an error trying to retrieve the potential issuers." We assume
   // there was no error if CERT_CreateSubjectCertList returns nullptr.
   UniqueCERTCertList candidates(CERT_CreateSubjectCertList(
       nullptr, CERT_GetDefaultCertDB(), &encodedIssuerNameItem, 0, false));
   if (candidates) {
@@ -186,17 +211,17 @@ Result NSSCertDBTrustDomain::GetCertTrus
   SECItem candidateCertDERSECItem = UnsafeMapInputToSECItem(candidateCertDER);
   UniqueCERTCertificate candidateCert(CERT_NewTempCertificate(
       CERT_GetDefaultCertDB(), &candidateCertDERSECItem, nullptr, false, true));
   if (!candidateCert) {
     return MapPRErrorCodeToResult(PR_GetError());
   }
 
   // Check the certificate against the OneCRL cert blocklist
-  if (!mCertBlocklist) {
+  if (!mCertStorage) {
     return Result::FATAL_ERROR_LIBRARY_FAILURE;
   }
 
   // The certificate blocklist currently only applies to TLS server
   // certificates.
   if (mCertDBTrustType == trustSSL) {
     int16_t revocationState;
 
@@ -207,17 +232,17 @@ Result NSSCertDBTrustDomain::GetCertTrus
 
     nsresult nsrv = BuildRevocationCheckArrays(
         candidateCert, issuerBytes, serialBytes, subjectBytes, pubKeyBytes);
 
     if (NS_FAILED(nsrv)) {
       return Result::FATAL_ERROR_LIBRARY_FAILURE;
     }
 
-    nsrv = mCertBlocklist->GetRevocationState(
+    nsrv = mCertStorage->GetRevocationState(
         issuerBytes, serialBytes, subjectBytes, pubKeyBytes, &revocationState);
     if (NS_FAILED(nsrv)) {
       return Result::FATAL_ERROR_LIBRARY_FAILURE;
     }
 
     if (revocationState == nsICertStorage::STATE_ENFORCE) {
       MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
               ("NSSCertDBTrustDomain: certificate is in blocklist"));
@@ -467,17 +492,17 @@ Result NSSCertDBTrustDomain::CheckRevoca
   }
   // At this point, if and only if cachedErrorResult is Success, there was no
   // cached response.
   MOZ_ASSERT((!cachedResponsePresent && cachedResponseResult == Success) ||
              (cachedResponsePresent && cachedResponseResult != Success));
 
   // If we have a fresh OneCRL Blocklist we can skip OCSP for CA certs
   bool blocklistIsFresh;
-  nsresult nsrv = mCertBlocklist->IsBlocklistFresh(&blocklistIsFresh);
+  nsresult nsrv = mCertStorage->IsBlocklistFresh(&blocklistIsFresh);
   if (NS_FAILED(nsrv)) {
     return Result::FATAL_ERROR_LIBRARY_FAILURE;
   }
 
   // TODO: We still need to handle the fallback for invalid stapled responses.
   // But, if/when we disable OCSP fetching by default, it would be ambiguous
   // whether security.OCSP.enable==0 means "I want the default" or "I really
   // never want you to ever fetch OCSP."
--- a/security/certverifier/NSSCertDBTrustDomain.h
+++ b/security/certverifier/NSSCertDBTrustDomain.h
@@ -227,17 +227,17 @@ class NSSCertDBTrustDomain : public mozi
   bool mSawDistrustedCAByPolicyError;
   const OriginAttributes& mOriginAttributes;
   const Vector<mozilla::pkix::Input>& mThirdPartyRootInputs;  // non-owning
   const Vector<mozilla::pkix::Input>&
       mThirdPartyIntermediateInputs;  // non-owning
   UniqueCERTCertList& mBuiltChain;    // non-owning
   PinningTelemetryInfo* mPinningTelemetryInfo;
   const char* mHostname;  // non-owning - only used for pinning checks
-  nsCOMPtr<nsICertStorage> mCertBlocklist;
+  nsCOMPtr<nsICertStorage> mCertStorage;
   CertVerifier::OCSPStaplingStatus mOCSPStaplingStatus;
   // Certificate Transparency data extracted during certificate verification
   UniqueSECItem mSCTListFromCertificate;
   UniqueSECItem mSCTListFromOCSPStapling;
 };
 
 }  // namespace psm
 }  // namespace mozilla
--- a/security/manager/ssl/RemoteSecuritySettings.jsm
+++ b/security/manager/ssl/RemoteSecuritySettings.jsm
@@ -4,16 +4,17 @@
 "use strict";
 
 const EXPORTED_SYMBOLS = ["RemoteSecuritySettings"];
 
 const {RemoteSettings} = ChromeUtils.import("resource://services-settings/remote-settings.js");
 
 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+const {X509} = ChromeUtils.import("resource://gre/modules/psm/X509.jsm", null);
 
 const INTERMEDIATES_BUCKET_PREF          = "security.remote_settings.intermediates.bucket";
 const INTERMEDIATES_CHECKED_SECONDS_PREF = "security.remote_settings.intermediates.checked";
 const INTERMEDIATES_COLLECTION_PREF      = "security.remote_settings.intermediates.collection";
 const INTERMEDIATES_DL_PER_POLL_PREF     = "security.remote_settings.intermediates.downloads_per_poll";
 const INTERMEDIATES_ENABLED_PREF         = "security.remote_settings.intermediates.enabled";
 const INTERMEDIATES_SIGNER_PREF          = "security.remote_settings.intermediates.signer";
 const LOGLEVEL_PREF                      = "browser.policies.loglevel";
@@ -59,19 +60,32 @@ function getHash(str) {
   let stringStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
   stringStream.data = str;
   hasher.updateFromStream(stringStream, -1);
 
   // convert the binary hash data to a hex string.
   return hexify(hasher.finish(false));
 }
 
-// Remove all colons from a string
-function stripColons(hexString) {
-  return hexString.replace(/:/g, "");
+// Converts a JS string to an array of bytes consisting of the char code at each
+// index in the string.
+function stringToBytes(s) {
+  let b = [];
+  for (let i = 0; i < s.length; i++) {
+    b.push(s.charCodeAt(i));
+  }
+  return b;
+}
+
+// Converts an array of bytes to a JS string using fromCharCode on each byte.
+function bytesToString(bytes) {
+  if (bytes.length > 65535) {
+    throw new Error("input too long for bytesToString");
+  }
+  return String.fromCharCode.apply(null, bytes);
 }
 
 this.RemoteSecuritySettings = class RemoteSecuritySettings {
     constructor() {
         this.client = RemoteSettings(Services.prefs.getCharPref(INTERMEDIATES_COLLECTION_PREF), {
           bucketNamePref: INTERMEDIATES_BUCKET_PREF,
           lastCheckTimePref: INTERMEDIATES_CHECKED_SECONDS_PREF,
           signerName: Services.prefs.getCharPref(INTERMEDIATES_SIGNER_PREF),
@@ -103,21 +117,21 @@ this.RemoteSecuritySettings = class Remo
         // certs more than once daily)
         const current = await this.client.get();
         const waiting = current.filter(record => !record.cert_import_complete);
 
         log.debug(`There are ${waiting.length} intermediates awaiting download.`);
 
         TelemetryStopwatch.start(INTERMEDIATES_UPDATE_MS_TELEMETRY);
 
-        const certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(Ci.nsIX509CertDB);
+        const certStorage = Cc["@mozilla.org/security/certstorage;1"].getService(Ci.nsICertStorage);
         const col = await this.client.openCollection();
 
         Promise.all(waiting.slice(0, maxDownloadsPerRun)
-          .map(record => this.maybeDownloadAttachment(record, col, certdb))
+          .map(record => this.maybeDownloadAttachment(record, col, certStorage))
         ).then(async () => {
           const finalCurrent = await this.client.get();
           const finalWaiting = finalCurrent.filter(record => !record.cert_import_complete);
           const countPreloaded = finalCurrent.length - finalWaiting.length;
 
           TelemetryStopwatch.finish(INTERMEDIATES_UPDATE_MS_TELEMETRY);
           Services.telemetry.scalarSet(INTERMEDIATES_PRELOADED_TELEMETRY,
                                        countPreloaded);
@@ -138,29 +152,28 @@ this.RemoteSecuritySettings = class Remo
           log.warn(`Unable to update intermediate preloads: ${err}`);
 
           Services.telemetry.getHistogramById(INTERMEDIATES_ERRORS_TELEMETRY)
             .add("failedToObserve");
         }
     }
 
     // This method returns a promise to RemoteSettingsClient.maybeSync method.
-    onSync(event) {
+    async onSync(event) {
         const {
           data: {deleted},
         } = event;
 
         if (!Services.prefs.getBoolPref(INTERMEDIATES_ENABLED_PREF, true)) {
           log.debug("Intermediate Preloading is disabled");
-          return Promise.resolve();
+          return;
         }
 
         log.debug(`Removing ${deleted.length} Intermediate certificates`);
-        this.removeCerts(deleted);
-        return Promise.resolve();
+        await this.removeCerts(deleted);
     }
 
     /**
      * Downloads the attachment data of the given record. Does not retry,
      * leaving that to the caller.
      * @param  {AttachmentRecord} record The data to obtain
      * @return {Promise}          resolves to a Uint8Array on success
      */
@@ -190,114 +203,122 @@ this.RemoteSecuritySettings = class Remo
 
     /**
      * Attempts to download the attachment, assuming it's not been processed
      * already. Does not retry, and always resolves (e.g., does not reject upon
      * failure.) Errors are reported via Cu.reportError; If you need to know
      * success/failure, check record.cert_import_complete.
      * @param  {AttachmentRecord} record defines which data to obtain
      * @param  {KintoCollection}  col The kinto collection to update
-     * @param  {nsIX509CertDB}    certdb The NSS DB to update
+     * @param  {nsICertStorage}   certStorage The certificate storage to update
      * @return {Promise}          a Promise representing the transaction
      */
-    async maybeDownloadAttachment(record, col, certdb) {
+    async maybeDownloadAttachment(record, col, certStorage) {
       const {attachment: {hash, size}} = record;
 
       return this._downloadAttachmentBytes(record)
-      .then(function(attachmentData) {
+      .then(async function(attachmentData) {
         if (!attachmentData || attachmentData.length == 0) {
           // Bug 1519273 - Log telemetry for these rejections
           log.debug(`Empty attachment. Hash=${hash}`);
 
           Services.telemetry.getHistogramById(INTERMEDIATES_ERRORS_TELEMETRY)
             .add("emptyAttachment");
 
-          return Promise.reject();
+          return;
         }
 
         // check the length
         if (attachmentData.length !== size) {
           log.debug(`Unexpected attachment length. Hash=${hash} Lengths ${attachmentData.length} != ${size}`);
 
           Services.telemetry.getHistogramById(INTERMEDIATES_ERRORS_TELEMETRY)
             .add("unexpectedLength");
 
-          return Promise.reject();
+          return;
         }
 
         // check the hash
         let dataAsString = gTextDecoder.decode(attachmentData);
         let calculatedHash = getHash(dataAsString);
         if (calculatedHash !== hash) {
           log.warn(`Invalid hash. CalculatedHash=${calculatedHash}, Hash=${hash}, data=${dataAsString}`);
 
           Services.telemetry.getHistogramById(INTERMEDIATES_ERRORS_TELEMETRY)
             .add("unexpectedHash");
 
-          return Promise.reject();
+          return;
         }
 
-        // split off the header and footer, base64 decode, construct the cert
-        // from the resulting DER data.
-        let b64data = dataAsString.split("-----")[2].replace(/\s/g, "");
-        let certDer = atob(b64data);
-
+        let certBase64;
+        let subjectBase64;
         try {
-          log.debug(`Adding cert. Hash=${hash}. Size=${size}`);
+          // split off the header and footer
+          certBase64 = dataAsString.split("-----")[2].replace(/\s/g, "");
+          // get an array of bytes so we can use X509.jsm
+          let certBytes = stringToBytes(atob(certBase64));
+          let cert = new X509.Certificate();
+          cert.parse(certBytes);
+          // get the DER-encoded subject and get a base64-encoded string from it
+          // TODO(bug 1542028): add getters for _der and _bytes
+          subjectBase64 = btoa(bytesToString(cert.tbsCertificate.subject._der._bytes));
+        } catch (err) {
+          Cu.reportError(`Failed to decode cert: ${err}`);
 
-          // We can assume that roots obtained from remote-settings are part of
-          // the root program. If they aren't, they won't be used for path-
-          // building or have trust anyway, so just add it to the DB.
-          certdb.addCert(certDer, ",,");
-        } catch (err) {
-          Cu.reportError(`Failed to update CertDB: ${err}`);
-
+          // Re-purpose the "failedToUpdateNSS" telemetry tag as "failed to
+          // decode preloaded intermediate certificate"
           Services.telemetry.getHistogramById(INTERMEDIATES_ERRORS_TELEMETRY)
             .add("failedToUpdateNSS");
 
-          return Promise.reject();
+          return;
+        }
+        log.debug(`Adding cert. Hash=${hash}. Size=${size}`);
+        // We can assume that certs obtained from remote-settings are part of
+        // the root program. If they aren't, they won't be used for path-
+        // building anyway, so just add it to the DB with trust set to
+        // "inherit".
+        let result = await new Promise((resolve) => {
+          certStorage.addCertBySubject(certBase64, subjectBase64,
+                                       Ci.nsICertStorage.TRUST_INHERIT,
+                                       resolve);
+        });
+        if (result != Cr.NS_OK) {
+          Cu.reportError(`Failed to add to cert storage: ${result}`);
+          Services.telemetry.getHistogramById(INTERMEDIATES_ERRORS_TELEMETRY)
+            .add("failedToUpdateDB");
+          return;
         }
 
         record.cert_import_complete = true;
-        return col.update(record);
+        await col.update(record);
       })
       .catch(() => {
         // Don't abort the outer Promise.all because of an error. Errors were
         // sent using Cu.reportError()
         Services.telemetry.getHistogramById(INTERMEDIATES_ERRORS_TELEMETRY)
           .add("failedToDownloadMisc");
       });
     }
 
     async maybeSync(expectedTimestamp, options) {
       return this.client.maybeSync(expectedTimestamp, options);
     }
 
-    // Note that removing certificates from the DB will likely not have an
-    // effect until restart.
-    removeCerts(records) {
-      let recordsToRemove = records;
-
-      let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(Ci.nsIX509CertDB);
-
-      for (let cert of certdb.getCerts().getEnumerator()) {
-        let certHash = stripColons(cert.sha256Fingerprint);
-        for (let i = 0; i < recordsToRemove.length; i++) {
-          let record = recordsToRemove[i];
-          if (record.pubKeyHash == certHash) {
-            try {
-              certdb.deleteCertificate(cert);
-              recordsToRemove.splice(i, 1);
-            } catch (err) {
-              Cu.reportError(`Failed to remove intermediate certificate Hash=${certHash}: ${err}`);
-            }
-            break;
-          }
+    async removeCerts(recordsToRemove) {
+      let certStorage = Cc["@mozilla.org/security/certstorage;1"].getService(Ci.nsICertStorage);
+      let failures = 0;
+      for (let record of recordsToRemove) {
+        let result = await new Promise((resolve) => {
+          certStorage.removeCertByHash(record.pubKeyHash, resolve);
+        });
+        if (result != Cr.NS_OK) {
+          Cu.reportError(`Failed to remove intermediate certificate Hash=${record.pubKeyHash}: ${result}`);
+          failures++;
         }
       }
 
-      if (recordsToRemove.length > 0) {
-        Cu.reportError(`Failed to remove ${recordsToRemove.length} intermediate certificates`);
+      if (failures > 0) {
+        Cu.reportError(`Failed to remove ${failures} intermediate certificates`);
         Services.telemetry.getHistogramById(INTERMEDIATES_ERRORS_TELEMETRY)
           .add("failedToRemove");
       }
     }
 };
--- a/security/manager/ssl/X509.jsm
+++ b/security/manager/ssl/X509.jsm
@@ -1,17 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-// Until DER.jsm is actually used in production code, this is where we have to
-// import it from.
-var { DER } = ChromeUtils.import("resource://testing-common/psm/DER.jsm", null);
+var { DER } = ChromeUtils.import("resource://gre/modules/psm/DER.jsm", null);
 
 const ERROR_UNSUPPORTED_ASN1 = "unsupported asn.1";
 const ERROR_TIME_NOT_VALID = "Time not valid";
 const ERROR_LIBRARY_FAILURE = "library failure";
 
 const X509v3 = 2;
 
 /**
--- a/security/manager/ssl/cert_storage/Cargo.toml
+++ b/security/manager/ssl/cert_storage/Cargo.toml
@@ -1,15 +1,16 @@
 [package]
 name = "cert_storage"
 version = "0.0.1"
 authors = ["Dana Keeler <dkeeler@mozilla.com>", "Mark Goodwin <mgoodwin@mozilla.com"]
 
 [dependencies]
 base64 = "0.10"
+byteorder = "1.2.7"
 crossbeam-utils = "0.6.3"
 lmdb-rkv = "0.11"
 log = "0.4"
 moz_task = { path = "../../../../xpcom/rust/moz_task" }
 nserror = { path = "../../../../xpcom/rust/nserror" }
 nsstring = { path = "../../../../xpcom/rust/nsstring" }
 rkv = "^0.9"
 sha2 = "^0.8"
--- a/security/manager/ssl/cert_storage/src/lib.rs
+++ b/security/manager/ssl/cert_storage/src/lib.rs
@@ -1,43 +1,47 @@
 /* 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/. */
 
 extern crate base64;
+extern crate byteorder;
 extern crate crossbeam_utils;
 extern crate lmdb;
 #[macro_use]
 extern crate log;
 extern crate moz_task;
 extern crate nserror;
 extern crate nsstring;
 extern crate rkv;
 extern crate sha2;
 extern crate thin_vec;
 extern crate time;
 #[macro_use]
 extern crate xpcom;
 extern crate style;
 
+use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt};
 use crossbeam_utils::atomic::AtomicCell;
 use lmdb::EnvironmentFlags;
 use moz_task::{create_thread, is_main_thread, Task, TaskRunnable};
 use nserror::{
     nsresult, NS_ERROR_FAILURE, NS_ERROR_NOT_SAME_THREAD, NS_ERROR_NO_AGGREGATION,
     NS_ERROR_UNEXPECTED, NS_OK,
 };
 use nsstring::{nsACString, nsAString, nsCStr, nsCString, nsString};
+use rkv::error::StoreError;
 use rkv::{Rkv, SingleStore, StoreOptions, Value};
 use sha2::{Digest, Sha256};
 use std::collections::HashMap;
 use std::ffi::{CStr, CString};
 use std::fmt::Display;
 use std::fs::{create_dir_all, remove_file, File};
 use std::io::{BufRead, BufReader};
+use std::mem::size_of;
 use std::os::raw::c_char;
 use std::path::PathBuf;
 use std::slice;
 use std::str;
 use std::sync::{Arc, Mutex, RwLock};
 use std::time::{Duration, SystemTime};
 use thin_vec::ThinVec;
 use xpcom::interfaces::{
@@ -46,22 +50,27 @@ use xpcom::interfaces::{
     nsISupports, nsIThread,
 };
 use xpcom::{nsIID, GetterAddrefs, RefPtr, ThreadBoundRefPtr, XpCom};
 
 const PREFIX_REV_IS: &str = "is";
 const PREFIX_REV_SPK: &str = "spk";
 const PREFIX_CRLITE: &str = "crlite";
 const PREFIX_WL: &str = "wl";
+const PREFIX_SUBJECT: &str = "subject";
+const PREFIX_CERT: &str = "cert";
 
-fn make_key(prefix: &str, part_a: &[u8], part_b: &[u8]) -> Vec<u8> {
-    let mut key = prefix.as_bytes().to_owned();
-    key.extend_from_slice(part_a);
-    key.extend_from_slice(part_b);
-    key
+macro_rules! make_key {
+    ( $prefix:expr, $( $part:expr ),+ ) => {
+        {
+            let mut key = $prefix.as_bytes().to_owned();
+            $( key.extend_from_slice($part); )+
+            key
+        }
+    }
 }
 
 #[allow(non_camel_case_types, non_snake_case)]
 
 /// `SecurityStateError` is a type to represent errors in accessing or
 /// modifying security state.
 #[derive(Debug)]
 struct SecurityStateError {
@@ -109,17 +118,20 @@ impl SecurityState {
         if self.env_and_store.is_some() {
             return Ok(());
         }
 
         let store_path = get_store_path(&self.profile_path)?;
 
         // Open the store in read-write mode initially to create it (if needed)
         // and migrate data from the old store (if any).
-        let env = Rkv::new(store_path.as_path())?;
+        let mut builder = Rkv::environment_builder();
+        builder.set_max_dbs(2);
+        builder.set_map_size(16777216); // 16MB
+        let env = Rkv::from_env(store_path.as_path(), builder)?;
         let store = env.open_single("cert_storage", StoreOptions::create())?;
 
         // if the profile has a revocations.txt, migrate it and remove the file
         let mut revocations_path = self.profile_path.clone();
         revocations_path.push("revocations.txt");
         if revocations_path.exists() {
             SecurityState::migrate(&revocations_path, &env, &store)?;
             remove_file(revocations_path)?;
@@ -178,23 +190,23 @@ impl SecurityState {
             let l_sans_prefix = match base64::decode(&l[1..]) {
                 Ok(decoded) => decoded,
                 Err(_) => continue,
             };
             if let Some(name) = &dn {
                 if leading_char == '\t' {
                     let _ = store.put(
                         &mut writer,
-                        &make_key(PREFIX_REV_SPK, name, &l_sans_prefix),
+                        &make_key!(PREFIX_REV_SPK, name, &l_sans_prefix),
                         &value,
                     );
                 } else {
                     let _ = store.put(
                         &mut writer,
-                        &make_key(PREFIX_REV_IS, name, &l_sans_prefix),
+                        &make_key!(PREFIX_REV_IS, name, &l_sans_prefix),
                         &value,
                     );
                 }
             }
         }
 
         writer.commit()?;
         Ok(())
@@ -202,17 +214,20 @@ impl SecurityState {
 
     fn reopen_store_read_write(&mut self) -> Result<(), SecurityStateError> {
         let store_path = get_store_path(&self.profile_path)?;
 
         // Move out and drop the EnvAndStore first so we don't have
         // two LMDB environments open at the same time.
         drop(self.env_and_store.take());
 
-        let env = Rkv::new(store_path.as_path())?;
+        let mut builder = Rkv::environment_builder();
+        builder.set_max_dbs(2);
+        builder.set_map_size(16777216); // 16MB
+        let env = Rkv::from_env(store_path.as_path(), builder)?;
         let store = env.open_single("cert_storage", StoreOptions::create())?;
         self.env_and_store.replace(EnvAndStore { env, store });
         Ok(())
     }
 
     fn reopen_store_read_only(&mut self) -> Result<(), SecurityStateError> {
         let store_path = get_store_path(&self.profile_path)?;
 
@@ -255,17 +270,20 @@ impl SecurityState {
         let reader = env_and_store.env.read()?;
         match env_and_store.store.get(&reader, key) {
             Ok(Some(Value::I64(i)))
                 if i <= (std::i16::MAX as i64) && i >= (std::i16::MIN as i64) =>
             {
                 Ok(Some(i as i16))
             }
             Ok(None) => Ok(None),
-            _ => Err(SecurityStateError::from(
+            Ok(_) => Err(SecurityStateError::from(
+                "Unexpected type when trying to get a Value::I64",
+            )),
+            Err(_) => Err(SecurityStateError::from(
                 "There was a problem getting the value",
             )),
         }
     }
 
     pub fn set_revocations(
         &mut self,
         entries: &[(Vec<u8>, i16)],
@@ -291,41 +309,41 @@ impl SecurityState {
     }
 
     pub fn set_enrollment(
         &mut self,
         issuer: &[u8],
         serial: &[u8],
         state: i16,
     ) -> Result<(), SecurityStateError> {
-        self.write_entry(&make_key(PREFIX_CRLITE, issuer, serial), state)
+        self.write_entry(&make_key!(PREFIX_CRLITE, issuer, serial), state)
     }
 
     pub fn set_whitelist(
         &mut self,
         issuer: &[u8],
         serial: &[u8],
         state: i16,
     ) -> Result<(), SecurityStateError> {
-        self.write_entry(&make_key(PREFIX_WL, issuer, serial), state)
+        self.write_entry(&make_key!(PREFIX_WL, issuer, serial), state)
     }
 
     pub fn get_revocation_state(
         &self,
         issuer: &[u8],
         serial: &[u8],
         subject: &[u8],
         pub_key: &[u8],
     ) -> Result<i16, SecurityStateError> {
         let mut digest = Sha256::default();
         digest.input(pub_key);
         let pub_key_hash = digest.result();
 
-        let subject_pubkey = make_key(PREFIX_REV_SPK, subject, &pub_key_hash);
-        let issuer_serial = make_key(PREFIX_REV_IS, issuer, serial);
+        let subject_pubkey = make_key!(PREFIX_REV_SPK, subject, &pub_key_hash);
+        let issuer_serial = make_key!(PREFIX_REV_IS, issuer, serial);
 
         let st: i16 = match self.read_entry(&issuer_serial) {
             Ok(Some(value)) => value,
             Ok(None) => nsICertStorage::STATE_UNSET as i16,
             Err(_) => {
                 return Err(SecurityStateError::from(
                     "problem reading revocation state (from issuer / serial)",
                 ));
@@ -347,30 +365,30 @@ impl SecurityState {
         }
     }
 
     pub fn get_enrollment_state(
         &self,
         issuer: &[u8],
         serial: &[u8],
     ) -> Result<i16, SecurityStateError> {
-        let issuer_serial = make_key(PREFIX_CRLITE, issuer, serial);
+        let issuer_serial = make_key!(PREFIX_CRLITE, issuer, serial);
         match self.read_entry(&issuer_serial) {
             Ok(Some(value)) => Ok(value),
             Ok(None) => Ok(nsICertStorage::STATE_UNSET as i16),
             Err(_) => return Err(SecurityStateError::from("problem reading enrollment state")),
         }
     }
 
     pub fn get_whitelist_state(
         &self,
         issuer: &[u8],
         serial: &[u8],
     ) -> Result<i16, SecurityStateError> {
-        let issuer_serial = make_key(PREFIX_WL, issuer, serial);
+        let issuer_serial = make_key!(PREFIX_WL, issuer, serial);
         match self.read_entry(&issuer_serial) {
             Ok(Some(value)) => Ok(value),
             Ok(None) => Ok(nsICertStorage::STATE_UNSET as i16),
             Err(_) => Err(SecurityStateError::from("problem reading whitelist state")),
         }
     }
 
     pub fn is_data_fresh(
@@ -415,16 +433,320 @@ impl SecurityState {
             "services.blocklist.crlite.checked",
             "security.onecrl.maximum_staleness_in_seconds",
         )
     }
 
     pub fn pref_seen(&mut self, name: &str, value: u32) {
         self.int_prefs.insert(name.to_owned(), value);
     }
+
+    // To store a certificate by subject, we first create a Cert out of the given cert, subject, and
+    // trust. We hash the certificate with sha-256 to obtain a unique* key for that certificate, and
+    // we store the Cert in the database. We also look up or create a CertHashList for the given
+    // subject and add the new certificate's hash if it isn't present in the list. If it wasn't
+    // present, we write out the updated CertHashList.
+    // *By the pigeon-hole principle, there exist collisions for sha-256, so this key is not
+    // actually unique. We rely on the assumption that sha-256 is a cryptographically strong hash.
+    // If an adversary can find two different certificates with the same sha-256 hash, they can
+    // probably forge a sha-256-based signature, so assuming the keys we create here are unique is
+    // not a security issue.
+    pub fn add_cert_by_subject(
+        &mut self,
+        cert_der: &[u8],
+        subject: &[u8],
+        trust: i16,
+    ) -> Result<(), SecurityStateError> {
+        self.reopen_store_read_write()?;
+        {
+            let env_and_store = match self.env_and_store.as_mut() {
+                Some(env_and_store) => env_and_store,
+                None => return Err(SecurityStateError::from("env and store not initialized?")),
+            };
+            let mut writer = env_and_store.env.write()?;
+
+            let mut digest = Sha256::default();
+            digest.input(cert_der);
+            let cert_hash = digest.result();
+            let cert_key = make_key!(PREFIX_CERT, &cert_hash);
+            let cert = Cert::new(cert_der, subject, trust)?;
+            env_and_store
+                .store
+                .put(&mut writer, &cert_key, &Value::Blob(&cert.to_bytes()?))?;
+            let subject_key = make_key!(PREFIX_SUBJECT, subject);
+            // This reader will only be able to "see" data outside the current transaction. This is
+            // fine, though, because what we're reading has not yet been touched by this
+            // transaction.
+            let reader = env_and_store.env.read()?;
+            let empty_vec = Vec::new();
+            let old_cert_hash_list = match env_and_store.store.get(&reader, &subject_key)? {
+                Some(Value::Blob(hashes)) => hashes,
+                Some(_) => &empty_vec,
+                None => &empty_vec,
+            };
+            let new_cert_hash_list = CertHashList::add(old_cert_hash_list, &cert_hash)?;
+            if new_cert_hash_list.len() != old_cert_hash_list.len() {
+                env_and_store.store.put(
+                    &mut writer,
+                    &subject_key,
+                    &Value::Blob(&new_cert_hash_list),
+                )?;
+            }
+
+            writer.commit()?;
+        }
+        self.reopen_store_read_only()?;
+        Ok(())
+    }
+
+    // Given a certificate's sha-256 hash, we can look up its Cert entry in the database. We use
+    // this to find its subject so we can look up the CertHashList it should appear in. If that list
+    // contains the given hash, we remove it and update the CertHashList. Finally we delete the Cert
+    // entry.
+    pub fn remove_cert_by_hash(&mut self, hash: &[u8]) -> Result<(), SecurityStateError> {
+        self.reopen_store_read_write()?;
+        {
+            let env_and_store = match self.env_and_store.as_mut() {
+                Some(env_and_store) => env_and_store,
+                None => return Err(SecurityStateError::from("env and store not initialized?")),
+            };
+            let mut writer = env_and_store.env.write()?;
+
+            let reader = env_and_store.env.read()?;
+            let cert_key = make_key!(PREFIX_CERT, hash);
+            if let Some(Value::Blob(cert_bytes)) = env_and_store.store.get(&reader, &cert_key)? {
+                if let Ok(cert) = Cert::from_bytes(cert_bytes) {
+                    let subject_key = make_key!(PREFIX_SUBJECT, &cert.subject);
+                    let empty_vec = Vec::new();
+                    let old_cert_hash_list = match env_and_store.store.get(&reader, &subject_key)? {
+                        Some(Value::Blob(hashes)) => hashes,
+                        Some(_) => &empty_vec,
+                        None => &empty_vec,
+                    };
+                    let new_cert_hash_list = CertHashList::remove(old_cert_hash_list, hash)?;
+                    if new_cert_hash_list.len() != old_cert_hash_list.len() {
+                        env_and_store.store.put(
+                            &mut writer,
+                            &subject_key,
+                            &Value::Blob(&new_cert_hash_list),
+                        )?;
+                    }
+                }
+            }
+            match env_and_store.store.delete(&mut writer, &cert_key) {
+                Ok(()) => {}
+                Err(StoreError::LmdbError(lmdb::Error::NotFound)) => {}
+                Err(e) => return Err(SecurityStateError::from(e)),
+            };
+            writer.commit()?;
+        }
+        self.reopen_store_read_only()?;
+        Ok(())
+    }
+
+    // Given a certificate's subject, we look up the corresponding CertHashList. In theory, each
+    // hash in that list corresponds to a certificate with the given subject, so we look up each of
+    // these (assuming the database is consistent and contains them) and add them to the given list.
+    // If we encounter an inconsistency, we continue looking as best we can.
+    pub fn find_certs_by_subject(
+        &self,
+        subject: &[u8],
+        certs: &mut ThinVec<ThinVec<u8>>,
+    ) -> Result<(), SecurityStateError> {
+        let env_and_store = match self.env_and_store.as_ref() {
+            Some(env_and_store) => env_and_store,
+            None => return Err(SecurityStateError::from("env and store not initialized?")),
+        };
+        let reader = env_and_store.env.read()?;
+        certs.clear();
+        let subject_key = make_key!(PREFIX_SUBJECT, subject);
+        let empty_vec = Vec::new();
+        let cert_hash_list_bytes = match env_and_store.store.get(&reader, &subject_key)? {
+            Some(Value::Blob(hashes)) => hashes,
+            Some(_) => &empty_vec,
+            None => &empty_vec,
+        };
+        let cert_hash_list = CertHashList::new(cert_hash_list_bytes)?;
+        for cert_hash in cert_hash_list.into_iter() {
+            let cert_key = make_key!(PREFIX_CERT, cert_hash);
+            // If there's some inconsistency, we don't want to fail the whole operation - just go
+            // for best effort and find as many certificates as we can.
+            if let Some(Value::Blob(cert_bytes)) = env_and_store.store.get(&reader, &cert_key)? {
+                if let Ok(cert) = Cert::from_bytes(cert_bytes) {
+                    let mut thin_vec_cert = ThinVec::with_capacity(cert.der.len());
+                    thin_vec_cert.extend_from_slice(&cert.der);
+                    certs.push(thin_vec_cert);
+                }
+            }
+        }
+        Ok(())
+    }
+}
+
+const CERT_SERIALIZATION_VERSION_1: u8 = 1;
+
+// A Cert consists of its DER encoding, its DER-encoded subject, and its trust (currently
+// nsICertStorage::TRUST_INHERIT, but in the future nsICertStorage::TRUST_ANCHOR may also be used).
+// The length of each encoding must be representable by a u16 (so 65535 bytes is the longest a
+// certificate can be).
+struct Cert<'a> {
+    der: &'a [u8],
+    subject: &'a [u8],
+    trust: i16,
+}
+
+impl<'a> Cert<'a> {
+    fn new(der: &'a [u8], subject: &'a [u8], trust: i16) -> Result<Cert<'a>, SecurityStateError> {
+        if der.len() > u16::max as usize {
+            return Err(SecurityStateError::from("certificate is too long"));
+        }
+        if subject.len() > u16::max as usize {
+            return Err(SecurityStateError::from("subject is too long"));
+        }
+        Ok(Cert {
+            der,
+            subject,
+            trust,
+        })
+    }
+
+    fn from_bytes(encoded: &'a [u8]) -> Result<Cert<'a>, SecurityStateError> {
+        if encoded.len() < size_of::<u8>() {
+            return Err(SecurityStateError::from("invalid Cert: no version?"));
+        }
+        let (mut version, rest) = encoded.split_at(size_of::<u8>());
+        let version = version.read_u8()?;
+        if version != CERT_SERIALIZATION_VERSION_1 {
+            return Err(SecurityStateError::from("invalid Cert: unexpected version"));
+        }
+
+        if rest.len() < size_of::<u16>() {
+            return Err(SecurityStateError::from("invalid Cert: no der len?"));
+        }
+        let (mut der_len, rest) = rest.split_at(size_of::<u16>());
+        let der_len = der_len.read_u16::<NetworkEndian>()? as usize;
+        if rest.len() < der_len {
+            return Err(SecurityStateError::from("invalid Cert: no der?"));
+        }
+        let (der, rest) = rest.split_at(der_len);
+
+        if rest.len() < size_of::<u16>() {
+            return Err(SecurityStateError::from("invalid Cert: no subject len?"));
+        }
+        let (mut subject_len, rest) = rest.split_at(size_of::<u16>());
+        let subject_len = subject_len.read_u16::<NetworkEndian>()? as usize;
+        if rest.len() < subject_len {
+            return Err(SecurityStateError::from("invalid Cert: no subject?"));
+        }
+        let (subject, mut rest) = rest.split_at(subject_len);
+
+        if rest.len() < size_of::<i16>() {
+            return Err(SecurityStateError::from("invalid Cert: no trust?"));
+        }
+        let trust = rest.read_i16::<NetworkEndian>()?;
+        if rest.len() > 0 {
+            return Err(SecurityStateError::from("invalid Cert: trailing data?"));
+        }
+
+        Ok(Cert {
+            der,
+            subject,
+            trust,
+        })
+    }
+
+    fn to_bytes(&self) -> Result<Vec<u8>, SecurityStateError> {
+        let mut bytes = Vec::with_capacity(
+            size_of::<u8>()
+                + size_of::<u16>()
+                + self.der.len()
+                + size_of::<u16>()
+                + self.subject.len()
+                + size_of::<i16>(),
+        );
+        bytes.write_u8(CERT_SERIALIZATION_VERSION_1)?;
+        if self.der.len() > u16::max as usize {
+            return Err(SecurityStateError::from("certificate is too long"));
+        }
+        bytes.write_u16::<NetworkEndian>(self.der.len() as u16)?;
+        bytes.extend_from_slice(&self.der);
+        if self.subject.len() > u16::max as usize {
+            return Err(SecurityStateError::from("subject is too long"));
+        }
+        bytes.write_u16::<NetworkEndian>(self.subject.len() as u16)?;
+        bytes.extend_from_slice(&self.subject);
+        bytes.write_i16::<NetworkEndian>(self.trust)?;
+        Ok(bytes)
+    }
+}
+
+// A CertHashList is a list of sha-256 hashes of DER-encoded certificates.
+struct CertHashList<'a> {
+    hashes: Vec<&'a [u8]>,
+}
+
+impl<'a> CertHashList<'a> {
+    fn new(hashes_bytes: &'a [u8]) -> Result<CertHashList<'a>, SecurityStateError> {
+        if hashes_bytes.len() % Sha256::output_size() != 0 {
+            return Err(SecurityStateError::from(
+                "unexpected length for cert hash list",
+            ));
+        }
+        let mut hashes = Vec::with_capacity(hashes_bytes.len() / Sha256::output_size());
+        for hash in hashes_bytes.chunks_exact(Sha256::output_size()) {
+            hashes.push(hash);
+        }
+        Ok(CertHashList { hashes })
+    }
+
+    fn add(hashes_bytes: &[u8], new_hash: &[u8]) -> Result<Vec<u8>, SecurityStateError> {
+        if hashes_bytes.len() % Sha256::output_size() != 0 {
+            return Err(SecurityStateError::from(
+                "unexpected length for cert hash list",
+            ));
+        }
+        if new_hash.len() != Sha256::output_size() {
+            return Err(SecurityStateError::from("unexpected cert hash length"));
+        }
+        for hash in hashes_bytes.chunks_exact(Sha256::output_size()) {
+            if hash == new_hash {
+                return Ok(hashes_bytes.to_owned());
+            }
+        }
+        let mut combined = hashes_bytes.to_owned();
+        combined.extend_from_slice(new_hash);
+        Ok(combined)
+    }
+
+    fn remove(hashes_bytes: &[u8], cert_hash: &[u8]) -> Result<Vec<u8>, SecurityStateError> {
+        if hashes_bytes.len() % Sha256::output_size() != 0 {
+            return Err(SecurityStateError::from(
+                "unexpected length for cert hash list",
+            ));
+        }
+        if cert_hash.len() != Sha256::output_size() {
+            return Err(SecurityStateError::from("unexpected cert hash length"));
+        }
+        let mut result = Vec::with_capacity(hashes_bytes.len());
+        for hash in hashes_bytes.chunks_exact(Sha256::output_size()) {
+            if hash != cert_hash {
+                result.extend_from_slice(hash);
+            }
+        }
+        Ok(result)
+    }
+}
+
+impl<'a> IntoIterator for CertHashList<'a> {
+    type Item = &'a [u8];
+    type IntoIter = std::vec::IntoIter<&'a [u8]>;
+
+    fn into_iter(self) -> Self::IntoIter {
+        self.hashes.into_iter()
+    }
 }
 
 fn get_path_from_directory_service(key: &str) -> Result<PathBuf, SecurityStateError> {
     let directory_service = match xpcom::services::get_DirectoryService() {
         Some(ds) => ds,
         _ => return Err(SecurityStateError::from("None")),
     };
 
@@ -534,142 +856,64 @@ fn read_int_pref(name: &str) -> Result<u
     };
     if pref_value < 0 {
         Ok(0)
     } else {
         Ok(pref_value as u32)
     }
 }
 
-// This is a helper for defining a task that will perform a specific action on a background thread.
-// Its arguments are the name of the task and the name of the function in SecurityState to call.
-macro_rules! security_state_task {
-    ($task_name:ident, $security_state_function_name:ident) => {
-        struct $task_name {
-            callback: AtomicCell<Option<ThreadBoundRefPtr<nsICertStorageCallback>>>,
-            security_state: Arc<RwLock<SecurityState>>,
-            argument_a: Vec<u8>,
-            argument_b: Vec<u8>,
-            state: i16,
-            result: AtomicCell<Option<nserror::nsresult>>,
-        }
-        impl $task_name {
-            fn new(
-                callback: &nsICertStorageCallback,
-                security_state: &Arc<RwLock<SecurityState>>,
-                argument_a: Vec<u8>,
-                argument_b: Vec<u8>,
-                state: i16,
-            ) -> $task_name {
-                $task_name {
-                    callback: AtomicCell::new(Some(ThreadBoundRefPtr::new(RefPtr::new(callback)))),
-                    security_state: Arc::clone(security_state),
-                    argument_a,
-                    argument_b,
-                    state,
-                    result: AtomicCell::new(None),
-                }
-            }
-        }
-        impl Task for $task_name {
-            fn run(&self) {
-                let mut ss = match self.security_state.write() {
-                    Ok(ss) => ss,
-                    Err(_) => {
-                        self.result.store(Some(NS_ERROR_FAILURE));
-                        return;
-                    }
-                };
-                // this is a no-op if the DB is already open
-                match ss.open_db() {
-                    Ok(()) => {}
-                    Err(_) => {
-                        self.result.store(Some(NS_ERROR_FAILURE));
-                        return;
-                    }
-                };
-                match ss.$security_state_function_name(
-                    &self.argument_a,
-                    &self.argument_b,
-                    self.state,
-                ) {
-                    Ok(_) => self.result.store(Some(NS_OK)),
-                    Err(_) => self.result.store(Some(NS_ERROR_FAILURE)),
-                };
-            }
-
-            fn done(&self) -> Result<(), nsresult> {
-                let threadbound = self.callback.swap(None).ok_or(NS_ERROR_FAILURE)?;
-                let callback = threadbound.get_ref().ok_or(NS_ERROR_FAILURE)?;
-                let nsrv = match self.result.swap(None) {
-                    Some(result) => unsafe { callback.Done(result) },
-                    None => unsafe { callback.Done(NS_ERROR_FAILURE) },
-                };
-                match nsrv {
-                    NS_OK => Ok(()),
-                    e => Err(e),
-                }
-            }
-        }
-    };
+// This is a helper for creating a task that will perform a specific action on a background thread.
+struct SecurityStateTask<F: FnOnce(&mut SecurityState) -> Result<(), SecurityStateError>> {
+    callback: AtomicCell<Option<ThreadBoundRefPtr<nsICertStorageCallback>>>,
+    security_state: Arc<RwLock<SecurityState>>,
+    result: AtomicCell<nserror::nsresult>,
+    task_action: AtomicCell<Option<F>>,
 }
 
-security_state_task!(SetEnrollmentTask, set_enrollment);
-security_state_task!(SetWhitelistTask, set_whitelist);
-
-struct SetRevocationsTask {
-    callback: AtomicCell<Option<ThreadBoundRefPtr<nsICertStorageCallback>>>,
-    security_state: Arc<RwLock<SecurityState>>,
-    entries: Vec<(Vec<u8>, i16)>,
-    result: AtomicCell<Option<nserror::nsresult>>,
-}
-impl SetRevocationsTask {
+impl<F: FnOnce(&mut SecurityState) -> Result<(), SecurityStateError>> SecurityStateTask<F> {
     fn new(
         callback: &nsICertStorageCallback,
         security_state: &Arc<RwLock<SecurityState>>,
-        entries: Vec<(Vec<u8>, i16)>,
-    ) -> SetRevocationsTask {
-        SetRevocationsTask {
+        task_action: F,
+    ) -> SecurityStateTask<F> {
+        SecurityStateTask {
             callback: AtomicCell::new(Some(ThreadBoundRefPtr::new(RefPtr::new(callback)))),
             security_state: Arc::clone(security_state),
-            entries,
-            result: AtomicCell::new(None),
+            result: AtomicCell::new(NS_ERROR_FAILURE),
+            task_action: AtomicCell::new(Some(task_action)),
         }
     }
 }
-impl Task for SetRevocationsTask {
+
+impl<F: FnOnce(&mut SecurityState) -> Result<(), SecurityStateError>> Task
+    for SecurityStateTask<F>
+{
     fn run(&self) {
         let mut ss = match self.security_state.write() {
             Ok(ss) => ss,
-            Err(_) => {
-                self.result.store(Some(NS_ERROR_FAILURE));
-                return;
-            }
+            Err(_) => return,
         };
         // this is a no-op if the DB is already open
-        match ss.open_db() {
-            Ok(()) => {}
-            Err(_) => {
-                self.result.store(Some(NS_ERROR_FAILURE));
-                return;
-            }
-        };
-        match ss.set_revocations(&self.entries) {
-            Ok(_) => self.result.store(Some(NS_OK)),
-            Err(_) => self.result.store(Some(NS_ERROR_FAILURE)),
-        };
+        if ss.open_db().is_err() {
+            return;
+        }
+        if let Some(task_action) = self.task_action.swap(None) {
+            let rv = task_action(&mut ss)
+                .and_then(|_| Ok(NS_OK))
+                .unwrap_or(NS_ERROR_FAILURE);
+            self.result.store(rv);
+        }
     }
 
     fn done(&self) -> Result<(), nsresult> {
         let threadbound = self.callback.swap(None).ok_or(NS_ERROR_FAILURE)?;
         let callback = threadbound.get_ref().ok_or(NS_ERROR_FAILURE)?;
-        let nsrv = match self.result.swap(None) {
-            Some(result) => unsafe { callback.Done(result) },
-            None => unsafe { callback.Done(NS_ERROR_FAILURE) },
-        };
+        let result = self.result.swap(NS_ERROR_FAILURE);
+        let nsrv = unsafe { callback.Done(result) };
         match nsrv {
             NS_OK => Ok(()),
             e => Err(e),
         }
     }
 }
 
 #[no_mangle]
@@ -704,16 +948,39 @@ macro_rules! try_ns {
             Err(err) => {
                 error!("{}", err);
                 continue;
             }
         }
     };
 }
 
+// This macro is a way to ensure the DB has been opened while minimizing lock acquisitions in the
+// common (read-only) case. First we acquire a read lock and see if we even need to open the DB. If
+// not, we can continue with the read lock we already have. Otherwise, we drop the read lock,
+// acquire the write lock, open the DB, drop the write lock, and re-acquire the read lock. While it
+// is possible for two or more threads to all come to the conclusion that they need to open the DB,
+// this isn't ultimately an issue - `open_db` will exit early if another thread has already done the
+// work.
+macro_rules! get_security_state {
+    ($self:expr) => {{
+        let ss_read_only = try_ns!($self.security_state.read());
+        if !ss_read_only.db_needs_opening() {
+            ss_read_only
+        } else {
+            drop(ss_read_only);
+            {
+                let mut ss_write = try_ns!($self.security_state.write());
+                try_ns!(ss_write.open_db());
+            }
+            try_ns!($self.security_state.read())
+        }
+    }};
+}
+
 #[derive(xpcom)]
 #[xpimplements(nsICertStorage, nsIObserver)]
 #[refcnt = "atomic"]
 struct InitCertStorage {
     security_state: Arc<RwLock<SecurityState>>,
     thread: Mutex<RefPtr<nsIThread>>,
 }
 
@@ -789,36 +1056,36 @@ impl CertStorage {
                 let mut issuer = nsCString::new();
                 try_ns!(revocation.GetIssuer(&mut *issuer).to_result(), or continue);
                 let issuer = try_ns!(base64::decode(&issuer), or continue);
 
                 let mut serial = nsCString::new();
                 try_ns!(revocation.GetSerial(&mut *serial).to_result(), or continue);
                 let serial = try_ns!(base64::decode(&serial), or continue);
 
-                entries.push((make_key(PREFIX_REV_IS, &issuer, &serial), state));
+                entries.push((make_key!(PREFIX_REV_IS, &issuer, &serial), state));
             } else if let Some(revocation) =
                 (*revocation).query_interface::<nsISubjectAndPubKeyRevocationState>()
             {
                 let mut subject = nsCString::new();
                 try_ns!(revocation.GetSubject(&mut *subject).to_result(), or continue);
                 let subject = try_ns!(base64::decode(&subject), or continue);
 
                 let mut pub_key_hash = nsCString::new();
                 try_ns!(revocation.GetPubKey(&mut *pub_key_hash).to_result(), or continue);
                 let pub_key_hash = try_ns!(base64::decode(&pub_key_hash), or continue);
 
-                entries.push((make_key(PREFIX_REV_SPK, &subject, &pub_key_hash), state));
+                entries.push((make_key!(PREFIX_REV_SPK, &subject, &pub_key_hash), state));
             }
         }
 
-        let task = Box::new(SetRevocationsTask::new(
+        let task = Box::new(SecurityStateTask::new(
             &*callback,
             &self.security_state,
-            entries,
+            move |ss| ss.set_revocations(&entries),
         ));
         let thread = try_ns!(self.thread.lock());
         let runnable = try_ns!(TaskRunnable::new("SetRevocations", task));
         try_ns!(runnable.dispatch(&*thread));
         NS_OK
     }
 
     unsafe fn SetEnrollment(
@@ -831,22 +1098,20 @@ impl CertStorage {
         if !is_main_thread() {
             return NS_ERROR_NOT_SAME_THREAD;
         }
         if issuer.is_null() || serial.is_null() || callback.is_null() {
             return NS_ERROR_FAILURE;
         }
         let issuer_decoded = try_ns!(base64::decode(&*issuer));
         let serial_decoded = try_ns!(base64::decode(&*serial));
-        let task = Box::new(SetEnrollmentTask::new(
+        let task = Box::new(SecurityStateTask::new(
             &*callback,
             &self.security_state,
-            issuer_decoded,
-            serial_decoded,
-            state,
+            move |ss| ss.set_enrollment(&issuer_decoded, &serial_decoded, state),
         ));
         let thread = try_ns!(self.thread.lock());
         let runnable = try_ns!(TaskRunnable::new("SetEnrollment", task));
         try_ns!(runnable.dispatch(&*thread));
         NS_OK
     }
 
     unsafe fn SetWhitelist(
@@ -859,22 +1124,20 @@ impl CertStorage {
         if !is_main_thread() {
             return NS_ERROR_NOT_SAME_THREAD;
         }
         if issuer.is_null() || serial.is_null() || callback.is_null() {
             return NS_ERROR_FAILURE;
         }
         let issuer_decoded = try_ns!(base64::decode(&*issuer));
         let serial_decoded = try_ns!(base64::decode(&*serial));
-        let task = Box::new(SetWhitelistTask::new(
+        let task = Box::new(SecurityStateTask::new(
             &*callback,
             &self.security_state,
-            issuer_decoded,
-            serial_decoded,
-            state,
+            move |ss| ss.set_whitelist(&issuer_decoded, &serial_decoded, state),
         ));
         let thread = try_ns!(self.thread.lock());
         let runnable = try_ns!(TaskRunnable::new("SetWhitelist", task));
         try_ns!(runnable.dispatch(&*thread));
         NS_OK
     }
 
     unsafe fn GetRevocationState(
@@ -886,36 +1149,17 @@ impl CertStorage {
         state: *mut i16,
     ) -> nserror::nsresult {
         // TODO (bug 1541212): We really want to restrict this to non-main-threads only, but we
         // can't do so until bug 1406854 and bug 1534600 are fixed.
         if issuer.is_null() || serial.is_null() || subject.is_null() || pub_key.is_null() {
             return NS_ERROR_FAILURE;
         }
         *state = nsICertStorage::STATE_UNSET as i16;
-        // The following is a way to ensure the DB has been opened while minimizing lock
-        // acquisitions in the common (read-only) case. First we acquire a read lock and see if we
-        // even need to open the DB. If not, we can continue with the read lock we already have.
-        // Otherwise, we drop the read lock, acquire the write lock, open the DB, drop the write
-        // lock, and re-acquire the read lock. While it is possible for two or more threads to all
-        // come to the conclusion that they need to open the DB, this isn't ultimately an issue -
-        // `open_db` will exit early if another thread has already done the work.
-        let ss = {
-            let ss_read_only = try_ns!(self.security_state.read());
-            if !ss_read_only.db_needs_opening() {
-                ss_read_only
-            } else {
-                drop(ss_read_only);
-                {
-                    let mut ss_write = try_ns!(self.security_state.write());
-                    try_ns!(ss_write.open_db());
-                }
-                try_ns!(self.security_state.read())
-            }
-        };
+        let ss = get_security_state!(self);
         match ss.get_revocation_state(&*issuer, &*serial, &*subject, &*pub_key) {
             Ok(st) => {
                 *state = st;
                 NS_OK
             }
             _ => NS_ERROR_FAILURE,
         }
     }
@@ -930,29 +1174,17 @@ impl CertStorage {
             return NS_ERROR_NOT_SAME_THREAD;
         }
         if issuer.is_null() || serial.is_null() {
             return NS_ERROR_FAILURE;
         }
         let issuer_decoded = try_ns!(base64::decode(&*issuer));
         let serial_decoded = try_ns!(base64::decode(&*serial));
         *state = nsICertStorage::STATE_UNSET as i16;
-        let ss = {
-            let ss_read_only = try_ns!(self.security_state.read());
-            if !ss_read_only.db_needs_opening() {
-                ss_read_only
-            } else {
-                drop(ss_read_only);
-                {
-                    let mut ss_write = try_ns!(self.security_state.write());
-                    try_ns!(ss_write.open_db());
-                }
-                try_ns!(self.security_state.read())
-            }
-        };
+        let ss = get_security_state!(self);
         match ss.get_enrollment_state(&issuer_decoded, &serial_decoded) {
             Ok(st) => {
                 *state = st;
                 NS_OK
             }
             _ => NS_ERROR_FAILURE,
         }
     }
@@ -967,29 +1199,17 @@ impl CertStorage {
             return NS_ERROR_NOT_SAME_THREAD;
         }
         if issuer.is_null() || serial.is_null() {
             return NS_ERROR_FAILURE;
         }
         let issuer_decoded = try_ns!(base64::decode(&*issuer));
         let serial_decoded = try_ns!(base64::decode(&*serial));
         *state = nsICertStorage::STATE_UNSET as i16;
-        let ss = {
-            let ss_read_only = try_ns!(self.security_state.read());
-            if !ss_read_only.db_needs_opening() {
-                ss_read_only
-            } else {
-                drop(ss_read_only);
-                {
-                    let mut ss_write = try_ns!(self.security_state.write());
-                    try_ns!(ss_write.open_db());
-                }
-                try_ns!(self.security_state.read())
-            }
-        };
+        let ss = get_security_state!(self);
         match ss.get_whitelist_state(&issuer_decoded, &serial_decoded) {
             Ok(st) => {
                 *state = st;
                 NS_OK
             }
             _ => NS_ERROR_FAILURE,
         }
     }
@@ -1025,16 +1245,82 @@ impl CertStorage {
         *fresh = match ss.is_enrollment_fresh() {
             Ok(is_fresh) => is_fresh,
             Err(_) => false,
         };
 
         NS_OK
     }
 
+    unsafe fn AddCertBySubject(
+        &self,
+        cert: *const nsACString,
+        subject: *const nsACString,
+        trust: i16,
+        callback: *const nsICertStorageCallback,
+    ) -> nserror::nsresult {
+        if !is_main_thread() {
+            return NS_ERROR_NOT_SAME_THREAD;
+        }
+        if cert.is_null() || subject.is_null() || callback.is_null() {
+            return NS_ERROR_FAILURE;
+        }
+        let cert_decoded = try_ns!(base64::decode(&*cert));
+        let subject_decoded = try_ns!(base64::decode(&*subject));
+        let task = Box::new(SecurityStateTask::new(
+            &*callback,
+            &self.security_state,
+            move |ss| ss.add_cert_by_subject(&cert_decoded, &subject_decoded, trust),
+        ));
+        let thread = try_ns!(self.thread.lock());
+        let runnable = try_ns!(TaskRunnable::new("AddCertBySubject", task));
+        try_ns!(runnable.dispatch(&*thread));
+        NS_OK
+    }
+
+    unsafe fn RemoveCertByHash(
+        &self,
+        hash: *const nsACString,
+        callback: *const nsICertStorageCallback,
+    ) -> nserror::nsresult {
+        if !is_main_thread() {
+            return NS_ERROR_NOT_SAME_THREAD;
+        }
+        if hash.is_null() || callback.is_null() {
+            return NS_ERROR_FAILURE;
+        }
+        let hash_decoded = try_ns!(base64::decode(&*hash));
+        let task = Box::new(SecurityStateTask::new(
+            &*callback,
+            &self.security_state,
+            move |ss| ss.remove_cert_by_hash(&hash_decoded),
+        ));
+        let thread = try_ns!(self.thread.lock());
+        let runnable = try_ns!(TaskRunnable::new("RemoveCertByHash", task));
+        try_ns!(runnable.dispatch(&*thread));
+        NS_OK
+    }
+
+    unsafe fn FindCertsBySubject(
+        &self,
+        subject: *const ThinVec<u8>,
+        certs: *mut ThinVec<ThinVec<u8>>,
+    ) -> nserror::nsresult {
+        // TODO (bug 1541212): We really want to restrict this to non-main-threads only, but we
+        // can't do so until bug 1406854 and bug 1534600 are fixed.
+        if subject.is_null() || certs.is_null() {
+            return NS_ERROR_FAILURE;
+        }
+        let ss = get_security_state!(self);
+        match ss.find_certs_by_subject(&*subject, &mut *certs) {
+            Ok(()) => NS_OK,
+            Err(_) => NS_ERROR_FAILURE,
+        }
+    }
+
     unsafe fn Observe(
         &self,
         subject: *const nsISupports,
         topic: *const c_char,
         pref_name: *const i16,
     ) -> nserror::nsresult {
         match CStr::from_ptr(topic).to_str() {
             Ok("nsPref:changed") => {
--- a/security/manager/ssl/moz.build
+++ b/security/manager/ssl/moz.build
@@ -50,27 +50,22 @@ if CONFIG['MOZ_XUL']:
     ]
 
 XPIDL_MODULE = 'pipnss'
 
 XPCOM_MANIFESTS += [
     'components.conf',
 ]
 
-# These aren't actually used in production code yet, so we don't want to
-# ship them with the browser.
-TESTING_JS_MODULES.psm += [
+EXTRA_JS_MODULES.psm += [
     'DER.jsm',
+    'RemoteSecuritySettings.jsm',
     'X509.jsm',
 ]
 
-EXTRA_JS_MODULES.psm += [
-    'RemoteSecuritySettings.jsm',
-]
-
 EXPORTS += [
     'CryptoTask.h',
     'EnterpriseRoots.h',
     'nsClientAuthRemember.h',
     'nsNSSCallbacks.h',
     'nsNSSCertificate.h',
     'nsNSSComponent.h',
     'nsNSSHelper.h',
--- a/security/manager/ssl/nsICertStorage.idl
+++ b/security/manager/ssl/nsICertStorage.idl
@@ -100,17 +100,17 @@ interface nsICertStorage : nsISupports {
   /**
    * Get the revocation state of a certificate. STATE_UNSET indicates the
    * certificate is not revoked. STATE_ENFORCE indicates the certificate is
    * revoked.
    * issuer - issuer name, DER encoded
    * serial - serial number, DER encoded
    * subject - subject name, DER encoded
    * pubkey - public key, DER encoded
-   * Must not be called from the main thread.
+   * Must not be called from the main thread. See bug 1541212.
    */
   [must_use]
   short getRevocationState(in Array<octet> issuer,
                            in Array<octet> serial,
                            in Array<octet> subject,
                            in Array<octet> pubkey);
 
   /**
@@ -154,9 +154,61 @@ interface nsICertStorage : nsISupports {
    /**
     * Check that the CRLite enrollment data is current. Specifically, that the current
     * time is no more than security.onecrl.maximum_staleness_in_seconds seconds
     * after the last crlite enrollment update (as stored in the
     * services.blocklist.crlite.checked pref)
     */
   [must_use]
   boolean isEnrollmentFresh();
+
+  /**
+   * Trust flags to use when adding a adding a certificate.
+   * TRUST_INHERIT indicates a certificate inherits trust from another
+   * certificate.
+   * TRUST_ANCHOR indicates the certificate is a root of trust.
+   */
+  const short TRUST_INHERIT = 0;
+  const short TRUST_ANCHOR = 1;
+
+  /**
+   * Asynchronously add a certificate to the backing storage.
+   * cert is the bytes of the certificate as base64-encoded DER.
+   * subject is the subject distinguished name of the certificate as
+   * base64-encoded DER (although we don't actually validate that the given
+   * certificate has the indicated subject).
+   * trust is one of the TRUST_* constants in this interface.
+   * The given callback is called with the result of the operation when it
+   * completes.
+   * Must only be called from the main thread.
+   */
+  [must_use]
+  void addCertBySubject(in ACString cert,
+                        in ACString subject,
+                        in short trust,
+                        in nsICertStorageCallback callback);
+
+  /**
+   * Asynchronously remove the certificate with the given sha-256 hash from the
+   * backing storage.
+   * hash is the base64-encoded bytes of the sha-256 hash of the certificate's
+   * bytes (DER-encoded).
+   * The given callback is called with the result of the operation when it
+   * completes.
+   * Must only be called from the main thread.
+   */
+  [must_use]
+  void removeCertByHash(in ACString hash,
+                        in nsICertStorageCallback callback);
+
+  /**
+   * Find all certificates in the backing storage with the given subject
+   * distinguished name.
+   * subject is the DER-encoded bytes of the subject distinguished name.
+   * Returns an array of arrays of bytes, where each inner array corresponds to
+   * the DER-encoded bytes of a certificate that has the given subject (although
+   * as these certificates were presumably added via addCertBySubject, this
+   * aspect is never actually valided by nsICertStorage).
+   * Must not be called from the main thread. See bug 1541212.
+   */
+  [must_use]
+  Array<Array<octet> > findCertsBySubject(in Array<octet> subject);
 };
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_cert_storage_direct.js
@@ -0,0 +1,128 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+// This file consists of unit tests for cert_storage (whereas test_cert_storage.js is more of an
+// integration test).
+
+do_get_profile();
+
+var certStorage = Cc["@mozilla.org/security/certstorage;1"].getService(Ci.nsICertStorage);
+
+async function addCertBySubject(cert, subject) {
+  let result = await new Promise((resolve) => {
+    certStorage.addCertBySubject(btoa(cert), btoa(subject), Ci.nsICertStorage.TRUST_INHERIT,
+                                 resolve);
+  });
+  Assert.equal(result, Cr.NS_OK, "addCertBySubject should succeed");
+}
+
+async function removeCertByHash(hashBase64) {
+  let result = await new Promise((resolve) => {
+    certStorage.removeCertByHash(hashBase64, resolve);
+  });
+  Assert.equal(result, Cr.NS_OK, "removeCertByHash should succeed");
+}
+
+function stringToArray(s) {
+  let a = [];
+  for (let i = 0; i < s.length; i++) {
+    a.push(s.charCodeAt(i));
+  }
+  return a;
+}
+
+function arrayToString(a) {
+  let s = "";
+  for (let b of a) {
+    s += String.fromCharCode(b);
+  }
+  return s;
+}
+
+function getLongString(uniquePart, length) {
+  return String(uniquePart).padStart(length, "0");
+}
+
+add_task(async function test_common_subject() {
+  await addCertBySubject("some certificate bytes 1", "some common subject");
+  await addCertBySubject("some certificate bytes 2", "some common subject");
+  await addCertBySubject("some certificate bytes 3", "some common subject");
+  let storedCerts = certStorage.findCertsBySubject(stringToArray("some common subject"));
+  let storedCertsAsStrings = storedCerts.map(arrayToString);
+  let expectedCerts = ["some certificate bytes 1", "some certificate bytes 2",
+                       "some certificate bytes 3"];
+  Assert.deepEqual(storedCertsAsStrings.sort(), expectedCerts.sort(), "should find expected certs");
+
+  await addCertBySubject("some other certificate bytes", "some other subject");
+  storedCerts = certStorage.findCertsBySubject(stringToArray("some common subject"));
+  storedCertsAsStrings = storedCerts.map(arrayToString);
+  Assert.deepEqual(storedCertsAsStrings.sort(), expectedCerts.sort(),
+                   "should still find expected certs");
+
+  let storedOtherCerts = certStorage.findCertsBySubject(stringToArray("some other subject"));
+  let storedOtherCertsAsStrings = storedOtherCerts.map(arrayToString);
+  let expectedOtherCerts = ["some other certificate bytes"];
+  Assert.deepEqual(storedOtherCertsAsStrings, expectedOtherCerts, "should have other certificate");
+});
+
+add_task(async function test_many_entries() {
+  const NUM_CERTS = 500;
+  const CERT_LENGTH = 3000;
+  const SUBJECT_LENGTH = 40;
+  for (let i = 0; i < NUM_CERTS; i++) {
+    await addCertBySubject(getLongString(i, CERT_LENGTH), getLongString(i, SUBJECT_LENGTH));
+  }
+  for (let i = 0; i < NUM_CERTS; i++) {
+    let subject = stringToArray(getLongString(i, SUBJECT_LENGTH));
+    let storedCerts = certStorage.findCertsBySubject(subject);
+    Assert.equal(storedCerts.length, 1, "should have 1 certificate (lots of data test)");
+    let storedCertAsString = arrayToString(storedCerts[0]);
+    Assert.equal(storedCertAsString, getLongString(i, CERT_LENGTH),
+                 "certificate should be as expected (lots of data test)");
+  }
+});
+
+add_task(async function test_removal() {
+  // As long as cert_storage is given valid base64, attempting to delete some nonexistent
+  // certificate will "succeed" (it'll do nothing).
+  await removeCertByHash(btoa("thishashisthewrongsize"));
+
+  await addCertBySubject("removal certificate bytes 1", "common subject to remove");
+  await addCertBySubject("removal certificate bytes 2", "common subject to remove");
+  await addCertBySubject("removal certificate bytes 3", "common subject to remove");
+
+  let storedCerts = certStorage.findCertsBySubject(stringToArray("common subject to remove"));
+  let storedCertsAsStrings = storedCerts.map(arrayToString);
+  let expectedCerts = ["removal certificate bytes 1", "removal certificate bytes 2",
+                       "removal certificate bytes 3"];
+  Assert.deepEqual(storedCertsAsStrings.sort(), expectedCerts.sort(),
+                   "should find expected certs before removing them");
+
+  // echo -n "removal certificate bytes 2" | sha256sum | xxd -r -p | base64
+  await removeCertByHash("2nUPHwl5TVr1mAD1FU9FivLTlTb0BAdnVUhsYgBccN4=");
+  storedCerts = certStorage.findCertsBySubject(stringToArray("common subject to remove"));
+  storedCertsAsStrings = storedCerts.map(arrayToString);
+  expectedCerts = ["removal certificate bytes 1", "removal certificate bytes 3"];
+  Assert.deepEqual(storedCertsAsStrings.sort(), expectedCerts.sort(),
+                   "should only have first and third certificates now");
+
+  // echo -n "removal certificate bytes 1" | sha256sum | xxd -r -p | base64
+  await removeCertByHash("8zoRqHYrklr7Zx6UWpzrPuL+ol8KL1Ml6XHBQmXiaTY=");
+  storedCerts = certStorage.findCertsBySubject(stringToArray("common subject to remove"));
+  storedCertsAsStrings = storedCerts.map(arrayToString);
+  expectedCerts = ["removal certificate bytes 3"];
+  Assert.deepEqual(storedCertsAsStrings.sort(), expectedCerts.sort(),
+                   "should only have third certificate now");
+
+  // echo -n "removal certificate bytes 3" | sha256sum | xxd -r -p | base64
+  await removeCertByHash("vZn7GwDSabB/AVo0T+N26nUsfSXIIx4NgQtSi7/0p/w=");
+  storedCerts = certStorage.findCertsBySubject(stringToArray("common subject to remove"));
+  Assert.equal(storedCerts.length, 0, "shouldn't have any certificates now");
+
+  // echo -n "removal certificate bytes 3" | sha256sum | xxd -r -p | base64
+  // Again, removing a nonexistent certificate should "succeed".
+  await removeCertByHash("vZn7GwDSabB/AVo0T+N26nUsfSXIIx4NgQtSi7/0p/w=");
+});
--- a/security/manager/ssl/tests/unit/test_der.js
+++ b/security/manager/ssl/tests/unit/test_der.js
@@ -2,17 +2,17 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Tests DER.jsm functionality.
 
 // Until DER.jsm is actually used in production code, this is where we have to
 // import it from.
-var { DER } = ChromeUtils.import("resource://testing-common/psm/DER.jsm", null);
+var { DER } = ChromeUtils.import("resource://gre/modules/psm/DER.jsm", null);
 
 function run_simple_tests() {
   throws(() => new DER.DER("this is not an array"), /invalid input/,
          "should throw given non-array input");
   throws(() => new DER.DER([0, "invalid input", 1]), /invalid input/,
          "should throw given non-byte data (string case)");
   throws(() => new DER.DER([31, 1, {}]), /invalid input/,
          "should throw given non-byte data (object case)");
--- a/security/manager/ssl/tests/unit/test_intermediate_preloads.js
+++ b/security/manager/ssl/tests/unit/test_intermediate_preloads.js
@@ -29,25 +29,29 @@ function* cyclingIterator(items, count =
   if (count == null) {
     count = items.length;
   }
   for (let i = 0; i < count; i++) {
     yield items[i % items.length];
   }
 }
 
-function getHash(aStr) {
+function getHashCommon(aStr, useBase64) {
   let hasher = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
   hasher.init(Ci.nsICryptoHash.SHA256);
   let stringStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
   stringStream.data = aStr;
   hasher.updateFromStream(stringStream, -1);
 
-  // convert the binary hash data to a hex string.
-  return hexify(hasher.finish(false));
+  return hasher.finish(useBase64);
+}
+
+// Get a hexified SHA-256 hash of the given string.
+function getHash(aStr) {
+  return hexify(getHashCommon(aStr, false));
 }
 
 function countTelemetryReports(histogram) {
   let count = 0;
   for (let x in histogram.values) {
     count += histogram.values[x];
   }
   return count;
@@ -128,24 +132,21 @@ function setupKintoPreloadServer(certGen
         "Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
         "Content-Type: application/json; charset=UTF-8",
         "Server: waitress",
         "Etag: \"1000\"",
     ]);
 
     let output = [];
     let count = 1;
-    let certDB = Cc["@mozilla.org/security/x509certdb;1"]
-                 .getService(Ci.nsIX509CertDB);
 
     let certIterator = certGenerator();
     let result = certIterator.next();
     while (!result.done) {
       let certBytes = result.value;
-      let cert = certDB.constructX509FromBase64(pemToBase64(certBytes));
 
       output.push({
         "details": {
           "who": "",
           "why": "",
           "name": "",
           "created": "",
         },
@@ -153,17 +154,18 @@ function setupKintoPreloadServer(certGen
         "attachment": {
           "hash": options.hashFunc(certBytes),
           "size": options.lengthFunc(certBytes),
           "filename": `intermediate certificate #${count}.pem`,
           "location": `int${count}`,
           "mimetype": "application/x-pem-file",
         },
         "whitelist": false,
-        "pubKeyHash": cert.sha256Fingerprint,
+        // "pubKeyHash" is actually just the hash of the DER bytes of the certificate
+        "pubKeyHash": getHashCommon(atob(pemToBase64(certBytes)), true),
         "crlite_enrolled": true,
         "id": `78cf8900-fdea-4ce5-f8fb-${count}`,
         "last_modified": Date.now(),
       });
 
       count++;
       result = certIterator.next();
     }
@@ -268,19 +270,18 @@ add_task(async function test_preload_inv
   clearTelemetry();
 
   equal(await syncAndPromiseUpdate(), "success", "Preloading update should have run");
 
   let errors_histogram = Services.telemetry
                           .getHistogramById("INTERMEDIATE_PRELOADING_ERRORS")
                           .snapshot();
 
-  equal(countTelemetryReports(errors_histogram), 2, "There should be two error reports");
+  equal(countTelemetryReports(errors_histogram), 1, "There should be one error report");
   equal(errors_histogram.values[7], 1, "There should be one invalid hash error");
-  equal(errors_histogram.values[1], 1, "There should be one generic download error");
 
   equal(countDownloadAttempts, 1, "There should have been one download attempt");
 
   let certDB = Cc["@mozilla.org/security/x509certdb;1"]
                .getService(Ci.nsIX509CertDB);
 
   // load the first root and end entity, ignore the initial intermediate
   addCertFromFile(certDB, "test_intermediate_preloads/ca.pem", "CTu,,");
@@ -308,19 +309,18 @@ add_task(async function test_preload_inv
   clearTelemetry();
 
   equal(await syncAndPromiseUpdate(), "success", "Preloading update should have run");
 
   let errors_histogram = Services.telemetry
                           .getHistogramById("INTERMEDIATE_PRELOADING_ERRORS")
                           .snapshot();
 
-  equal(countTelemetryReports(errors_histogram), 2, "There should be only two error reports");
+  equal(countTelemetryReports(errors_histogram), 1, "There should be only one error report");
   equal(errors_histogram.values[8], 1, "There should be one invalid length error");
-  equal(errors_histogram.values[1], 1, "There should be one generic download error");
 
   equal(countDownloadAttempts, 1, "There should have been one download attempt");
 
   let certDB = Cc["@mozilla.org/security/x509certdb;1"]
                .getService(Ci.nsIX509CertDB);
 
   // load the first root and end entity, ignore the initial intermediate
   addCertFromFile(certDB, "test_intermediate_preloads/ca.pem", "CTu,,");
--- a/security/manager/ssl/tests/unit/test_x509.js
+++ b/security/manager/ssl/tests/unit/test_x509.js
@@ -1,18 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Tests X509.jsm functionality.
 
-// Until X509.jsm is actually used in production code, this is where we have to
-// import it from.
-var { X509 } = ChromeUtils.import("resource://testing-common/psm/X509.jsm");
+var { X509 } = ChromeUtils.import("resource://gre/modules/psm/X509.jsm");
 
 function stringToBytes(s) {
   let b = [];
   for (let i = 0; i < s.length; i++) {
     b.push(s.charCodeAt(i));
   }
   return b;
 }
--- a/security/manager/ssl/tests/unit/xpcshell.ini
+++ b/security/manager/ssl/tests/unit/xpcshell.ini
@@ -43,16 +43,17 @@ support-files =
 [test_baseline_requirements_subject_common_name.js]
 [test_broken_fips.js]
 # FIPS has never been a thing on Android, so the workaround doesn't
 # exist on that platform.
 # FIPS still works on Linux/Windows, so this test doesn't make any sense there.
 skip-if = os != 'mac'
 [test_cert_storage.js]
 tags = addons psm blocklist
+[test_cert_storage_direct.js]
 [test_cert_storage_prefs.js]
 [test_cert_chains.js]
 run-sequentially = hardcoded ports
 [test_cert_dbKey.js]
 [test_cert_eku.js]
 [test_cert_embedded_null.js]
 [test_cert_expiration_canary.js]
 run-if = nightly_build
--- a/testing/raptor/raptor/cmdline.py
+++ b/testing/raptor/raptor/cmdline.py
@@ -97,20 +97,22 @@ def create_parser(mach_interface=False):
             help=argparse.SUPPRESS)
     add_arg('--geckoProfileEntries', dest="gecko_profile_entries", type=int,
             help=argparse.SUPPRESS)
     add_arg('--gecko-profile', action="store_true", dest="gecko_profile",
             help="Profile the run and output the results in $MOZ_UPLOAD_DIR. "
             "After talos is finished, profiler.firefox.com will be launched in Firefox "
             "so you can analyze the local profiles. To disable auto-launching of "
             "profiler.firefox.com set the DISABLE_PROFILE_LAUNCH=1 env var.")
-    add_arg('--gecko-profile-interval', dest='gecko_profile_interval', type=float,
-            help="How frequently to take samples (milliseconds)")
     add_arg('--gecko-profile-entries', dest="gecko_profile_entries", type=int,
-            help="How many samples to take with the profiler")
+            help='How many samples to take with the profiler')
+    add_arg('--gecko-profile-interval', dest='gecko_profile_interval', type=int,
+            help='How frequently to take samples (milliseconds)')
+    add_arg('--gecko-profile-thread', dest='gecko_profile_threads', action='append',
+            help='Name of the extra thread to be profiled')
     add_arg('--symbolsPath', dest='symbols_path',
             help="Path to the symbols for the build we are testing")
     add_arg('--page-cycles', dest="page_cycles", type=int,
             help="How many times to repeat loading the test page (for page load tests); "
                  "for benchmark tests this is how many times the benchmark test will be run")
     add_arg('--page-timeout', dest="page_timeout", type=int,
             help="How long to wait (ms) for one page_cycle to complete, before timing out")
     add_arg('--post-startup-delay',
@@ -147,17 +149,17 @@ def verify_options(parser, args):
 
     # if geckoProfile specified but not running on Firefox, not supported
     if args.gecko_profile is True and args.app != "firefox":
         parser.error("Gecko profiling is only supported when running raptor on Firefox!")
 
     # if --power-test specified, must be on geckoview/android with --host specified.
     if args.power_test:
         if args.app not in ["fennec", "geckoview", "refbrow", "fenix"] \
-          or args.host in ('localhost', '127.0.0.1'):
+                or args.host in ('localhost', '127.0.0.1'):
             parser.error("Power test is only supported when running raptor on Firefox Android "
                          "browsers when host is specified!")
 
     # if running on geckoview/refbrow/fenix, we need an activity and intent
     if args.app in ["geckoview", "refbrow", "fenix"]:
         if not args.activity:
             # if we have a default activity specified in APPS above, use that
             if APPS[args.app].get("default_activity", None) is not None:
--- a/testing/raptor/raptor/manifest.py
+++ b/testing/raptor/raptor/manifest.py
@@ -162,27 +162,34 @@ def write_test_settings_json(args, test_
             subtest_lower_is_better)
 
     if test_details.get("alert_threshold", None) is not None:
         test_settings['raptor-options']['alert_threshold'] = float(test_details['alert_threshold'])
 
     if test_details.get("screen_capture", None) is not None:
         test_settings['raptor-options']['screen_capture'] = test_details.get("screen_capture")
 
-    # if gecko profiling is enabled, write profiling settings for webext
+    # if Gecko profiling is enabled, write profiling settings for webext
     if test_details.get("gecko_profile", False):
-        test_settings['raptor-options']['gecko_profile'] = True
-        # when profiling, if webRender is enabled we need to set that, so
-        # the runner can add the web render threads to gecko profiling
-        test_settings['raptor-options']['gecko_profile_interval'] = \
-            float(test_details.get("gecko_profile_interval", 0))
-        test_settings['raptor-options']['gecko_profile_entries'] = \
-            float(test_details.get("gecko_profile_entries", 0))
-        if str(os.getenv('MOZ_WEBRENDER')) == '1':
-            test_settings['raptor-options']['webrender_enabled'] = True
+        threads = ['GeckoMain', 'Compositor']
+
+        # With WebRender enabled profile some extra threads
+        if os.getenv('MOZ_WEBRENDER') == '1':
+            threads.extend(['Renderer', 'WR'])
+
+        if test_details.get('gecko_profile_threads'):
+            test_threads = filter(None, test_details['gecko_profile_threads'].split(','))
+            threads.extend(test_threads)
+
+        test_settings['raptor-options'].update({
+            'gecko_profile': True,
+            'gecko_profile_entries': int(test_details.get('gecko_profile_entries')),
+            'gecko_profile_interval': int(test_details.get('gecko_profile_interval')),
+            'gecko_profile_threads': ','.join(set(threads)),
+        })
 
     if test_details.get("newtab_per_cycle", None) is not None:
         test_settings['raptor-options']['newtab_per_cycle'] = \
             bool(test_details['newtab_per_cycle'])
 
     settings_file = os.path.join(tests_dir, test_details['name'] + '.json')
     try:
         with open(settings_file, 'w') as out_file:
@@ -236,35 +243,60 @@ def get_raptor_test_list(args, oskey):
                 tests_to_run.append(next_test)
 
     # go through each test and set the page-cycles and page-timeout, and some config flags
     # the page-cycles value in the INI can be overriden when debug-mode enabled, when
     # gecko-profiling enabled, or when --page-cycles cmd line arg was used (that overrides all)
     for next_test in tests_to_run:
         LOG.info("configuring settings for test %s" % next_test['name'])
         max_page_cycles = next_test.get('page_cycles', 1)
+
         if args.gecko_profile is True:
             next_test['gecko_profile'] = True
-            LOG.info("gecko-profiling enabled")
+            LOG.info('gecko-profiling enabled')
             max_page_cycles = 3
+
+            if 'gecko_profile_entries' in args and args.gecko_profile_entries is not None:
+                next_test['gecko_profile_entries'] = str(args.gecko_profile_entries)
+                LOG.info('gecko-profiling entries set to %s' % args.gecko_profile_entries)
+
+            if 'gecko_profile_interval' in args and args.gecko_profile_interval is not None:
+                next_test['gecko_profile_interval'] = str(args.gecko_profile_interval)
+                LOG.info('gecko-profiling interval set to %s' % args.gecko_profile_interval)
+
+            if 'gecko_profile_threads' in args and args.gecko_profile_threads is not None:
+                threads = filter(None, next_test.get('gecko_profile_threads', '').split(','))
+                threads.extend(args.gecko_profile_threads)
+                next_test['gecko_profile_threads'] = ','.join(threads)
+                LOG.info('gecko-profiling extra threads %s' % args.gecko_profile_threads)
+
+        else:
+            # if the gecko profiler is not enabled, ignore all of it's settings
+            next_test.pop('gecko_profile_entries', None)
+            next_test.pop('gecko_profile_interval', None)
+            next_test.pop('gecko_profile_threads', None)
+
         if args.debug_mode is True:
             next_test['debug_mode'] = True
             LOG.info("debug-mode enabled")
             max_page_cycles = 2
+
         if args.page_cycles is not None:
             next_test['page_cycles'] = args.page_cycles
             LOG.info("set page-cycles to %d as specified on cmd line" % args.page_cycles)
         else:
             if int(next_test.get('page_cycles', 1)) > max_page_cycles:
                 next_test['page_cycles'] = max_page_cycles
                 LOG.info("page-cycles set to %d" % next_test['page_cycles'])
+
         # if --page-timeout was provided on the command line, use that instead of INI
         if args.page_timeout is not None:
             LOG.info("setting page-timeout to %d as specified on cmd line" % args.page_timeout)
             next_test['page_timeout'] = args.page_timeout
+
         # if --browser-cycles was provided on the command line, use that instead of INI
         if args.browser_cycles is not None:
             LOG.info("setting browser-cycles to %d as specified on cmd line" % args.browser_cycles)
             next_test['browser_cycles'] = args.browser_cycles
 
         if next_test.get("cold", "false") == "true":
             # when running in cold mode, set browser-cycles to the page-cycles value; as we want
             # the browser to restart between page-cycles; and set page-cycles to 1 as we only
--- a/testing/raptor/test/test_manifest.py
+++ b/testing/raptor/test/test_manifest.py
@@ -187,26 +187,79 @@ def test_get_raptor_test_list_geckoview(
                        test="raptor-unity-webgl",
                        browser_cycles=1)
 
     test_list = get_raptor_test_list(args, mozinfo.os)
     assert len(test_list) == 1
     assert test_list[0]['name'] == 'raptor-unity-webgl-geckoview'
 
 
-def test_get_raptor_test_list_gecko_profiling(create_args):
+def test_get_raptor_test_list_gecko_profiling_enabled(create_args):
     args = create_args(test="raptor-tp6-google-firefox",
                        gecko_profile=True,
                        browser_cycles=1)
 
     test_list = get_raptor_test_list(args, mozinfo.os)
     assert len(test_list) == 1
     assert test_list[0]['name'] == 'raptor-tp6-google-firefox'
     assert test_list[0]['gecko_profile'] is True
-    assert test_list[0]['page_cycles'] == 3
+    assert test_list[0].get('gecko_profile_entries') == '14000000'
+    assert test_list[0].get('gecko_profile_interval') == '1'
+    assert test_list[0].get('gecko_profile_threads') is None
+
+
+def test_get_raptor_test_list_gecko_profiling_enabled_args_override(create_args):
+    args = create_args(test="raptor-tp6-google-firefox",
+                       gecko_profile=True,
+                       gecko_profile_entries=42,
+                       gecko_profile_interval=100,
+                       gecko_profile_threads=['Foo'],
+                       browser_cycles=1)
+
+    test_list = get_raptor_test_list(args, mozinfo.os)
+    assert len(test_list) == 1
+    assert test_list[0]['name'] == 'raptor-tp6-google-firefox'
+    assert test_list[0]['gecko_profile'] is True
+    assert test_list[0]['gecko_profile_entries'] == '42'
+    assert test_list[0]['gecko_profile_interval'] == '100'
+    assert test_list[0]['gecko_profile_threads'] == 'Foo'
+
+
+def test_get_raptor_test_list_gecko_profiling_disabled(create_args):
+    args = create_args(test="raptor-tp6-google-firefox",
+                       gecko_profile=False,
+                       gecko_profile_entries=42,
+                       gecko_profile_interval=100,
+                       gecko_profile_threads=['Foo'],
+                       browser_cycles=1)
+
+    test_list = get_raptor_test_list(args, mozinfo.os)
+    assert len(test_list) == 1
+    assert test_list[0]['name'] == 'raptor-tp6-google-firefox'
+    assert test_list[0].get('gecko_profile') is None
+    assert test_list[0].get('gecko_profile_entries') is None
+    assert test_list[0].get('gecko_profile_interval') is None
+    assert test_list[0].get('gecko_profile_threads') is None
+
+
+def test_get_raptor_test_list_gecko_profiling_disabled_args_override(create_args):
+    args = create_args(test="raptor-tp6-google-firefox",
+                       gecko_profile=False,
+                       gecko_profile_entries=42,
+                       gecko_profile_interval=100,
+                       gecko_profile_threads=['Foo'],
+                       browser_cycles=1)
+
+    test_list = get_raptor_test_list(args, mozinfo.os)
+    assert len(test_list) == 1
+    assert test_list[0]['name'] == 'raptor-tp6-google-firefox'
+    assert test_list[0].get('gecko_profile') is None
+    assert test_list[0].get('gecko_profile_entries') is None
+    assert test_list[0].get('gecko_profile_interval') is None
+    assert test_list[0].get('gecko_profile_threads') is None
 
 
 def test_get_raptor_test_list_debug_mode(create_args):
     args = create_args(test="raptor-tp6-google-firefox",
                        debug_mode=True,
                        browser_cycles=1)
 
     test_list = get_raptor_test_list(args, mozinfo.os)
--- a/testing/raptor/webext/raptor/runner.js
+++ b/testing/raptor/webext/raptor/runner.js
@@ -56,17 +56,17 @@ var isFCPPending = false;
 var isDCFPending = false;
 var isTTFIPending = false;
 var isLoadTimePending = false;
 var isBenchmarkPending = false;
 var pageTimeout = 10000; // default pageload timeout
 var geckoProfiling = false;
 var geckoInterval = 1;
 var geckoEntries = 1000000;
-var webRenderEnabled = false;
+var geckoThreads = [];
 var debugMode = 0;
 var screenCapture = false;
 
 var results = {"name": "",
                "page": "",
                "type": "",
                "browser_cycle": 0,
                "expected_browser_cycles": 0,
@@ -111,30 +111,23 @@ function getTestSettings() {
         results.expected_browser_cycles = settings.expected_browser_cycles;
         results.cold = settings.cold;
         results.unit = settings.unit;
         results.subtest_unit = settings.subtest_unit;
         results.lower_is_better = settings.lower_is_better === true;
         results.subtest_lower_is_better = settings.subtest_lower_is_better === true;
         results.alert_threshold = settings.alert_threshold;
 
-        if (settings.gecko_profile !== undefined) {
-          if (settings.gecko_profile === true) {
-            geckoProfiling = true;
-            results.extra_options = ["gecko_profile"];
-            if (settings.gecko_interval !== undefined) {
-              geckoInterval = settings.gecko_interval;
-            }
-            if (settings.gecko_entries !== undefined) {
-              geckoEntries = settings.gecko_entries;
-            }
-            if (settings.webrender_enabled !== undefined) {
-              webRenderEnabled = settings.webrender_enabled;
-            }
-          }
+        if (settings.gecko_profile === true) {
+          results.extra_options = ["gecko_profile"];
+
+          geckoProfiling = true;
+          geckoEntries = settings.gecko_profile_entries;
+          geckoInterval = settings.gecko_profile_interval;
+          geckoThreads = settings.gecko_profile_threads;
         }
 
         if (settings.screen_capture !== undefined) {
           screenCapture = settings.screen_capture;
         }
 
         if (settings.newtab_per_cycle !== undefined) {
           reuseTab = settings.newtab_per_cycle;
@@ -313,30 +306,23 @@ function onCaptured(screenshotUri) {
   postToControlServer("screenshot", [screenshotUri, testName, pageCycle]);
 }
 
 function onError(error) {
   console.log("Screenshot captured failed!");
   console.log(`Error: ${error}`);
 }
 
-
 async function startGeckoProfiling() {
-  var _threads;
-  if (webRenderEnabled) {
-    _threads = ["GeckoMain", "Compositor", "WR,Renderer"];
-  } else {
-    _threads = ["GeckoMain", "Compositor"];
-  }
-  postToControlServer("status", "starting gecko profiling");
+  postToControlServer("status", `starting Gecko profiling for threads: ${geckoThreads}`);
   await browser.geckoProfiler.start({
     bufferSize: geckoEntries,
     interval: geckoInterval,
     features: ["js", "leaf", "stackwalk", "threads", "responsiveness"],
-    threads: _threads,
+    threads: geckoThreads.split(","),
   });
 }
 
 async function stopGeckoProfiling() {
   postToControlServer("status", "stopping gecko profiling");
   await browser.geckoProfiler.stop();
 }
 
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-contain/contain-layout-button-001.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[contain-layout-button-001.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-contain/contain-size-button-001.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[contain-size-button-001.html]
-  expected: FAIL
--- a/testing/web-platform/meta/css/css-multicol/multicol-span-all-margin-bottom-001.xht.ini
+++ b/testing/web-platform/meta/css/css-multicol/multicol-span-all-margin-bottom-001.xht.ini
@@ -1,2 +1,4 @@
 [multicol-span-all-margin-bottom-001.xht]
   prefs: [layout.css.column-span.enabled:true]
+  expected:
+    if os == "android" and not e10s: FAIL # Bug 1547160
deleted file mode 100644
--- a/testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-button-001.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[contain-size-button-001.html]
-  expected: FAIL
--- a/testing/web-platform/meta/html/browsers/offline/introduction-4/__dir__.ini
+++ b/testing/web-platform/meta/html/browsers/offline/introduction-4/__dir__.ini
@@ -1,1 +1,1 @@
-lsan-allowed: [Alloc]
+lsan-allowed: [Alloc, mozilla::dom::ChromeUtils::GenerateQI]
--- a/toolkit/components/extensions/ExtensionXPCShellUtils.jsm
+++ b/toolkit/components/extensions/ExtensionXPCShellUtils.jsm
@@ -111,20 +111,21 @@ function promiseBrowserLoaded(browser, u
     // use one. But we also need to make sure it stays alive until we're
     // done with it, so thunk away a strong reference to keep it alive.
     kungFuDeathGrip.add(listener);
     browser.addProgressListener(listener, Ci.nsIWebProgress.NOTIFY_STATE_WINDOW);
   });
 }
 
 class ContentPage {
-  constructor(remote = REMOTE_CONTENT_SCRIPTS, extension = null, privateBrowsing = false) {
+  constructor(remote = REMOTE_CONTENT_SCRIPTS, extension = null, privateBrowsing = false, userContextId = undefined) {
     this.remote = remote;
     this.extension = extension;
     this.privateBrowsing = privateBrowsing;
+    this.userContextId = userContextId;
 
     this.browserReady = this._initBrowser();
   }
 
   async _initBrowser() {
     this.windowlessBrowser = Services.appShell.createWindowlessBrowser(true);
 
     if (this.privateBrowsing) {
@@ -148,16 +149,19 @@ class ContentPage {
     await promiseObserved("chrome-document-global-created",
                           win => win.document == chromeShell.document);
 
     let chromeDoc = await promiseDocumentLoaded(chromeShell.document);
 
     let browser = chromeDoc.createElement("browser");
     browser.setAttribute("type", "content");
     browser.setAttribute("disableglobalhistory", "true");
+    if (this.userContextId) {
+      browser.setAttribute("usercontextid", this.userContextId);
+    }
 
     if (this.extension && this.extension.remote) {
       this.remote = true;
       browser.setAttribute("remote", "true");
       browser.setAttribute("remoteType", "extension");
       browser.sameProcessAsFrameLoader = this.extension.groupFrameLoader;
     }
 
@@ -855,18 +859,18 @@ var ExtensionTestUtils = {
    *        If true, load the URL in a content process. If false, load
    *        it in the parent process.
    * @param {string} [options.redirectUrl]
    *        An optional URL that the initial page is expected to
    *        redirect to.
    *
    * @returns {ContentPage}
    */
-  loadContentPage(url, {extension = undefined, remote = undefined, redirectUrl = undefined, privateBrowsing = false} = {}) {
+  loadContentPage(url, {extension = undefined, remote = undefined, redirectUrl = undefined, privateBrowsing = false, userContextId = undefined} = {}) {
     ContentTask.setTestScope(this.currentScope);
 
-    let contentPage = new ContentPage(remote, extension && extension.extension, privateBrowsing);
+    let contentPage = new ContentPage(remote, extension && extension.extension, privateBrowsing, userContextId);
 
     return contentPage.loadURL(url, redirectUrl).then(() => {
       return contentPage;
     });
   },
 };
--- a/toolkit/components/extensions/ProxyScriptContext.jsm
+++ b/toolkit/components/extensions/ProxyScriptContext.jsm
@@ -22,16 +22,19 @@ ChromeUtils.defineModuleGetter(this, "Sc
                                "resource://gre/modules/Schemas.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "ProxyService",
                                    "@mozilla.org/network/protocol-proxy-service;1",
                                    "nsIProtocolProxyService");
 
 XPCOMUtils.defineLazyGetter(this, "tabTracker", () => {
   return ExtensionParent.apiManager.global.tabTracker;
 });
+XPCOMUtils.defineLazyGetter(this, "getCookieStoreIdForOriginAttributes", () => {
+  return ExtensionParent.apiManager.global.getCookieStoreIdForOriginAttributes;
+});
 
 const CATEGORY_EXTENSION_SCRIPTS_CONTENT = "webextension-scripts-content";
 
 // DNS is resolved on the SOCKS proxy server.
 const {TRANSPARENT_PROXY_RESOLVES_HOST} = Ci.nsIProxyInfo;
 
 // The length of time (seconds) to wait for a proxy to resolve before ignoring it.
 const PROXY_TIMEOUT_SEC = 10;
@@ -251,37 +254,44 @@ class ProxyChannelFilter {
     this.extraInfoSpec = extraInfoSpec || [];
 
     ProxyService.registerChannelFilter(
       this /* nsIProtocolProxyChannelFilter aFilter */,
       0 /* unsigned long aPosition */
     );
   }
 
-  // Copy from WebRequest.jsm with small changes.
+  // Originally duplicated from WebRequest.jsm with small changes.  Keep this
+  // in sync with WebRequest.jsm as well as parent/ext-webRequest.js when
+  // apropiate.
   getRequestData(channel, extraData) {
+    let originAttributes = channel.loadInfo && channel.loadInfo.originAttributes;
     let data = {
       requestId: String(channel.id),
       url: channel.finalURL,
       method: channel.method,
       type: channel.type,
       fromCache: !!channel.fromCache,
+      incognito: originAttributes && originAttributes.privateBrowsingId > 0,
 
       originUrl: channel.originURL || undefined,
       documentUrl: channel.documentURL || undefined,
 
       frameId: channel.windowId,
       parentFrameId: channel.parentWindowId,
 
       frameAncestors: channel.frameAncestors || undefined,
 
       timeStamp: Date.now(),
 
       ...extraData,
     };
+    if (originAttributes && this.extension.hasPermission("cookies")) {
+      data.cookieStoreId = getCookieStoreIdForOriginAttributes(originAttributes);
+    }
     if (this.extraInfoSpec.includes("requestHeaders")) {
       data.requestHeaders = channel.getRequestHeaders();
     }
     return data;
   }
 
   /**
    * This method (which is required by the nsIProtocolProxyService interface)
--- a/toolkit/components/extensions/parent/.eslintrc.js
+++ b/toolkit/components/extensions/parent/.eslintrc.js
@@ -12,15 +12,16 @@ module.exports = {
     "TabManagerBase": true,
     "TabTrackerBase": true,
     "WindowBase": true,
     "WindowManagerBase": true,
     "WindowTrackerBase": true,
     "getUserContextIdForCookieStoreId": true,
     "getContainerForCookieStoreId": true,
     "getCookieStoreIdForContainer": true,
+    "getCookieStoreIdForOriginAttributes": true,
     "getCookieStoreIdForTab": true,
     "isContainerCookieStoreId": true,
     "isDefaultCookieStoreId": true,
     "isPrivateCookieStoreId": true,
     "isValidCookieStoreId": true,
   },
 };
--- a/toolkit/components/extensions/parent/ext-toolkit.js
+++ b/toolkit/components/extensions/parent/ext-toolkit.js
@@ -1,17 +1,18 @@
 "use strict";
 
 // These are defined on "global" which is used for the same scopes as the other
 // ext-*.js files.
 /* exported getCookieStoreIdForTab, getCookieStoreIdForContainer,
             getContainerForCookieStoreId,
             isValidCookieStoreId, isContainerCookieStoreId,
             EventManager, URL */
-/* global getCookieStoreIdForTab:false, getCookieStoreIdForContainer:false,
+/* global getCookieStoreIdForTab:false, getCookieStoreIdForOriginAttributes:false,
+          getCookieStoreIdForContainer:false,
           getContainerForCookieStoreId: false,
           isValidCookieStoreId:false, isContainerCookieStoreId:false,
           isDefaultCookieStoreId: false, isPrivateCookieStoreId:false,
           EventManager: false */
 
 ChromeUtils.defineModuleGetter(this, "ContextualIdentityService",
                                "resource://gre/modules/ContextualIdentityService.jsm");
 
@@ -35,16 +36,28 @@ global.getCookieStoreIdForTab = function
 
   if (tab.userContextId) {
     return getCookieStoreIdForContainer(tab.userContextId);
   }
 
   return DEFAULT_STORE;
 };
 
+global.getCookieStoreIdForOriginAttributes = function(originAttributes) {
+  if (originAttributes.privateBrowsingId) {
+    return PRIVATE_STORE;
+  }
+
+  if (originAttributes.userContextId) {
+    return getCookieStoreIdForContainer(originAttributes.userContextId);
+  }
+
+  return DEFAULT_STORE;
+};
+
 global.isPrivateCookieStoreId = function(storeId) {
   return storeId == PRIVATE_STORE;
 };
 
 global.isDefaultCookieStoreId = function(storeId) {
   return storeId == DEFAULT_STORE;
 };
 
--- a/toolkit/components/extensions/parent/ext-webRequest.js
+++ b/toolkit/components/extensions/parent/ext-webRequest.js
@@ -19,17 +19,22 @@ function registerEvent(extension, eventN
       return;
     }
     if (filter.windowId != null && browserData.windowId != filter.windowId) {
       return;
     }
 
     let event = data.serialize(eventName);
     event.tabId = browserData.tabId;
-
+    if (data.originAttributes) {
+      event.incognito = data.originAttributes.privateBrowsingId > 0;
+      if (extension.hasPermission("cookies")) {
+        event.cookieStoreId = getCookieStoreIdForOriginAttributes(data.originAttributes);
+      }
+    }
     if (data.registerTraceableChannel) {
       // If this is a primed listener, no tabParent was passed in here,
       // but the convert() callback later in this function will be called
       // when the background page is started.  Force that to happen here
       // after which we'll have a valid tabParent.
       if (fire.wakeup) {
         await fire.wakeup();
       }
--- a/toolkit/components/extensions/schemas/proxy.json
+++ b/toolkit/components/extensions/schemas/proxy.json
@@ -142,16 +142,18 @@
             "type": "object",
             "name": "details",
             "properties": {
               "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
               "url": {"type": "string"},
               "method": {"type": "string", "description": "Standard HTTP method."},
               "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
               "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+              "incognito": {"type": "boolean", "optional": true, "description": "True for private browsing requests."},
+              "cookieStoreId": {"type": "string", "optional": true, "description": "The cookie store ID of the contextual identity."},
               "originUrl": {"type": "string", "optional": true, "description": "URL of the resource that triggered this request."},
               "documentUrl": {"type": "string", "optional": true, "description": "URL of the page into which the requested resource will be loaded."},
               "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
               "type": {"$ref": "webRequest.ResourceType", "description": "How the requested resource will be used."},
               "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
               "ip": {"type": "string", "optional": true, "description": "The server IP address that the request was actually sent to. Note that it may be a literal IPv6 address."},
               "fromCache": {"type": "boolean", "description": "Indicates if this response was fetched from disk cache."},
               "requestHeaders": {"$ref": "webRequest.HttpHeaders", "optional": true, "description": "The HTTP request headers that are going to be sent out with this request."}
--- a/toolkit/components/extensions/schemas/web_request.json
+++ b/toolkit/components/extensions/schemas/web_request.json
@@ -420,16 +420,18 @@
             "type": "object",
             "name": "details",
             "properties": {
               "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
               "url": {"type": "string"},
               "method": {"type": "string", "description": "Standard HTTP method."},
               "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
               "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+              "incognito": {"type": "boolean", "optional": true, "description": "True for private browsing requests."},
+              "cookieStoreId": {"type": "string", "optional": true, "description": "The cookie store ID of the contextual identity."},
               "originUrl": {"type": "string", "optional": true, "description": "URL of the resource that triggered this request."},
               "documentUrl": {"type": "string", "optional": true, "description": "URL of the page into which the requested resource will be loaded."},
               "requestBody": {
                 "type": "object",
                 "optional": true,
                 "description": "Contains the HTTP request body data. Only provided if extraInfoSpec contains 'requestBody'.",
                 "properties": {
                   "error": {"type": "string", "optional": true, "description": "Errors when obtaining request body data."},
@@ -488,16 +490,18 @@
             "type": "object",
             "name": "details",
             "properties": {
               "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
               "url": {"type": "string"},
               "method": {"type": "string", "description": "Standard HTTP method."},
               "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
               "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+              "incognito": {"type": "boolean", "optional": true, "description": "True for private browsing requests."},
+              "cookieStoreId": {"type": "string", "optional": true, "description": "The cookie store ID of the contextual identity."},
               "originUrl": {"type": "string", "optional": true, "description": "URL of the resource that triggered this request."},
               "documentUrl": {"type": "string", "optional": true, "description": "URL of the page into which the requested resource will be loaded."},
               "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
               "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."},
               "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
               "requestHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP request headers that are going to be sent out with this request."}
             }
           }
@@ -533,16 +537,18 @@
             "type": "object",
             "name": "details",
             "properties": {
               "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
               "url": {"type": "string"},
               "method": {"type": "string", "description": "Standard HTTP method."},
               "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
               "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+              "incognito": {"type": "boolean", "optional": true, "description": "True for private browsing requests."},
+              "cookieStoreId": {"type": "string", "optional": true, "description": "The cookie store ID of the contextual identity."},
               "originUrl": {"type": "string", "optional": true, "description": "URL of the resource that triggered this request."},
               "documentUrl": {"type": "string", "optional": true, "description": "URL of the page into which the requested resource will be loaded."},
               "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
               "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."},
               "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
               "requestHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP request headers that have been sent out with this request."}
             }
           }
@@ -573,16 +579,18 @@
             "type": "object",
             "name": "details",
             "properties": {
               "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
               "url": {"type": "string"},
               "method": {"type": "string", "description": "Standard HTTP method."},
               "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
               "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+              "incognito": {"type": "boolean", "optional": true, "description": "True for private browsing requests."},
+              "cookieStoreId": {"type": "string", "optional": true, "description": "The cookie store ID of the contextual identity."},
               "originUrl": {"type": "string", "optional": true, "description": "URL of the resource that triggered this request."},
               "documentUrl": {"type": "string", "optional": true, "description": "URL of the page into which the requested resource will be loaded."},
               "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
               "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."},
               "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
               "statusLine": {"type": "string", "description": "HTTP status line of the response or the 'HTTP/0.9 200 OK' string for HTTP/0.9 responses (i.e., responses that lack a status line)."},
               "responseHeaders": {"$ref": "HttpHeaders", "optional": true, "description": "The HTTP response headers that have been received with this response."},
               "statusCode": {"type": "integer", "description": "Standard HTTP status code returned by the server."}
@@ -620,16 +628,18 @@
             "type": "object",
             "name": "details",
             "properties": {
               "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
               "url": {"type": "string"},
               "method": {"type": "string", "description": "Standard HTTP method."},
               "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
               "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+              "incognito": {"type": "boolean", "optional": true, "description": "True for private browsing requests."},
+              "cookieStoreId": {"type": "string", "optional": true, "description": "The cookie store ID of the contextual identity."},
               "originUrl": {"type": "string", "optional": true, "description": "URL of the resource that triggered this request."},
               "documentUrl": {"type": "string", "optional": true, "description": "URL of the page into which the requested resource will be loaded."},
               "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
               "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."},
               "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
               "scheme": {"type": "string", "description": "The authentication scheme, e.g. Basic or Digest."},
               "realm": {"type": "string", "description": "The authentication realm provided by the server, if there is one.", "optional": true},
               "challenger": {"type": "object", "description": "The server requesting authentication.", "properties": {"host": {"type": "string"}, "port": {"type": "integer"}}},
@@ -679,16 +689,18 @@
             "type": "object",
             "name": "details",
             "properties": {
               "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
               "url": {"type": "string"},
               "method": {"type": "string", "description": "Standard HTTP method."},
               "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
               "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+              "incognito": {"type": "boolean", "optional": true, "description": "True for private browsing requests."},
+              "cookieStoreId": {"type": "string", "optional": true, "description": "The cookie store ID of the contextual identity."},
               "originUrl": {"type": "string", "optional": true, "description": "URL of the resource that triggered this request."},
               "documentUrl": {"type": "string", "optional": true, "description": "URL of the page into which the requested resource will be loaded."},
               "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
               "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."},
               "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
               "ip": {"type": "string", "optional": true, "description": "The server IP address that the request was actually sent to. Note that it may be a literal IPv6 address."},
               "fromCache": {"type": "boolean", "description": "Indicates if this response was fetched from disk cache."},
               "statusCode": {"type": "integer", "description": "Standard HTTP status code returned by the server."},
@@ -723,16 +735,18 @@
             "type": "object",
             "name": "details",
             "properties": {
               "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
               "url": {"type": "string"},
               "method": {"type": "string", "description": "Standard HTTP method."},
               "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
               "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+              "incognito": {"type": "boolean", "optional": true, "description": "True for private browsing requests."},
+              "cookieStoreId": {"type": "string", "optional": true, "description": "The cookie store ID of the contextual identity."},
               "originUrl": {"type": "string", "optional": true, "description": "URL of the resource that triggered this request."},
               "documentUrl": {"type": "string", "optional": true, "description": "URL of the page into which the requested resource will be loaded."},
               "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
               "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."},
               "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
               "ip": {"type": "string", "optional": true, "description": "The server IP address that the request was actually sent to. Note that it may be a literal IPv6 address."},
               "fromCache": {"type": "boolean", "description": "Indicates if this response was fetched from disk cache."},
               "statusCode": {"type": "integer", "description": "Standard HTTP status code returned by the server."},
@@ -768,16 +782,18 @@
             "type": "object",
             "name": "details",
             "properties": {
               "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
               "url": {"type": "string"},
               "method": {"type": "string", "description": "Standard HTTP method."},
               "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
               "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+              "incognito": {"type": "boolean", "optional": true, "description": "True for private browsing requests."},
+              "cookieStoreId": {"type": "string", "optional": true, "description": "The cookie store ID of the contextual identity."},
               "originUrl": {"type": "string", "optional": true, "description": "URL of the resource that triggered this request."},
               "documentUrl": {"type": "string", "optional": true, "description": "URL of the page into which the requested resource will be loaded."},
               "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
               "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."},
               "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
               "ip": {"type": "string", "optional": true, "description": "The server IP address that the request was actually sent to. Note that it may be a literal IPv6 address."},
               "fromCache": {"type": "boolean", "description": "Indicates if this response was fetched from disk cache."},
               "statusCode": {"type": "integer", "description": "Standard HTTP status code returned by the server."},
@@ -812,16 +828,18 @@
             "type": "object",
             "name": "details",
             "properties": {
               "requestId": {"type": "string", "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."},
               "url": {"type": "string"},
               "method": {"type": "string", "description": "Standard HTTP method."},
               "frameId": {"type": "integer", "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."},
               "parentFrameId": {"type": "integer", "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."},
+              "incognito": {"type": "boolean", "optional": true, "description": "True for private browsing requests."},
+              "cookieStoreId": {"type": "string", "optional": true, "description": "The cookie store ID of the contextual identity."},
               "originUrl": {"type": "string", "optional": true, "description": "URL of the resource that triggered this request."},
               "documentUrl": {"type": "string", "optional": true, "description": "URL of the page into which the requested resource will be loaded."},
               "tabId": {"type": "integer", "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."},
               "type": {"$ref": "ResourceType", "description": "How the requested resource will be used."},
               "timeStamp": {"type": "number", "description": "The time when this signal is triggered, in milliseconds since the epoch."},
               "ip": {"type": "string", "optional": true, "description": "The server IP address that the request was actually sent to. Note that it may be a literal IPv6 address."},
               "fromCache": {"type": "boolean", "description": "Indicates if this response was fetched from disk cache."},
               "error": {"type": "string", "description": "The error description. This string is <em>not</em> guaranteed to remain backwards compatible between releases. You must not parse and act based upon its content."}
--- a/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_incognito.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_incognito.js
@@ -6,28 +6,48 @@ server.registerPathHandler("/dummy", (re
   response.setStatusLine(request.httpVersion, 200, "OK");
   response.setHeader("Content-Type", "text/html", false);
   response.write("<!DOCTYPE html><html></html>");
 });
 
 add_task(async function test_incognito_webrequest_access() {
   Services.prefs.setBoolPref("extensions.allowPrivateBrowsingByDefault", false);
 
-  // This extension will fail if it gets a request
+  let pb_extension = ExtensionTestUtils.loadExtension({
+    incognitoOverride: "spanning",
+    manifest: {
+      permissions: ["webRequest", "webRequestBlocking", "<all_urls>"],
+    },
+    background() {
+      browser.webRequest.onBeforeRequest.addListener(async (details) => {
+        browser.test.assertTrue(details.incognito, "incognito flag is set");
+        browser.test.notifyPass("webRequest.private");
+      }, {urls: ["<all_urls>"]}, ["blocking"]);
+    },
+  });
+  await pb_extension.startup();
+
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       permissions: ["webRequest", "webRequestBlocking", "<all_urls>"],
     },
     background() {
       browser.webRequest.onBeforeRequest.addListener(async (details) => {
-        browser.test.fail("webrequest received incognito request");
+        browser.test.assertFalse(details.incognito, "incognito flag is not set");
+        browser.test.notifyPass("webRequest");
       }, {urls: ["<all_urls>"]}, ["blocking"]);
     },
   });
+  // Load non-incognito extension to check that private requests are invisible to it.
   await extension.startup();
 
   let contentPage = await ExtensionTestUtils.loadContentPage("http://example.com/dummy", {privateBrowsing: true});
+  await pb_extension.awaitFinish("webRequest.private");
+  await pb_extension.unload();
+  await contentPage.close();
 
+  contentPage = await ExtensionTestUtils.loadContentPage("http://example.com/dummy");
+  await extension.awaitFinish("webRequest");
   await extension.unload();
   await contentPage.close();
 
   Services.prefs.clearUserPref("extensions.allowPrivateBrowsingByDefault");
 });
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_userContextId.js
@@ -0,0 +1,51 @@
+"use strict";
+
+const server = createHttpServer({hosts: ["example.com"]});
+
+server.registerPathHandler("/dummy", (request, response) => {
+  response.setStatusLine(request.httpVersion, 200, "OK");
+  response.setHeader("Content-Type", "text/html", false);
+  response.write("<!DOCTYPE html><html></html>");
+});
+
+add_task(async function test_userContextId_webrequest() {
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["webRequest", "webRequestBlocking", "<all_urls>", "cookies"],
+    },
+    background() {
+      browser.webRequest.onBeforeRequest.addListener(async (details) => {
+        browser.test.assertEq(details.cookieStoreId, "firefox-container-2", "cookieStoreId is set");
+        browser.test.notifyPass("webRequest");
+      }, {urls: ["<all_urls>"]}, ["blocking"]);
+    },
+  });
+  await extension.startup();
+
+  let contentPage = await ExtensionTestUtils.loadContentPage("http://example.com/dummy", {userContextId: 2});
+  await extension.awaitFinish("webRequest");
+
+  await extension.unload();
+  await contentPage.close();
+});
+
+add_task(async function test_userContextId_webrequest_nopermission() {
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["webRequest", "webRequestBlocking", "<all_urls>"],
+    },
+    background() {
+      browser.webRequest.onBeforeRequest.addListener(async (details) => {
+        browser.test.assertEq(details.cookieStoreId, undefined, "cookieStoreId not set, requires cookies permission");
+        browser.test.notifyPass("webRequest");
+      }, {urls: ["<all_urls>"]}, ["blocking"]);
+    },
+  });
+  await extension.startup();
+
+  let contentPage = await ExtensionTestUtils.loadContentPage("http://example.com/dummy", {userContextId: 2});
+  await extension.awaitFinish("webRequest");
+
+  await extension.unload();
+  await contentPage.close();
+});
--- a/toolkit/components/extensions/test/xpcshell/test_proxy_incognito.js
+++ b/toolkit/components/extensions/test/xpcshell/test_proxy_incognito.js
@@ -13,60 +13,64 @@ server.registerPathHandler("/dummy", (re
 
 add_task(async function test_incognito_proxy_onRequest_access() {
   // No specific support exists in the proxy api for this test,
   // rather it depends on functionality existing in ChannelWrapper
   // that prevents notification of private channels if the
   // extension does not have permission.
   Services.prefs.setBoolPref("extensions.allowPrivateBrowsingByDefault", false);
 
-  // This extension will fail if it gets a request
+  // This extension will fail if it gets a private request.
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       permissions: ["proxy", "<all_urls>"],
     },
     async background() {
       browser.proxy.onRequest.addListener(async (details) => {
-        browser.test.fail("proxy.onRequest received incognito request");
+        browser.test.assertFalse(details.incognito, "incognito flag is not set");
+        browser.test.notifyPass("proxy.onRequest");
       }, {urls: ["<all_urls>"]});
 
       // Actual call arguments do not matter here.
       await browser.test.assertRejects(
         browser.proxy.settings.set({value: {
           proxyType: "none",
         }}),
         /proxy.settings requires private browsing permission/,
         "proxy.settings requires private browsing permission.");
 
       browser.test.sendMessage("ready");
     },
   });
   await extension.startup();
   await extension.awaitMessage("ready");
 
-  // This extension will succeed if it gets a request
   let pextension = ExtensionTestUtils.loadExtension({
     incognitoOverride: "spanning",
     manifest: {
       permissions: ["proxy", "<all_urls>"],
     },
     background() {
       browser.proxy.onRequest.addListener(async (details) => {
-        browser.test.notifyPass("proxy.onRequest");
+        browser.test.assertTrue(details.incognito, "incognito flag is set");
+        browser.test.notifyPass("proxy.onRequest.private");
       }, {urls: ["<all_urls>"]});
     },
   });
   await pextension.startup();
 
-  let finished = pextension.awaitFinish("proxy.onRequest");
   let contentPage = await ExtensionTestUtils.loadContentPage("http://example.com/dummy", {privateBrowsing: true});
-  await finished;
+  await pextension.awaitFinish("proxy.onRequest.private");
+  await pextension.unload();
+  await contentPage.close();
+
+  contentPage = await ExtensionTestUtils.loadContentPage("http://example.com/dummy");
+  await extension.awaitFinish("proxy.onRequest");
 
   await extension.unload();
-  await pextension.unload();
   await contentPage.close();
 
   Services.prefs.clearUserPref("extensions.allowPrivateBrowsingByDefault");
 });
 
 function scriptData(script) {
   return String(script).replace(/^.*?\{([^]*)\}$/, "$1");
 }
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_proxy_userContextId.js
@@ -0,0 +1,52 @@
+"use strict";
+
+const server = createHttpServer({hosts: ["example.com"]});
+
+server.registerPathHandler("/dummy", (request, response) => {
+  response.setStatusLine(request.httpVersion, 200, "OK");
+  response.setHeader("Content-Type", "text/html", false);
+  response.write("<!DOCTYPE html><html></html>");
+});
+
+
+add_task(async function test_userContextId_proxy_onRequest() {
+  // This extension will succeed if it gets a request
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["proxy", "<all_urls>", "cookies"],
+    },
+    background() {
+      browser.proxy.onRequest.addListener(async (details) => {
+        browser.test.assertEq(details.cookieStoreId, "firefox-container-2", "cookieStoreId is set");
+        browser.test.notifyPass("proxy.onRequest");
+      }, {urls: ["<all_urls>"]});
+    },
+  });
+  await extension.startup();
+
+  let contentPage = await ExtensionTestUtils.loadContentPage("http://example.com/dummy", {userContextId: 2});
+  await extension.awaitFinish("proxy.onRequest");
+  await extension.unload();
+  await contentPage.close();
+});
+
+add_task(async function test_userContextId_proxy_onRequest_nopermission() {
+  // This extension will succeed if it gets a request
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["proxy", "<all_urls>"],
+    },
+    background() {
+      browser.proxy.onRequest.addListener(async (details) => {
+        browser.test.assertEq(details.cookieStoreId, undefined, "cookieStoreId not set, requires cookies permission");
+        browser.test.notifyPass("proxy.onRequest");
+      }, {urls: ["<all_urls>"]});
+    },
+  });
+  await extension.startup();
+
+  let contentPage = await ExtensionTestUtils.loadContentPage("http://example.com/dummy", {userContextId: 2});
+  await extension.awaitFinish("proxy.onRequest");
+  await extension.unload();
+  await contentPage.close();
+});
--- a/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
@@ -153,26 +153,28 @@ skip-if = os == "android" && debug
 skip-if = os == "android" && debug
 [test_ext_webRequest_responseBody.js]
 skip-if = os == "android" && debug
 [test_ext_webRequest_set_cookie.js]
 skip-if = appname == "thunderbird" || (os == "android" && debug)
 [test_ext_webRequest_startup.js]
 skip-if = os == "android" && debug
 [test_ext_webRequest_suspend.js]
+[test_ext_webRequest_userContextId.js]
 [test_ext_webRequest_webSocket.js]
 skip-if = appname == "thunderbird"
 [test_ext_xhr_capabilities.js]
 [test_native_manifests.js]
 subprocess = true
 skip-if = os == "android"
 [test_ext_permissions.js]
 skip-if = appname == "thunderbird" || os == "android" # Bug 1350559
 [test_ext_permissions_uninstall.js]
 skip-if = appname == "thunderbird" || os == "android" # Bug 1350559
 [test_proxy_listener.js]
 [test_proxy_incognito.js]
 skip-if = os == "android" # incognito not supported on android
 [test_proxy_scripts.js]
 [test_proxy_scripts_results.js]
+[test_proxy_userContextId.js]
 [test_ext_brokenlinks.js]
 [test_ext_performance_counters.js]
 skip-if = appname == "thunderbird" || os == "android"
--- a/toolkit/components/satchel/nsFormFillController.cpp
+++ b/toolkit/components/satchel/nsFormFillController.cpp
@@ -382,19 +382,18 @@ nsFormFillController::SetPopupOpen(bool 
       // make sure input field is visible before showing popup (bug 320938)
       nsCOMPtr<nsIContent> content = mFocusedInput;
       NS_ENSURE_STATE(content);
       nsCOMPtr<nsIDocShell> docShell = GetDocShellForInput(mFocusedInput);
       NS_ENSURE_STATE(docShell);
       RefPtr<PresShell> presShell = docShell->GetPresShell();
       NS_ENSURE_STATE(presShell);
       presShell->ScrollContentIntoView(
-          content,
-          nsIPresShell::ScrollAxis(kScrollMinimum, WhenToScroll::IfNotVisible),
-          nsIPresShell::ScrollAxis(kScrollMinimum, WhenToScroll::IfNotVisible),
+          content, ScrollAxis(kScrollMinimum, WhenToScroll::IfNotVisible),
+          ScrollAxis(kScrollMinimum, WhenToScroll::IfNotVisible),
           ScrollFlags::ScrollOverflowHidden |
               ScrollFlags::IgnoreMarginAndPadding);
       // mFocusedPopup can be destroyed after ScrollContentIntoView, see bug
       // 420089
       if (mFocusedPopup) {
         mFocusedPopup->OpenAutocompletePopup(this, mFocusedInput);
       }
     } else {
rename from toolkit/modules/ClientID.jsm
rename to toolkit/components/telemetry/app/ClientID.jsm
--- a/toolkit/components/telemetry/moz.build
+++ b/toolkit/components/telemetry/moz.build
@@ -96,16 +96,17 @@ if CONFIG['MOZ_GECKO_PROFILER']:
     ]
 
 XPCOM_MANIFESTS += [
     'components.conf',
     'core/components.conf',
 ]
 
 EXTRA_JS_MODULES += [
+    'app/ClientID.jsm',
     'app/TelemetryArchive.jsm',
     'app/TelemetryController.jsm',
     'app/TelemetryEnvironment.jsm',
     'app/TelemetryReportingPolicy.jsm',
     'app/TelemetryScheduler.jsm',
     'app/TelemetrySend.jsm',
     'app/TelemetryStorage.jsm',
     'app/TelemetryTimestamps.jsm',
rename from toolkit/modules/tests/xpcshell/test_client_id.js
rename to toolkit/components/telemetry/tests/unit/test_client_id.js
--- a/toolkit/modules/tests/xpcshell/test_client_id.js
+++ b/toolkit/components/telemetry/tests/unit/test_client_id.js
@@ -1,17 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 const {ClientID} = ChromeUtils.import("resource://gre/modules/ClientID.jsm");
 const {CommonUtils} = ChromeUtils.import("resource://services-common/utils.js");
 const {OS} = ChromeUtils.import("resource://gre/modules/osfile.jsm");
-const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 
 const PREF_CACHED_CLIENTID = "toolkit.telemetry.cachedClientID";
 
 function run_test() {
   do_get_profile();
   run_next_test();
 }
 
--- a/toolkit/components/telemetry/tests/unit/xpcshell.ini
+++ b/toolkit/components/telemetry/tests/unit/xpcshell.ini
@@ -17,16 +17,17 @@ support-files =
   testUnicodePDBAArch64.dll
   testNoPDBAArch64.dll
   !/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
 generated-files =
   dictionary.xpi
   system.xpi
   restartless.xpi
 
+[test_client_id.js]
 [test_GeckoView.js]
 skip-if = os == "android" # Disabled due to crashes (see bug 1331366)
 head = head_GeckoView.js
 support-files =
   test_GeckoView_content_histograms.js
 [test_GeckoView_ScalarSemantics.js]
 skip-if = os == "android" # Disabled due to crashes (see bug 1331366)
 head = head_GeckoView.js
--- a/toolkit/components/typeaheadfind/nsTypeAheadFind.cpp
+++ b/toolkit/components/typeaheadfind/nsTypeAheadFind.cpp
@@ -324,26 +324,26 @@ void nsTypeAheadFind::PlayNotFoundSound(
 nsresult nsTypeAheadFind::FindItNow(bool aIsLinksOnly,
                                     bool aIsFirstVisiblePreferred,
                                     bool aFindPrev, uint16_t* aResult) {
   *aResult = FIND_NOTFOUND;
   mFoundLink = nullptr;
   mFoundEditable = nullptr;
   mFoundRange = nullptr;
   mCurrentWindow = nullptr;
-  nsCOMPtr<nsIPresShell> startingPresShell(GetPresShell());
+  RefPtr<PresShell> startingPresShell = GetPresShell();
   if (!startingPresShell) {
     nsCOMPtr<nsIDocShell> ds = do_QueryReferent(mDocShell);
     NS_ENSURE_TRUE(ds, NS_ERROR_FAILURE);
 
     startingPresShell = ds->GetPresShell();
     mPresShell = do_GetWeakReference(startingPresShell);
   }
 
-  nsCOMPtr<nsIPresShell> presShell = startingPresShell;
+  RefPtr<PresShell> presShell = startingPresShell;
   if (!presShell) {
     return NS_ERROR_FAILURE;
   }
 
   // There could be unflushed notifications which hide textareas or other
   // elements that we don't want to find text in.
   presShell->FlushPendingNotifications(mozilla::FlushType::Layout);
 
@@ -435,18 +435,17 @@ nsresult nsTypeAheadFind::FindItNow(bool
         break;  // Nothing found in this doc, go to outer loop (try next doc)
       }
 
       // ------- Test resulting found range for success conditions ------
       bool isInsideLink = false, isStartingLink = false;
 
       if (aIsLinksOnly) {
         // Don't check if inside link when searching all text
-        RangeStartsInsideLink(returnRange, presShell, &isInsideLink,
-                              &isStartingLink);
+        RangeStartsInsideLink(returnRange, &isInsideLink, &isStartingLink);
       }
 
       bool usesIndependentSelection;
       // Check actual visibility of the range, and generate some
       // side effects (like updating mStartPointRange and
       // setting usesIndependentSelection) that we'll need whether
       // or not the range is visible.
       bool canSeeRange = IsRangeVisible(
@@ -723,17 +722,17 @@ nsTypeAheadFind::GetCurrentWindow(mozIDO
   NS_ENSURE_ARG_POINTER(aCurrentWindow);
   *aCurrentWindow = mCurrentWindow;
   NS_IF_ADDREF(*aCurrentWindow);
   return NS_OK;
 }
 
 nsresult nsTypeAheadFind::GetSearchContainers(
     nsISupports* aContainer, nsISelectionController* aSelectionController,
-    bool aIsFirstVisiblePreferred, bool aFindPrev, nsIPresShell** aPresShell,
+    bool aIsFirstVisiblePreferred, bool aFindPrev, PresShell** aPresShell,
     nsPresContext** aPresContext) {
   NS_ENSURE_ARG_POINTER(aContainer);
   NS_ENSURE_ARG_POINTER(aPresShell);
   NS_ENSURE_ARG_POINTER(aPresContext);
 
   *aPresShell = nullptr;
   *aPresContext = nullptr;
 
@@ -792,17 +791,17 @@ nsresult nsTypeAheadFind::GetSearchConta
   }
   mEndPointRange->SetEnd(*searchRootNode, searchRootNode->Length(),
                          IgnoreErrors());
   mEndPointRange->Collapse(false);  // collapse to end
 
   // Consider current selection as null if
   // it's not in the currently focused document
   RefPtr<nsRange> currentSelectionRange;
-  nsCOMPtr<nsIPresShell> selectionPresShell = GetPresShell();
+  RefPtr<PresShell> selectionPresShell = GetPresShell();
   if (aSelectionController && selectionPresShell &&
       selectionPresShell == presShell) {
     RefPtr<Selection> selection = aSelectionController->GetSelection(
         nsISelectionController::SELECTION_NORMAL);
     if (selection) {
       currentSelectionRange = selection->GetRangeAt(0);
     }
   }
@@ -838,17 +837,16 @@ nsresult nsTypeAheadFind::GetSearchConta
 
   presShell.forget(aPresShell);
   presContext.forget(aPresContext);
 
   return NS_OK;
 }
 
 void nsTypeAheadFind::RangeStartsInsideLink(nsRange* aRange,
-                                            nsIPresShell* aPresShell,
                                             bool* aIsInsideLink,
                                             bool* aIsStartingLink) {
   *aIsInsideLink = false;
   *aIsStartingLink = true;
 
   // ------- Get nsIContent to test -------
   uint32_t startOffset = aRange->StartOffset();
 
@@ -953,17 +951,17 @@ nsTypeAheadFind::FindAgain(bool aFindBac
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsTypeAheadFind::Find(const nsAString& aSearchString, bool aLinksOnly,
                       uint16_t* aResult) {
   *aResult = FIND_NOTFOUND;
 
-  nsCOMPtr<nsIPresShell> presShell(GetPresShell());
+  RefPtr<PresShell> presShell = GetPresShell();
   if (!presShell) {
     nsCOMPtr<nsIDocShell> ds(do_QueryReferent(mDocShell));
     NS_ENSURE_TRUE(ds, NS_ERROR_FAILURE);
 
     presShell = ds->GetPresShell();
     NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
     mPresShell = do_GetWeakReference(presShell);
   }
@@ -1082,17 +1080,17 @@ nsTypeAheadFind::Find(const nsAString& a
     if (!mEntireWord && mTypeAheadBuffer.Length() > mLastFindLength)
       PlayNotFoundSound();
   }
 
   SaveFind();
   return NS_OK;
 }
 
-void nsTypeAheadFind::GetSelection(nsIPresShell* aPresShell,
+void nsTypeAheadFind::GetSelection(PresShell* aPresShell,
                                    nsISelectionController** aSelCon,
                                    Selection** aDOMSel) {
   if (!aPresShell) return;
 
   // if aCurrentNode is nullptr, get selection for document
   *aDOMSel = nullptr;
 
   nsPresContext* presContext = aPresShell->GetPresContext();
@@ -1133,17 +1131,17 @@ nsTypeAheadFind::IsRangeVisible(nsRange*
   }
   RefPtr<nsPresContext> presContext = presShell->GetPresContext();
   RefPtr<nsRange> ignored;
   *aResult = IsRangeVisible(presShell, presContext, aRange, aMustBeInViewPort,
                             false, getter_AddRefs(ignored), nullptr);
   return NS_OK;
 }
 
-bool nsTypeAheadFind::IsRangeVisible(nsIPresShell* aPresShell,
+bool nsTypeAheadFind::IsRangeVisible(PresShell* aPresShell,
                                      nsPresContext* aPresContext,
                                      nsRange* aRange, bool aMustBeInViewPort,
                                      bool aGetTopVisibleLeaf,
                                      nsRange** aFirstVisibleRange,
                                      bool* aUsesIndependentSelection) {
   NS_ASSERTION(aPresShell && aPresContext && aRange && aFirstVisibleRange,
                "params are invalid");
 
@@ -1317,17 +1315,17 @@ nsTypeAheadFind::IsRangeRendered(nsRange
   if (!presShell) {
     return NS_ERROR_UNEXPECTED;
   }
   RefPtr<nsPresContext> presContext = presShell->GetPresContext();
   *aResult = IsRangeRendered(presShell, presContext, aRange);
   return NS_OK;
 }
 
-bool nsTypeAheadFind::IsRangeRendered(nsIPresShell* aPresShell,
+bool nsTypeAheadFind::IsRangeRendered(PresShell* aPresShell,
                                       nsPresContext* aPresContext,
                                       nsRange* aRange) {
   using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
   NS_ASSERTION(aPresShell && aPresContext && aRange, "params are invalid");
 
   nsCOMPtr<nsIContent> content = do_QueryInterface(aRange->GetCommonAncestor());
   if (!content) {
     return false;
@@ -1372,21 +1370,21 @@ bool nsTypeAheadFind::IsRangeRendered(ns
     }
 
     frames.ClearAndRetainStorage();
   }
 
   return false;
 }
 
-already_AddRefed<nsIPresShell> nsTypeAheadFind::GetPresShell() {
+already_AddRefed<PresShell> nsTypeAheadFind::GetPresShell() {
   if (!mPresShell) return nullptr;
 
-  nsCOMPtr<nsIPresShell> shell = do_QueryReferent(mPresShell);
-  if (shell) {
-    nsPresContext* pc = shell->GetPresContext();
+  nsCOMPtr<nsIPresShell> iPresShell = do_QueryReferent(mPresShell);
+  if (iPresShell) {
+    nsPresContext* pc = iPresShell->GetPresContext();
     if (!pc || !pc->GetContainerWeak()) {
       return nullptr;
     }
   }
-
-  return shell.forget();
+  RefPtr<PresShell> presShell = static_cast<PresShell*>(iPresShell.get());
+  return presShell.forget();
 }
--- a/toolkit/components/typeaheadfind/nsTypeAheadFind.h
+++ b/toolkit/components/typeaheadfind/nsTypeAheadFind.h
@@ -13,21 +13,21 @@
 #include "nsIFind.h"
 #include "nsIWebBrowserFind.h"
 #include "nsWeakReference.h"
 #include "nsIDocShellTreeItem.h"
 #include "nsITypeAheadFind.h"
 #include "nsISound.h"
 
 class nsPIDOMWindowInner;
-class nsIPresShell;
 class nsPresContext;
 class nsRange;
 
 namespace mozilla {
+class PresShell;
 namespace dom {
 class Element;
 class Selection;
 }  // namespace dom
 }  // namespace mozilla
 
 #define TYPEAHEADFIND_NOTFOUND_WAV_URL "chrome://global/content/notfound.wav"
 
@@ -48,41 +48,42 @@ class nsTypeAheadFind : public nsITypeAh
 
   nsresult PrefsReset();
 
   void SaveFind();
   void PlayNotFoundSound();
   nsresult GetWebBrowserFind(nsIDocShell *aDocShell,
                              nsIWebBrowserFind **aWebBrowserFind);
 
-  void RangeStartsInsideLink(nsRange *aRange, nsIPresShell *aPresShell,
-                             bool *aIsInsideLink, bool *aIsStartingLink);
+  void RangeStartsInsideLink(nsRange *aRange, bool *aIsInsideLink,
+                             bool *aIsStartingLink);
 
-  void GetSelection(nsIPresShell *aPresShell, nsISelectionController **aSelCon,
+  void GetSelection(mozilla::PresShell *aPresShell,
+                    nsISelectionController **aSelCon,
                     mozilla::dom::Selection **aDomSel);
   // *aNewRange may not be collapsed.  If you want to collapse it in a
   // particular way, you need to do it yourself.
-  bool IsRangeVisible(nsIPresShell *aPresShell, nsPresContext *aPresContext,
-                      nsRange *aRange, bool aMustBeVisible,
-                      bool aGetTopVisibleLeaf, nsRange **aNewRange,
-                      bool *aUsesIndependentSelection);
-  bool IsRangeRendered(nsIPresShell *aPresShell, nsPresContext *aPresContext,
-                       nsRange *aRange);
+  bool IsRangeVisible(mozilla::PresShell *aPresShell,
+                      nsPresContext *aPresContext, nsRange *aRange,
+                      bool aMustBeVisible, bool aGetTopVisibleLeaf,
+                      nsRange **aNewRange, bool *aUsesIndependentSelection);
+  bool IsRangeRendered(mozilla::PresShell *aPresShell,
+                       nsPresContext *aPresContext, nsRange *aRange);
   MOZ_CAN_RUN_SCRIPT_BOUNDARY
   nsresult FindItNow(bool aIsLinksOnly, bool aIsFirstVisiblePreferred,
                      bool aFindPrev, uint16_t *aResult);
   nsresult GetSearchContainers(nsISupports *aContainer,
                                nsISelectionController *aSelectionController,
                                bool aIsFirstVisiblePreferred, bool aFindPrev,
-                               nsIPresShell **aPresShell,
+                               mozilla::PresShell **aPresShell,
                                nsPresContext **aPresContext);
 
   // Get the pres shell from mPresShell and return it only if it is still
   // attached to the DOM window.
-  already_AddRefed<nsIPresShell> GetPresShell();
+  already_AddRefed<mozilla::PresShell> GetPresShell();
 
   void ReleaseStrongMemberVariables();
 
   // Current find state
   nsString mTypeAheadBuffer;
   nsCString mNotFoundSoundURL;
 
   // PRBools are used instead of PRPackedBools because the address of the
--- a/toolkit/modules/addons/WebRequest.jsm
+++ b/toolkit/modules/addons/WebRequest.jsm
@@ -659,23 +659,25 @@ HttpObserverManager = {
       });
     } else if (lastActivity !== this.GOOD_LAST_ACTIVITY &&
                lastActivity !== nsIHttpActivityObserver.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE) {
       channel.lastActivity = activitySubtype;
     }
   },
 
   getRequestData(channel, extraData) {
+    let originAttributes = channel.loadInfo && channel.loadInfo.originAttributes;
     let data = {
       requestId: String(channel.id),
       url: channel.finalURL,
       method: channel.method,
       browser: channel.browserElement,
       type: channel.type,
       fromCache: channel.fromCache,
+      originAttributes,
 
       originUrl: channel.originURL || undefined,
       documentUrl: channel.documentURL || undefined,
 
       windowId: channel.windowId,
       parentWindowId: channel.parentWindowId,
 
       frameAncestors: channel.frameAncestors || undefined,
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -43,31 +43,25 @@ with Files('tests/xpcshell/test_Match*.j
     BUG_COMPONENT = ('WebExtensions', 'General')
 
 with Files('tests/xpcshell/test_NewTabUtils.js'):
     BUG_COMPONENT = ('Firefox', 'New Tab Page')
 
 with Files('tests/xpcshell/test_UpdateUtils*.js'):
     BUG_COMPONENT = ('Toolkit', 'Application Update')
 
-with Files('tests/xpcshell/test_client_id.js'):
-    BUG_COMPONENT = ('Toolkit', 'Telemetry')
-
 with Files('AsyncPrefs.jsm'):
     BUG_COMPONENT = ('Core', 'Security: Process Sandboxing')
 
 with Files('AutoScrollController.jsm'):
     BUG_COMPONENT = ('Core', 'Panning and Zooming')
 
 with Files('CharsetMenu.jsm'):
     BUG_COMPONENT = ('Firefox', 'Toolbars and Customization')
 
-with Files('ClientID.jsm'):
-    BUG_COMPONENT = ('Toolkit', 'Telemetry')
-
 with Files('Color.jsm'):
     BUG_COMPONENT = ('Toolkit', 'Find Toolbar')
 
 with Files('Console.jsm'):
     BUG_COMPONENT = ('DevTools', 'Console')
 
 with Files('DateTimePicker*.jsm'):
     BUG_COMPONENT = ('Core', 'Layout: Form Controls ')
@@ -186,17 +180,16 @@ EXTRA_JS_MODULES += [
     'AsyncPrefs.jsm',
     'AutoCompletePopupContent.jsm',
     'AutoScrollController.jsm',
     'BinarySearch.jsm',
     'BrowserUtils.jsm',
     'CanonicalJSON.jsm',
     'CertUtils.jsm',
     'CharsetMenu.jsm',
-    'ClientID.jsm',
     'Color.jsm',
     'Console.jsm',
     'CreditCard.jsm',
     'css-selector.js',
     'DateTimePickerPanel.jsm',
     'DateTimePickerParent.jsm',
     'DeferredTask.jsm',
     'Deprecated.jsm',
--- a/toolkit/modules/tests/xpcshell/xpcshell.ini
+++ b/toolkit/modules/tests/xpcshell/xpcshell.ini
@@ -6,17 +6,16 @@ support-files =
   propertyLists/bug710259_propertyListXML.plist
   chromeappsstore.sqlite
   corrupt.sqlite
   zips/zen.zip
 
 [test_BinarySearch.js]
 skip-if = toolkit == 'android'
 [test_CanonicalJSON.js]
-[test_client_id.js]
 [test_Color.js]
 [test_CreditCard.js]
 [test_DeferredTask.js]
 skip-if = toolkit == 'android'
 [test_FileUtils.js]
 skip-if = toolkit == 'android'
 [test_FinderIterator.js]
 [test_GMPInstallManager.js]
--- a/toolkit/mozapps/extensions/test/browser/browser.ini
+++ b/toolkit/mozapps/extensions/test/browser/browser.ini
@@ -73,16 +73,17 @@ skip-if = os == "linux" && !debug # Bug 
 [browser_file_xpi_no_process_switch.js]
 skip-if = true # Bug 1449071 - Frequent failures
 [browser_globalwarnings.js]
 [browser_gmpProvider.js]
 skip-if = os == 'linux' && !debug # Bug 1398766
 [browser_html_detail_view.js]
 [browser_html_list_view.js]
 [browser_html_plugins.js]
+skip-if = (os == 'win' && processor == 'aarch64') # aarch64 has no plugin support, bug 1525174 and 1547495
 [browser_html_updates.js]
 [browser_inlinesettings_browser.js]
 skip-if = os == 'mac' || os == 'linux' # Bug 1483347
 [browser_installssl.js]
 skip-if = verify
 [browser_interaction_telemetry.js]
 [browser_langpack_signing.js]
 [browser_legacy.js]
--- a/toolkit/mozapps/update/tests/browser/browser.bits.ini
+++ b/toolkit/mozapps/update/tests/browser/browser.bits.ini
@@ -39,28 +39,41 @@ prefs =
 [browser_aboutPrefs_fc_patch_completeBadSize.js]
 [browser_aboutPrefs_fc_patch_partialBadSize.js]
 [browser_aboutPrefs_fc_patch_partialBadSize_complete.js]
 [browser_aboutPrefs_fc_patch_partialBadSize_completeBadSize.js]
 
 # Doorhanger Application Update Tests
 [browser_doorhanger_bc_downloaded.js]
 [browser_doorhanger_bc_downloaded_staged.js]
+[browser_doorhanger_bc_downloadAutoFailures.js]
+[browser_doorhanger_bc_downloadAutoFailures_bgWin.js]
 [browser_doorhanger_bc_downloadOptIn.js]
+[browser_doorhanger_bc_downloadOptIn_bgWin.js]
 [browser_doorhanger_bc_downloadOptIn_staging.js]
+[browser_doorhanger_bc_patch_completeBadSize.js]
+[browser_doorhanger_bc_patch_partialBadSize.js]
+[browser_doorhanger_bc_patch_partialBadSize_complete.js]
+[browser_doorhanger_bc_patch_partialBadSize_completeBadSize.js]
+[browser_doorhanger_sp_patch_completeApplyFailure.js]
+[browser_doorhanger_sp_patch_partialApplyFailure.js]
 [browser_doorhanger_sp_patch_partialApplyFailure_complete.js]
 [browser_doorhanger_sp_patch_partialApplyFailure_complete_staging.js]
+[browser_doorhanger_sp_patch_partialApplyFailure_completeBadSize.js]
 
 # Telemetry Application Update Tests
 [browser_telemetry_completeBadSize.js]
 [browser_telemetry_partialBadSize_completeBadSize.js]
 [browser_telemetry_complete_stageFailure.js]
 [browser_telemetry_partial_stageFailure_complete_stageFailure.js]
 [browser_telemetry_complete_applyFailure.js]
 [browser_telemetry_partial_applyFailure_complete_applyFailure.js]
 [browser_telemetry_partial_applyFailure_complete_stageFailure.js]
 [browser_telemetry_partial_applyFailure_complete_applied.js]
 [browser_telemetry_partial_applyFailure_complete_staged_applied.js]
 [browser_telemetry_partialBadSize_complete_staged_applied.js]
 [browser_telemetry_complete_applied.js]
 [browser_telemetry_partial_applied.js]
 [browser_telemetry_partial_staged_applied.js]
 [browser_telemetry_complete_staged_applied.js]
+
+# Telemetry Update Ping Tests
+[browser_telemetry_updatePing_ready.js]
--- a/toolkit/mozapps/update/tests/browser/browser.ini
+++ b/toolkit/mozapps/update/tests/browser/browser.ini
@@ -88,9 +88,11 @@ reason = test must be able to prevent fi
 [browser_telemetry_partial_applyFailure_complete_stageFailure.js]
 [browser_telemetry_partial_applyFailure_complete_applied.js]
 [browser_telemetry_partial_applyFailure_complete_staged_applied.js]
 [browser_telemetry_partialBadSize_complete_staged_applied.js]
 [browser_telemetry_complete_applied.js]
 [browser_telemetry_partial_applied.js]
 [browser_telemetry_partial_staged_applied.js]
 [browser_telemetry_complete_staged_applied.js]
-[browser_TelemetryUpdatePing.js]
+
+# Telemetry Update Ping Tests
+[browser_telemetry_updatePing_ready.js]
--- a/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_check_cantApply.js
+++ b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_check_cantApply.js
@@ -1,19 +1,19 @@
-add_task(async function testBasicPrompt() {
-  SpecialPowers.pushPrefEnv({set: [[PREF_APP_UPDATE_SERVICE_ENABLED, false]]});
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function doorhanger_bc_check_cantApply() {
   lockWriteTestFile();
 
-  let updateParams = "promptWaitTime=0";
-
-  await runUpdateTest(updateParams, 1, [
+  let updateParams = "&promptWaitTime=0";
+  await runDoorhangerUpdateTest(updateParams, 1, [
     {
       notificationId: "update-manual",
       button: "button",
-      async cleanup() {
-        await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
-        is(gBrowser.selectedBrowser.currentURI.spec,
-           URL_MANUAL_UPDATE, "Landed on manual update page.");
-        gBrowser.removeTab(gBrowser.selectedTab);
-      },
+      checkActiveUpdate: null,
+      pageURLs: {whatsNew: gDetailsURL,
+                 manual: URL_MANUAL_UPDATE},
     },
   ]);
 });
--- a/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_check_malformedXML.js
+++ b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_check_malformedXML.js
@@ -1,28 +1,26 @@
-add_task(async function testMalformedXml() {
-  const updateDetailsUrl = "http://example.com/details";
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function doorhanger_bc_check_malformedXML() {
   const maxBackgroundErrors = 10;
-  SpecialPowers.pushPrefEnv({set: [
-    [PREF_APP_UPDATE_BACKGROUNDMAXERRORS, maxBackgroundErrors],
-    [PREF_APP_UPDATE_URL_DETAILS, updateDetailsUrl],
-  ]});
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      [PREF_APP_UPDATE_BACKGROUNDMAXERRORS, maxBackgroundErrors],
+    ],
+  });
 
-  let updateParams = "xmlMalformed=1";
-
-  await runUpdateTest(updateParams, maxBackgroundErrors, [
+  let updateParams = "&xmlMalformed=1";
+  await runDoorhangerUpdateTest(updateParams, maxBackgroundErrors, [
     {
-      // if we fail 10 check attempts, then we want to just show the user a manual update
-      // workflow.
+      // If the update check fails 10 consecutive attempts then the manual
+      // update doorhanger.
       notificationId: "update-manual",
       button: "button",
-      beforeClick() {
-        checkWhatsNewLink(window, "update-manual-whats-new", updateDetailsUrl);
-      },
-      async cleanup() {
-        await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
-        is(gBrowser.selectedBrowser.currentURI.spec,
-           URL_MANUAL_UPDATE, "Landed on manual update page.");
-        gBrowser.removeTab(gBrowser.selectedTab);
-      },
+      checkActiveUpdate: null,
+      pageURLs: {whatsNew: gDetailsURL,
+                 manual: URL_MANUAL_UPDATE},
     },
   ]);
 });
--- a/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloadAutoFailures.js
+++ b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloadAutoFailures.js
@@ -1,30 +1,40 @@
-add_task(async function testDownloadFailures() {
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function doorhanger_bc_downloadAutoFailures() {
   const maxBackgroundErrors = 5;
-  SpecialPowers.pushPrefEnv({set: [
-    [PREF_APP_UPDATE_BACKGROUNDMAXERRORS, maxBackgroundErrors],
-  ]});
-  let updateParams = "badURL=1";
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      [PREF_APP_UPDATE_BACKGROUNDMAXERRORS, maxBackgroundErrors],
+    ],
+  });
 
-  await runUpdateTest(updateParams, 1, [
+  let updateParams = "&badURL=1";
+  await runDoorhangerUpdateTest(updateParams, 1, [
     {
-      // if we fail maxBackgroundErrors download attempts, then we want to
-      // first show the user an update available prompt.
+      // If the update download fails maxBackgroundErrors download attempts then
+      // show the update available prompt.
       notificationId: "update-available",
       button: "button",
+      checkActiveUpdate: null,
+      pageURLs: {whatsNew: gDefaultWhatsNewURL},
     },
     {
       notificationId: "update-available",