Bug 1430751 - Apply unified behavior model for about: pages to about:privatebrowsing. r=gijs,mrbkap
authorChristoph Kerschbaumer <ckerschb@christophkerschbaumer.com>
Mon, 05 Mar 2018 09:20:34 +0100
changeset 406544 ed9129a6115596564bffe8b855ef014d42eec646
parent 406529 99fd2c41687b7a9f7912a2b74b1eaaf4fa7a96f9
child 406545 b47e9371891215b7f5bb5e7ec26485f51b4e477c
push id33570
push usercsabou@mozilla.com
push dateMon, 05 Mar 2018 18:12:27 +0000
treeherdermozilla-central@0ef34a9ec4fb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgijs, mrbkap
bugs1430751
milestone60.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
Bug 1430751 - Apply unified behavior model for about: pages to about:privatebrowsing. r=gijs,mrbkap
browser/base/content/browser.js
browser/base/content/tab-content.js
browser/components/about/AboutRedirector.cpp
browser/components/about/aboutcapabilities.manifest
browser/components/about/moz.build
browser/components/about/nsAboutCapabilities.js
browser/components/privatebrowsing/content/aboutPrivateBrowsing.js
browser/components/privatebrowsing/content/aboutPrivateBrowsing.xhtml
browser/installer/package-manifest.in
dom/base/nsDocument.cpp
dom/base/nsDocument.h
dom/base/nsIDocument.h
dom/webidl/AboutCapabilities.webidl
dom/webidl/Document.webidl
dom/webidl/moz.build
toolkit/modules/AsyncPrefs.jsm
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1281,17 +1281,17 @@ var gBrowserInit = {
     // message sent between when the frame script is loaded and when
     // the listener is registered.
     DOMEventHandler.init();
     gPageStyleMenu.init();
     LanguageDetectionListener.init();
     BrowserOnClick.init();
     FeedHandler.init();
     CompactTheme.init();
-    AboutPrivateBrowsingListener.init();
+    AboutCapabilitiesListener.init();
     TrackingProtection.init();
     CaptivePortalWatcher.init();
     ZoomUI.init(window);
 
     let mm = window.getGroupMessageManager("browsers");
     mm.loadFrameScript("chrome://browser/content/tab-content.js", true);
     mm.loadFrameScript("chrome://browser/content/content.js", true);
     mm.loadFrameScript("chrome://browser/content/content-UITour.js", true);
@@ -1853,16 +1853,18 @@ var gBrowserInit = {
     gTabletModePageCounter.finish();
 
     BrowserOnClick.uninit();
 
     FeedHandler.uninit();
 
     CompactTheme.uninit();
 
+    AboutCapabilitiesListener.uninit();
+
     TrackingProtection.uninit();
 
     CaptivePortalWatcher.uninit();
 
     SidebarUI.uninit();
 
     DownloadsButton.uninit();
 
@@ -9028,35 +9030,47 @@ var PanicButtonNotifier = {
     }
   },
   close() {
     let popup = document.getElementById("panic-button-success-notification");
     popup.hidePopup();
   },
 };
 
-var AboutPrivateBrowsingListener = {
+var AboutCapabilitiesListener = {
+  _topics: [
+    "AboutCapabilities:OpenPrivateWindow",
+    "AboutCapabilities:DontShowIntroPanelAgain",
+  ],
+
   init() {
-    window.messageManager.addMessageListener(
-      "AboutPrivateBrowsing:OpenPrivateWindow",
-      msg => {
+    let mm = window.messageManager;
+    for (let topic of this._topics) {
+      mm.addMessageListener(topic, this);
+    }
+  },
+
+  uninit() {
+    let mm = window.messageManager;
+    for (let topic of this._topics) {
+      mm.removeMessageListener(topic, this);
+    }
+  },
+
+  receiveMessage(aMsg) {
+    switch (aMsg.name) {
+      case "AboutCapabilities:OpenPrivateWindow":
         OpenBrowserWindow({private: true});
-    });
-    window.messageManager.addMessageListener(
-      "AboutPrivateBrowsing:ToggleTrackingProtection",
-      msg => {
-        const PREF = "privacy.trackingprotection.pbmode.enabled";
-        Services.prefs.setBoolPref(PREF, !Services.prefs.getBoolPref(PREF));
-    });
-    window.messageManager.addMessageListener(
-      "AboutPrivateBrowsing:DontShowIntroPanelAgain",
-      msg => {
+        break;
+
+      case "AboutCapabilities:DontShowIntroPanelAgain":
         TrackingProtection.dontShowIntroPanelAgain();
-    });
-  }
+        break;
+    }
+  },
 };
 
 const SafeBrowsingNotificationBox = {
   _currentURIBaseDomain: null,
   show(title, buttons) {
     let uri = gBrowser.currentURI;
 
     // start tracking host so that we know when we leave the domain
--- a/browser/base/content/tab-content.js
+++ b/browser/base/content/tab-content.js
@@ -204,49 +204,16 @@ var AboutHomeListener = {
     }
     removeMessageListener("AboutHome:Update", this);
     removeEventListener("click", this, true);
     removeEventListener("pagehide", this, true);
   },
 };
 AboutHomeListener.init(this);
 
-var AboutPrivateBrowsingListener = {
-  init(chromeGlobal) {
-    chromeGlobal.addEventListener("AboutPrivateBrowsingOpenWindow", this,
-                                  false, true);
-    chromeGlobal.addEventListener("AboutPrivateBrowsingToggleTrackingProtection", this,
-                                  false, true);
-    chromeGlobal.addEventListener("AboutPrivateBrowsingDontShowIntroPanelAgain", this,
-                                  false, true);
-  },
-
-  get isAboutPrivateBrowsing() {
-    return content.document.documentURI.toLowerCase() == "about:privatebrowsing";
-  },
-
-  handleEvent(aEvent) {
-    if (!this.isAboutPrivateBrowsing) {
-      return;
-    }
-    switch (aEvent.type) {
-      case "AboutPrivateBrowsingOpenWindow":
-        sendAsyncMessage("AboutPrivateBrowsing:OpenPrivateWindow");
-        break;
-      case "AboutPrivateBrowsingToggleTrackingProtection":
-        sendAsyncMessage("AboutPrivateBrowsing:ToggleTrackingProtection");
-        break;
-      case "AboutPrivateBrowsingDontShowIntroPanelAgain":
-        sendAsyncMessage("AboutPrivateBrowsing:DontShowIntroPanelAgain");
-        break;
-    }
-  },
-};
-AboutPrivateBrowsingListener.init(this);
-
 var AboutReaderListener = {
 
   _articlePromise: null,
 
   _isLeavingReaderableReaderMode: false,
 
   init() {
     addEventListener("AboutReaderContentLoaded", this, false, true);
--- a/browser/components/about/AboutRedirector.cpp
+++ b/browser/components/about/AboutRedirector.cpp
@@ -50,16 +50,17 @@ static const RedirEntry kRedirMap[] = {
     nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
     nsIAboutModule::ALLOW_SCRIPT |
     nsIAboutModule::HIDE_FROM_ABOUTABOUT },
   { "feeds", "chrome://browser/content/feeds/subscribe.xhtml",
     nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
     nsIAboutModule::ALLOW_SCRIPT |
     nsIAboutModule::HIDE_FROM_ABOUTABOUT },
   { "privatebrowsing", "chrome://browser/content/aboutPrivateBrowsing.xhtml",
+    nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
     nsIAboutModule::URI_MUST_LOAD_IN_CHILD |
     nsIAboutModule::ALLOW_SCRIPT },
   { "rights",
     "chrome://global/content/aboutRights.xhtml",
     nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
     nsIAboutModule::MAKE_LINKABLE |
     nsIAboutModule::ALLOW_SCRIPT },
   { "robots", "chrome://browser/content/aboutRobots.xhtml",
new file mode 100644
--- /dev/null
+++ b/browser/components/about/aboutcapabilities.manifest
@@ -0,0 +1,2 @@
+component {4c2b1f46-e637-4a91-8108-8a9fb7aab91d} nsAboutCapabilities.js
+contract @mozilla.org/aboutcapabilities;1 {4c2b1f46-e637-4a91-8108-8a9fb7aab91d}
--- a/browser/components/about/moz.build
+++ b/browser/components/about/moz.build
@@ -12,13 +12,18 @@ EXPORTS.mozilla.browser += [
 ]
 
 XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
 
 SOURCES += [
     'AboutRedirector.cpp',
 ]
 
+EXTRA_COMPONENTS += [
+    'aboutcapabilities.manifest',
+    'nsAboutCapabilities.js',
+]
+
 FINAL_LIBRARY = 'browsercomps'
 
 LOCAL_INCLUDES += [
     '../build',
 ]
new file mode 100644
--- /dev/null
+++ b/browser/components/about/nsAboutCapabilities.js
@@ -0,0 +1,74 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+ChromeUtils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+ChromeUtils.defineModuleGetter(this, "AsyncPrefs",
+  "resource://gre/modules/AsyncPrefs.jsm");
+
+function nsAboutCapabilities() {
+}
+
+nsAboutCapabilities.prototype = {
+  init(window) {
+    this.window = window;
+    try {
+      let docShell = window.document.docShell
+                           .QueryInterface(Ci.nsIInterfaceRequestor);
+      this.mm = docShell.getInterface(Ci.nsIContentFrameMessageManager);
+    } catch (e) {
+      Cu.reportError(e);
+    }
+  },
+
+  getBoolPref(aPref, aDefaultValue) {
+    return Services.prefs.getBoolPref(aPref, aDefaultValue);
+  },
+
+  setBoolPref(aPref, aValue) {
+    return new this.window.Promise(function(resolve) {
+      AsyncPrefs.set(aPref, aValue).then(function() {
+        resolve();
+      });
+    });
+  },
+
+  getCharPref(aPref, aDefaultValue) {
+    return Services.prefs.getCharPref(aPref, aDefaultValue);
+  },
+
+  setCharPref(aPref, aValue) {
+    return new this.window.Promise(function(resolve) {
+      AsyncPrefs.set(aPref, aValue).then(function() {
+        resolve();
+      });
+    });
+  },
+
+  getStringFromBundle(aStrBundle, aStr) {
+    let bundle = Services.strings.createBundle(aStrBundle);
+    return bundle.GetStringFromName(aStr);
+  },
+
+  formatURLPref(aFormatURL) {
+    return Services.urlFormatter.formatURLPref(aFormatURL);
+  },
+
+  sendAsyncMessage(aMessage, aParams) {
+    this.mm.sendAsyncMessage("AboutCapabilities:" + aMessage, aParams);
+  },
+
+  isWindowPrivate() {
+    return PrivateBrowsingUtils.isContentWindowPrivate(this.window);
+  },
+
+  contractID: "@mozilla.org/aboutcapabilities;1",
+  classID: Components.ID("{4c2b1f46-e637-4a91-8108-8a9fb7aab91d}"),
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
+                                         Ci.nsIDOMGlobalPropertyInitializer])
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsAboutCapabilities]);
--- a/browser/components/privatebrowsing/content/aboutPrivateBrowsing.js
+++ b/browser/components/privatebrowsing/content/aboutPrivateBrowsing.js
@@ -1,87 +1,64 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-ChromeUtils.import("resource://gre/modules/Services.jsm");
-ChromeUtils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
-ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+const FAVICON_QUESTION = "chrome://global/skin/icons/question-32.png";
+const STRING_BUNDLE = "chrome://browser/locale/aboutPrivateBrowsing.properties";
+const TP_ENABLED_PREF = "privacy.trackingprotection.enabled";
+const TP_PB_ENABLED_PREF = "privacy.trackingprotection.pbmode.enabled";
 
-const FAVICON_QUESTION = "chrome://global/skin/icons/question-32.png";
-
-var stringBundle = Services.strings.createBundle(
-                    "chrome://browser/locale/aboutPrivateBrowsing.properties");
+function updateTPInfo() {
+  let aboutCapabilities = document.aboutCapabilities;
+  let tpButton = document.getElementById("tpButton");
+  let tpToggle = document.getElementById("tpToggle");
+  let title = document.getElementById("title");
+  let titleTracking = document.getElementById("titleTracking");
+  let tpSubHeader = document.getElementById("tpSubHeader");
 
-var prefBranch = Services.prefs.getBranch("privacy.trackingprotection.");
-var prefObserver = {
- QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
-                                        Ci.nsISupportsWeakReference]),
- observe() {
-   let tpSubHeader = document.getElementById("tpSubHeader");
-   let tpToggle = document.getElementById("tpToggle");
-   let tpButton = document.getElementById("tpButton");
-   let title = document.getElementById("title");
-   let titleTracking = document.getElementById("titleTracking");
-   let globalTrackingEnabled = prefBranch.getBoolPref("enabled");
-   let trackingEnabled = globalTrackingEnabled ||
-                         prefBranch.getBoolPref("pbmode.enabled");
+  let globalTrackingEnabled = aboutCapabilities.getBoolPref(TP_ENABLED_PREF, null);
+  let trackingEnabled = globalTrackingEnabled ||
+                        aboutCapabilities.getBoolPref(TP_PB_ENABLED_PREF, null);
 
-   tpButton.classList.toggle("hide", globalTrackingEnabled);
-   tpToggle.checked = trackingEnabled;
-   title.classList.toggle("hide", trackingEnabled);
-   titleTracking.classList.toggle("hide", !trackingEnabled);
-   tpSubHeader.classList.toggle("tp-off", !trackingEnabled);
- }
-};
-prefBranch.addObserver("pbmode.enabled", prefObserver, true);
-prefBranch.addObserver("enabled", prefObserver, true);
+  // if tracking protection is enabled globally we don't even give the user
+  // a choice here by hiding the toggle completely.
+  tpButton.classList.toggle("hide", globalTrackingEnabled);
+  tpToggle.checked = trackingEnabled;
+  title.classList.toggle("hide", trackingEnabled);
+  titleTracking.classList.toggle("hide", !trackingEnabled);
+  tpSubHeader.classList.toggle("tp-off", !trackingEnabled);
+}
 
 document.addEventListener("DOMContentLoaded", function() {
- if (!PrivateBrowsingUtils.isContentWindowPrivate(window)) {
-   document.documentElement.classList.remove("private");
-   document.documentElement.classList.add("normal");
-   document.title = stringBundle.GetStringFromName("title.normal");
-   document.getElementById("favicon")
-           .setAttribute("href", FAVICON_QUESTION);
-   document.getElementById("startPrivateBrowsing")
-           .addEventListener("command", openPrivateWindow);
-   return;
- }
-
- let tpToggle = document.getElementById("tpToggle");
- document.getElementById("tpButton").addEventListener("click", () => {
-   tpToggle.click();
- });
-
- document.title = stringBundle.GetStringFromName("title.head");
- tpToggle.addEventListener("change", toggleTrackingProtection);
- document.getElementById("startTour")
-         .addEventListener("click", dontShowIntroPanelAgain);
+  let aboutCapabilities = document.aboutCapabilities;
+  if (!aboutCapabilities.isWindowPrivate()) {
+    document.documentElement.classList.remove("private");
+    document.documentElement.classList.add("normal");
+    document.title = aboutCapabilities.getStringFromBundle(STRING_BUNDLE, "title.normal");
+    document.getElementById("favicon").setAttribute("href", FAVICON_QUESTION);
+    document.getElementById("startPrivateBrowsing").addEventListener("click", function() {
+      aboutCapabilities.sendAsyncMessage("OpenPrivateWindow", null);
+    });
+    return;
+  }
 
- document.getElementById("startTour").setAttribute("href",
-   Services.urlFormatter.formatURLPref("privacy.trackingprotection.introURL"));
- document.getElementById("learnMore").setAttribute("href",
-   Services.urlFormatter.formatURLPref("app.support.baseURL") + "private-browsing");
-
- // Update state that depends on preferences.
- prefObserver.observe();
-});
+  document.title = aboutCapabilities.getStringFromBundle(STRING_BUNDLE, "title.head");
+  document.getElementById("startTour").addEventListener("click", function() {
+    aboutCapabilities.sendAsyncMessage("DontShowIntroPanelAgain", null);
+  });
+  document.getElementById("startTour").setAttribute("href",
+    aboutCapabilities.formatURLPref("privacy.trackingprotection.introURL"));
+  document.getElementById("learnMore").setAttribute("href",
+    aboutCapabilities.formatURLPref("app.support.baseURL") + "private-browsing");
 
-function openPrivateWindow() {
- // Ask chrome to open a private window
- document.dispatchEvent(
-   new CustomEvent("AboutPrivateBrowsingOpenWindow", {bubbles: true}));
-}
+  let tpToggle = document.getElementById("tpToggle");
+  document.getElementById("tpButton").addEventListener("click", () => {
+    tpToggle.click();
+  });
+  tpToggle.addEventListener("change", function() {
+    aboutCapabilities.setBoolPref(TP_PB_ENABLED_PREF, tpToggle.checked).then(function() {
+      updateTPInfo();
+    });
+  });
 
-function toggleTrackingProtection() {
- // Ask chrome to enable tracking protection
- document.dispatchEvent(
-   new CustomEvent("AboutPrivateBrowsingToggleTrackingProtection",
-                   {bubbles: true}));
-}
-
-function dontShowIntroPanelAgain() {
- // Ask chrome to disable the doorhanger
- document.dispatchEvent(
-   new CustomEvent("AboutPrivateBrowsingDontShowIntroPanelAgain",
-                   {bubbles: true}));
-}
+  updateTPInfo();
+});
--- a/browser/components/privatebrowsing/content/aboutPrivateBrowsing.xhtml
+++ b/browser/components/privatebrowsing/content/aboutPrivateBrowsing.xhtml
@@ -14,29 +14,28 @@
   <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
   %browserDTD;
   <!ENTITY % aboutPrivateBrowsingDTD SYSTEM "chrome://browser/locale/aboutPrivateBrowsing.dtd">
   %aboutPrivateBrowsingDTD;
 ]>
 
 <html xmlns="http://www.w3.org/1999/xhtml" class="private">
   <head>
+    <meta http-equiv="Content-Security-Policy" content="default-src chrome:"/>
     <link id="favicon" rel="icon" type="image/png" href="chrome://browser/skin/privatebrowsing/favicon.svg"/>
     <link rel="stylesheet" href="chrome://browser/content/aboutPrivateBrowsing.css" type="text/css" media="all"/>
     <link rel="stylesheet" href="chrome://browser/skin/privatebrowsing/aboutPrivateBrowsing.css" type="text/css" media="all"/>
     <script type="application/javascript" src="chrome://browser/content/aboutPrivateBrowsing.js"></script>
   </head>
 
   <body dir="&locale.dir;">
     <p class="showNormal">&aboutPrivateBrowsing.notPrivate;</p>
-    <button xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-            id="startPrivateBrowsing"
+    <button id="startPrivateBrowsing"
             class="showNormal"
-            label="&privatebrowsingpage.openPrivateWindow.label;"
-            accesskey="&privatebrowsingpage.openPrivateWindow.accesskey;"/>
+            accesskey="&privatebrowsingpage.openPrivateWindow.accesskey;">&privatebrowsingpage.openPrivateWindow.label;</button>
     <div class="showPrivate container">
       <h1 class="title">
         <span id="title">&privateBrowsing.title;</span>
         <span id="titleTracking">&privateBrowsing.title.tracking;</span>
       </h1>
       <section class="section-main">
         <p>&aboutPrivateBrowsing.info.notsaved.before;<strong>&aboutPrivateBrowsing.info.notsaved.emphasize;</strong>&aboutPrivateBrowsing.info.notsaved.after;</p>
         <div class="list-row">
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -365,16 +365,18 @@
 @RESPATH@/browser/components/devtools-startup.manifest
 @RESPATH@/browser/components/devtools-startup.js
 @RESPATH@/browser/components/aboutdebugging-registration.js
 @RESPATH@/browser/components/aboutdebugging.manifest
 @RESPATH@/browser/components/aboutdevtools-registration.js
 @RESPATH@/browser/components/aboutdevtools.manifest
 @RESPATH@/browser/components/aboutdevtoolstoolbox-registration.js
 @RESPATH@/browser/components/aboutdevtoolstoolbox.manifest
+@RESPATH@/browser/components/nsAboutCapabilities.js
+@RESPATH@/browser/components/aboutcapabilities.manifest
 @RESPATH@/browser/components/Experiments.manifest
 @RESPATH@/browser/components/ExperimentsService.js
 @RESPATH@/browser/components/browser-newtab.xpt
 @RESPATH@/browser/components/aboutNewTabService.js
 @RESPATH@/browser/components/NewTabComponents.manifest
 @RESPATH@/browser/components/EnterprisePolicies.js
 @RESPATH@/browser/components/EnterprisePoliciesContent.js
 @RESPATH@/browser/components/EnterprisePolicies.manifest
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -47,16 +47,17 @@
 #include "nsIDocShellTreeItem.h"
 #include "nsCOMArray.h"
 #include "nsQueryObject.h"
 #include "nsDOMClassInfo.h"
 #include "mozilla/Services.h"
 #include "nsScreen.h"
 #include "ChildIterator.h"
 
+#include "mozilla/dom/AboutCapabilitiesBinding.h"
 #include "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/BasicEvents.h"
 #include "mozilla/EventListenerManager.h"
 #include "mozilla/EventStateManager.h"
 
 #include "mozilla/dom/Attr.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/Element.h"
@@ -1917,16 +1918,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
     cb.NoteXPCOMChild(tmp->mChildren.ChildAt(indx - 1));
   }
 
   // Traverse all nsIDocument pointer members.
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSecurityInfo)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDisplayDocument)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFontFaceSet)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReadyForIdle)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAboutCapabilities)
 
   // Traverse all nsDocument nsCOMPtrs.
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParser)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptGlobalObject)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMStyleSheets)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheetSetList)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptLoader)
@@ -2059,16 +2061,17 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedEncoder)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentTimeline)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingAnimationTracker)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mTemplateContentsOwner)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildrenCollection)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mOrientationPendingPromise)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mFontFaceSet)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mReadyForIdle);
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mAboutCapabilities)
 
   tmp->mParentDocument = nullptr;
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadingImages)
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mIntersectionObservers)
 
   tmp->ClearAllBoxObjects();
@@ -3387,16 +3390,72 @@ nsDocument::GetAllowPlugins()
   if (classification == FlashClassification::Denied) {
     return false;
   }
 
   return true;
 }
 
 bool
+nsDocument::CallerIsTrustedAboutPage(JSContext* aCx, JSObject* aObject)
+{
+  /*
+   * If you want an about: page to have access to AboutCapabilities,
+   * please add it to the list of trusted about: pages underneath.
+   */
+  static const char* kTrustedAboutPages[] = {
+    "about:privatebrowsing",
+  };
+
+  nsIPrincipal* principal = nsContentUtils::SubjectPrincipal(aCx);
+  if (!principal) {
+    return false;
+  }
+  nsCOMPtr<nsIURI> uri;
+  principal->GetURI(getter_AddRefs(uri));
+  if (!uri) {
+    return false;
+  }
+  // getSpec is an expensive operation, hence we first check the scheme
+  // to see if the caller is actually an about: page.
+  bool isAboutScheme = false;
+  uri->SchemeIs("about", &isAboutScheme);
+  if (!isAboutScheme) {
+    return false;
+  }
+
+  nsAutoCString aboutSpec;
+  uri->GetSpec(aboutSpec);
+  for (auto & aboutPageEntry : kTrustedAboutPages) {
+    if (aboutSpec.EqualsIgnoreCase(aboutPageEntry)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+already_AddRefed<AboutCapabilities>
+nsIDocument::GetAboutCapabilities(ErrorResult& aRv)
+{
+  if (!mAboutCapabilities) {
+    AutoJSContext cx;
+    JS::Rooted<JSObject*> jsImplObj(cx);
+    nsIGlobalObject* sgo = GetScopeObject();
+    ConstructJSImplementation("@mozilla.org/aboutcapabilities;1", sgo, &jsImplObj, aRv);
+    if (aRv.Failed()) {
+      return nullptr;
+    }
+    mAboutCapabilities = new AboutCapabilities(jsImplObj, sgo);
+  }
+  RefPtr<AboutCapabilities> aboutCapabilities =
+    static_cast<AboutCapabilities*>(mAboutCapabilities.get());
+  return aboutCapabilities.forget();
+}
+
+bool
 nsDocument::IsElementAnimateEnabled(JSContext* aCx, JSObject* /*unused*/)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   return nsContentUtils::IsSystemCaller(aCx) ||
          nsContentUtils::AnimationsAPICoreEnabled() ||
          nsContentUtils::AnimationsAPIElementAnimateEnabled();
 }
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -413,16 +413,17 @@ public:
   already_AddRefed<nsIPresShell> CreateShell(nsPresContext* aContext,
                                              nsViewManager* aViewManager,
                                              mozilla::StyleSetHandle aStyleSet)
     final;
   virtual void DeleteShell() override;
 
   virtual bool GetAllowPlugins() override;
 
+  static bool CallerIsTrustedAboutPage(JSContext* aCx, JSObject* aObject);
   static bool IsElementAnimateEnabled(JSContext* aCx, JSObject* aObject);
   static bool IsWebAnimationsEnabled(JSContext* aCx, JSObject* aObject);
   static bool IsWebAnimationsEnabled(mozilla::dom::CallerType aCallerType);
   virtual mozilla::dom::DocumentTimeline* Timeline() override;
   virtual void GetAnimations(
       nsTArray<RefPtr<mozilla::dom::Animation>>& aAnimations) override;
   mozilla::LinkedList<mozilla::dom::DocumentTimeline>& Timelines() override
   {
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -124,16 +124,17 @@ struct URLExtraData;
 
 namespace css {
 class Loader;
 class ImageLoader;
 class Rule;
 } // namespace css
 
 namespace dom {
+class AboutCapabilities;
 class Animation;
 class AnonymousContent;
 class Attr;
 class BoxObject;
 class ClientInfo;
 class ClientState;
 class CDATASection;
 class Comment;
@@ -2862,16 +2863,20 @@ public:
                       mozilla::ErrorResult& rv);
   void SetAllowUnsafeHTML(bool aAllow) { mAllowUnsafeHTML = aAllow; }
   bool AllowUnsafeHTML() const;
   void GetInputEncoding(nsAString& aInputEncoding) const;
   already_AddRefed<mozilla::dom::Location> GetLocation() const;
   void GetReferrer(nsAString& aReferrer) const;
   void GetLastModified(nsAString& aLastModified) const;
   void GetReadyState(nsAString& aReadyState) const;
+
+  already_AddRefed<mozilla::dom::AboutCapabilities> GetAboutCapabilities(
+    ErrorResult& aRv);
+
   virtual void GetTitle(nsAString& aTitle) = 0;
   virtual void SetTitle(const nsAString& aTitle, mozilla::ErrorResult& rv) = 0;
   void GetDir(nsAString& aDirection) const;
   void SetDir(const nsAString& aDirection);
   already_AddRefed<nsContentList> GetElementsByName(const nsAString& aName)
   {
     return GetFuncStringContentList<nsCachableElementsByNameNodeList>(this,
                                                                       MatchNameAttribute,
@@ -3368,16 +3373,18 @@ protected:
   // Last time this document or a one of its sub-documents was focused.  If
   // focus has never occurred then mLastFocusTime.IsNull() will be true.
   mozilla::TimeStamp mLastFocusTime;
 
   mozilla::EventStates mDocumentState;
 
   RefPtr<mozilla::dom::Promise> mReadyForIdle;
 
+  RefPtr<mozilla::dom::AboutCapabilities> mAboutCapabilities;
+
   // True if BIDI is enabled.
   bool mBidiEnabled : 1;
   // True if a MathML element has ever been owned by this document.
   bool mMathMLEnabled : 1;
 
   // True if this document is the initial document for a window.  This should
   // basically be true only for documents that exist in newly-opened windows or
   // documents created to satisfy a GetDocument() on a window when there's no
new file mode 100644
--- /dev/null
+++ b/dom/webidl/AboutCapabilities.webidl
@@ -0,0 +1,32 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/.
+ */
+
+[NoInterfaceObject, JSImplementation="@mozilla.org/aboutcapabilities;1"]
+interface AboutCapabilities
+{
+  /**
+   * When using setBoolPref, one also has to whitelist the
+   * pref to be updated within AsyncPrefs.jsm
+   */
+  Promise<void> setBoolPref(DOMString aPref, boolean aValue);
+  boolean getBoolPref(DOMString aPref, boolean? aDefaultValue);
+
+  /**
+   * When using setCharPref, one also has to whitelist the
+   * pref to be updated within AsyncPrefs.jsm
+   */
+  Promise<void> setCharPref(DOMString aPref, DOMString aValue);
+  DOMString getCharPref(DOMString aPref, DOMString? aDefaultValue);
+
+  DOMString getStringFromBundle(DOMString aStrBundle, DOMString aStr);
+
+  DOMString formatURLPref(DOMString aFormatURL);
+
+  void sendAsyncMessage(DOMString aMessage, object? aParams);
+
+  /* about:privatebrowsing */
+  boolean isWindowPrivate();
+};
--- a/dom/webidl/Document.webidl
+++ b/dom/webidl/Document.webidl
@@ -472,16 +472,21 @@ enum FlashClassification {
   "allowed",        // Site is on the Flash whitelist
   "denied"          // Site is on the Flash blacklist
 };
 partial interface Document {
   [ChromeOnly]
   readonly attribute FlashClassification documentFlashClassification;
 };
 
+// Allows about: pages to query aboutCapabilities
+partial interface Document {
+  [Throws, Func="nsDocument::CallerIsTrustedAboutPage"] readonly attribute AboutCapabilities aboutCapabilities;
+};
+
 Document implements XPathEvaluator;
 Document implements GlobalEventHandlers;
 Document implements DocumentAndElementEventHandlers;
 Document implements TouchEventHandlers;
 Document implements ParentNode;
 Document implements OnErrorEventHandlerForNodes;
 Document implements GeometryUtils;
 Document implements FontFaceSource;
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -363,16 +363,17 @@ PREPROCESSED_WEBIDL_FILES = [
     'ChromeUtils.webidl',
     'Node.webidl',
     'Window.webidl',
 ]
 
 WEBIDL_FILES = [
     'AbortController.webidl',
     'AbortSignal.webidl',
+    'AboutCapabilities.webidl',
     'AbstractWorker.webidl',
     'AddonManager.webidl',
     'AnalyserNode.webidl',
     'Animatable.webidl',
     'Animation.webidl',
     'AnimationEffectReadOnly.webidl',
     'AnimationEffectTiming.webidl',
     'AnimationEffectTimingReadOnly.webidl',
--- a/toolkit/modules/AsyncPrefs.jsm
+++ b/toolkit/modules/AsyncPrefs.jsm
@@ -15,16 +15,18 @@ const kAllowedPrefs = new Set([
   // anything.
   "testing.allowed-prefs.some-bool-pref",
   "testing.allowed-prefs.some-char-pref",
   "testing.allowed-prefs.some-int-pref",
 
   "narrate.rate",
   "narrate.voice",
 
+  "privacy.trackingprotection.pbmode.enabled",
+
   "reader.font_size",
   "reader.font_type",
   "reader.color_scheme",
   "reader.content_width",
   "reader.line_height",
 ]);
 
 const kPrefTypeMap = new Map([