Merge inbound to mozilla-central. a=merge
authorOana Pop Rus <opoprus@mozilla.com>
Wed, 06 Feb 2019 11:53:48 +0200
changeset 457381 e697db5f161b25cdaa4ceb43e918ada09e997acb
parent 457380 2853480ed90d0a995593e66fcd68a8bff1ad8d96 (current diff)
parent 457322 e38316ca21d4acc9f4377a93778ae6586d9b3e38 (diff)
child 457382 1fef2c79b6de13278223af95977d9b9468c59a5c
child 457410 d365f8c69b081ab8b96d94d0307e7fd262dfd4fa
push id111717
push useropoprus@mozilla.com
push dateWed, 06 Feb 2019 10:10:24 +0000
treeherdermozilla-inbound@1fef2c79b6de [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone67.0a1
first release with
nightly linux32
e697db5f161b / 67.0a1 / 20190206095431 / files
nightly linux64
e697db5f161b / 67.0a1 / 20190206095431 / files
nightly mac
e697db5f161b / 67.0a1 / 20190206095431 / files
nightly win32
e697db5f161b / 67.0a1 / 20190206095431 / files
nightly win64
e697db5f161b / 67.0a1 / 20190206095431 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
gfx/layers/wr/StackingContextHelper.cpp
gfx/webrender_bindings/WebRenderAPI.h
layout/painting/nsDisplayList.cpp
toolkit/mozapps/extensions/test/xpcshell/data/BootstrapMonitor.jsm
toolkit/mozapps/extensions/test/xpcshell/data/from_sources/bootstrap.js
toolkit/mozapps/extensions/test/xpcshell/data/from_sources/install.rdf
--- a/devtools/server/tests/mochitest/webextension-helpers.js
+++ b/devtools/server/tests/mochitest/webextension-helpers.js
@@ -1,27 +1,24 @@
 /* exported attachAddon, setWebExtensionOOPMode, waitForFramesUpdated, reloadAddon,
-            collectFrameUpdates, generateWebExtensionXPI, promiseInstallFile,
-            promiseAddonByID, promiseWebExtensionStartup, promiseWebExtensionShutdown
+   collectFrameUpdates, generateWebExtensionXPI, promiseInstallFile,
+   promiseWebExtensionStartup, promiseWebExtensionShutdown
  */
 
 "use strict";
 
 const {require, loader} = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
 const {DebuggerClient} = require("devtools/shared/client/debugger-client");
 const {DebuggerServer} = require("devtools/server/main");
 const {TargetFactory} = require("devtools/client/framework/target");
 
-const {AddonManager} = require("resource://gre/modules/AddonManager.jsm");
-const {Extension, Management} = require("resource://gre/modules/Extension.jsm");
-const {flushJarCache} = require("resource://gre/modules/ExtensionUtils.jsm");
-const {Services} = require("resource://gre/modules/Services.jsm");
+const {AddonTestUtils} = require("resource://testing-common/AddonTestUtils.jsm");
+const {ExtensionTestCommon} = require("resource://testing-common/ExtensionTestCommon.jsm");
 
 loader.lazyImporter(this, "ExtensionParent", "resource://gre/modules/ExtensionParent.jsm");
-loader.lazyImporter(this, "OS", "resource://gre/modules/osfile.jsm");
 
 // Initialize a minimal DebuggerServer and connect to the webextension addon actor.
 if (!DebuggerServer.initialized) {
   DebuggerServer.init();
   DebuggerServer.registerAllActors();
   SimpleTest.registerCleanupFunction(function() {
     DebuggerServer.destroy();
   });
@@ -124,83 +121,16 @@ async function reloadAddon({client}, add
   }
 
   await addonTargetFront.reload();
 }
 
 // Test helpers related to the AddonManager.
 
 function generateWebExtensionXPI(extDetails) {
-  const addonFile = Extension.generateXPI(extDetails);
-
-  flushJarCache(addonFile.path);
-  Services.ppmm.broadcastAsyncMessage("Extension:FlushJarCache",
-                                      {path: addonFile.path});
-
-  // Remove the file on cleanup if needed.
-  SimpleTest.registerCleanupFunction(() => {
-    flushJarCache(addonFile.path);
-    Services.ppmm.broadcastAsyncMessage("Extension:FlushJarCache",
-                                        {path: addonFile.path});
-
-    if (addonFile.exists()) {
-      OS.File.remove(addonFile.path);
-    }
-  });
-
-  return addonFile;
+  return ExtensionTestCommon.generateXPI(extDetails);
 }
 
-function promiseCompleteInstall(install) {
-  let listener;
-  return new Promise((resolve, reject) => {
-    listener = {
-      onDownloadFailed: reject,
-      onDownloadCancelled: reject,
-      onInstallFailed: reject,
-      onInstallCancelled: reject,
-      onInstallEnded: resolve,
-      onInstallPostponed: reject,
-    };
-
-    install.addListener(listener);
-    install.install();
-  }).then(() => {
-    install.removeListener(listener);
-    return install;
-  });
-}
-
-function promiseInstallFile(file) {
-  return AddonManager.getInstallForFile(file).then(install => {
-    if (!install) {
-      throw new Error(`No AddonInstall created for ${file.path}`);
-    }
-
-    if (install.state != AddonManager.STATE_DOWNLOADED) {
-      throw new Error(`Expected file to be downloaded for install of ${file.path}`);
-    }
-
-    return promiseCompleteInstall(install);
-  });
-}
-
-function promiseWebExtensionStartup() {
-  return new Promise(resolve => {
-    const listener = (evt, extension) => {
-      Management.off("ready", listener);
-      resolve(extension);
-    };
-
-    Management.on("ready", listener);
-  });
-}
-
-function promiseWebExtensionShutdown() {
-  return new Promise(resolve => {
-    const listener = (event, extension) => {
-      Management.off("shutdown", listener);
-      resolve(extension);
-    };
-
-    Management.on("shutdown", listener);
-  });
-}
+let {
+  promiseInstallFile,
+  promiseWebExtensionStartup,
+  promiseWebExtensionShutdown,
+} = AddonTestUtils;
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -292,16 +292,17 @@
 #include "mozilla/ClearOnShutdown.h"
 #include "nsHTMLTags.h"
 #include "NodeUbiReporting.h"
 #include "nsICookieService.h"
 #include "mozilla/net/ChannelEventQueue.h"
 #include "mozilla/net/RequestContextService.h"
 #include "StorageAccessPermissionRequest.h"
 #include "mozilla/dom/WindowProxyHolder.h"
+#include "ThirdPartyUtil.h"
 
 #define XML_DECLARATION_BITS_DECLARATION_EXISTS (1 << 0)
 #define XML_DECLARATION_BITS_ENCODING_EXISTS (1 << 1)
 #define XML_DECLARATION_BITS_STANDALONE_EXISTS (1 << 2)
 #define XML_DECLARATION_BITS_STANDALONE_YES (1 << 3)
 
 extern bool sDisablePrefetchHTTPSPref;
 
@@ -2849,16 +2850,23 @@ void Document::SetDocumentURI(nsIURI* aU
   if (!mOriginalURI) mOriginalURI = mDocumentURI;
 
   // If changing the document's URI changed the base URI of the document, we
   // need to refresh the hrefs of all the links on the page.
   if (!equalBases) {
     RefreshLinkHrefs();
   }
 
+  // Recalculate our base domain
+  mBaseDomain.Truncate();
+  ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
+  if (thirdPartyUtil) {
+    Unused << thirdPartyUtil->GetBaseDomain(mDocumentURI, mBaseDomain);
+  }
+
   // Tell our WindowGlobalParent that the document's URI has been changed.
   nsPIDOMWindowInner* inner = GetInnerWindow();
   WindowGlobalChild* wgc = inner ? inner->GetWindowGlobalChild() : nullptr;
   if (wgc) {
     Unused << wgc->SendUpdateDocumentURI(mDocumentURI);
   }
 }
 
--- a/dom/base/Document.h
+++ b/dom/base/Document.h
@@ -639,16 +639,23 @@ class Document : public nsINode,
    * This method corresponds to the "creation URL" in HTML5 and, once set,
    * doesn't change over the lifetime of the document.
    *
    * https://html.spec.whatwg.org/multipage/webappapis.html#creation-url
    */
   nsIURI* GetOriginalURI() const { return mOriginalURI; }
 
   /**
+   * Return the base domain of the document.  This has been computed using
+   * mozIThirdPartyUtil::GetBaseDomain() and can be used for third-party
+   * checks.  When the URI of the document changes, this value is recomputed.
+   */
+  nsCString GetBaseDomain() const { return mBaseDomain; }
+
+  /**
    * Set the URI for the document.  This also sets the document's original URI,
    * if it's null.
    */
   void SetDocumentURI(nsIURI* aURI);
 
   /**
    * Set the URI for the document loaded via XHR, when accessed from
    * chrome privileged script.
@@ -3815,16 +3822,19 @@ class Document : public nsINode,
   nsString mLastModified;
 
   nsCOMPtr<nsIURI> mDocumentURI;
   nsCOMPtr<nsIURI> mOriginalURI;
   nsCOMPtr<nsIURI> mChromeXHRDocURI;
   nsCOMPtr<nsIURI> mDocumentBaseURI;
   nsCOMPtr<nsIURI> mChromeXHRDocBaseURI;
 
+  // The base domain of the document for third-party checks.
+  nsCString mBaseDomain;
+
   // A lazily-constructed URL data for style system to resolve URL value.
   RefPtr<mozilla::URLExtraData> mCachedURLData;
 
   nsWeakPtr mDocumentLoadGroup;
 
   bool mReferrerPolicySet;
   ReferrerPolicyEnum mReferrerPolicy;
 
--- a/dom/base/ThirdPartyUtil.cpp
+++ b/dom/base/ThirdPartyUtil.cpp
@@ -11,57 +11,80 @@
 #include "nsIServiceManager.h"
 #include "nsIHttpChannelInternal.h"
 #include "nsIDOMWindow.h"
 #include "nsILoadContext.h"
 #include "nsIPrincipal.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "nsIURI.h"
 #include "nsThreadUtils.h"
+#include "mozilla/ClearOnShutdown.h"
 #include "mozilla/Logging.h"
+#include "mozilla/StaticPtr.h"
 #include "mozilla/Unused.h"
 #include "nsPIDOMWindow.h"
 
 NS_IMPL_ISUPPORTS(ThirdPartyUtil, mozIThirdPartyUtil)
 
 //
 // MOZ_LOG=thirdPartyUtil:5
 //
 static mozilla::LazyLogModule gThirdPartyLog("thirdPartyUtil");
 #undef LOG
 #define LOG(args) MOZ_LOG(gThirdPartyLog, mozilla::LogLevel::Debug, args)
 
+static mozilla::StaticRefPtr<ThirdPartyUtil> gService;
+
 nsresult ThirdPartyUtil::Init() {
   NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_AVAILABLE);
 
-  nsresult rv;
-  mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv);
+  MOZ_ASSERT(!gService);
+  gService = this;
+  mozilla::ClearOnShutdown(&gService);
+
+  mTLDService = nsEffectiveTLDService::GetInstance();
+  return mTLDService ? NS_OK : NS_ERROR_FAILURE;
+}
+
+ThirdPartyUtil::~ThirdPartyUtil() { gService = nullptr; }
 
-  return rv;
+// static
+ThirdPartyUtil* ThirdPartyUtil::GetInstance() {
+  if (gService) {
+    return gService;
+  }
+  nsCOMPtr<mozIThirdPartyUtil> tpuService =
+      mozilla::services::GetThirdPartyUtil();
+  if (!tpuService) {
+    return nullptr;
+  }
+  MOZ_ASSERT(
+      gService,
+      "gService must have been initialized in nsEffectiveTLDService::Init");
+  return gService;
 }
 
 // Determine if aFirstDomain is a different base domain to aSecondURI; or, if
 // the concept of base domain does not apply, determine if the two hosts are not
 // string-identical.
 nsresult ThirdPartyUtil::IsThirdPartyInternal(const nsCString& aFirstDomain,
                                               nsIURI* aSecondURI,
                                               bool* aResult) {
   if (!aSecondURI) {
     return NS_ERROR_INVALID_ARG;
   }
 
   // Get the base domain for aSecondURI.
-  nsCString secondDomain;
+  nsAutoCString secondDomain;
   nsresult rv = GetBaseDomain(aSecondURI, secondDomain);
   LOG(("ThirdPartyUtil::IsThirdPartyInternal %s =? %s", aFirstDomain.get(),
        secondDomain.get()));
   if (NS_FAILED(rv)) return rv;
 
-  // Check strict equality.
-  *aResult = aFirstDomain != secondDomain;
+  *aResult = IsThirdPartyInternal(aFirstDomain, secondDomain);
   return NS_OK;
 }
 
 // Get the URI associated with a window.
 NS_IMETHODIMP
 ThirdPartyUtil::GetURIFromWindow(mozIDOMWindowProxy* aWin, nsIURI** result) {
   nsresult rv;
   nsCOMPtr<nsIScriptObjectPrincipal> scriptObjPrin = do_QueryInterface(aWin);
@@ -87,80 +110,79 @@ ThirdPartyUtil::GetURIFromWindow(mozIDOM
 // for mozIThirdPartyUtil.
 NS_IMETHODIMP
 ThirdPartyUtil::IsThirdPartyURI(nsIURI* aFirstURI, nsIURI* aSecondURI,
                                 bool* aResult) {
   NS_ENSURE_ARG(aFirstURI);
   NS_ENSURE_ARG(aSecondURI);
   NS_ASSERTION(aResult, "null outparam pointer");
 
-  nsCString firstHost;
+  nsAutoCString firstHost;
   nsresult rv = GetBaseDomain(aFirstURI, firstHost);
   if (NS_FAILED(rv)) return rv;
 
   return IsThirdPartyInternal(firstHost, aSecondURI, aResult);
 }
 
 // Determine if any URI of the window hierarchy of aWindow is foreign with
 // respect to aSecondURI. See docs for mozIThirdPartyUtil.
 NS_IMETHODIMP
 ThirdPartyUtil::IsThirdPartyWindow(mozIDOMWindowProxy* aWindow, nsIURI* aURI,
                                    bool* aResult) {
   NS_ENSURE_ARG(aWindow);
   NS_ASSERTION(aResult, "null outparam pointer");
 
   bool result;
 
-  // Get the URI of the window, and its base domain.
-  nsresult rv;
-  nsCOMPtr<nsIURI> currentURI;
-  rv = GetURIFromWindow(aWindow, getter_AddRefs(currentURI));
-  if (NS_FAILED(rv)) return rv;
-
-  nsCString bottomDomain;
-  rv = GetBaseDomain(currentURI, bottomDomain);
-  if (NS_FAILED(rv)) return rv;
+  nsCString bottomDomain =
+      GetBaseDomainFromWindow(nsPIDOMWindowOuter::From(aWindow));
+  if (bottomDomain.IsEmpty()) {
+    return NS_ERROR_FAILURE;
+  }
 
   if (aURI) {
     // Determine whether aURI is foreign with respect to currentURI.
-    rv = IsThirdPartyInternal(bottomDomain, aURI, &result);
+    nsresult rv = IsThirdPartyInternal(bottomDomain, aURI, &result);
     if (NS_FAILED(rv)) return rv;
 
     if (result) {
       *aResult = true;
       return NS_OK;
     }
   }
 
-  nsCOMPtr<nsPIDOMWindowOuter> current = nsPIDOMWindowOuter::From(aWindow),
-                               parent;
-  nsCOMPtr<nsIURI> parentURI;
+  nsPIDOMWindowOuter* current = nsPIDOMWindowOuter::From(aWindow);
   do {
     // We use GetScriptableParent rather than GetParent because we consider
     // <iframe mozbrowser> to be a top-level frame.
-    parent = current->GetScriptableParent();
-    if (SameCOMIdentity(parent, current)) {
+    nsPIDOMWindowOuter* parent = current->GetScriptableParent();
+    // We don't use SameCOMIdentity here since we know that nsPIDOMWindowOuter
+    // is only implemented by nsGlobalWindowOuter, so different objects of that
+    // type will not have different nsISupports COM identities, and checking the
+    // actual COM identity using SameCOMIdentity is expensive due to the virtual
+    // calls involved.
+    if (parent == current) {
       // We're at the topmost content window. We already know the answer.
       *aResult = false;
       return NS_OK;
     }
 
-    rv = GetURIFromWindow(parent, getter_AddRefs(parentURI));
-    NS_ENSURE_SUCCESS(rv, rv);
+    nsCString parentDomain = GetBaseDomainFromWindow(parent);
+    if (parentDomain.IsEmpty()) {
+      return NS_ERROR_FAILURE;
+    }
 
-    rv = IsThirdPartyInternal(bottomDomain, parentURI, &result);
-    if (NS_FAILED(rv)) return rv;
+    result = IsThirdPartyInternal(bottomDomain, parentDomain);
 
     if (result) {
       *aResult = true;
       return NS_OK;
     }
 
     current = parent;
-    currentURI = parentURI;
   } while (1);
 
   MOZ_ASSERT_UNREACHABLE("should've returned");
   return NS_ERROR_UNEXPECTED;
 }
 
 // Determine if the URI associated with aChannel or any URI of the window
 // hierarchy associated with the channel is foreign with respect to aSecondURI.
@@ -196,17 +218,17 @@ ThirdPartyUtil::IsThirdPartyChannel(nsIC
 
   bool parentIsThird = false;
 
   // Obtain the URI from the channel, and its base domain.
   nsCOMPtr<nsIURI> channelURI;
   rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI));
   if (NS_FAILED(rv)) return rv;
 
-  nsCString channelDomain;
+  nsAutoCString channelDomain;
   rv = GetBaseDomain(channelURI, channelDomain);
   if (NS_FAILED(rv)) return rv;
 
   if (!doForce) {
     if (nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo()) {
       parentIsThird = loadInfo->GetIsInThirdPartyContext();
       if (!parentIsThird && loadInfo->GetExternalContentPolicyType() !=
                                 nsIContentPolicy::TYPE_DOCUMENT) {
--- a/dom/base/ThirdPartyUtil.h
+++ b/dom/base/ThirdPartyUtil.h
@@ -5,30 +5,49 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef ThirdPartyUtil_h__
 #define ThirdPartyUtil_h__
 
 #include "nsCOMPtr.h"
 #include "nsString.h"
 #include "mozIThirdPartyUtil.h"
-#include "nsIEffectiveTLDService.h"
+#include "nsEffectiveTLDService.h"
 #include "mozilla/Attributes.h"
 
 class nsIURI;
 
 class ThirdPartyUtil final : public mozIThirdPartyUtil {
  public:
   NS_DECL_ISUPPORTS
   NS_DECL_MOZITHIRDPARTYUTIL
 
   nsresult Init();
 
+  static ThirdPartyUtil* GetInstance();
+
  private:
-  ~ThirdPartyUtil() {}
+  ~ThirdPartyUtil();
 
+  bool IsThirdPartyInternal(const nsCString& aFirstDomain,
+                            const nsCString& aSecondDomain) {
+    // Check strict equality.
+    return aFirstDomain != aSecondDomain;
+  }
   nsresult IsThirdPartyInternal(const nsCString& aFirstDomain,
                                 nsIURI* aSecondURI, bool* aResult);
 
-  nsCOMPtr<nsIEffectiveTLDService> mTLDService;
+  nsCString GetBaseDomainFromWindow(nsPIDOMWindowOuter* aWindow) {
+    MOZ_ASSERT(aWindow);
+
+    mozilla::dom::Document* doc = aWindow->GetExtantDoc();
+
+    if (!doc) {
+      return EmptyCString();
+    }
+
+    return doc->GetBaseDomain();
+  }
+
+  RefPtr<nsEffectiveTLDService> mTLDService;
 };
 
 #endif
--- a/dom/base/Timeout.cpp
+++ b/dom/base/Timeout.cpp
@@ -10,16 +10,19 @@
 #include "nsGlobalWindowInner.h"
 
 namespace mozilla {
 namespace dom {
 
 Timeout::Timeout()
     : mTimeoutId(0),
       mFiringId(TimeoutManager::InvalidFiringId),
+#ifdef DEBUG
+      mFiringIndex(-1),
+#endif
       mPopupState(PopupBlocker::openAllowed),
       mReason(Reason::eTimeoutOrInterval),
       mNestingLevel(0),
       mCleared(false),
       mRunning(false),
       mIsInterval(false) {}
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(Timeout)
--- a/dom/base/Timeout.h
+++ b/dom/base/Timeout.h
@@ -84,16 +84,20 @@ class Timeout final : public LinkedListE
 
   // Returned as value of setTimeout()
   uint32_t mTimeoutId;
 
   // Identifies which firing level this Timeout is being processed in
   // when sync loops trigger nested firing.
   uint32_t mFiringId;
 
+#ifdef DEBUG
+  int64_t mFiringIndex;
+#endif
+
   // The popup state at timeout creation time if not created from
   // another timeout
   PopupBlocker::PopupControlState mPopupState;
 
   // Used to allow several reasons for setting a timeout, where each
   // 'Reason' value is using a possibly overlapping set of id:s.
   Reason mReason;
 
--- a/dom/base/TimeoutManager.cpp
+++ b/dom/base/TimeoutManager.cpp
@@ -449,16 +449,20 @@ int32_t gDisableOpenClickDelay;
 TimeoutManager::TimeoutManager(nsGlobalWindowInner& aWindow,
                                uint32_t aMaxIdleDeferMS)
     : mWindow(aWindow),
       mExecutor(new TimeoutExecutor(this, false, 0)),
       mIdleExecutor(new TimeoutExecutor(this, true, aMaxIdleDeferMS)),
       mTimeouts(*this),
       mTimeoutIdCounter(1),
       mNextFiringId(InvalidFiringId + 1),
+#ifdef DEBUG
+      mFiringIndex(0),
+      mLastFiringIndex(-1),
+#endif
       mRunningTimeout(nullptr),
       mIdleTimeouts(*this),
       mIdleCallbackTimeoutCounter(1),
       mLastBudgetUpdate(TimeStamp::Now()),
       mExecutionBudget(GetMaxBudget(mWindow.IsBackgroundInternal())),
       mThrottleTimeouts(false),
       mThrottleTrackingTimeouts(false),
       mBudgetThrottleTimeouts(false),
@@ -759,16 +763,17 @@ void TimeoutManager::RunTimeout(const Ti
   uint32_t numTimersToRun = 0;
 
   // The timeout list is kept in deadline order. Discover the latest timeout
   // whose deadline has expired. On some platforms, native timeout events fire
   // "early", but we handled that above by setting deadline to aTargetDeadline
   // if the timer fired early.  So we can stop walking if we get to timeouts
   // whose When() is greater than deadline, since once that happens we know
   // nothing past that point is expired.
+
   for (Timeout* timeout = timeouts.GetFirst(); timeout != nullptr;
        timeout = timeout->getNext()) {
     if (totalTimeLimit.IsZero() || timeout->When() > deadline) {
       nextDeadline = timeout->When();
       break;
     }
 
     if (IsInvalidFiringId(timeout->mFiringId)) {
@@ -874,16 +879,32 @@ void TimeoutManager::RunTimeout(const Ti
       MOZ_ASSERT_IF(mWindow.IsFrozen(), mWindow.IsSuspended());
       if (mWindow.IsSuspended()) {
         break;
       }
 
       // The timeout is on the list to run at this depth, go ahead and
       // process it.
 
+      // Record the first time we try to fire a timeout, and ensure that
+      // all actual firings occur in that order.  This ensures that we
+      // retain compliance with the spec language
+      // (https://html.spec.whatwg.org/#dom-settimeout) specifically items
+      // 15 ("If method context is a Window object, wait until the Document
+      // associated with method context has been fully active for a further
+      // timeout milliseconds (not necessarily consecutively)") and item 16
+      // ("Wait until any invocations of this algorithm that had the same
+      // method context, that started before this one, and whose timeout is
+      // equal to or less than this one's, have completed.").
+#ifdef DEBUG
+      if (timeout->mFiringIndex == -1) {
+        timeout->mFiringIndex = mFiringIndex++;
+      }
+#endif
+
       if (mIsLoading && !aProcessIdle) {
         // Any timeouts that would fire during a load will be deferred
         // until the load event occurs, but if there's an idle time,
         // they'll be run before the load event.
         timeout->remove();
         // MOZ_RELEASE_ASSERT(timeout->When() <= (TimeStamp::Now()));
         mIdleTimeouts.InsertBack(timeout);
         if (MOZ_LOG_TEST(gTimeoutLog, LogLevel::Debug)) {
@@ -908,16 +929,20 @@ void TimeoutManager::RunTimeout(const Ti
         if (!scx) {
           // No context means this window was closed or never properly
           // initialized for this language.  This timer will never fire
           // so just remove it.
           timeout->remove();
           continue;
         }
 
+#ifdef DEBUG
+        MOZ_ASSERT(timeout->mFiringIndex > mLastFiringIndex);
+        mLastFiringIndex = timeout->mFiringIndex;
+#endif
         // This timeout is good to run
         bool timeout_was_cleared = mWindow.RunTimeoutHandler(timeout, scx);
 #if MOZ_GECKO_PROFILER
         if (profiler_is_active()) {
           TimeDuration elapsed = now - timeout->SubmitTime();
           TimeDuration target = timeout->When() - timeout->SubmitTime();
           TimeDuration delta = now - timeout->When();
           TimeDuration runtime = TimeStamp::Now() - now;
@@ -1033,16 +1058,19 @@ bool TimeoutManager::RescheduleTimeout(T
 
   // Compute time to next timeout for interval timer.
   // Make sure nextInterval is at least CalculateDelay().
   TimeDuration nextInterval = CalculateDelay(aTimeout);
 
   TimeStamp firingTime = aLastCallbackTime + nextInterval;
   TimeDuration delay = firingTime - aCurrentNow;
 
+#ifdef DEBUG
+  aTimeout->mFiringIndex = -1;
+#endif
   // And make sure delay is nonnegative; that might happen if the timer
   // thread is firing our timers somewhat early or if they're taking a long
   // time to run the callback.
   if (delay < TimeDuration(0)) {
     delay = TimeDuration(0);
   }
 
   aTimeout->SetWhenOrTimeRemaining(aCurrentNow, delay);
--- a/dom/base/TimeoutManager.h
+++ b/dom/base/TimeoutManager.h
@@ -198,16 +198,20 @@ class TimeoutManager final {
   // it must be a separate ref-counted object.
   RefPtr<TimeoutExecutor> mExecutor;
   // For timeouts run off the idle queue
   RefPtr<TimeoutExecutor> mIdleExecutor;
   // The list of timeouts coming from non-tracking scripts.
   Timeouts mTimeouts;
   uint32_t mTimeoutIdCounter;
   uint32_t mNextFiringId;
+#ifdef DEBUG
+  int64_t mFiringIndex;
+  int64_t mLastFiringIndex;
+#endif
   AutoTArray<uint32_t, 2> mFiringIdStack;
   mozilla::dom::Timeout* mRunningTimeout;
 
   // Timeouts that would have fired but are being deferred until MainThread
   // is idle (because we're loading)
   Timeouts mIdleTimeouts;
 
   // The current idle request callback timeout handle
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -219,17 +219,17 @@
 #include "nsWrapperCacheInlines.h"
 #include "nsXULPopupManager.h"
 #include "xpcprivate.h"  // nsXPConnect
 #include "HTMLSplitOnSpacesTokenizer.h"
 #include "InProcessTabChildMessageManager.h"
 #include "nsContentTypeParser.h"
 #include "nsICookiePermission.h"
 #include "nsICookieService.h"
-#include "mozIThirdPartyUtil.h"
+#include "ThirdPartyUtil.h"
 #include "mozilla/EnumSet.h"
 #include "mozilla/BloomFilter.h"
 #include "TabChild.h"
 #include "mozilla/dom/DocGroup.h"
 #include "mozilla/dom/TabGroup.h"
 #include "nsIWebNavigationInfo.h"
 #include "nsPluginHost.h"
 #include "nsIBrowser.h"
@@ -8199,17 +8199,17 @@ void nsContentUtils::GetCookieLifetimePo
 
 // static public
 bool nsContentUtils::IsThirdPartyWindowOrChannel(nsPIDOMWindowInner* aWindow,
                                                  nsIChannel* aChannel,
                                                  nsIURI* aURI) {
   MOZ_ASSERT(!aWindow || !aChannel,
              "A window and channel should not both be provided.");
 
-  nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil = services::GetThirdPartyUtil();
+  ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
   if (!thirdPartyUtil) {
     return false;
   }
 
   // In the absence of a window or channel, we assume that we are first-party.
   bool thirdParty = false;
 
   if (aWindow) {
--- a/extensions/cookie/nsPermissionManager.cpp
+++ b/extensions/cookie/nsPermissionManager.cpp
@@ -42,18 +42,20 @@
 #include "mozilla/Telemetry.h"
 #include "nsIConsoleService.h"
 #include "nsINavHistoryService.h"
 #include "nsToolkitCompsCID.h"
 #include "nsIObserverService.h"
 #include "nsPrintfCString.h"
 #include "mozilla/AbstractThread.h"
 #include "ExpandedPrincipal.h"
-
-static nsPermissionManager* gPermissionManager = nullptr;
+#include "mozilla/StaticPtr.h"
+#include "mozilla/ClearOnShutdown.h"
+
+static mozilla::StaticRefPtr<nsPermissionManager> gPermissionManager;
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 static bool IsChildProcess() { return XRE_IsContentProcess(); }
 
 static void LogToConsole(const nsAString& aMsg) {
   nsCOMPtr<nsIConsoleService> console(
@@ -973,22 +975,33 @@ nsPermissionManager::GetXPCOMSingleton()
   // teardowns - by the time our module destructor is called, it's too late to
   // Release our members, since GC cycles have already been completed and
   // would result in serious leaks.
   // See bug 209571.
   auto permManager = MakeRefPtr<nsPermissionManager>();
   if (NS_SUCCEEDED(permManager->Init())) {
     // Note: This is cleared in the nsPermissionManager destructor.
     gPermissionManager = permManager.get();
+    ClearOnShutdown(&gPermissionManager);
     return permManager.forget();
   }
 
   return nullptr;
 }
 
+// static
+nsPermissionManager* nsPermissionManager::GetInstance() {
+  if (!gPermissionManager) {
+    // Hand off the creation of the permission manager to GetXPCOMSingleton.
+    nsCOMPtr<nsIPermissionManager> permManager = GetXPCOMSingleton();
+  }
+
+  return gPermissionManager;
+}
+
 nsresult nsPermissionManager::Init() {
   // If the 'permissions.memory_only' pref is set to true, then don't write any
   // permission settings to disk, but keep them in a memory-only database.
   mMemoryOnlyDB =
       mozilla::Preferences::GetBool("permissions.memory_only", false);
 
   nsresult rv;
   nsCOMPtr<nsIPrefService> prefService =
--- a/extensions/cookie/nsPermissionManager.h
+++ b/extensions/cookie/nsPermissionManager.h
@@ -143,16 +143,17 @@ class nsPermissionManager final : public
 
   // nsISupports
   NS_DECL_ISUPPORTS
   NS_DECL_NSIPERMISSIONMANAGER
   NS_DECL_NSIOBSERVER
 
   nsPermissionManager();
   static already_AddRefed<nsIPermissionManager> GetXPCOMSingleton();
+  static nsPermissionManager* GetInstance();
   nsresult Init();
 
   // enums for AddInternal()
   enum OperationType {
     eOperationNone,
     eOperationAdding,
     eOperationRemoving,
     eOperationChanging,
--- a/gfx/layers/wr/StackingContextHelper.cpp
+++ b/gfx/layers/wr/StackingContextHelper.cpp
@@ -25,17 +25,17 @@ StackingContextHelper::StackingContextHe
     const StackingContextHelper& aParentSC, const ActiveScrolledRoot* aAsr,
     nsIFrame* aContainerFrame, nsDisplayItem* aContainerItem,
     wr::DisplayListBuilder& aBuilder, const wr::StackingContextParams& aParams,
     const LayoutDeviceRect& aBounds)
     : mBuilder(&aBuilder),
       mScale(1.0f, 1.0f),
       mDeferredTransformItem(aParams.mDeferredTransformItem),
       mIsPreserve3D(aParams.transform_style == wr::TransformStyle::Preserve3D),
-      mRasterizeLocally(aParams.mAnimated || aParentSC.mRasterizeLocally) {
+      mRasterizeLocally(aParams.mRasterizeLocally || aParentSC.mRasterizeLocally) {
   // Compute scale for fallback rendering. We don't try to guess a scale for 3d
   // transformed items
   gfx::Matrix transform2d;
   if (aParams.mBoundTransform &&
       aParams.mBoundTransform->CanDraw2D(&transform2d) &&
       aParams.reference_frame_kind != wr::WrReferenceFrameKind::Perspective &&
       !aParentSC.mIsPreserve3D) {
     mInheritedTransform = transform2d * aParentSC.mInheritedTransform;
--- a/gfx/vr/ipc/VRProcessManager.cpp
+++ b/gfx/vr/ipc/VRProcessManager.cpp
@@ -15,32 +15,35 @@ namespace mozilla {
 namespace gfx {
 
 static StaticAutoPtr<VRProcessManager> sSingleton;
 
 /* static */ VRProcessManager* VRProcessManager::Get() { return sSingleton; }
 
 /* static */ void VRProcessManager::Initialize() {
   MOZ_ASSERT(XRE_IsParentProcess());
-  sSingleton = new VRProcessManager();
+  if (sSingleton == nullptr) {
+    sSingleton = new VRProcessManager();
+  }
 }
 
 /* static */ void VRProcessManager::Shutdown() { sSingleton = nullptr; }
 
 VRProcessManager::VRProcessManager() : mProcess(nullptr) {
   MOZ_COUNT_CTOR(VRProcessManager);
 
   mObserver = new Observer(this);
   nsContentUtils::RegisterShutdownObserver(mObserver);
 }
 
 VRProcessManager::~VRProcessManager() {
   MOZ_COUNT_DTOR(VRProcessManager);
 
   if (mObserver) {
+    mObserver->Unregister();
     nsContentUtils::UnregisterShutdownObserver(mObserver);
     mObserver = nullptr;
   }
 
   DestroyProcess();
   // The VR process should have already been shut down.
   MOZ_ASSERT(!mProcess);
 }
@@ -140,16 +143,18 @@ NS_IMETHODIMP
 VRProcessManager::Observer::Observe(nsISupports* aSubject, const char* aTopic,
                                     const char16_t* aData) {
   if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
     mManager->OnXPCOMShutdown();
   }
   return NS_OK;
 }
 
+void VRProcessManager::Observer::Unregister() { mManager = nullptr; }
+
 void VRProcessManager::CleanShutdown() { DestroyProcess(); }
 
 void VRProcessManager::OnXPCOMShutdown() {
   if (mObserver) {
     nsContentUtils::UnregisterShutdownObserver(mObserver);
     mObserver = nullptr;
   }
 
--- a/gfx/vr/ipc/VRProcessManager.h
+++ b/gfx/vr/ipc/VRProcessManager.h
@@ -49,16 +49,17 @@ class VRProcessManager final : public VR
   // Permanently disable the VR process and record a message why.
   void DisableVRProcess(const char* aMessage);
 
   class Observer final : public nsIObserver {
    public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIOBSERVER
     explicit Observer(VRProcessManager* aManager);
+    void Unregister();
 
    protected:
     ~Observer() {}
 
     VRProcessManager* mManager;
   };
   friend class Observer;
 
--- a/gfx/webrender_bindings/WebRenderAPI.h
+++ b/gfx/webrender_bindings/WebRenderAPI.h
@@ -328,17 +328,23 @@ struct MOZ_STACK_CLASS StackingContextPa
         aPreserve ? wr::TransformStyle::Preserve3D : wr::TransformStyle::Flat;
   }
 
   nsTArray<wr::FilterOp> mFilters;
   wr::LayoutRect mBounds = wr::ToLayoutRect(LayoutDeviceRect());
   const gfx::Matrix4x4* mBoundTransform = nullptr;
   const gfx::Matrix4x4* mTransformPtr = nullptr;
   Maybe<nsDisplayTransform*> mDeferredTransformItem;
+  // Whether the stacking context is possibly animated. This alters how coordinates
+  // are transformed/snapped to invalidate less when transforms change frequently.
   bool mAnimated = false;
+  // Whether items should be rasterized in a local space that is (mostly) invariant
+  // to transforms, i.e. disabling subpixel AA and screen space pixel snapping on
+  // text runs that would only make sense in screen space.
+  bool mRasterizeLocally = false;
 };
 
 /// This is a simple C++ wrapper around WrState defined in the rust bindings.
 /// We may want to turn this into a direct wrapper on top of
 /// WebRenderFrameBuilder instead, so the interface may change a bit.
 class DisplayListBuilder {
  public:
   explicit DisplayListBuilder(wr::PipelineId aId,
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -7931,27 +7931,34 @@ bool nsDisplayTransform::CreateWebRender
     // If it has perspective, we create a new scroll data via the
     // UpdateScrollData call because that scenario is more complex. Otherwise
     // we can just stash the transform on the StackingContextHelper and
     // apply it to any scroll data that are created inside this
     // nsDisplayTransform.
     deferredTransformItem = Some(this);
   }
 
-  // If it looks like we're animated, we should rasterize in local space
-  // (disabling subpixel-aa and global pixel snapping)
-  bool animated = Frame()->HasAnimationOfTransform();
+  // Determine if we're possibly animated (= would need an active layer in FLB).
+  bool animated =
+    ActiveLayerTracker::IsStyleMaybeAnimated(Frame(), eCSSProperty_transform);
 
   wr::StackingContextParams params;
   params.mBoundTransform = &newTransformMatrix;
   params.animation = animationsId ? &prop : nullptr;
   params.mTransformPtr = transformForSC;
   params.is_backface_visible = !BackfaceIsHidden();
   params.mDeferredTransformItem = deferredTransformItem;
   params.mAnimated = animated;
+  // Determine if we would have to rasterize any items in local raster space
+  // (i.e. disable subpixel AA). We don't always need to rasterize locally even
+  // if the stacking context is possibly animated (at the cost of potentially
+  // some false negatives with respect to will-change handling), so we pass in
+  // this determination separately to accurately match with when FLB would normally
+  // disable subpixel AA.
+  params.mRasterizeLocally = animated && Frame()->HasAnimationOfTransform();
   params.SetPreserve3D(mFrame->Extend3DContext() && !mIsTransformSeparator);
   params.clip =
       wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId());
 
   StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder,
                            params,
                            LayoutDeviceRect(position, LayoutDeviceSize()));
 
--- a/netwerk/base/nsNetUtil.h
+++ b/netwerk/base/nsNetUtil.h
@@ -7,43 +7,43 @@
 #ifndef nsNetUtil_h__
 #define nsNetUtil_h__
 
 #include "mozilla/Maybe.h"
 #include "nsCOMPtr.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsILoadGroup.h"
+#include "nsINestedURI.h"
 #include "nsINetUtil.h"
 #include "nsIRequest.h"
 #include "nsILoadInfo.h"
 #include "nsIIOService.h"
+#include "nsIURI.h"
 #include "mozilla/NotNull.h"
 #include "mozilla/Services.h"
 #include "mozilla/Unused.h"
 #include "nsNetCID.h"
 #include "nsReadableUtils.h"
 #include "nsServiceManagerUtils.h"
 #include "nsString.h"
 
-class nsIURI;
 class nsIPrincipal;
 class nsIAsyncStreamCopier;
 class nsIAuthPrompt;
 class nsIAuthPrompt2;
 class nsIChannel;
 class nsIChannelPolicy;
 class nsIDownloadObserver;
 class nsIEventTarget;
 class nsIFileProtocolHandler;
 class nsIFileStream;
 class nsIInputStream;
 class nsIInputStreamPump;
 class nsIInterfaceRequestor;
-class nsINestedURI;
 class nsIOutputStream;
 class nsIParentChannel;
 class nsIPersistentProperties;
 class nsIProxyInfo;
 class nsIRequestObserver;
 class nsIStreamListener;
 class nsIStreamLoader;
 class nsIStreamLoaderObserver;
@@ -713,16 +713,54 @@ nsresult NS_URIChainHasFlags(nsIURI *uri
 
 /**
  * Helper function for getting the innermost URI for a given URI.  The return
  * value could be just the object passed in if it's not a nested URI.
  */
 already_AddRefed<nsIURI> NS_GetInnermostURI(nsIURI *aURI);
 
 /**
+ * Helper function for getting the host name of the innermost URI for a given
+ * URI.  The return value could be the host name of the URI passed in if it's
+ * not a nested URI.
+ */
+inline nsresult NS_GetInnermostURIHost(nsIURI *aURI, nsACString &aHost) {
+  aHost.Truncate();
+
+  // This block is optimized in order to avoid the overhead of calling
+  // NS_GetInnermostURI() which incurs a lot of overhead in terms of
+  // AddRef/Release calls.
+  nsINestedURI *nestedURI = nullptr;
+  nsresult rv = CallQueryInterface(aURI, &nestedURI);
+  if (NS_SUCCEEDED(rv)) {
+    // We have a nested URI!
+    nsCOMPtr<nsIURI> uri;
+    rv = nestedURI->GetInnermostURI(getter_AddRefs(uri));
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+
+    NS_RELEASE(nestedURI);
+
+    rv = uri->GetAsciiHost(aHost);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+  } else {
+    // We have a non-nested URI!
+    rv = aURI->GetAsciiHost(aHost);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+  }
+
+  return NS_OK;
+}
+
+/**
  * Get the "final" URI for a channel.  This is either channel's load info
  * resultPrincipalURI, if set, or GetOriginalURI.  In most cases (but not all)
  * load info resultPrincipalURI, if set, corresponds to URI of the channel if
  * it's required to represent the actual principal for the channel.
  */
 nsresult NS_GetFinalChannelURI(nsIChannel *channel, nsIURI **uri);
 
 // NS_SecurityHashURI must return the same hash value for any two URIs that
--- a/netwerk/dns/moz.build
+++ b/netwerk/dns/moz.build
@@ -18,16 +18,20 @@ XPIDL_SOURCES += [
     'nsIDNSService.idl',
     'nsIEffectiveTLDService.idl',
     'nsIIDNService.idl',
     'nsPIDNSService.idl',
 ]
 
 XPIDL_MODULE = 'necko_dns'
 
+EXPORTS += [
+    'nsEffectiveTLDService.h',
+]
+
 EXPORTS.mozilla.net += [
     'ChildDNSService.h',
     'DNS.h',
     'DNSListenerProxy.h',
     'DNSRequestChild.h',
     'DNSRequestParent.h',
     'IDNBlocklistUtils.h',
     'PDNSParams.h',
--- a/netwerk/dns/nsEffectiveTLDService.cpp
+++ b/netwerk/dns/nsEffectiveTLDService.cpp
@@ -53,16 +53,32 @@ nsresult nsEffectiveTLDService::Init() {
   return NS_OK;
 }
 
 nsEffectiveTLDService::~nsEffectiveTLDService() {
   UnregisterWeakMemoryReporter(this);
   gService = nullptr;
 }
 
+// static
+already_AddRefed<nsEffectiveTLDService> nsEffectiveTLDService::GetInstance() {
+  if (gService) {
+    return do_AddRef(gService);
+  }
+  nsCOMPtr<nsIEffectiveTLDService> tldService =
+      do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+  if (!tldService) {
+    return nullptr;
+  }
+  MOZ_ASSERT(
+      gService,
+      "gService must have been initialized in nsEffectiveTLDService::Init");
+  return do_AddRef(gService);
+}
+
 MOZ_DEFINE_MALLOC_SIZE_OF(EffectiveTLDServiceMallocSizeOf)
 
 // The amount of heap memory measured here is tiny. It used to be bigger when
 // nsEffectiveTLDService used a separate hash table instead of binary search.
 // Nonetheless, we keep this code here in anticipation of bug 1083971 which will
 // change ETLDEntries::entries to a heap-allocated array modifiable at runtime.
 NS_IMETHODIMP
 nsEffectiveTLDService::CollectReports(nsIHandleReportCallback *aHandleReport,
@@ -89,41 +105,39 @@ size_t nsEffectiveTLDService::SizeOfIncl
 // External function for dealing with URI's correctly.
 // Pulls out the host portion from an nsIURI, and calls through to
 // GetPublicSuffixFromHost().
 NS_IMETHODIMP
 nsEffectiveTLDService::GetPublicSuffix(nsIURI *aURI,
                                        nsACString &aPublicSuffix) {
   NS_ENSURE_ARG_POINTER(aURI);
 
-  nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(aURI);
-  NS_ENSURE_ARG_POINTER(innerURI);
-
   nsAutoCString host;
-  nsresult rv = innerURI->GetAsciiHost(host);
-  if (NS_FAILED(rv)) return rv;
+  nsresult rv = NS_GetInnermostURIHost(aURI, host);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
 
   return GetBaseDomainInternal(host, 0, aPublicSuffix);
 }
 
 // External function for dealing with URI's correctly.
 // Pulls out the host portion from an nsIURI, and calls through to
 // GetBaseDomainFromHost().
 NS_IMETHODIMP
 nsEffectiveTLDService::GetBaseDomain(nsIURI *aURI, uint32_t aAdditionalParts,
                                      nsACString &aBaseDomain) {
   NS_ENSURE_ARG_POINTER(aURI);
   NS_ENSURE_TRUE(((int32_t)aAdditionalParts) >= 0, NS_ERROR_INVALID_ARG);
 
-  nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(aURI);
-  NS_ENSURE_ARG_POINTER(innerURI);
-
   nsAutoCString host;
-  nsresult rv = innerURI->GetAsciiHost(host);
-  if (NS_FAILED(rv)) return rv;
+  nsresult rv = NS_GetInnermostURIHost(aURI, host);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
 
   return GetBaseDomainInternal(host, aAdditionalParts + 1, aBaseDomain);
 }
 
 // External function for dealing with a host string directly: finds the public
 // suffix (e.g. co.uk) for the given hostname. See GetBaseDomainInternal().
 NS_IMETHODIMP
 nsEffectiveTLDService::GetPublicSuffixFromHost(const nsACString &aHostname,
@@ -184,53 +198,73 @@ nsresult nsEffectiveTLDService::GetBaseD
   bool trailingDot = aHostname.Last() == '.';
   if (trailingDot) aHostname.Truncate(aHostname.Length() - 1);
 
   // check the edge cases of the host being '.' or having a second trailing '.',
   // since subsequent checks won't catch it.
   if (aHostname.IsEmpty() || aHostname.Last() == '.')
     return NS_ERROR_INVALID_ARG;
 
-  // Check if we're dealing with an IPv4/IPv6 hostname, and return
-  PRNetAddr addr;
-  PRStatus result = PR_StringToNetAddr(aHostname.get(), &addr);
-  if (result == PR_SUCCESS) return NS_ERROR_HOST_IS_IP_ADDRESS;
-
   // Lookup in the cache if this is a normal query. This is restricted to
   // main thread-only as the cache is not thread-safe.
   Maybe<TldCache::Entry> entry;
   if (aAdditionalParts == 1 && NS_IsMainThread()) {
     auto p = mMruTable.Lookup(aHostname);
     if (p) {
+      if (NS_FAILED(p.Data().mResult)) {
+        return p.Data().mResult;
+      }
+
       // There was a match, just return the cached value.
       aBaseDomain = p.Data().mBaseDomain;
       if (trailingDot) {
         aBaseDomain.Append('.');
       }
 
       return NS_OK;
     }
 
     entry = Some(p);
   }
 
+  // Check if we're dealing with an IPv4/IPv6 hostname, and return
+  PRNetAddr addr;
+  PRStatus result = PR_StringToNetAddr(aHostname.get(), &addr);
+  if (result == PR_SUCCESS) {
+    // Update the MRU table if in use.
+    if (entry) {
+      entry->Set(TLDCacheEntry{aHostname, EmptyCString(),
+                               NS_ERROR_HOST_IS_IP_ADDRESS});
+    }
+
+    return NS_ERROR_HOST_IS_IP_ADDRESS;
+  }
+
   // Walk up the domain tree, most specific to least specific,
   // looking for matches at each level.  Note that a given level may
   // have multiple attributes (e.g. IsWild() and IsNormal()).
   const char *prevDomain = nullptr;
   const char *currDomain = aHostname.get();
   const char *nextDot = strchr(currDomain, '.');
   const char *end = currDomain + aHostname.Length();
   // Default value of *eTLD is currDomain as set in the while loop below
   const char *eTLD = nullptr;
   while (true) {
     // sanity check the string we're about to look up: it should not begin with
     // a '.'; this would mean the hostname began with a '.' or had an
     // embedded '..' sequence.
-    if (*currDomain == '.') return NS_ERROR_INVALID_ARG;
+    if (*currDomain == '.') {
+      // Update the MRU table if in use.
+      if (entry) {
+        entry->Set(
+            TLDCacheEntry{aHostname, EmptyCString(), NS_ERROR_INVALID_ARG});
+      }
+
+      return NS_ERROR_INVALID_ARG;
+    }
 
     // Perform the lookup.
     const int result = mGraph.Lookup(Substring(currDomain, end));
     if (result != Dafsa::kKeyNotFound) {
       if (result == kWildcardRule && prevDomain) {
         // wildcard rules imply an eTLD one level inferior to the match.
         eTLD = prevDomain;
         break;
@@ -282,23 +316,31 @@ nsresult nsEffectiveTLDService::GetBaseD
       if (*(--iter) == '.' && aAdditionalParts-- == 0) {
         ++iter;
         ++aAdditionalParts;
         break;
       }
     }
   }
 
-  if (aAdditionalParts != 0) return NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS;
+  if (aAdditionalParts != 0) {
+    // Update the MRU table if in use.
+    if (entry) {
+      entry->Set(TLDCacheEntry{aHostname, EmptyCString(),
+                               NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS});
+    }
+
+    return NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS;
+  }
 
   aBaseDomain = Substring(iter, end);
 
   // Update the MRU table if in use.
   if (entry) {
-    entry->Set(TLDCacheEntry{aHostname, nsCString(aBaseDomain)});
+    entry->Set(TLDCacheEntry{aHostname, nsCString(aBaseDomain), NS_OK});
   }
 
   // add on the trailing dot, if applicable
   if (trailingDot) aBaseDomain.Append('.');
 
   return NS_OK;
 }
 
--- a/netwerk/dns/nsEffectiveTLDService.h
+++ b/netwerk/dns/nsEffectiveTLDService.h
@@ -24,32 +24,39 @@ class nsEffectiveTLDService final : publ
  public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIEFFECTIVETLDSERVICE
   NS_DECL_NSIMEMORYREPORTER
 
   nsEffectiveTLDService();
   nsresult Init();
 
+  static already_AddRefed<nsEffectiveTLDService> GetInstance();
+
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
 
  private:
   nsresult GetBaseDomainInternal(nsCString& aHostname, int32_t aAdditionalParts,
                                  nsACString& aBaseDomain);
   nsresult NormalizeHostname(nsCString& aHostname);
   ~nsEffectiveTLDService();
 
   nsCOMPtr<nsIIDNService> mIDNService;
 
   // The DAFSA provides a compact encoding of the rather large eTLD list.
   mozilla::Dafsa mGraph;
 
+  // Note that the cache entries here can record entries that were cached
+  // successfully or unsuccessfully.  mResult must be checked before using an
+  // entry.  If it's a success error code, the cache entry is valid and can be
+  // used.
   struct TLDCacheEntry {
     nsCString mHost;
     nsCString mBaseDomain;
+    nsresult mResult;
   };
 
   // We use a small most recently used cache to compensate for DAFSA lookups
   // being slightly slower than a binary search on a larger table of strings.
   //
   // We first check the cache for a matching result and avoid a DAFSA lookup
   // if a match is found. Otherwise we lookup the domain in the DAFSA and then
   // cache the result. During standard browsing the same domains are repeatedly
--- a/python/mozboot/mozboot/linux_common.py
+++ b/python/mozboot/mozboot/linux_common.py
@@ -1,36 +1,58 @@
 # 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/.
 
 # An easy way for distribution-specific bootstrappers to share the code
 # needed to install Stylo and Node dependencies.  This class must come before
 # BaseBootstrapper in the inheritance list.
 
-from __future__ import absolute_import
+from __future__ import absolute_import, print_function
+
+import os
+
+
+def is_non_x86_64():
+    return os.uname()[4] != 'x86_64'
 
 
 class StyloInstall(object):
     def __init__(self, **kwargs):
         pass
 
     def ensure_stylo_packages(self, state_dir, checkout_root):
         from mozboot import stylo
+
+        if is_non_x86_64():
+            print('Cannot install bindgen clang and cbindgen packages from taskcluster.\n'
+                  'Please install these packages manually.')
+            return
+
         self.install_toolchain_artifact(state_dir, checkout_root, stylo.LINUX_CLANG)
         self.install_toolchain_artifact(state_dir, checkout_root, stylo.LINUX_CBINDGEN)
 
 
 class NodeInstall(object):
     def __init__(self, **kwargs):
         pass
 
     def ensure_node_packages(self, state_dir, checkout_root):
+        if is_non_x86_64():
+            print('Cannot install node package from taskcluster.\n'
+                  'Please install this package manually.')
+            return
+
         from mozboot import node
         self.install_toolchain_artifact(state_dir, checkout_root, node.LINUX)
 
 
 class ClangStaticAnalysisInstall(object):
     def __init__(self, **kwargs):
         pass
 
     def ensure_clang_static_analysis_package(self, checkout_root):
+        if is_non_x86_64():
+            print('Cannot install static analysis tools from taskcluster.\n'
+                  'Please install these tools manually.')
+            return
+
         self.install_toolchain_static_analysis(checkout_root)
--- a/python/mozboot/mozboot/mozillabuild.py
+++ b/python/mozboot/mozboot/mozillabuild.py
@@ -1,21 +1,48 @@
 # 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/.
 
 from __future__ import absolute_import, print_function
 
+import ctypes
 import os
 import sys
 import subprocess
 
 from mozboot.base import BaseBootstrapper
 
 
+def is_aarch64_host():
+    from ctypes import wintypes
+    kernel32 = ctypes.windll.kernel32
+    IMAGE_FILE_MACHINE_UNKNOWN = 0
+    IMAGE_FILE_MACHINE_ARM64 = 0xAA64
+
+    try:
+        iswow64process2 = kernel32.IsWow64Process2
+    except Exception:
+        # If we can't access the symbol, we know we're not on aarch64.
+        return False
+
+    currentProcess = kernel32.GetCurrentProcess()
+    processMachine = wintypes.USHORT(IMAGE_FILE_MACHINE_UNKNOWN)
+    nativeMachine = wintypes.USHORT(IMAGE_FILE_MACHINE_UNKNOWN)
+
+    gotValue = iswow64process2(currentProcess,
+                               ctypes.byref(processMachine),
+                               ctypes.byref(nativeMachine))
+    # If this call fails, we have no idea.
+    if not gotValue:
+        return False
+
+    return nativeMachine.value == IMAGE_FILE_MACHINE_ARM64
+
+
 class MozillaBuildBootstrapper(BaseBootstrapper):
     '''Bootstrapper for MozillaBuild to install rustup.'''
     def __init__(self, no_interactive=False, no_system_changes=False):
         BaseBootstrapper.__init__(self, no_interactive=no_interactive,
                                   no_system_changes=no_system_changes)
         print("mach bootstrap is not fully implemented in MozillaBuild")
 
     def which(self, name, *extra_search_dirs):
@@ -44,24 +71,34 @@ class MozillaBuildBootstrapper(BaseBoots
 
     def install_mobile_android_artifact_mode_packages(self):
         pass
 
     def ensure_clang_static_analysis_package(self, checkout_root):
         self.install_toolchain_static_analysis(checkout_root)
 
     def ensure_stylo_packages(self, state_dir, checkout_root):
+        # On-device artifact builds are supported; on-device desktop builds are not.
+        if is_aarch64_host():
+            raise Exception('You should not be performing desktop builds on an '
+                            'AArch64 device.  If you want to do artifact builds '
+                            'instead, please choose the appropriate artifact build '
+                            'option when beginning bootstrap.')
+
         from mozboot import stylo
         self.install_toolchain_artifact(state_dir, checkout_root, stylo.WINDOWS_CLANG)
         self.install_toolchain_artifact(state_dir, checkout_root, stylo.WINDOWS_CBINDGEN)
 
     def ensure_node_packages(self, state_dir, checkout_root):
         from mozboot import node
+        # We don't have native aarch64 node available, but aarch64 windows
+        # runs x86 binaries, so just use the x86 packages for such hosts.
+        node_artifact = node.WIN32 if is_aarch64_host() else node.WIN64
         self.install_toolchain_artifact(
-            state_dir, checkout_root, node.WINDOWS)
+            state_dir, checkout_root, node_artifact)
 
     def _update_package_manager(self):
         pass
 
     def run(self, command):
         subprocess.check_call(command, stdin=sys.stdin)
 
     def pip_install(self, *packages):
--- a/python/mozboot/mozboot/node.py
+++ b/python/mozboot/mozboot/node.py
@@ -1,9 +1,10 @@
 # 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/.
 
 from __future__ import absolute_import, print_function, unicode_literals
 
-WINDOWS = 'win64-node'
+WIN32 = 'win32-node'
+WIN64 = 'win64-node'
 LINUX = 'linux64-node'
 OSX = 'macosx64-node'
--- a/python/mozboot/mozboot/windows.py
+++ b/python/mozboot/mozboot/windows.py
@@ -1,21 +1,48 @@
 # 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/.
 
 from __future__ import absolute_import, print_function
 
+import ctypes
 import os
 import sys
 import subprocess
 
 from mozboot.base import BaseBootstrapper
 
 
+def is_aarch64_host():
+    from ctypes import wintypes
+    kernel32 = ctypes.windll.kernel32
+    IMAGE_FILE_MACHINE_UNKNOWN = 0
+    IMAGE_FILE_MACHINE_ARM64 = 0xAA64
+
+    try:
+        iswow64process2 = kernel32.IsWow64Process2
+    except Exception:
+        # If we can't access the symbol, we know we're not on aarch64.
+        return False
+
+    currentProcess = kernel32.GetCurrentProcess()
+    processMachine = wintypes.USHORT(IMAGE_FILE_MACHINE_UNKNOWN)
+    nativeMachine = wintypes.USHORT(IMAGE_FILE_MACHINE_UNKNOWN)
+
+    gotValue = iswow64process2(currentProcess,
+                               ctypes.byref(processMachine),
+                               ctypes.byref(nativeMachine))
+    # If this call fails, we have no idea.
+    if not gotValue:
+        return False
+
+    return nativeMachine.value == IMAGE_FILE_MACHINE_ARM64
+
+
 class WindowsBootstrapper(BaseBootstrapper):
     '''Bootstrapper for msys2 based environments for building in Windows.'''
 
     SYSTEM_PACKAGES = [
         'mingw-w64-x86_64-make',
         'mingw-w64-x86_64-python2-pip',
         'mingw-w64-x86_64-perl',
         'patch',
@@ -71,24 +98,34 @@ class WindowsBootstrapper(BaseBootstrapp
 
     def install_mobile_android_artifact_mode_packages(self):
         raise NotImplementedError('We do not support building Android on Windows. Sorry!')
 
     def ensure_clang_static_analysis_package(self, checkout_root):
         self.install_toolchain_static_analysis(checkout_root)
 
     def ensure_stylo_packages(self, state_dir, checkout_root):
+        # On-device artifact builds are supported; on-device desktop builds are not.
+        if is_aarch64_host():
+            raise Exception('You should not be performing desktop builds on an '
+                            'AArch64 device.  If you want to do artifact builds '
+                            'instead, please choose the appropriate artifact build '
+                            'option when beginning bootstrap.')
+
         from mozboot import stylo
         self.install_toolchain_artifact(state_dir, checkout_root, stylo.WINDOWS_CLANG)
         self.install_toolchain_artifact(state_dir, checkout_root, stylo.WINDOWS_CBINDGEN)
 
     def ensure_node_packages(self, state_dir, checkout_root):
         from mozboot import node
+        # We don't have native aarch64 node available, but aarch64 windows
+        # runs x86 binaries, so just use the x86 packages for such hosts.
+        node_artifact = node.WIN32 if is_aarch64_host() else node.WIN64
         self.install_toolchain_artifact(
-            state_dir, checkout_root, node.WINDOWS)
+            state_dir, checkout_root, node_artifact)
 
     def _update_package_manager(self):
         self.pacman_update()
 
     def run(self, command):
         subprocess.check_call(command, stdin=sys.stdin)
 
     def pacman_update(self):
--- a/taskcluster/ci/toolchain/node.yml
+++ b/taskcluster/ci/toolchain/node.yml
@@ -29,8 +29,18 @@ macosx64-node:
         arguments: ['macosx64']
 
 win64-node:
     treeherder:
         symbol: TW64(node)
     run:
         arguments: ['win64']
         toolchain-artifact: public/build/node.tar.bz2
+
+win32-node:
+    treeherder:
+        symbol: TW32(node)
+    run:
+        arguments: ['win32']
+        toolchain-artifact: public/build/node.tar.bz2
+    run-on-projects:
+        - trunk
+        - try
--- a/taskcluster/scripts/misc/repack-node.sh
+++ b/taskcluster/scripts/misc/repack-node.sh
@@ -25,16 +25,25 @@ win64)
     ARCH=win-x64
     # From https://nodejs.org/dist/v8.11.3/SHASUMS256.txt.asc
     SHA256SUM=91b779def1b21dcd1def7fc9671a869a1e2f989952e76fdc08a5d73570075f31
     SUFFIX=zip
     UNARCHIVE=unzip
     REPACK_TAR_COMPRESSION_SWITCH=j
     REPACK_SUFFIX=tar.bz2
     ;;
+win32)
+    ARCH=win-x86
+    # From https://nodejs.org/dist/v8.11.3/SHASUMS256.txt.asc
+    SHA256SUM=9482a0ad7aa5cd964cbeb11a605377b5c5aae4eae952c838aecf079de6088dc6
+    SUFFIX=zip
+    UNARCHIVE=unzip
+    REPACK_TAR_COMPRESSION_SWITCH=j
+    REPACK_SUFFIX=tar.bz2
+    ;;
 esac
 
 VERSION=8.11.3
 # From https://nodejs.org/en/download/
 URL=https://nodejs.org/dist/v$VERSION/node-v$VERSION-$ARCH.$SUFFIX
 ARCHIVE=node-v$VERSION-$ARCH.$SUFFIX
 DIR=node-v$VERSION
 
--- a/toolkit/components/antitracking/AntiTrackingCommon.cpp
+++ b/toolkit/components/antitracking/AntiTrackingCommon.cpp
@@ -18,17 +18,17 @@
 #include "nsGlobalWindowInner.h"
 #include "nsCookiePermission.h"
 #include "nsICookieService.h"
 #include "nsIDocShell.h"
 #include "nsIHttpChannelInternal.h"
 #include "nsIIOService.h"
 #include "nsIParentChannel.h"
 #include "nsIPermission.h"
-#include "nsIPermissionManager.h"
+#include "nsPermissionManager.h"
 #include "nsIPrincipal.h"
 #include "nsIScriptError.h"
 #include "nsIURI.h"
 #include "nsIURIFixup.h"
 #include "nsIURL.h"
 #include "nsIWebProgressListener.h"
 #include "nsNetUtil.h"
 #include "nsPIDOMWindow.h"
@@ -452,17 +452,17 @@ already_AddRefed<nsPIDOMWindowOuter> Get
   return pwin.forget();
 }
 
 class TemporaryAccessGrantObserver final : public nsIObserver {
  public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
-  static void Create(nsIPermissionManager* aPM, nsIPrincipal* aPrincipal,
+  static void Create(nsPermissionManager* aPM, nsIPrincipal* aPrincipal,
                      const nsACString& aType) {
     nsCOMPtr<nsITimer> timer;
     RefPtr<TemporaryAccessGrantObserver> observer =
         new TemporaryAccessGrantObserver(aPM, aPrincipal, aType);
     nsresult rv = NS_NewTimerWithObserver(getter_AddRefs(timer), observer,
                                           24 * 60 * 60 * 1000,  // 24 hours
                                           nsITimer::TYPE_ONE_SHOT);
 
@@ -478,30 +478,30 @@ class TemporaryAccessGrantObserver final
     nsCOMPtr<nsIObserverService> observerService =
         mozilla::services::GetObserverService();
     if (observerService) {
       observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
     }
   }
 
  private:
-  TemporaryAccessGrantObserver(nsIPermissionManager* aPM,
+  TemporaryAccessGrantObserver(nsPermissionManager* aPM,
                                nsIPrincipal* aPrincipal,
                                const nsACString& aType)
       : mPM(aPM), mPrincipal(aPrincipal), mType(aType) {
     MOZ_ASSERT(XRE_IsParentProcess(),
                "Enforcing temporary access grant lifetimes can only be done in "
                "the parent process");
   }
 
   ~TemporaryAccessGrantObserver() = default;
 
  private:
   nsCOMPtr<nsITimer> mTimer;
-  nsCOMPtr<nsIPermissionManager> mPM;
+  RefPtr<nsPermissionManager> mPM;
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsCString mType;
 };
 
 NS_IMPL_ISUPPORTS(TemporaryAccessGrantObserver, nsIObserver)
 
 NS_IMETHODIMP
 TemporaryAccessGrantObserver::Observe(nsISupports* aSubject, const char* aTopic,
@@ -756,18 +756,18 @@ AntiTrackingCommon::SaveFirstPartyStorag
 
   if (NS_WARN_IF(!aParentPrincipal)) {
     // The child process is sending something wrong. Let's ignore it.
     LOG(("aParentPrincipal is null, bailing out early"));
     return FirstPartyStorageAccessGrantPromise::CreateAndReject(false,
                                                                 __func__);
   }
 
-  nsCOMPtr<nsIPermissionManager> pm = services::GetPermissionManager();
-  if (NS_WARN_IF(!pm)) {
+  nsPermissionManager* permManager = nsPermissionManager::GetInstance();
+  if (NS_WARN_IF(!permManager)) {
     LOG(("Permission manager is null, bailing out early"));
     return FirstPartyStorageAccessGrantPromise::CreateAndReject(false,
                                                                 __func__);
   }
 
   // Remember that this pref is stored in seconds!
   uint32_t expirationType = nsIPermissionManager::EXPIRE_TIME;
   uint32_t expirationTime =
@@ -785,19 +785,19 @@ AntiTrackingCommon::SaveFirstPartyStorag
       when = 0;
     }
 
     LOG(
         ("Setting 'any site' permission expiry: %u, proceeding to save in the "
          "permission manager",
          expirationTime));
 
-    rv = pm->AddFromPrincipal(aTrackingPrincipal, "cookie",
-                              nsICookiePermission::ACCESS_ALLOW, expirationType,
-                              when);
+    rv = permManager->AddFromPrincipal(aTrackingPrincipal, "cookie",
+                                       nsICookiePermission::ACCESS_ALLOW,
+                                       expirationType, when);
   } else {
     uint32_t privateBrowsingId = 0;
     rv = aParentPrincipal->GetPrivateBrowsingId(&privateBrowsingId);
     if ((!NS_WARN_IF(NS_FAILED(rv)) && privateBrowsingId > 0) ||
         (aAllowMode == eAllowAutoGrant)) {
       // If we are coming from a private window or are automatically granting a
       // permission, make sure to store a session-only permission which won't
       // get persisted to disk.
@@ -808,23 +808,23 @@ AntiTrackingCommon::SaveFirstPartyStorag
     nsAutoCString type;
     CreatePermissionKey(aTrackingOrigin, aGrantedOrigin, type);
 
     LOG(
         ("Computed permission key: %s, expiry: %u, proceeding to save in the "
          "permission manager",
          type.get(), expirationTime));
 
-    rv = pm->AddFromPrincipal(aParentPrincipal, type.get(),
-                              nsIPermissionManager::ALLOW_ACTION,
-                              expirationType, when);
+    rv = permManager->AddFromPrincipal(aParentPrincipal, type.get(),
+                                       nsIPermissionManager::ALLOW_ACTION,
+                                       expirationType, when);
 
     if (NS_SUCCEEDED(rv) && (aAllowMode == eAllowAutoGrant)) {
       // Make sure temporary access grants do not survive more than 24 hours.
-      TemporaryAccessGrantObserver::Create(pm, aParentPrincipal, type);
+      TemporaryAccessGrantObserver::Create(permManager, aParentPrincipal, type);
     }
   }
   Unused << NS_WARN_IF(NS_FAILED(rv));
 
   LOG(("Result: %s", NS_SUCCEEDED(rv) ? "success" : "failure"));
   return FirstPartyStorageAccessGrantPromise::CreateAndResolve(rv, __func__);
 }
 
@@ -1028,24 +1028,25 @@ bool AntiTrackingCommon::IsFirstPartySto
   nsAutoCString type;
   CreatePermissionKey(trackingOrigin, grantedOrigin, type);
 
   if (topInnerWindow->HasStorageAccessGranted(type)) {
     LOG(("Permission stored in the window. All good."));
     return true;
   }
 
-  nsCOMPtr<nsIPermissionManager> pm = services::GetPermissionManager();
-  if (NS_WARN_IF(!pm)) {
+  nsPermissionManager* permManager = nsPermissionManager::GetInstance();
+  if (NS_WARN_IF(!permManager)) {
     LOG(("Failed to obtain the permission manager"));
     return false;
   }
 
   uint32_t result = 0;
-  rv = pm->TestPermissionFromPrincipal(parentPrincipal, type.get(), &result);
+  rv = permManager->TestPermissionFromPrincipal(parentPrincipal, type.get(),
+                                                &result);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     LOG(("Failed to test the permission"));
     return false;
   }
 
   LOG_SPEC(
       ("Testing permission type %s for %s resulted in %d (%s)", type.get(),
        _spec, int(result),
@@ -1275,24 +1276,25 @@ bool AntiTrackingCommon::IsFirstPartySto
   if (NS_WARN_IF(NS_FAILED(rv))) {
     LOG_SPEC(("Failed to compute the origin from %s", _spec), aURI);
     return false;
   }
 
   nsAutoCString type;
   CreatePermissionKey(trackingOrigin, origin, type);
 
-  nsCOMPtr<nsIPermissionManager> pm = services::GetPermissionManager();
-  if (NS_WARN_IF(!pm)) {
+  nsPermissionManager* permManager = nsPermissionManager::GetInstance();
+  if (NS_WARN_IF(!permManager)) {
     LOG(("Failed to obtain the permission manager"));
     return false;
   }
 
   uint32_t result = 0;
-  rv = pm->TestPermissionFromPrincipal(parentPrincipal, type.get(), &result);
+  rv = permManager->TestPermissionFromPrincipal(parentPrincipal, type.get(),
+                                                &result);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     LOG(("Failed to test the permission"));
     return false;
   }
 
   LOG_SPEC(
       ("Testing permission type %s for %s resulted in %d (%s)", type.get(),
        _spec, int(result),
@@ -1370,24 +1372,25 @@ bool AntiTrackingCommon::IsFirstPartySto
   if (NS_WARN_IF(NS_FAILED(rv))) {
     LOG_SPEC(("Failed to compute the origin from %s", _spec), aURI);
     return false;
   }
 
   nsAutoCString type;
   CreatePermissionKey(origin, origin, type);
 
-  nsCOMPtr<nsIPermissionManager> pm = services::GetPermissionManager();
-  if (NS_WARN_IF(!pm)) {
+  nsPermissionManager* permManager = nsPermissionManager::GetInstance();
+  if (NS_WARN_IF(!permManager)) {
     LOG(("Failed to obtain the permission manager"));
     return false;
   }
 
   uint32_t result = 0;
-  rv = pm->TestPermissionFromPrincipal(parentPrincipal, type.get(), &result);
+  rv = permManager->TestPermissionFromPrincipal(parentPrincipal, type.get(),
+                                                &result);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     LOG(("Failed to test the permission"));
     return false;
   }
 
   if (MOZ_LOG_TEST(gAntiTrackingLog, LogLevel::Debug)) {
     nsCOMPtr<nsIURI> parentPrincipalURI;
     Unused << parentPrincipal->GetURI(getter_AddRefs(parentPrincipalURI));
@@ -1436,33 +1439,33 @@ nsresult AntiTrackingCommon::IsOnContent
   nsresult rv = aTopWinURI ? aTopWinURI->GetHostPort(temp) : NS_ERROR_FAILURE;
   // GetHostPort returns an empty string (with a success error code) for file://
   // URIs.
   if (NS_FAILED(rv) || temp.IsEmpty()) {
     return rv;  // normal for some loads, no need to print a warning
   }
   escaped.Append(temp);
 
-  nsCOMPtr<nsIPermissionManager> permMgr = services::GetPermissionManager();
-  NS_ENSURE_TRUE(permMgr, NS_ERROR_FAILURE);
+  nsPermissionManager* permManager = nsPermissionManager::GetInstance();
+  NS_ENSURE_TRUE(permManager, NS_ERROR_FAILURE);
 
   // Check both the normal mode and private browsing mode user override
   // permissions.
   Pair<const char*, bool> types[] = {{"trackingprotection", false},
                                      {"trackingprotection-pb", true}};
 
   auto topWinURI = PromiseFlatCString(escaped);
   for (size_t i = 0; i < ArrayLength(types); ++i) {
     if (aIsPrivateBrowsing != types[i].second()) {
       continue;
     }
 
     uint32_t permissions = nsIPermissionManager::UNKNOWN_ACTION;
-    rv = permMgr->TestPermissionOriginNoSuffix(topWinURI, types[i].first(),
-                                               &permissions);
+    rv = permManager->TestPermissionOriginNoSuffix(topWinURI, types[i].first(),
+                                                   &permissions);
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (permissions == nsIPermissionManager::ALLOW_ACTION) {
       aIsAllowListed = true;
       LOG(("Found user override type %s for %s", types[i].first(),
            topWinURI.get()));
       // Stop checking the next permisson type if we decided to override.
       break;
@@ -1592,18 +1595,18 @@ nsresult AntiTrackingCommon::IsOnContent
 
 /* static */ void AntiTrackingCommon::StoreUserInteractionFor(
     nsIPrincipal* aPrincipal) {
   if (XRE_IsParentProcess()) {
     nsCOMPtr<nsIURI> uri;
     Unused << aPrincipal->GetURI(getter_AddRefs(uri));
     LOG_SPEC(("Saving the userInteraction for %s", _spec), uri);
 
-    nsCOMPtr<nsIPermissionManager> pm = services::GetPermissionManager();
-    if (NS_WARN_IF(!pm)) {
+    nsPermissionManager* permManager = nsPermissionManager::GetInstance();
+    if (NS_WARN_IF(!permManager)) {
       LOG(("Permission manager is null, bailing out early"));
       return;
     }
 
     // Remember that this pref is stored in seconds!
     uint32_t expirationType = nsIPermissionManager::EXPIRE_TIME;
     uint32_t expirationTime =
         StaticPrefs::privacy_userInteraction_expiration() * 1000;
@@ -1613,19 +1616,19 @@ nsresult AntiTrackingCommon::IsOnContent
     nsresult rv = aPrincipal->GetPrivateBrowsingId(&privateBrowsingId);
     if (!NS_WARN_IF(NS_FAILED(rv)) && privateBrowsingId > 0) {
       // If we are coming from a private window, make sure to store a
       // session-only permission which won't get persisted to disk.
       expirationType = nsIPermissionManager::EXPIRE_SESSION;
       when = 0;
     }
 
-    rv = pm->AddFromPrincipal(aPrincipal, USER_INTERACTION_PERM,
-                              nsIPermissionManager::ALLOW_ACTION,
-                              expirationType, when);
+    rv = permManager->AddFromPrincipal(aPrincipal, USER_INTERACTION_PERM,
+                                       nsIPermissionManager::ALLOW_ACTION,
+                                       expirationType, when);
     Unused << NS_WARN_IF(NS_FAILED(rv));
     return;
   }
 
   ContentChild* cc = ContentChild::GetSingleton();
   MOZ_ASSERT(cc);
 
   nsCOMPtr<nsIURI> uri;
@@ -1633,24 +1636,24 @@ nsresult AntiTrackingCommon::IsOnContent
   LOG_SPEC(("Asking the parent process to save the user-interaction for us: %s",
             _spec),
            uri);
   cc->SendStoreUserInteractionAsPermission(IPC::Principal(aPrincipal));
 }
 
 /* static */ bool AntiTrackingCommon::HasUserInteraction(
     nsIPrincipal* aPrincipal) {
-  nsCOMPtr<nsIPermissionManager> pm = services::GetPermissionManager();
-  if (NS_WARN_IF(!pm)) {
+  nsPermissionManager* permManager = nsPermissionManager::GetInstance();
+  if (NS_WARN_IF(!permManager)) {
     return false;
   }
 
   uint32_t result = 0;
-  nsresult rv = pm->TestPermissionFromPrincipal(aPrincipal,
-                                                USER_INTERACTION_PERM, &result);
+  nsresult rv = permManager->TestPermissionFromPrincipal(
+      aPrincipal, USER_INTERACTION_PERM, &result);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return false;
   }
 
   return result == nsIPermissionManager::ALLOW_ACTION;
 }
 
 /* static */ already_AddRefed<nsIURI>
--- a/toolkit/components/antitracking/moz.build
+++ b/toolkit/components/antitracking/moz.build
@@ -10,13 +10,17 @@ with Files('**'):
 EXPORTS.mozilla = [
     'AntiTrackingCommon.h',
 ]
 
 UNIFIED_SOURCES += [
     'AntiTrackingCommon.cpp',
 ]
 
+LOCAL_INCLUDES += [
+    '/extensions/cookie',
+]
+
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 
 BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -41,17 +41,16 @@ XPCOMUtils.defineLazyModuleGetters(this,
   AddonSettings: "resource://gre/modules/addons/AddonSettings.jsm",
   AppConstants: "resource://gre/modules/AppConstants.jsm",
   AsyncShutdown: "resource://gre/modules/AsyncShutdown.jsm",
   ExtensionPermissions: "resource://gre/modules/ExtensionPermissions.jsm",
   ExtensionProcessScript: "resource://gre/modules/ExtensionProcessScript.jsm",
   ExtensionStorage: "resource://gre/modules/ExtensionStorage.jsm",
   ExtensionStorageIDB: "resource://gre/modules/ExtensionStorageIDB.jsm",
   ExtensionTelemetry: "resource://gre/modules/ExtensionTelemetry.jsm",
-  ExtensionTestCommon: "resource://testing-common/ExtensionTestCommon.jsm",
   FileSource: "resource://gre/modules/L10nRegistry.jsm",
   L10nRegistry: "resource://gre/modules/L10nRegistry.jsm",
   Log: "resource://gre/modules/Log.jsm",
   MessageChannel: "resource://gre/modules/MessageChannel.jsm",
   NetUtil: "resource://gre/modules/NetUtil.jsm",
   OS: "resource://gre/modules/osfile.jsm",
   PluralForm: "resource://gre/modules/PluralForm.jsm",
   Schemas: "resource://gre/modules/Schemas.jsm",
@@ -1501,28 +1500,16 @@ class Extension extends ExtensionData {
       }
       if (!frameLoader && view.xulBrowser) {
         frameLoader = view.xulBrowser.frameLoader;
       }
     }
     return frameLoader || ExtensionParent.DebugUtils.getFrameLoader(this.id);
   }
 
-  static generateXPI(data) {
-    return ExtensionTestCommon.generateXPI(data);
-  }
-
-  static generateZipFile(files, baseName = "generated-extension.xpi") {
-    return ExtensionTestCommon.generateZipFile(files, baseName);
-  }
-
-  static generate(data) {
-    return ExtensionTestCommon.generate(data);
-  }
-
   on(hook, f) {
     return this.emitter.on(hook, f);
   }
 
   off(hook, f) {
     return this.emitter.off(hook, f);
   }
 
--- a/toolkit/components/extensions/ExtensionXPCShellUtils.jsm
+++ b/toolkit/components/extensions/ExtensionXPCShellUtils.jsm
@@ -15,30 +15,28 @@ const {XPCOMUtils} = ChromeUtils.import(
 ChromeUtils.import("resource://gre/modules/CustomElementsListener.jsm", null);
 
 ChromeUtils.defineModuleGetter(this, "AddonManager",
                                "resource://gre/modules/AddonManager.jsm");
 ChromeUtils.defineModuleGetter(this, "AddonTestUtils",
                                "resource://testing-common/AddonTestUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "ContentTask",
                                "resource://testing-common/ContentTask.jsm");
-ChromeUtils.defineModuleGetter(this, "Extension",
-                               "resource://gre/modules/Extension.jsm");
+ChromeUtils.defineModuleGetter(this, "ExtensionTestCommon",
+                               "resource://testing-common/ExtensionTestCommon.jsm");
 ChromeUtils.defineModuleGetter(this, "FileUtils",
                                "resource://gre/modules/FileUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "MessageChannel",
                                "resource://gre/modules/MessageChannel.jsm");
 ChromeUtils.defineModuleGetter(this, "Schemas",
                                "resource://gre/modules/Schemas.jsm");
 ChromeUtils.defineModuleGetter(this, "Services",
                                "resource://gre/modules/Services.jsm");
 ChromeUtils.defineModuleGetter(this, "TestUtils",
                                "resource://testing-common/TestUtils.jsm");
-ChromeUtils.defineModuleGetter(this, "ExtensionTestCommon",
-                               "resource://testing-common/ExtensionTestCommon.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "Management", () => {
   const {Management} = ChromeUtils.import("resource://gre/modules/Extension.jsm", null);
   return Management;
 });
 
 Services.mm.loadFrameScript("chrome://global/content/browser-content.js", true, true);
 
@@ -574,17 +572,17 @@ class AOMExtensionWrapper extends Extens
   async upgrade(data) {
     this.startupPromise = new Promise(resolve => {
       this.resolveStartup = resolve;
     });
     this.state = "restarting";
 
     await this._flushCache();
 
-    let xpiFile = Extension.generateXPI(data);
+    let xpiFile = ExtensionTestCommon.generateXPI(data);
 
     this.cleanupFiles.push(xpiFile);
 
     return this._install(xpiFile);
   }
 }
 
 class InstallableWrapper extends AOMExtensionWrapper {
@@ -780,22 +778,22 @@ var ExtensionTestUtils = {
 
     let manager = Cc["@mozilla.org/addons/integration;1"].getService(Ci.nsIObserver)
                                                          .QueryInterface(Ci.nsITimerCallback);
     manager.observe(null, "addons-startup", null);
   },
 
   loadExtension(data) {
     if (data.useAddonManager) {
-      let xpiFile = Extension.generateXPI(data);
+      let xpiFile = ExtensionTestCommon.generateXPI(data);
 
       return this.loadExtensionXPI(xpiFile, data.useAddonManager, data.amInstallTelemetryInfo);
     }
 
-    let extension = Extension.generate(data);
+    let extension = ExtensionTestCommon.generate(data);
 
     return new ExtensionWrapper(this.currentScope, extension);
   },
 
   loadExtensionXPI(xpiFile, useAddonManager = "temporary", installTelemetryInfo) {
     return new InstallableWrapper(this.currentScope, xpiFile, useAddonManager, installTelemetryInfo);
   },
 
--- a/toolkit/components/extensions/test/xpcshell/test_ext_json_parser.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_json_parser.js
@@ -1,16 +1,16 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 add_task(async function test_json_parser() {
   const ID = "json@test.web.extension";
 
-  let xpi = Extension.generateXPI({
+  let xpi = AddonTestUtils.createTempWebExtensionFile({
     files: {
       "manifest.json": String.raw`{
         // This is a manifest.
         "applications": {"gecko": {"id": "${ID}"}},
         "name": "This \" is // not a comment",
         "version": "0.1\\" // , "description": "This is not a description"
       }`,
     },
@@ -28,10 +28,9 @@ add_task(async function test_json_parser
   let extension = new ExtensionData(uri);
 
   await extension.parseManifest();
 
   Assert.deepEqual(extension.rawManifest, expectedManifest,
                    "Manifest with correctly-filtered comments");
 
   Services.obs.notifyObservers(xpi, "flush-cache-entry");
-  xpi.remove(false);
 });
--- a/toolkit/components/extensions/test/xpcshell/test_ext_permissions.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_permissions.js
@@ -1,13 +1,12 @@
 "use strict";
 
 const {AddonManager} = ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
 const {ExtensionPermissions} = ChromeUtils.import("resource://gre/modules/ExtensionPermissions.jsm");
-const {OS} = ChromeUtils.import("resource://gre/modules/osfile.jsm");
 
 const BROWSER_PROPERTIES = "chrome://browser/locale/browser.properties";
 
 AddonTestUtils.init(this);
 AddonTestUtils.overrideCertDB();
 AddonTestUtils.createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
 
 Services.prefs.setBoolPref("extensions.webextensions.background-delayed-startup", false);
@@ -484,17 +483,17 @@ add_task(async function test_permissions
       origins: ["https://test2.example.com/*"],
     });
     let result = await extension.awaitMessage("result");
     equal(result, true, "request() for optional permissions succeeded");
   });
 
   const PERMS = ["history", "tabs"];
   const ORIGINS = ["https://test1.example.com/*", "https://test3.example.com/"];
-  let xpi = Extension.generateXPI({
+  let xpi = AddonTestUtils.createTempWebExtensionFile({
     background,
     manifest: {
       name: "permissions test",
       description: "permissions test",
       manifest_version: 2,
       version: "2.0",
 
       applications: {gecko: {id: extension.id}},
@@ -521,17 +520,16 @@ add_task(async function test_permissions
   await extension.awaitStartup();
 
   notEqual(perminfo, undefined, "Permission handler was invoked");
   let perms = perminfo.addon.userPermissions;
   deepEqual(perms.permissions, PERMS, "Update details includes only manifest api permissions");
   deepEqual(perms.origins, ORIGINS, "Update details includes only manifest origin permissions");
 
   await extension.unload();
-  await OS.File.remove(xpi.path);
 });
 
 // Check that internal permissions can not be set and are not returned by the API.
 add_task(async function test_internal_permisisons() {
   Services.prefs.setBoolPref("extensions.allowPrivateBrowsingByDefault", false);
 
   function background() {
     browser.test.onMessage.addListener(async (method, arg) => {
--- a/toolkit/components/extensions/test/xpcshell/test_locale_data.js
+++ b/toolkit/components/extensions/test/xpcshell/test_locale_data.js
@@ -1,27 +1,14 @@
 "use strict";
 
-const {Extension, ExtensionData} = ChromeUtils.import("resource://gre/modules/Extension.jsm");
-
-/* globals ExtensionData */
-
-const uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
+const {ExtensionData} = ChromeUtils.import("resource://gre/modules/Extension.jsm");
 
 async function generateAddon(data) {
-  let id = uuidGenerator.generateUUID().number;
-
-  data = Object.assign({}, data);
-  data.manifest = Object.assign({applications: {gecko: {id}}}, data.manifest || {});
-
-  let xpi = Extension.generateXPI(data);
-  registerCleanupFunction(() => {
-    Services.obs.notifyObservers(xpi, "flush-cache-entry");
-    xpi.remove(false);
-  });
+  let xpi = AddonTestUtils.createTempWebExtensionFile(data);
 
   let fileURI = Services.io.newFileURI(xpi);
   let jarURI = NetUtil.newURI(`jar:${fileURI.spec}!/`);
 
   let extension = new ExtensionData(jarURI);
   await extension.loadManifest();
 
   return extension;
--- a/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
+++ b/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
@@ -31,18 +31,16 @@ XPCOMUtils.defineLazyGetter(this, "Manag
   let {Management} = ChromeUtils.import("resource://gre/modules/Extension.jsm", null);
   return Management;
 });
 
 ChromeUtils.defineModuleGetter(this, "FileTestUtils",
                                "resource://testing-common/FileTestUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "HttpServer",
                                "resource://testing-common/httpd.js");
-ChromeUtils.defineModuleGetter(this, "InstallRDF",
-                               "resource://gre/modules/addons/RDFManifestConverter.jsm");
 ChromeUtils.defineModuleGetter(this, "MockRegistrar",
                                "resource://testing-common/MockRegistrar.jsm");
 
 XPCOMUtils.defineLazyServiceGetters(this, {
   aomStartup: ["@mozilla.org/addons/addon-manager-startup;1", "amIAddonManagerStartup"],
   proxyService: ["@mozilla.org/network/protocol-proxy-service;1", "nsIProtocolProxyService"],
   uuidGen: ["@mozilla.org/uuid-generator;1", "nsIUUIDGenerator"],
 });
@@ -360,19 +358,16 @@ var AddonTestUtils = {
     Services.prefs.setCharPref("extensions.update.url", "http://127.0.0.1/updateURL");
     Services.prefs.setCharPref("extensions.update.background.url", "http://127.0.0.1/updateBackgroundURL");
     Services.prefs.setCharPref("extensions.blocklist.url", "http://127.0.0.1/blocklistURL");
     Services.prefs.setCharPref("services.settings.server", "http://localhost/dummy-kinto/v1");
 
     // By default ignore bundled add-ons
     Services.prefs.setBoolPref("extensions.installDistroAddons", false);
 
-    // By default don't check for hotfixes
-    Services.prefs.setCharPref("extensions.hotfix.id", "");
-
     // Ensure signature checks are enabled by default
     Services.prefs.setBoolPref("xpinstall.signatures.required", true);
 
 
     // Write out an empty blocklist.xml file to the profile to ensure nothing
     // is blocklisted by default
     var blockFile = OS.Path.join(this.profileDir.path, "blocklist.xml");
 
@@ -610,58 +605,43 @@ var AddonTestUtils = {
         browserTabsRemoteAutostart: false,
       },
     });
     this.appInfo = AppInfo.getAppInfo();
   },
 
   getManifestURI(file) {
     if (file.isDirectory()) {
-      file.append("install.rdf");
-      if (file.exists()) {
-        return NetUtil.newURI(file);
-      }
-
       file.leafName = "manifest.json";
       if (file.exists())
         return NetUtil.newURI(file);
 
       throw new Error("No manifest file present");
     }
 
     let zip = ZipReader(file);
     try {
       let uri = NetUtil.newURI(file);
 
-      if (zip.hasEntry("install.rdf")) {
-        return NetUtil.newURI(`jar:${uri.spec}!/install.rdf`);
-      }
-
       if (zip.hasEntry("manifest.json")) {
         return NetUtil.newURI(`jar:${uri.spec}!/manifest.json`);
       }
 
       throw new Error("No manifest file present");
     } finally {
       zip.close();
     }
   },
 
   getIDFromExtension(file) {
     return this.getIDFromManifest(this.getManifestURI(file));
   },
 
   async getIDFromManifest(manifestURI) {
     let body = await fetch(manifestURI.spec);
-
-    if (manifestURI.spec.endsWith(".rdf")) {
-      let manifest = InstallRDF.loadFromBuffer(await body.arrayBuffer()).decode();
-      return manifest.id;
-    }
-
     let manifest = await body.json();
     try {
       return manifest.applications.gecko.id;
     } catch (e) {
       // IDs for WebExtensions are extracted from the certificate when
       // not present in the manifest, so just generate a random one.
       return uuidGen.generateUUID().number;
     }
@@ -946,68 +926,16 @@ var AddonTestUtils = {
     let items = [];
 
     items.push(this._writeProps(data, ["name", "description", "creator", "homepageURL"]));
     items.push(this._writeArrayProps(data, ["developer", "translator", "contributor"]));
 
     return items.join("");
   },
 
-  createInstallRDF(data) {
-    let defaults = {
-      bootstrap: true,
-      version: "1.0",
-      name: `Test Extension ${data.id}`,
-      targetApplications: [
-        {
-          "id": "xpcshell@tests.mozilla.org",
-          "minVersion": "1",
-          "maxVersion": "64.*",
-        },
-      ],
-    };
-
-    var rdf = '<?xml version="1.0"?>\n';
-    rdf += '<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"\n' +
-           '     xmlns:em="http://www.mozilla.org/2004/em-rdf#">\n';
-
-    rdf += '<Description about="urn:mozilla:install-manifest">\n';
-
-    data = Object.assign({}, defaults, data);
-
-    let props = ["id", "version", "type", "internalName", "updateURL",
-                 "optionsURL", "optionsType", "aboutURL", "iconURL",
-                 "skinnable", "bootstrap", "strictCompatibility"];
-    rdf += this._writeProps(data, props);
-
-    rdf += this._writeLocaleStrings(data);
-
-    for (let platform of data.targetPlatforms || [])
-      rdf += escaped`<em:targetPlatform>${platform}</em:targetPlatform>\n`;
-
-    for (let app of data.targetApplications || []) {
-      rdf += "<em:targetApplication><Description>\n";
-      rdf += this._writeProps(app, ["id", "minVersion", "maxVersion"]);
-      rdf += "</Description></em:targetApplication>\n";
-    }
-
-    for (let localized of data.localized || []) {
-      rdf += "<em:localized><Description>\n";
-      rdf += this._writeArrayProps(localized, ["locale"]);
-      rdf += this._writeLocaleStrings(localized);
-      rdf += "</Description></em:localized>\n";
-    }
-
-    for (let dep of data.dependencies || [])
-      rdf += escaped`<em:dependency><Description em:id="${dep}"/></em:dependency>\n`;
-
-    rdf += "</Description>\n</RDF>\n";
-    return rdf;
-  },
-
   /**
    * Recursively create all directories up to and including the given
    * path, if they do not exist.
    *
    * @param {string} path The path of the directory to create.
    * @returns {Promise} Resolves when all directories have been created.
    */
   recursiveMakeDir(path) {
@@ -1081,19 +1009,16 @@ var AddonTestUtils = {
 
       await OS.File.writeAtomic(OS.Path.join(dirPath, leafName), data);
     }
 
     return nsFile(dir);
   },
 
   promiseWriteFilesToExtension(dir, id, files, unpacked = this.testUnpacked) {
-    if (typeof files["install.rdf"] === "object")
-      files["install.rdf"] = this.createInstallRDF(files["install.rdf"]);
-
     if (unpacked) {
       let path = OS.Path.join(dir, id);
 
       return this.promiseWriteFilesToDir(path, files);
     }
 
     let xpi = OS.Path.join(dir, `${id}.xpi`);
 
@@ -1117,19 +1042,16 @@ var AddonTestUtils = {
    * returns the nsIFile for it. The file will be deleted when the test completes.
    *
    * @param {object} files
    *          The object holding data about the add-on
    * @return {nsIFile} A file pointing to the created XPI file
    */
   createTempXPIFile(files) {
     let file = this.allocTempXPIFile();
-    if (typeof files["install.rdf"] === "object")
-      files["install.rdf"] = this.createInstallRDF(files["install.rdf"]);
-
     this.writeFilesToZip(file.path, files);
     return file;
   },
 
   /**
    * Creates an XPI file for some WebExtension data in the temporary directory and
    * returns the nsIFile for it. The file will be deleted when the test completes.
    *
@@ -1276,18 +1198,17 @@ var AddonTestUtils = {
       dir.append(id);
     else
       dir.append(`${id}.xpi`);
     return dir;
   },
 
   /**
    * Sets the last modified time of the extension, usually to trigger an update
-   * of its metadata. If the extension is unpacked, this function assumes that
-   * the extension contains only the install.rdf file.
+   * of its metadata.
    *
    * @param {nsIFile} ext A file pointing to either the packed extension or its unpacked directory.
    * @param {number} time The time to which we set the lastModifiedTime of the extension
    *
    * @deprecated Please use promiseSetExtensionModifiedTime instead
    */
   setExtensionModifiedTime(ext, time) {
     ext.lastModifiedTime = time;
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/data/BootstrapMonitor.jsm
+++ /dev/null
@@ -1,30 +0,0 @@
-const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
-
-var EXPORTED_SYMBOLS = [ "monitor" ];
-
-function notify(event, originalMethod, data, reason) {
-  let info = {
-    event,
-    data: Object.assign({}, data, {
-      installPath: data.installPath.path,
-      resourceURI: data.resourceURI.spec,
-    }),
-    reason,
-  };
-
-  let subject = {wrappedJSObject: {data}};
-
-  Services.obs.notifyObservers(subject, "bootstrapmonitor-event", JSON.stringify(info));
-
-  // If the bootstrap scope already declares a method call it
-  if (originalMethod)
-    originalMethod(data, reason);
-}
-
-// Allows a simple one-line bootstrap script:
-// Components.utils.import("resource://xpcshelldata/bootstrapmonitor.jsm").monitor(this);
-var monitor = function(scope, methods = ["install", "startup", "shutdown", "uninstall"]) {
-  for (let event of methods) {
-    scope[event] = notify.bind(null, event, scope[event]);
-  }
-};
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/data/from_sources/bootstrap.js
+++ /dev/null
@@ -1,1 +0,0 @@
-ChromeUtils.import("resource://xpcshell-data/BootstrapMonitor.jsm", {}).monitor(this);
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/data/from_sources/install.rdf
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0"?>
-
-<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
-
-  <Description about="urn:mozilla:install-manifest">
-    <em:id>bootstrap1@tests.mozilla.org</em:id>
-    <em:version>1.0</em:version>
-    <em:bootstrap>true</em:bootstrap>
-
-    <!-- Front End MetaData -->
-    <em:name>Test Bootstrap 1</em:name>
-    <em:description>Test Description</em:description>
-
-    <em:iconURL>chrome://foo/skin/icon.png</em:iconURL>
-    <em:aboutURL>chrome://foo/content/about.xul</em:aboutURL>
-    <em:optionsURL>chrome://foo/content/options.xul</em:optionsURL>
-
-    <em:targetApplication>
-      <Description>
-        <em:id>xpcshell@tests.mozilla.org</em:id>
-        <em:minVersion>1</em:minVersion>
-        <em:maxVersion>1</em:maxVersion>
-      </Description>
-    </em:targetApplication>
-
-  </Description>
-</RDF>
--- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
@@ -58,19 +58,17 @@ ChromeUtils.defineModuleGetter(this, "Te
 
 XPCOMUtils.defineLazyServiceGetter(this, "aomStartup",
                                    "@mozilla.org/addons/addon-manager-startup;1",
                                    "amIAddonManagerStartup");
 
 const {
   createAppInfo,
   createHttpServer,
-  createInstallRDF,
   createTempWebExtensionFile,
-  createUpdateRDF,
   getFileForAddon,
   manuallyInstall,
   manuallyUninstall,
   overrideBuiltIns,
   promiseAddonEvent,
   promiseCompleteAllInstalls,
   promiseCompleteInstall,
   promiseConsoleOutput,
@@ -158,213 +156,18 @@ Object.defineProperty(this, "TEST_UNPACK
 // We need some internal bits of AddonManager
 var AMscope = ChromeUtils.import("resource://gre/modules/AddonManager.jsm", null);
 var { AddonManager, AddonManagerInternal, AddonManagerPrivate } = AMscope;
 
 const promiseAddonByID = AddonManager.getAddonByID;
 const promiseAddonsByIDs = AddonManager.getAddonsByIDs;
 
 var gPort = null;
-var gUrlToFileMap = {};
 
-// Map resource://xpcshell-data/ to the data directory
-var resHandler = Services.io.getProtocolHandler("resource")
-                         .QueryInterface(Ci.nsISubstitutingProtocolHandler);
-// Allow non-existent files because of bug 1207735
-var dataURI = NetUtil.newURI(do_get_file("data", true));
-resHandler.setSubstitution("xpcshell-data", dataURI);
-
-function isManifestRegistered(file) {
-  let manifests = Components.manager.getManifestLocations();
-  for (let i = 0; i < manifests.length; i++) {
-    let manifest = manifests.queryElementAt(i, Ci.nsIURI);
-
-    // manifest is the url to the manifest file either in an XPI or a directory.
-    // We want the location of the XPI or directory itself.
-    if (manifest instanceof Ci.nsIJARURI) {
-      manifest = manifest.JARFile.QueryInterface(Ci.nsIFileURL).file;
-    } else if (manifest instanceof Ci.nsIFileURL) {
-      manifest = manifest.file.parent;
-    } else {
-      continue;
-    }
-
-    if (manifest.equals(file))
-      return true;
-  }
-  return false;
-}
-
-const BOOTSTRAP_MONITOR_BOOTSTRAP_JS = `
-  ChromeUtils.import("resource://xpcshell-data/BootstrapMonitor.jsm").monitor(this);
-`;
-
-// Listens to messages from bootstrap.js telling us what add-ons were started
-// and stopped etc. and performs some sanity checks that only installed add-ons
-// are started etc.
-this.BootstrapMonitor = {
-  inited: false,
-
-  // Contain the current state of add-ons in the system
-  installed: new Map(),
-  started: new Map(),
-
-  // Contain the last state of shutdown and uninstall calls for an add-on
-  stopped: new Map(),
-  uninstalled: new Map(),
-
-  startupPromises: [],
-  installPromises: [],
-
-  restartfulIds: new Set(),
-
-  init() {
-    this.inited = true;
-    Services.obs.addObserver(this, "bootstrapmonitor-event");
-  },
-
-  shutdownCheck() {
-    if (!this.inited)
-      return;
-
-    Assert.equal(this.started.size, 0);
-  },
-
-  clear(id) {
-    this.installed.delete(id);
-    this.started.delete(id);
-    this.stopped.delete(id);
-    this.uninstalled.delete(id);
-  },
-
-  promiseAddonStartup(id) {
-    return new Promise(resolve => {
-      this.startupPromises.push(resolve);
-    });
-  },
-
-  promiseAddonInstall(id) {
-    return new Promise(resolve => {
-      this.installPromises.push(resolve);
-    });
-  },
-
-  checkMatches(cached, current) {
-    Assert.notEqual(cached, undefined);
-    Assert.equal(current.data.version, cached.data.version);
-    Assert.equal(current.data.installPath, cached.data.installPath);
-    Assert.ok(Services.io.newURI(current.data.resourceURI).equals(Services.io.newURI(cached.data.resourceURI)),
-              `Resource URIs match: "${current.data.resourceURI}" == "${cached.data.resourceURI}"`);
-  },
-
-  checkAddonStarted(id, version = undefined) {
-    let started = this.started.get(id);
-    Assert.notEqual(started, undefined);
-    if (version != undefined)
-      Assert.equal(started.data.version, version);
-
-    // Chrome should be registered by now
-    let installPath = new FileUtils.File(started.data.installPath);
-    let isRegistered = isManifestRegistered(installPath);
-    Assert.ok(isRegistered);
-  },
-
-  checkAddonNotStarted(id) {
-    Assert.ok(!this.started.has(id));
-  },
-
-  checkAddonInstalled(id, version = undefined) {
-    const installed = this.installed.get(id);
-    notEqual(installed, undefined);
-    if (version !== undefined) {
-      equal(installed.data.version, version);
-    }
-    return installed;
-  },
-
-  checkAddonNotInstalled(id) {
-    Assert.ok(!this.installed.has(id));
-  },
-
-  observe(subject, topic, data) {
-    let info = JSON.parse(data);
-    let id = info.data.id;
-    let installPath = new FileUtils.File(info.data.installPath);
-
-    if (subject && subject.wrappedJSObject) {
-      // NOTE: in some of the new tests, we need to received the real objects instead of
-      // their JSON representations, but most of the current tests expect intallPath
-      // and resourceURI to have been converted to strings.
-      info.data = Object.assign({}, subject.wrappedJSObject.data, {
-        installPath: info.data.installPath,
-        resourceURI: info.data.resourceURI,
-      });
-    }
-
-    // If this is the install event the add-ons shouldn't already be installed
-    if (info.event == "install") {
-      this.checkAddonNotInstalled(id);
-
-      this.installed.set(id, info);
-
-      for (let resolve of this.installPromises)
-        resolve();
-      this.installPromises = [];
-    } else {
-      this.checkMatches(this.installed.get(id), info);
-    }
-
-    // If this is the shutdown event than the add-on should already be started
-    if (info.event == "shutdown") {
-      this.checkMatches(this.started.get(id), info);
-
-      this.started.delete(id);
-      this.stopped.set(id, info);
-
-      // Chrome should still be registered at this point
-      let isRegistered = isManifestRegistered(installPath);
-      Assert.ok(isRegistered);
-
-      // XPIProvider doesn't bother unregistering chrome on app shutdown but
-      // since we simulate restarts we must do so manually to keep the registry
-      // consistent.
-      if (info.reason == 2 /* APP_SHUTDOWN */)
-        Components.manager.removeBootstrappedManifestLocation(installPath);
-    } else {
-      this.checkAddonNotStarted(id);
-    }
-
-    if (info.event == "uninstall") {
-      // We currently support registering, but not unregistering,
-      // restartful add-on manifests during xpcshell AOM "restarts".
-      if (!this.restartfulIds.has(id)) {
-        // Chrome should be unregistered at this point
-        let isRegistered = isManifestRegistered(installPath);
-        Assert.ok(!isRegistered);
-      }
-
-      this.installed.delete(id);
-      this.uninstalled.set(id, info);
-    } else if (info.event == "startup") {
-      this.started.set(id, info);
-
-      // Chrome should be registered at this point
-      let isRegistered = isManifestRegistered(installPath);
-      Assert.ok(isRegistered);
-
-      for (let resolve of this.startupPromises)
-        resolve();
-      this.startupPromises = [];
-    }
-  },
-};
-
-AddonTestUtils.on("addon-manager-shutdown", () => BootstrapMonitor.shutdownCheck());
-
-var SlightlyLessDodgyBootstrapMonitor = {
+var BootstrapMonitor = {
   started: new Map(),
   stopped: new Map(),
   installed: new Map(),
   uninstalled: new Map(),
 
   init() {
     this.onEvent = this.onEvent.bind(this);
 
@@ -789,111 +592,16 @@ function isThemeInAddonsList(aDir, aId) 
   return AddonTestUtils.addonsList.hasTheme(aDir, aId);
 }
 
 function isExtensionInBootstrappedList(aDir, aId) {
   return AddonTestUtils.addonsList.hasExtension(aDir, aId);
 }
 
 /**
- * Writes an install.rdf manifest into a directory using the properties passed
- * in a JS object. The objects should contain a property for each property to
- * appear in the RDF. The object may contain an array of objects with id,
- * minVersion and maxVersion in the targetApplications property to give target
- * application compatibility.
- *
- * @param   aData
- *          The object holding data about the add-on
- * @param   aDir
- *          The directory to add the install.rdf to
- * @param   aId
- *          An optional string to override the default installation aId
- * @param   aExtraFile
- *          An optional dummy file to create in the directory
- * @return  An nsIFile for the directory in which the add-on is installed.
- */
-async function promiseWriteInstallRDFToDir(aData, aDir, aId = aData.id, aExtraFile = null) {
-  let files = {
-    "install.rdf": AddonTestUtils.createInstallRDF(aData),
-  };
-  if (typeof aExtraFile === "object")
-    Object.assign(files, aExtraFile);
-  else
-    files[aExtraFile] = "";
-
-  let dir = aDir.clone();
-  dir.append(aId);
-
-  await AddonTestUtils.promiseWriteFilesToDir(dir.path, files);
-  return dir;
-}
-
-/**
- * Writes an install.rdf manifest into a packed extension using the properties passed
- * in a JS object. The objects should contain a property for each property to
- * appear in the RDF. The object may contain an array of objects with id,
- * minVersion and maxVersion in the targetApplications property to give target
- * application compatibility.
- *
- * @param   aData
- *          The object holding data about the add-on
- * @param   aDir
- *          The install directory to add the extension to
- * @param   aId
- *          An optional string to override the default installation aId
- * @param   aExtraFile
- *          An optional dummy file to create in the extension
- * @return  A file pointing to where the extension was installed
- */
-async function promiseWriteInstallRDFToXPI(aData, aDir, aId = aData.id, aExtraFile = null) {
-  let files = {
-    "install.rdf": AddonTestUtils.createInstallRDF(aData),
-  };
-  if (typeof aExtraFile === "object")
-    Object.assign(files, aExtraFile);
-  else
-  if (aExtraFile)
-    files[aExtraFile] = "";
-
-  if (!aDir.exists())
-    aDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
-
-  var file = aDir.clone();
-  file.append(`${aId}.xpi`);
-
-  AddonTestUtils.writeFilesToZip(file.path, files);
-
-  return file;
-}
-
-/**
- * Writes an install.rdf manifest into an extension using the properties passed
- * in a JS object. The objects should contain a property for each property to
- * appear in the RDF. The object may contain an array of objects with id,
- * minVersion and maxVersion in the targetApplications property to give target
- * application compatibility.
- *
- * @param   aData
- *          The object holding data about the add-on
- * @param   aDir
- *          The install directory to add the extension to
- * @param   aId
- *          An optional string to override the default installation aId
- * @param   aExtraFile
- *          An optional dummy file to create in the extension
- * @return  A file pointing to where the extension was installed
- */
-function promiseWriteInstallRDFForExtension(aData, aDir, aId, aExtraFile) {
-  if (TEST_UNPACKED) {
-    return promiseWriteInstallRDFToDir(aData, aDir, aId, aExtraFile);
-  }
-  return promiseWriteInstallRDFToXPI(aData, aDir, aId, aExtraFile);
-}
-
-/**
  * Writes a manifest.json manifest into an extension using the properties passed
  * in a JS object.
  *
  * @param   aManifest
  *          The data to write
  * @param   aDir
  *          The install directory to add the extension to
  * @param   aId
@@ -902,40 +610,16 @@ function promiseWriteInstallRDFForExtens
  */
 function promiseWriteWebManifestForExtension(aData, aDir, aId = aData.applications.gecko.id) {
   let files = {
     "manifest.json": JSON.stringify(aData),
   };
   return AddonTestUtils.promiseWriteFilesToExtension(aDir.path, aId, files);
 }
 
-/**
- * Creates an XPI file for some manifest data in the temporary directory and
- * returns the nsIFile for it. The file will be deleted when the test completes.
- *
- * @param   aData
- *          The object holding data about the add-on
- * @return  A file pointing to the created XPI file
- */
-function createTempXPIFile(aData, aExtraFile) {
-  let files = {
-    "install.rdf": aData,
-  };
-  if (typeof aExtraFile == "object")
-    Object.assign(files, aExtraFile);
-  else if (aExtraFile)
-    files[aExtraFile] = "";
-
-  return AddonTestUtils.createTempXPIFile(files);
-}
-
-function promiseInstallXPI(installRDF) {
-  return AddonTestUtils.promiseInstallXPI({"install.rdf": installRDF});
-}
-
 var gExpectedEvents = {};
 var gExpectedInstalls = [];
 var gNext = null;
 
 function getExpectedEvent(aId) {
   if (!(aId in gExpectedEvents))
     do_throw("Wasn't expecting events for " + aId);
   if (gExpectedEvents[aId].length == 0)
@@ -1185,45 +869,16 @@ function ensure_test_completed() {
     if (gExpectedEvents[i].length > 0)
       do_throw(`Didn't see all the expected events for ${i}: Still expecting ${gExpectedEvents[i]}`);
   }
   gExpectedEvents = {};
   if (gExpectedInstalls)
     Assert.equal(gExpectedInstalls.length, 0);
 }
 
-/**
- * A helper method to install an array of AddonInstall to completion and then
- * call a provided callback.
- *
- * @param   aInstalls
- *          The array of AddonInstalls to install
- * @param   aCallback
- *          The callback to call when all installs have finished
- */
-function completeAllInstalls(aInstalls, aCallback) {
-  promiseCompleteAllInstalls(aInstalls).then(aCallback);
-}
-
-/**
- * A helper method to install an array of files and call a callback after the
- * installs are completed.
- *
- * @param   aFiles
- *          The array of files to install
- * @param   aCallback
- *          The callback to call when all installs have finished
- * @param   aIgnoreIncompatible
- *          Optional parameter to ignore add-ons that are incompatible in
- *          aome way with the application
- */
-function installAllFiles(aFiles, aCallback, aIgnoreIncompatible) {
-  promiseInstallAllFiles(aFiles, aIgnoreIncompatible).then(aCallback);
-}
-
 const EXTENSIONS_DB = "extensions.json";
 var gExtensionsJSON = gProfD.clone();
 gExtensionsJSON.append(EXTENSIONS_DB);
 
 async function promiseInstallWebExtension(aData) {
   let addonFile = createTempWebExtensionFile(aData);
 
   let {addon} = await promiseInstallFile(addonFile);
@@ -1244,34 +899,16 @@ function copyBlocklistToProfile(blocklis
   var dest = gProfD.clone();
   dest.append("blocklist.xml");
   if (dest.exists())
     dest.remove(false);
   blocklistFile.copyTo(gProfD, "blocklist.xml");
   dest.lastModifiedTime = Date.now();
 }
 
-// Make sure that a given path does not exist
-function pathShouldntExist(file) {
-  if (file.exists()) {
-    do_throw(`Test cleanup: path ${file.path} exists when it should not`);
-  }
-}
-
-// Wrap a function (typically a callback) to catch and report exceptions
-function do_exception_wrap(func) {
-  return function() {
-    try {
-      func.apply(null, arguments);
-    } catch (e) {
-      do_report_unexpected_exception(e);
-    }
-  };
-}
-
 /**
  * Change the schema version of the JSON extensions database
  */
 async function changeXPIDBVersion(aNewVersion) {
   let json = await loadJSON(gExtensionsJSON.path);
   json.schemaVersion = aNewVersion;
   await saveJSON(json, gExtensionsJSON.path);
 }
@@ -1297,27 +934,16 @@ async function loadJSON(aFile) {
  * Raw save of a JSON blob to file
  */
 async function saveJSON(aData, aFile) {
   info("Starting to save JSON file " + aFile);
   await OS.File.writeAtomic(aFile, new TextEncoder().encode(JSON.stringify(aData, null, 2)));
   info("Done saving JSON file " + aFile.path);
 }
 
-/**
- * Create a callback function that calls do_execute_soon on an actual callback and arguments
- */
-function callback_soon(aFunction) {
-  return function(...args) {
-    executeSoon(function() {
-      aFunction.apply(null, args);
-    }, aFunction.name ? "delayed callback " + aFunction.name : "delayed callback");
-  };
-}
-
 XPCOMUtils.defineLazyServiceGetter(this, "pluginHost",
                                   "@mozilla.org/plugin/host;1", "nsIPluginHost");
 
 class MockPluginTag {
   constructor(opts, enabledState = Ci.nsIPluginTag.STATE_ENABLED) {
     this.pluginTag = pluginHost.createFakePlugin({
       handlerURI: "resource://fake-plugin/${Math.random()}.xhtml",
       mimeEntries: [{type: "application/x-fake-plugin"}],
--- a/toolkit/mozapps/extensions/test/xpcshell/test_error.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_error.js
@@ -30,17 +30,17 @@ add_task(async function run_test_2() {
   let install = await AddonManager.getInstallForFile(xpi);
   Assert.notEqual(install, null);
   Assert.equal(install.state, AddonManager.STATE_DOWNLOAD_FAILED);
   Assert.equal(install.error, AddonManager.ERROR_CORRUPT_FILE);
 });
 
 // Checks that an empty file shows an error
 add_task(async function run_test_3() {
-  let xpi = await createTempXPIFile({});
+  let xpi = await AddonTestUtils.createTempXPIFile({});
   let install = await AddonManager.getInstallForFile(xpi);
   Assert.notEqual(install, null);
   Assert.equal(install.state, AddonManager.STATE_DOWNLOAD_FAILED);
   Assert.equal(install.error, AddonManager.ERROR_CORRUPT_FILE);
 });
 
 // Checks that a file that doesn't match its hash shows an error
 add_task(async function run_test_4() {
--- a/toolkit/mozapps/extensions/test/xpcshell/test_moved_extension_metadata.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_moved_extension_metadata.js
@@ -1,12 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
+// This test is disabled but is being kept around so it can eventualy
+// be modernized and re-enabled.  But is uses obsolete test helpers that
+// fail lint, so just skip linting it for now.
+/* eslint-disable */
+
 // This verifies that moving an extension in the filesystem without any other
 // change still keeps updated compatibility information
 
 // The test extension uses an insecure update url.
 Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false);
 // Enable loading extensions from the user and system scopes
 Services.prefs.setIntPref("extensions.enabledScopes",
                           AddonManager.SCOPE_PROFILE + AddonManager.SCOPE_USER);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_systemaddomstartupprefs.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_systemaddomstartupprefs.js
@@ -8,49 +8,48 @@ createAppInfo("xpcshell@tests.mozilla.or
 
 let distroDir = FileUtils.getDir("ProfD", ["sysfeatures", "empty"], true);
 registerDirectory("XREAppFeat", distroDir);
 
 AddonTestUtils.usePrivilegedSignatures = "system";
 
 add_task(initSystemAddonDirs);
 
-let Monitor = SlightlyLessDodgyBootstrapMonitor;
-Monitor.init();
+BootstrapMonitor.init();
 
 add_task(async function setup() {
   let xpi = await getSystemAddonXPI(1, "1.0");
   await AddonTestUtils.manuallyInstall(xpi, distroDir);
 });
 
 add_task(async function systemAddonPreffedOff() {
   const id = "system1@tests.mozilla.org";
   Services.prefs.setBoolPref("extensions.system1.enabled", false);
 
   await overrideBuiltIns({"system": [id]});
 
   await promiseStartupManager();
 
-  Monitor.checkInstalled(id);
-  Monitor.checkNotStarted(id);
+  BootstrapMonitor.checkInstalled(id);
+  BootstrapMonitor.checkNotStarted(id);
 
   await promiseRestartManager();
 
-  Monitor.checkNotStarted(id);
+  BootstrapMonitor.checkNotStarted(id);
 
   await promiseShutdownManager(false);
 });
 
 add_task(async function systemAddonPreffedOn() {
   const id = "system1@tests.mozilla.org";
   Services.prefs.setBoolPref("extensions.system1.enabled", true);
 
   await promiseStartupManager("2.0");
 
-  Monitor.checkInstalled(id);
-  Monitor.checkStarted(id);
+  BootstrapMonitor.checkInstalled(id);
+  BootstrapMonitor.checkStarted(id);
 
   await promiseRestartManager();
 
-  Monitor.checkStarted(id);
+  BootstrapMonitor.checkStarted(id);
 
   await promiseShutdownManager();
 });
--- a/toolkit/mozapps/extensions/test/xpcshell/test_temporary.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_temporary.js
@@ -27,18 +27,17 @@ async function checkEvent(promise, {reas
   equal(event.reason, reason,
         `Expected bootstrap reason ${getReasonName(reason)} got ${getReasonName(event.reason)}`);
 
   for (let [param, value] of Object.entries(params)) {
     equal(event.params[param], value, `Expected value for params.${param}`);
   }
 }
 
-let Monitor = SlightlyLessDodgyBootstrapMonitor;
-Monitor.init();
+BootstrapMonitor.init();
 
 const XPIS = {};
 
 add_task(async function setup() {
   for (let n of [1, 2]) {
     XPIS[n] = await createTempWebExtensionFile({
       manifest: {
         name: "Test",
@@ -82,22 +81,22 @@ add_task(async function test_new_tempora
   });
 
   await AddonManager.installTemporaryAddon(XPIS[1]);
 
   Assert.ok(extInstallCalled);
   Assert.ok(installingCalled);
   Assert.ok(installedCalled);
 
-  const install = Monitor.checkInstalled(ID, "1.0");
+  const install = BootstrapMonitor.checkInstalled(ID, "1.0");
   equal(install.reason, BOOTSTRAP_REASONS.ADDON_INSTALL);
 
-  Monitor.checkStarted(ID, "1.0");
+  BootstrapMonitor.checkStarted(ID, "1.0");
 
-  let info = Monitor.started.get(ID);
+  let info = BootstrapMonitor.started.get(ID);
   Assert.equal(info.reason, BOOTSTRAP_REASONS.ADDON_INSTALL);
 
   let addon = await promiseAddonByID(ID);
 
   checkAddon(ID, addon, {
     version: "1.0",
     name: "Test",
     isCompatible: true,
@@ -114,33 +113,33 @@ add_task(async function test_new_tempora
   await promiseRestartManager();
 
   let shutdown = await onShutdown;
   equal(shutdown.reason, BOOTSTRAP_REASONS.ADDON_UNINSTALL);
 
   let uninstall = await onUninstall;
   equal(uninstall.reason, BOOTSTRAP_REASONS.ADDON_UNINSTALL);
 
-  Monitor.checkNotInstalled(ID);
-  Monitor.checkNotStarted(ID);
+  BootstrapMonitor.checkNotInstalled(ID);
+  BootstrapMonitor.checkNotStarted(ID);
 
   addon = await promiseAddonByID(ID);
   Assert.equal(addon, null);
 
   await promiseRestartManager();
 });
 
 // Install a temporary add-on over the top of an existing add-on.
 // Restart and make sure the existing add-on comes back.
 add_task(async function test_replace_temporary() {
   await promiseInstallFile(XPIS[2]);
   let addon = await promiseAddonByID(ID);
 
-  Monitor.checkInstalled(ID, "2.0");
-  Monitor.checkStarted(ID, "2.0");
+  BootstrapMonitor.checkInstalled(ID, "2.0");
+  BootstrapMonitor.checkStarted(ID, "2.0");
 
   checkAddon(ID, addon, {
     version: "2.0",
     name: "Test",
     isCompatible: true,
     appDisabled: false,
     isActive: true,
     type: "extension",
@@ -257,18 +256,18 @@ add_task(async function test_replace_tem
         // the browser restart when a temporary addon is removed.  See
         // bug 1359558 for detailed reasoning.
         reason: BOOTSTRAP_REASONS.APP_STARTUP,
         params: {
           version: "2.0",
         },
       });
 
-      Monitor.checkInstalled(ID, "2.0");
-      Monitor.checkStarted(ID, "2.0");
+      BootstrapMonitor.checkInstalled(ID, "2.0");
+      BootstrapMonitor.checkStarted(ID, "2.0");
 
       addon = await promiseAddonByID(ID);
 
       // existing add-on is back
       checkAddon(ID, addon, {
         version: "2.0",
         name: "Test",
         isCompatible: true,
@@ -282,18 +281,18 @@ add_task(async function test_replace_tem
       Services.obs.notifyObservers(target, "flush-cache-entry");
       target.remove(true);
     }
   }
 
   // remove original add-on
   await addon.uninstall();
 
-  Monitor.checkNotInstalled(ID);
-  Monitor.checkNotStarted(ID);
+  BootstrapMonitor.checkNotInstalled(ID);
+  BootstrapMonitor.checkNotStarted(ID);
 
   await promiseRestartManager();
 });
 
 // Test that loading from the same path multiple times work
 add_task(async function test_samefile() {
   // File locking issues on Windows, ugh
   if (AppConstants.platform == "win") {
@@ -365,18 +364,18 @@ add_task(async function test_replace_per
   await promiseInstallWebExtension({
     manifest: {
       applications: {gecko: {id: ID}},
       version: "1.0",
       name: "Test Bootstrap 1",
     },
   });
 
-  Monitor.checkInstalled(ID, "1.0");
-  Monitor.checkStarted(ID, "1.0");
+  BootstrapMonitor.checkInstalled(ID, "1.0");
+  BootstrapMonitor.checkStarted(ID, "1.0");
 
   let unpacked_addon = gTmpD.clone();
   unpacked_addon.append(ID);
 
   let files = ExtensionTestCommon.generateFiles({
     manifest: {
       applications: {gecko: {id: ID}},
       version: "2.0",
@@ -415,35 +414,35 @@ add_task(async function test_replace_per
   });
 
   let addon = await AddonManager.installTemporaryAddon(unpacked_addon);
 
   Assert.ok(extInstallCalled);
   Assert.ok(installingCalled);
   Assert.ok(installedCalled);
 
-  Monitor.checkInstalled(ID);
-  Monitor.checkStarted(ID);
+  BootstrapMonitor.checkInstalled(ID);
+  BootstrapMonitor.checkStarted(ID);
 
   // temporary add-on is installed and started
   checkAddon(ID, addon, {
     version: "2.0",
     name: "Test Bootstrap 1 (temporary)",
     isCompatible: true,
     appDisabled: false,
     isActive: true,
     type: "extension",
     signedState: AddonManager.SIGNEDSTATE_UNKNOWN,
     temporarilyInstalled: true,
   });
 
   await addon.uninstall();
 
-  Monitor.checkInstalled(ID);
-  Monitor.checkStarted(ID);
+  BootstrapMonitor.checkInstalled(ID);
+  BootstrapMonitor.checkStarted(ID);
 
   addon = await promiseAddonByID(ID);
 
   // existing add-on is back
   checkAddon(ID, addon, {
     version: "1.0",
     name: "Test Bootstrap 1",
     isCompatible: true,
@@ -452,18 +451,18 @@ add_task(async function test_replace_per
     type: "extension",
     signedState: AddonManager.SIGNEDSTATE_PRIVILEGED,
     temporarilyInstalled: false,
   });
 
   unpacked_addon.remove(true);
   await addon.uninstall();
 
-  Monitor.checkNotInstalled(ID);
-  Monitor.checkNotStarted(ID);
+  BootstrapMonitor.checkNotInstalled(ID);
+  BootstrapMonitor.checkNotStarted(ID);
 
   await promiseRestartManager();
 });
 
 // Install a temporary add-on as a version upgrade over the top of an
 // existing temporary add-on.
 add_task(async function test_replace_temporary() {
   const unpackedAddon = gTmpD.clone();
@@ -611,17 +610,17 @@ add_task(async function test_replace_sam
 
   await checkEvent(onInitialStartup, {
     reason: BOOTSTRAP_REASONS.ADDON_INSTALL,
     params: {
       version: "1.0",
     },
   });
 
-  let info = Monitor.started.get(ID);
+  let info = BootstrapMonitor.started.get(ID);
   Assert.equal(info.reason, BOOTSTRAP_REASONS.ADDON_INSTALL);
 
   // Install it again.
   const onShutdown = waitForBootstrapEvent("shutdown", ID);
   const onUpdate = waitForBootstrapEvent("update", ID);
   const onStartup = waitForBootstrapEvent("startup", ID);
   await AddonManager.installTemporaryAddon(unpackedAddon);
 
@@ -655,23 +654,23 @@ add_task(async function test_replace_sam
 });
 
 // Install a temporary add-on over the top of an existing disabled add-on.
 // After restart, the existing add-on should continue to be installed and disabled.
 add_task(async function test_replace_permanent_disabled() {
   await promiseInstallFile(XPIS[1]);
   let addon = await promiseAddonByID(ID);
 
-  Monitor.checkInstalled(ID, "1.0");
-  Monitor.checkStarted(ID, "1.0");
+  BootstrapMonitor.checkInstalled(ID, "1.0");
+  BootstrapMonitor.checkStarted(ID, "1.0");
 
   await addon.disable();
 
-  Monitor.checkInstalled(ID, "1.0");
-  Monitor.checkNotStarted(ID);
+  BootstrapMonitor.checkInstalled(ID, "1.0");
+  BootstrapMonitor.checkNotStarted(ID);
 
   let unpacked_addon = gTmpD.clone();
   unpacked_addon.append(ID);
 
   let files = ExtensionTestCommon.generateFiles({
     manifest: {
       applications: {gecko: {id: ID}},
       name: "Test",
@@ -688,18 +687,18 @@ add_task(async function test_replace_per
       extInstallCalled = true;
     },
   });
 
   let tempAddon = await AddonManager.installTemporaryAddon(unpacked_addon);
 
   Assert.ok(extInstallCalled);
 
-  Monitor.checkInstalled(ID, "2.0");
-  Monitor.checkStarted(ID);
+  BootstrapMonitor.checkInstalled(ID, "2.0");
+  BootstrapMonitor.checkStarted(ID);
 
   // temporary add-on is installed and started
   checkAddon(ID, tempAddon, {
     version: "2.0",
     name: "Test",
     isCompatible: true,
     appDisabled: false,
     isActive: true,
@@ -710,35 +709,35 @@ add_task(async function test_replace_per
 
   await tempAddon.uninstall();
   unpacked_addon.remove(true);
 
   await addon.enable();
   await new Promise(executeSoon);
   addon = await promiseAddonByID(ID);
 
-  Monitor.checkInstalled(ID, "1.0");
-  Monitor.checkStarted(ID);
+  BootstrapMonitor.checkInstalled(ID, "1.0");
+  BootstrapMonitor.checkStarted(ID);
 
   // existing add-on is back
   checkAddon(ID, addon, {
     version: "1.0",
     name: "Test",
     isCompatible: true,
     appDisabled: false,
     isActive: true,
     type: "extension",
     signedState: AddonManager.SIGNEDSTATE_PRIVILEGED,
     temporarilyInstalled: false,
   });
 
   await addon.uninstall();
 
-  Monitor.checkNotInstalled(ID);
-  Monitor.checkNotStarted(ID);
+  BootstrapMonitor.checkNotInstalled(ID);
+  BootstrapMonitor.checkNotStarted(ID);
 
   await promiseRestartManager();
 });
 
 // Tests that XPIs with a .zip extension work when loaded temporarily.
 add_task(async function test_zip_extension() {
   let xpi = createTempWebExtensionFile({
     background() {
--- a/toolkit/mozapps/extensions/test/xpcshell/test_undouninstall.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_undouninstall.js
@@ -40,41 +40,40 @@ const ADDONS = {
         },
       },
     },
   },
 };
 
 const XPIS = {};
 
-let Monitor = SlightlyLessDodgyBootstrapMonitor;
-Monitor.init();
+BootstrapMonitor.init();
 
 function getStartupReason(id) {
-  let info = Monitor.started.get(id);
+  let info = BootstrapMonitor.started.get(id);
   return info ? info.reason : undefined;
 }
 
 function getShutdownReason(id) {
-  let info = Monitor.stopped.get(id);
+  let info = BootstrapMonitor.stopped.get(id);
   return info ? info.reason : undefined;
 }
 
 function getInstallReason(id) {
-  let info = Monitor.installed.get(id);
+  let info = BootstrapMonitor.installed.get(id);
   return info ? info.reason : undefined;
 }
 
 function getUninstallReason(id) {
-  let info = Monitor.uninstalled.get(id);
+  let info = BootstrapMonitor.uninstalled.get(id);
   return info ? info.reason : undefined;
 }
 
 function getShutdownNewVersion(id) {
-  let info = Monitor.stopped.get(id);
+  let info = BootstrapMonitor.stopped.get(id);
   return info ? info.params.newVersion : undefined;
 }
 
 // Sets up the profile by installing an add-on.
 add_task(async function setup() {
   createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
 
   await promiseStartupManager();
@@ -88,52 +87,52 @@ add_task(async function setup() {
 // Tests that an enabled restartless add-on can be uninstalled and goes away
 // when the uninstall is committed
 add_task(async function uninstallRestartless() {
   await promiseInstallFile(XPIS.test_undouninstall1);
 
   let a1 = await promiseAddonByID(ID);
 
   Assert.notEqual(a1, null);
-  Monitor.checkInstalled(ID, "1.0");
-  Monitor.checkStarted(ID, "1.0");
+  BootstrapMonitor.checkInstalled(ID, "1.0");
+  BootstrapMonitor.checkStarted(ID, "1.0");
   Assert.equal(getInstallReason(ID), ADDON_INSTALL);
   Assert.equal(getStartupReason(ID), ADDON_INSTALL);
   Assert.equal(a1.pendingOperations, AddonManager.PENDING_NONE);
   Assert.ok(a1.isActive);
   Assert.ok(!a1.userDisabled);
 
   await a1.uninstall(true);
 
   a1 = await promiseAddonByID(ID);
 
   Assert.notEqual(a1, null);
-  Monitor.checkInstalled(ID);
-  Monitor.checkNotStarted(ID);
+  BootstrapMonitor.checkInstalled(ID);
+  BootstrapMonitor.checkNotStarted(ID);
   Assert.equal(getShutdownReason(ID), ADDON_UNINSTALL);
   Assert.ok(hasFlag(AddonManager.PENDING_UNINSTALL, a1.pendingOperations));
   Assert.ok(!a1.isActive);
   Assert.ok(!a1.userDisabled);
 
   await a1.uninstall();
 
   a1 = await promiseAddonByID(ID);
 
   Assert.equal(a1, null);
-  Monitor.checkNotStarted(ID);
+  BootstrapMonitor.checkNotStarted(ID);
 });
 
 // Tests that an enabled restartless add-on can be uninstalled and then cancelled
 add_task(async function cancelUninstallOfRestartless() {
   await promiseInstallFile(XPIS.test_undouninstall1);
   let a1 = await promiseAddonByID(ID);
 
   Assert.notEqual(a1, null);
-  Monitor.checkInstalled(ID, "1.0");
-  Monitor.checkStarted(ID, "1.0");
+  BootstrapMonitor.checkInstalled(ID, "1.0");
+  BootstrapMonitor.checkStarted(ID, "1.0");
   Assert.equal(getInstallReason(ID), ADDON_INSTALL);
   Assert.equal(getStartupReason(ID), ADDON_INSTALL);
   Assert.equal(a1.pendingOperations, AddonManager.PENDING_NONE);
   Assert.ok(a1.isActive);
   Assert.ok(!a1.userDisabled);
 
   prepare_test({
     "undouninstall1@tests.mozilla.org": [
@@ -143,66 +142,66 @@ add_task(async function cancelUninstallO
   await a1.uninstall(true);
   ensure_test_completed();
 
   clearListeners();
 
   a1 = await promiseAddonByID("undouninstall1@tests.mozilla.org");
 
   Assert.notEqual(a1, null);
-  Monitor.checkInstalled(ID);
-  Monitor.checkNotStarted(ID);
+  BootstrapMonitor.checkInstalled(ID);
+  BootstrapMonitor.checkNotStarted(ID);
   Assert.equal(getShutdownReason(ID), ADDON_UNINSTALL);
   Assert.ok(hasFlag(AddonManager.PENDING_UNINSTALL, a1.pendingOperations));
   Assert.ok(!a1.isActive);
   Assert.ok(!a1.userDisabled);
 
   let promises = [
     promiseAddonEvent("onOperationCancelled"),
     promiseWebExtensionStartup(ID),
   ];
   a1.cancelUninstall();
   await Promise.all(promises);
 
-  Monitor.checkInstalled(ID, "1.0");
-  Monitor.checkStarted(ID, "1.0");
+  BootstrapMonitor.checkInstalled(ID, "1.0");
+  BootstrapMonitor.checkStarted(ID, "1.0");
   Assert.equal(getStartupReason(ID), ADDON_INSTALL);
   Assert.equal(a1.pendingOperations, AddonManager.PENDING_NONE);
   Assert.ok(a1.isActive);
   Assert.ok(!a1.userDisabled);
 
   await promiseShutdownManager();
 
   Assert.equal(getShutdownReason(ID), APP_SHUTDOWN);
   Assert.equal(getShutdownNewVersion(ID), undefined);
 
   await promiseStartupManager();
 
   a1 = await promiseAddonByID("undouninstall1@tests.mozilla.org");
 
   Assert.notEqual(a1, null);
-  Monitor.checkStarted(ID, "1.0");
+  BootstrapMonitor.checkStarted(ID, "1.0");
   Assert.equal(getStartupReason(ID), APP_STARTUP);
   Assert.equal(a1.pendingOperations, AddonManager.PENDING_NONE);
   Assert.ok(a1.isActive);
   Assert.ok(!a1.userDisabled);
 
   await a1.uninstall();
 });
 
 // Tests that reinstalling an enabled restartless add-on waiting to be
 // uninstalled aborts the uninstall and leaves the add-on enabled
 add_task(async function reinstallAddonAwaitingUninstall() {
   await promiseInstallFile(XPIS.test_undouninstall1);
 
   let a1 = await promiseAddonByID("undouninstall1@tests.mozilla.org");
 
   Assert.notEqual(a1, null);
-  Monitor.checkInstalled(ID, "1.0");
-  Monitor.checkStarted(ID, "1.0");
+  BootstrapMonitor.checkInstalled(ID, "1.0");
+  BootstrapMonitor.checkStarted(ID, "1.0");
   Assert.equal(getInstallReason(ID), ADDON_INSTALL);
   Assert.equal(getStartupReason(ID), ADDON_INSTALL);
   Assert.equal(a1.pendingOperations, AddonManager.PENDING_NONE);
   Assert.ok(a1.isActive);
   Assert.ok(!a1.userDisabled);
 
   prepare_test({
     "undouninstall1@tests.mozilla.org": [
@@ -210,18 +209,18 @@ add_task(async function reinstallAddonAw
     ],
   });
   await a1.uninstall(true);
   ensure_test_completed();
 
   a1 = await promiseAddonByID("undouninstall1@tests.mozilla.org");
 
   Assert.notEqual(a1, null);
-  Monitor.checkInstalled(ID);
-  Monitor.checkNotStarted(ID);
+  BootstrapMonitor.checkInstalled(ID);
+  BootstrapMonitor.checkNotStarted(ID);
   Assert.equal(getShutdownReason(ID), ADDON_UNINSTALL);
   Assert.ok(hasFlag(AddonManager.PENDING_UNINSTALL, a1.pendingOperations));
   Assert.ok(!a1.isActive);
   Assert.ok(!a1.userDisabled);
 
   prepare_test({
     "undouninstall1@tests.mozilla.org": [
       ["onInstalling", false],
@@ -234,95 +233,95 @@ add_task(async function reinstallAddonAw
   ]);
 
   await promiseInstallFile(XPIS.test_undouninstall1);
 
   a1 = await promiseAddonByID("undouninstall1@tests.mozilla.org");
 
   ensure_test_completed();
 
-  Monitor.checkInstalled(ID, "1.0");
-  Monitor.checkStarted(ID, "1.0");
+  BootstrapMonitor.checkInstalled(ID, "1.0");
+  BootstrapMonitor.checkStarted(ID, "1.0");
   Assert.equal(getInstallReason(ID), ADDON_UPGRADE);
   Assert.equal(getStartupReason(ID), ADDON_UPGRADE);
   Assert.equal(a1.pendingOperations, AddonManager.PENDING_NONE);
   Assert.ok(a1.isActive);
   Assert.ok(!a1.userDisabled);
 
   await promiseShutdownManager();
 
   Assert.equal(getShutdownReason(ID), APP_SHUTDOWN);
 
   await promiseStartupManager();
 
   a1 = await promiseAddonByID("undouninstall1@tests.mozilla.org");
 
   Assert.notEqual(a1, null);
-  Monitor.checkStarted(ID, "1.0");
+  BootstrapMonitor.checkStarted(ID, "1.0");
   Assert.equal(getStartupReason(ID), APP_STARTUP);
   Assert.equal(a1.pendingOperations, AddonManager.PENDING_NONE);
   Assert.ok(a1.isActive);
   Assert.ok(!a1.userDisabled);
 
   await a1.uninstall();
 });
 
 // Tests that a disabled restartless add-on can be uninstalled and goes away
 // when the uninstall is committed
 add_task(async function uninstallDisabledRestartless() {
   await promiseInstallFile(XPIS.test_undouninstall1);
 
   let a1 = await promiseAddonByID("undouninstall1@tests.mozilla.org");
 
   Assert.notEqual(a1, null);
-  Monitor.checkInstalled(ID, "1.0");
-  Monitor.checkStarted(ID, "1.0");
+  BootstrapMonitor.checkInstalled(ID, "1.0");
+  BootstrapMonitor.checkStarted(ID, "1.0");
   Assert.equal(getInstallReason(ID), ADDON_INSTALL);
   Assert.equal(getStartupReason(ID), ADDON_INSTALL);
   Assert.equal(a1.pendingOperations, AddonManager.PENDING_NONE);
   Assert.ok(a1.isActive);
   Assert.ok(!a1.userDisabled);
 
   await a1.disable();
-  Monitor.checkNotStarted(ID);
+  BootstrapMonitor.checkNotStarted(ID);
   Assert.equal(getShutdownReason(ID), ADDON_DISABLE);
   Assert.equal(a1.pendingOperations, AddonManager.PENDING_NONE);
   Assert.ok(!a1.isActive);
   Assert.ok(a1.userDisabled);
 
   prepare_test({
     "undouninstall1@tests.mozilla.org": [
       "onUninstalling",
     ],
   });
   await a1.uninstall(true);
   ensure_test_completed();
 
   a1 = await promiseAddonByID("undouninstall1@tests.mozilla.org");
 
   Assert.notEqual(a1, null);
-  Monitor.checkNotStarted(ID);
+  BootstrapMonitor.checkNotStarted(ID);
   Assert.ok(hasFlag(AddonManager.PENDING_UNINSTALL, a1.pendingOperations));
   Assert.ok(!a1.isActive);
   Assert.ok(a1.userDisabled);
 
   // commit the uninstall
   prepare_test({
     "undouninstall1@tests.mozilla.org": [
       "onUninstalled",
     ],
   });
   await a1.uninstall();
   ensure_test_completed();
 
   a1 = await promiseAddonByID("undouninstall1@tests.mozilla.org");
 
   Assert.equal(a1, null);
-  Monitor.checkNotStarted(ID);
-  Monitor.checkNotInstalled(ID);
+  BootstrapMonitor.checkNotStarted(ID);
+  BootstrapMonitor.checkNotInstalled(ID);
   Assert.equal(getUninstallReason(ID), ADDON_UNINSTALL);
 });
 
 // Tests that a disabled restartless add-on can be uninstalled and then cancelled
 add_task(async function cancelUninstallDisabledRestartless() {
   prepare_test({
     "undouninstall1@tests.mozilla.org": [
       ["onInstalling", false],
@@ -334,119 +333,119 @@ add_task(async function cancelUninstallD
     "onInstallEnded",
   ]);
   await promiseInstallFile(XPIS.test_undouninstall1);
   ensure_test_completed();
 
   let a1 = await promiseAddonByID("undouninstall1@tests.mozilla.org");
 
   Assert.notEqual(a1, null);
-  Monitor.checkInstalled(ID, "1.0");
-  Monitor.checkStarted(ID, "1.0");
+  BootstrapMonitor.checkInstalled(ID, "1.0");
+  BootstrapMonitor.checkStarted(ID, "1.0");
   Assert.equal(getInstallReason(ID), ADDON_INSTALL);
   Assert.equal(getStartupReason(ID), ADDON_INSTALL);
   Assert.equal(a1.pendingOperations, AddonManager.PENDING_NONE);
   Assert.ok(a1.isActive);
   Assert.ok(!a1.userDisabled);
 
   prepare_test({
     "undouninstall1@tests.mozilla.org": [
       ["onDisabling", false],
       "onDisabled",
     ],
   });
   await a1.disable();
   ensure_test_completed();
 
-  Monitor.checkNotStarted(ID);
+  BootstrapMonitor.checkNotStarted(ID);
   Assert.equal(getShutdownReason(ID), ADDON_DISABLE);
   Assert.equal(a1.pendingOperations, AddonManager.PENDING_NONE);
   Assert.ok(!a1.isActive);
   Assert.ok(a1.userDisabled);
 
   prepare_test({
     "undouninstall1@tests.mozilla.org": [
       "onUninstalling",
     ],
   });
   await a1.uninstall(true);
   ensure_test_completed();
 
   a1 = await promiseAddonByID("undouninstall1@tests.mozilla.org");
 
   Assert.notEqual(a1, null);
-  Monitor.checkNotStarted(ID);
-  Monitor.checkInstalled(ID);
+  BootstrapMonitor.checkNotStarted(ID);
+  BootstrapMonitor.checkInstalled(ID);
   Assert.ok(hasFlag(AddonManager.PENDING_UNINSTALL, a1.pendingOperations));
   Assert.ok(!a1.isActive);
   Assert.ok(a1.userDisabled);
 
   prepare_test({
     "undouninstall1@tests.mozilla.org": [
       "onOperationCancelled",
     ],
   });
   a1.cancelUninstall();
   ensure_test_completed();
 
-  Monitor.checkNotStarted(ID);
-  Monitor.checkInstalled(ID);
+  BootstrapMonitor.checkNotStarted(ID);
+  BootstrapMonitor.checkInstalled(ID);
   Assert.equal(a1.pendingOperations, AddonManager.PENDING_NONE);
   Assert.ok(!a1.isActive);
   Assert.ok(a1.userDisabled);
 
   await promiseRestartManager();
 
   a1 = await promiseAddonByID("undouninstall1@tests.mozilla.org");
 
   Assert.notEqual(a1, null);
-  Monitor.checkNotStarted(ID);
-  Monitor.checkInstalled(ID);
+  BootstrapMonitor.checkNotStarted(ID);
+  BootstrapMonitor.checkInstalled(ID);
   Assert.equal(a1.pendingOperations, AddonManager.PENDING_NONE);
   Assert.ok(!a1.isActive);
   Assert.ok(a1.userDisabled);
 
   await a1.uninstall();
 });
 
 // Tests that reinstalling a disabled restartless add-on waiting to be
 // uninstalled aborts the uninstall and leaves the add-on disabled
 add_task(async function reinstallDisabledAddonAwaitingUninstall() {
   await promiseInstallFile(XPIS.test_undouninstall1);
 
   let a1 = await promiseAddonByID("undouninstall1@tests.mozilla.org");
 
   Assert.notEqual(a1, null);
-  Monitor.checkInstalled(ID, "1.0");
-  Monitor.checkStarted(ID, "1.0");
+  BootstrapMonitor.checkInstalled(ID, "1.0");
+  BootstrapMonitor.checkStarted(ID, "1.0");
   Assert.equal(getInstallReason(ID), ADDON_INSTALL);
   Assert.equal(getStartupReason(ID), ADDON_INSTALL);
   Assert.equal(a1.pendingOperations, AddonManager.PENDING_NONE);
   Assert.ok(a1.isActive);
   Assert.ok(!a1.userDisabled);
 
   await a1.disable();
-  Monitor.checkNotStarted(ID);
+  BootstrapMonitor.checkNotStarted(ID);
   Assert.equal(getShutdownReason(ID), ADDON_DISABLE);
   Assert.equal(a1.pendingOperations, AddonManager.PENDING_NONE);
   Assert.ok(!a1.isActive);
   Assert.ok(a1.userDisabled);
 
   prepare_test({
     "undouninstall1@tests.mozilla.org": [
       "onUninstalling",
     ],
   });
   await a1.uninstall(true);
   ensure_test_completed();
 
   a1 = await promiseAddonByID("undouninstall1@tests.mozilla.org");
 
   Assert.notEqual(a1, null);
-  Monitor.checkNotStarted(ID);
+  BootstrapMonitor.checkNotStarted(ID);
   Assert.ok(hasFlag(AddonManager.PENDING_UNINSTALL, a1.pendingOperations));
   Assert.ok(!a1.isActive);
   Assert.ok(a1.userDisabled);
 
   prepare_test({
     "undouninstall1@tests.mozilla.org": [
       ["onInstalling", false],
       "onInstalled",
@@ -458,88 +457,88 @@ add_task(async function reinstallDisable
   ]);
 
   await promiseInstallFile(XPIS.test_undouninstall1);
 
   a1 = await promiseAddonByID("undouninstall1@tests.mozilla.org");
 
   ensure_test_completed();
 
-  Monitor.checkInstalled(ID, "1.0");
-  Monitor.checkNotStarted(ID, "1.0");
+  BootstrapMonitor.checkInstalled(ID, "1.0");
+  BootstrapMonitor.checkNotStarted(ID, "1.0");
   Assert.equal(getInstallReason(ID), ADDON_UPGRADE);
   Assert.equal(a1.pendingOperations, AddonManager.PENDING_NONE);
   Assert.ok(!a1.isActive);
   Assert.ok(a1.userDisabled);
 
   await promiseRestartManager();
 
   a1 = await promiseAddonByID("undouninstall1@tests.mozilla.org");
 
   Assert.notEqual(a1, null);
-  Monitor.checkNotStarted(ID, "1.0");
+  BootstrapMonitor.checkNotStarted(ID, "1.0");
   Assert.equal(a1.pendingOperations, AddonManager.PENDING_NONE);
   Assert.ok(!a1.isActive);
   Assert.ok(a1.userDisabled);
 
   await a1.uninstall();
 });
 
 
 // Test that uninstalling a temporary addon can be canceled
 add_task(async function cancelUninstallTemporary() {
   await AddonManager.installTemporaryAddon(XPIS.test_undouninstall1);
 
   let a1 = await promiseAddonByID("undouninstall1@tests.mozilla.org");
   Assert.notEqual(a1, null);
-  Monitor.checkInstalled(ID, "1.0");
-  Monitor.checkStarted(ID, "1.0");
+  BootstrapMonitor.checkInstalled(ID, "1.0");
+  BootstrapMonitor.checkStarted(ID, "1.0");
   Assert.equal(getInstallReason(ID), ADDON_INSTALL);
   Assert.equal(getStartupReason(ID), ADDON_INSTALL);
   Assert.equal(a1.pendingOperations, AddonManager.PENDING_NONE);
   Assert.ok(a1.isActive);
   Assert.ok(!a1.userDisabled);
 
   prepare_test({
     "undouninstall1@tests.mozilla.org": [
       "onUninstalling",
     ],
   });
   await a1.uninstall(true);
   ensure_test_completed();
 
   clearListeners();
 
-  Monitor.checkNotStarted(ID, "1.0");
+  BootstrapMonitor.checkNotStarted(ID, "1.0");
   Assert.ok(hasFlag(AddonManager.PENDING_UNINSTALL, a1.pendingOperations));
 
   let promises = [
     promiseAddonEvent("onOperationCancelled"),
     promiseWebExtensionStartup("undouninstall1@tests.mozilla.org"),
   ];
   a1.cancelUninstall();
   await Promise.all(promises);
 
   a1 = await promiseAddonByID("undouninstall1@tests.mozilla.org");
 
   Assert.notEqual(a1, null);
-  Monitor.checkStarted(ID, "1.0");
+  BootstrapMonitor.checkStarted(ID, "1.0");
   Assert.equal(a1.pendingOperations, 0);
 
   await promiseRestartManager();
 });
 
 // Tests that cancelling the uninstall of an incompatible restartless addon
 // does not start the addon
 add_task(async function cancelUninstallIncompatibleRestartless() {
   await promiseInstallFile(XPIS.test_undoincompatible);
 
   let a1 = await promiseAddonByID(INCOMPAT_ID);
   Assert.notEqual(a1, null);
-  Monitor.checkNotStarted(INCOMPAT_ID);
+  BootstrapMonitor.checkNotStarted(INCOMPAT_ID);
   Assert.ok(!a1.isActive);
 
   prepare_test({
     "incompatible@tests.mozilla.org": [
       "onUninstalling",
     ],
   });
   await a1.uninstall(true);
@@ -555,12 +554,12 @@ add_task(async function cancelUninstallI
       "onOperationCancelled",
     ],
   });
   a1.cancelUninstall();
   ensure_test_completed();
 
   a1 = await promiseAddonByID(INCOMPAT_ID);
   Assert.notEqual(a1, null);
-  Monitor.checkNotStarted(INCOMPAT_ID);
+  BootstrapMonitor.checkNotStarted(INCOMPAT_ID);
   Assert.equal(a1.pendingOperations, 0);
   Assert.ok(!a1.isActive);
 });
--- a/toolkit/mozapps/extensions/test/xpcshell/test_update_ignorecompat.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_update_ignorecompat.js
@@ -1,12 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
+// This test is disabled but is being kept around so it can eventualy
+// be modernized and re-enabled.  But is uses obsolete test helpers that
+// fail lint, so just skip linting it for now.
+/* eslint-disable */
+
 // This verifies that add-on update checks work correctly when compatibility
 // check is disabled.
 
 const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled";
 
 // The test extension uses an insecure update url.
 Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
 Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, false);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_update_webextensions.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_update_webextensions.js
@@ -1,12 +1,10 @@
 "use strict";
 
-const TOOLKIT_ID = "toolkit@mozilla.org";
-
 // We don't have an easy way to serve update manifests from a secure URL.
 Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
 
 var testserver = createHttpServer();
 gPort = testserver.identity.primaryPort;
 
 const uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
 
@@ -81,28 +79,18 @@ var checkUpdates = async function(aData,
     update.version = version;
 
     provide(update, "addon.id", id);
     provide(update, "addon.manifest.applications.gecko.id", id);
     let addon = update.addon;
 
     delete update.addon;
 
-    let file;
-    if (addon.rdf) {
-      provide(addon, "version", version);
-      provide(addon, "targetApplications", [{id: TOOLKIT_ID,
-                                             minVersion: "42",
-                                             maxVersion: "*"}]);
-
-      file = createTempXPIFile(addon);
-    } else {
-      provide(addon, "manifest.version", version);
-      file = createTempWebExtensionFile(addon);
-    }
+    provide(addon, "manifest.version", version);
+    let file = createTempWebExtensionFile(addon);
     file.moveTo(addonsDir, `${id}-${version}.xpi`.replace(/[{}]/g, ""));
 
     let path = `/addons/${file.leafName}`;
     provide(update, "update_link", `http://localhost:${gPort}${path}`);
 
     addonData.updates.push(update);
   }
 
--- a/toolkit/mozapps/extensions/test/xpcshell/test_webextension_install_syntax_error.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_webextension_install_syntax_error.js
@@ -2,29 +2,29 @@ const ADDON_ID = "webext-test@tests.mozi
 
 add_task(async function setup() {
   createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1");
   await promiseStartupManager();
 });
 
 add_task(async function install_xpi() {
   // WebExtension with a JSON syntax error in manifest.json
-  let xpi1 = Extension.generateXPI({
+  let xpi1 = AddonTestUtils.createTempWebExtensionFile({
     files: {
       "manifest.json": String.raw`{
         "manifest_version: 2,
         "applications": {"gecko": {"id": "${ADDON_ID}"}},
         "name": "Temp WebExt with Error",
         "version": "0.1"
       }`,
     },
   });
 
   // Valid WebExtension
-  let xpi2 = Extension.generateXPI({
+  let xpi2 = AddonTestUtils.createTempWebExtensionFile({
     files: {
       "manifest.json": String.raw`{
         "manifest_version": 2,
         "applications": {"gecko": {"id": "${ADDON_ID}"}},
         "name": "Temp WebExt without Error",
         "version": "0.1"
       }`,
     },
@@ -34,11 +34,9 @@ add_task(async function install_xpi() {
   Assert.equal(install1.state, AddonManager.STATE_DOWNLOAD_FAILED);
   Assert.equal(install1.error, AddonManager.ERROR_CORRUPT_FILE);
 
   // Replace xpi1 with xpi2 to have the same filename to reproduce install error
   xpi2.moveTo(xpi1.parent, xpi1.leafName);
 
   let install2 = await AddonManager.getInstallForFile(xpi2);
   Assert.equal(install2.error, 0);
-
-  xpi1.remove(false);
 });
--- a/tools/lint/eslint/modules.json
+++ b/tools/lint/eslint/modules.json
@@ -21,17 +21,16 @@
   "Battery.jsm": ["GetBattery", "Battery"],
   "blocklist-clients.js": ["AddonBlocklistClient", "GfxBlocklistClient", "OneCRLBlocklistClient", "PluginBlocklistClient"],
   "blocklist-updater.js": ["checkVersions", "addTestBlocklistClient"],
   "bogus_element_type.jsm": [],
   "bookmarks.js": ["BookmarksEngine", "PlacesItem", "Bookmark", "BookmarkFolder", "BookmarkQuery", "Livemark", "BookmarkSeparator", "BufferedBookmarksEngine"],
   "bookmarks.jsm": ["PlacesItem", "Bookmark", "Separator", "Livemark", "BookmarkFolder", "DumpBookmarks"],
   "bookmark_repair.js": ["BookmarkRepairRequestor", "BookmarkRepairResponder"],
   "bookmark_validator.js": ["BookmarkValidator", "BookmarkProblemData"],
-  "BootstrapMonitor.jsm": ["monitor"],
   "browser-loader.js": ["BrowserLoader"],
   "browser.js": ["browser", "Context", "WindowState"],
   "browserid_identity.js": ["BrowserIDManager", "AuthenticationError"],
   "capabilities.js": ["Capabilities", "PageLoadStrategy", "Proxy", "Timeouts", "UnhandledPromptBehavior"],
   "cert.js": ["CertificateOverrideManager", "InsecureSweepingOverride"],
   "clients.js": ["ClientEngine", "ClientsRec"],
   "collection_repair.js": ["getRepairRequestor", "getAllRepairRequestors", "CollectionRepairRequestor", "getRepairResponder", "CollectionRepairResponder"],
   "collection_validator.js": ["CollectionValidator", "CollectionProblemData"],