Merge autoland to mozilla-central. a=merge
authorNarcis Beleuzu <nbeleuzu@mozilla.com>
Tue, 22 Oct 2019 00:50:23 +0300
changeset 498402 563f437f24b9e12495b0f2c364538a2b8d8a0ca7
parent 498313 1ef186a26a84da3dce52d4870d75ebde29a5b649 (current diff)
parent 498401 fd0d2f3403803d8a66ceb55595ca5a2202d858e9 (diff)
child 498443 864f4569b4cf447109375aeaf762ac13a4a972c7
push id36717
push usernbeleuzu@mozilla.com
push dateMon, 21 Oct 2019 21:51:55 +0000
treeherdermozilla-central@563f437f24b9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone72.0a1
first release with
nightly linux32
563f437f24b9 / 72.0a1 / 20191021215155 / files
nightly linux64
563f437f24b9 / 72.0a1 / 20191021215155 / files
nightly mac
563f437f24b9 / 72.0a1 / 20191021215155 / files
nightly win32
563f437f24b9 / 72.0a1 / 20191021215155 / files
nightly win64
563f437f24b9 / 72.0a1 / 20191021215155 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge autoland to mozilla-central. a=merge
browser/base/content/browser.js
devtools/client/application/test/components/.eslintrc.js
devtools/client/application/test/components/__snapshots__/components_application_panel-App.test.js.snap
devtools/client/application/test/components/babel.config.js
devtools/client/application/test/components/components_application_panel-App.test.js
devtools/client/application/test/components/fixtures/Chrome.js
devtools/client/application/test/components/fixtures/Services.js
devtools/client/application/test/components/fixtures/data/constants.js
devtools/client/application/test/components/fixtures/fluent-l10n.js
devtools/client/application/test/components/fixtures/stub.js
devtools/client/application/test/components/fixtures/unicode-url.js
devtools/client/application/test/components/helpers/helpers.js
devtools/client/application/test/components/jest.config.js
devtools/client/application/test/components/manifest/__snapshots__/components_application_panel-Manifest.test.js.snap
devtools/client/application/test/components/manifest/__snapshots__/components_application_panel-ManifestColorItem.test.js.snap
devtools/client/application/test/components/manifest/__snapshots__/components_application_panel-ManifestEmpty.test.js.snap
devtools/client/application/test/components/manifest/__snapshots__/components_application_panel-ManifestIconItem.test.js.snap
devtools/client/application/test/components/manifest/__snapshots__/components_application_panel-ManifestIssue.test.js.snap
devtools/client/application/test/components/manifest/__snapshots__/components_application_panel-ManifestIssueList.test.js.snap
devtools/client/application/test/components/manifest/__snapshots__/components_application_panel-ManifestItem.test.js.snap
devtools/client/application/test/components/manifest/__snapshots__/components_application_panel-ManifestJsonLink.test.js.snap
devtools/client/application/test/components/manifest/__snapshots__/components_application_panel-ManifestLoader.test.js.snap
devtools/client/application/test/components/manifest/__snapshots__/components_application_panel-ManifestPage.test.js.snap
devtools/client/application/test/components/manifest/__snapshots__/components_application_panel-ManifestSection.test.js.snap
devtools/client/application/test/components/manifest/components_application_panel-Manifest.test.js
devtools/client/application/test/components/manifest/components_application_panel-ManifestColorItem.test.js
devtools/client/application/test/components/manifest/components_application_panel-ManifestEmpty.test.js
devtools/client/application/test/components/manifest/components_application_panel-ManifestIconItem.test.js
devtools/client/application/test/components/manifest/components_application_panel-ManifestIssue.test.js
devtools/client/application/test/components/manifest/components_application_panel-ManifestIssueList.test.js
devtools/client/application/test/components/manifest/components_application_panel-ManifestItem.test.js
devtools/client/application/test/components/manifest/components_application_panel-ManifestJsonLink.test.js
devtools/client/application/test/components/manifest/components_application_panel-ManifestLoader.test.js
devtools/client/application/test/components/manifest/components_application_panel-ManifestPage.test.js
devtools/client/application/test/components/manifest/components_application_panel-ManifestSection.test.js
devtools/client/application/test/components/package.json
devtools/client/application/test/components/routing/__snapshots__/components_application_panel-PageSwitcher.test.js.snap
devtools/client/application/test/components/routing/__snapshots__/components_application_panel-Sidebar.test.js.snap
devtools/client/application/test/components/routing/__snapshots__/components_application_panel-SidebarItem.test.js.snap
devtools/client/application/test/components/routing/components_application_panel-PageSwitcher.test.js
devtools/client/application/test/components/routing/components_application_panel-Sidebar.test.js
devtools/client/application/test/components/routing/components_application_panel-SidebarItem.test.js
devtools/client/application/test/components/service-workers/__snapshots__/components_application_panel-WorkerList.test.js.snap
devtools/client/application/test/components/service-workers/__snapshots__/components_application_panel-WorkerListEmpty.test.js.snap
devtools/client/application/test/components/service-workers/__snapshots__/components_application_panel-WorkersPage.test.js.snap
devtools/client/application/test/components/service-workers/components_application_panel-WorkerList.test.js
devtools/client/application/test/components/service-workers/components_application_panel-WorkerListEmpty.test.js
devtools/client/application/test/components/service-workers/components_application_panel-WorkersPage.test.js
devtools/client/application/test/components/setup.js
devtools/client/application/test/components/yarn.lock
devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/__mocks__/object-client.js
devtools/shared/client/object-client.js
mfbt/double-conversion/double-conversion/diy-fp.cc
mfbt/double-conversion/double-conversion/double-conversion.cc
taskcluster/docker/debian10-test/dot-files/pulse/default.pa
--- a/accessible/base/AccIterator.cpp
+++ b/accessible/base/AccIterator.cpp
@@ -64,23 +64,21 @@ AccIterator::IteratorState::IteratorStat
 
 ////////////////////////////////////////////////////////////////////////////////
 // RelatedAccIterator
 ////////////////////////////////////////////////////////////////////////////////
 
 RelatedAccIterator::RelatedAccIterator(DocAccessible* aDocument,
                                        nsIContent* aDependentContent,
                                        nsAtom* aRelAttr)
-    : mDocument(aDocument),
-      mRelAttr(aRelAttr),
-      mProviders(nullptr),
-      mIndex(0) {
+    : mDocument(aDocument), mRelAttr(aRelAttr), mProviders(nullptr), mIndex(0) {
   nsAutoString id;
   if (aDependentContent->IsElement() &&
-      aDependentContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id)) {
+      aDependentContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::id,
+                                              id)) {
     mProviders = mDocument->GetRelProviders(aDependentContent->AsElement(), id);
   }
 }
 
 Accessible* RelatedAccIterator::Next() {
   if (!mProviders) return nullptr;
 
   while (mIndex < mProviders->Length()) {
--- a/accessible/ipc/win/HandlerProvider.cpp
+++ b/accessible/ipc/win/HandlerProvider.cpp
@@ -98,21 +98,22 @@ void HandlerProvider::GetAndSerializePay
   MOZ_ASSERT(mscom::IsCurrentThreadMTA());
 
   if (mSerializer) {
     return;
   }
 
   IA2Payload payload{};
 
-  if (!mscom::InvokeOnMainThread("HandlerProvider::BuildInitialIA2Data", this,
-                                 &HandlerProvider::BuildInitialIA2Data,
-                                 std::forward<NotNull<mscom::IInterceptor*>>(aInterceptor),
-                                 std::forward<StaticIA2Data*>(&payload.mStaticData),
-                                 std::forward<DynamicIA2Data*>(&payload.mDynamicData)) ||
+  if (!mscom::InvokeOnMainThread(
+          "HandlerProvider::BuildInitialIA2Data", this,
+          &HandlerProvider::BuildInitialIA2Data,
+          std::forward<NotNull<mscom::IInterceptor*>>(aInterceptor),
+          std::forward<StaticIA2Data*>(&payload.mStaticData),
+          std::forward<DynamicIA2Data*>(&payload.mDynamicData)) ||
       !payload.mDynamicData.mUniqueId) {
     return;
   }
 
   // But we set mGeckoBackChannel on the current thread which resides in the
   // MTA. This is important to ensure that COM always invokes
   // IGeckoBackChannel methods in an MTA background thread.
 
@@ -629,18 +630,17 @@ HandlerProvider::get_AllTextInfo(BSTR* a
   HRESULT hr;
   if (!mscom::InvokeOnMainThread(
           "HandlerProvider::GetAllTextInfoMainThread", this,
           &HandlerProvider::GetAllTextInfoMainThread,
           std::forward<BSTR*>(aText),
           std::forward<IAccessibleHyperlink***>(aHyperlinks),
           std::forward<long*>(aNHyperlinks),
           std::forward<IA2TextSegment**>(aAttribRuns),
-          std::forward<long*>(aNAttribRuns),
-          std::forward<HRESULT*>(&hr))) {
+          std::forward<long*>(aNAttribRuns), std::forward<HRESULT*>(&hr))) {
     return E_FAIL;
   }
 
   return hr;
 }
 
 void HandlerProvider::GetRelationsInfoMainThread(IARelationData** aRelations,
                                                  long* aNRelations,
@@ -695,22 +695,21 @@ HandlerProvider::get_RelationsInfo(IARel
                                    long* aNRelations) {
   MOZ_ASSERT(mscom::IsCurrentThreadMTA());
 
   if (!mTargetUnk) {
     return CO_E_OBJNOTCONNECTED;
   }
 
   HRESULT hr;
-  if (!mscom::InvokeOnMainThread("HandlerProvider::GetRelationsInfoMainThread",
-                                 this,
-                                 &HandlerProvider::GetRelationsInfoMainThread,
-                                 std::forward<IARelationData**>(aRelations),
-                                 std::forward<long*>(aNRelations),
-                                 std::forward<HRESULT*>(&hr))) {
+  if (!mscom::InvokeOnMainThread(
+          "HandlerProvider::GetRelationsInfoMainThread", this,
+          &HandlerProvider::GetRelationsInfoMainThread,
+          std::forward<IARelationData**>(aRelations),
+          std::forward<long*>(aNRelations), std::forward<HRESULT*>(&hr))) {
     return E_FAIL;
   }
 
   return hr;
 }
 
 // Helper function for GetAllChildrenMainThread.
 static bool SetChildDataForTextLeaf(NEWEST_IA2_INTERFACE* acc,
@@ -838,22 +837,21 @@ void HandlerProvider::GetAllChildrenMain
   *hr = S_OK;
 }
 
 HRESULT
 HandlerProvider::get_AllChildren(AccChildData** aChildren, ULONG* aNChildren) {
   MOZ_ASSERT(mscom::IsCurrentThreadMTA());
 
   HRESULT hr;
-  if (!mscom::InvokeOnMainThread("HandlerProvider::GetAllChildrenMainThread",
-                                 this,
-                                 &HandlerProvider::GetAllChildrenMainThread,
-                                 std::forward<AccChildData**>(aChildren),
-                                 std::forward<ULONG*>(aNChildren),
-                                 std::forward<HRESULT*>(&hr))) {
+  if (!mscom::InvokeOnMainThread(
+          "HandlerProvider::GetAllChildrenMainThread", this,
+          &HandlerProvider::GetAllChildrenMainThread,
+          std::forward<AccChildData**>(aChildren),
+          std::forward<ULONG*>(aNChildren), std::forward<HRESULT*>(&hr))) {
     return E_FAIL;
   }
 
   return hr;
 }
 
 }  // namespace a11y
 }  // namespace mozilla
--- a/accessible/mac/mozAccessible.mm
+++ b/accessible/mac/mozAccessible.mm
@@ -85,19 +85,19 @@ static inline NSMutableArray* ConvertToN
 @implementation mozAccessible
 
 - (id)initWithAccessible:(uintptr_t)aGeckoAccessible {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
   if ((self = [super init])) {
     mGeckoAccessible = aGeckoAccessible;
     if (aGeckoAccessible & IS_PROXY)
-      mRole = [self getProxyAccessible] -> Role();
+      mRole = [self getProxyAccessible]->Role();
     else
-      mRole = [self getGeckoAccessible] -> Role();
+      mRole = [self getGeckoAccessible]->Role();
   }
 
   return self;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 - (void)dealloc {
--- a/accessible/mac/mozActionElements.mm
+++ b/accessible/mac/mozActionElements.mm
@@ -279,23 +279,23 @@ enum CheckboxValue {
   }
 
   return [super accessibilityArrayAttributeCount:attribute];
 }
 
 - (NSArray*)children {
   if (![self getGeckoAccessible]) return nil;
 
-  nsDeckFrame* deckFrame = do_QueryFrame([self getGeckoAccessible] -> GetFrame());
+  nsDeckFrame* deckFrame = do_QueryFrame([self getGeckoAccessible]->GetFrame());
   nsIFrame* selectedFrame = deckFrame ? deckFrame->GetSelectedBox() : nullptr;
 
   Accessible* selectedAcc = nullptr;
   if (selectedFrame) {
     nsINode* node = selectedFrame->GetContent();
-    selectedAcc = [self getGeckoAccessible] -> Document() -> GetAccessible(node);
+    selectedAcc = [self getGeckoAccessible]->Document() -> GetAccessible(node);
   }
 
   if (selectedAcc) {
     mozAccessible* curNative = GetNativeFromGeckoAccessible(selectedAcc);
     if (curNative) return [NSArray arrayWithObjects:GetObjectOrRepresentedView(curNative), nil];
   }
 
   return nil;
--- a/accessible/xul/XULComboboxAccessible.cpp
+++ b/accessible/xul/XULComboboxAccessible.cpp
@@ -23,19 +23,17 @@ using namespace mozilla::a11y;
 ////////////////////////////////////////////////////////////////////////////////
 
 XULComboboxAccessible::XULComboboxAccessible(nsIContent* aContent,
                                              DocAccessible* aDoc)
     : AccessibleWrap(aContent, aDoc) {
   mGenericTypes |= eCombobox;
 }
 
-role XULComboboxAccessible::NativeRole() const {
-  return roles::COMBOBOX;
-}
+role XULComboboxAccessible::NativeRole() const { return roles::COMBOBOX; }
 
 uint64_t XULComboboxAccessible::NativeState() const {
   // As a nsComboboxAccessible we can have the following states:
   //     STATE_FOCUSED
   //     STATE_FOCUSABLE
   //     STATE_HASPOPUP
   //     STATE_EXPANDED
   //     STATE_COLLAPSED
--- a/browser/app/blocklist.xml
+++ b/browser/app/blocklist.xml
@@ -1,10 +1,10 @@
 <?xml version='1.0' encoding='UTF-8'?>
-<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1571235848152">
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1571605630002">
   <emItems>
     <emItem blockID="i334" id="{0F827075-B026-42F3-885D-98981EE7B1AE}">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
     <emItem blockID="i1211" id="flvto@hotger.com">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
@@ -3512,16 +3512,20 @@
     <emItem blockID="dd1261a3-6944-4f51-8118-b0a8f2055d69" id="{381f21b1-95bf-4042-bc5c-3a40b2a03f10}">
       <prefs/>
       <versionRange minVersion="0" maxVersion="1.0.0" severity="3"/>
     </emItem>
     <emItem blockID="96b2e7d5-d4e4-425e-b275-086dc7ccd6ad" id="/^((firefox@browser-security\.de)|(firefox@smarttube\.io)|({0fde9597-0508-47ff-ad8a-793fa059c4e7})|(info@browser-privacy\.com)|({d3b98a68-fd64-4763-8b66-e15e47ef000a})|({36ea170d-2586-45fb-9f48-5f6b6fd59da7})|(youtubemp3converter@yttools\.io)|(simplysearch@dirtylittlehelpers\.com)|(extreme@smarttube\.io)|(selfdestructingcookies@dirtylittlehelpers\.com)|({27a1b6d8-c6c9-4ddd-bf20-3afa0ccf5040})|({2e9cae8b-ee3f-4762-a39e-b53d31dffd37})|(adblock@smarttube\.io)|({a659bdfa-dbbe-4e58-baf8-70a6975e47d0})|({f9455ec1-203a-4fe8-95b6-f6c54a9e56af})|({8c85526d-1be9-4b96-9462-aa48a811f4cf})|(mail@quick-buttons\.de)|(youtubeadblocker@yttools\.io)|(extension@browser-safety\.org)|(contact@web-security\.com)|(videodownloader@dirtylittlehelpers\.com)|(googlenotrack@dirtylittlehelpers\.com)|(develop@quick-amz\.com))$/">
       <prefs/>
       <versionRange minVersion="0" maxVersion="2.0.9" severity="3"/>
     </emItem>
+    <emItem blockID="9e8f80d6-a818-4004-9a20-deec55f3fb96" id="/^((\{a059a924-e43a-495d-9620-ad8c111d62d9\})|(\{79f4bfc6-b1da-4dc4-85cc-ecbcc5dd152e\}))$/">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="3"/>
+    </emItem>
   </emItems>
   <pluginItems>
     <pluginItem blockID="p332">
       <match name="filename" exp="libflashplayer\.so"/>
       <match name="description" exp="^Shockwave Flash 11.(0|1) r[0-9]{1,3}$"/>
       <infoURL>https://get.adobe.com/flashplayer/</infoURL>
       <versionRange severity="0" vulnerabilitystatus="1">
         <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -2255,23 +2255,16 @@ pref("devtools.webconsole.timestampMessa
 
 // Enable the webconsole sidebar toggle in Nightly builds.
 #if defined(NIGHTLY_BUILD)
   pref("devtools.webconsole.sidebarToggle", true);
 #else
   pref("devtools.webconsole.sidebarToggle", false);
 #endif
 
-// Enable editor mode in the console in Nightly and DevEdition builds.
-#if defined(NIGHTLY_BUILD) || defined(MOZ_DEV_EDITION)
-  pref("devtools.webconsole.features.editor", true);
-#else
-  pref("devtools.webconsole.features.editor", false);
-#endif
-
 // Saved editor mode state in the console.
 pref("devtools.webconsole.input.editor", false);
 pref("devtools.browserconsole.input.editor", false);
 
 // Editor width for webconsole and browserconsole.
 pref("devtools.webconsole.input.editorWidth", 0);
 pref("devtools.browserconsole.input.editorWidth", 0);
 
--- a/browser/base/content/browser-siteProtections.js
+++ b/browser/base/content/browser-siteProtections.js
@@ -601,65 +601,54 @@ var ThirdPartyCookies = {
       "behaviorPref",
       this.PREF_ENABLED,
       Ci.nsICookieService.BEHAVIOR_ACCEPT,
       this.updateCategoryItem.bind(this)
     );
     this.updateCategoryItem();
   },
 
-  get disabledCategoryLabel() {
-    delete this.disabledCategoryLabel;
-    return (this.disabledCategoryLabel = document.getElementById(
-      "protections-popup-cookies-category-label-disabled"
-    ));
-  },
-
-  get enabledCategoryLabel() {
-    delete this.enabledCategoryLabel;
-    return (this.enabledCategoryLabel = document.getElementById(
-      "protections-popup-cookies-category-label-enabled"
+  get categoryLabel() {
+    delete this.categoryLabel;
+    return (this.categoryLabel = document.getElementById(
+      "protections-popup-cookies-category-label"
     ));
   },
 
   updateCategoryItem() {
     this.categoryItem.classList.toggle("blocked", this.enabled);
 
-    if (!this.enabled) {
-      this.disabledCategoryLabel.hidden = false;
-      this.enabledCategoryLabel.hidden = true;
-      return;
-    }
+    let label;
 
-    let label;
-    switch (this.behaviorPref) {
-      case Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN:
-        label = "contentBlocking.cookies.blocking3rdParty2.label";
-        break;
-      case Ci.nsICookieService.BEHAVIOR_REJECT:
-        label = "contentBlocking.cookies.blockingAll2.label";
-        break;
-      case Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN:
-        label = "contentBlocking.cookies.blockingUnvisited2.label";
-        break;
-      case Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER:
-        label = "contentBlocking.cookies.blockingTrackers3.label";
-        break;
-      default:
-        Cu.reportError(
-          `Error: Unknown cookieBehavior pref observed: ${this.behaviorPref}`
-        );
-        break;
+    if (!this.enabled) {
+      label = "contentBlocking.cookies.blockingTrackers3.label";
+    } else {
+      switch (this.behaviorPref) {
+        case Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN:
+          label = "contentBlocking.cookies.blocking3rdParty2.label";
+          break;
+        case Ci.nsICookieService.BEHAVIOR_REJECT:
+          label = "contentBlocking.cookies.blockingAll2.label";
+          break;
+        case Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN:
+          label = "contentBlocking.cookies.blockingUnvisited2.label";
+          break;
+        case Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER:
+          label = "contentBlocking.cookies.blockingTrackers3.label";
+          break;
+        default:
+          Cu.reportError(
+            `Error: Unknown cookieBehavior pref observed: ${this.behaviorPref}`
+          );
+          break;
+      }
     }
-    this.enabledCategoryLabel.textContent = label
+    this.categoryLabel.textContent = label
       ? gNavigatorBundle.getString(label)
       : "";
-
-    this.disabledCategoryLabel.hidden = true;
-    this.enabledCategoryLabel.hidden = false;
   },
 
   get enabled() {
     return this.PREF_ENABLED_VALUES.includes(this.behaviorPref);
   },
 
   isBlocking(state) {
     return (
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -32,16 +32,17 @@ XPCOMUtils.defineLazyModuleGetters(this,
   DownloadsCommon: "resource:///modules/DownloadsCommon.jsm",
   DownloadUtils: "resource://gre/modules/DownloadUtils.jsm",
   E10SUtils: "resource://gre/modules/E10SUtils.jsm",
   ExtensionsUI: "resource:///modules/ExtensionsUI.jsm",
   HomePage: "resource:///modules/HomePage.jsm",
   LightweightThemeConsumer:
     "resource://gre/modules/LightweightThemeConsumer.jsm",
   Log: "resource://gre/modules/Log.jsm",
+  LoginHelper: "resource://gre/modules/LoginHelper.jsm",
   LoginManagerParent: "resource://gre/modules/LoginManagerParent.jsm",
   MigrationUtils: "resource:///modules/MigrationUtils.jsm",
   NetUtil: "resource://gre/modules/NetUtil.jsm",
   NewTabUtils: "resource://gre/modules/NewTabUtils.jsm",
   OpenInTabsUtils: "resource:///modules/OpenInTabsUtils.jsm",
   PageActions: "resource:///modules/PageActions.jsm",
   PageThumbs: "resource://gre/modules/PageThumbs.jsm",
   PanelMultiView: "resource:///modules/PanelMultiView.jsm",
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -1,22 +1,14 @@
 /* -*- tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ts=2 sw=2 sts=2 et tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-XPCOMUtils.defineLazyModuleGetters(this, {
-  LoginHelper: "resource://gre/modules/LoginHelper.jsm",
-  LoginManagerContextMenu: "resource://gre/modules/LoginManagerContextMenu.jsm",
-  DevToolsShim: "chrome://devtools-startup/content/DevToolsShim.jsm",
-});
-
-var gContextMenuContentData = null;
-
 function openContextMenu(aMessage, aBrowser, aActor) {
   if (BrowserHandler.kiosk) {
     // Don't display context menus in kiosk mode
     return;
   }
   let data = aMessage.data;
   let browser = aBrowser;
   let actor = aActor;
@@ -59,17 +51,17 @@ function openContextMenu(aMessage, aBrow
     );
   }
   if (data.context.storagePrincipal) {
     data.context.storagePrincipal = E10SUtils.deserializePrincipal(
       data.context.storagePrincipal
     );
   }
 
-  gContextMenuContentData = {
+  nsContextMenu.contentData = {
     context: data.context,
     popupNodeSelectors: data.popupNodeSelectors,
     browser,
     actor,
     editFlags: data.editFlags,
     spellInfo,
     principal,
     storagePrincipal,
@@ -87,17 +79,17 @@ function openContextMenu(aMessage, aBrow
     disableSetDesktopBackground: data.disableSetDesktopBackground,
     loginFillInfo: data.loginFillInfo,
     parentAllowsMixedContent: data.parentAllowsMixedContent,
     userContextId: data.userContextId,
     webExtContextData: data.webExtContextData,
   };
 
   let popup = browser.ownerDocument.getElementById("contentAreaContextMenu");
-  let context = gContextMenuContentData.context;
+  let context = nsContextMenu.contentData.context;
 
   // The event is a CPOW that can't be passed into the native openPopupAtScreen
   // function. Therefore we synthesize a new MouseEvent to propagate the
   // inputSource to the subsequently triggered popupshowing event.
   var newEvent = document.createEvent("MouseEvent");
   newEvent.initNSMouseEvent(
     "contextmenu",
     true,
@@ -115,36 +107,30 @@ function openContextMenu(aMessage, aBrow
     0,
     null,
     0,
     context.mozInputSource
   );
   popup.openPopupAtScreen(newEvent.screenX, newEvent.screenY, true, newEvent);
 }
 
-function nsContextMenu(aXulMenu, aIsShift) {
-  this.shouldDisplay = true;
-  this.initMenu(aXulMenu, aIsShift);
-}
-
-// Prototype for nsContextMenu "class."
-nsContextMenu.prototype = {
-  initMenu: function CM_initMenu(aXulMenu, aIsShift) {
+class nsContextMenu {
+  constructor(aXulMenu, aIsShift) {
     // Get contextual info.
     this.setContext();
 
     if (!this.shouldDisplay) {
       return;
     }
 
     this.hasPageMenu = false;
     this.isContentSelected = !this.selectionInfo.docSelectionIsCollapsed;
     if (!aIsShift) {
       this.hasPageMenu = PageMenuParent.addToPopup(
-        gContextMenuContentData.customMenuItems,
+        this.contentData.customMenuItems,
         this.browser,
         aXulMenu
       );
 
       let tab =
         gBrowser && gBrowser.getTabForBrowser
           ? gBrowser.getTabForBrowser(this.browser)
           : undefined;
@@ -161,29 +147,27 @@ nsContextMenu.prototype = {
         onImage: this.onImage,
         onVideo: this.onVideo,
         onAudio: this.onAudio,
         onCanvas: this.onCanvas,
         onEditable: this.onEditable,
         onSpellcheckable: this.onSpellcheckable,
         onPassword: this.onPassword,
         srcUrl: this.mediaURL,
-        frameUrl: gContextMenuContentData
-          ? gContextMenuContentData.docLocation
-          : undefined,
+        frameUrl: this.contentData ? this.contentData.docLocation : undefined,
         pageUrl: this.browser ? this.browser.currentURI.spec : undefined,
         linkText: this.linkTextStr,
         linkUrl: this.linkURL,
         selectionText: this.isTextSelected
           ? this.selectionInfo.fullText
           : undefined,
         frameId: this.frameOuterWindowID,
         webExtBrowserType: this.webExtBrowserType,
-        webExtContextData: gContextMenuContentData
-          ? gContextMenuContentData.webExtContextData
+        webExtContextData: this.contentData
+          ? this.contentData.webExtContextData
           : undefined,
       };
       subject.wrappedJSObject = subject;
       Services.obs.notifyObservers(subject, "on-build-contextmenu");
     }
 
     this.viewFrameSourceElement = document.getElementById(
       "context-viewframesource"
@@ -203,24 +187,25 @@ nsContextMenu.prototype = {
 
     let bookmarkPage = document.getElementById("context-bookmarkpage");
     if (bookmarkPage) {
       BookmarkingUI.onCurrentPageContextPopupShowing();
     }
 
     // Initialize (disable/remove) menu items.
     this.initItems();
-  },
+  }
 
   setContext() {
     let context = Object.create(null);
 
-    if (gContextMenuContentData) {
-      context = gContextMenuContentData.context;
-      gContextMenuContentData.context = null;
+    if (nsContextMenu.contentData) {
+      this.contentData = nsContextMenu.contentData;
+      context = this.contentData.context;
+      nsContextMenu.contentData = null;
     }
 
     this.shouldDisplay = context.shouldDisplay;
     this.timeStamp = context.timeStamp;
 
     // Assign what's _possibly_ needed from `context` sent by ContextMenuChild.jsm
     // Keep this consistent with the similar code in ContextMenu's _setContext
     this.bgImageURL = context.bgImageURL;
@@ -278,26 +263,26 @@ nsContextMenu.prototype = {
 
     // Everything after this isn't sent directly from ContextMenu
     if (this.target) {
       this.ownerDoc = this.target.ownerDocument;
     }
 
     this.csp = E10SUtils.deserializeCSP(context.csp);
 
-    // Remember the CSS selectors corresponding to clicked node. gContextMenuContentData
+    // Remember the CSS selectors corresponding to clicked node. this.contentData
     // can be null if the menu was triggered by tests in which case use an empty array.
-    this.targetSelectors = gContextMenuContentData
-      ? gContextMenuContentData.popupNodeSelectors
+    this.targetSelectors = this.contentData
+      ? this.contentData.popupNodeSelectors
       : [];
 
-    if (gContextMenuContentData) {
-      this.browser = gContextMenuContentData.browser;
-      this.selectionInfo = gContextMenuContentData.selectionInfo;
-      this.actor = gContextMenuContentData.actor;
+    if (this.contentData) {
+      this.browser = this.contentData.browser;
+      this.selectionInfo = this.contentData.selectionInfo;
+      this.actor = this.contentData.actor;
     } else {
       this.browser = this.ownerDoc.defaultView.docShell.chromeEventHandler;
       this.selectionInfo = BrowserUtils.getSelectionDetails(window);
       this.actor = this.browser.browsingContext.currentWindowGlobal.getActor(
         "ContextMenu"
       );
     }
 
@@ -311,74 +296,74 @@ nsContextMenu.prototype = {
     this.inWebExtBrowser = !!this.webExtBrowserType;
     this.inTabBrowser =
       gBrowser && gBrowser.getTabForBrowser
         ? !!gBrowser.getTabForBrowser(this.browser)
         : false;
 
     if (context.shouldInitInlineSpellCheckerUINoChildren) {
       InlineSpellCheckerUI.initFromRemote(
-        gContextMenuContentData.spellInfo,
+        this.contentData.spellInfo,
         this.actor.manager
       );
     }
 
     if (context.shouldInitInlineSpellCheckerUIWithChildren) {
       InlineSpellCheckerUI.initFromRemote(
-        gContextMenuContentData.spellInfo,
+        this.contentData.spellInfo,
         this.actor.manager
       );
       let canSpell = InlineSpellCheckerUI.canSpellCheck && this.canSpellCheck;
       this.showItem("spell-check-enabled", canSpell);
       this.showItem("spell-separator", canSpell);
     }
-  }, // setContext
+  } // setContext
 
-  hiding: function CM_hiding() {
+  hiding() {
     if (this.actor) {
       this.actor.hiding();
     }
 
-    gContextMenuContentData = null;
+    this.contentData = null;
     InlineSpellCheckerUI.clearSuggestionsFromMenu();
     InlineSpellCheckerUI.clearDictionaryListFromMenu();
     InlineSpellCheckerUI.uninit();
     if (
       Cu.isModuleLoaded("resource://gre/modules/LoginManagerContextMenu.jsm")
     ) {
-      LoginManagerContextMenu.clearLoginsFromMenu(document);
+      nsContextMenu.LoginManagerContextMenu.clearLoginsFromMenu(document);
     }
 
     // This handler self-deletes, only run it if it is still there:
     if (this._onPopupHiding) {
       this._onPopupHiding();
     }
-  },
+  }
 
-  initItems: function CM_initItems() {
+  initItems() {
     this.initPageMenuSeparator();
     this.initOpenItems();
     this.initNavigationItems();
     this.initViewItems();
     this.initMiscItems();
     this.initSpellingItems();
     this.initSaveItems();
     this.initClipboardItems();
     this.initMediaPlayerItems();
     this.initLeaveDOMFullScreenItems();
     this.initClickToPlayItems();
     this.initPasswordManagerItems();
     this.initSyncItems();
-  },
+  }
 
-  initPageMenuSeparator: function CM_initPageMenuSeparator() {
+  initPageMenuSeparator() {
     this.showItem("page-menu-separator", this.hasPageMenu);
-  },
+  }
 
-  initOpenItems: function CM_initOpenItems() {
+  initOpenItems() {
     var isMailtoInternal = false;
     if (this.onMailtoLink) {
       var mailtoHandler = Cc[
         "@mozilla.org/uriloader/external-protocol-service;1"
       ]
         .getService(Ci.nsIExternalProtocolService)
         .getProtocolHandlerInfo("mailto");
       isMailtoInternal =
@@ -399,27 +384,24 @@ nsContextMenu.prototype = {
         this.linkURI = makeURI(this.linkURL);
       } catch (ex) {}
 
       this.linkTextStr = this.selectionInfo.linkText;
       this.onPlainTextLink = true;
     }
 
     var inContainer = false;
-    if (gContextMenuContentData.userContextId) {
+    if (this.contentData.userContextId) {
       inContainer = true;
       var item = document.getElementById("context-openlinkincontainertab");
 
-      item.setAttribute(
-        "data-usercontextid",
-        gContextMenuContentData.userContextId
-      );
+      item.setAttribute("data-usercontextid", this.contentData.userContextId);
 
       var label = ContextualIdentityService.getUserContextLabel(
-        gContextMenuContentData.userContextId
+        this.contentData.userContextId
       );
       item.setAttribute(
         "label",
         gBrowserBundle.formatStringFromName("userContextOpenLink.label", [
           label,
         ])
       );
     }
@@ -438,19 +420,19 @@ nsContextMenu.prototype = {
     this.showItem("context-openlinkintab", shouldShow && !inContainer);
     this.showItem("context-openlinkincontainertab", shouldShow && inContainer);
     this.showItem(
       "context-openlinkinusercontext-menu",
       shouldShow && !isWindowPrivate && showContainers
     );
     this.showItem("context-openlinkincurrent", this.onPlainTextLink);
     this.showItem("context-sep-open", shouldShow);
-  },
+  }
 
-  initNavigationItems: function CM_initNavigationItems() {
+  initNavigationItems() {
     var shouldShow =
       !(
         this.isContentSelected ||
         this.onLink ||
         this.onImage ||
         this.onCanvas ||
         this.onVideo ||
         this.onAudio ||
@@ -464,30 +446,30 @@ nsContextMenu.prototype = {
 
     let stopReloadItem = "";
     if (shouldShow || !this.inTabBrowser) {
       stopReloadItem = stopped || !this.inTabBrowser ? "reload" : "stop";
     }
 
     this.showItem("context-reload", stopReloadItem == "reload");
     this.showItem("context-stop", stopReloadItem == "stop");
-  },
+  }
 
-  initLeaveDOMFullScreenItems: function CM_initLeaveFullScreenItem() {
+  initLeaveDOMFullScreenItems() {
     // only show the option if the user is in DOM fullscreen
     var shouldShow = this.target.ownerDocument.fullscreen;
     this.showItem("context-leave-dom-fullscreen", shouldShow);
 
     // Explicitly show if in DOM fullscreen, but do not hide it has already been shown
     if (shouldShow) {
       this.showItem("context-media-sep-commands", true);
     }
-  },
+  }
 
-  initSaveItems: function CM_initSaveItems() {
+  initSaveItems() {
     var shouldShow = !(
       this.onTextInput ||
       this.onLink ||
       this.isContentSelected ||
       this.onImage ||
       this.onCanvas ||
       this.onVideo ||
       this.onAudio
@@ -517,19 +499,19 @@ nsContextMenu.prototype = {
       "disabled",
       !this.mediaURL || mediaIsBlob
     );
     this.setItemAttr(
       "context-sendaudio",
       "disabled",
       !this.mediaURL || mediaIsBlob
     );
-  },
+  }
 
-  initViewItems: function CM_initViewItems() {
+  initViewItems() {
     // View source is always OK, unless in directory listing.
     this.showItem(
       "context-viewpartialsource-selection",
       !this.inAboutDevtoolsToolbox && this.isContentSelected
     );
 
     var shouldShow = !(
       this.isContentSelected ||
@@ -588,18 +570,19 @@ nsContextMenu.prototype = {
     }
 
     this.showItem(
       "context-setDesktopBackground",
       haveSetDesktopBackground && this.onLoadedImage
     );
 
     if (haveSetDesktopBackground && this.onLoadedImage) {
-      document.getElementById("context-setDesktopBackground").disabled =
-        gContextMenuContentData.disableSetDesktopBackground;
+      document.getElementById(
+        "context-setDesktopBackground"
+      ).disabled = this.contentData.disableSetDesktopBackground;
     }
 
     // Reload image depends on an image that's not fully loaded
     this.showItem(
       "context-reloadimage",
       this.onImage && !this.onCompletedImage
     );
 
@@ -636,19 +619,19 @@ nsContextMenu.prototype = {
       "context-viewimageinfo",
       "disabled",
       this.webExtBrowserType === "popup"
     );
     this.showItem(
       "context-viewimagedesc",
       this.onImage && this.imageDescURL !== ""
     );
-  },
+  }
 
-  initMiscItems: function CM_initMiscItems() {
+  initMiscItems() {
     // Use "Bookmark This Link" if on a link.
     let bookmarkPage = document.getElementById("context-bookmarkpage");
     this.showItem(
       bookmarkPage,
       !(
         this.isContentSelected ||
         this.onTextInput ||
         this.onLink ||
@@ -710,17 +693,17 @@ nsContextMenu.prototype = {
     this.showItem(
       "context-bidi-text-direction-toggle",
       this.onTextInput && !this.onNumeric && top.gBidiUI
     );
     this.showItem(
       "context-bidi-page-direction-toggle",
       !this.onTextInput && top.gBidiUI
     );
-  },
+  }
 
   initSpellingItems() {
     var canSpell =
       InlineSpellCheckerUI.canSpellCheck &&
       !InlineSpellCheckerUI.initialSpellCheckPending &&
       this.canSpellCheck;
     let showDictionaries = canSpell && InlineSpellCheckerUI.enabled;
     var onMisspelling = InlineSpellCheckerUI.overMisspelling;
@@ -765,17 +748,17 @@ nsContextMenu.prototype = {
       // when there is no spellchecker but we might be able to spellcheck
       // add the add to dictionaries item. This will ensure that people
       // with no dictionaries will be able to download them
       this.showItem("spell-language-separator", showDictionaries);
       this.showItem("spell-add-dictionaries-main", showDictionaries);
     } else {
       this.showItem("spell-add-dictionaries-main", false);
     }
-  },
+  }
 
   initClipboardItems() {
     // Copy depends on whether there is selected text.
     // Enabling this context menu item is now done through the global
     // command updating system
     // this.setItemAttr( "context-copy", "disabled", !this.isTextSelected() );
     goUpdateGlobalEditMenuItems();
 
@@ -826,17 +809,17 @@ nsContextMenu.prototype = {
     this.showItem("context-copyvideourl", this.onVideo);
     this.showItem("context-copyaudiourl", this.onAudio);
     this.setItemAttr("context-copyvideourl", "disabled", !this.mediaURL);
     this.setItemAttr("context-copyaudiourl", "disabled", !this.mediaURL);
     this.showItem(
       "context-sep-copyimage",
       this.onImage || this.onVideo || this.onAudio
     );
-  },
+  }
 
   initMediaPlayerItems() {
     var onMedia = this.onVideo || this.onAudio;
     // Several mutually exclusive items... play/pause, mute/unmute, show/hide
     this.showItem(
       "context-media-play",
       onMedia && (this.target.paused || this.target.ended)
     );
@@ -937,28 +920,27 @@ nsContextMenu.prototype = {
         this.setItemAttr(
           "context-video-pictureinpicture",
           "disabled",
           !this.onPiPVideo && hasError
         );
       }
     }
     this.showItem("context-media-sep-commands", onMedia);
-  },
+  }
 
   initClickToPlayItems() {
     this.showItem("context-ctp-play", this.onCTPPlugin);
     this.showItem("context-ctp-hide", this.onCTPPlugin);
     this.showItem("context-sep-ctp", this.onCTPPlugin);
-  },
+  }
 
   initPasswordManagerItems() {
-    let loginFillInfo =
-      gContextMenuContentData && gContextMenuContentData.loginFillInfo;
-    let documentURI = gContextMenuContentData.documentURIObject;
+    let loginFillInfo = this.contentData && this.contentData.loginFillInfo;
+    let documentURI = this.contentData.documentURIObject;
 
     // If we could not find a password field we
     // don't want to show the form fill option.
     let showFill =
       loginFillInfo &&
       loginFillInfo.passwordField.found &&
       !documentURI.schemeIs("about");
 
@@ -992,17 +974,17 @@ nsContextMenu.prototype = {
       );
     }
 
     if (!showFill || disableFill) {
       return;
     }
 
     let formOrigin = LoginHelper.getLoginOrigin(documentURI.spec);
-    let fragment = LoginManagerContextMenu.addLoginsToMenu(
+    let fragment = nsContextMenu.LoginManagerContextMenu.addLoginsToMenu(
       this.targetIdentifier,
       this.browser,
       formOrigin
     );
     let isGeneratedPasswordEnabled =
       LoginHelper.generationAvailable && LoginHelper.generationEnabled;
     let canFillGeneratedPassword =
       this.onPassword &&
@@ -1020,164 +1002,170 @@ nsContextMenu.prototype = {
     );
 
     if (!fragment) {
       return;
     }
     let popup = document.getElementById("fill-login-popup");
     let insertBeforeElement = document.getElementById("fill-login-no-logins");
     popup.insertBefore(fragment, insertBeforeElement);
-  },
+  }
 
   initSyncItems() {
     gSync.updateContentContextMenu(this);
-  },
+  }
 
   openPasswordManager() {
     LoginHelper.openPasswordManager(window, {
-      filterString: gContextMenuContentData.documentURIObject.host,
+      filterString: this.contentData.documentURIObject.host,
       entryPoint: "contextmenu",
     });
-  },
+  }
 
   fillGeneratedPassword() {
-    LoginManagerContextMenu.fillGeneratedPassword(
+    nsContextMenu.LoginManagerContextMenu.fillGeneratedPassword(
       this.targetIdentifier,
-      gContextMenuContentData.documentURIObject,
+      this.contentData.documentURIObject,
       this.browser
     );
-  },
+  }
 
   inspectNode() {
-    return DevToolsShim.inspectNode(gBrowser.selectedTab, this.targetSelectors);
-  },
+    return nsContextMenu.DevToolsShim.inspectNode(
+      gBrowser.selectedTab,
+      this.targetSelectors
+    );
+  }
 
   inspectA11Y() {
-    return DevToolsShim.inspectA11Y(gBrowser.selectedTab, this.targetSelectors);
-  },
+    return nsContextMenu.DevToolsShim.inspectA11Y(
+      gBrowser.selectedTab,
+      this.targetSelectors
+    );
+  }
 
   _openLinkInParameters(extra) {
     let params = {
-      charset: gContextMenuContentData.charSet,
+      charset: this.contentData.charSet,
       originPrincipal: this.principal,
       originStoragePrincipal: this.storagePrincipal,
       triggeringPrincipal: this.principal,
       csp: this.csp,
-      frameOuterWindowID: gContextMenuContentData.frameOuterWindowID,
+      frameOuterWindowID: this.contentData.frameOuterWindowID,
     };
     for (let p in extra) {
       params[p] = extra[p];
     }
 
     let referrerInfo = this.onLink
-      ? gContextMenuContentData.linkReferrerInfo
-      : gContextMenuContentData.referrerInfo;
+      ? this.contentData.linkReferrerInfo
+      : this.contentData.referrerInfo;
     // If we want to change userContextId, we must be sure that we don't
     // propagate the referrer.
     if (
       ("userContextId" in params &&
-        params.userContextId != gContextMenuContentData.userContextId) ||
+        params.userContextId != this.contentData.userContextId) ||
       this.onPlainTextLink
     ) {
       referrerInfo = new ReferrerInfo(
         referrerInfo.referrerPolicy,
         false,
         referrerInfo.originalReferrer
       );
     }
 
     params.referrerInfo = referrerInfo;
     return params;
-  },
+  }
 
   // Open linked-to URL in a new window.
   openLink() {
     openLinkIn(this.linkURL, "window", this._openLinkInParameters());
-  },
+  }
 
   // Open linked-to URL in a new private window.
   openLinkInPrivateWindow() {
     openLinkIn(
       this.linkURL,
       "window",
       this._openLinkInParameters({ private: true })
     );
-  },
+  }
 
   // Open linked-to URL in a new tab.
   openLinkInTab(event) {
-    let referrerURI = gContextMenuContentData.documentURIObject;
+    let referrerURI = this.contentData.documentURIObject;
 
     // if its parent allows mixed content and the referring URI passes
     // a same origin check with the target URI, we can preserve the users
     // decision of disabling MCB on a page for it's child tabs.
     let persistAllowMixedContentInChildTab = false;
 
-    if (gContextMenuContentData.parentAllowsMixedContent) {
+    if (this.contentData.parentAllowsMixedContent) {
       const sm = Services.scriptSecurityManager;
       try {
         let targetURI = this.linkURI;
         let isPrivateWin =
           this.browser.contentPrincipal.originAttributes.privateBrowsingId > 0;
         sm.checkSameOriginURI(referrerURI, targetURI, false, isPrivateWin);
         persistAllowMixedContentInChildTab = true;
       } catch (e) {}
     }
 
     let params = {
       allowMixedContent: persistAllowMixedContentInChildTab,
       userContextId: parseInt(event.target.getAttribute("data-usercontextid")),
     };
 
     openLinkIn(this.linkURL, "tab", this._openLinkInParameters(params));
-  },
+  }
 
   // open URL in current tab
   openLinkInCurrent() {
     openLinkIn(this.linkURL, "current", this._openLinkInParameters());
-  },
+  }
 
   // Open frame in a new tab.
   openFrameInTab() {
-    openLinkIn(gContextMenuContentData.docLocation, "tab", {
-      charset: gContextMenuContentData.charSet,
+    openLinkIn(this.contentData.docLocation, "tab", {
+      charset: this.contentData.charSet,
       triggeringPrincipal: this.browser.contentPrincipal,
       csp: this.browser.csp,
-      referrerInfo: gContextMenuContentData.frameReferrerInfo,
+      referrerInfo: this.contentData.frameReferrerInfo,
     });
-  },
+  }
 
   // Reload clicked-in frame.
   reloadFrame(aEvent) {
     let forceReload = aEvent.shiftKey;
     this.actor.reloadFrame(this.targetIdentifier, forceReload);
-  },
+  }
 
   // Open clicked-in frame in its own window.
   openFrame() {
-    openLinkIn(gContextMenuContentData.docLocation, "window", {
-      charset: gContextMenuContentData.charSet,
+    openLinkIn(this.contentData.docLocation, "window", {
+      charset: this.contentData.charSet,
       triggeringPrincipal: this.browser.contentPrincipal,
       csp: this.browser.csp,
-      referrerInfo: gContextMenuContentData.frameReferrerInfo,
+      referrerInfo: this.contentData.frameReferrerInfo,
     });
-  },
+  }
 
   // Open clicked-in frame in the same window.
   showOnlyThisFrame() {
     urlSecurityCheck(
-      gContextMenuContentData.docLocation,
+      this.contentData.docLocation,
       this.browser.contentPrincipal,
       Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT
     );
-    openWebLinkIn(gContextMenuContentData.docLocation, "current", {
-      referrerInfo: gContextMenuContentData.frameReferrerInfo,
+    openWebLinkIn(this.contentData.docLocation, "current", {
+      referrerInfo: this.contentData.frameReferrerInfo,
       triggeringPrincipal: this.browser.contentPrincipal,
     });
-  },
+  }
 
   // View Partial Source
   viewPartialSource() {
     let { browser } = this;
     let openSelectionFn = function() {
       let tabBrowser = gBrowser;
       const inNewWindow = !Services.prefs.getBoolPref("view_source.tab");
       // In the case of popups, we need to find a non-popup browser window.
@@ -1200,86 +1188,86 @@ nsContextMenu.prototype = {
       if (inNewWindow) {
         tabBrowser.hideTab(tab);
         tabBrowser.replaceTabsWithWindow(tab);
       }
       return viewSourceBrowser;
     };
 
     top.gViewSourceUtils.viewPartialSourceInBrowser(browser, openSelectionFn);
-  },
+  }
 
   // Open new "view source" window with the frame's URL.
   viewFrameSource() {
     BrowserViewSourceOfDocument({
       browser: this.browser,
-      URL: gContextMenuContentData.docLocation,
+      URL: this.contentData.docLocation,
       outerWindowID: this.frameOuterWindowID,
     });
-  },
+  }
 
   viewInfo() {
     BrowserPageInfo(
-      gContextMenuContentData.docLocation,
+      this.contentData.docLocation,
       null,
       null,
       null,
       this.browser
     );
-  },
+  }
 
   viewImageInfo() {
     BrowserPageInfo(
-      gContextMenuContentData.docLocation,
+      this.contentData.docLocation,
       "mediaTab",
       this.imageInfo,
       null,
       this.browser
     );
-  },
+  }
 
   viewImageDesc(e) {
     urlSecurityCheck(
       this.imageDescURL,
       this.principal,
       Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT
     );
     openUILink(this.imageDescURL, e, {
-      referrerInfo: gContextMenuContentData.referrerInfo,
+      referrerInfo: this.contentData.referrerInfo,
       triggeringPrincipal: this.principal,
       csp: this.csp,
     });
-  },
+  }
 
   viewFrameInfo() {
     BrowserPageInfo(
-      gContextMenuContentData.docLocation,
+      this.contentData.docLocation,
       null,
       null,
       this.actor.browsingContext,
       this.browser
     );
-  },
+  }
 
   reloadImage() {
     urlSecurityCheck(
       this.mediaURL,
       this.principal,
       Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT
     );
     this.actor.reloadImage(this.targetIdentifier);
-  },
+  }
 
   _canvasToBlobURL(targetIdentifier) {
     return this.actor.canvasToBlobURL(targetIdentifier);
-  },
+  }
 
   // Change current window to the URL of the image, video, or audio.
   viewMedia(e) {
-    let referrerInfo = gContextMenuContentData.referrerInfo;
+    let referrerInfo = this.contentData.referrerInfo;
     let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
     if (this.onCanvas) {
       this._canvasToBlobURL(this.targetIdentifier).then(function(blobURL) {
         openUILink(blobURL, e, {
           referrerInfo,
           triggeringPrincipal: systemPrincipal,
         });
       }, Cu.reportError);
@@ -1291,17 +1279,17 @@ nsContextMenu.prototype = {
       );
       openUILink(this.mediaURL, e, {
         referrerInfo,
         forceAllowDataURI: true,
         triggeringPrincipal: this.principal,
         csp: this.csp,
       });
     }
-  },
+  }
 
   saveVideoFrameAsImage() {
     let isPrivate = PrivateBrowsingUtils.isBrowserPrivate(this.browser);
 
     let name = "";
     if (this.mediaURL) {
       try {
         let uri = makeURI(this.mediaURL);
@@ -1311,17 +1299,17 @@ nsContextMenu.prototype = {
         }
       } catch (e) {}
     }
     if (!name) {
       name = "snapshot.jpg";
     }
 
     // Cache this because we fetch the data async
-    let referrerInfo = gContextMenuContentData.referrerInfo;
+    let referrerInfo = this.contentData.referrerInfo;
 
     this.actor.saveVideoFrameAsImage(this.targetIdentifier).then(dataURL => {
       // FIXME can we switch this to a blob URL?
       saveImageURL(
         dataURL,
         name,
         "SaveImageTitle",
         true, // bypass cache
@@ -1329,36 +1317,36 @@ nsContextMenu.prototype = {
         referrerInfo, // referrer info
         null, // document
         null, // content type
         null, // content disposition
         isPrivate,
         this.principal
       );
     });
-  },
+  }
 
   leaveDOMFullScreen() {
     document.exitFullscreen();
-  },
+  }
 
   // Change current window to the URL of the background image.
   viewBGImage(e) {
     urlSecurityCheck(
       this.bgImageURL,
       this.principal,
       Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT
     );
 
     openUILink(this.bgImageURL, e, {
-      referrerInfo: gContextMenuContentData.referrerInfo,
+      referrerInfo: this.contentData.referrerInfo,
       triggeringPrincipal: this.principal,
       csp: this.csp,
     });
-  },
+  }
 
   setDesktopBackground() {
     if (!Services.policies.isAllowed("setDesktopBackground")) {
       return;
     }
 
     this.actor
       .setAsDesktopBackground(this.targetIdentifier)
@@ -1401,22 +1389,22 @@ nsContextMenu.prototype = {
             kDesktopBackgroundURL,
             "",
             "centerscreen,chrome,dialog,modal,dependent",
             image,
             imageName
           );
         }
       });
-  },
+  }
 
   // Save URL of clicked-on frame.
   saveFrame() {
     saveBrowser(this.browser, false, this.frameOuterWindowID);
-  },
+  }
 
   // Helper function to wait for appropriate MIME-type headers and
   // then prompt the user with a file picker
   saveHelper(
     linkURL,
     linkText,
     dialogTitle,
     bypassCache,
@@ -1585,50 +1573,50 @@ nsContextMenu.prototype = {
     timer.initWithCallback(
       new timerCallback(),
       timeToWait,
       timer.TYPE_ONE_SHOT
     );
 
     // kick off the channel with our proxy object as the listener
     channel.asyncOpen(new saveAsListener(this.principal));
-  },
+  }
 
   // Save URL of clicked-on link.
   saveLink() {
     let referrerInfo = this.onLink
-      ? gContextMenuContentData.linkReferrerInfo
-      : gContextMenuContentData.referrerInfo;
+      ? this.contentData.linkReferrerInfo
+      : this.contentData.referrerInfo;
 
     let isContentWindowPrivate = this.ownerDoc.isPrivate;
     this.saveHelper(
       this.linkURL,
       this.linkTextStr,
       null,
       true,
       this.ownerDoc,
       referrerInfo,
       this.frameOuterWindowID,
       this.linkDownload,
       isContentWindowPrivate
     );
-  },
+  }
 
   // Backwards-compatibility wrapper
   saveImage() {
     if (this.onCanvas || this.onImage) {
       this.saveMedia();
     }
-  },
+  }
 
   // Save URL of the clicked upon image, video, or audio.
   saveMedia() {
     let doc = this.ownerDoc;
     let isContentWindowPrivate = this.ownerDoc.isPrivate;
-    let referrerInfo = gContextMenuContentData.referrerInfo;
+    let referrerInfo = this.contentData.referrerInfo;
     let isPrivate = PrivateBrowsingUtils.isBrowserPrivate(this.browser);
     if (this.onCanvas) {
       // Bypass cache, since it's a data: URL.
       this._canvasToBlobURL(this.targetIdentifier).then(function(blobURL) {
         saveImageURL(
           blobURL,
           "canvas.png",
           "SaveImageTitle",
@@ -1647,18 +1635,18 @@ nsContextMenu.prototype = {
       saveImageURL(
         this.mediaURL,
         null,
         "SaveImageTitle",
         false,
         false,
         referrerInfo,
         null,
-        gContextMenuContentData.contentType,
-        gContextMenuContentData.contentDisposition,
+        this.contentData.contentType,
+        this.contentData.contentDisposition,
         isPrivate,
         this.principal
       );
     } else if (this.onVideo || this.onAudio) {
       var dialogTitle = this.onVideo ? "SaveVideoTitle" : "SaveAudioTitle";
       this.saveHelper(
         this.mediaURL,
         null,
@@ -1666,36 +1654,36 @@ nsContextMenu.prototype = {
         false,
         doc,
         referrerInfo,
         this.frameOuterWindowID,
         "",
         isContentWindowPrivate
       );
     }
-  },
+  }
 
   // Backwards-compatibility wrapper
   sendImage() {
     if (this.onCanvas || this.onImage) {
       this.sendMedia();
     }
-  },
+  }
 
   sendMedia() {
     MailIntegration.sendMessage(this.mediaURL, "");
-  },
+  }
 
   playPlugin() {
     this.actor.pluginCommand("play", this.targetIdentifier);
-  },
+  }
 
   hidePlugin() {
     this.actor.pluginCommand("hide", this.targetIdentifier);
-  },
+  }
 
   // Generate email address and put it on clipboard.
   copyEmail() {
     // Copy the comma-separated list of email addresses only.
     // There are other ways of embedding email addresses in a mailto:
     // link, but such complex parsing is beyond us.
     var url = this.linkURL;
     var qmark = url.indexOf("?");
@@ -1703,37 +1691,37 @@ nsContextMenu.prototype = {
 
     // 7 == length of "mailto:"
     addresses = qmark > 7 ? url.substring(7, qmark) : url.substr(7);
 
     // Let's try to unescape it using a character set
     // in case the address is not ASCII.
     try {
       addresses = Services.textToSubURI.unEscapeURIForUI(
-        gContextMenuContentData.charSet,
+        this.contentData.charSet,
         addresses
       );
     } catch (ex) {
       // Do nothing.
     }
 
     var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(
       Ci.nsIClipboardHelper
     );
     clipboard.copyString(addresses);
-  },
+  }
 
   copyLink() {
     // If we're in a view source tab, remove the view-source: prefix
     let linkURL = this.linkURL.replace(/^view-source:/, "");
     var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(
       Ci.nsIClipboardHelper
     );
     clipboard.copyString(linkURL);
-  },
+  }
 
   addKeywordForSearchField() {
     this.actor.getSearchFieldBookmarkData(this.targetIdentifier).then(data => {
       let title = gNavigatorBundle.getFormattedString(
         "addKeywordTitleAutoFill",
         [data.title]
       );
       PlacesUIUtils.showBookmarkDialog(
@@ -1745,17 +1733,17 @@ nsContextMenu.prototype = {
           keyword: "",
           postData: data.postData,
           charSet: data.charset,
           hiddenRows: ["location", "tags"],
         },
         window
       );
     });
-  },
+  }
 
   /**
    * Utilities
    */
 
   /**
    * Show/hide one item (specified via name or the item element itself).
    * If the element is not found, then this function finishes silently.
@@ -1767,81 +1755,81 @@ nsContextMenu.prototype = {
   showItem(aItemOrId, aShow) {
     var item =
       aItemOrId.constructor == String
         ? document.getElementById(aItemOrId)
         : aItemOrId;
     if (item) {
       item.hidden = !aShow;
     }
-  },
+  }
 
   // Set given attribute of specified context-menu item.  If the
   // value is null, then it removes the attribute (which works
   // nicely for the disabled attribute).
   setItemAttr(aID, aAttr, aVal) {
     var elem = document.getElementById(aID);
     if (elem) {
       if (aVal == null) {
         // null indicates attr should be removed.
         elem.removeAttribute(aAttr);
       } else {
         // Set attr=val.
         elem.setAttribute(aAttr, aVal);
       }
     }
-  },
+  }
 
   // Temporary workaround for DOM api not yet implemented by XUL nodes.
   cloneNode(aItem) {
     // Create another element like the one we're cloning.
     var node = document.createElement(aItem.tagName);
 
     // Copy attributes from argument item to the new one.
     var attrs = aItem.attributes;
     for (var i = 0; i < attrs.length; i++) {
       var attr = attrs.item(i);
       node.setAttribute(attr.nodeName, attr.nodeValue);
     }
 
     // Voila!
     return node;
-  },
+  }
 
   getLinkURI() {
     try {
       return makeURI(this.linkURL);
     } catch (ex) {
       // e.g. empty URL string
     }
 
     return null;
-  },
+  }
 
   // Kept for addon compat
   linkText() {
     return this.linkTextStr;
-  },
+  }
 
   // Determines whether or not the separator with the specified ID should be
   // shown or not by determining if there are any non-hidden items between it
   // and the previous separator.
   shouldShowSeparator(aSeparatorID) {
     var separator = document.getElementById(aSeparatorID);
     if (separator) {
       var sibling = separator.previousSibling;
       while (sibling && sibling.localName != "menuseparator") {
         if (!sibling.hidden) {
           return true;
         }
         sibling = sibling.previousSibling;
       }
     }
     return false;
-  },
+  }
 
   addDictionaries() {
     var uri = formatURL("browser.dictionaries.download.url", true);
 
     var locale = "-";
     try {
       locale = Services.prefs.getComplexValue(
         "intl.accept_languages",
@@ -1857,86 +1845,86 @@ nsContextMenu.prototype = {
     uri = uri.replace(/%LOCALE%/, escape(locale)).replace(/%VERSION%/, version);
 
     var newWindowPref = Services.prefs.getIntPref(
       "browser.link.open_newwindow"
     );
     var where = newWindowPref == 3 ? "tab" : "window";
 
     openTrustedLinkIn(uri, where);
-  },
+  }
 
-  bookmarkThisPage: function CM_bookmarkThisPage() {
+  bookmarkThisPage() {
     window.top.PlacesCommandHook.bookmarkPage().catch(Cu.reportError);
-  },
+  }
 
-  bookmarkLink: function CM_bookmarkLink() {
+  bookmarkLink() {
     window.top.PlacesCommandHook.bookmarkLink(
       this.linkURL,
       this.linkTextStr
     ).catch(Cu.reportError);
-  },
+  }
 
-  addBookmarkForFrame: function CM_addBookmarkForFrame() {
-    let uri = gContextMenuContentData.documentURIObject;
+  addBookmarkForFrame() {
+    let uri = this.contentData.documentURIObject;
 
     this.actor.getFrameTitle(this.targetIdentifier).then(title => {
       window.top.PlacesCommandHook.bookmarkLink(uri.spec, title).catch(
         Cu.reportError
       );
     });
-  },
+  }
 
-  savePageAs: function CM_savePageAs() {
+  savePageAs() {
     saveBrowser(this.browser);
-  },
+  }
 
-  printFrame: function CM_printFrame() {
+  printFrame() {
     PrintUtils.printWindow(this.frameOuterWindowID, this.browser);
-  },
+  }
 
-  switchPageDirection: function CM_switchPageDirection() {
+  switchPageDirection() {
     gBrowser.selectedBrowser.sendMessageToActor(
       "SwitchDocumentDirection",
       {},
       "SwitchDocumentDirection",
       true
     );
-  },
+  }
 
-  mediaCommand: function CM_mediaCommand(command, data) {
+  mediaCommand(command, data) {
     this.actor.mediaCommand(this.targetIdentifier, command, data);
-  },
+  }
 
   copyMediaLocation() {
     var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(
       Ci.nsIClipboardHelper
     );
     clipboard.copyString(this.mediaURL);
-  },
+  }
 
   drmLearnMore(aEvent) {
     let drmInfoURL =
       Services.urlFormatter.formatURLPref("app.support.baseURL") +
       "drm-content";
     let dest = whereToOpenLink(aEvent);
     // Don't ever want this to open in the same tab as it'll unload the
     // DRM'd video, which is going to be a bad idea in most cases.
     if (dest == "current") {
       dest = "tab";
     }
     openTrustedLinkIn(drmInfoURL, dest);
-  },
+  }
 
   get imageURL() {
     if (this.onImage) {
       return this.mediaURL;
     }
     return "";
-  },
+  }
 
   // Formats the 'Search <engine> for "<selection or link text>"' context menu.
   showAndFormatSearchContextItem() {
     const docIsPrivate = PrivateBrowsingUtils.isBrowserPrivate(this.browser);
     const privatePref = "browser.search.separatePrivateDefault.ui.enabled";
     let showSearchSelect =
       !this.inAboutDevtoolsToolbox &&
       (this.isTextSelected || this.onLink) &&
@@ -2004,22 +1992,27 @@ nsContextMenu.prototype = {
         accessKey = "contextMenuPrivateSearchOtherEngine.accesskey";
       } else {
         menuItemPrivate.label = gNavigatorBundle.getString(
           "contextMenuPrivateSearch"
         );
       }
       menuItemPrivate.accessKey = gNavigatorBundle.getString(accessKey);
     }
-  },
+  }
 
   createContainerMenu(aEvent) {
     let createMenuOptions = {
       isContextMenu: true,
-      excludeUserContextId: gContextMenuContentData.userContextId,
+      excludeUserContextId: this.contentData.userContextId,
     };
     return createUserContextMenu(aEvent, createMenuOptions);
-  },
+  }
 
   doCustomCommand(generatedItemId, handlingUserInput) {
     this.actor.doCustomCommand(generatedItemId, handlingUserInput);
-  },
-};
+  }
+}
+
+XPCOMUtils.defineLazyModuleGetters(nsContextMenu, {
+  LoginManagerContextMenu: "resource://gre/modules/LoginManagerContextMenu.jsm",
+  DevToolsShim: "chrome://devtools-startup/content/DevToolsShim.jsm",
+});
--- a/browser/base/content/test/general/browser_addKeywordSearch.js
+++ b/browser/base/content/test/general/browser_addKeywordSearch.js
@@ -22,17 +22,17 @@ add_task(async function() {
   for (let method of ["GET", "POST"]) {
     for (let { desc, action, param } of testData) {
       info(`Running ${method} keyword test '${desc}'`);
       let id = `keyword-form-${count++}`;
       let contextMenu = document.getElementById("contentAreaContextMenu");
       let contextMenuPromise = BrowserTestUtils.waitForEvent(
         contextMenu,
         "popupshown"
-      ).then(() => gContextMenuContentData.target);
+      );
 
       await ContentTask.spawn(
         tab.linkedBrowser,
         { action, param, method, id },
         async function(args) {
           let doc = content.document;
           let form = doc.createElement("form");
           form.id = args.id;
--- a/browser/base/content/test/trackingUI/browser_trackingUI_categories.js
+++ b/browser/base/content/test/trackingUI/browser_trackingUI_categories.js
@@ -18,146 +18,108 @@ registerCleanupFunction(function() {
   Services.prefs.clearUserPref(TP_PB_PREF);
   Services.prefs.clearUserPref(TPC_PREF);
   Services.prefs.clearUserPref(CM_PREF);
   Services.prefs.clearUserPref(FP_PREF);
   Services.prefs.clearUserPref(ST_PREF);
   Services.prefs.clearUserPref(STC_PREF);
 });
 
-add_task(async function testCookieCategoryLabels() {
+add_task(async function testCookieCategoryLabel() {
   await BrowserTestUtils.withNewTab("http://www.example.com", async function() {
     let categoryItem = document.getElementById(
       "protections-popup-category-cookies"
     );
-    let categoryLabelDisabled = document.getElementById(
-      "protections-popup-cookies-category-label-disabled"
-    );
-    let categoryLabelEnabled = document.getElementById(
-      "protections-popup-cookies-category-label-enabled"
+    let categoryLabel = document.getElementById(
+      "protections-popup-cookies-category-label"
     );
 
     Services.prefs.setIntPref(TPC_PREF, Ci.nsICookieService.BEHAVIOR_ACCEPT);
     await TestUtils.waitForCondition(
-      () =>
-        !categoryItem.classList.contains("blocked") &&
-        !categoryLabelDisabled.hidden &&
-        categoryLabelEnabled.hidden,
+      () => !categoryItem.classList.contains("blocked"),
       "The category label has updated correctly"
     );
-    ok(
-      !categoryItem.classList.contains("blocked") &&
-        !categoryLabelDisabled.hidden &&
-        categoryLabelEnabled.hidden
-    );
+    ok(!categoryItem.classList.contains("blocked"));
 
     Services.prefs.setIntPref(TPC_PREF, Ci.nsICookieService.BEHAVIOR_REJECT);
     await TestUtils.waitForCondition(
-      () =>
-        categoryItem.classList.contains("blocked") &&
-        categoryLabelDisabled.hidden &&
-        !categoryLabelEnabled.hidden,
+      () => categoryItem.classList.contains("blocked"),
       "The category label has updated correctly"
     );
-    ok(
-      categoryItem.classList.contains("blocked") &&
-        categoryLabelDisabled.hidden &&
-        !categoryLabelEnabled.hidden
-    );
+    ok(categoryItem.classList.contains("blocked"));
     await TestUtils.waitForCondition(
       () =>
-        categoryLabelEnabled.textContent ==
+        categoryLabel.textContent ==
         gNavigatorBundle.getString(
           "contentBlocking.cookies.blockingAll2.label"
         ),
       "The category label has updated correctly"
     );
     ok(
-      categoryLabelEnabled.textContent ==
+      categoryLabel.textContent ==
         gNavigatorBundle.getString("contentBlocking.cookies.blockingAll2.label")
     );
 
     Services.prefs.setIntPref(
       TPC_PREF,
       Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN
     );
     await TestUtils.waitForCondition(
-      () =>
-        categoryItem.classList.contains("blocked") &&
-        categoryLabelDisabled.hidden &&
-        !categoryLabelEnabled.hidden,
+      () => categoryItem.classList.contains("blocked"),
       "The category label has updated correctly"
     );
-    ok(
-      categoryItem.classList.contains("blocked") &&
-        categoryLabelDisabled.hidden &&
-        !categoryLabelEnabled.hidden
-    );
+    ok(categoryItem.classList.contains("blocked"));
     await TestUtils.waitForCondition(
       () =>
-        categoryLabelEnabled.textContent ==
+        categoryLabel.textContent ==
         gNavigatorBundle.getString(
           "contentBlocking.cookies.blocking3rdParty2.label"
         ),
       "The category label has updated correctly"
     );
     ok(
-      categoryLabelEnabled.textContent ==
+      categoryLabel.textContent ==
         gNavigatorBundle.getString(
           "contentBlocking.cookies.blocking3rdParty2.label"
         )
     );
 
     Services.prefs.setIntPref(
       TPC_PREF,
       Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER
     );
     await TestUtils.waitForCondition(
-      () =>
-        categoryItem.classList.contains("blocked") &&
-        categoryLabelDisabled.hidden &&
-        !categoryLabelEnabled.hidden,
+      () => categoryItem.classList.contains("blocked"),
       "The category label has updated correctly"
     );
-    ok(
-      categoryItem.classList.contains("blocked") &&
-        categoryLabelDisabled.hidden &&
-        !categoryLabelEnabled.hidden
-    );
+    ok(categoryItem.classList.contains("blocked"));
     await TestUtils.waitForCondition(
       () =>
-        categoryLabelEnabled.textContent ==
+        categoryLabel.textContent ==
         gNavigatorBundle.getString(
           "contentBlocking.cookies.blockingTrackers3.label"
         ),
       "The category label has updated correctly"
     );
     ok(
-      categoryLabelEnabled.textContent ==
+      categoryLabel.textContent ==
         gNavigatorBundle.getString(
           "contentBlocking.cookies.blockingTrackers3.label"
         )
     );
 
     Services.prefs.setIntPref(
       TPC_PREF,
       Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN
     );
     await TestUtils.waitForCondition(
-      () =>
-        !categoryItem.classList.contains("blocked") &&
-        !categoryLabelDisabled.hidden &&
-        categoryLabelEnabled.hidden,
+      () => !categoryItem.classList.contains("blocked"),
       "The category label has updated correctly"
     );
-    ok(
-      !categoryItem.classList.contains("blocked") &&
-        !categoryLabelDisabled.hidden &&
-        categoryLabelEnabled.hidden
-    );
+    ok(!categoryItem.classList.contains("blocked"));
   });
 });
 
 let categoryItems = [
   "protections-popup-category-tracking-protection",
   "protections-popup-category-socialblock",
   "protections-popup-category-cookies",
   "protections-popup-category-cryptominers",
--- a/browser/components/controlcenter/content/protectionsPanel.inc.xul
+++ b/browser/components/controlcenter/content/protectionsPanel.inc.xul
@@ -83,21 +83,18 @@
               class="protections-popup-category-label">&contentBlocking.socialblock.label;</label>
             </toolbarbutton>
             <!-- wrap=true is needed for descriptionheightworkaround, see bug 1564077 -->
             <toolbarbutton id="protections-popup-category-cookies"
                            oncommand="gProtectionsHandler.showCookiesSubview(event); gProtectionsHandler.recordClick('cookies');"
                            class="protections-popup-category" align="center"
                            wrap="true">
               <image class="protections-popup-category-icon thirdpartycookies-icon"/>
-              <label flex="1" id="protections-popup-cookies-category-label-disabled"
-                     class="protections-popup-category-label">&contentBlocking.cookies2.label;</label>
-              <label flex="1" id="protections-popup-cookies-category-label-enabled"
-                     class="protections-popup-category-label"
-                     hidden="true"></label>
+              <label flex="1" id="protections-popup-cookies-category-label"
+                     class="protections-popup-category-label"/>
             </toolbarbutton>
             <!-- wrap=true is needed for descriptionheightworkaround, see bug 1564077 -->
             <toolbarbutton id="protections-popup-category-cryptominers"
                            oncommand="gProtectionsHandler.showCryptominersSubview(event); gProtectionsHandler.recordClick('cryptominers');"
                            class="protections-popup-category" align="center"
                            wrap="true">
               <image class="protections-popup-category-icon cryptominers-icon"/>
               <label flex="1" class="protections-popup-category-label">&contentBlocking.cryptominers.label;</label>
--- a/browser/components/newtab/lib/CFRMessageProvider.jsm
+++ b/browser/components/newtab/lib/CFRMessageProvider.jsm
@@ -124,17 +124,19 @@ const PINNED_TABS_TARGET_LOCALES = [
 const CFR_MESSAGES = [
   {
     id: "FACEBOOK_CONTAINER_3",
     template: "cfr_doorhanger",
     content: {
       layout: "addon_recommendation",
       category: "cfrAddons",
       bucket_id: "CFR_M1",
-      notification_text: { string_id: "cfr-doorhanger-extension-notification" },
+      notification_text: {
+        string_id: "cfr-doorhanger-extension-notification2",
+      },
       heading_text: { string_id: "cfr-doorhanger-extension-heading" },
       info_icon: {
         label: { string_id: "cfr-doorhanger-extension-sumo-link" },
         sumo_path: FACEBOOK_CONTAINER_PARAMS.sumo_path,
       },
       addon: {
         id: "954390",
         title: "Facebook Container",
@@ -193,17 +195,19 @@ const CFR_MESSAGES = [
   },
   {
     id: "GOOGLE_TRANSLATE_3",
     template: "cfr_doorhanger",
     content: {
       layout: "addon_recommendation",
       category: "cfrAddons",
       bucket_id: "CFR_M1",
-      notification_text: { string_id: "cfr-doorhanger-extension-notification" },
+      notification_text: {
+        string_id: "cfr-doorhanger-extension-notification2",
+      },
       heading_text: { string_id: "cfr-doorhanger-extension-heading" },
       info_icon: {
         label: { string_id: "cfr-doorhanger-extension-sumo-link" },
         sumo_path: GOOGLE_TRANSLATE_PARAMS.sumo_path,
       },
       addon: {
         id: "445852",
         title: "To Google Translate",
@@ -263,17 +267,19 @@ const CFR_MESSAGES = [
   },
   {
     id: "YOUTUBE_ENHANCE_3",
     template: "cfr_doorhanger",
     content: {
       layout: "addon_recommendation",
       category: "cfrAddons",
       bucket_id: "CFR_M1",
-      notification_text: { string_id: "cfr-doorhanger-extension-notification" },
+      notification_text: {
+        string_id: "cfr-doorhanger-extension-notification2",
+      },
       heading_text: { string_id: "cfr-doorhanger-extension-heading" },
       info_icon: {
         label: { string_id: "cfr-doorhanger-extension-sumo-link" },
         sumo_path: YOUTUBE_ENHANCE_PARAMS.sumo_path,
       },
       addon: {
         id: "700308",
         title: "Enhancer for YouTube\u2122",
@@ -334,17 +340,19 @@ const CFR_MESSAGES = [
   {
     id: "WIKIPEDIA_CONTEXT_MENU_SEARCH_3",
     template: "cfr_doorhanger",
     exclude: true,
     content: {
       layout: "addon_recommendation",
       category: "cfrAddons",
       bucket_id: "CFR_M1",
-      notification_text: { string_id: "cfr-doorhanger-extension-notification" },
+      notification_text: {
+        string_id: "cfr-doorhanger-extension-notification2",
+      },
       heading_text: { string_id: "cfr-doorhanger-extension-heading" },
       info_icon: {
         label: { string_id: "cfr-doorhanger-extension-sumo-link" },
         sumo_path: WIKIPEDIA_CONTEXT_MENU_SEARCH_PARAMS.sumo_path,
       },
       addon: {
         id: "659026",
         title: "Wikipedia Context Menu Search",
@@ -408,17 +416,19 @@ const CFR_MESSAGES = [
   {
     id: "REDDIT_ENHANCEMENT_3",
     template: "cfr_doorhanger",
     exclude: true,
     content: {
       layout: "addon_recommendation",
       category: "cfrAddons",
       bucket_id: "CFR_M1",
-      notification_text: { string_id: "cfr-doorhanger-extension-notification" },
+      notification_text: {
+        string_id: "cfr-doorhanger-extension-notification2",
+      },
       heading_text: { string_id: "cfr-doorhanger-extension-heading" },
       info_icon: {
         label: { string_id: "cfr-doorhanger-extension-sumo-link" },
         sumo_path: REDDIT_ENHANCEMENT_PARAMS.sumo_path,
       },
       addon: {
         id: "387429",
         title: "Reddit Enhancement Suite",
@@ -478,17 +488,17 @@ const CFR_MESSAGES = [
   },
   {
     id: "PIN_TAB",
     template: "cfr_doorhanger",
     content: {
       layout: "message_and_animation",
       category: "cfrFeatures",
       bucket_id: "CFR_PIN_TAB",
-      notification_text: { string_id: "cfr-doorhanger-extension-notification" },
+      notification_text: { string_id: "cfr-doorhanger-feature-notification" },
       heading_text: { string_id: "cfr-doorhanger-pintab-heading" },
       info_icon: {
         label: { string_id: "cfr-doorhanger-extension-sumo-link" },
         sumo_path: REDDIT_ENHANCEMENT_PARAMS.sumo_path,
       },
       text: { string_id: "cfr-doorhanger-pintab-description" },
       descriptionDetails: {
         steps: [
@@ -592,17 +602,17 @@ const CFR_MESSAGES = [
       },
       info_icon: {
         label: {
           string_id: "cfr-doorhanger-extension-sumo-link",
         },
         sumo_path: "extensionrecommendations",
       },
       notification_text: {
-        string_id: "cfr-doorhanger-extension-notification",
+        string_id: "cfr-doorhanger-feature-notification",
       },
       category: "cfrFeatures",
     },
     trigger: {
       id: "newSavedLogin",
     },
   },
   {
--- a/browser/components/newtab/lib/CFRPageActions.jsm
+++ b/browser/components/newtab/lib/CFRPageActions.jsm
@@ -177,23 +177,28 @@ class PageAction {
 
   reloadL10n() {
     this._l10n = this._createDOML10n();
   }
 
   async showAddressBarNotifier(recommendation, shouldExpand = false) {
     this.container.hidden = false;
 
-    this.label.value = await this.getStrings(
+    let notificationText = await this.getStrings(
       recommendation.content.notification_text
     );
-
+    this.label.value = notificationText;
     this.button.setAttribute(
       "tooltiptext",
-      await this.getStrings(recommendation.content.notification_text)
+      notificationText.attributes.tooltiptext
+    );
+    // For a11y, we want the more descriptive text.
+    this.container.setAttribute(
+      "aria-label",
+      notificationText.attributes.tooltiptext
     );
     this.button.setAttribute(
       "data-cfr-icon",
       CATEGORY_ICONS[recommendation.content.category]
     );
 
     // Wait for layout to flush to avoid a synchronous reflow then calculate the
     // label width. We can safely get the width even though the recommendation is
@@ -211,16 +216,21 @@ class PageAction {
     if (shouldExpand) {
       this._clearScheduledStateChanges();
 
       // After one second, expand
       this._expand(DELAY_BEFORE_EXPAND_MS);
 
       this.addImpression(recommendation);
     }
+
+    this.window.A11yUtils.announce({
+      raw: notificationText.attributes["a11y-announcement"],
+      source: this.container,
+    });
   }
 
   hideAddressBarNotifier() {
     this.container.hidden = true;
     this._clearScheduledStateChanges();
     this.urlbar.removeAttribute("cfr-recommendation-state");
     this.container.removeEventListener("click", this._showPopupOnClick);
     this.urlbar.removeEventListener("focus", this._collapse);
--- a/browser/components/newtab/lib/PanelTestProvider.jsm
+++ b/browser/components/newtab/lib/PanelTestProvider.jsm
@@ -138,17 +138,17 @@ const MESSAGES = () => [
     trigger: { id: "whatsNewPanelOpened" },
   },
   {
     id: "BOOKMARK_CFR",
     template: "cfr_doorhanger",
     content: {
       layout: "icon_and_message",
       category: "cfrFeatures",
-      notification_text: { string_id: "cfr-doorhanger-extension-notification" },
+      notification_text: { string_id: "cfr-doorhanger-feature-notification" },
       heading_text: { string_id: "cfr-doorhanger-sync-bookmarks-header" },
       info_icon: {
         label: { string_id: "cfr-doorhanger-extension-sumo-link" },
         sumo_path: "https://example.com",
       },
       text: { string_id: "cfr-doorhanger-sync-bookmarks-body" },
       icon: "chrome://branding/content/icon64.png",
       buttons: {
--- a/browser/components/newtab/test/browser/browser_asrouter_cfr.js
+++ b/browser/components/newtab/test/browser/browser_asrouter_cfr.js
@@ -10,75 +10,82 @@ const { ASRouter } = ChromeUtils.import(
 
 const createDummyRecommendation = ({
   action,
   category,
   heading_text,
   layout,
   skip_address_bar_notifier,
   template,
-}) => ({
-  template,
-  content: {
-    layout: layout || "addon_recommendation",
-    category,
-    anchor_id: "page-action-buttons",
-    skip_address_bar_notifier,
-    notification_text: "Mochitest",
-    heading_text: heading_text || "Mochitest",
-    info_icon: {
-      label: { attributes: { tooltiptext: "Why am I seeing this" } },
-      sumo_path: "extensionrecommendations",
-    },
-    icon: "foo",
-    icon_dark_theme: "bar",
-    learn_more: "extensionrecommendations",
-    addon: {
-      id: "addon-id",
-      title: "Addon name",
+}) => {
+  let recommendation = {
+    template,
+    content: {
+      layout: layout || "addon_recommendation",
+      category,
+      anchor_id: "page-action-buttons",
+      skip_address_bar_notifier,
+      heading_text: heading_text || "Mochitest",
+      info_icon: {
+        label: { attributes: { tooltiptext: "Why am I seeing this" } },
+        sumo_path: "extensionrecommendations",
+      },
       icon: "foo",
-      author: "Author name",
-      amo_url: "https://example.com",
-    },
-    descriptionDetails: { steps: [] },
-    text: "Mochitest",
-    buttons: {
-      primary: {
-        label: {
-          value: "OK",
-          attributes: { accesskey: "O" },
-        },
-        action: {
-          type: action.type,
-          data: {},
-        },
+      icon_dark_theme: "bar",
+      learn_more: "extensionrecommendations",
+      addon: {
+        id: "addon-id",
+        title: "Addon name",
+        icon: "foo",
+        author: "Author name",
+        amo_url: "https://example.com",
       },
-      secondary: [
-        {
+      descriptionDetails: { steps: [] },
+      text: "Mochitest",
+      buttons: {
+        primary: {
           label: {
-            value: "Cancel",
-            attributes: { accesskey: "C" },
+            value: "OK",
+            attributes: { accesskey: "O" },
+          },
+          action: {
+            type: action.type,
+            data: {},
           },
         },
-        {
-          label: {
-            value: "Cancel 1",
-            attributes: { accesskey: "A" },
+        secondary: [
+          {
+            label: {
+              value: "Cancel",
+              attributes: { accesskey: "C" },
+            },
+          },
+          {
+            label: {
+              value: "Cancel 1",
+              attributes: { accesskey: "A" },
+            },
           },
-        },
-        {
-          label: {
-            value: "Cancel 2",
-            attributes: { accesskey: "B" },
+          {
+            label: {
+              value: "Cancel 2",
+              attributes: { accesskey: "B" },
+            },
           },
-        },
-      ],
+        ],
+      },
     },
-  },
-});
+  };
+  recommendation.content.notification_text = new String("Mochitest"); // eslint-disable-line
+  recommendation.content.notification_text.attributes = {
+    tooltiptext: "Mochitest tooltip",
+    "a11y-announcement": "Mochitest announcement",
+  };
+  return recommendation;
+};
 
 function checkCFRFeaturesElements(notification) {
   Assert.ok(notification.hidden === false, "Panel should be visible");
   Assert.equal(
     notification.getAttribute("data-notification-category"),
     "message_and_animation",
     "Panel have correct data attribute"
   );
--- a/browser/components/newtab/test/unit/asrouter/ASRouter.test.js
+++ b/browser/components/newtab/test/unit/asrouter/ASRouter.test.js
@@ -2489,16 +2489,17 @@ describe("ASRouter", () => {
           sendAsyncMessage: sandbox.stub(),
           documentURI: { scheme: "https", host: "mozilla.com" },
         };
         target.ownerGlobal = {
           gBrowser: { selectedBrowser: target },
           document: { getElementById },
           promiseDocumentFlushed: sandbox.stub().resolves([{ width: 0 }]),
           setTimeout: sandbox.stub(),
+          A11yUtils: { announce: sandbox.stub() },
         };
         const firstMessage = { ...FAKE_RECOMMENDATION, id: "first_message" };
         const secondMessage = { ...FAKE_RECOMMENDATION, id: "second_message" };
         await Router.setState({ messages: [firstMessage, secondMessage] });
         global.DOMLocalization = class DOMLocalization {};
         sandbox.spy(CFRPageActions, "addRecommendation");
         sandbox.stub(Router, "addImpression").resolves();
 
--- a/browser/components/newtab/test/unit/asrouter/CFRPageActions.test.js
+++ b/browser/components/newtab/test/unit/asrouter/CFRPageActions.test.js
@@ -7,16 +7,17 @@ describe("CFRPageActions", () => {
   let clock;
   let fakeRecommendation;
   let fakeHost;
   let fakeBrowser;
   let dispatchStub;
   let globals;
   let containerElem;
   let elements;
+  let announceStub;
 
   const elementIDs = [
     "urlbar",
     "urlbar-input",
     "contextual-feature-recommendation",
     "cfr-button",
     "cfr-label",
     "contextual-feature-recommendation-notification",
@@ -36,16 +37,18 @@ describe("CFRPageActions", () => {
     "cfr-notification-footer-animation-label",
   ];
   const elementClassNames = ["popup-notification-body-container"];
 
   beforeEach(() => {
     sandbox = sinon.createSandbox();
     clock = sandbox.useFakeTimers();
 
+    announceStub = sandbox.stub();
+    const A11yUtils = { announce: announceStub };
     fakeRecommendation = { ...FAKE_RECOMMENDATION };
     fakeHost = "mozilla.org";
     fakeBrowser = {
       documentURI: {
         scheme: "https",
         host: fakeHost,
       },
       ownerGlobal: window,
@@ -59,16 +62,17 @@ describe("CFRPageActions", () => {
         .stub()
         .callsFake(fn => Promise.resolve(fn())),
       PopupNotifications: {
         show: sandbox.stub(),
         remove: sandbox.stub(),
       },
       PrivateBrowsingUtils: { isWindowPrivate: sandbox.stub().returns(false) },
       gBrowser: { selectedBrowser: fakeBrowser },
+      A11yUtils,
     });
     document.createXULElement = document.createElement;
 
     elements = {};
     const [body] = document.getElementsByTagName("body");
     containerElem = document.createElement("div");
     body.appendChild(containerElem);
     for (const id of elementIDs) {
@@ -89,32 +93,29 @@ describe("CFRPageActions", () => {
     CFRPageActions.clearRecommendations();
     containerElem.remove();
     sandbox.restore();
     globals.restore();
   });
 
   describe("PageAction", () => {
     let pageAction;
-    let getStringsStub;
 
     beforeEach(() => {
       pageAction = new PageAction(window, dispatchStub);
-      getStringsStub = sandbox.stub(pageAction, "getStrings").resolves("");
     });
 
     describe("#showAddressBarNotifier", () => {
       it("should un-hideAddressBarNotifier the element and set the right label value", async () => {
-        const FAKE_NOTIFICATION_TEXT = "FAKE_NOTIFICATION_TEXT";
-        getStringsStub
-          .withArgs(fakeRecommendation.content.notification_text)
-          .resolves(FAKE_NOTIFICATION_TEXT);
         await pageAction.showAddressBarNotifier(fakeRecommendation);
         assert.isFalse(pageAction.container.hidden);
-        assert.equal(pageAction.label.value, FAKE_NOTIFICATION_TEXT);
+        assert.equal(
+          pageAction.label.value,
+          fakeRecommendation.content.notification_text
+        );
       });
       it("should wait for the document layout to flush", async () => {
         sandbox.spy(pageAction.label, "getClientRects");
         await pageAction.showAddressBarNotifier(fakeRecommendation);
         assert.calledOnce(global.promiseDocumentFlushed);
         assert.callOrder(
           global.promiseDocumentFlushed,
           pageAction.label.getClientRects
@@ -351,17 +352,16 @@ describe("CFRPageActions", () => {
             { name: "first_attr", value: 42 },
             { name: "second_attr", value: "some string" },
             { name: "third_attr", value: [1, 2, 3] },
           ],
         },
       ];
 
       beforeEach(() => {
-        getStringsStub.restore();
         formatMessagesStub = sandbox
           .stub()
           .withArgs({ id: "hello_world" })
           .resolves(localeStrings);
         global.DOMLocalization.prototype.formatMessages = formatMessagesStub;
       });
 
       it("should return the argument if a string_id is not defined", async () => {
@@ -428,24 +428,26 @@ describe("CFRPageActions", () => {
         assert.calledOnce(stub);
         stub.restore();
       });
     });
 
     describe("#_showPopupOnClick", () => {
       let translateElementsStub;
       let setAttributesStub;
+      let getStringsStub;
       beforeEach(async () => {
         CFRPageActions.PageActionMap.set(fakeBrowser.ownerGlobal, pageAction);
         await CFRPageActions.addRecommendation(
           fakeBrowser,
           fakeHost,
           fakeRecommendation,
           dispatchStub
         );
+        getStringsStub = sandbox.stub(pageAction, "getStrings").resolves("");
         getStringsStub
           .callsFake(async a => a) // eslint-disable-line max-nested-callbacks
           .withArgs({ string_id: "primary_button_id" })
           .resolves({ value: "Primary Button", attributes: { accesskey: "p" } })
           .withArgs({ string_id: "secondary_button_id" })
           .resolves({
             value: "Secondary Button",
             attributes: { accesskey: "s" },
--- a/browser/components/newtab/test/unit/asrouter/constants.js
+++ b/browser/components/newtab/test/unit/asrouter/constants.js
@@ -76,23 +76,26 @@ export const FAKE_REMOTE_PROVIDER = {
 
 export const FAKE_REMOTE_SETTINGS_PROVIDER = {
   id: "remotey-settingsy",
   type: "remote-settings",
   bucket: "bucketname",
   enabled: true,
 };
 
+const notificationText = new String("Fake notification text"); // eslint-disable-line
+notificationText.attributes = { tooltiptext: "Fake tooltip text" };
+
 export const FAKE_RECOMMENDATION = {
   id: "fake_id",
   template: "cfr_doorhanger",
   content: {
     category: "cfrDummy",
     bucket_id: "fake_bucket_id",
-    notification_text: "Fake Notification Text",
+    notification_text: notificationText,
     info_icon: {
       label: "Fake Info Icon Label",
       sumo_path: "a_help_path_fragment",
     },
     heading_text: "Fake Heading Text",
     addon: {
       title: "Fake Addon Title",
       author: "Fake Addon Author",
--- a/browser/components/originattributes/test/browser/browser.ini
+++ b/browser/components/originattributes/test/browser/browser.ini
@@ -58,51 +58,39 @@ support-files =
   window2.html
   window3.html
   window_redirect.html
   worker_blobify.js
   worker_deblobify.js
   !/toolkit/content/tests/browser/common/mockTransfer.js
 
 [browser_broadcastChannel.js]
-fail-if = fission
 [browser_cache.js]
-fail-if = fission
 skip-if = verify
 [browser_cookieIsolation.js]
-fail-if = fission
 [browser_favicon_firstParty.js]
-fail-if = fission
 [browser_favicon_userContextId.js]
-fail-if = fission
 [browser_firstPartyIsolation.js]
 skip-if = fission || debug #Bug 1345346
 [browser_firstPartyIsolation_about_newtab.js]
 [browser_firstPartyIsolation_aboutPages.js]
 fail-if = fission
 [browser_firstPartyIsolation_blobURI.js]
 skip-if = fission # Crashes: @ mozilla::dom::BrowserBridgeParent::RecvShow(mozilla::gfx::IntSizeTyped<mozilla::ScreenPixel> const&, bool const&, nsSizeMode const&)
 [browser_firstPartyIsolation_js_uri.js]
 skip-if = fission
 [browser_firstPartyIsolation_saveAs.js]
 skip-if = fission
 [browser_localStorageIsolation.js]
-fail-if = fission
 [browser_blobURLIsolation.js]
-fail-if = fission
 skip-if = (verify && debug && (os == 'win'))
 [browser_imageCacheIsolation.js]
 [browser_sharedworker.js]
-fail-if = fission
 [browser_httpauth.js]
-skip-if = fission
 [browser_clientAuth.js]
 skip-if = verify
 [browser_cacheAPI.js]
-fail-if = fission
 [browser_permissions.js]
-fail-if = fission
 [browser_postMessage.js]
 [browser_sanitize.js]
-fail-if = fission
 skip-if = (os == 'win') || (os == "mac" && os_version == "10.14") || (os == "linux" && bits == 64) #Bug 1544810
 [browser_windowOpenerRestriction.js]
 fail-if = fission
--- a/browser/components/originattributes/test/browser/browser_blobURLIsolation.js
+++ b/browser/components/originattributes/test/browser/browser_blobURLIsolation.js
@@ -4,25 +4,27 @@
 
 const TEST_PAGE =
   "http://mochi.test:8888/browser/browser/components/" +
   "originattributes/test/browser/file_firstPartyBasic.html";
 const SCRIPT_WORKER_BLOBIFY = "worker_blobify.js";
 const SCRIPT_WORKER_DEBLOBIFY = "worker_deblobify.js";
 
 function page_blobify(browser, input) {
-  return ContentTask.spawn(browser, input, function(contentInput) {
+  return SpecialPowers.spawn(browser, [input], function(contentInput) {
     return {
       blobURL: content.URL.createObjectURL(new content.Blob([contentInput])),
     };
   });
 }
 
 function page_deblobify(browser, blobURL) {
-  return ContentTask.spawn(browser, blobURL, async function(contentBlobURL) {
+  return SpecialPowers.spawn(browser, [blobURL], async function(
+    contentBlobURL
+  ) {
     if ("error" in contentBlobURL) {
       return contentBlobURL;
     }
     contentBlobURL = contentBlobURL.blobURL;
 
     function blobURLtoBlob(aBlobURL) {
       return new content.Promise(function(resolve) {
         let xhr = new content.XMLHttpRequest();
@@ -53,17 +55,19 @@ function page_deblobify(browser, blobURL
       return "xhr error";
     }
 
     return blobToString(blob);
   });
 }
 
 function workerIO(browser, scriptFile, message) {
-  return ContentTask.spawn(browser, { scriptFile, message }, function(args) {
+  return SpecialPowers.spawn(browser, [{ scriptFile, message }], function(
+    args
+  ) {
     let worker = new content.Worker(args.scriptFile);
     let promise = new content.Promise(function(resolve) {
       let listenFunction = function(event) {
         worker.removeEventListener("message", listenFunction);
         worker.terminate();
         resolve(event.data);
       };
       worker.addEventListener("message", listenFunction);
--- a/browser/components/originattributes/test/browser/browser_broadcastChannel.js
+++ b/browser/components/originattributes/test/browser/browser_broadcastChannel.js
@@ -3,17 +3,17 @@
  */
 
 const TEST_DOMAIN = "http://example.net/";
 const TEST_PATH =
   TEST_DOMAIN + "browser/browser/components/originattributes/test/browser/";
 const TEST_PAGE = TEST_PATH + "file_broadcastChannel.html";
 
 async function doTest(aBrowser) {
-  let response = await ContentTask.spawn(aBrowser, null, async function() {
+  let response = await SpecialPowers.spawn(aBrowser, [], async function() {
     let displayItem = content.document.getElementById("display");
 
     // If there is nothing in the 'display', we will try to send a message to
     // the broadcast channel and wait until this message has been delivered.
     // The way that how we make sure the message is delivered is based on an
     // iframe which will reply everything it receives from the broadcast channel
     // to the current window through the postMessage. So, we can know that the
     // boradcast message is sent successfully when the window receives a message
@@ -28,17 +28,17 @@ async function doTest(aBrowser) {
         };
 
         let bc = new content.BroadcastChannel("testBroadcastChannel");
 
         content.addEventListener("message", listenFunc);
         bc.postMessage(data);
       });
 
-      is(receivedData, data, "The value should be the same.");
+      Assert.equal(receivedData, data, "The value should be the same.");
 
       return receivedData;
     }
 
     return displayItem.innerHTML;
   });
 
   return response;
--- a/browser/components/originattributes/test/browser/browser_cache.js
+++ b/browser/components/originattributes/test/browser/browser_cache.js
@@ -165,54 +165,54 @@ async function doInit(aMode) {
 // and assign a random suffix to their URL to isolate them across different
 // test runs.
 async function doTest(aBrowser) {
   let argObj = {
     randomSuffix,
     urlPrefix: TEST_DOMAIN + TEST_PATH,
   };
 
-  await ContentTask.spawn(aBrowser, argObj, async function(arg) {
+  await SpecialPowers.spawn(aBrowser, [argObj], async function(arg) {
     let videoURL = arg.urlPrefix + "file_thirdPartyChild.video.ogv";
     let audioURL = arg.urlPrefix + "file_thirdPartyChild.audio.ogg";
     let trackURL = arg.urlPrefix + "file_thirdPartyChild.track.vtt";
     let URLSuffix = "?r=" + arg.randomSuffix;
 
     // Create the audio and video elements.
     let audio = content.document.createElement("audio");
     let video = content.document.createElement("video");
     let audioSource = content.document.createElement("source");
     let audioTrack = content.document.createElement("track");
 
     // Append the audio and track element into the body, and wait until they're finished.
-    await new Promise(resolve => {
+    await new content.Promise(resolve => {
       let audioLoaded = false;
       let trackLoaded = false;
 
       let audioListener = () => {
-        info(`Audio suspended: ${audioURL + URLSuffix}`);
+        Assert.ok(true, `Audio suspended: ${audioURL + URLSuffix}`);
         audio.removeEventListener("suspend", audioListener);
 
         audioLoaded = true;
         if (audioLoaded && trackLoaded) {
           resolve();
         }
       };
 
       let trackListener = () => {
-        info(`Audio track loaded: ${audioURL + URLSuffix}`);
+        Assert.ok(true, `Audio track loaded: ${audioURL + URLSuffix}`);
         audioTrack.removeEventListener("load", trackListener);
 
         trackLoaded = true;
         if (audioLoaded && trackLoaded) {
           resolve();
         }
       };
 
-      info(`Loading audio: ${audioURL + URLSuffix}`);
+      Assert.ok(true, `Loading audio: ${audioURL + URLSuffix}`);
 
       // Add the event listeners before everything in case we lose events.
       audioTrack.addEventListener("load", trackListener);
       audio.addEventListener("suspend", audioListener);
 
       // Assign attributes for the audio element.
       audioSource.setAttribute("src", audioURL + URLSuffix);
       audioSource.setAttribute("type", "audio/ogg");
@@ -223,24 +223,24 @@ async function doTest(aBrowser) {
       audio.appendChild(audioSource);
       audio.appendChild(audioTrack);
       audio.autoplay = true;
 
       content.document.body.appendChild(audio);
     });
 
     // Append the video element into the body, and wait until it's finished.
-    await new Promise(resolve => {
+    await new content.Promise(resolve => {
       let listener = () => {
-        info(`Video suspended: ${videoURL + URLSuffix}`);
+        Assert.ok(true, `Video suspended: ${videoURL + URLSuffix}`);
         video.removeEventListener("suspend", listener);
         resolve();
       };
 
-      info(`Loading video: ${videoURL + URLSuffix}`);
+      Assert.ok(true, `Loading video: ${videoURL + URLSuffix}`);
 
       // Add the event listener before everything in case we lose the event.
       video.addEventListener("suspend", listener);
 
       // Assign attributes for the video element.
       video.setAttribute("src", videoURL + URLSuffix);
       video.setAttribute("type", "video/ogg");
 
--- a/browser/components/originattributes/test/browser/browser_cacheAPI.js
+++ b/browser/components/originattributes/test/browser/browser_cacheAPI.js
@@ -1,12 +1,12 @@
 const requestURL = "https://test1.example.com";
 
 function getResult(aBrowser) {
-  return ContentTask.spawn(aBrowser, requestURL, async function(url) {
+  return SpecialPowers.spawn(aBrowser, [requestURL], async function(url) {
     let cache = await content.caches.open("TEST_CACHE");
     let response = await cache.match(url);
     if (response) {
       return response.statusText;
     }
     let result = Math.random().toString();
     response = new content.Response("", { statusText: result });
     await cache.put(url, response);
--- a/browser/components/originattributes/test/browser/browser_cookieIsolation.js
+++ b/browser/components/originattributes/test/browser/browser_cookieIsolation.js
@@ -7,20 +7,20 @@ const TEST_PAGE =
   "originattributes/test/browser/file_firstPartyBasic.html";
 
 // Use a random key so we don't access it in later tests.
 const key = "key" + Math.random().toString();
 const re = new RegExp(key + "=([0-9.]+)");
 
 // Define the testing function
 function doTest(aBrowser) {
-  return ContentTask.spawn(aBrowser, [key, re], function([
+  return SpecialPowers.spawn(aBrowser, [key, re], function(
     contentKey,
-    contentRe,
-  ]) {
+    contentRe
+  ) {
     let result = contentRe.exec(content.document.cookie);
     if (result) {
       return result[1];
     }
     // No value is found, so we create one.
     let value = Math.random().toString();
     content.document.cookie = contentKey + "=" + value;
     return value;
--- a/browser/components/originattributes/test/browser/browser_favicon_firstParty.js
+++ b/browser/components/originattributes/test/browser/browser_favicon_firstParty.js
@@ -201,17 +201,19 @@ async function openTab(aURL) {
 }
 
 async function assignCookiesUnderFirstParty(aURL, aFirstParty, aCookieValue) {
   // Open a tab under the given aFirstParty, and this tab will have an
   // iframe which loads the aURL.
   let tabInfo = await openTabInFirstParty(aURL, aFirstParty);
 
   // Add cookies into the iframe.
-  await ContentTask.spawn(tabInfo.browser, aCookieValue, async function(value) {
+  await SpecialPowers.spawn(tabInfo.browser, [aCookieValue], async function(
+    value
+  ) {
     content.document.cookie = value;
   });
 
   BrowserTestUtils.removeTab(tabInfo.tab);
 }
 
 async function generateCookies(aThirdParty) {
   // we generate two different cookies for two first party domains.
--- a/browser/components/originattributes/test/browser/browser_favicon_userContextId.js
+++ b/browser/components/originattributes/test/browser/browser_favicon_userContextId.js
@@ -171,21 +171,25 @@ async function generateCookies(aHost) {
   let cookies = [];
   cookies.push(Math.random().toString());
   cookies.push(Math.random().toString());
 
   // Then, we add cookies into the site for 'personal' and 'work'.
   let tabInfoA = await openTabInUserContext(aHost, USER_CONTEXT_ID_PERSONAL);
   let tabInfoB = await openTabInUserContext(aHost, USER_CONTEXT_ID_WORK);
 
-  await ContentTask.spawn(tabInfoA.browser, cookies[0], async function(value) {
+  await SpecialPowers.spawn(tabInfoA.browser, [cookies[0]], async function(
+    value
+  ) {
     content.document.cookie = value;
   });
 
-  await ContentTask.spawn(tabInfoB.browser, cookies[1], async function(value) {
+  await SpecialPowers.spawn(tabInfoB.browser, [cookies[1]], async function(
+    value
+  ) {
     content.document.cookie = value;
   });
 
   BrowserTestUtils.removeTab(tabInfoA.tab);
   BrowserTestUtils.removeTab(tabInfoB.tab);
 
   return cookies;
 }
--- a/browser/components/originattributes/test/browser/browser_localStorageIsolation.js
+++ b/browser/components/originattributes/test/browser/browser_localStorageIsolation.js
@@ -6,17 +6,17 @@ const TEST_PAGE =
   "http://mochi.test:8888/browser/browser/components/" +
   "originattributes/test/browser/file_firstPartyBasic.html";
 
 // Use a random key so we don't access it in later tests.
 const key = Math.random().toString();
 
 // Define the testing function
 function doTest(aBrowser) {
-  return ContentTask.spawn(aBrowser, key, function(contentKey) {
+  return SpecialPowers.spawn(aBrowser, [key], function(contentKey) {
     let value = content.localStorage.getItem(contentKey);
     if (value === null) {
       // No value is found, so we create one.
       value = Math.random().toString();
       content.localStorage.setItem(contentKey, value);
     }
     return value;
   });
--- a/browser/components/originattributes/test/browser/browser_permissions.js
+++ b/browser/components/originattributes/test/browser/browser_permissions.js
@@ -9,22 +9,37 @@ const { PermissionTestUtils } = ChromeUt
 );
 
 const TEST_PAGE = "http://example.net";
 const uri = Services.io.newURI(TEST_PAGE);
 
 function disableCookies() {
   Services.cookies.removeAll();
   PermissionTestUtils.add(uri, "cookie", Services.perms.DENY_ACTION);
+
+  // A workaround for making this test working. In Bug 1330467, we separate the
+  // permissions between different firstPartyDomains, but not for the
+  // userContextID and the privateBrowsingId. So we need to manually add the
+  // permission for FPDs in order to make this test working. This test should be
+  // eventually removed once the permissions are isolated by OAs.
+  let principal = Services.scriptSecurityManager.createContentPrincipal(uri, {
+    firstPartyDomain: "example.com",
+  });
+  PermissionTestUtils.add(principal, "cookie", Services.perms.DENY_ACTION);
+
+  principal = Services.scriptSecurityManager.createContentPrincipal(uri, {
+    firstPartyDomain: "example.org",
+  });
+  PermissionTestUtils.add(principal, "cookie", Services.perms.DENY_ACTION);
 }
 
-function ensureCookieNotSet(aBrowser) {
-  ContentTask.spawn(aBrowser, null, async function() {
+async function ensureCookieNotSet(aBrowser) {
+  await SpecialPowers.spawn(aBrowser, [], async function() {
     content.document.cookie = "key=value";
-    is(
+    Assert.equal(
       content.document.cookie,
       "",
       "Setting/reading cookies should be disabled" +
         " for this domain for all origin attribute combinations."
     );
   });
 }
 
@@ -33,22 +48,33 @@ IsolationTestTools.runTests(
   ensureCookieNotSet,
   () => true,
   disableCookies
 );
 
 function enableCookies() {
   Services.cookies.removeAll();
   PermissionTestUtils.add(uri, "cookie", Services.perms.ALLOW_ACTION);
+
+  // A workaround for making this test working.
+  let principal = Services.scriptSecurityManager.createContentPrincipal(uri, {
+    firstPartyDomain: "example.com",
+  });
+  PermissionTestUtils.add(principal, "cookie", Services.perms.ALLOW_ACTION);
+
+  principal = Services.scriptSecurityManager.createContentPrincipal(uri, {
+    firstPartyDomain: "example.org",
+  });
+  PermissionTestUtils.add(principal, "cookie", Services.perms.ALLOW_ACTION);
 }
 
-function ensureCookieSet(aBrowser) {
-  ContentTask.spawn(aBrowser, null, function() {
+async function ensureCookieSet(aBrowser) {
+  await SpecialPowers.spawn(aBrowser, [], function() {
     content.document.cookie = "key=value";
-    is(
+    Assert.equal(
       content.document.cookie,
       "key=value",
       "Setting/reading cookies should be" +
         " enabled for this domain for all origin attribute combinations."
     );
   });
 }
 
--- a/browser/components/originattributes/test/browser/browser_sanitize.js
+++ b/browser/components/originattributes/test/browser/browser_sanitize.js
@@ -7,18 +7,18 @@ if (Services.prefs.getBoolPref("fission.
 }
 
 const CC = Components.Constructor;
 
 const TEST_DOMAIN = "http://example.net/";
 
 const { Sanitizer } = ChromeUtils.import("resource:///modules/Sanitizer.jsm");
 
-function setCookies(aBrowser) {
-  ContentTask.spawn(aBrowser, null, function() {
+async function setCookies(aBrowser) {
+  await SpecialPowers.spawn(aBrowser, [], function() {
     content.document.cookie = "key=value";
   });
 }
 
 function cacheDataForContext(loadContextInfo) {
   return new Promise(resolve => {
     let cachedURIs = [];
     let cacheVisitor = {
@@ -33,19 +33,19 @@ function cacheDataForContext(loadContext
     };
     // Visiting the disk cache also visits memory storage so we do not
     // need to use Services.cache2.memoryCacheStorage() here.
     let storage = Services.cache2.diskCacheStorage(loadContextInfo, false);
     storage.asyncVisitStorage(cacheVisitor, true);
   });
 }
 
-function checkCookiesSanitized(aBrowser) {
-  ContentTask.spawn(aBrowser, null, function() {
-    is(
+async function checkCookiesSanitized(aBrowser) {
+  await SpecialPowers.spawn(aBrowser, [], function() {
+    Assert.equal(
       content.document.cookie,
       "",
       "Cookies of all origin attributes should be cleared."
     );
   });
 }
 
 function checkCacheExists(aShouldExist) {
--- a/browser/components/originattributes/test/browser/browser_sharedworker.js
+++ b/browser/components/originattributes/test/browser/browser_sharedworker.js
@@ -3,23 +3,23 @@
  */
 
 const TEST_DOMAIN = "http://example.net/";
 const TEST_PATH =
   TEST_DOMAIN + "browser/browser/components/originattributes/test/browser/";
 const TEST_PAGE = TEST_PATH + "file_sharedworker.html";
 
 async function getResultFromSharedworker(aBrowser) {
-  let response = await ContentTask.spawn(aBrowser, null, async function() {
+  let response = await SpecialPowers.spawn(aBrowser, [], async function() {
     let worker = new content.SharedWorker(
       "file_sharedworker.js",
       "isolationSharedWorkerTest"
     );
 
-    let result = await new Promise(resolve => {
+    let result = await new content.Promise(resolve => {
       worker.port.onmessage = function(e) {
         // eslint-disable-next-line no-unsanitized/property
         content.document.getElementById("display").innerHTML = e.data;
         resolve(e.data);
       };
     });
 
     return result;
--- a/browser/components/originattributes/test/browser/head.js
+++ b/browser/components/originattributes/test/browser/head.js
@@ -11,18 +11,18 @@ const TEST_URL_PATH =
 const TEST_MODE_FIRSTPARTY = 0;
 const TEST_MODE_NO_ISOLATION = 1;
 const TEST_MODE_CONTAINERS = 2;
 
 // The name of each mode.
 const TEST_MODE_NAMES = ["first party isolation", "no isolation", "containers"];
 
 // The frame types.
-const TEST_TYPE_FRAME = 0;
-const TEST_TYPE_IFRAME = 1;
+const TEST_TYPE_FRAME = 1;
+const TEST_TYPE_IFRAME = 2;
 
 // The default frame setting.
 const DEFAULT_FRAME_SETTING = [TEST_TYPE_IFRAME];
 
 let gFirstPartyBasicPage = TEST_URL_PATH + "file_firstPartyBasic.html";
 
 /**
  * Add a tab for the given url with the specific user context id.
@@ -31,35 +31,40 @@ let gFirstPartyBasicPage = TEST_URL_PATH
  *    The url of the page.
  * @param aUserContextId
  *    The user context id for this tab.
  *
  * @return tab     - The tab object of this tab.
  *         browser - The browser object of this tab.
  */
 async function openTabInUserContext(aURL, aUserContextId) {
+  info(`Start to open tab in specific userContextID: ${aUserContextId}.`);
   let originAttributes = {
     userContextId: aUserContextId,
   };
+  info("Create triggeringPrincipal.");
   let triggeringPrincipal = Services.scriptSecurityManager.createContentPrincipal(
     makeURI(aURL),
     originAttributes
   );
   // Open the tab in the correct userContextId.
+  info("Open the tab and wait for it to be loaded.");
   let tab = BrowserTestUtils.addTab(gBrowser, aURL, {
     userContextId: aUserContextId,
     triggeringPrincipal,
   });
 
   // Select tab and make sure its browser is focused.
   gBrowser.selectedTab = tab;
   tab.ownerGlobal.focus();
 
   let browser = gBrowser.getBrowserForTab(tab);
   await BrowserTestUtils.browserLoaded(browser);
+  info("Finished tab opening.");
+
   return { tab, browser };
 }
 
 /**
  * Add a tab for a page with the given first party domain. This page will have
  * an iframe which is loaded with the given url by default or you could specify
  * a frame setting to create nested frames. And this function will also modify
  * the 'content' in the ContentTask to the target frame's window object.
@@ -78,111 +83,118 @@ async function openTabInUserContext(aURL
  * @return tab     - The tab object of this tab.
  *         browser - The browser object of this tab.
  */
 async function openTabInFirstParty(
   aURL,
   aFirstPartyDomain,
   aFrameSetting = DEFAULT_FRAME_SETTING
 ) {
+  info(`Start to open tab under first party domain "${aFirstPartyDomain}".`);
   // If the first party domain ends with '/', we remove it.
   if (aFirstPartyDomain.endsWith("/")) {
     aFirstPartyDomain = aFirstPartyDomain.slice(0, -1);
   }
 
   let basicPageURL = aFirstPartyDomain + gFirstPartyBasicPage;
 
   // Open the tab for the basic first party page.
+  info("Open the tab and then wait for it to be loaded.");
   let tab = BrowserTestUtils.addTab(gBrowser, basicPageURL);
 
   // Select tab and make sure its browser is focused.
   gBrowser.selectedTab = tab;
   tab.ownerGlobal.focus();
 
   let browser = gBrowser.getBrowserForTab(tab);
   await BrowserTestUtils.browserLoaded(browser);
 
-  let pageArgs = {
-    url: aURL,
-    frames: aFrameSetting,
-    typeFrame: TEST_TYPE_FRAME,
-    typeIFrame: TEST_TYPE_IFRAME,
-    basicFrameSrc: basicPageURL,
-  };
+  // Clone the frame setting here since we will modify it later.
+  let frameSetting = aFrameSetting.slice(0);
+  let frameType;
+  let targetBrowsingContext;
 
   // Create the frame structure.
-  await ContentTask.spawn(browser, pageArgs, async function(arg) {
-    let typeFrame = arg.typeFrame;
-    let typeIFrame = arg.typeIFrame;
+  info("Create the frame structure.");
+  while ((frameType = frameSetting.shift())) {
+    if (!targetBrowsingContext) {
+      targetBrowsingContext = browser;
+    }
+
+    let frameURL = !frameSetting.length ? aURL : basicPageURL;
 
-    // Redefine the 'content' for allowing us to change its target, and making
-    // ContentTask.spawn can directly work on the frame element.
-    this.frameWindow = content;
-
-    Object.defineProperty(this, "content", {
-      get: () => this.frameWindow,
-    });
-
-    let frameElement;
-    let numOfLayers = 0;
+    if (frameType == TEST_TYPE_FRAME) {
+      info("Add the frameset.");
+      targetBrowsingContext = await SpecialPowers.spawn(
+        targetBrowsingContext,
+        [frameURL],
+        async function(aFrameURL) {
+          // Add a frameset which carries the frame element.
+          let frameSet = content.document.createElement("frameset");
+          frameSet.cols = "50%,50%";
 
-    for (let type of arg.frames) {
-      let document = content.document;
-      numOfLayers++;
+          let frame = content.document.createElement("frame");
+          let dummyFrame = content.document.createElement("frame");
+
+          frameSet.appendChild(frame);
+          frameSet.appendChild(dummyFrame);
+
+          content.document.body.appendChild(frameSet);
 
-      if (type === typeFrame) {
-        // Add a frameset which carries the frame element.
-        let frameSet = document.createElement("frameset");
-        frameSet.cols = "50%,50%";
+          // Wait for the frame to be loaded.
+          await new Promise(done => {
+            frame.addEventListener(
+              "load",
+              function() {
+                done();
+              },
+              { capture: true, once: true }
+            );
 
-        let frame = document.createElement("frame");
-        let dummyFrame = document.createElement("frame");
-
-        frameSet.appendChild(frame);
-        frameSet.appendChild(dummyFrame);
-
-        document.body.appendChild(frameSet);
+            frame.setAttribute("src", aFrameURL);
+          });
 
-        frameElement = frame;
-      } else if (type === typeIFrame) {
-        // Add an iframe.
-        let iframe = document.createElement("iframe");
-        document.body.appendChild(iframe);
-
-        frameElement = iframe;
-      } else {
-        ok(false, "Invalid frame type.");
-        break;
-      }
+          return frame.browsingContext;
+        }
+      );
+    } else if (frameType == TEST_TYPE_IFRAME) {
+      info("Add the iframe.");
+      targetBrowsingContext = await SpecialPowers.spawn(
+        targetBrowsingContext,
+        [frameURL],
+        async function(aFrameURL) {
+          // Add an iframe.
+          let frame = content.document.createElement("iframe");
+          content.document.body.appendChild(frame);
 
-      // Wait for the frame to be loaded.
-      await new Promise(done => {
-        frameElement.addEventListener(
-          "load",
-          function() {
-            done();
-          },
-          { capture: true, once: true }
-        );
+          // Wait for the frame to be loaded.
+          await new Promise(done => {
+            frame.addEventListener(
+              "load",
+              function() {
+                done();
+              },
+              { capture: true, once: true }
+            );
+
+            frame.setAttribute("src", aFrameURL);
+          });
 
-        // If it is the deepest layer, we load the target URL. Otherwise, we
-        // load a basic page.
-        if (numOfLayers === arg.frames.length) {
-          frameElement.setAttribute("src", arg.url);
-        } else {
-          frameElement.setAttribute("src", arg.basicFrameSrc);
+          return frame.browsingContext;
         }
-      });
+      );
+    } else {
+      ok(false, "Invalid frame type.");
+      break;
+    }
+    info("Successfully added a frame");
+  }
+  info("Finished the frame structure");
 
-      // Redirect the 'content' to the frame's window.
-      this.frameWindow = frameElement.contentWindow;
-    }
-  });
-
-  return { tab, browser };
+  return { tab, browser: targetBrowsingContext };
 }
 
 this.IsolationTestTools = {
   /**
    * Adds isolation tests for first party isolation, no isolation
    * and containers respectively.
    *
    * @param aTask
@@ -219,17 +231,17 @@ this.IsolationTestTools = {
   },
 
   _addTaskForMode(aMode, aPref, aSkip, aTask) {
     if (aSkip) {
       return;
     }
 
     add_task(async function() {
-      info("Starting the test for " + TEST_MODE_NAMES[aMode]);
+      info(`Starting the test for ${TEST_MODE_NAMES[aMode]}.`);
 
       // Before run this task, reset the preferences first.
       await SpecialPowers.flushPrefEnv();
 
       // Make sure preferences are set properly.
       await SpecialPowers.pushPrefEnv({ set: aPref });
 
       await SpecialPowers.pushPrefEnv({ set: [["dom.ipc.processCount", 1]] });
@@ -347,64 +359,106 @@ this.IsolationTestTools = {
         ];
 
     this._add_task(async function(aMode) {
       let tabSettingA = 0;
 
       for (let tabSettingB of [0, 1]) {
         // Give the test a chance to set up before each case is run.
         if (aBeforeFunc) {
-          await aBeforeFunc(aMode);
+          try {
+            await aBeforeFunc(aMode);
+          } catch (e) {
+            ok(false, `Caught error while doing testing setup: ${e}.`);
+          }
         }
-
         // Create Tabs.
+        info(`Create tab A for ${TEST_MODE_NAMES[aMode]} test.`);
         let tabInfoA = await IsolationTestTools._addTab(
           aMode,
           pageURL,
           tabSettings[tabSettingA],
           firstFrameSetting
         );
+        info(`Finished Create tab A for ${TEST_MODE_NAMES[aMode]} test.`);
         let resultsA = [];
         if (aGetResultImmediately) {
-          for (let getResultFunc of aGetResultFuncs) {
-            resultsA.push(await getResultFunc(tabInfoA.browser));
+          try {
+            info(
+              `Immediately get result from tab A for ${
+                TEST_MODE_NAMES[aMode]
+              } test`
+            );
+            for (let getResultFunc of aGetResultFuncs) {
+              resultsA.push(await getResultFunc(tabInfoA.browser));
+            }
+          } catch (e) {
+            ok(false, `Caught error while getting result from Tab A: ${e}.`);
           }
         }
+        info(`Create tab B for ${TEST_MODE_NAMES[aMode]}.`);
         let tabInfoB = await IsolationTestTools._addTab(
           aMode,
           pageURL,
           tabSettings[tabSettingB],
           secondFrameSetting
         );
+        info(`Finished Create tab B for ${TEST_MODE_NAMES[aMode]} test.`);
         let i = 0;
         for (let getResultFunc of aGetResultFuncs) {
           // Fetch results from tabs.
-          let resultA = aGetResultImmediately
-            ? resultsA[i++]
-            : await getResultFunc(tabInfoA.browser);
-          let resultB = await getResultFunc(tabInfoB.browser);
-
+          info(`Fetching result from tab A for ${TEST_MODE_NAMES[aMode]}.`);
+          let resultA;
+          try {
+            resultA = aGetResultImmediately
+              ? resultsA[i++]
+              : await getResultFunc(tabInfoA.browser);
+          } catch (e) {
+            ok(false, `Caught error while getting result from Tab A: ${e}.`);
+          }
+          info(`Fetching result from tab B for ${TEST_MODE_NAMES[aMode]}.`);
+          let resultB;
+          try {
+            resultB = await getResultFunc(tabInfoB.browser);
+          } catch (e) {
+            ok(false, `Caught error while getting result from Tab B: ${e}.`);
+          }
           // Compare results.
           let result = false;
           let shouldIsolate =
             aMode !== TEST_MODE_NO_ISOLATION && tabSettingA !== tabSettingB;
           if (aCompareResultFunc) {
             result = await aCompareResultFunc(shouldIsolate, resultA, resultB);
           } else {
             result = shouldIsolate ? resultA !== resultB : resultA === resultB;
           }
 
           let msg =
-            `Testing ${TEST_MODE_NAMES[aMode]} for ` +
+            `Result of Testing ${TEST_MODE_NAMES[aMode]} for ` +
             `isolation ${shouldIsolate ? "on" : "off"} with TabSettingA ` +
             `${tabSettingA} and tabSettingB ${tabSettingB}` +
             `, resultA = ${resultA}, resultB = ${resultB}`;
 
           ok(result, msg);
         }
 
         // Close Tabs.
         BrowserTestUtils.removeTab(tabInfoA.tab);
         BrowserTestUtils.removeTab(tabInfoB.tab);
+
+        // A workaround for avoiding a timing issue in Fission. This workaround
+        // makes sure that the shutdown process between parent and content
+        // is finished before the next round of testing.
+        if (SpecialPowers.useRemoteSubframes) {
+          await new Promise(resolve => {
+            let observer = (subject, topic, data) => {
+              if (topic === "ipc:content-shutdown") {
+                Services.obs.removeObserver(observer, "ipc:content-shutdown");
+                resolve();
+              }
+            };
+            Services.obs.addObserver(observer, "ipc:content-shutdown");
+          });
+        }
       }
     });
   },
 };
--- a/browser/components/preferences/in-content/search.js
+++ b/browser/components/preferences/in-content/search.js
@@ -422,23 +422,29 @@ var gSearchPane = {
           let selectedEngine = document.getElementById("defaultEngine")
             .selectedItem.engine;
           if (selectedEngine.name != aEngine.name) {
             gSearchPane.buildDefaultEngineDropDowns();
           }
           break;
         }
         case "engine-default-private": {
-          // If the user is going through the drop down using up/down keys, the
-          // dropdown may still be open (eg. on Windows) when engine-default is
-          // fired, so rebuilding the list unconditionally would get in the way.
-          const selectedEngine = document.getElementById("defaultPrivateEngine")
-            .selectedItem.engine;
-          if (selectedEngine.name != aEngine.name) {
-            gSearchPane.buildDefaultEngineDropDowns();
+          if (
+            this._separatePrivateDefaultEnabledPref.value &&
+            this._separatePrivateDefaultPref.value
+          ) {
+            // If the user is going through the drop down using up/down keys, the
+            // dropdown may still be open (eg. on Windows) when engine-default is
+            // fired, so rebuilding the list unconditionally would get in the way.
+            const selectedEngine = document.getElementById(
+              "defaultPrivateEngine"
+            ).selectedItem.engine;
+            if (selectedEngine.name != aEngine.name) {
+              gSearchPane.buildDefaultEngineDropDowns();
+            }
           }
           break;
         }
       }
     }
   },
 
   onInputBlur(aEvent) {
--- a/browser/locales/en-US/browser/newtab/asrouter.ftl
+++ b/browser/locales/en-US/browser/newtab/asrouter.ftl
@@ -30,17 +30,25 @@ cfr-doorhanger-extension-learn-more-link
 
 # This string is used on a new line below the add-on name
 # Variables:
 #   $name (String) - Add-on author name
 cfr-doorhanger-extension-author = by { $name }
 
 # This is a notification displayed in the address bar.
 # When clicked it opens a panel with a message for the user.
-cfr-doorhanger-extension-notification = Recommendation
+cfr-doorhanger-extension-notification2 = Recommendation
+  .tooltiptext = Extension recommendation
+  .a11y-announcement = Extension recommendation available
+
+# This is a notification displayed in the address bar.
+# When clicked it opens a panel with a message for the user.
+cfr-doorhanger-feature-notification = Recommendation
+  .tooltiptext = Feature recommendation
+  .a11y-announcement = Feature recommendation available
 
 ## Add-on statistics
 ## These strings are used to display the total number of
 ## users and rating for an add-on. They are shown next to each other.
 
 # Variables:
 #   $total (Number) - The rating of the add-on from 1 to 5
 cfr-doorhanger-extension-rating =
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -723,18 +723,16 @@ you can use these alternative items. Oth
 
 <!ENTITY contentBlocking.trackingProtection4.label "Tracking Content">
 
 <!ENTITY contentBlocking.manageSettings2.label "Manage Protection Settings">
 <!ENTITY contentBlocking.manageSettings2.accesskey "M">
 
 <!ENTITY contentBlocking.socialblock.label "Social Media Trackers">
 
-<!ENTITY contentBlocking.cookies2.label "Cross-Site Tracking Cookies">
-
 <!ENTITY contentBlocking.cryptominers.label "Cryptominers">
 
 <!ENTITY contentBlocking.fingerprinters.label "Fingerprinters">
 
 <!ENTITY contentBlocking.breakageReportView.title "Report a Broken Site">
 <!ENTITY contentBlocking.breakageReportView3.description "Blocking certain trackers can cause problems with some websites. Reporting these problems helps make &brandShortName; better for everyone. Sending this report will send a URL and information about your browser settings to Mozilla.">
 <!ENTITY contentBlocking.breakageReportView2.learnMore "Learn more">
 <!ENTITY contentBlocking.breakageReportView.collection.url.label "URL">
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -194,80 +194,41 @@
     color: CaptionText;
   }
 
   :root[tabsintitlebar]:not([inFullscreen]):not(:-moz-lwtheme):-moz-window-inactive {
     color: InactiveCaptionText;
   }
 }
 
-/**
- * In the classic themes, the titlebar has a horizontal gradient, which is
- * problematic for reading the text of background tabs when they're in the
- * titlebar. We side-step this issue by layering our own background underneath
- * the tabs. Unfortunately, this requires a bunch of positioning in order to get
- * text and icons to not appear fuzzy.
- */
 @media (-moz-windows-classic) {
   /**
-   * We need to bump up the z-index of the tabbrowser-tabs so that they appear
-   * over top of the fog we're applying for classic themes, as well as the nav-bar.
+   * In the classic themes, the titlebar has a horizontal gradient, which is
+   * problematic for reading the text of background tabs when they're in the
+   * titlebar. We side-step this issue by layering our own background underneath
+   * the tabs.
    */
-  #main-window[tabsintitlebar]:not([sizemode=fullscreen]) #tabbrowser-tabs {
-    position: relative;
-    z-index: 2;
-  }
-
-  #main-window[tabsintitlebar] #TabsToolbar:not(:-moz-lwtheme) {
-    position: relative;
+  :root[tabsintitlebar]:not([sizemode=fullscreen]) #TabsToolbar:not(:-moz-lwtheme) {
+    background-image: linear-gradient(transparent, ActiveCaption);
+    background-size: auto 200%;
   }
 
-  #main-window[tabsintitlebar]:not([sizemode=fullscreen]) #TabsToolbar:not(:-moz-lwtheme)::after {
-    /* Because we use placeholders for window controls etc. in the tabstrip,
-     * and position those with ordinal attributes, and because our layout code
-     * expects :before/:after nodes to come first/last in the frame list,
-     * we have to reorder this element to come last, hence the
-     * ordinal group value (see bug 853415). */
-    -moz-box-ordinal-group: 1001;
-    box-shadow: 0 0 50px 8px ActiveCaption;
-    content: "";
-    display: block;
-    height: 0;
-    margin: 0 50px;
-    position: absolute;
-    pointer-events: none;
-    top: 100%;
-    width: -moz-available;
+  :root[tabsintitlebar]:not([sizemode=fullscreen]) #TabsToolbar:not(:-moz-lwtheme):-moz-window-inactive {
+    background-image: linear-gradient(transparent, InactiveCaption);
   }
 
-  #main-window[tabsintitlebar]:not([sizemode=fullscreen]) #TabsToolbar:not(:-moz-lwtheme):-moz-window-inactive::after {
-    box-shadow: 0 0 50px 8px InactiveCaption;
-  }
-
-  #main-window[tabsintitlebar]:not([sizemode=fullscreen]) toolbar[customindex]:not(:-moz-lwtheme),
-  #main-window[tabsintitlebar]:not([sizemode=fullscreen]) #PersonalToolbar:not(:-moz-lwtheme) {
-    position: relative;
-  }
-
-  /* Need to constrain the box shadow fade to avoid overlapping layers, see bug 886281. */
-  #main-window[tabsintitlebar]:not([sizemode=fullscreen]) #navigator-toolbox:not(:-moz-lwtheme) {
-    overflow: -moz-hidden-unscrollable;
-  }
-
-  #main-window[tabsintitlebar][sizemode="normal"] > #navigator-toolbox:-moz-lwtheme {
-    /* Render a window top border: */
+  /* Add a window top border for webextension themes */
+  :root[tabsintitlebar][sizemode="normal"] > #navigator-toolbox:-moz-lwtheme {
     background-image: linear-gradient(to bottom,
           ThreeDLightShadow 0, ThreeDLightShadow 1px,
           ThreeDHighlight 1px, ThreeDHighlight 2px,
           ActiveBorder 2px, ActiveBorder 4px, transparent 4px);
   }
 
-  /* End classic titlebar gradient */
-
-  #main-window[tabsintitlebar]:not([inFullscreen]) :-moz-any(#TabsToolbar, #toolbar-menubar) toolbarbutton:not(:-moz-lwtheme) {
+  :root[tabsintitlebar]:not([inFullscreen]) :-moz-any(#TabsToolbar, #toolbar-menubar) toolbarbutton:not(:-moz-lwtheme) {
     color: inherit;
   }
 }
 
 #nav-bar:not([tabs-hidden="true"]) {
   /* This is needed for some toolbar button animations. Gross :( */
   position: relative;
 }
--- a/caps/nsJSPrincipals.cpp
+++ b/caps/nsJSPrincipals.cpp
@@ -384,10 +384,11 @@ bool nsJSPrincipals::write(JSContext* aC
     xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
     return false;
   }
 
   return WritePrincipalInfo(aWriter, info);
 }
 
 bool nsJSPrincipals::isSystemOrAddonPrincipal() {
-  return this->IsSystemPrincipal() || this->GetIsAddonOrExpandedAddonPrincipal();
+  return this->IsSystemPrincipal() ||
+         this->GetIsAddonOrExpandedAddonPrincipal();
 }
--- a/devtools/client/application/src/actions/manifest.js
+++ b/devtools/client/application/src/actions/manifest.js
@@ -1,30 +1,40 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { services } = require("../modules/services");
+const { l10n } = require("../modules/l10n");
+
+const { services, ManifestDevToolsError } = require("../modules/services");
 const {
   FETCH_MANIFEST_FAILURE,
   FETCH_MANIFEST_START,
   FETCH_MANIFEST_SUCCESS,
   RESET_MANIFEST,
 } = require("../constants");
 
 function fetchManifest() {
   return async (dispatch, getState) => {
     dispatch({ type: FETCH_MANIFEST_START });
-    const { manifest, errorMessage } = await services.fetchManifest();
+    try {
+      const manifest = await services.fetchManifest();
+      dispatch({ type: FETCH_MANIFEST_SUCCESS, manifest });
+    } catch (error) {
+      let errorMessage = error.message;
 
-    if (!errorMessage) {
-      dispatch({ type: FETCH_MANIFEST_SUCCESS, manifest });
-    } else {
+      // since Firefox DevTools errors may not make sense for the user, swap
+      // their message for a generic one.
+      if (error instanceof ManifestDevToolsError) {
+        console.error(error);
+        errorMessage = l10n.getString("manifest-loaded-devtools-error");
+      }
+
       dispatch({ type: FETCH_MANIFEST_FAILURE, error: errorMessage });
     }
   };
 }
 
 function resetManifest() {
   return { type: RESET_MANIFEST };
 }
--- a/devtools/client/application/src/modules/services.js
+++ b/devtools/client/application/src/modules/services.js
@@ -1,34 +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/. */
 
 "use strict";
 
+class ManifestDevToolsError extends Error {
+  constructor(...params) {
+    super(...params);
+
+    this.name = "ManifestDevToolsError";
+  }
+}
+
 class Services {
   init(toolbox) {
     this._toolbox = toolbox;
   }
 
   selectTool(toolId) {
     this._assertInit();
     return this._toolbox.selectTool(toolId);
   }
 
   async fetchManifest() {
-    this._assertInit();
+    let response;
 
-    const manifestFront = await this._toolbox.target.getFront("manifest");
-    const response = await manifestFront.fetchCanonicalManifest();
+    try {
+      this._assertInit();
+      const manifestFront = await this._toolbox.target.getFront("manifest");
+      response = await manifestFront.fetchCanonicalManifest();
+    } catch (error) {
+      throw new ManifestDevToolsError(
+        error.message,
+        error.fileName,
+        error.lineNumber
+      );
+    }
 
-    return response;
+    if (response.errorMessage) {
+      throw new Error(response.errorMessage);
+    }
+
+    return response.manifest;
   }
 
   _assertInit() {
     if (!this._toolbox) {
       throw new Error("Services singleton has not been initialized");
     }
   }
 }
 
-// exports a singleton, which will be used across all application panel modules
-exports.services = new Services();
+module.exports = {
+  ManifestDevToolsError,
+  // exports a singleton, which will be used across all application panel modules
+  services: new Services(),
+};
rename from devtools/client/application/test/components/.eslintrc.js
rename to devtools/client/application/test/node/.eslintrc.js
new file mode 100644
--- /dev/null
+++ b/devtools/client/application/test/node/actions/actions_application_panel-manifest.test.js
@@ -0,0 +1,81 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const {
+  MANIFEST_NO_ISSUES,
+} = require("devtools/client/application/test/node/fixtures/data/constants");
+
+const { setupStore } = require("devtools/client/application/test/node/helpers");
+
+const {
+  ManifestDevToolsError,
+  services,
+} = require("devtools/client/application/src/modules/services");
+
+const {
+  FETCH_MANIFEST_FAILURE,
+  FETCH_MANIFEST_START,
+  FETCH_MANIFEST_SUCCESS,
+} = require("devtools/client/application/src/constants");
+
+const {
+  fetchManifest,
+} = require("devtools/client/application/src/actions/manifest");
+
+describe("Manifest actions: fetchManifest", () => {
+  it("dispatches a START - SUCCESS sequence when fetching is OK", async () => {
+    const fetchManifestSpy = jest
+      .spyOn(services, "fetchManifest")
+      .mockResolvedValue(MANIFEST_NO_ISSUES);
+
+    const store = setupStore({});
+    await store.dispatch(fetchManifest());
+
+    expect(store.getActions()).toEqual([
+      { type: FETCH_MANIFEST_START },
+      { type: FETCH_MANIFEST_SUCCESS, manifest: MANIFEST_NO_ISSUES },
+    ]);
+
+    fetchManifestSpy.mockRestore();
+  });
+
+  it("dispatches a START - FAILURE sequence when fetching fails", async () => {
+    const fetchManifestSpy = jest
+      .spyOn(services, "fetchManifest")
+      .mockRejectedValue(new Error("lorem ipsum"));
+
+    const store = setupStore({});
+    await store.dispatch(fetchManifest());
+
+    expect(store.getActions()).toEqual([
+      { type: FETCH_MANIFEST_START },
+      { type: FETCH_MANIFEST_FAILURE, error: "lorem ipsum" },
+    ]);
+
+    fetchManifestSpy.mockRestore();
+  });
+
+  it("dispatches a START - FAILURE sequence when fetching fails due to a devtools error", async () => {
+    const error = new ManifestDevToolsError(":(");
+    const fetchManifestSpy = jest
+      .spyOn(services, "fetchManifest")
+      .mockRejectedValue(error);
+    const consoleErrorSpy = jest
+      .spyOn(console, "error")
+      .mockImplementation(() => {});
+
+    const store = setupStore({});
+    await store.dispatch(fetchManifest());
+
+    expect(store.getActions()).toEqual([
+      { type: FETCH_MANIFEST_START },
+      { type: FETCH_MANIFEST_FAILURE, error: "manifest-loaded-devtools-error" },
+    ]);
+    expect(consoleErrorSpy).toHaveBeenCalledWith(error);
+
+    fetchManifestSpy.mockRestore();
+    consoleErrorSpy.mockRestore();
+  });
+});
rename from devtools/client/application/test/components/babel.config.js
rename to devtools/client/application/test/node/babel.config.js
rename from devtools/client/application/test/components/__snapshots__/components_application_panel-App.test.js.snap
rename to devtools/client/application/test/node/components/__snapshots__/components_application_panel-App.test.js.snap
rename from devtools/client/application/test/components/components_application_panel-App.test.js
rename to devtools/client/application/test/node/components/components_application_panel-App.test.js
rename from devtools/client/application/test/components/manifest/__snapshots__/components_application_panel-Manifest.test.js.snap
rename to devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-Manifest.test.js.snap
rename from devtools/client/application/test/components/manifest/__snapshots__/components_application_panel-ManifestColorItem.test.js.snap
rename to devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestColorItem.test.js.snap
rename from devtools/client/application/test/components/manifest/__snapshots__/components_application_panel-ManifestEmpty.test.js.snap
rename to devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestEmpty.test.js.snap
rename from devtools/client/application/test/components/manifest/__snapshots__/components_application_panel-ManifestIconItem.test.js.snap
rename to devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestIconItem.test.js.snap
rename from devtools/client/application/test/components/manifest/__snapshots__/components_application_panel-ManifestIssue.test.js.snap
rename to devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestIssue.test.js.snap
rename from devtools/client/application/test/components/manifest/__snapshots__/components_application_panel-ManifestIssueList.test.js.snap
rename to devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestIssueList.test.js.snap
rename from devtools/client/application/test/components/manifest/__snapshots__/components_application_panel-ManifestItem.test.js.snap
rename to devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestItem.test.js.snap
rename from devtools/client/application/test/components/manifest/__snapshots__/components_application_panel-ManifestJsonLink.test.js.snap
rename to devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestJsonLink.test.js.snap
rename from devtools/client/application/test/components/manifest/__snapshots__/components_application_panel-ManifestLoader.test.js.snap
rename to devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestLoader.test.js.snap
rename from devtools/client/application/test/components/manifest/__snapshots__/components_application_panel-ManifestPage.test.js.snap
rename to devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestPage.test.js.snap
rename from devtools/client/application/test/components/manifest/__snapshots__/components_application_panel-ManifestSection.test.js.snap
rename to devtools/client/application/test/node/components/manifest/__snapshots__/components_application_panel-ManifestSection.test.js.snap
rename from devtools/client/application/test/components/manifest/components_application_panel-Manifest.test.js
rename to devtools/client/application/test/node/components/manifest/components_application_panel-Manifest.test.js
--- a/devtools/client/application/test/components/manifest/components_application_panel-Manifest.test.js
+++ b/devtools/client/application/test/node/components/manifest/components_application_panel-Manifest.test.js
@@ -13,17 +13,17 @@ const Manifest = createFactory(
 
 const {
   MANIFEST_COLOR_MEMBERS,
   MANIFEST_ICON_MEMBERS,
   MANIFEST_STRING_MEMBERS,
   MANIFEST_UNKNOWN_TYPE_MEMBERS,
   MANIFEST_NO_ISSUES,
   MANIFEST_WITH_ISSUES,
-} = require("../fixtures/data/constants");
+} = require("devtools/client/application/test/node/fixtures/data/constants");
 
 /*
  * Test for Manifest component
  */
 
 describe("Manifest", () => {
   it("renders the expected snapshot for a manifest with string members", () => {
     const wrapper = shallow(Manifest(MANIFEST_STRING_MEMBERS));
rename from devtools/client/application/test/components/manifest/components_application_panel-ManifestColorItem.test.js
rename to devtools/client/application/test/node/components/manifest/components_application_panel-ManifestColorItem.test.js
rename from devtools/client/application/test/components/manifest/components_application_panel-ManifestEmpty.test.js
rename to devtools/client/application/test/node/components/manifest/components_application_panel-ManifestEmpty.test.js
rename from devtools/client/application/test/components/manifest/components_application_panel-ManifestIconItem.test.js
rename to devtools/client/application/test/node/components/manifest/components_application_panel-ManifestIconItem.test.js
rename from devtools/client/application/test/components/manifest/components_application_panel-ManifestIssue.test.js
rename to devtools/client/application/test/node/components/manifest/components_application_panel-ManifestIssue.test.js
rename from devtools/client/application/test/components/manifest/components_application_panel-ManifestIssueList.test.js
rename to devtools/client/application/test/node/components/manifest/components_application_panel-ManifestIssueList.test.js
rename from devtools/client/application/test/components/manifest/components_application_panel-ManifestItem.test.js
rename to devtools/client/application/test/node/components/manifest/components_application_panel-ManifestItem.test.js
rename from devtools/client/application/test/components/manifest/components_application_panel-ManifestJsonLink.test.js
rename to devtools/client/application/test/node/components/manifest/components_application_panel-ManifestJsonLink.test.js
rename from devtools/client/application/test/components/manifest/components_application_panel-ManifestLoader.test.js
rename to devtools/client/application/test/node/components/manifest/components_application_panel-ManifestLoader.test.js
--- a/devtools/client/application/test/components/manifest/components_application_panel-ManifestLoader.test.js
+++ b/devtools/client/application/test/node/components/manifest/components_application_panel-ManifestLoader.test.js
@@ -2,92 +2,58 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Import libs
 const { shallow } = require("enzyme");
 const { createFactory } = require("react");
 // Import test helpers
-const {
-  flushPromises,
-  setupStore,
-} = require("devtools/client/application/test/components/helpers/helpers");
+const { setupStore } = require("devtools/client/application/test/node/helpers");
 // Import fixtures
 const {
   MANIFEST_NO_ISSUES,
-} = require("devtools/client/application/test/components/fixtures/data/constants");
+} = require("devtools/client/application/test/node/fixtures/data/constants");
 
-// Import app modules
-const {
-  services,
-} = require("devtools/client/application/src/modules/services");
-
-const {
-  FETCH_MANIFEST_FAILURE,
-  FETCH_MANIFEST_START,
-  FETCH_MANIFEST_SUCCESS,
-} = require("devtools/client/application/src/constants");
+const manifestActions = require("devtools/client/application/src/actions/manifest");
+// NOTE: we need to spy on the action before we load the component, so it gets
+//       bound to the spy, not the original implementation
+const fetchManifestActionSpy = jest.spyOn(manifestActions, "fetchManifest");
 
 const ManifestLoader = createFactory(
   require("devtools/client/application/src/components/manifest/ManifestLoader")
 );
 
-/**
- * Test for ManifestPage.js component
- */
-
 describe("ManifestLoader", () => {
   function buildStore({ manifest, errorMessage, isLoading }) {
     const manifestState = Object.assign(
       {
         manifest: null,
         errorMessage: "",
         isLoading: false,
       },
       { manifest, errorMessage, isLoading }
     );
 
     return setupStore({ manifest: manifestState });
   }
 
-  it("loads a manifest when mounted and triggers actions when loading is OK", async () => {
-    const fetchManifestSpy = jest
-      .spyOn(services, "fetchManifest")
-      .mockResolvedValue({ manifest: MANIFEST_NO_ISSUES, errorMessage: "" });
+  afterAll(() => {
+    fetchManifestActionSpy.mockRestore();
+  });
+
+  it("loads a manifest when mounted", async () => {
+    fetchManifestActionSpy.mockReturnValue({ type: "foo" });
 
     const store = buildStore({});
 
     shallow(ManifestLoader({ store })).dive();
-    await flushPromises();
 
-    expect(store.getActions()).toEqual([
-      { type: FETCH_MANIFEST_START },
-      { type: FETCH_MANIFEST_SUCCESS, manifest: MANIFEST_NO_ISSUES },
-    ]);
-
-    fetchManifestSpy.mockRestore();
-  });
-
-  it("loads a manifest when mounted and triggers actions when loading fails", async () => {
-    const fetchManifestSpy = jest
-      .spyOn(services, "fetchManifest")
-      .mockResolvedValue({ manifest: null, errorMessage: "lorem ipsum" });
-
-    const store = buildStore({});
-
-    shallow(ManifestLoader({ store })).dive();
-    await flushPromises();
-
-    expect(store.getActions()).toEqual([
-      { type: FETCH_MANIFEST_START },
-      { type: FETCH_MANIFEST_FAILURE, error: "lorem ipsum" },
-    ]);
-
-    fetchManifestSpy.mockRestore();
+    expect(manifestActions.fetchManifest).toHaveBeenCalled();
+    fetchManifestActionSpy.mockReset();
   });
 
   it("renders a message when it is loading", async () => {
     const store = buildStore({ isLoading: true });
     const wrapper = shallow(ManifestLoader({ store })).dive();
     expect(wrapper).toMatchSnapshot();
   });
 
rename from devtools/client/application/test/components/manifest/components_application_panel-ManifestPage.test.js
rename to devtools/client/application/test/node/components/manifest/components_application_panel-ManifestPage.test.js
--- a/devtools/client/application/test/components/manifest/components_application_panel-ManifestPage.test.js
+++ b/devtools/client/application/test/node/components/manifest/components_application_panel-ManifestPage.test.js
@@ -2,22 +2,20 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Import libs
 const { shallow } = require("enzyme");
 const { createFactory } = require("react");
 
-const {
-  setupStore,
-} = require("devtools/client/application/test/components/helpers/helpers");
+const { setupStore } = require("devtools/client/application/test/node/helpers");
 const {
   MANIFEST_SIMPLE,
-} = require("devtools/client/application/test/components/fixtures/data/constants");
+} = require("devtools/client/application/test/node/fixtures/data/constants");
 
 const ManifestPage = createFactory(
   require("devtools/client/application/src/components/manifest/ManifestPage")
 );
 
 /**
  * Test for ManifestPage.js component
  */
rename from devtools/client/application/test/components/manifest/components_application_panel-ManifestSection.test.js
rename to devtools/client/application/test/node/components/manifest/components_application_panel-ManifestSection.test.js
rename from devtools/client/application/test/components/routing/__snapshots__/components_application_panel-PageSwitcher.test.js.snap
rename to devtools/client/application/test/node/components/routing/__snapshots__/components_application_panel-PageSwitcher.test.js.snap
rename from devtools/client/application/test/components/routing/__snapshots__/components_application_panel-Sidebar.test.js.snap
rename to devtools/client/application/test/node/components/routing/__snapshots__/components_application_panel-Sidebar.test.js.snap
rename from devtools/client/application/test/components/routing/__snapshots__/components_application_panel-SidebarItem.test.js.snap
rename to devtools/client/application/test/node/components/routing/__snapshots__/components_application_panel-SidebarItem.test.js.snap
rename from devtools/client/application/test/components/routing/components_application_panel-PageSwitcher.test.js
rename to devtools/client/application/test/node/components/routing/components_application_panel-PageSwitcher.test.js
--- a/devtools/client/application/test/components/routing/components_application_panel-PageSwitcher.test.js
+++ b/devtools/client/application/test/node/components/routing/components_application_panel-PageSwitcher.test.js
@@ -3,19 +3,17 @@
 
 "use strict";
 
 // Import libs
 const { shallow } = require("enzyme");
 const { createFactory } = require("react");
 
 // Import setupStore with imported & combined reducers
-const {
-  setupStore,
-} = require("devtools/client/application/test/components/helpers/helpers");
+const { setupStore } = require("devtools/client/application/test/node/helpers");
 
 const PageSwitcher = createFactory(
   require("devtools/client/application/src/components/routing/PageSwitcher")
 );
 
 const { PAGE_TYPES } = require("devtools/client/application/src/constants");
 
 /**
rename from devtools/client/application/test/components/routing/components_application_panel-Sidebar.test.js
rename to devtools/client/application/test/node/components/routing/components_application_panel-Sidebar.test.js
--- a/devtools/client/application/test/components/routing/components_application_panel-Sidebar.test.js
+++ b/devtools/client/application/test/node/components/routing/components_application_panel-Sidebar.test.js
@@ -2,19 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Import libs
 const { shallow } = require("enzyme");
 const { createFactory } = require("react");
 
-const {
-  setupStore,
-} = require("devtools/client/application/test/components/helpers/helpers");
+const { setupStore } = require("devtools/client/application/test/node/helpers");
 
 const { PAGE_TYPES } = require("devtools/client/application/src/constants");
 
 const Sidebar = createFactory(
   require("devtools/client/application/src/components/routing/Sidebar")
 );
 
 /**
rename from devtools/client/application/test/components/routing/components_application_panel-SidebarItem.test.js
rename to devtools/client/application/test/node/components/routing/components_application_panel-SidebarItem.test.js
--- a/devtools/client/application/test/components/routing/components_application_panel-SidebarItem.test.js
+++ b/devtools/client/application/test/node/components/routing/components_application_panel-SidebarItem.test.js
@@ -2,19 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Import libs
 const { shallow } = require("enzyme");
 const { createFactory } = require("react");
 
-const {
-  setupStore,
-} = require("devtools/client/application/test/components/helpers/helpers");
+const { setupStore } = require("devtools/client/application/test/node/helpers");
 
 const { PAGE_TYPES } = require("devtools/client/application/src/constants");
 
 const SidebarItem = createFactory(
   require("devtools/client/application/src/components/routing/SidebarItem")
 );
 
 /**
rename from devtools/client/application/test/components/service-workers/__snapshots__/components_application_panel-WorkerList.test.js.snap
rename to devtools/client/application/test/node/components/service-workers/__snapshots__/components_application_panel-WorkerList.test.js.snap
rename from devtools/client/application/test/components/service-workers/__snapshots__/components_application_panel-WorkerListEmpty.test.js.snap
rename to devtools/client/application/test/node/components/service-workers/__snapshots__/components_application_panel-WorkerListEmpty.test.js.snap
rename from devtools/client/application/test/components/service-workers/__snapshots__/components_application_panel-WorkersPage.test.js.snap
rename to devtools/client/application/test/node/components/service-workers/__snapshots__/components_application_panel-WorkersPage.test.js.snap
rename from devtools/client/application/test/components/service-workers/components_application_panel-WorkerList.test.js
rename to devtools/client/application/test/node/components/service-workers/components_application_panel-WorkerList.test.js
--- a/devtools/client/application/test/components/service-workers/components_application_panel-WorkerList.test.js
+++ b/devtools/client/application/test/node/components/service-workers/components_application_panel-WorkerList.test.js
@@ -6,17 +6,17 @@
 // Import libs
 const { shallow } = require("enzyme");
 const { createFactory } = require("react");
 
 // Import constants
 const {
   SINGLE_WORKER_DEFAULT_DOMAIN_LIST,
   MULTIPLE_WORKER_LIST,
-} = require("devtools/client/application/test/components/fixtures/data/constants");
+} = require("devtools/client/application/test/node/fixtures/data/constants");
 
 const WorkerList = createFactory(
   require("devtools/client/application/src/components/service-workers/WorkerList")
 );
 
 /**
  * Test for workerList.js component
  */
rename from devtools/client/application/test/components/service-workers/components_application_panel-WorkerListEmpty.test.js
rename to devtools/client/application/test/node/components/service-workers/components_application_panel-WorkerListEmpty.test.js
rename from devtools/client/application/test/components/service-workers/components_application_panel-WorkersPage.test.js
rename to devtools/client/application/test/node/components/service-workers/components_application_panel-WorkersPage.test.js
--- a/devtools/client/application/test/components/service-workers/components_application_panel-WorkersPage.test.js
+++ b/devtools/client/application/test/node/components/service-workers/components_application_panel-WorkersPage.test.js
@@ -9,22 +9,20 @@ const { createFactory } = require("react
 
 // Import fixtures
 const {
   EMPTY_WORKER_LIST,
   SINGLE_WORKER_DEFAULT_DOMAIN_LIST,
   SINGLE_WORKER_DIFFERENT_DOMAIN_LIST,
   MULTIPLE_WORKER_LIST,
   MULTIPLE_WORKER_MIXED_DOMAINS_LIST,
-} = require("devtools/client/application/test/components/fixtures/data/constants");
+} = require("devtools/client/application/test/node/fixtures/data/constants");
 
 // Import setupStore with imported & combined reducers
-const {
-  setupStore,
-} = require("devtools/client/application/test/components/helpers/helpers");
+const { setupStore } = require("devtools/client/application/test/node/helpers");
 
 // Import component
 const WorkersPage = createFactory(
   require("devtools/client/application/src/components/service-workers/WorkersPage")
 );
 
 /**
  * Test for App.js component
rename from devtools/client/application/test/components/fixtures/Chrome.js
rename to devtools/client/application/test/node/fixtures/Chrome.js
rename from devtools/client/application/test/components/fixtures/Services.js
rename to devtools/client/application/test/node/fixtures/Services.js
rename from devtools/client/application/test/components/fixtures/data/constants.js
rename to devtools/client/application/test/node/fixtures/data/constants.js
rename from devtools/client/application/test/components/fixtures/fluent-l10n.js
rename to devtools/client/application/test/node/fixtures/fluent-l10n.js
rename from devtools/client/application/test/components/fixtures/stub.js
rename to devtools/client/application/test/node/fixtures/stub.js
rename from devtools/client/application/test/components/fixtures/unicode-url.js
rename to devtools/client/application/test/node/fixtures/unicode-url.js
rename from devtools/client/application/test/components/helpers/helpers.js
rename to devtools/client/application/test/node/helpers.js
rename from devtools/client/application/test/components/jest.config.js
rename to devtools/client/application/test/node/jest.config.js
rename from devtools/client/application/test/components/package.json
rename to devtools/client/application/test/node/package.json
rename from devtools/client/application/test/components/setup.js
rename to devtools/client/application/test/node/setup.js
rename from devtools/client/application/test/components/yarn.lock
rename to devtools/client/application/test/node/yarn.lock
--- a/devtools/client/bin/devtools-node-test-runner.js
+++ b/devtools/client/bin/devtools-node-test-runner.js
@@ -29,17 +29,17 @@ const SUITES = {
     path: "../aboutdebugging/test/node",
     type: TEST_TYPES.JEST,
   },
   accessibility: {
     path: "../accessibility/test/node",
     type: TEST_TYPES.JEST,
   },
   application: {
-    path: "../application/test/components",
+    path: "../application/test/node",
     type: TEST_TYPES.JEST,
   },
   compatibility: {
     path: "../inspector/compatibility/test/node",
     type: TEST_TYPES.JEST,
   },
   framework: {
     path: "../framework/test/node",
--- a/devtools/client/debugger/images/window.svg
+++ b/devtools/client/debugger/images/window.svg
@@ -1,4 +1,6 @@
 <!-- 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/. -->
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill="context-fill" d="M13 1H3a3 3 0 0 0-3 3v8a3 3 0 0 0 3 3h11a2 2 0 0 0 2-2V4a3 3 0 0 0-3-3zm1 11a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V6h12zm0-7H2V4a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1z"></path></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
+  <path d="M1 3a1 1 0 011-1h12a1 1 0 011 1v10a1 1 0 01-1 1H2a1 1 0 01-1-1V3zm13 0H2v2h12V3zm0 3H2v7h12V6z"/>
+</svg>
--- a/devtools/client/debugger/images/worker.svg
+++ b/devtools/client/debugger/images/worker.svg
@@ -1,6 +1,7 @@
 <!-- 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/. -->
 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
-  <path fill-rule="evenodd" d="M7.95 8.68L5.67 6.41l-.03-.03 1.45-1.45c.17-.17.26-.35.26-.52 0-.17-.09-.34-.18-.51l-.86-.86c-.34-.26-.77-.26-1.03 0L1.76 6.56c-.17.17-.26.34-.26.52 0 .17.09.34.17.51l.86.86c.26.26.78.26 1.03 0l1.47-1.47.03.04 2.28 2.27-3.14 3.13a.43.43 0 0 0 .61.61L7.95 9.9l3.13 3.13a.43.43 0 0 0 .61-.6L8.56 9.28l2.2-2.2 1.31 1.34c.09.09 1.98.95 2.33.6.34-.34-.26-2.32-.43-2.5l-.95-.94a.92.92 0 0 0 .17-.51c0-.18-.08-.35-.17-.52l-.34-.34a.67.67 0 0 0-.95 0l-1.29-1.2c-.17-.18-.26-.18-.43-.18-.17 0-.26.09-.43.17L8.55 4.05c-.17.08-.17.17-.17.34s.08.34.17.43l1.61 1.65-2.21 2.21z"/>
+  <path d="M1.293 6.078a1 1 0 000 1.415L2 8.2a1 1 0 001.414 0l1.414-1.414 6.364 6.364a.5.5 0 10.707-.708L5.536 6.078 6.95 4.664a1 1 0 000-1.414l-.707-.707a1 1 0 00-1.415 0L1.293 6.078z"/>
+  <path d="M13.208 3.497a.6.6 0 00-.849 0l-.106.107-1.06-1.061a1 1 0 00-1.415 0l-.707.707a1 1 0 000 1.414l1.414 1.414-6.364 6.364a.5.5 0 10.707.707l6.364-6.363L12.607 8.2c.282.283 2.192.636 2.475.353.282-.283-.071-2.192-.354-2.475l-1.06-1.06.105-.106a.6.6 0 000-.849l-.565-.566z"/>
 </svg>
--- a/devtools/client/debugger/packages/devtools-reps/src/launchpad/actions/expressions.js
+++ b/devtools/client/debugger/packages/devtools-reps/src/launchpad/actions/expressions.js
@@ -42,19 +42,19 @@ function showResultPacket(key) {
 
 function hideResultPacket(key) {
   return {
     key,
     type: constants.HIDE_RESULT_PACKET,
   };
 }
 
-function createObjectClient(grip) {
+function createObjectFront(grip) {
   return function({ dispatch, client }) {
-    return client.getObjectClient(grip);
+    return client.getObjectFront(grip);
   };
 }
 
 function createLongStringClient(grip) {
   return function({ dispatch, client }) {
     return client.getLongStringClient(grip);
   };
 }
@@ -65,12 +65,12 @@ function releaseActor(actor) {
   };
 }
 module.exports = {
   addExpression,
   clearExpressions,
   evaluateInput,
   showResultPacket,
   hideResultPacket,
-  createObjectClient,
+  createObjectFront,
   createLongStringClient,
   releaseActor,
 };
--- a/devtools/client/debugger/packages/devtools-reps/src/launchpad/index.js
+++ b/devtools/client/debugger/packages/devtools-reps/src/launchpad/index.js
@@ -22,17 +22,17 @@ function onConnect(connection) {
   }
 
   const client = {
     clientCommands: {
       evaluate: input =>
         connection.tabConnection.tabTarget.activeConsole.evaluateJSAsync(input),
     },
 
-    createObjectClient: function(grip) {
+    createObjectFront: function(grip) {
       return connection.tabConnection.threadFront.pauseGrip(grip);
     },
     createLongStringClient: function(grip) {
       return connection.tabConnection.tabTarget.activeConsole.longString(grip);
     },
     releaseActor: function(actor) {
       const debuggerClient = connection.tabConnection.debuggerClient;
       const objFront = debuggerClient.getFrontByID(actor);
--- a/devtools/client/debugger/packages/devtools-reps/src/object-inspector/actions.js
+++ b/devtools/client/debugger/packages/devtools-reps/src/object-inspector/actions.js
@@ -32,17 +32,17 @@ function nodeCollapse(node: Node) {
   return {
     type: "NODE_COLLAPSE",
     data: { node },
   };
 }
 
 /*
  * This action checks if we need to fetch properties, entries, prototype and
- * symbols for a given node. If we do, it will call the appropriate ObjectClient
+ * symbols for a given node. If we do, it will call the appropriate ObjectFront
  * functions.
  */
 function nodeLoadProperties(node: Node, actor) {
   return async ({ dispatch, client, getState }: ThunkArg) => {
     const state = getState();
     const loadedProperties = getLoadedProperties(state);
     if (loadedProperties.has(node.path)) {
       return;
@@ -179,21 +179,18 @@ async function releaseActors(state, clie
 function invokeGetter(
   node: Node,
   targetGrip: object,
   receiverId: string | null,
   getterName: string
 ) {
   return async ({ dispatch, client, getState }: ThunkArg) => {
     try {
-      const objectClient = client.createObjectClient(targetGrip);
-      const result = await objectClient.getPropertyValue(
-        getterName,
-        receiverId
-      );
+      const objectFront = client.createObjectFront(targetGrip);
+      const result = await objectFront.getPropertyValue(getterName, receiverId);
       dispatch({
         type: "GETTER_INVOKED",
         data: {
           node,
           result,
         },
       });
     } catch (e) {
rename from devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/__mocks__/object-client.js
rename to devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/__mocks__/object-front.js
--- a/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/__mocks__/object-client.js
+++ b/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/__mocks__/object-front.js
@@ -1,35 +1,35 @@
 /* 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/>. */
 
-function ObjectClient(grip, overrides) {
+function ObjectFront(grip, overrides) {
   return {
     grip,
     enumEntries: function() {
-      return Promise.resolve({
-        iterator: this.getIterator({
+      return Promise.resolve(
+        this.getIterator({
           ownProperties: {},
-        }),
-      });
+        })
+      );
     },
     enumProperties: function(options) {
-      return Promise.resolve({
-        iterator: this.getIterator({
+      return Promise.resolve(
+        this.getIterator({
           ownProperties: {},
-        }),
-      });
+        })
+      );
     },
     enumSymbols: function() {
-      return Promise.resolve({
-        iterator: this.getIterator({
+      return Promise.resolve(
+        this.getIterator({
           ownSymbols: [],
-        }),
-      });
+        })
+      );
     },
     getPrototype: function() {
       return Promise.resolve({
         prototype: {},
       });
     },
     // Declared here so we can override it.
     getIterator(res) {
@@ -38,9 +38,9 @@ function ObjectClient(grip, overrides) {
           return Promise.resolve(res);
         },
       };
     },
     ...overrides,
   };
 }
 
-module.exports = ObjectClient;
+module.exports = ObjectFront;
--- a/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/component/__snapshots__/entries.js.snap
+++ b/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/component/__snapshots__/entries.js.snap
@@ -1,18 +1,18 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`ObjectInspector - entries calls ObjectClient.enumEntries when expected 1`] = `
+exports[`ObjectInspector - entries calls ObjectFront.enumEntries when expected 1`] = `
 "
 ▼ Map(11)
 |  ▶︎ <entries>
 "
 `;
 
-exports[`ObjectInspector - entries calls ObjectClient.enumEntries when expected 2`] = `
+exports[`ObjectInspector - entries calls ObjectFront.enumEntries when expected 2`] = `
 "
   ▼ Map(11)
 [ |  ▼ <entries> ]
   |  |  ▶︎ 0: \\"key-0\\" → \\"value-0\\"
   |  |  ▶︎ 1: \\"key-1\\" → \\"value-1\\"
   |  |  ▶︎ 2: \\"key-2\\" → \\"value-2\\"
   |  |  ▶︎ 3: \\"key-3\\" → \\"value-3\\"
   |  |  ▶︎ 4: \\"key-4\\" → \\"value-4\\"
@@ -20,24 +20,24 @@ exports[`ObjectInspector - entries calls
   |  |  ▶︎ 6: \\"key-6\\" → \\"value-6\\"
   |  |  ▶︎ 7: \\"key-7\\" → \\"value-7\\"
   |  |  ▶︎ 8: \\"key-8\\" → \\"value-8\\"
   |  |  ▶︎ 9: \\"key-9\\" → \\"value-9\\"
   |  |  ▶︎ 10: \\"key-10\\" → \\"value-10\\"
 "
 `;
 
-exports[`ObjectInspector - entries calls ObjectClient.enumEntries when expected 3`] = `
+exports[`ObjectInspector - entries calls ObjectFront.enumEntries when expected 3`] = `
 "
   ▼ Map(11)
 [ |  ▶︎ <entries> ]
 "
 `;
 
-exports[`ObjectInspector - entries calls ObjectClient.enumEntries when expected 4`] = `
+exports[`ObjectInspector - entries calls ObjectFront.enumEntries when expected 4`] = `
 "
   ▼ Map(11)
 [ |  ▼ <entries> ]
   |  |  ▶︎ 0: \\"key-0\\" → \\"value-0\\"
   |  |  ▶︎ 1: \\"key-1\\" → \\"value-1\\"
   |  |  ▶︎ 2: \\"key-2\\" → \\"value-2\\"
   |  |  ▶︎ 3: \\"key-3\\" → \\"value-3\\"
   |  |  ▶︎ 4: \\"key-4\\" → \\"value-4\\"
--- a/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/component/basic.js
+++ b/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/component/basic.js
@@ -8,29 +8,29 @@ const { createNode, NODE_TYPES } = requi
 const repsPath = "../../../reps";
 const { MODE } = require(`${repsPath}/constants`);
 const { Rep } = require(`${repsPath}/rep`);
 const {
   formatObjectInspector,
   waitForDispatch,
   waitForLoadedProperties,
 } = require("../test-utils");
-const ObjectClient = require("../__mocks__/object-client");
+const ObjectFront = require("../__mocks__/object-front");
 const gripRepStubs = require(`${repsPath}/stubs/grip`);
 
 function generateDefaults(overrides) {
   return {
     autoExpandDepth: 0,
     ...overrides,
   };
 }
 
 function mountOI(props, { initialState } = {}) {
   const client = {
-    createObjectClient: grip => ObjectClient(grip),
+    createObjectFront: grip => ObjectFront(grip),
   };
 
   const obj = mountObjectInspector({
     client,
     props: generateDefaults(props),
     initialState: {
       objectInspector: {
         ...initialState,
--- a/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/component/classnames.js
+++ b/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/component/classnames.js
@@ -1,13 +1,13 @@
 /* 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/>. */
 
-const ObjectClient = require("../__mocks__/object-client");
+const ObjectFront = require("../__mocks__/object-front");
 const { mountObjectInspector } = require("../test-utils");
 
 function generateDefaults(overrides) {
   return {
     autoExpandDepth: 0,
     roots: [
       {
         path: "root",
@@ -15,17 +15,17 @@ function generateDefaults(overrides) {
         contents: { value: 42 },
       },
     ],
     ...overrides,
   };
 }
 
 function mount(props) {
-  const client = { createObjectClient: grip => ObjectClient(grip) };
+  const client = { createObjectFront: grip => ObjectFront(grip) };
 
   return mountObjectInspector({
     client,
     props: generateDefaults(props),
   });
 }
 
 describe("ObjectInspector - classnames", () => {
--- a/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/component/create-long-string-client.js
+++ b/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/component/create-long-string-client.js
@@ -1,26 +1,26 @@
 /* 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/>. */
 
 /* global jest */
 
 const { mountObjectInspector } = require("../test-utils");
-const ObjectClient = require("../__mocks__/object-client");
+const ObjectFront = require("../__mocks__/object-front");
 const LongStringClient = require("../__mocks__/long-string-client");
 
 const repsPath = "../../../reps";
 const longStringStubs = require(`${repsPath}/stubs/long-string`);
 
 function mount(props) {
   const substring = jest.fn(() => Promise.resolve({ fullText: "" }));
 
   const client = {
-    createObjectClient: grip => ObjectClient(grip),
+    createObjectFront: grip => ObjectFront(grip),
     createLongStringClient: jest.fn(grip =>
       LongStringClient(grip, { substring })
     ),
   };
 
   const obj = mountObjectInspector({
     client,
     props,
--- a/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/component/create-object-client.js
+++ b/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/component/create-object-client.js
@@ -1,112 +1,112 @@
 /* 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/>. */
 
 /* global jest */
 
 const { mountObjectInspector } = require("../test-utils");
-const ObjectClient = require("../__mocks__/object-client");
+const ObjectFront = require("../__mocks__/object-front");
 
 const {
   createNode,
   makeNodesForEntries,
   makeNumericalBuckets,
 } = require("../../utils/node");
 
 const repsPath = "../../../reps";
 const gripRepStubs = require(`${repsPath}/stubs/grip`);
 const gripArrayRepStubs = require(`${repsPath}/stubs/grip-array`);
 
 function mount(props, overrides = {}) {
   const client = {
-    createObjectClient:
-      overrides.createObjectClient || jest.fn(grip => ObjectClient(grip)),
+    createObjectFront:
+      overrides.createObjectFront || jest.fn(grip => ObjectFront(grip)),
   };
 
   return mountObjectInspector({
     client,
     props,
   });
 }
 
-describe("createObjectClient", () => {
+describe("createObjectFront", () => {
   it("is called with the expected object for regular node", () => {
     const stub = gripRepStubs.get("testMoreThanMaxProps");
     const { client } = mount({
       autoExpandDepth: 1,
       roots: [
         {
           path: "root",
           contents: {
             value: stub,
           },
         },
       ],
     });
 
-    expect(client.createObjectClient.mock.calls[0][0]).toBe(stub);
+    expect(client.createObjectFront.mock.calls[0][0]).toBe(stub);
   });
 
   it("is called with the expected object for entries node", () => {
     const grip = Symbol();
     const mapStubNode = createNode({
       name: "map",
       contents: { value: grip },
     });
     const entriesNode = makeNodesForEntries(mapStubNode);
 
     const { client } = mount({
       autoExpandDepth: 1,
       roots: [entriesNode],
     });
 
-    expect(client.createObjectClient.mock.calls[0][0]).toBe(grip);
+    expect(client.createObjectFront.mock.calls[0][0]).toBe(grip);
   });
 
   it("is called with the expected object for bucket node", () => {
     const grip = gripArrayRepStubs.get("testMaxProps");
     const root = createNode({ name: "root", contents: { value: grip } });
     const [bucket] = makeNumericalBuckets(root);
 
     const { client } = mount({
       autoExpandDepth: 1,
       roots: [bucket],
     });
-    expect(client.createObjectClient.mock.calls[0][0]).toBe(grip);
+    expect(client.createObjectFront.mock.calls[0][0]).toBe(grip);
   });
 
   it("is called with the expected object for sub-bucket node", () => {
     const grip = gripArrayRepStubs.get("testMaxProps");
     const root = createNode({ name: "root", contents: { value: grip } });
     const [bucket] = makeNumericalBuckets(root);
     const [subBucket] = makeNumericalBuckets(bucket);
 
     const { client } = mount({
       autoExpandDepth: 1,
       roots: [subBucket],
     });
 
-    expect(client.createObjectClient.mock.calls[0][0]).toBe(grip);
+    expect(client.createObjectFront.mock.calls[0][0]).toBe(grip);
   });
 
-  it("doesn't fail when ObjectClient doesn't have expected methods", () => {
+  it("doesn't fail when ObjectFront doesn't have expected methods", () => {
     const stub = gripRepStubs.get("testMoreThanMaxProps");
     const root = createNode({ name: "root", contents: { value: stub } });
 
     // Override console.error so we don't spam test results.
     const originalConsoleError = console.error;
     console.error = () => {};
 
-    const createObjectClient = x => ({});
+    const createObjectFront = x => ({});
     mount(
       {
         autoExpandDepth: 1,
         roots: [root],
       },
-      { createObjectClient }
+      { createObjectFront }
     );
 
     // rollback console.error.
     console.error = originalConsoleError;
   });
 });
--- a/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/component/entries.js
+++ b/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/component/entries.js
@@ -8,39 +8,37 @@ const { mountObjectInspector } = require
 const { MODE } = require("../../../reps/constants");
 const {
   formatObjectInspector,
   waitForDispatch,
   waitForLoadedProperties,
 } = require("../test-utils");
 const gripMapRepStubs = require("../../../reps/stubs/grip-map");
 const mapStubs = require("../../stubs/map");
-const ObjectClient = require("../__mocks__/object-client");
+const ObjectFront = require("../__mocks__/object-front");
 
 function generateDefaults(overrides) {
   return {
     autoExpandDepth: 0,
-    createObjectClient: grip => ObjectClient(grip),
+    createObjectFront: grip => ObjectFront(grip),
     ...overrides,
   };
 }
 
 function getEnumEntriesMock() {
   return jest.fn(() => ({
-    iterator: {
-      slice: () => mapStubs.get("11-entries"),
-    },
+    slice: () => mapStubs.get("11-entries"),
   }));
 }
 
 function mount(props, { initialState }) {
   const enumEntries = getEnumEntriesMock();
 
   const client = {
-    createObjectClient: grip => ObjectClient(grip, { enumEntries }),
+    createObjectFront: grip => ObjectFront(grip, { enumEntries }),
   };
   const obj = mountObjectInspector({
     client,
     props: generateDefaults(props),
     initialState: {
       objectInspector: {
         ...initialState,
         evaluations: new Map(),
@@ -81,17 +79,17 @@ describe("ObjectInspector - entries", ()
     wrapper.update();
     expect(formatObjectInspector(wrapper)).toMatchSnapshot();
 
     // enumEntries shouldn't have been called since everything
     // is already in the preview property.
     expect(enumEntries.mock.calls).toHaveLength(0);
   });
 
-  it("calls ObjectClient.enumEntries when expected", async () => {
+  it("calls ObjectFront.enumEntries when expected", async () => {
     const stub = gripMapRepStubs.get("testMoreThanMaxEntries");
 
     const { wrapper, store, enumEntries } = mount(
       {
         autoExpandDepth: 1,
         injectWaitService: true,
         roots: [
           {
--- a/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/component/events.js
+++ b/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/component/events.js
@@ -1,27 +1,27 @@
 /* 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/>. */
 
 /* global jest */
 const { mountObjectInspector } = require("../test-utils");
 
 const gripRepStubs = require("../../../reps/stubs/grip");
-const ObjectClient = require("../__mocks__/object-client");
+const ObjectFront = require("../__mocks__/object-front");
 
 function generateDefaults(overrides) {
   return {
     autoExpandDepth: 0,
     ...overrides,
   };
 }
 
 function mount(props) {
-  const client = { createObjectClient: grip => ObjectClient(grip) };
+  const client = { createObjectFront: grip => ObjectFront(grip) };
 
   return mountObjectInspector({
     client,
     props: generateDefaults(props),
   });
 }
 
 describe("ObjectInspector - properties", () => {
--- a/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/component/expand.js
+++ b/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/component/expand.js
@@ -1,16 +1,16 @@
 /* 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/>. */
 const { mountObjectInspector } = require("../test-utils");
 
 const repsPath = "../../../reps";
 const { MODE } = require(`${repsPath}/constants`);
-const ObjectClient = require("../__mocks__/object-client");
+const ObjectFront = require("../__mocks__/object-front");
 const gripRepStubs = require(`${repsPath}/stubs/grip`);
 const gripPropertiesStubs = require("../../stubs/grip");
 const {
   formatObjectInspector,
   storeHasExactExpandedPaths,
   storeHasExpandedPath,
   storeHasLoadedProperty,
   waitForDispatch,
@@ -38,27 +38,27 @@ function generateDefaults(overrides) {
       },
       {
         path: "root-2",
         contents: {
           value: gripRepStubs.get("testProxy"),
         },
       },
     ],
-    createObjectClient: grip => ObjectClient(grip),
+    createObjectFront: grip => ObjectFront(grip),
     mode: MODE.LONG,
     ...overrides,
   };
 }
 const LongStringClientMock = require("../__mocks__/long-string-client");
 
 function mount(props, { initialState } = {}) {
   const client = {
-    createObjectClient: grip =>
-      ObjectClient(grip, {
+    createObjectFront: grip =>
+      ObjectFront(grip, {
         getPrototype: () => Promise.resolve(protoStub),
         getProxySlots: () =>
           Promise.resolve(gripRepStubs.get("testProxySlots")),
       }),
 
     createLongStringClient: grip =>
       LongStringClientMock(grip, {
         substring: function(initiaLength, length, cb) {
--- a/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/component/function.js
+++ b/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/component/function.js
@@ -2,27 +2,27 @@
  * 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/>. */
 
 const { mountObjectInspector } = require("../test-utils");
 const { MODE } = require("../../../reps/constants");
 const { createNode } = require("../../utils/node");
 
 const functionStubs = require("../../../reps/stubs/function");
-const ObjectClient = require("../__mocks__/object-client");
+const ObjectFront = require("../__mocks__/object-front");
 
 function generateDefaults(overrides) {
   return {
     autoExpandDepth: 1,
     ...overrides,
   };
 }
 
 function mount(props) {
-  const client = { createObjectClient: grip => ObjectClient(grip) };
+  const client = { createObjectFront: grip => ObjectFront(grip) };
 
   return mountObjectInspector({
     client,
     props: generateDefaults(props),
   });
 }
 
 describe("ObjectInspector - functions", () => {
--- a/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/component/getter-setter.js
+++ b/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/component/getter-setter.js
@@ -6,29 +6,29 @@ const { MODE } = require("../../../reps/
 const {
   formatObjectInspector,
   waitForLoadedProperties,
   mountObjectInspector,
 } = require("../test-utils");
 
 const { makeNodesForProperties } = require("../../utils/node");
 const accessorStubs = require("../../../reps/stubs/accessor");
-const ObjectClient = require("../__mocks__/object-client");
+const ObjectFront = require("../__mocks__/object-front");
 
 function generateDefaults(overrides) {
   return {
     autoExpandDepth: 1,
-    createObjectClient: grip => ObjectClient(grip),
+    createObjectFront: grip => ObjectFront(grip),
     mode: MODE.LONG,
     ...overrides,
   };
 }
 
 function mount(stub, propsOverride = {}) {
-  const client = { createObjectClient: grip => ObjectClient(grip) };
+  const client = { createObjectFront: grip => ObjectFront(grip) };
 
   const root = { path: "root", name: "root" };
   const nodes = makeNodesForProperties(
     {
       ownProperties: {
         x: stub,
       },
     },
--- a/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/component/keyboard-navigation.js
+++ b/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/component/keyboard-navigation.js
@@ -2,29 +2,29 @@
  * 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/>. */
 
 const { mountObjectInspector } = require("../test-utils");
 const repsPath = "../../../reps";
 const { MODE } = require(`${repsPath}/constants`);
 
 const { formatObjectInspector, waitForDispatch } = require("../test-utils");
-const ObjectClient = require("../__mocks__/object-client");
+const ObjectFront = require("../__mocks__/object-front");
 const gripRepStubs = require(`${repsPath}/stubs/grip`);
 
 function generateDefaults(overrides) {
   return {
     autoExpandDepth: 0,
     mode: MODE.LONG,
     ...overrides,
   };
 }
 
 function mount(props) {
-  const client = { createObjectClient: grip => ObjectClient(grip) };
+  const client = { createObjectFront: grip => ObjectFront(grip) };
 
   return mountObjectInspector({
     client,
     props: generateDefaults(props),
   });
 }
 
 describe("ObjectInspector - keyboard navigation", () => {
--- a/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/component/properties.js
+++ b/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/component/properties.js
@@ -1,41 +1,41 @@
 /* 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/>. */
 
 /* global jest */
 
 const { mountObjectInspector } = require("../test-utils");
 const gripRepStubs = require("../../../reps/stubs/grip");
-const ObjectClient = require("../__mocks__/object-client");
+const ObjectFront = require("../__mocks__/object-front");
 
 const { formatObjectInspector } = require("../test-utils");
 
 function generateDefaults(overrides) {
   return {
     autoExpandDepth: 0,
-    createObjectClient: grip => ObjectClient(grip),
+    createObjectFront: grip => ObjectFront(grip),
     ...overrides,
   };
 }
 
 function getEnumPropertiesMock() {
   return jest.fn(() => ({
     iterator: {
       slice: () => ({}),
     },
   }));
 }
 
 function mount(props, { initialState } = {}) {
   const enumProperties = getEnumPropertiesMock();
 
   const client = {
-    createObjectClient: grip => ObjectClient(grip, { enumProperties }),
+    createObjectFront: grip => ObjectFront(grip, { enumProperties }),
   };
 
   const obj = mountObjectInspector({
     client,
     props: generateDefaults(props),
     initialState,
   });
 
@@ -78,17 +78,17 @@ describe("ObjectInspector - properties",
       roots: [
         {
           path: "root",
           contents: {
             value: stub,
           },
         },
       ],
-      createObjectClient: grip => ObjectClient(grip, { enumProperties }),
+      createObjectFront: grip => ObjectFront(grip, { enumProperties }),
     });
 
     const node = wrapper.find(".node");
     node.simulate("click");
 
     // The function is called twice, to get both non-indexed and indexed props.
     expect(enumProperties.mock.calls).toHaveLength(2);
     expect(enumProperties.mock.calls[0][0]).toEqual({
--- a/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/component/proxy.js
+++ b/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/component/proxy.js
@@ -6,17 +6,17 @@
 const { mountObjectInspector } = require("../test-utils");
 
 const { MODE } = require("../../../reps/constants");
 const gripStubs = require("../../../reps/stubs/grip");
 const stub = gripStubs.get("testProxy");
 const proxySlots = gripStubs.get("testProxySlots");
 const { formatObjectInspector } = require("../test-utils");
 
-const ObjectClient = require("../__mocks__/object-client");
+const ObjectFront = require("../__mocks__/object-front");
 function generateDefaults(overrides) {
   return {
     roots: [
       {
         path: "root",
         contents: { value: stub },
       },
     ],
@@ -38,18 +38,18 @@ function getProxySlotsMock() {
   return jest.fn(() => proxySlots);
 }
 
 function mount(props, { initialState } = {}) {
   const enumProperties = getEnumPropertiesMock();
   const getProxySlots = getProxySlotsMock();
 
   const client = {
-    createObjectClient: grip =>
-      ObjectClient(grip, { enumProperties, getProxySlots }),
+    createObjectFront: grip =>
+      ObjectFront(grip, { enumProperties, getProxySlots }),
   };
 
   const obj = mountObjectInspector({
     client,
     props: generateDefaults(props),
     initialState,
   });
 
--- a/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/component/release-actors.js
+++ b/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/component/release-actors.js
@@ -2,17 +2,17 @@
  * 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/>. */
 
 /* global jest */
 const { mountObjectInspector } = require("../test-utils");
 
 const repsPath = "../../../reps";
 const gripRepStubs = require(`${repsPath}/stubs/grip`);
-const ObjectClient = require("../__mocks__/object-client");
+const ObjectFront = require("../__mocks__/object-front");
 const stub = gripRepStubs.get("testMoreThanMaxProps");
 const { waitForDispatch } = require("../test-utils");
 
 function getEnumPropertiesMock() {
   return jest.fn(() => ({
     iterator: {
       slice: () => ({}),
     },
@@ -33,17 +33,17 @@ function generateDefaults(overrides) {
     ...overrides,
   };
 }
 
 function mount(props, { initialState } = {}) {
   const enumProperties = getEnumPropertiesMock();
 
   const client = {
-    createObjectClient: grip => ObjectClient(grip, { enumProperties }),
+    createObjectFront: grip => ObjectFront(grip, { enumProperties }),
     releaseActor: jest.fn(),
   };
 
   return mountObjectInspector({
     client,
     props: generateDefaults(props),
     initialState: {
       objectInspector: {
--- a/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/component/should-item-update.js
+++ b/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/component/should-item-update.js
@@ -1,33 +1,33 @@
 /* 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/>. */
 
 /* global jest */
 
 const { mountObjectInspector } = require("../test-utils");
-const ObjectClient = require("../__mocks__/object-client");
+const ObjectFront = require("../__mocks__/object-front");
 const LongStringClient = require("../__mocks__/long-string-client");
 
 const repsPath = "../../../reps";
 const longStringStubs = require(`${repsPath}/stubs/long-string`);
 const gripStubs = require(`${repsPath}/stubs/grip`);
 
 function mount(stub) {
   const root = {
     path: "root",
     contents: {
       value: stub,
     },
   };
 
   const { wrapper } = mountObjectInspector({
     client: {
-      createObjectClient: grip => ObjectClient(grip),
+      createObjectFront: grip => ObjectFront(grip),
       createLongStringClient: grip => LongStringClient(grip),
     },
     props: {
       roots: [root],
     },
   });
 
   return { wrapper, root };
--- a/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/component/window.js
+++ b/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/component/window.js
@@ -1,23 +1,23 @@
 /* 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/>. */
 
 const { createNode } = require("../../utils/node");
 const { waitForDispatch, mountObjectInspector } = require("../test-utils");
 
 const gripWindowStubs = require("../../../reps/stubs/window");
-const ObjectClient = require("../__mocks__/object-client");
+const ObjectFront = require("../__mocks__/object-front");
 const windowNode = createNode({
   name: "window",
   contents: { value: gripWindowStubs.get("Window") },
 });
 
-const client = { createObjectClient: grip => ObjectClient(grip) };
+const client = { createObjectFront: grip => ObjectFront(grip) };
 
 function generateDefaults(overrides) {
   return {
     autoExpandDepth: 0,
     roots: [windowNode],
     ...overrides,
   };
 }
--- a/devtools/client/debugger/packages/devtools-reps/src/object-inspector/types.js
+++ b/devtools/client/debugger/packages/devtools-reps/src/object-inspector/types.js
@@ -64,17 +64,17 @@ export type RdpGrip = {
   type: string,
 };
 
 export type PropertiesIterator = {
   count: number,
   slice: (start: number, count: number) => Promise<GripProperties>,
 };
 
-export type ObjectClient = {
+export type ObjectFront = {
   enumEntries: () => Promise<PropertiesIterator>,
   enumProperties: (options: Object) => Promise<PropertiesIterator>,
   enumSymbols: () => Promise<PropertiesIterator>,
   getPrototype: () => Promise<{ prototype: Object }>,
   getProxySlots: () => Promise<{ proxyTarget: Object, proxyHandler: Object }>,
 };
 
 export type LongStringClient = {
@@ -84,17 +84,17 @@ export type LongStringClient = {
     response: {
       substring?: string,
       error?: Error,
       message?: string,
     }
   ) => void,
 };
 
-export type CreateObjectClient = RdpGrip => ObjectClient;
+export type CreateObjectFront = RdpGrip => ObjectFront;
 
 export type CreateLongStringClient = RdpGrip => LongStringClient;
 
 export type CachedNodes = Map<Path, Array<Node>>;
 
 export type LoadedProperties = Map<Path, GripProperties>;
 
 export type Evaluations = Map<
@@ -112,17 +112,17 @@ export type Props = {
   focusable: boolean,
   itemHeight: number,
   inline: boolean,
   mode: Mode,
   roots: Array<Node>,
   disableWrap: boolean,
   dimTopLevelWindow: boolean,
   releaseActor: string => void,
-  createObjectClient: CreateObjectClient,
+  createObjectFront: CreateObjectFront,
   createLongStringClient: CreateLongStringClient,
   onFocus: ?(Node) => any,
   onActivate: ?(Node) => any,
   onDoubleClick: ?(
     item: Node,
     options: {
       depth: number,
       focused: boolean,
--- a/devtools/client/debugger/packages/devtools-reps/src/object-inspector/utils/client.js
+++ b/devtools/client/debugger/packages/devtools-reps/src/object-inspector/utils/client.js
@@ -1,95 +1,95 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
 
 // @flow
 import type {
   GripProperties,
-  ObjectClient,
+  ObjectFront,
   PropertiesIterator,
   Node,
   LongStringClient,
 } from "../types";
 
 const { getValue, nodeHasFullText } = require("../utils/node");
 
 async function enumIndexedProperties(
-  objectClient: ObjectClient,
+  objectFront: ObjectFront,
   start: ?number,
   end: ?number
 ): Promise<{ ownProperties?: Object }> {
   try {
-    const { iterator } = await objectClient.enumProperties({
+    const iterator = await objectFront.enumProperties({
       ignoreNonIndexedProperties: true,
     });
     const response = await iteratorSlice(iterator, start, end);
     return response;
   } catch (e) {
     console.error("Error in enumIndexedProperties", e);
     return {};
   }
 }
 
 async function enumNonIndexedProperties(
-  objectClient: ObjectClient,
+  objectFront: ObjectFront,
   start: ?number,
   end: ?number
 ): Promise<{ ownProperties?: Object }> {
   try {
-    const { iterator } = await objectClient.enumProperties({
+    const iterator = await objectFront.enumProperties({
       ignoreIndexedProperties: true,
     });
     const response = await iteratorSlice(iterator, start, end);
     return response;
   } catch (e) {
     console.error("Error in enumNonIndexedProperties", e);
     return {};
   }
 }
 
 async function enumEntries(
-  objectClient: ObjectClient,
+  objectFront: ObjectFront,
   start: ?number,
   end: ?number
 ): Promise<{ ownProperties?: Object }> {
   try {
-    const { iterator } = await objectClient.enumEntries();
+    const iterator = await objectFront.enumEntries();
     const response = await iteratorSlice(iterator, start, end);
     return response;
   } catch (e) {
     console.error("Error in enumEntries", e);
     return {};
   }
 }
 
 async function enumSymbols(
-  objectClient: ObjectClient,
+  objectFront: ObjectFront,
   start: ?number,
   end: ?number
 ): Promise<{ ownSymbols?: Array<Object> }> {
   try {
-    const { iterator } = await objectClient.enumSymbols();
+    const iterator = await objectFront.enumSymbols();
     const response = await iteratorSlice(iterator, start, end);
     return response;
   } catch (e) {
     console.error("Error in enumSymbols", e);
     return {};
   }
 }
 
 async function getPrototype(
-  objectClient: ObjectClient
+  objectFront: ObjectFront
 ): ?Promise<{ prototype?: Object }> {
-  if (typeof objectClient.getPrototype !== "function") {
-    console.error("objectClient.getPrototype is not a function");
+  if (typeof objectFront.getPrototype !== "function") {
+    console.error("objectFront.getPrototype is not a function");
     return Promise.resolve({});
   }
-  return objectClient.getPrototype();
+  return objectFront.getPrototype();
 }
 
 async function getFullText(
   longStringClient: LongStringClient,
   item: Node
 ): Promise<{ fullText?: string }> {
   const { initial, fullText, length } = getValue(item);
 
@@ -113,19 +113,19 @@ async function getFullText(
       resolve({
         fullText: initial + response.substring,
       });
     });
   });
 }
 
 async function getProxySlots(
-  objectClient: ObjectClient
+  objectFront: ObjectFront
 ): Promise<{ proxyTarget?: Object, proxyHandler?: Object }> {
-  return objectClient.getProxySlots();
+  return objectFront.getProxySlots();
 }
 
 function iteratorSlice(
   iterator: PropertiesIterator,
   start: ?number,
   end: ?number
 ): Promise<GripProperties> {
   start = start || 0;
--- a/devtools/client/debugger/packages/devtools-reps/src/object-inspector/utils/load-properties.js
+++ b/devtools/client/debugger/packages/devtools-reps/src/object-inspector/utils/load-properties.js
@@ -26,70 +26,80 @@ const {
   nodeIsPrimitive,
   nodeIsProxy,
   nodeNeedsNumericalBuckets,
   nodeIsLongString,
 } = require("./node");
 
 import type {
   CreateLongStringClient,
-  CreateObjectClient,
+  CreateObjectFront,
   GripProperties,
   LoadedProperties,
   Node,
 } from "../types";
 
 type Client = {
-  createObjectClient: CreateObjectClient,
+  createObjectFront: CreateObjectFront,
   createLongStringClient: CreateLongStringClient,
 };
 
 function loadItemProperties(
   item: Node,
   client: Client,
   loadedProperties: LoadedProperties
 ): Promise<GripProperties> {
   const gripItem = getClosestGripNode(item);
   const value = getValue(gripItem);
 
   const [start, end] = item.meta
     ? [item.meta.startIndex, item.meta.endIndex]
     : [];
 
   const promises = [];
-  let objectClient;
-  const getObjectClient = () =>
-    objectClient || client.createObjectClient(value);
+  let objectFront;
+
+  if (value && client && client.getFrontByID) {
+    objectFront = client.getFrontByID(value.actor);
+  }
+
+  const getObjectFront = function() {
+    if (!objectFront) {
+      objectFront = client.createObjectFront(value);
+    }
+
+    return objectFront;
+  };
 
   if (shouldLoadItemIndexedProperties(item, loadedProperties)) {
-    promises.push(enumIndexedProperties(getObjectClient(), start, end));
+    promises.push(enumIndexedProperties(getObjectFront(), start, end));
   }
 
   if (shouldLoadItemNonIndexedProperties(item, loadedProperties)) {
-    promises.push(enumNonIndexedProperties(getObjectClient(), start, end));
+    promises.push(enumNonIndexedProperties(getObjectFront(), start, end));
   }
 
   if (shouldLoadItemEntries(item, loadedProperties)) {
-    promises.push(enumEntries(getObjectClient(), start, end));
+    promises.push(enumEntries(getObjectFront(), start, end));
   }
 
   if (shouldLoadItemPrototype(item, loadedProperties)) {
-    promises.push(getPrototype(getObjectClient()));
+    promises.push(getPrototype(getObjectFront()));
   }
 
   if (shouldLoadItemSymbols(item, loadedProperties)) {
-    promises.push(enumSymbols(getObjectClient(), start, end));
+    promises.push(enumSymbols(getObjectFront(), start, end));
   }
 
   if (shouldLoadItemFullText(item, loadedProperties)) {
     promises.push(getFullText(client.createLongStringClient(value), item));
   }
 
   if (shouldLoadItemProxySlots(item, loadedProperties)) {
-    promises.push(getProxySlots(getObjectClient()));
+    promises.push(getProxySlots(getObjectFront()));
   }
 
   return Promise.all(promises).then(mergeResponses);
 }
 
 function mergeResponses(responses: Array<Object>): Object {
   const data = {};
 
--- a/devtools/client/debugger/panel.js
+++ b/devtools/client/debugger/panel.js
@@ -109,28 +109,32 @@ DebuggerPanel.prototype = {
     const onNodeFrontSet = this.toolbox.selection.setNodeFront(front, {
       reason: "debugger",
     });
 
     return Promise.all([onNodeFrontSet, onInspectorUpdated]);
   },
 
   highlightDomElement: async function(gripOrFront) {
-    const nodeFront = await getNodeFront(gripOrFront, this.toolbox);
-    nodeFront.highlighterFront.highlight(nodeFront);
+    if (!this._highlight) {
+      const { highlight, unhighlight } = this.toolbox.getHighlighter();
+      this._highlight = highlight;
+      this._unhighlight = unhighlight;
+    }
+
+    return this._highlight(gripOrFront);
   },
 
-  unHighlightDomElement: async function(gripOrFront) {
-    try {
-      const nodeFront = await getNodeFront(gripOrFront, this.toolbox);
-      nodeFront.highlighterFront.unhighlight();
-    } catch (e) {
-      // This call might fail if called asynchrously after the toolbox is finished
-      // closing.
+  unHighlightDomElement: function() {
+    if (!this._unhighlight) {
+      return;
     }
+
+    const forceUnHighlightInTest = true;
+    return this._unhighlight(forceUnHighlightInTest);
   },
 
   getFrames: function() {
     const thread = this._selectors.getCurrentThread(this._getState());
     const frames = this._selectors.getFrames(this._getState(), thread);
 
     // Frames is null when the debugger is not paused.
     if (!frames) {
--- a/devtools/client/debugger/src/client/firefox.js
+++ b/devtools/client/debugger/src/client/firefox.js
@@ -5,18 +5,18 @@
 // @flow
 
 import { setupCommands, clientCommands } from "./firefox/commands";
 import { setupEvents, clientEvents } from "./firefox/events";
 import { features, prefs } from "../utils/prefs";
 import type { Grip } from "../types";
 let DebuggerClient;
 
-function createObjectClient(grip: Grip) {
-  return DebuggerClient.createObjectClient(grip);
+function createObjectFront(grip: Grip) {
+  return DebuggerClient.createObjectFront(grip);
 }
 
 export async function onConnect(connection: any, actions: Object) {
   const {
     tabConnection: { tabTarget, threadFront, debuggerClient },
   } = connection;
 
   DebuggerClient = debuggerClient;
@@ -71,9 +71,9 @@ export async function onConnect(connecti
   // bfcache) so explicity fire `newSource` events for all returned
   // sources.
   const sources = await clientCommands.fetchSources();
   await actions.newGeneratedSources(sources);
 
   await clientCommands.checkIfAlreadyPaused();
 }
 
-export { createObjectClient, clientCommands, clientEvents };
+export { createObjectFront, clientCommands, clientEvents };
--- a/devtools/client/debugger/src/client/firefox/commands.js
+++ b/devtools/client/debugger/src/client/firefox/commands.js
@@ -27,17 +27,17 @@ import type {
   ThreadType,
 } from "../../types";
 
 import type {
   Target,
   DebuggerClient,
   Grip,
   ThreadFront,
-  ObjectClient,
+  ObjectFront,
   SourcesPacket,
 } from "./types";
 
 import type {
   EventListenerCategoryList,
   EventListenerActiveList,
 } from "../../actions/types";
 
@@ -59,18 +59,18 @@ function setupCommands(dependencies: Dep
   currentThreadFront = dependencies.threadFront;
   currentTarget = dependencies.tabTarget;
   debuggerClient = dependencies.debuggerClient;
   targets = { worker: {}, contentProcess: {} };
   sourceActors = {};
   breakpoints = {};
 }
 
-function createObjectClient(grip: Grip) {
-  return debuggerClient.createObjectClient(grip);
+function createObjectFront(grip: Grip) {
+  return debuggerClient.createObjectFront(grip);
 }
 
 async function loadObjectProperties(root: Node) {
   const utils = Reps.objectInspector.utils;
   const properties = await utils.loadProperties.loadItemProperties(
     root,
     debuggerClient
   );
@@ -188,25 +188,25 @@ function removeXHRBreakpoint(path: strin
 
 function addWatchpoint(
   object: Grip,
   property: string,
   label: string,
   watchpointType: string
 ) {
   if (currentTarget.traits.watchpoints) {
-    const objectClient = createObjectClient(object);
-    return objectClient.addWatchpoint(property, label, watchpointType);
+    const objectFront = createObjectFront(object);
+    return objectFront.addWatchpoint(property, label, watchpointType);
   }
 }
 
 function removeWatchpoint(object: Grip, property: string) {
   if (currentTarget.traits.watchpoints) {
-    const objectClient = createObjectClient(object);
-    return objectClient.removeWatchpoint(property);
+    const objectFront = createObjectFront(object);
+    return objectFront.removeWatchpoint(property);
   }
 }
 
 // Get the string key to use for a breakpoint location.
 // See also duplicate code in breakpoint-actor-map.js :(
 function locationKey(location: BreakpointLocation) {
   const { sourceUrl, line, column } = location;
   const sourceId = location.sourceId || "";
@@ -396,17 +396,17 @@ async function getEventListenerBreakpoin
       categories = null;
     }
   } catch (err) {
     // Event bps aren't supported on this firefox version.
   }
   return categories || [];
 }
 
-function pauseGrip(thread: string, func: Function): ObjectClient {
+function pauseGrip(thread: string, func: Function): ObjectFront {
   return lookupThreadFront(thread).pauseGrip(func);
 }
 
 function registerSourceActor(sourceActorId: string, sourceId: SourceId) {
   sourceActors[sourceActorId] = sourceId;
 }
 
 async function getSources(
@@ -543,17 +543,17 @@ async function getSourceActorBreakableLi
 
 function getFrontByID(actorID: String) {
   return debuggerClient.getFrontByID(actorID);
 }
 
 const clientCommands = {
   autocomplete,
   blackBox,
-  createObjectClient,
+  createObjectFront,
   loadObjectProperties,
   releaseActor,
   interrupt,
   pauseGrip,
   resume,
   stepIn,
   stepOut,
   stepOver,
--- a/devtools/client/debugger/src/client/firefox/types.js
+++ b/devtools/client/debugger/src/client/firefox/types.js
@@ -255,17 +255,17 @@ export type DebuggerClient = {
     traits: any,
     getFront: string => Promise<*>,
     listProcesses: () => Promise<{ processes: ProcessDescriptor }>,
     on: (string, Function) => void,
   },
   connect: () => Promise<*>,
   request: (packet: Object) => Promise<*>,
   attachConsole: (actor: String, listeners: Array<*>) => Promise<*>,
-  createObjectClient: (grip: Grip) => ObjectClient,
+  createObjectFront: (grip: Grip) => ObjectFront,
   release: (actor: String) => {},
   getFrontByID: (actor: String) => { release: () => Promise<*> },
 };
 
 type ProcessDescriptor = Object;
 
 /**
  * A grip is a JSON value that refers to a specific JavaScript value in the
@@ -328,21 +328,21 @@ export type SourceClient = {
   prettyPrint: number => Promise<*>,
   disablePrettyPrint: () => Promise<*>,
   blackBox: (range?: Range) => Promise<*>,
   unblackBox: (range?: Range) => Promise<*>,
   getBreakableLines: () => Promise<number[]>,
 };
 
 /**
- * ObjectClient
+ * ObjectFront
  * @memberof firefox
  * @static
  */
-export type ObjectClient = {
+export type ObjectFront = {
   getPrototypeAndProperties: () => any,
   addWatchpoint: (
     property: string,
     label: string,
     watchpointType: string
   ) => {},
   removeWatchpoint: (property: string) => {},
 };
@@ -357,17 +357,17 @@ export type ThreadFront = {
   stepIn: Function => Promise<*>,
   stepOver: Function => Promise<*>,
   stepOut: Function => Promise<*>,
   rewind: Function => Promise<*>,
   reverseStepOver: Function => Promise<*>,
   breakOnNext: () => Promise<*>,
   // FIXME: unclear if SourceId or ActorId here
   source: ({ actor: SourceId }) => SourceClient,
-  pauseGrip: (Grip | Function) => ObjectClient,
+  pauseGrip: (Grip | Function) => ObjectFront,
   pauseOnExceptions: (boolean, boolean) => Promise<*>,
   setBreakpoint: (BreakpointLocation, BreakpointOptions) => Promise<*>,
   removeBreakpoint: PendingLocation => Promise<*>,
   setXHRBreakpoint: (path: string, method: string) => Promise<boolean>,
   removeXHRBreakpoint: (path: string, method: string) => Promise<boolean>,
   interrupt: () => Promise<*>,
   eventListeners: () => Promise<*>,
   getFrames: (number, number) => FramesResponse,
--- a/devtools/client/debugger/src/components/Editor/Footer.css
+++ b/devtools/client/debugger/src/components/Editor/Footer.css
@@ -54,22 +54,16 @@
 :root.theme-dark .source-footer .commands .action {
   fill: var(--theme-body-color);
 }
 
 :root.theme-dark .source-footer .commands .action:hover {
   fill: var(--theme-selection-color);
 }
 
-.source-footer .commands div.loader {
-  vertical-align: top;
-  width: 20px;
-  margin: 0 4px;
-}
-
 .source-footer .blackboxed .img.blackBox {
   background-color: var(--theme-icon-checked-color);
 }
 
 .source-footer .mapped-source,
 .source-footer .cursor-position {
   color: var(--theme-body-color);
   padding-right: 2.5px;
--- a/devtools/client/debugger/src/components/Editor/Footer.js
+++ b/devtools/client/debugger/src/components/Editor/Footer.js
@@ -97,18 +97,18 @@ class SourceFooter extends PureComponent
     } = this.props;
 
     if (!selectedSource) {
       return;
     }
 
     if (!selectedSource.content && selectedSource.isPrettyPrinted) {
       return (
-        <div className="loader" key="pretty-loader">
-          <AccessibleImage className="loader" />
+        <div className="action" key="pretty-loader">
+          <AccessibleImage className="loader spin" />
         </div>
       );
     }
 
     if (!canPrettyPrint) {
       return;
     }
 
--- a/devtools/client/debugger/src/components/Editor/Preview/Popup.js
+++ b/devtools/client/debugger/src/components/Editor/Preview/Popup.js
@@ -20,17 +20,17 @@ const {
   node: { nodeIsPrimitive, nodeIsFunction, nodeIsObject },
 } = utils;
 
 import actions from "../../../actions";
 import { getThreadContext } from "../../../selectors";
 import Popover from "../../shared/Popover";
 import PreviewFunction from "../../shared/PreviewFunction";
 
-import { createObjectClient } from "../../../client/firefox";
+import { createObjectFront } from "../../../client/firefox";
 
 import "./Popup.css";
 
 import type { ThreadContext } from "../../../types";
 import type { Preview } from "../../../reducers/types";
 
 type OwnProps = {|
   editor: any,
@@ -135,17 +135,17 @@ export class Popup extends Component<Pro
         style={{ maxHeight: this.calculateMaxHeight() }}
       >
         <ObjectInspector
           roots={properties}
           autoExpandDepth={0}
           disableWrap={true}
           focusable={false}
           openLink={openLink}
-          createObjectClient={grip => createObjectClient(grip)}
+          createObjectFront={grip => createObjectFront(grip)}
           onDOMNodeClick={grip => openElementInInspector(grip)}
           onInspectIconClick={grip => openElementInInspector(grip)}
           onDOMNodeMouseOver={grip => highlightDomElement(grip)}
           onDOMNodeMouseOut={grip => unHighlightDomElement(grip)}
         />
       </div>
     );
   }
--- a/devtools/client/debugger/src/components/ProjectSearch.css
+++ b/devtools/client/debugger/src/components/ProjectSearch.css
@@ -84,36 +84,40 @@
   overflow-wrap: break-word;
   hyphens: auto;
 }
 
 .project-text-search .file-result {
   grid-column: 1/3;
   display: flex;
   align-items: center;
-  font-weight: bold;
-  cursor: default;
+  width: 100%;
   min-height: 24px;
   padding: 2px 4px;
+  font-weight: bold;
   font-size: 12px;
-  line-height: calc(16 / 12);
+  line-height: 16px;
+  cursor: default;
 }
 
 .project-text-search .file-result .img {
   margin-inline: 2px;
 }
 
 .project-text-search .file-result .img.file {
   margin-inline-end: 4px;
 }
 
 .project-text-search .file-path {
   flex: 0 1 auto;
-  white-space: normal;
   padding-inline-end: 4px;
+  display: block;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
 }
 
 .project-text-search .file-path:empty {
   display: none;
 }
 
 .project-text-search .search-field {
   display: flex;
--- a/devtools/client/debugger/src/components/SecondaryPanes/Expressions.js
+++ b/devtools/client/debugger/src/components/SecondaryPanes/Expressions.js
@@ -14,17 +14,17 @@ import { objectInspector } from "devtool
 import actions from "../../actions";
 import {
   getExpressions,
   getExpressionError,
   getAutocompleteMatchset,
   getThreadContext,
 } from "../../selectors";
 import { getValue } from "../../utils/expressions";
-import { createObjectClient } from "../../client/firefox";
+import { createObjectFront } from "../../client/firefox";
 
 import { CloseButton } from "../shared/Button";
 import { debounce } from "lodash";
 
 import type { List } from "immutable";
 import type { Expression, ThreadContext } from "../../types";
 
 import "./Expressions.css";
@@ -269,17 +269,17 @@ class Expressions extends Component<Prop
         }
       >
         <div className="expression-content">
           <ObjectInspector
             roots={[root]}
             autoExpandDepth={0}
             disableWrap={true}
             openLink={openLink}
-            createObjectClient={grip => createObjectClient(grip)}
+            createObjectFront={grip => createObjectFront(grip)}
             onDOMNodeClick={grip => openElementInInspector(grip)}
             onInspectIconClick={grip => openElementInInspector(grip)}
             onDOMNodeMouseOver={grip => highlightDomElement(grip)}
             onDOMNodeMouseOut={grip => unHighlightDomElement(grip)}
           />
           <div className="expression-container__close-btn">
             <CloseButton
               handleClick={e => this.deleteExpression(e, expression)}
--- a/devtools/client/debugger/src/components/SecondaryPanes/Scopes.js
+++ b/devtools/client/debugger/src/components/SecondaryPanes/Scopes.js
@@ -2,17 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
 
 // @flow
 import React, { PureComponent } from "react";
 import { showMenu } from "devtools-contextmenu";
 import { connect } from "../../utils/connect";
 import actions from "../../actions";
-import { createObjectClient } from "../../client/firefox";
+import { createObjectFront } from "../../client/firefox";
 import { features } from "../../utils/prefs";
 
 import {
   getSelectedSource,
   getSelectedFrame,
   getGeneratedFrameScope,
   getOriginalFrameScope,
   getPauseReason,
@@ -193,17 +193,17 @@ class Scopes extends PureComponent<Props
         <div className="pane scopes-list">
           <ObjectInspector
             roots={scopes}
             autoExpandAll={false}
             autoExpandDepth={1}
             disableWrap={true}
             dimTopLevelWindow={true}
             openLink={openLink}
-            createObjectClient={grip => createObjectClient(grip)}
+            createObjectFront={grip => createObjectFront(grip)}
             onDOMNodeClick={grip => openElementInInspector(grip)}
             onInspectIconClick={grip => openElementInInspector(grip)}
             onDOMNodeMouseOver={grip => highlightDomElement(grip)}
             onDOMNodeMouseOut={grip => unHighlightDomElement(grip)}
             onContextMenu={this.onContextMenu}
             setExpanded={(path, expand) => setExpandedScope(cx, path, expand)}
             initiallyExpanded={initiallyExpanded}
           />
--- a/devtools/client/debugger/src/components/SecondaryPanes/tests/__snapshots__/Expressions.spec.js.snap
+++ b/devtools/client/debugger/src/components/SecondaryPanes/tests/__snapshots__/Expressions.spec.js.snap
@@ -10,17 +10,17 @@ exports[`Expressions should always have 
     onDoubleClick={[Function]}
     title="expression1"
   >
     <div
       className="expression-content"
     >
       <Component
         autoExpandDepth={0}
-        createObjectClient={[Function]}
+        createObjectFront={[Function]}
         disableWrap={true}
         onDOMNodeClick={[Function]}
         onDOMNodeMouseOut={[Function]}
         onDOMNodeMouseOver={[Function]}
         onInspectIconClick={[Function]}
         roots={
           Array [
             Object {
@@ -52,17 +52,17 @@ exports[`Expressions should always have 
     onDoubleClick={[Function]}
     title="expression2"
   >
     <div
       className="expression-content"
     >
       <Component
         autoExpandDepth={0}
-        createObjectClient={[Function]}
+        createObjectFront={[Function]}
         disableWrap={true}
         onDOMNodeClick={[Function]}
         onDOMNodeMouseOut={[Function]}
         onDOMNodeMouseOver={[Function]}
         onInspectIconClick={[Function]}
         roots={
           Array [
             Object {
@@ -101,17 +101,17 @@ exports[`Expressions should render 1`] =
     onDoubleClick={[Function]}
     title="expression1"
   >
     <div
       className="expression-content"
     >
       <Component
         autoExpandDepth={0}
-        createObjectClient={[Function]}
+        createObjectFront={[Function]}
         disableWrap={true}
         onDOMNodeClick={[Function]}
         onDOMNodeMouseOut={[Function]}
         onDOMNodeMouseOver={[Function]}
         onInspectIconClick={[Function]}
         roots={
           Array [
             Object {
@@ -143,17 +143,17 @@ exports[`Expressions should render 1`] =
     onDoubleClick={[Function]}
     title="expression2"
   >
     <div
       className="expression-content"
     >
       <Component
         autoExpandDepth={0}
-        createObjectClient={[Function]}
+        createObjectFront={[Function]}
         disableWrap={true}
         onDOMNodeClick={[Function]}
         onDOMNodeMouseOut={[Function]}
         onDOMNodeMouseOver={[Function]}
         onInspectIconClick={[Function]}
         roots={
           Array [
             Object {
--- a/devtools/client/debugger/src/components/shared/AccessibleImage.css
+++ b/devtools/client/debugger/src/components/shared/AccessibleImage.css
@@ -77,17 +77,16 @@ html[dir="rtl"] .img.arrow {
 
 .img.globe-small {
   mask-image: url(resource://devtools/client/debugger/images/globe-small.svg);
   mask-size: 12px 12px;
 }
 
 .img.window {
   mask-image: url(resource://devtools/client/debugger/images/window.svg);
-  mask-size: 12px 12px;
 }
 
 .img.file {
   mask-image: url(resource://devtools/client/debugger/images/file-small.svg);
   mask-size: 12px 12px;
 }
 
 .img.folder {
@@ -166,16 +165,20 @@ html[dir="rtl"] .img.more-tabs {
 .img.search {
   mask-image: url(resource://devtools/client/debugger/images/search.svg);
 }
 
 .img.shortcuts {
   mask-image: url(resource://devtools/client/debugger/images/help.svg);
 }
 
+.img.spin {
+  animation: spin 0.5s linear infinite;
+}
+
 .img.stepIn {
   mask-image: url(resource://devtools/client/debugger/images/stepIn.svg);
 }
 
 .img.stepOut {
   mask-image: url(resource://devtools/client/debugger/images/stepOut.svg);
 }
 
--- a/devtools/client/debugger/src/components/shared/SearchInput.css
+++ b/devtools/client/debugger/src/components/shared/SearchInput.css
@@ -1,21 +1,12 @@
 /* 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/>. */
 
-@keyframes search-loader-rotate {
-  from {
-    transform: rotate(0deg);
-  }
-  to {
-    transform: rotate(360deg);
-  }
-}
-
 .search-outline {
   border: 1px solid var(--theme-toolbar-background);
   border-bottom: 1px solid var(--theme-splitter-color);
   transition: border-color 200ms ease-in-out;
 }
 
 .search-outline:focus-within {
   border-color: var(--blue-50);
@@ -54,17 +45,16 @@
 [dir="rtl"] .search-field .img.search {
   right: var(--icon-inset-inline-start);
 }
 
 .search-field .img.loader {
   width: 24px;
   height: 24px;
   margin-inline-end: 4px;
-  animation: search-loader-rotate 0.5s linear infinite;
 }
 
 .search-field input {
   align-self: stretch;
   flex-grow: 1;
   height: 24px;
   width: 40px;
   border: none;
--- a/devtools/client/debugger/src/components/shared/SearchInput.js
+++ b/devtools/client/debugger/src/components/shared/SearchInput.js
@@ -193,17 +193,17 @@ class SearchInput extends Component<Prop
     }
 
     return <div className="search-field-summary">{summaryMsg}</div>;
   }
 
   renderSpinner() {
     const { isLoading } = this.props;
     if (isLoading) {
-      return <AccessibleImage className="loader" />;
+      return <AccessibleImage className="loader spin" />;
     }
   }
 
   renderNav() {
     const { count, handleNext, handlePrev } = this.props;
     if ((!handleNext && !handlePrev) || (!count || count == 1)) {
       return;
     }
--- a/devtools/client/debugger/src/components/variables.css
+++ b/devtools/client/debugger/src/components/variables.css
@@ -31,8 +31,15 @@
 :root .theme-dark {
   --search-overlays-semitransparent: rgba(42, 46, 56, 0.66);
   --popup-shadow-color: #5c667b;
   --theme-inline-preview-background: rgba(192, 105, 255, 0.05);
   --theme-inline-preview-border-color: #47326c;
   --theme-inline-preview-label-color: #dfccff;
   --theme-inline-preview-label-background: #3f2e5f;
 }
+
+/* Animations */
+
+@keyframes spin {
+  from { transform: rotate(0deg); }
+  to { transform: rotate(360deg); }
+}
--- a/devtools/client/debugger/src/utils/pause/mapScopes/buildGeneratedBindingList.js
+++ b/devtools/client/debugger/src/utils/pause/mapScopes/buildGeneratedBindingList.js
@@ -2,17 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
 
 // @flow
 
 import { has } from "lodash";
 import type { SourceScope, BindingLocation } from "../../../workers/parser";
 import type { Scope, BindingContents } from "../../../types";
-import { createObjectClient } from "../../../client/firefox";
+import { createObjectFront } from "../../../client/firefox";
 
 import { locColumn } from "./locColumn";
 
 export type GeneratedBindingLocation = {
   name: string,
   loc: BindingLocation,
   desc: () => Promise<BindingContents | null>,
 };
@@ -97,18 +97,18 @@ export function buildGeneratedBindingLis
           const globalGrip = globalScope && globalScope.object;
           if (globalGrip) {
             // Should always exist, just checking to keep Flow happy.
 
             generatedBindings.push({
               name,
               loc,
               desc: async () => {
-                const objectClient = createObjectClient(globalGrip);
-                return (await objectClient.getProperty(name)).descriptor;
+                const objectFront = createObjectFront(globalGrip);
+                return (await objectFront.getProperty(name)).descriptor;
               },
             });
           }
         }
       }
     }
   }
 
--- a/devtools/client/debugger/src/utils/pause/mapScopes/findGeneratedBindingFromPosition.js
+++ b/devtools/client/debugger/src/utils/pause/mapScopes/findGeneratedBindingFromPosition.js
@@ -6,17 +6,17 @@
 
 import { locColumn } from "./locColumn";
 import { mappingContains } from "./mappingContains";
 
 import type { BindingContents } from "../../../types";
 // eslint-disable-next-line max-len
 import type { ApplicableBinding } from "./getApplicableBindingsForOriginalPosition";
 
-import { createObjectClient } from "../../../client/firefox";
+import { createObjectFront } from "../../../client/firefox";
 
 export type GeneratedDescriptor = {
   name: string,
   // Falsy if the binding itself matched a location, but the location didn't
   // have a value descriptor attached. Happens if the binding was 'this'
   // or if there was a mismatch between client and generated scopes.
   desc: ?BindingContents,
 
@@ -319,11 +319,11 @@ async function readDescriptorProperty(
   }
 
   if (!isObjectValue(desc)) {
     // If we got a non-primitive descriptor but it isn't an object, then
     // it's definitely not the namespace and it is probably an error.
     return desc;
   }
 
-  const objectClient = createObjectClient(desc.value);
-  return (await objectClient.getProperty(property)).descriptor;
+  const objectFront = createObjectFront(desc.value);
+  return (await objectFront.getProperty(property)).descriptor;
 }
--- a/devtools/client/debugger/test/mochitest/browser_dbg-inspector-integration.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg-inspector-integration.js
@@ -1,55 +1,152 @@
 /* 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/>. */
 
 // Tests that clicking the DOM node button in any ObjectInspect
 // opens the Inspector panel
 
+// Import helpers registering the test-actor in remote targets
+Services.scriptloader.loadSubScript(
+  "chrome://mochitests/content/browser/devtools/client/shared/test/test-actor-registry.js",
+  this
+);
+
 add_task(async function() {
   // Ensures the end panel is wide enough to show the inspector icon
   await pushPref("devtools.debugger.end-panel-size", 600);
+  // Disable 3-pane inspector as it might trigger unwanted server communication.
+  await pushPref("devtools.inspector.three-pane-enabled", false);
 
   const dbg = await initDebugger("doc-script-switching.html");
   const { toolbox } = dbg;
+  await registerTestActor(toolbox.target.client);
+  const testActor = await getTestActor(toolbox);
 
+  // Bug 1562165: the WhyPaused element is displayed for a few hundred ms when adding an
+  // expression, which can break synthesizeMouseAtCenter. So here we wait for the
+  // whyPaused element to be displayed then hidden before testing the highlight feature.
+  const onWhyPausedDisplayed = waitUntil(() =>
+    dbg.win.document.querySelector(".why-paused")
+  );
   await addExpression(dbg, "window.document.querySelector('button')");
+  // TODO: Remove when Bug 1562165 lands.
+  await onWhyPausedDisplayed;
+  // TODO: Remove when Bug 1562165 lands.
+  waitUntil(() => !dbg.win.document.querySelector(".why-paused"));
 
-  await waitForElement(dbg, "openInspector");
+  info(
+    "Check that hovering over DOM element highlights the node in content panel"
+  );
+  const inspectorFront = await toolbox.target.getFront("inspector");
+  let onNodeHighlight = inspectorFront.highlighter.once("node-highlight");
 
-  const inspectorNode = findElement(dbg, "openInspector");
+  info("Mouseover the open in inspector button");
+  const inspectorNode = await waitUntilPredicate(() =>
+    findElement(dbg, "openInspector")
+  );
+  const view = inspectorNode.ownerDocument.defaultView;
+  EventUtils.synthesizeMouseAtCenter(
+    inspectorNode,
+    { type: "mouseover" },
+    view
+  );
+
+  info("Wait for node-highlight");
+  const nodeFront = await onNodeHighlight;
+  is(nodeFront.displayName, "button", "The correct node was highlighted");
+  isVisible = await testActor.isHighlighting();
+  ok(isVisible, "Highlighter is displayed");
 
-  // Ensure hovering over button highlights the node in content pane
-  const view = inspectorNode.ownerDocument.defaultView;
-  const inspectorFront = await toolbox.target.getFront("inspector");
-  const onNodeHighlight = inspectorFront.highlighter.once("node-highlight");
+  info("Check that moving the mouse away from the node hides the highlighter");
+  let onNodeUnhighlight = inspectorFront.highlighter.once("node-unhighlight");
+  const nonHighlightEl = inspectorNode.closest(".object-node");
+  EventUtils.synthesizeMouseAtCenter(
+    nonHighlightEl,
+    { type: "mouseover" },
+    view
+  );
+
+  await onNodeUnhighlight;
+  isVisible = await testActor.isHighlighting();
+  is(isVisible, false, "The highlighter is not displayed anymore");
+
+  info("Check we don't have zombie highlighters when briefly hovering a node");
+  onNodeHighlight = inspectorFront.highlighter.once("node-highlight");
+  onNodeUnhighlight = inspectorFront.highlighter.once("node-unhighlight");
+
+  // Move hover the node and then, right after, move out.
   EventUtils.synthesizeMouseAtCenter(
     inspectorNode,
     { type: "mousemove" },
     view
   );
-  const nodeFront = await onNodeHighlight;
-  is(nodeFront.displayName, "button", "The correct node was highlighted");
+  EventUtils.synthesizeMouseAtCenter(
+    nonHighlightEl,
+    { type: "mousemove" },
+    view
+  );
+
+  await Promise.all([onNodeHighlight, onNodeUnhighlight]);
+  isVisible = await testActor.isHighlighting();
+  is(isVisible, false, "The highlighter is not displayed anymore - no zombie");
+
+  info("Ensure panel changes when button is clicked");
+  // Loading the inspector panel at first, to make it possible to listen for
+  // new node selections
+  await toolbox.loadTool("inspector");
+  const inspector = toolbox.getPanel("inspector");
 
-  // Ensure panel changes when button is clicked
+  const onInspectorSelected = toolbox.once("inspector-selected");
+  const onInspectorUpdated = inspector.once("inspector-updated");
+  const onNewNode = toolbox.selection.once("new-node-front");
+
   inspectorNode.click();
-  await waitForInspectorPanelChange(dbg);
+
+  await onInspectorSelected;
+  await onInspectorUpdated;
+  const inspectorNodeFront = await onNewNode;
+
+  ok(true, "Inspector selected and new node got selected");
+  is(
+    inspectorNodeFront.displayName,
+    "button",
+    "The expected node was selected"
+  );
 });
 
 add_task(async function() {
+  // Disable 3-pane inspector as it might trigger unwanted server communication.
+  await pushPref("devtools.inspector.three-pane-enabled", false);
+
   const dbg = await initDebugger("doc-event-handler.html");
+  const { toolbox } = dbg;
 
   invokeInTab("synthesizeClick");
   await waitForPaused(dbg);
 
   findElement(dbg, "frame", 2).focus();
   await clickElement(dbg, "frame", 2);
 
   // Hover over the token to launch preview popup
   await tryHovering(dbg, 5, 8, "popup");
 
   // Click the first inspector buttom to view node in inspector
   await waitForElement(dbg, "openInspector");
+
+  // Loading the inspector panel at first, to make it possible to listen for
+  // new node selections
+  await toolbox.loadTool("inspector");
+  const inspector = toolbox.getPanel("inspector");
+
+  const onInspectorSelected = toolbox.once("inspector-selected");
+  const onInspectorUpdated = inspector.once("inspector-updated");
+  const onNewNode = toolbox.selection.once("new-node-front");
+
   findElement(dbg, "openInspector").click();
 
-  await waitForInspectorPanelChange(dbg);
+  await onInspectorSelected;
+  await onInspectorUpdated;
+  await onNewNode;
+
+  ok(true, "Inspector selected and new node got selected");
 });
--- a/devtools/client/dom/panel.js
+++ b/devtools/client/dom/panel.js
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const { Cu } = require("chrome");
-const ObjectClient = require("devtools/shared/client/object-client");
+const ObjectFront = require("devtools/shared/fronts/object");
 
 const EventEmitter = require("devtools/shared/event-emitter");
 loader.lazyRequireGetter(
   this,
   "openContentLink",
   "devtools/client/shared/link",
   true
 );
@@ -156,18 +156,18 @@ DomPanel.prototype = {
       return null;
     }
 
     // Check for a previously stored request for grip.
     let request = this.pendingRequests.get(grip.actor);
 
     // If no request is in progress create a new one.
     if (!request) {
-      const client = new ObjectClient(this.target.client, grip);
-      request = client.getPrototypeAndProperties();
+      const objectFront = new ObjectFront(this.target.client, grip);
+      request = objectFront.getPrototypeAndProperties();
       this.pendingRequests.set(grip.actor, request);
     }
 
     const response = await request;
     this.pendingRequests.delete(grip.actor);
 
     // Fire an event about not having any pending requests.
     if (!this.pendingRequests.size) {
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -170,16 +170,23 @@ loader.lazyGetter(
 
 loader.lazyRequireGetter(
   this,
   "defaultThreadOptions",
   "devtools/client/shared/thread-utils",
   true
 );
 
+loader.lazyRequireGetter(
+  this,
+  "NodeFront",
+  "devtools/shared/fronts/node",
+  true
+);
+
 /**
  * A "Toolbox" is the component that holds all the tools for one specific
  * target. Visually, it's a document that includes the tools tabs and all
  * the iframes where the tool panels will be living in.
  *
  * @param {object} target
  *        The object the toolbox is debugging.
  * @param {string} selectedTool
@@ -3405,39 +3412,37 @@ Toolbox.prototype = {
     // instead of gDevTools
     this.emit("tool-unregistered", toolId);
   },
 
   /**
    * An helper function that returns an object contain a highlighter and unhighlighter
    * function.
    *
-   * @param {Boolean} isGrip: Set to true if the `highlight` function is going to be
-   *                          called with a Grip (and not from a NodeFront).
    * @returns {Object} an object of the following shape:
    *   - {AsyncFunction} highlight: A function that will initialize the highlighter front
    *                                and call highlighter.highlight with the provided node
    *                                front (which will be retrieved from a grip, if
    *                                `fromGrip` is true.)
    *   - {AsyncFunction} unhighlight: A function that will unhighlight the node that is
    *                                  currently highlighted. If the `highlight` function
    *                                  isn't settled yet, it will wait until it's done and
    *                                  then unhighlight to prevent zombie highlighters.
    *
    */
-  getHighlighter(fromGrip = false) {
+  getHighlighter() {
     let pendingHighlight;
     let currentHighlighterFront;
 
     return {
       highlight: async (object, options) => {
         pendingHighlight = (async () => {
           let nodeFront = object;
 
-          if (fromGrip) {
+          if (!(nodeFront instanceof NodeFront)) {
             const inspectorFront = await this.target.getFront("inspector");
             nodeFront = await inspectorFront.getNodeFrontFromNodeGrip(object);
           }
 
           if (!nodeFront) {
             return null;
           }
 
--- a/devtools/client/inspector/extensions/types.js
+++ b/devtools/client/inspector/extensions/types.js
@@ -4,14 +4,14 @@
 
 "use strict";
 
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 
 // Helpers injected as props by extension-sidebar.js and used by the
 // ObjectInspector component (which is part of the ObjectValueGripView).
 exports.serviceContainer = {
-  createObjectClient: PropTypes.func.isRequired,
+  createObjectFront: PropTypes.func.isRequired,
   releaseActor: PropTypes.func.isRequired,
   highlightDomElement: PropTypes.func.isRequired,
   unHighlightDomElement: PropTypes.func.isRequired,
   openNodeInInspector: PropTypes.func.isRequired,
 };
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -61,21 +61,17 @@ loader.lazyRequireGetter(
   "ExtensionSidebar",
   "devtools/client/inspector/extensions/extension-sidebar"
 );
 loader.lazyRequireGetter(
   this,
   "saveScreenshot",
   "devtools/shared/screenshot/save"
 );
-loader.lazyRequireGetter(
-  this,
-  "ObjectClient",
-  "devtools/shared/client/object-client"
-);
+loader.lazyRequireGetter(this, "ObjectFront", "devtools/shared/fronts/object");
 
 loader.lazyImporter(
   this,
   "DeferredTask",
   "resource://gre/modules/DeferredTask.jsm"
 );
 
 const { LocalizationHelper, localizeMarkup } = require("devtools/shared/l10n");
@@ -146,18 +142,18 @@ function Inspector(toolbox) {
 
   this._toolbox = toolbox;
   this._target = toolbox.target;
   this.panelDoc = window.document;
   this.panelWin = window;
   this.panelWin.inspector = this;
   this.telemetry = toolbox.telemetry;
   this.store = Store({
-    createObjectClient: object => {
-      return new ObjectClient(toolbox.target.client, object);
+    createObjectFront: object => {
+      return new ObjectFront(toolbox.target.client, object);
     },
     releaseActor: actor => {
       if (!actor) {
         return;
       }
       const objFront = toolbox.target.client.getFrontByID(actor);
       if (objFront) {
         objFront.release();
--- a/devtools/client/inspector/store.js
+++ b/devtools/client/inspector/store.js
@@ -9,14 +9,14 @@ const reducers = require("devtools/clien
 
 module.exports = services =>
   createStore(reducers, {
     // Enable log middleware in tests
     shouldLog: true,
     thunkOptions: {
       // Needed for the ObjectInspector
       client: {
-        createObjectClient: services && services.createObjectClient,
+        createObjectFront: services && services.createObjectFront,
         createLongStringClient: services && services.createLongStringClient,
         releaseActor: services && services.releaseActor,
       },
     },
   });
--- a/devtools/client/locales/en-US/application.ftl
+++ b/devtools/client/locales/en-US/application.ftl
@@ -100,19 +100,24 @@ manifest-item-presentation = Presentatio
 manifest-item-icons = Icons
 
 # Text displayed while we are loading the manifest file
 manifest-loading = Loading manifest…
 
 # Text displayed when the manifest has been successfully loaded
 manifest-loaded-ok = Manifest loaded.
 
-# Text displayed when there has been an error while trying to load the manifest
+# Text displayed as a caption when there has been an error while trying to
+# load the manifest
 manifest-loaded-error = There was an error while loading the manifest:
 
+# Text displayed as an error when there has been a Firefox DevTools error while
+# trying to load the manifest
+manifest-loaded-devtools-error = Firefox DevTools error
+
 # Text displayed when the page has no manifest available
 manifest-non-existing = No manifest found to inspect.
 
 # Text displayed when the page has a manifest embedded in a Data URL and
 # thus we cannot link to it.
 manifest-json-link-data-url = The manifest is embedded in a Data URL.
 
 # Text displayed at manifest icons to label their purpose, as declared
--- a/devtools/client/scratchpad/scratchpad.js
+++ b/devtools/client/scratchpad/scratchpad.js
@@ -119,21 +119,17 @@ loader.lazyRequireGetter(
   "devtools/shared/client/debugger-client",
   true
 );
 loader.lazyRequireGetter(
   this,
   "EnvironmentFront",
   "devtools/shared/fronts/environment"
 );
-loader.lazyRequireGetter(
-  this,
-  "ObjectClient",
-  "devtools/shared/client/object-client"
-);
+loader.lazyRequireGetter(this, "ObjectFront", "devtools/shared/fronts/object");
 loader.lazyRequireGetter(
   this,
   "BrowserConsoleManager",
   "devtools/client/webconsole/browser-console-manager",
   true
 );
 loader.lazyRequireGetter(
   this,
@@ -653,18 +649,18 @@ var Scratchpad = {
     const [string, error, result] = await this.execute();
     if (error) {
       await this.writeAsErrorComment(error);
       return [string, error, result];
     } else if (VariablesView.isPrimitive({ value: result })) {
       await this._writePrimitiveAsComment(result);
       return [string, error, result];
     }
-    const objectClient = new ObjectClient(this.debuggerClient, result);
-    const response = await objectClient.getDisplayString();
+    const objectFront = new ObjectFront(this.debuggerClient, result);
+    const response = await objectFront.getDisplayString();
     if (response.error) {
       reportError("display", response);
       throw new Error(response.error);
     } else {
       this.writeAsComment(response.displayString);
       return [string, error, result];
     }
   },
@@ -924,20 +920,20 @@ var Scratchpad = {
         const stack = this._constructErrorStack(exception.preview);
         if (typeof error.exceptionMessage == "string") {
           resolve(error.exceptionMessage + stack);
         } else {
           resolve(stack);
         }
       } else {
         // If there is no preview information, we need to ask the server for more.
-        const objectClient = new ObjectClient(this.debuggerClient, exception);
+        const objectFront = new ObjectFront(this.debuggerClient, exception);
         let response;
         try {
-          response = await objectClient.getPrototypeAndProperties();
+          response = await objectFront.getPrototypeAndProperties();
         } catch (ex) {
           reject(ex);
         }
         if (response.error) {
           reject(response);
           return;
         }
 
@@ -955,17 +951,17 @@ var Scratchpad = {
 
         const stack = this._constructErrorStack(error);
 
         if (typeof error.message == "string") {
           resolve(error.message + stack);
         } else {
           let response;
           try {
-            response = await objectClient.getDisplayString();
+            response = await objectFront.getDisplayString();
           } catch (ex) {
             reject(ex);
           }
           if (response.error) {
             reject(response);
           } else if (typeof response.displayString == "string") {
             resolve(response.displayString + stack);
           } else {
@@ -2334,18 +2330,18 @@ ScratchpadSidebar.prototype = {
 
           VariablesViewController.attach(this.variablesView, {
             getEnvironmentFront: grip => {
               return new EnvironmentFront(
                 this._scratchpad.debuggerClient,
                 grip
               );
             },
-            getObjectClient: grip => {
-              return new ObjectClient(this._scratchpad.debuggerClient, grip);
+            getObjectFront: grip => {
+              return new ObjectFront(this._scratchpad.debuggerClient, grip);
             },
             getLongStringClient: actor => {
               return this._scratchpad.webConsoleFront.longString(actor);
             },
             releaseActor: actor => {
               const objFront = this._scratchpad.debuggerClient.getFrontByID(
                 actor
               );
--- a/devtools/client/shared/components/reps/reps.js
+++ b/devtools/client/shared/components/reps/reps.js
@@ -2354,17 +2354,17 @@ function getChildren(options) {
 
   return addToCache(makeNodesForProperties(loadedProps, item));
 } // Builds an expression that resolves to the value of the item in question
 // e.g. `b` in { a: { b: 2 } } resolves to `a.b`
 
 
 function getPathExpression(item) {
   if (item && item.parent) {
-    let parent = nodeIsBucket(item.parent) ? item.parent.parent : item.parent;
+    const parent = nodeIsBucket(item.parent) ? item.parent.parent : item.parent;
     return `${getPathExpression(parent)}.${item.name}`;
   }
 
   return item.name;
 }
 
 function getParent(item) {
   return item.parent;
@@ -3952,61 +3952,60 @@ const {
   nodeIsProxy,
   nodeNeedsNumericalBuckets,
   nodeIsLongString
 } = __webpack_require__(114);
 
 function loadItemProperties(item, client, loadedProperties) {
   const gripItem = getClosestGripNode(item);
   const value = getValue(gripItem);
-
   const [start, end] = item.meta ? [item.meta.startIndex, item.meta.endIndex] : [];
   const promises = [];
-  let objectClient;
-  
+  let objectFront;
+
   if (value && client && client.getFrontByID) {
-    objectClient = client.getFrontByID(value.actor);
-  }
-
-  const getObjectClient = function() {
-    if (!objectClient) {
-      objectClient = client.createObjectClient(value);
-    }
-
-    return objectClient;
-  }
+    objectFront = client.getFrontByID(value.actor);
+  }
+
+  const getObjectFront = function () {
+    if (!objectFront) {
+      objectFront = client.createObjectFront(value);
+    }
+
+    return objectFront;
+  };
 
   if (shouldLoadItemIndexedProperties(item, loadedProperties)) {
-    promises.push(enumIndexedProperties(getObjectClient(), start, end));
-  }
-  
+    promises.push(enumIndexedProperties(getObjectFront(), start, end));
+  }
+
   if (shouldLoadItemNonIndexedProperties(item, loadedProperties)) {
-    promises.push(enumNonIndexedProperties(getObjectClient(), start, end));
-  }
-  
+    promises.push(enumNonIndexedProperties(getObjectFront(), start, end));
+  }
+
   if (shouldLoadItemEntries(item, loadedProperties)) {
-    promises.push(enumEntries(getObjectClient(), start, end));
-  }
-  
+    promises.push(enumEntries(getObjectFront(), start, end));
+  }
+
   if (shouldLoadItemPrototype(item, loadedProperties)) {
-    promises.push(getPrototype(getObjectClient()));
-  }
-  
+    promises.push(getPrototype(getObjectFront()));
+  }
+
   if (shouldLoadItemSymbols(item, loadedProperties)) {
-    promises.push(enumSymbols(getObjectClient(), start, end));
-  }
-  
+    promises.push(enumSymbols(getObjectFront(), start, end));
+  }
+
   if (shouldLoadItemFullText(item, loadedProperties)) {
     promises.push(getFullText(client.createLongStringClient(value), item));
   }
-  
+
   if (shouldLoadItemProxySlots(item, loadedProperties)) {
-    promises.push(getProxySlots(getObjectClient()));
-  }
-  
+    promises.push(getProxySlots(getObjectFront()));
+  }
+
   return Promise.all(promises).then(mergeResponses);
 }
 
 function mergeResponses(responses) {
   const data = {};
 
   for (const response of responses) {
     if (response.hasOwnProperty("ownProperties")) {
@@ -4094,71 +4093,71 @@ module.exports = {
 /* 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/>. */
 const {
   getValue,
   nodeHasFullText
 } = __webpack_require__(114);
 
-async function enumIndexedProperties(objectClient, start, end) {
+async function enumIndexedProperties(objectFront, start, end) {
   try {
-    const iterator = await objectClient.enumProperties({
+    const iterator = await objectFront.enumProperties({
       ignoreNonIndexedProperties: true
     });
     const response = await iteratorSlice(iterator, start, end);
     return response;
   } catch (e) {
     console.error("Error in enumIndexedProperties", e);
     return {};
   }
 }
 
-async function enumNonIndexedProperties(objectClient, start, end) {
+async function enumNonIndexedProperties(objectFront, start, end) {
   try {
-    const iterator = await objectClient.enumProperties({
+    const iterator = await objectFront.enumProperties({
       ignoreIndexedProperties: true
     });
     const response = await iteratorSlice(iterator, start, end);
     return response;
   } catch (e) {
     console.error("Error in enumNonIndexedProperties", e);
     return {};
   }
 }
 
-async function enumEntries(objectClient, start, end) {
+async function enumEntries(objectFront, start, end) {
   try {
-    const iterator = await objectClient.enumEntries();
+    const iterator = await objectFront.enumEntries();
     const response = await iteratorSlice(iterator, start, end);
     return response;
   } catch (e) {
     console.error("Error in enumEntries", e);
     return {};
   }
 }
 
-async function enumSymbols(objectClient, start, end) {
+async function enumSymbols(objectFront, start, end) {
   try {
-    const iterator = await objectClient.enumSymbols();
+    const iterator = await objectFront.enumSymbols();
     const response = await iteratorSlice(iterator, start, end);
     return response;
   } catch (e) {
     console.error("Error in enumSymbols", e);
     return {};
   }
 }
 
-async function getPrototype(objectClient) {
-  if (typeof objectClient.getPrototype !== "function") {
-    console.error("objectClient.getPrototype is not a function");
+async function getPrototype(objectFront) {
+  if (typeof objectFront.getPrototype !== "function") {
+    console.error("objectFront.getPrototype is not a function");
     return Promise.resolve({});
   }
 
-  return objectClient.getPrototype();
+  return objectFront.getPrototype();
 }
 
 async function getFullText(longStringClient, item) {
   const {
     initial,
     fullText,
     length
   } = getValue(item); // Return fullText property if it exists so that it can be added to the
@@ -4180,18 +4179,18 @@ async function getFullText(longStringCli
 
       resolve({
         fullText: initial + response.substring
       });
     });
   });
 }
 
-async function getProxySlots(objectClient) {
-  return objectClient.getProxySlots();
+async function getProxySlots(objectFront) {
+  return objectFront.getProxySlots();
 }
 
 function iteratorSlice(iterator, start, end) {
   start = start || 0;
   const count = end ? end - start + 1 : iterator.count;
 
   if (count === 0) {
     return Promise.resolve({});
@@ -7945,17 +7944,17 @@ function nodeCollapse(node) {
     type: "NODE_COLLAPSE",
     data: {
       node
     }
   };
 }
 /*
  * This action checks if we need to fetch properties, entries, prototype and
- * symbols for a given node. If we do, it will call the appropriate ObjectClient
+ * symbols for a given node. If we do, it will call the appropriate ObjectFront
  * functions.
  */
 
 
 function nodeLoadProperties(node, actor) {
   return async ({
     dispatch,
     client,
@@ -8124,18 +8123,18 @@ async function releaseActors(state, clie
 
 function invokeGetter(node, targetGrip, receiverId, getterName) {
   return async ({
     dispatch,
     client,
     getState
   }) => {
     try {
-      const objectClient = client.createObjectClient(targetGrip);
-      const result = await objectClient.getPropertyValue(getterName, receiverId);
+      const objectFront = client.createObjectFront(targetGrip);
+      const result = await objectFront.getPropertyValue(getterName, receiverId);
       dispatch({
         type: "GETTER_INVOKED",
         data: {
           node,
           result
         }
       });
     } catch (e) {
@@ -8547,9 +8546,9 @@ var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBP
 		window.classNames = classNames;
 	}
 }());
 
 
 /***/ })
 
 /******/ });
-});
+});
\ No newline at end of file
--- a/devtools/client/shared/widgets/VariablesViewController.jsm
+++ b/devtools/client/shared/widgets/VariablesViewController.jsm
@@ -32,17 +32,17 @@ var L10N = new LocalizationHelper(DBG_ST
  * Controller for a VariablesView that handles interfacing with the debugger
  * protocol. Is able to populate scopes and variables via the protocol as well
  * as manage actor lifespans.
  *
  * @param VariablesView aView
  *        The view to attach to.
  * @param object aOptions [optional]
  *        Options for configuring the controller. Supported options:
- *        - getObjectClient: @see this._setClientGetters
+ *        - getObjectFront: @see this._setClientGetters
  *        - getLongStringClient: @see this._setClientGetters
  *        - getEnvironmentFront: @see this._setClientGetters
  *        - releaseActor: @see this._setClientGetters
  *        - overrideValueEvalMacro: @see _setEvaluationMacros
  *        - getterOrSetterEvalMacro: @see _setEvaluationMacros
  *        - simpleValueEvalMacro: @see _setEvaluationMacros
  */
 function VariablesViewController(aView, aOptions = {}) {
@@ -73,24 +73,24 @@ VariablesViewController.prototype = {
    */
   _simpleValueEvalMacro: VariablesView.simpleValueEvalMacro,
 
   /**
    * Set the functions used to retrieve debugger client grips.
    *
    * @param object aOptions
    *        Options for getting the client grips. Supported options:
-   *        - getObjectClient: callback for creating an object grip client
+   *        - getObjectFront: callback for creating an object grip front
    *        - getLongStringClient: callback for creating a long string grip client
    *        - getEnvironmentFront: callback for creating an environment front
    *        - releaseActor: callback for releasing an actor when it's no longer needed
    */
   _setClientGetters: function(aOptions) {
-    if (aOptions.getObjectClient) {
-      this._getObjectClient = aOptions.getObjectClient;
+    if (aOptions.getObjectFront) {
+      this._getObjectFront = aOptions.getObjectFront;
     }
     if (aOptions.getLongStringClient) {
       this._getLongStringClient = aOptions.getLongStringClient;
     }
     if (aOptions.getEnvironmentFront) {
       this._getEnvironmentFront = aOptions.getEnvironmentFront;
     }
     if (aOptions.releaseActor) {
@@ -240,53 +240,53 @@ VariablesViewController.prototype = {
    *        The grip to use to populate the target.
    * @param string aQuery [optional]
    *        The query string used to fetch only a subset of properties
    */
   _populateFromObjectWithIterator: function(aTarget, aGrip, aQuery) {
     // FF40+ starts exposing `ownPropertyLength` on ObjectActor's grip,
     // as well as `enumProperties` request.
     const deferred = defer();
-    const objectClient = this._getObjectClient(aGrip);
+    const objectFront = this._getObjectFront(aGrip);
     const isArray = aGrip.preview && aGrip.preview.kind === "ArrayLike";
     if (isArray) {
       // First enumerate array items, e.g. properties from `0` to `array.length`.
       const options = {
         ignoreNonIndexedProperties: true,
         query: aQuery,
       };
-      objectClient.enumProperties(options).then(iterator => {
+      objectFront.enumProperties(options).then(iterator => {
         const sliceGrip = {
           type: "property-iterator",
           propertyIterator: iterator,
           start: 0,
           count: iterator.count,
         };
         this._populatePropertySlices(aTarget, sliceGrip).then(() => {
           // Then enumerate the rest of the properties, like length, buffer, etc.
           const options = {
             ignoreIndexedProperties: true,
             sort: true,
             query: aQuery,
           };
-          objectClient.enumProperties(options).then(iterator => {
+          objectFront.enumProperties(options).then(iterator => {
             const sliceGrip = {
               type: "property-iterator",
               propertyIterator: iterator,
               start: 0,
               count: iterator.count,
             };
             deferred.resolve(this._populatePropertySlices(aTarget, sliceGrip));
           });
         });
       });
     } else {
       const options = { sort: true, query: aQuery };
       // For objects, we just enumerate all the properties sorted by name.
-      objectClient.enumProperties(options).then(iterator => {
+      objectFront.enumProperties(options).then(iterator => {
         const sliceGrip = {
           type: "property-iterator",
           propertyIterator: iterator,
           start: 0,
           count: iterator.count,
         };
         deferred.resolve(this._populatePropertySlices(aTarget, sliceGrip));
       });
@@ -318,18 +318,18 @@ VariablesViewController.prototype = {
    *        The Scope where the properties will be placed into.
    * @param object aGrip
    *        The grip to use to populate the target.
    */
   _populateFromObject: function(aTarget, aGrip) {
     if (aGrip.class === "Proxy") {
       // Refuse to play the proxy's stupid game and just expose the target and handler.
       const deferred = defer();
-      const objectClient = this._getObjectClient(aGrip);
-      objectClient.getProxySlots().then(aResponse => {
+      const objectFront = this._getObjectFront(aGrip);
+      objectFront.getProxySlots().then(aResponse => {
         const target = aTarget.addItem(
           "<target>",
           { value: aResponse.proxyTarget },
           { internalItem: true }
         );
         this.addExpander(target, aResponse.proxyTarget);
         const handler = aTarget.addItem(
           "<handler>",
@@ -375,33 +375,33 @@ VariablesViewController.prototype = {
 
     // Fetch properties by slices if there is too many in order to prevent UI freeze.
     if (
       "ownPropertyLength" in aGrip &&
       aGrip.ownPropertyLength >= MAX_PROPERTY_ITEMS
     ) {
       return this._populateFromObjectWithIterator(aTarget, aGrip).then(() => {
         const deferred = defer();
-        const objectClient = this._getObjectClient(aGrip);
-        objectClient.getPrototype().then(prototype => {
+        const objectFront = this._getObjectFront(aGrip);
+        objectFront.getPrototype().then(prototype => {
           this._populateObjectPrototype(aTarget, prototype);
           deferred.resolve();
         });
         return deferred.promise;
       });
     }
 
     return this._populateProperties(aTarget, aGrip);
   },
 
   _populateProperties: function(aTarget, aGrip, aOptions) {
     const deferred = defer();
 
-    const objectClient = this._getObjectClient(aGrip);
-    objectClient.getPrototypeAndProperties().then(aResponse => {
+    const objectFront = this._getObjectFront(aGrip);
+    objectFront.getPrototypeAndProperties().then(aResponse => {
       const ownProperties = aResponse.ownProperties || {};
       const prototype = aResponse.prototype || null;
       // 'safeGetterValues' is new and isn't necessary defined on old actors.
       const safeGetterValues = aResponse.safeGetterValues || {};
       const sortable = VariablesView.isSortable(aGrip.class);
 
       // Merge the safe getter values into one object such that we can use it
       // in VariablesView.
@@ -424,17 +424,17 @@ VariablesViewController.prototype = {
       });
 
       // Add the variable's __proto__.
       this._populateObjectPrototype(aTarget, prototype);
 
       // If the object is a function we need to fetch its scope chain
       // to show them as closures for the respective function.
       if (aGrip.class == "Function") {
-        objectClient.getScope().then(aResponse => {
+        objectFront.getScope().then(aResponse => {
           if (aResponse.error) {
             // This function is bound to a built-in object or it's not present
             // in the current scope chain. Not necessarily an actual error,
             // it just means that there's no closure for the function.
             console.warn(aResponse.error + ": " + aResponse.message);
             return void deferred.resolve();
           }
           this._populateWithClosure(aTarget, aResponse.scope).then(
@@ -525,21 +525,21 @@ VariablesViewController.prototype = {
       sorted: VARIABLES_SORTING_ENABLED,
       // Expansion handlers must be set after the properties are added.
       callback: this.addExpander,
     });
   },
 
   _populateFromEntries: function(target, grip) {
     const objGrip = grip.obj;
-    const objectClient = this._getObjectClient(objGrip);
+    const objectFront = this._getObjectFront(objGrip);
 
     // eslint-disable-next-line new-cap
     return new promise((resolve, reject) => {
-      objectClient.enumEntries().then(response => {
+      objectFront.enumEntries().then(response => {
         if (response.error) {
           // Older server might not support the enumEntries method
           console.warn(response.error + ": " + response.message);
           resolve();
         } else {
           const sliceGrip = {
             type: "property-iterator",
             propertyIterator: response.iterator,
--- a/devtools/client/webconsole/commands.js
+++ b/devtools/client/webconsole/commands.js
@@ -1,31 +1,31 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const ObjectClient = require("devtools/shared/client/object-client");
+const ObjectFront = require("devtools/shared/fronts/object");
 const LongStringClient = require("devtools/shared/client/long-string-client");
 
 class ConsoleCommands {
   constructor({ debuggerClient, proxy, threadFront, currentTarget }) {
     this.debuggerClient = debuggerClient;
     this.proxy = proxy;
     this.threadFront = threadFront;
     this.currentTarget = currentTarget;
   }
 
   evaluateJSAsync(expression, options) {
     return this.proxy.webConsoleFront.evaluateJSAsync(expression, options);
   }
 
-  createObjectClient(object) {
-    return new ObjectClient(this.debuggerClient, object);
+  createObjectFront(object) {
+    return new ObjectFront(this.debuggerClient, object);
   }
 
   createLongStringClient(object) {
     return new LongStringClient(this.debuggerClient, object);
   }
 
   releaseActor(actor) {
     if (!actor) {
@@ -37,26 +37,26 @@ class ConsoleCommands {
       return objFront.release();
     }
 
     // In case there's no object front, use the client's release method.
     return this.debuggerClient.release(actor).catch(() => {});
   }
 
   async fetchObjectProperties(grip, ignoreNonIndexedProperties) {
-    const client = new ObjectClient(this.currentTarget.client, grip);
+    const client = new ObjectFront(this.currentTarget.client, grip);
     const iterator = await client.enumProperties({
       ignoreNonIndexedProperties,
     });
     const { ownProperties } = await iterator.slice(0, iterator.count);
     return ownProperties;
   }
 
   async fetchObjectEntries(grip) {
-    const client = new ObjectClient(this.currentTarget.client, grip);
+    const client = new ObjectFront(this.currentTarget.client, grip);
     const iterator = await client.enumEntries();
     const { ownProperties } = await iterator.slice(0, iterator.count);
     return ownProperties;
   }
 
   timeWarp(executionPoint) {
     return this.threadFront.timeWarp(executionPoint);
   }
--- a/devtools/client/webconsole/components/App.css
+++ b/devtools/client/webconsole/components/App.css
@@ -277,17 +277,17 @@ body {
   height: 16px;
   width: 16px;
   -moz-context-properties: fill;
   fill: currentColor;
   margin-inline-end: 2px;
 }
 
 [dir="rtl"] .jsterm-editor .webconsole-editor-toolbar .webconsole-editor-toolbar-executeButton::before {
-  rotate: 180deg;
+  transform: rotate(180deg);
 }
 
 .jsterm-editor .webconsole-editor-toolbar .webconsole-editor-toolbar-history-prevExpressionButton {
   grid-column: -5 / -6;
 }
 
 .jsterm-editor .webconsole-editor-toolbar .webconsole-editor-toolbar-history-nextExpressionButton {
   grid-column: -4 / -5;
--- a/devtools/client/webconsole/components/App.js
+++ b/devtools/client/webconsole/components/App.js
@@ -86,17 +86,16 @@ class App extends Component {
       editorMode: PropTypes.bool,
       editorWidth: PropTypes.number,
       hidePersistLogsCheckbox: PropTypes.bool,
       hideShowContentMessagesCheckbox: PropTypes.bool,
       sidebarVisible: PropTypes.bool.isRequired,
       filterBarDisplayMode: PropTypes.oneOf([
         ...Object.values(FILTERBAR_DISPLAY_MODES),
       ]).isRequired,
-      editorFeatureEnabled: PropTypes.bool.isRequired,
     };
   }
 
   constructor(props) {
     super(props);
 
     this.onClick = this.onClick.bind(this);
     this.onPaste = this.onPaste.bind(this);
@@ -108,30 +107,29 @@ class App extends Component {
     window.addEventListener("blur", this.onBlur);
   }
 
   onBlur() {
     this.props.dispatch(actions.autocompleteClear());
   }
 
   onKeyDown(event) {
-    const { dispatch, webConsoleUI, editorFeatureEnabled } = this.props;
+    const { dispatch, webConsoleUI } = this.props;
 
     if (
       (!isMacOS && event.key === "F9") ||
       (isMacOS && event.key === "r" && event.ctrlKey === true)
     ) {
       const initialValue =
         webConsoleUI.jsterm && webConsoleUI.jsterm.getSelectedText();
       dispatch(actions.reverseSearchInputToggle({ initialValue }));
       event.stopPropagation();
     }
 
     if (
-      editorFeatureEnabled &&
       event.key.toLowerCase() === "b" &&
       ((isMacOS && event.metaKey) || (!isMacOS && event.ctrlKey))
     ) {
       event.stopPropagation();
       event.preventDefault();
       dispatch(actions.editorToggle());
     }
   }
@@ -261,24 +259,23 @@ class App extends Component {
       displayMode: filterBarDisplayMode,
       webConsoleUI,
     });
   }
 
   renderEditorToolbar() {
     const {
       editorMode,
-      editorFeatureEnabled,
       dispatch,
       reverseSearchInputVisible,
       serviceContainer,
       webConsoleUI,
     } = this.props;
 
-    return editorFeatureEnabled && editorMode
+    return editorMode
       ? EditorToolbar({
           key: "editor-toolbar",
           editorMode,
           dispatch,
           reverseSearchInputVisible,
           serviceContainer,
           webConsoleUI,
         })
@@ -297,28 +294,26 @@ class App extends Component {
 
   renderJsTerm() {
     const {
       webConsoleUI,
       serviceContainer,
       autocomplete,
       editorMode,
       editorWidth,
-      editorFeatureEnabled,
     } = this.props;
 
     return JSTerm({
       key: "jsterm",
       webConsoleUI,
       serviceContainer,
       onPaste: this.onPaste,
       autocomplete,
-      editorMode: editorMode && editorFeatureEnabled,
+      editorMode,
       editorWidth,
-      editorFeatureEnabled,
     });
   }
 
   renderReverseSearch() {
     const { serviceContainer, reverseSearchInitialValue } = this.props;
 
     return ReverseSearchInput({
       key: "reverse-search-input",
@@ -333,43 +328,43 @@ class App extends Component {
     return SideBar({
       key: "sidebar",
       serviceContainer,
       visible: sidebarVisible,
     });
   }
 
   renderNotificationBox() {
-    const { notifications, editorMode, editorFeatureEnabled } = this.props;
+    const { notifications, editorMode } = this.props;
 
     return NotificationBox({
       id: "webconsole-notificationbox",
       key: "notification-box",
-      displayBorderTop: !(editorMode && editorFeatureEnabled),
-      displayBorderBottom: editorMode && editorFeatureEnabled,
+      displayBorderTop: !editorMode,
+      displayBorderBottom: editorMode,
       wrapping: true,
       notifications,
     });
   }
 
   renderConfirmDialog() {
     const { webConsoleUI, serviceContainer } = this.props;
 
     return ConfirmDialog({
       webConsoleUI,
       serviceContainer,
       key: "confirm-dialog",
     });
   }
 
   renderRootElement(children) {
-    const { editorMode, editorFeatureEnabled, serviceContainer } = this.props;
+    const { editorMode, serviceContainer } = this.props;
 
     const classNames = ["webconsole-app"];
-    if (editorMode && editorFeatureEnabled) {
+    if (editorMode) {
       classNames.push("jsterm-editor");
     }
     if (serviceContainer.canRewind()) {
       classNames.push("can-rewind");
     }
 
     return div(
       {
@@ -380,22 +375,17 @@ class App extends Component {
           this.node = node;
         },
       },
       children
     );
   }
 
   render() {
-    const {
-      webConsoleUI,
-      editorMode,
-      editorFeatureEnabled,
-      dispatch,
-    } = this.props;
+    const { webConsoleUI, editorMode, dispatch } = this.props;
 
     const filterBar = this.renderFilterBar();
     const editorToolbar = this.renderEditorToolbar();
     const consoleOutput = this.renderConsoleOutput();
     const notificationBox = this.renderNotificationBox();
     const jsterm = this.renderJsTerm();
     const reverseSearch = this.renderReverseSearch();
     const sidebar = this.renderSideBar();
@@ -407,17 +397,17 @@ class App extends Component {
       dom.div(
         { className: "flexible-output-input", key: "in-out-container" },
         consoleOutput,
         notificationBox,
         jsterm
       ),
       GridElementWidthResizer({
         key: "editor-resizer",
-        enabled: editorFeatureEnabled && editorMode,
+        enabled: editorMode,
         position: "end",
         className: "editor-resizer",
         getControlledElementNode: () => webConsoleUI.jsterm.node,
         onResizeEnd: width => dispatch(actions.setEditorWidth(width)),
       }),
       reverseSearch,
       sidebar,
       confirmDialog,
--- a/devtools/client/webconsole/components/Input/JSTerm.js
+++ b/devtools/client/webconsole/components/Input/JSTerm.js
@@ -28,17 +28,17 @@ loader.lazyRequireGetter(
 );
 loader.lazyRequireGetter(
   this,
   "Editor",
   "devtools/client/shared/sourceeditor/editor"
 );
 loader.lazyRequireGetter(
   this,
-  "focusableSelector",
+  "getFocusableElements",
   "devtools/client/shared/focus",
   true
 );
 loader.lazyRequireGetter(
   this,
   "l10n",
   "devtools/client/webconsole/utils/messages",
   true
@@ -94,18 +94,16 @@ class JSTerm extends Component {
       autocompleteUpdate: PropTypes.func.isRequired,
       autocompleteClear: PropTypes.func.isRequired,
       // Data to be displayed in the autocomplete popup.
       autocompleteData: PropTypes.object.isRequired,
       // Toggle the editor mode.
       editorToggle: PropTypes.func.isRequired,
       // Dismiss the editor onboarding UI.
       editorOnboardingDismiss: PropTypes.func.isRequired,
-      // Is the editor feature enabled
-      editorFeatureEnabled: PropTypes.bool,
       // Is the input in editor mode.
       editorMode: PropTypes.bool,
       editorWidth: PropTypes.number,
       showEditorOnboarding: PropTypes.bool,
       autocomplete: PropTypes.bool,
     };
   }
 
@@ -537,17 +535,22 @@ class JSTerm extends Component {
   focusPreviousElement() {
     const inputField = this.editor.codeMirror.getInputField();
 
     const findPreviousFocusableElement = el => {
       if (!el || !el.querySelectorAll) {
         return null;
       }
 
-      const items = Array.from(el.querySelectorAll(focusableSelector));
+      // We only want to get visible focusable element, and for that we can assert that
+      // the offsetParent isn't null. We can do that because we don't have fixed position
+      // element in the console.
+      const items = getFocusableElements(el).filter(
+        ({ offsetParent }) => offsetParent !== null
+      );
       const inputIndex = items.indexOf(inputField);
 
       if (items.length === 0 || (inputIndex > -1 && items.length === 1)) {
         return findPreviousFocusableElement(el.parentNode);
       }
 
       const index = inputIndex > 0 ? inputIndex - 1 : items.length - 1;
       return items[index];
@@ -1044,31 +1047,31 @@ class JSTerm extends Component {
       this.editor.destroy();
       this.editor = null;
     }
 
     this.webConsoleUI = null;
   }
 
   renderOpenEditorButton() {
-    if (!this.props.editorFeatureEnabled || this.props.editorMode) {
+    if (this.props.editorMode) {
       return null;
     }
 
     return dom.button({
       className: "devtools-button webconsole-input-openEditorButton",
       title: l10n.getFormatStr("webconsole.input.openEditorButton.tooltip2", [
         isMacOS ? "Cmd + B" : "Ctrl + B",
       ]),
       onClick: this.props.editorToggle,
     });
   }
 
   renderEditorOnboarding() {
-    if (!this.props.editorFeatureEnabled || !this.props.showEditorOnboarding) {
+    if (!this.props.showEditorOnboarding) {
       return null;
     }
 
     // We deliberately use getStr, and not getFormatStr, because we want keyboard
     // shortcuts to be wrapped in their own span.
     const label = l10n.getStr("webconsole.input.editor.onboarding.label");
     let [prefix, suffix] = label.split("%1$S");
     suffix = suffix.split("%2$S");
--- a/devtools/client/webconsole/constants.js
+++ b/devtools/client/webconsole/constants.js
@@ -80,17 +80,16 @@ const prefs = {
       // Show the Editor onboarding UI
       EDITOR_ONBOARDING: "devtools.webconsole.input.editorOnboarding",
     },
     FEATURES: {
       // We use the same pref to enable the sidebar on webconsole and browser console.
       SIDEBAR_TOGGLE: "devtools.webconsole.sidebarToggle",
       AUTOCOMPLETE: "devtools.webconsole.input.autocomplete",
       GROUP_WARNINGS: "devtools.webconsole.groupWarningMessages",
-      EDITOR: "devtools.webconsole.features.editor",
       BROWSER_TOOLBOX_FISSION: "devtools.browsertoolbox.fission",
     },
   },
 };
 
 const FILTERS = {
   CSS: "css",
   DEBUG: "debug",
--- a/devtools/client/webconsole/reducers/prefs.js
+++ b/devtools/client/webconsole/reducers/prefs.js
@@ -10,17 +10,16 @@ const {
 const PrefState = overrides =>
   Object.freeze(
     Object.assign(
       {
         logLimit: 1000,
         sidebarToggle: false,
         groupWarnings: false,
         historyCount: 50,
-        editor: false,
       },
       overrides
     )
   );
 
 function prefs(state = PrefState(), action) {
   if (action.type === WARNING_GROUPS_TOGGLE) {
     return {
--- a/devtools/client/webconsole/service-container.js
+++ b/devtools/client/webconsole/service-container.js
@@ -48,17 +48,17 @@ function setupServiceContainer({
     getJsTermTooltipAnchor: () => webConsoleUI.getJsTermTooltipAnchor(),
     emitEvent: (event, value) => webConsoleUI.emit(event, value),
     attachRefToWebConsoleUI: (id, node) => webConsoleUI.attachRef(id, node),
     requestData: (id, type) => webConsoleWrapper.requestData(id, type),
     createElement: nodename => webConsoleWrapper.createElement(nodename),
   };
 
   if (toolbox) {
-    const { highlight, unhighlight } = toolbox.getHighlighter(true);
+    const { highlight, unhighlight } = toolbox.getHighlighter();
 
     Object.assign(serviceContainer, {
       sourceMapService: toolbox.sourceMapURLService,
       highlightDomElement: highlight,
       unHighlightDomElement: unhighlight,
       jumpToExecutionPoint: executionPoint =>
         toolbox.threadFront.timeWarp(executionPoint),
       onViewSourceInDebugger: frame => hud.onViewSourceInDebugger(frame),
--- a/devtools/client/webconsole/store.js
+++ b/devtools/client/webconsole/store.js
@@ -44,27 +44,25 @@ function configureStore(webConsoleUI, op
   const prefsService = getPrefsService(webConsoleUI);
   const { getBoolPref, getIntPref } = prefsService;
 
   const logLimit =
     options.logLimit || Math.max(getIntPref("devtools.hud.loglimit"), 1);
   const sidebarToggle = getBoolPref(PREFS.FEATURES.SIDEBAR_TOGGLE);
   const autocomplete = getBoolPref(PREFS.FEATURES.AUTOCOMPLETE);
   const groupWarnings = getBoolPref(PREFS.FEATURES.GROUP_WARNINGS);
-  const editor = getBoolPref(PREFS.FEATURES.EDITOR);
   const historyCount = getIntPref(PREFS.UI.INPUT_HISTORY_COUNT);
 
   const initialState = {
     prefs: PrefState({
       logLimit,
       sidebarToggle,
       autocomplete,
       historyCount,
       groupWarnings,
-      editor,
     }),
     filters: FilterState({
       error: getBoolPref(PREFS.FILTER.ERROR),
       warn: getBoolPref(PREFS.FILTER.WARN),
       info: getBoolPref(PREFS.FILTER.INFO),
       debug: getBoolPref(PREFS.FILTER.DEBUG),
       log: getBoolPref(PREFS.FILTER.LOG),
       css: getBoolPref(PREFS.FILTER.CSS),
--- a/devtools/client/webconsole/test/browser/browser_console_cpow.js
+++ b/devtools/client/webconsole/test/browser/browser_console_cpow.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Test the basic features of the Browser Console.
 
 "use strict";
 
-const ObjectClient = require("devtools/shared/client/object-client");
+const ObjectFront = require("devtools/shared/fronts/object");
 
 const {
   gDevToolsBrowser,
 } = require("devtools/client/framework/devtools-browser");
 
 add_task(async function() {
   await addTab("about:blank");
 
@@ -67,30 +67,30 @@ async function testFrontEnd(hud, message
   expandObjectInspectorNode(cpow);
   is(getObjectInspectorChildrenNodes(cpow).length, 0, "CPOW has no children");
 }
 
 async function testBackEnd(hud, actor) {
   // Check that inspecting an object with CPOW doesn't throw in the server.
   // This would be done in a mochitest-chrome suite, but that doesn't run in
   // e10s, so it's harder to get ahold of a CPOW.
-  info("Creating an ObjectClient with: " + actor);
-  const objectClient = new ObjectClient(hud.ui.proxy.client, { actor });
+  info("Creating an ObjectFront with: " + actor);
+  const objectFront = new ObjectFront(hud.ui.proxy.client, { actor });
 
   // Before the fix for Bug 1382833, this wouldn't resolve due to a CPOW error
   // in the ObjectActor.
-  const prototypeAndProperties = await objectClient.getPrototypeAndProperties();
+  const prototypeAndProperties = await objectFront.getPrototypeAndProperties();
 
   // The CPOW is in the "cpow" property.
   const cpow = prototypeAndProperties.ownProperties.cpow.value;
 
   is(cpow.class, "CPOW", "The CPOW grip has the right class.");
 
   // Check that various protocol request methods work for the CPOW.
-  const objClient = new ObjectClient(hud.ui.proxy.client, cpow);
+  const objClient = new ObjectFront(hud.ui.proxy.client, cpow);
 
   let response = await objClient.getPrototypeAndProperties();
   is(
     Reflect.ownKeys(response.ownProperties).length,
     0,
     "No property was retrieved."
   );
   is(response.ownSymbols.length, 0, "No symbol property was retrieved.");
--- a/devtools/client/webconsole/test/browser/browser_jsterm_await_concurrent_same_result.js
+++ b/devtools/client/webconsole/test/browser/browser_jsterm_await_concurrent_same_result.js
@@ -6,17 +6,16 @@
 
 "use strict";
 
 const TEST_URI =
   "data:text/html;charset=utf-8,Test concurrent top-level await expressions returning same value";
 
 add_task(async function() {
   // Enable editor mode as we'll be able to quicly trigger multiple evaluations.
-  await pushPref("devtools.webconsole.features.editor", true);
   await pushPref("devtools.webconsole.input.editor", true);
 
   const hud = await openNewTabAndConsole(TEST_URI);
   setInputValue(
     hud,
     `await new Promise(res => setTimeout(() => res("foo"), 5000))`
   );
 
@@ -31,11 +30,10 @@ add_task(async function() {
 
   await waitFor(
     () => findMessages(hud, "foo", ".result").length === 3,
     "Waiting for all results to be printed in console",
     1000
   );
   ok(true, "There are as many results as commands");
 
-  Services.prefs.clearUserPref("devtools.webconsole.features.editor");
   Services.prefs.clearUserPref("devtools.webconsole.input.editor");
 });
--- a/devtools/client/webconsole/test/browser/browser_jsterm_editor.js
+++ b/devtools/client/webconsole/test/browser/browser_jsterm_editor.js
@@ -3,17 +3,16 @@
 
 // Check that the editor is displayed as expected.
 
 "use strict";
 
 const TEST_URI = "data:text/html;charset=utf8,<p>Test editor";
 
 add_task(async function() {
-  await pushPref("devtools.webconsole.features.editor", true);
   await pushPref("devtools.webconsole.input.editor", false);
 
   const tab = await addTab(TEST_URI);
   let hud = await openConsole(tab);
 
   info("Test that the editor mode is disabled when the pref is set to false");
   is(
     isEditorModeEnabled(hud),
--- a/devtools/client/webconsole/test/browser/browser_jsterm_editor_disabled_history_nav_with_keyboard.js
+++ b/devtools/client/webconsole/test/browser/browser_jsterm_editor_disabled_history_nav_with_keyboard.js
@@ -6,17 +6,16 @@
 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1519313
 
 "use strict";
 
 const TEST_URI =
   "data:text/html;charset=utf-8,Web Console test for bug 1519313";
 
 add_task(async function() {
-  await pushPref("devtools.webconsole.features.editor", true);
   await pushPref("devtools.webconsole.input.editor", true);
   const hud = await openNewTabAndConsole(TEST_URI);
 
   const testExpressions = [
     "`Mozilla 😍 Firefox`",
     "`Firefox Devtools are awesome`",
     "`2 + 2 = 5?`",
     "`I'm running out of ideas...`",
--- a/devtools/client/webconsole/test/browser/browser_jsterm_editor_enter.js
+++ b/devtools/client/webconsole/test/browser/browser_jsterm_editor_enter.js
@@ -6,17 +6,16 @@
 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1519314
 
 "use strict";
 
 const TEST_URI =
   "data:text/html;charset=utf-8,Web Console test for bug 1519314";
 
 add_task(async function() {
-  await pushPref("devtools.webconsole.features.editor", true);
   await pushPref("devtools.webconsole.input.editor", true);
   await performEditorEnabledTests();
 });
 
 add_task(async function() {
   await pushPref("devtools.webconsole.input.editor", false);
   await performEditorDisabledTests();
 });
--- a/devtools/client/webconsole/test/browser/browser_jsterm_editor_execute.js
+++ b/devtools/client/webconsole/test/browser/browser_jsterm_editor_execute.js
@@ -6,17 +6,16 @@
 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1519313
 
 "use strict";
 
 const TEST_URI =
   "data:text/html;charset=utf-8,Web Console test for bug 1519313";
 
 add_task(async function() {
-  await pushPref("devtools.webconsole.features.editor", true);
   await pushPref("devtools.webconsole.input.editor", true);
   const hud = await openNewTabAndConsole(TEST_URI);
 
   const expression = `x = 10`;
   setInputValue(hud, expression);
   await executeAndWaitForMessage(hud, undefined, "", ".result");
   is(getInputValue(hud), expression, "input line is not cleared after submit");
 });
--- a/devtools/client/webconsole/test/browser/browser_jsterm_editor_execute_selection.js
+++ b/devtools/client/webconsole/test/browser/browser_jsterm_editor_execute_selection.js
@@ -6,17 +6,16 @@
 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1576563
 
 "use strict";
 
 const TEST_URI =
   "data:text/html;charset=utf-8,Web Console test for executing input selection";
 
 add_task(async function() {
-  await pushPref("devtools.webconsole.features.editor", true);
   await pushPref("devtools.webconsole.input.editor", true);
   const hud = await openNewTabAndConsole(TEST_URI);
 
   const expression = `x = 10;x;
     x = 20;x;`;
 
   info("Evaluate the whole expression");
   setInputValue(hud, expression);
--- a/devtools/client/webconsole/test/browser/browser_jsterm_editor_gutter.js
+++ b/devtools/client/webconsole/test/browser/browser_jsterm_editor_gutter.js
@@ -5,17 +5,16 @@
 // 'devtools.webconsole.input.editor' is true.
 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1519315
 
 "use strict";
 
 const TEST_URI = "data:text/html;charset=utf-8,Test JsTerm editor line gutters";
 
 add_task(async function() {
-  await pushPref("devtools.webconsole.features.editor", true);
   await pushPref("devtools.webconsole.input.editor", true);
 
   const hud = await openNewTabAndConsole(TEST_URI);
 
   info("Check that the line numbers gutter is rendered when in editor layout");
   ok(
     getLineNumbersGutterElement(hud),
     "line numbers gutter is rendered on the input when in editor mode."
--- a/devtools/client/webconsole/test/browser/browser_jsterm_editor_onboarding.js
+++ b/devtools/client/webconsole/test/browser/browser_jsterm_editor_onboarding.js
@@ -3,23 +3,21 @@
 
 // Test that the onboarding UI is displayed when first displaying the editor mode, and
 // that it can be permanentely dismissed.
 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1558417
 
 "use strict";
 
 const TEST_URI = "data:text/html;charset=utf-8,Test onboarding UI";
-const EDITOR_FEATURE_PREF = "devtools.webconsole.features.editor";
 const EDITOR_UI_PREF = "devtools.webconsole.input.editor";
 const EDITOR_ONBOARDING_PREF = "devtools.webconsole.input.editorOnboarding";
 
 add_task(async function() {
   // Enable editor mode and force the onboarding pref to true so it's displayed.
-  await pushPref(EDITOR_FEATURE_PREF, true);
   await pushPref(EDITOR_UI_PREF, true);
   await pushPref(EDITOR_ONBOARDING_PREF, true);
 
   let hud = await openNewTabAndConsole(TEST_URI);
 
   info("Check that the onboarding UI is displayed");
   const onboardingElement = getOnboardingEl(hud);
   ok(onboardingElement, "The onboarding UI exists");
@@ -38,16 +36,15 @@ add_task(async function() {
   await closeConsole();
   hud = await openConsole();
   is(
     getOnboardingEl(hud),
     null,
     "The onboarding UI isn't displayed after a toolbox restart after being dismissed"
   );
 
-  Services.prefs.clearUserPref(EDITOR_FEATURE_PREF);
   Services.prefs.clearUserPref(EDITOR_UI_PREF);
   Services.prefs.clearUserPref(EDITOR_ONBOARDING_PREF);
 });
 
 function getOnboardingEl(hud) {
   return hud.ui.outputNode.querySelector(".editor-onboarding");
 }
--- a/devtools/client/webconsole/test/browser/browser_jsterm_editor_resize.js
+++ b/devtools/client/webconsole/test/browser/browser_jsterm_editor_resize.js
@@ -4,17 +4,16 @@
 // Test that the editor can be resized and that its width is persisted.
 
 "use strict";
 
 const TEST_URI =
   "data:text/html;charset=utf-8,Web Console test for editor resize";
 
 add_task(async function() {
-  await pushPref("devtools.webconsole.features.editor", true);
   await pushPref("devtools.webconsole.input.editor", true);
   await pushPref("devtools.webconsole.input.editorOnboarding", false);
 
   // Reset editorWidth pref so we have steady results when running multiple times.
   await pushPref("devtools.webconsole.input.editorWidth", null);
 
   let hud = await openNewTabAndConsole(TEST_URI);
   const getEditorEl = () =>
--- a/devtools/client/webconsole/test/browser/browser_jsterm_editor_reverse_search_button.js
+++ b/devtools/client/webconsole/test/browser/browser_jsterm_editor_reverse_search_button.js
@@ -2,17 +2,16 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 const TEST_URI =
   "data:text/html;charset=utf-8,Web Console test for bug 1567372";
 
 add_task(async function() {
-  await pushPref("devtools.webconsole.features.editor", true);
   await pushPref("devtools.webconsole.input.editor", true);
 
   const hud = await openNewTabAndConsole(TEST_URI);
 
   info("Searching for `.webconsole-editor-toolbar`");
   const editorToolbar = hud.ui.outputNode.querySelector(
     ".webconsole-editor-toolbar"
   );
--- a/devtools/client/webconsole/test/browser/browser_jsterm_editor_toggle_keyboard_shortcut.js
+++ b/devtools/client/webconsole/test/browser/browser_jsterm_editor_toggle_keyboard_shortcut.js
@@ -6,18 +6,16 @@
 
 "use strict";
 
 const TEST_URI =
   "data:text/html;charset=utf-8,Test editor mode toggle keyboard shortcut";
 const EDITOR_PREF = "devtools.webconsole.input.editor";
 
 add_task(async function() {
-  await pushPref("devtools.webconsole.features.editor", true);
-
   // Start with the editor turned off
   await pushPref(EDITOR_PREF, false);
   let hud = await openNewTabAndConsole(TEST_URI);
 
   const INPUT_VALUE = "hello";
   setInputValue(hud, INPUT_VALUE);
 
   is(isEditorModeEnabled(hud), false, "The console isn't in editor mode");
--- a/devtools/client/webconsole/test/browser/browser_jsterm_editor_toolbar.js
+++ b/devtools/client/webconsole/test/browser/browser_jsterm_editor_toolbar.js
@@ -3,17 +3,16 @@
 
 // Check that the editor toolbar works as expected when in editor mode.
 
 "use strict";
 
 const TEST_URI = "data:text/html;charset=utf8,<p>Test editor toolbar";
 
 add_task(async function() {
-  await pushPref("devtools.webconsole.features.editor", true);
   await pushPref("devtools.webconsole.input.editor", false);
 
   const tab = await addTab(TEST_URI);
   let hud = await openConsole(tab);
 
   info("Test that the toolbar is not displayed when in editor mode");
   let toolbar = getEditorToolbar(hud);
   is(toolbar, null, "The toolbar isn't displayed when not in editor mode");
--- a/devtools/client/webconsole/test/browser/browser_jsterm_no_input_and_tab_key_pressed.js
+++ b/devtools/client/webconsole/test/browser/browser_jsterm_no_input_and_tab_key_pressed.js
@@ -4,20 +4,16 @@
 "use strict";
 
 // See Bug 583816.
 
 const TEST_URI =
   "data:text/html,<meta charset=utf8>Testing jsterm with no input";
 
 add_task(async function() {
-  // For now, let's disable editor as we don't know what the final placement of the
-  // open editor button (which may impact this test).
-  await pushPref("devtools.webconsole.features.editor", false);
-
   const hud = await openNewTabAndConsole(TEST_URI);
   const jsterm = hud.jsterm;
 
   info("Check that hitting Tab when input is empty insert blur the input");
   jsterm.focus();
   setInputValue(hud, "");
   EventUtils.synthesizeKey("KEY_Tab");
   is(getInputValue(hud), "", "inputnode is empty - matched");
@@ -25,21 +21,30 @@ add_task(async function() {
 
   info("Check that hitting Shift+Tab when input is empty blur the input");
   jsterm.focus();
   EventUtils.synthesizeKey("KEY_Tab", { shiftKey: true });
   is(getInputValue(hud), "", "inputnode is empty - matched");
   ok(!isInputFocused(hud), "input isn't focused anymore");
   ok(
     hasFocus(
+      hud.ui.outputNode.querySelector(".webconsole-input-openEditorButton")
+    ),
+    `The "Toggle Editor" button is now focused`
+  );
+
+  info("Check that hitting Shift+Tab again place the focus on the filter bar");
+  EventUtils.synthesizeKey("KEY_Tab", { shiftKey: true });
+  ok(
+    hasFocus(
       hud.ui.outputNode.querySelector(
         ".webconsole-console-settings-menu-button"
       )
     ),
-    `The "Console Settings" menu button is now focused`
+    `The "Console Settings" button is now focused`
   );
 
   info("Check that hitting Tab when input is not empty insert a tab");
   jsterm.focus();
 
   const testString = "window.Bug583816";
   await setInputValueForAutocompletion(hud, testString, 0);
   checkInputValueAndCursorPosition(
--- a/devtools/client/webconsole/test/browser/browser_webconsole_telemetry_execute_js.js
+++ b/devtools/client/webconsole/test/browser/browser_webconsole_telemetry_execute_js.js
@@ -6,18 +6,16 @@
 // when evaluating expressions.
 
 "use strict";
 
 const TEST_URI = `data:text/html,<meta charset=utf8>Test execute_js telemetry event`;
 const ALL_CHANNELS = Ci.nsITelemetry.DATASET_ALL_CHANNELS;
 
 add_task(async function() {
-  await pushPref("devtools.webconsole.features.editor", true);
-
   // Let's reset the counts.
   Services.telemetry.clearEvents();
 
   // Ensure no events have been logged
   const snapshot = Services.telemetry.snapshotEvents(ALL_CHANNELS, true);
   ok(!snapshot.parent, "No events have been logged for the main process");
 
   const hud = await openNewTabAndConsole(TEST_URI);
--- a/devtools/client/webconsole/test/node/mocha-test-setup.js
+++ b/devtools/client/webconsole/test/node/mocha-test-setup.js
@@ -22,17 +22,16 @@ pref("devtools.webconsole.filter.netxhr"
 pref("devtools.webconsole.inputHistoryCount", 300);
 pref("devtools.webconsole.persistlog", false);
 pref("devtools.webconsole.timestampMessages", false);
 pref("devtools.webconsole.sidebarToggle", true);
 pref("devtools.webconsole.groupWarningMessages", false);
 pref("devtools.webconsole.input.editor", false);
 pref("devtools.webconsole.input.autocomplete", true);
 pref("devtools.browserconsole.contentMessages", true);
-pref("devtools.webconsole.features.editor", true);
 pref("devtools.webconsole.input.editorWidth", 800);
 pref("devtools.webconsole.input.editorOnboarding", true);
 
 global.loader = {
   lazyServiceGetter: () => {},
   lazyGetter: (context, name, fn) => {
     try {
       global[name] = fn();
@@ -108,17 +107,17 @@ requireHacker.global_hook("default", (pa
     // is required, replace it with a mock version.
     "devtools/shared/l10n": () =>
       getModule(
         "devtools/client/webconsole/test/node/fixtures/LocalizationHelper"
       ),
     "devtools/shared/plural-form": () =>
       getModule("devtools/client/webconsole/test/node/fixtures/PluralForm"),
     Services: () => `module.exports = require("devtools-services")`,
-    "devtools/shared/client/object-client": () => `() => {}`,
+    "devtools/shared/fronts/object": () => `() => {}`,
     "devtools/shared/client/long-string-client": () => `() => {}`,
     "devtools/client/shared/components/SmartTrace": () => "{}",
     "devtools/client/netmonitor/src/components/TabboxPanel": () => "{}",
     "devtools/client/webconsole/utils/context-menu": () => "{}",
     "devtools/client/shared/telemetry": () => `module.exports = function() {
       this.recordEvent = () => {};
       this.getKeyedHistogramById = () => ({add: () => {}});
     }`,
--- a/devtools/client/webconsole/webconsole-wrapper.js
+++ b/devtools/client/webconsole/webconsole-wrapper.js
@@ -108,25 +108,23 @@ class WebConsoleWrapper {
 
       if (this.toolbox) {
         this.toolbox.threadFront.on("paused", this.dispatchPaused);
         this.toolbox.threadFront.on("progress", this.dispatchProgress);
       }
 
       const { prefs } = store.getState();
       const autocomplete = prefs.autocomplete;
-      const editorFeatureEnabled = prefs.editor;
 
       const app = App({
         serviceContainer,
         webConsoleUI,
         onFirstMeaningfulPaint: resolve,
         closeSplitConsole: this.closeSplitConsole.bind(this),
         autocomplete,
-        editorFeatureEnabled,
         hidePersistLogsCheckbox:
           webConsoleUI.isBrowserConsole || webConsoleUI.isBrowserToolboxConsole,
         hideShowContentMessagesCheckbox:
           !webConsoleUI.isBrowserConsole &&
           !webConsoleUI.isBrowserToolboxConsole,
       });
 
       // Render the root Application component.
--- a/devtools/docs/backend/client-api.md
+++ b/devtools/docs/backend/client-api.md
@@ -2,18 +2,18 @@
 
 DevTools has a client module that allows applications to be written that debug or inspect web pages using the [Remote Debugging Protocol](protocol.md).
 
 ## Starting communication
 
 In order to communicate, a client and a server instance must be created and a protocol connection must be established. The connection can be either over a TCP socket or an nsIPipe. The `start` function displayed below establishes an nsIPipe-backed connection:
 
 ```javascript
-Components.utils.import("resource://gre/modules/devtools/dbg-server.jsm");
-Components.utils.import("resource://gre/modules/devtools/dbg-client.jsm");
+const { DebuggerServer } = require("devtools/server/debugger-server");
+const { DebuggerClient } = require("devtools/shared/client/debugger-client");
 
 function start() {
   // Start the server.
   DebuggerServer.init();
   DebuggerServer.registerAllActors();
 
   // Listen to an nsIPipe
   let transport = DebuggerServer.connectPipe();
@@ -26,18 +26,18 @@ function start() {
     debugTab();
   });
 }
 ```
 
 If a TCP socket is required, the function should be split in two parts, a server-side and a client-side, like this:
 
 ```javascript
-Components.utils.import("resource://gre/modules/devtools/dbg-server.jsm");
-Components.utils.import("resource://gre/modules/devtools/dbg-client.jsm");
+const { DebuggerServer } = require("devtools/server/debugger-server");
+const { DebuggerClient } = require("devtools/shared/client/debugger-client");
 
 function startServer() {
   // Start the server.
   DebuggerServer.init();
   DebuggerServer.registerAllActors();
 
   // For an nsIServerSocket we do this:
   DebuggerServer.openListener(2929); // A connection on port 2929.
@@ -135,18 +135,18 @@ client.attachThread(response.threadActor
 Here is the source code for a complete debugger application:
 
 ```javascript
 /*
  * Debugger API demo.
  * Try it in Scratchpad with Environment -> Browser, using
  * http://htmlpad.org/debugger/ as the current page.
  */
-Components.utils.import("resource://gre/modules/devtools/dbg-server.jsm");
-Components.utils.import("resource://gre/modules/devtools/dbg-client.jsm");
+const { DebuggerServer } = require("devtools/server/debugger-server");
+const { DebuggerClient } = require("devtools/shared/client/debugger-client");
 
 let client;
 let threadFront;
 
 function startDebugger() {
   // Start the server.
   DebuggerServer.init();
   DebuggerServer.registerAllActors();
--- a/devtools/server/tests/unit/head_dbg.js
+++ b/devtools/server/tests/unit/head_dbg.js
@@ -38,17 +38,17 @@ const DevToolsUtils = require("devtools/
 const {
   ActorRegistry,
 } = require("devtools/server/actors/utils/actor-registry");
 const { DebuggerServer } = require("devtools/server/debugger-server");
 const { DebuggerServer: WorkerDebuggerServer } = worker.require(
   "devtools/server/debugger-server"
 );
 const { DebuggerClient } = require("devtools/shared/client/debugger-client");
-const ObjectClient = require("devtools/shared/client/object-client");
+const ObjectFront = require("devtools/shared/fronts/object");
 const { LongStringFront } = require("devtools/shared/fronts/string");
 const { TargetFactory } = require("devtools/client/framework/target");
 
 const { addDebuggerToGlobal } = ChromeUtils.import(
   "resource://gre/modules/jsdebugger.jsm"
 );
 
 const systemPrincipal = Cc["@mozilla.org/systemprincipal;1"].createInstance(
--- a/devtools/server/tests/unit/test_objectgrips-13.js
+++ b/devtools/server/tests/unit/test_objectgrips-13.js
@@ -1,14 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-// Test that ObjectClient.prototype.getDefinitionSite and the "definitionSite"
+// Test that ObjectFront.prototype.getDefinitionSite and the "definitionSite"
 // request work properly.
 
 var gDebuggee;
 var gClient;
 var gThreadFront;
 
 function run_test() {
   initTestDebuggerServer();
--- a/devtools/server/tests/unit/test_objectgrips-21.js
+++ b/devtools/server/tests/unit/test_objectgrips-21.js
@@ -268,17 +268,17 @@ async function test_unsafe_grips(
 
           response = await objClient.getPrototype();
           check_prototype(response.prototype, data, isUnsafe);
 
           response = await objClient.getDisplayString();
           check_display_string(response.displayString, data, isUnsafe);
 
           if (data.isFunction && isUnsafe) {
-            // For function-related methods, object-client.js checks that the class
+            // For function-related methods, the object front checks that the class
             // of the grip is "Function", and if it's not, the method in object.js
             // is not called. But some tests have a grip with a class that is not
             // "Function" (e.g. it's "Proxy") but the DebuggerObject has a "Function"
             // class because the object is callable (despite not being a Function object).
             // So the grip class is changed in order to test the object.js method.
             grip.class = "Function";
             objClient = threadFront.pauseGrip(grip);
             try {
--- a/devtools/server/tests/unit/test_threadlifetime-02.js
+++ b/devtools/server/tests/unit/test_threadlifetime-02.js
@@ -43,19 +43,19 @@ function test_thread_lifetime() {
       type: "threadGrip",
     });
     // Successful promotion won't return an error.
     Assert.equal(response.error, undefined);
     gThreadFront.once("paused", async function(packet) {
       // Verify that the promoted actor is returned again.
       Assert.equal(pauseGrip.actor, packet.frame.arguments[0].actor);
       // Now that we've resumed, release the thread-lifetime grip.
-      const objFront = new ObjectClient(gClient, pauseGrip);
+      const objFront = new ObjectFront(gClient, pauseGrip);
       await objFront.release();
-      const objFront2 = new ObjectClient(gClient, pauseGrip);
+      const objFront2 = new ObjectFront(gClient, pauseGrip);
 
       try {
         await objFront2
           .request({ to: pauseGrip.actor, type: "bogusRequest" })
           .catch(function(response) {
             Assert.ok(!!response.match(/noSuchActor/));
             gThreadFront.resume().then(function() {
               finishClient(gClient);
--- a/devtools/shared/adb/test/adb.py
+++ b/devtools/shared/adb/test/adb.py
@@ -5,20 +5,18 @@
 
 """
 A fake ADB binary
 """
 
 from __future__ import absolute_import
 
 import os
-import socket
 import SocketServer
 import sys
-import thread
 
 HOST = '127.0.0.1'
 PORT = 5037
 
 class ADBRequestHandler(SocketServer.BaseRequestHandler):
     def sendData(self, data):
         header = 'OKAY%04x' % len(data)
         all_data = header + data
@@ -28,29 +26,24 @@ class ADBRequestHandler(SocketServer.Bas
         # Though the data length here is pretty small but sometimes when the
         # client is on heavy load (e.g. MOZ_CHAOSMODE) we can't send the whole
         # data at once.
         while sent_length < total_length:
             sent = self.request.send(all_data[sent_length:])
             sent_length = sent_length + sent
 
     def handle(self):
-        if server.is_shuttingdown:
-            return
-
         while True:
             data = self.request.recv(4096)
             if 'host:kill' in data:
-                def shutdown(server):
-                    server.shutdown()
-                    thread.exit()
-                server.is_shuttingdown = True
                 self.sendData('')
-                self.request.close()
-                thread.start_new_thread(shutdown, (server, ))
+                # Implicitly close all open sockets by exiting the program.
+                # This should be done ASAP, because upon receiving the OKAY,
+                # the client expects adb to have released the server's port.
+                os._exit(0)
                 break
             elif 'host:version' in data:
                 self.sendData('001F')
                 self.request.close()
                 break
             elif 'host:track-devices' in data:
                 self.sendData('1234567890\tdevice')
                 break
@@ -58,17 +51,16 @@ class ADBRequestHandler(SocketServer.Bas
 class ADBServer(SocketServer.TCPServer):
     def __init__(self, server_address):
         # Create a SocketServer with bind_and_activate 'False' to set
         # allow_reuse_address before binding.
         SocketServer.TCPServer.__init__(self, \
                                         server_address, \
                                         ADBRequestHandler, \
                                         bind_and_activate = False)
-        self.is_shuttingdown = False
 
 if len(sys.argv) == 2 and sys.argv[1] == 'start-server':
     # daemonize
     if os.fork() > 0:
         sys.exit(0)
     os.setsid()
     if os.fork() > 0:
         sys.exit(0)
--- a/devtools/shared/client/debugger-client.js
+++ b/devtools/shared/client/debugger-client.js
@@ -30,21 +30,17 @@ loader.lazyRequireGetter(
 loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
 
 loader.lazyRequireGetter(
   this,
   "RootFront",
   "devtools/shared/fronts/root",
   true
 );
-loader.lazyRequireGetter(
-  this,
-  "ObjectClient",
-  "devtools/shared/client/object-client"
-);
+loader.lazyRequireGetter(this, "ObjectFront", "devtools/shared/fronts/object");
 loader.lazyRequireGetter(this, "Front", "devtools/shared/protocol", true);
 
 /**
  * Creates a client for the remote debugging protocol server. This client
  * provides the means to communicate with the server and exchange the messages
  * required by the protocol in a traditional JavaScript API.
  */
 function DebuggerClient(transport) {
@@ -948,22 +944,22 @@ DebuggerClient.prototype = {
   },
 
   /**
    * Currently attached addon.
    */
   activeAddon: null,
 
   /**
-   * Creates an object client for this DebuggerClient and the grip in parameter,
-   * @param {Object} grip: The grip to create the ObjectClient for.
-   * @returns {ObjectClient}
+   * Creates an object front for this DebuggerClient and the grip in parameter,
+   * @param {Object} grip: The grip to create the ObjectFront for.
+   * @returns {ObjectFront}
    */
-  createObjectClient: function(grip) {
-    return new ObjectClient(this, grip);
+  createObjectFront: function(grip) {
+    return new ObjectFront(this, grip);
   },
 
   get transport() {
     return this._transport;
   },
 };
 
 EventEmitter.decorate(DebuggerClient.prototype);
--- a/devtools/shared/client/deprecated-thread-client.js
+++ b/devtools/shared/client/deprecated-thread-client.js
@@ -6,21 +6,17 @@
 
 const {
   arg,
   DebuggerClient,
 } = require("devtools/shared/client/debugger-client");
 const EventEmitter = require("devtools/shared/event-emitter");
 const { ThreadStateTypes } = require("devtools/shared/client/constants");
 
-loader.lazyRequireGetter(
-  this,
-  "ObjectClient",
-  "devtools/shared/client/object-client"
-);
+loader.lazyRequireGetter(this, "ObjectFront", "devtools/shared/client/object");
 loader.lazyRequireGetter(
   this,
   "SourceFront",
   "devtools/shared/fronts/source",
   true
 );
 
 /**
@@ -287,58 +283,58 @@ ThreadClient.prototype = {
    *
    * @param frameId string
    */
   getEnvironment: function(frameId) {
     return this.request({ to: frameId, type: "getEnvironment" });
   },
 
   /**
-   * Return a ObjectClient object for the given object grip.
+   * Return a ObjectFront object for the given object grip.
    *
    * @param grip object
    *        A pause-lifetime object grip returned by the protocol.
    */
   pauseGrip: function(grip) {
     if (grip.actor in this._pauseGrips) {
       return this._pauseGrips[grip.actor];
     }
 
-    const client = new ObjectClient(this.client, grip);
-    this._pauseGrips[grip.actor] = client;
-    return client;
+    const objectFront = new ObjectFront(this.client, grip);
+    this._pauseGrips[grip.actor] = objectFront;
+    return objectFront;
   },
 
   /**
-   * Clear and invalidate all the grip clients from the given cache.
+   * Clear and invalidate all the grip fronts from the given cache.
    *
    * @param gripCacheName
    *        The property name of the grip cache we want to clear.
    */
-  _clearObjectClients: function(gripCacheName) {
+  _clearObjectFronts: function(gripCacheName) {
     for (const id in this[gripCacheName]) {
       this[gripCacheName][id].valid = false;
     }
     this[gripCacheName] = {};
   },
 
   /**
    * Invalidate pause-lifetime grip clients and clear the list of current grip
    * clients.
    */
   _clearPauseGrips: function() {
-    this._clearObjectClients("_pauseGrips");
+    this._clearObjectFronts("_pauseGrips");
   },
 
   /**
    * Invalidate thread-lifetime grip clients and clear the list of current grip
    * clients.
    */
   _clearThreadGrips: function() {
-    this._clearObjectClients("_threadGrips");
+    this._clearObjectFronts("_threadGrips");
   },
 
   /**
    * Handle thread state change by doing necessary cleanup and notifying all
    * registered listeners.
    */
   _onThreadState: function(packet) {
     this._state = ThreadStateTypes[packet.type];
--- a/devtools/shared/client/moz.build
+++ b/devtools/shared/client/moz.build
@@ -6,10 +6,9 @@
 
 DevToolsModules(
     'connection-manager.js',
     'constants.js',
     'debugger-client.js',
     'deprecated-thread-client.js',
     'event-source.js',
     'long-string-client.js',
-    'object-client.js',
 )
--- a/devtools/shared/fronts/moz.build
+++ b/devtools/shared/fronts/moz.build
@@ -24,16 +24,17 @@ DevToolsModules(
     'environment.js',
     'framerate.js',
     'highlighters.js',
     'inspector.js',
     'layout.js',
     'manifest.js',
     'memory.js',
     'node.js',
+    'object.js',
     'perf.js',
     'performance-recording.js',
     'performance.js',
     'preference.js',
     'property-iterator.js',
     'reflow.js',
     'root.js',
     'screenshot.js',
rename from devtools/shared/client/object-client.js
rename to devtools/shared/fronts/object.js
--- a/devtools/shared/client/object-client.js
+++ b/devtools/shared/fronts/object.js
@@ -13,17 +13,17 @@ const {
 /**
  * Grip clients are used to retrieve information about the relevant object.
  *
  * @param client DebuggerClient
  *        The debugger client parent.
  * @param grip object
  *        A pause-lifetime object grip returned by the protocol.
  */
-class ObjectClient extends FrontClassWithSpec(objectSpec) {
+class ObjectFront extends FrontClassWithSpec(objectSpec) {
   constructor(client, grip) {
     super(client);
     this._grip = grip;
     this._client = client;
     this.valid = true;
     this.actorID = this._grip.actor;
 
     this.manage(this);
@@ -193,10 +193,10 @@ class ObjectClient extends FrontClassWit
       const { proxyTarget, proxyHandler } = this._grip;
       return { proxyTarget, proxyHandler };
     }
 
     return response;
   }
 }
 
-module.exports = ObjectClient;
-registerFront(ObjectClient);
+module.exports = ObjectFront;
+registerFront(ObjectFront);
--- a/devtools/shared/fronts/property-iterator.js
+++ b/devtools/shared/fronts/property-iterator.js
@@ -12,17 +12,17 @@ const {
   propertyIteratorSpec,
 } = require("devtools/shared/specs/property-iterator");
 
 /**
  * A PropertyIteratorFront provides a way to access to property names and
  * values of an object efficiently, slice by slice.
  * Note that the properties can be sorted in the backend,
  * this is controled while creating the PropertyIteratorFront
- * from ObjectClient.enumProperties.
+ * from ObjectFront.enumProperties.
  */
 class PropertyIteratorFront extends FrontClassWithSpec(propertyIteratorSpec) {
   constructor(client, targetFront, parentFront) {
     super(client, targetFront, parentFront);
     this._client = client;
   }
 
   get actor() {
--- a/devtools/shared/fronts/thread.js
+++ b/devtools/shared/fronts/thread.js
@@ -6,21 +6,17 @@
 
 const { ThreadStateTypes } = require("devtools/shared/client/constants");
 const {
   FrontClassWithSpec,
   registerFront,
 } = require("devtools/shared/protocol");
 const { threadSpec } = require("devtools/shared/specs/thread");
 
-loader.lazyRequireGetter(
-  this,
-  "ObjectClient",
-  "devtools/shared/client/object-client"
-);
+loader.lazyRequireGetter(this, "ObjectFront", "devtools/shared/fronts/object");
 loader.lazyRequireGetter(
   this,
   "SourceFront",
   "devtools/shared/fronts/source",
   true
 );
 
 /**
@@ -258,58 +254,58 @@ class ThreadFront extends FrontClassWith
    *
    * @param frameId string
    */
   getEnvironment(frameId) {
     return this.client.request({ to: frameId, type: "getEnvironment" });
   }
 
   /**
-   * Return a ObjectClient object for the given object grip.
+   * Return a ObjectFront object for the given object grip.
    *
    * @param grip object
    *        A pause-lifetime object grip returned by the protocol.
    */
   pauseGrip(grip) {
     if (grip.actor in this._pauseGrips) {
       return this._pauseGrips[grip.actor];
     }
 
-    const client = new ObjectClient(this.client, grip);
-    this._pauseGrips[grip.actor] = client;
-    return client;
+    const objectFront = new ObjectFront(this.client, grip);
+    this._pauseGrips[grip.actor] = objectFront;
+    return objectFront;
   }
 
   /**
-   * Clear and invalidate all the grip clients from the given cache.
+   * Clear and invalidate all the grip fronts from the given cache.
    *
    * @param gripCacheName
    *        The property name of the grip cache we want to clear.
    */
-  _clearObjectClients(gripCacheName) {
+  _clearObjectFronts(gripCacheName) {
     for (const id in this[gripCacheName]) {
       this[gripCacheName][id].valid = false;
     }
     this[gripCacheName] = {};
   }
 
   /**
    * Invalidate pause-lifetime grip clients and clear the list of current grip
    * clients.
    */
   _clearPauseGrips() {
-    this._clearObjectClients("_pauseGrips");
+    this._clearObjectFronts("_pauseGrips");
   }
 
   /**
    * Invalidate thread-lifetime grip clients and clear the list of current grip
    * clients.
    */
   _clearThreadGrips() {
-    this._clearObjectClients("_threadGrips");
+    this._clearObjectFronts("_threadGrips");
   }
 
   _beforePaused(packet) {
     this._state = "paused";
     this._onThreadState(packet);
   }
 
   _beforeResumed() {
--- a/devtools/shared/webconsole/test/common.js
+++ b/devtools/shared/webconsole/test/common.js
@@ -1,22 +1,22 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-/* exported ObjectClient, attachConsole, attachConsoleToTab, attachConsoleToWorker,
+/* exported ObjectFront, attachConsole, attachConsoleToTab, attachConsoleToWorker,
    closeDebugger, checkConsoleAPICalls, checkRawHeaders, runTests, nextTest, Ci, Cc,
    withActiveServiceWorker, Services, consoleAPICall */
 
 const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
 const { DebuggerServer } = require("devtools/server/debugger-server");
 const { DebuggerClient } = require("devtools/shared/client/debugger-client");
-const ObjectClient = require("devtools/shared/client/object-client");
+const ObjectFront = require("devtools/shared/fronts/object");
 const Services = require("Services");
 
 function initCommon() {
   // Services.prefs.setBoolPref("devtools.debugger.log", true);
 }
 
 function initDebuggerServer() {
   DebuggerServer.init();
--- a/devtools/shared/webconsole/test/test_bug819670_getter_throws.html
+++ b/devtools/shared/webconsole/test/test_bug819670_getter_throws.html
@@ -36,17 +36,17 @@ function onEvaluate(aState, aResponse)
       actor: /[a-z]/,
     },
   });
 
   ok(!aResponse.exception, "no eval exception");
   ok(!aResponse.helperResult, "no helper result");
 
   onInspect = onInspect.bind(null, aState);
-  let client = new ObjectClient(aState.dbgClient, aResponse.result);
+  let client = new ObjectFront(aState.dbgClient, aResponse.result);
   client.getPrototypeAndProperties().then(onInspect);
 }
 
 function onInspect(aState, aResponse)
 {
   ok(!aResponse.error, "no response error");
 
   let expectedProps =  Object.getOwnPropertyNames(document.__proto__);
--- a/devtools/shared/webconsole/test/test_object_actor.html
+++ b/devtools/shared/webconsole/test/test_object_actor.html
@@ -147,17 +147,17 @@ function onConsoleCall(state, aPacket) {
   });
 
   state.webConsoleFront.off("consoleAPICall", onConsoleCall);
 
   info("inspecting object properties");
   let args = aPacket.message.arguments;
   onProperties = onProperties.bind(null, state);
 
-  let client = new ObjectClient(state.dbgClient, args[1]);
+  let client = new ObjectFront(state.dbgClient, args[1]);
   client.getPrototypeAndProperties().then(onProperties);
 }
 
 function onProperties(state, response) {
   let props = response.ownProperties;
   is(Object.keys(props).length, Object.keys(expectedProps).length,
      "number of enumerable properties");
   checkObject(props, expectedProps);
--- a/devtools/shared/webconsole/test/test_object_actor_native_getters.html
+++ b/devtools/shared/webconsole/test/test_object_actor_native_getters.html
@@ -68,17 +68,17 @@ function onConsoleCall(aState, aPacket)
   });
 
   aState.webConsoleFront.off("consoleAPICall", onConsoleCall);
 
   info("inspecting object properties");
   let args = aPacket.message.arguments;
   onProperties = onProperties.bind(null, aState);
 
-  let client = new ObjectClient(aState.dbgClient, args[1]);
+  let client = new ObjectFront(aState.dbgClient, args[1]);
   client.getPrototypeAndProperties().then(onProperties);
 }
 
 function onProperties(aState, aResponse)
 {
   let props = aResponse.ownProperties;
   let keys = Object.keys(props);
   info(keys.length + " ownProperties: " + keys);
--- a/devtools/shared/webconsole/test/test_object_actor_native_getters_lenient_this.html
+++ b/devtools/shared/webconsole/test/test_object_actor_native_getters_lenient_this.html
@@ -47,17 +47,17 @@ function onConsoleCall(aState, aPacket)
   });
 
   aState.webConsoleFront.off("consoleAPICall", onConsoleCall);
 
   info("inspecting object properties");
   let args = aPacket.message.arguments;
   onProperties = onProperties.bind(null, aState);
 
-  let client = new ObjectClient(aState.dbgClient, args[1]);
+  let client = new ObjectFront(aState.dbgClient, args[1]);
   client.getPrototypeAndProperties().then(onProperties);
 }
 
 function onProperties(aState, aResponse)
 {
   let props = aResponse.ownProperties;
   let keys = Object.keys(props);
   info(keys.length + " ownProperties: " + keys);
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -16046,34 +16046,39 @@ nsICookieSettings* Document::CookieSetti
 }
 
 nsIPrincipal* Document::EffectiveStoragePrincipal() const {
   nsPIDOMWindowInner* inner = GetInnerWindow();
   if (!inner) {
     return NodePrincipal();
   }
 
+  // Return our cached storage principal if one exists.
+  if (mActiveStoragePrincipal) {
+    return mActiveStoragePrincipal;
+  }
+
   // We use the lower-level AntiTrackingCommon API here to ensure this
   // check doesn't send notifications.
   uint32_t rejectedReason = 0;
   if (AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(
           inner, GetDocumentURI(), &rejectedReason)) {
-    return NodePrincipal();
+    return mActiveStoragePrincipal = NodePrincipal();
   }
 
   // Let's use the storage principal only if we need to partition the cookie
   // jar. When the permission is granted, access will be different and the
   // normal principal will be used.
   if (ShouldPartitionStorage(rejectedReason) &&
       !StoragePartitioningEnabled(
           rejectedReason, const_cast<Document*>(this)->CookieSettings())) {
-    return NodePrincipal();
-  }
-
-  return mIntrinsicStoragePrincipal;
+    return mActiveStoragePrincipal = NodePrincipal();
+  }
+
+  return mActiveStoragePrincipal = mIntrinsicStoragePrincipal;
 }
 
 void Document::SetIsInitialDocument(bool aIsInitialDocument) {
   mIsInitialDocumentInWindow = aIsInitialDocument;
 
   // Asynchronously tell the parent process that we are, or are no longer, the
   // initial document. This happens async.
   if (RefPtr<nsPIDOMWindowInner> inner = GetInnerWindow()) {
--- a/dom/base/Document.h
+++ b/dom/base/Document.h
@@ -568,16 +568,18 @@ class Document : public nsINode,
   // You should probably not be using this function, since it performs no
   // checks to ensure that the intrinsic storage principal should really be
   // used here.  It is only designed to be used in very specific circumstances,
   // such as when inheriting the document/storage principal.
   nsIPrincipal* IntrinsicStoragePrincipal() const {
     return mIntrinsicStoragePrincipal;
   }
 
+  void ClearActiveStoragePrincipal() { mActiveStoragePrincipal = nullptr; }
+
   nsIPrincipal* GetContentBlockingAllowListPrincipal() const {
     return mContentBlockingAllowListPrincipal;
   }
 
   already_AddRefed<nsIPrincipal> RecomputeContentBlockingAllowListPrincipal(
       nsIURI* aURIBeingLoaded, const OriginAttributes& aAttrs);
 
   // EventTarget
@@ -5338,16 +5340,21 @@ class Document : public nsINode,
   int32_t mCachedTabSizeGeneration;
   nsTabSizes mCachedTabSizes;
 
   bool mInRDMPane;
 
   // The principal to use for the storage area of this document.
   nsCOMPtr<nsIPrincipal> mIntrinsicStoragePrincipal;
 
+  // The cached storage principal for this document.
+  // This is mutable so that we can keep EffectiveStoragePrincipal() const
+  // which is required due to its CloneDocHelper() call site.  :-(
+  mutable nsCOMPtr<nsIPrincipal> mActiveStoragePrincipal;
+
   // The principal to use for the content blocking allow list.
   nsCOMPtr<nsIPrincipal> mContentBlockingAllowListPrincipal;
 
   // See GetNextFormNumber and GetNextControlNumber.
   int32_t mNextFormNumber;
   int32_t mNextControlNumber;
 
  public:
--- a/dom/base/SerializedStackHolder.cpp
+++ b/dom/base/SerializedStackHolder.cpp
@@ -76,17 +76,18 @@ JSObject* SerializedStackHolder::ReadSta
   JS::RootedValue stackValue(aCx);
 
   {
     Maybe<nsJSPrincipals::AutoSetActiveWorkerPrincipal> set;
     if (mWorkerRef) {
       set.emplace(mWorkerRef->Private()->GetPrincipal());
     }
 
-    mHolder.Read(xpc::CurrentNativeGlobal(aCx), aCx, &stackValue, IgnoreErrors());
+    mHolder.Read(xpc::CurrentNativeGlobal(aCx), aCx, &stackValue,
+                 IgnoreErrors());
   }
 
   return stackValue.isObject() ? &stackValue.toObject() : nullptr;
 }
 
 UniquePtr<SerializedStackHolder> GetCurrentStackForNetMonitor(JSContext* aCx) {
   MOZ_ASSERT_IF(!NS_IsMainThread(),
                 GetCurrentThreadWorkerPrivate()->IsWatchedByDevtools());
--- a/dom/base/nsGlobalWindowInner.cpp
+++ b/dom/base/nsGlobalWindowInner.cpp
@@ -2395,16 +2395,21 @@ void nsGlobalWindowInner::UpdateTopInner
   }
 
   mTopInnerWindow->UpdateWebSocketCount(-(int32_t)mNumOfOpenWebSockets);
 }
 
 bool nsGlobalWindowInner::CanShareMemory(const nsID& aAgentClusterId) {
   MOZ_ASSERT(NS_IsMainThread());
 
+  if (StaticPrefs::
+          dom_postMessage_sharedArrayBuffer_bypassCOOP_COEP_insecure_enabled()) {
+    return true;
+  }
+
   if (!StaticPrefs::dom_postMessage_sharedArrayBuffer_withCOOP_COEP()) {
     return false;
   }
 
   MOZ_DIAGNOSTIC_ASSERT(GetDocGroup());
   // Ensure they are on the same agent cluster
   if (!GetDocGroup()->AgentClusterId().Equals(aAgentClusterId)) {
     return false;
@@ -7142,16 +7147,21 @@ void nsGlobalWindowInner::StorageAccessG
     }
   }
 
   // Reset the IndexedDB factory.
   mIndexedDB = nullptr;
 
   // Reset DOM Cache
   mCacheStorage = nullptr;
+
+  // Reset the active storage principal
+  if (mDoc) {
+    mDoc->ClearActiveStoragePrincipal();
+  }
 }
 
 mozilla::dom::TabGroup* nsPIDOMWindowInner::TabGroup() {
   return nsGlobalWindowInner::Cast(this)->TabGroupInner();
 }
 
 /* static */
 already_AddRefed<nsGlobalWindowInner> nsGlobalWindowInner::Create(
@@ -7208,16 +7218,27 @@ const nsIGlobalObject* nsPIDOMWindowInne
   return nsGlobalWindowInner::Cast(this);
 }
 
 void nsPIDOMWindowInner::SaveStorageAccessGranted(
     const nsACString& aPermissionKey) {
   if (!HasStorageAccessGranted(aPermissionKey)) {
     mStorageAccessGranted.AppendElement(aPermissionKey);
   }
+
+  nsGlobalWindowInner::Cast(this)->ClearActiveStoragePrincipal();
+}
+
+void nsGlobalWindowInner::ClearActiveStoragePrincipal() {
+  Document* doc = GetExtantDoc();
+  if (doc) {
+    doc->ClearActiveStoragePrincipal();
+  }
+
+  CallOnChildren(&nsGlobalWindowInner::ClearActiveStoragePrincipal);
 }
 
 bool nsPIDOMWindowInner::HasStorageAccessGranted(
     const nsACString& aPermissionKey) {
   return mStorageAccessGranted.Contains(aPermissionKey);
 }
 
 nsPIDOMWindowInner::nsPIDOMWindowInner(nsPIDOMWindowOuter* aOuterWindow,
--- a/dom/base/nsGlobalWindowInner.h
+++ b/dom/base/nsGlobalWindowInner.h
@@ -304,16 +304,18 @@ class nsGlobalWindowInner final : public
   bool DispatchEvent(mozilla::dom::Event& aEvent,
                      mozilla::dom::CallerType aCallerType,
                      mozilla::ErrorResult& aRv) override;
 
   void GetEventTargetParent(mozilla::EventChainPreVisitor& aVisitor) override;
 
   nsresult PostHandleEvent(mozilla::EventChainPostVisitor& aVisitor) override;
 
+  void ClearActiveStoragePrincipal();
+
   void Suspend();
   void Resume();
   virtual bool IsSuspended() const override;
 
   // Calling Freeze() on a window will automatically Suspend() it.  In
   // addition, the window and its children are further treated as no longer
   // suitable for interaction with the user.  For example, it may be marked
   // non-visible, cannot be focused, etc.  All worker threads are also frozen
--- a/dom/base/nsMappedAttributes.cpp
+++ b/dom/base/nsMappedAttributes.cpp
@@ -82,17 +82,17 @@ void* nsMappedAttributes::operator new(s
                                        uint32_t aAttrCount) noexcept(true) {
   size_t size = aSize + aAttrCount * sizeof(InternalAttr);
 
   // aSize will include the mAttrs buffer so subtract that.
   // We don't want to under-allocate, however, so do not subtract
   // if we have zero attributes. The zero attribute case only happens
   // for <body>'s mapped attributes
   if (aAttrCount != 0) {
-    size -= sizeof(void * [1]);
+    size -= sizeof(void* [1]);
   }
 
   if (sCachedMappedAttributeAllocations) {
     void* cached = sCachedMappedAttributeAllocations->SafeElementAt(aAttrCount);
     if (cached) {
       (*sCachedMappedAttributeAllocations)[aAttrCount] = nullptr;
       return cached;
     }
--- a/dom/base/nsPlainTextSerializer.cpp
+++ b/dom/base/nsPlainTextSerializer.cpp
@@ -63,17 +63,16 @@ static const char16_t kSPACE = ' ';
 constexpr int32_t kNoFlags = 0;
 
 static int32_t HeaderLevel(const nsAtom* aTag);
 static int32_t GetUnicharWidth(char16_t ucs);
 static int32_t GetUnicharStringWidth(const nsString& aString);
 
 // Someday may want to make this non-const:
 static const uint32_t TagStackSize = 500;
-static const uint32_t OLStackSize = 100;
 
 static bool gPreferenceInitialized = false;
 static bool gAlwaysIncludeRuby = false;
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsPlainTextSerializer)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsPlainTextSerializer)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsPlainTextSerializer)
@@ -259,34 +258,29 @@ nsPlainTextSerializer::nsPlainTextSerial
 
   // initialize the tag stack to zero:
   // The stack only ever contains pointers to static atoms, so they don't
   // need refcounting.
   mTagStack = new const nsAtom*[TagStackSize];
   mTagStackIndex = 0;
   mIgnoreAboveIndex = (uint32_t)kNotFound;
 
-  // initialize the OL stack, where numbers for ordered lists are kept
-  mOLStack = new int32_t[OLStackSize];
-  mOLStackIndex = 0;
-
   mULCount = 0;
 
   mIgnoredChildNodeLevel = 0;
 
   if (!gPreferenceInitialized) {
     Preferences::AddBoolVarCache(&gAlwaysIncludeRuby, PREF_ALWAYS_INCLUDE_RUBY,
                                  true);
     gPreferenceInitialized = true;
   }
 }
 
 nsPlainTextSerializer::~nsPlainTextSerializer() {
   delete[] mTagStack;
-  delete[] mOLStack;
   NS_WARNING_ASSERTION(mHeadLevel == 0, "Wrong head level!");
 }
 
 nsPlainTextSerializer::Settings::HeaderStrategy
 nsPlainTextSerializer::Settings::Convert(const int32_t aPrefHeaderStrategy) {
   HeaderStrategy result{HeaderStrategy::kIndentIncreasedWithHeaderLevel};
 
   switch (aPrefHeaderStrategy) {
@@ -370,16 +364,18 @@ nsPlainTextSerializer::Init(const uint32
     mLineBreaker = nsContentUtils::LineBreaker();
   }
 
   mLineBreakDue = false;
   mFloatingLines = -1;
 
   mPreformattedBlockBoundary = false;
 
+  MOZ_ASSERT(mOLStack.IsEmpty());
+
   return NS_OK;
 }
 
 bool nsPlainTextSerializer::GetLastBool(const nsTArray<bool>& aStack) {
   uint32_t size = aStack.Length();
   if (size == 0) {
     return false;
   }
@@ -605,16 +601,18 @@ nsPlainTextSerializer::GetOutputLength(u
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsPlainTextSerializer::AppendDocumentStart(Document* aDocument) {
   return NS_OK;
 }
 
+constexpr int32_t kOlStackDummyValue = 0;
+
 nsresult nsPlainTextSerializer::DoOpenContainer(const nsAtom* aTag) {
   if (IsIgnorableRubyAnnotation(aTag)) {
     // Ignorable ruby annotation shouldn't be replaced by a placeholder
     // character, neither any of its descendants.
     mIgnoredChildNodeLevel++;
     return NS_OK;
   }
   if (IsIgnorableScriptOrStyle(mElement)) {
@@ -738,51 +736,53 @@ nsresult nsPlainTextSerializer::DoOpenCo
       // Bypass |Write| so that the TAB isn't compressed away.
       AddToLine(u"\t", 1);
       mInWhitespace = true;
     } else {
       SetLastBool(mHasWrittenCellsForRow, true);
     }
   } else if (aTag == nsGkAtoms::ul) {
     // Indent here to support nested lists, which aren't included in li :-(
-    EnsureVerticalSpace(mULCount + mOLStackIndex == 0 ? 1 : 0);
+    EnsureVerticalSpace(IsInOlOrUl() ? 0 : 1);
     // Must end the current line before we change indention
     mCurrentLine.mIndentation.mLength += kIndentSizeList;
     mULCount++;
   } else if (aTag == nsGkAtoms::ol) {
-    EnsureVerticalSpace(mULCount + mOLStackIndex == 0 ? 1 : 0);
+    EnsureVerticalSpace(IsInOlOrUl() ? 0 : 1);
     if (mSettings.HasFlag(nsIDocumentEncoder::OutputFormatted)) {
       // Must end the current line before we change indention
-      if (mOLStackIndex < OLStackSize) {
-        nsAutoString startAttr;
-        int32_t startVal = 1;
-        if (NS_SUCCEEDED(GetAttributeValue(nsGkAtoms::start, startAttr))) {
-          nsresult rv = NS_OK;
-          startVal = startAttr.ToInteger(&rv);
-          if (NS_FAILED(rv)) startVal = 1;
+      nsAutoString startAttr;
+      int32_t startVal = 1;
+      if (NS_SUCCEEDED(GetAttributeValue(nsGkAtoms::start, startAttr))) {
+        nsresult rv = NS_OK;
+        startVal = startAttr.ToInteger(&rv);
+        if (NS_FAILED(rv)) {
+          startVal = 1;
         }
-        mOLStack[mOLStackIndex++] = startVal;
       }
+      mOLStack.AppendElement(startVal);
     } else {
-      mOLStackIndex++;
+      mOLStack.AppendElement(kOlStackDummyValue);
     }
     mCurrentLine.mIndentation.mLength += kIndentSizeList;  // see ul
   } else if (aTag == nsGkAtoms::li &&
              mSettings.HasFlag(nsIDocumentEncoder::OutputFormatted)) {
     if (mTagStackIndex > 1 && IsInOL()) {
-      if (mOLStackIndex > 0) {
+      if (!mOLStack.IsEmpty()) {
         nsAutoString valueAttr;
         if (NS_SUCCEEDED(GetAttributeValue(nsGkAtoms::value, valueAttr))) {
           nsresult rv = NS_OK;
           int32_t valueAttrVal = valueAttr.ToInteger(&rv);
-          if (NS_SUCCEEDED(rv)) mOLStack[mOLStackIndex - 1] = valueAttrVal;
+          if (NS_SUCCEEDED(rv)) {
+            mOLStack.LastElement() = valueAttrVal;
+          }
         }
         // This is what nsBulletFrame does for OLs:
-        mCurrentLine.mIndentation.mHeader.AppendInt(
-            mOLStack[mOLStackIndex - 1]++, 10);
+        mCurrentLine.mIndentation.mHeader.AppendInt(mOLStack.LastElement(), 10);
+        mOLStack.LastElement()++;
       } else {
         mCurrentLine.mIndentation.mHeader.Append(char16_t('#'));
       }
 
       mCurrentLine.mIndentation.mHeader.Append(char16_t('.'));
 
     } else {
       static const char bulletCharArray[] = "*o+#";
@@ -969,27 +969,28 @@ nsresult nsPlainTextSerializer::DoCloseC
     if (mFloatingLines < 0) mFloatingLines = 0;
     mLineBreakDue = true;
   } else if (aTag == nsGkAtoms::pre) {
     mFloatingLines = GetLastBool(mIsInCiteBlockquote) ? 0 : 1;
     mLineBreakDue = true;
   } else if (aTag == nsGkAtoms::ul) {
     mOutputManager->Flush(mCurrentLine);
     mCurrentLine.mIndentation.mLength -= kIndentSizeList;
-    if (--mULCount + mOLStackIndex == 0) {
+    --mULCount;
+    if (!IsInOlOrUl()) {
       mFloatingLines = 1;
       mLineBreakDue = true;
     }
   } else if (aTag == nsGkAtoms::ol) {
     mOutputManager->Flush(mCurrentLine);  // Doing this after decreasing
                                           // OLStackIndex would be wrong.
     mCurrentLine.mIndentation.mLength -= kIndentSizeList;
-    NS_ASSERTION(mOLStackIndex, "Wrong OLStack level!");
-    mOLStackIndex--;
-    if (mULCount + mOLStackIndex == 0) {
+    MOZ_ASSERT(!mOLStack.IsEmpty(), "Wrong OLStack level!");
+    mOLStack.RemoveLastElement();
+    if (!IsInOlOrUl()) {
       mFloatingLines = 1;
       mLineBreakDue = true;
     }
   } else if (aTag == nsGkAtoms::dl) {
     mFloatingLines = 1;
     mLineBreakDue = true;
   } else if (aTag == nsGkAtoms::dd) {
     mOutputManager->Flush(mCurrentLine);
@@ -1760,16 +1761,20 @@ bool nsPlainTextSerializer::IsInOL() con
       // If a UL is reached first, LI belongs the UL nested in OL.
       return false;
     }
   }
   // We may reach here for orphan LI's.
   return false;
 }
 
+bool nsPlainTextSerializer::IsInOlOrUl() const {
+  return (mULCount > 0) || !mOLStack.IsEmpty();
+}
+
 /*
   @return 0 = no header, 1 = h1, ..., 6 = h6
 */
 int32_t HeaderLevel(const nsAtom* aTag) {
   if (aTag == nsGkAtoms::h1) {
     return 1;
   }
   if (aTag == nsGkAtoms::h2) {
--- a/dom/base/nsPlainTextSerializer.h
+++ b/dom/base/nsPlainTextSerializer.h
@@ -101,16 +101,17 @@ class nsPlainTextSerializer final : publ
 
   void Write(const nsAString& aString);
 
   // @return true, iff the elements' whitespace and newline characters have to
   //         be preserved according to its style or because it's a `<pre>`
   //         element.
   bool IsElementPreformatted() const;
   bool IsInOL() const;
+  bool IsInOlOrUl() const;
   bool IsCurrentNodeConverted() const;
   bool MustSuppressLeaf() const;
 
   /**
    * Returns the local name of the element as an atom if the element is an
    * HTML element and the atom is a static atom. Otherwise, nullptr is returned.
    */
   static nsAtom* GetIdForContent(nsIContent* aContent);
@@ -360,18 +361,17 @@ class nsPlainTextSerializer final : publ
   // CSS preformatted elements, so that we can tell if the text inside them
   // should be formatted.
   std::stack<bool> mPreformatStack;
 
   // Content in the stack above this index should be ignored:
   uint32_t mIgnoreAboveIndex;
 
   // The stack for ordered lists
-  int32_t* mOLStack;
-  uint32_t mOLStackIndex;
+  AutoTArray<int32_t, 100> mOLStack;
 
   uint32_t mULCount;
 
   RefPtr<mozilla::intl::LineBreaker> mLineBreaker;
 
   // Conveniance constant. It would be nice to have it as a const static
   // variable, but that causes issues with OpenBSD and module unloading.
   const nsString kSpace;
--- a/dom/base/test/gtest/TestContentUtils.cpp
+++ b/dom/base/test/gtest/TestContentUtils.cpp
@@ -9,36 +9,36 @@
 #include "jsapi.h"
 #include "nsContentUtils.h"
 #include "mozilla/CycleCollectedJSContext.h"
 #include "mozilla/dom/SimpleGlobalObject.h"
 
 TEST(DOM_Base_ContentUtils, StringifyJSON_EmptyValue)
 {
   JS::RootedObject globalObject(
-    mozilla::dom::RootingCx(),
-    mozilla::dom::SimpleGlobalObject::Create(
-      mozilla::dom::SimpleGlobalObject::GlobalType::BindingDetail));
+      mozilla::dom::RootingCx(),
+      mozilla::dom::SimpleGlobalObject::Create(
+          mozilla::dom::SimpleGlobalObject::GlobalType::BindingDetail));
   mozilla::dom::AutoJSAPI jsAPI;
   ASSERT_TRUE(jsAPI.Init(globalObject));
   JSContext* cx = jsAPI.cx();
   nsAutoString serializedValue;
 
   JS::RootedValue jsValue(cx);
   ASSERT_TRUE(nsContentUtils::StringifyJSON(cx, &jsValue, serializedValue));
 
   ASSERT_TRUE(serializedValue.EqualsLiteral("null"));
 }
 
 TEST(DOM_Base_ContentUtils, StringifyJSON_Object)
 {
   JS::RootedObject globalObject(
-    mozilla::dom::RootingCx(),
-    mozilla::dom::SimpleGlobalObject::Create(
-      mozilla::dom::SimpleGlobalObject::GlobalType::BindingDetail));
+      mozilla::dom::RootingCx(),
+      mozilla::dom::SimpleGlobalObject::Create(
+          mozilla::dom::SimpleGlobalObject::GlobalType::BindingDetail));
   mozilla::dom::AutoJSAPI jsAPI;
   ASSERT_TRUE(jsAPI.Init(globalObject));
   JSContext* cx = jsAPI.cx();
   nsAutoString serializedValue;
 
   JS::RootedObject jsObj(cx, JS_NewPlainObject(cx));
   JS::RootedString valueStr(cx, JS_NewStringCopyZ(cx, "Hello World!"));
   ASSERT_TRUE(JS_DefineProperty(cx, jsObj, "key1", valueStr, JSPROP_ENUMERATE));
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -1523,18 +1523,18 @@ template <typename T>
 static inline JSObject* WrapNativeISupports(JSContext* cx, T* p,
                                             nsWrapperCache* cache) {
   JS::Rooted<JSObject*> retval(cx);
   {
     xpcObjectHelper helper(ToSupports(p), cache);
     JS::Rooted<JSObject*> scope(cx, JS::CurrentGlobalOrNull(cx));
     JS::Rooted<JS::Value> v(cx);
     retval = XPCOMObjectToJsval(cx, scope, helper, nullptr, false, &v)
-      ? v.toObjectOrNull()
-      : nullptr;
+                 ? v.toObjectOrNull()
+                 : nullptr;
   }
   return retval;
 }
 
 // Wrapping of our native parent, for cases when it's a WebIDL object.
 template <typename T, bool hasWrapObject = NativeHasMember<T>::WrapObject>
 struct WrapNativeHelper {
   static inline JSObject* Wrap(JSContext* cx, T* parent,
--- a/dom/canvas/WebGLTexelConversions.h
+++ b/dom/canvas/WebGLTexelConversions.h
@@ -21,26 +21,26 @@
  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
 #ifndef WEBGLTEXELCONVERSIONS_H_
-#  define WEBGLTEXELCONVERSIONS_H_
+#define WEBGLTEXELCONVERSIONS_H_
 
-#  ifdef __SUNPRO_CC
-#    define __restrict
-#  endif
+#ifdef __SUNPRO_CC
+#  define __restrict
+#endif
 
-#  include "WebGLTypes.h"
-#  include <stdint.h>
-#  include "mozilla/Attributes.h"
-#  include "mozilla/Casting.h"
+#include "WebGLTypes.h"
+#include <stdint.h>
+#include "mozilla/Attributes.h"
+#include "mozilla/Casting.h"
 
 namespace mozilla {
 
 bool ConvertImage(size_t width, size_t height, const void* srcBegin,
                   size_t srcStride, gl::OriginPos srcOrigin,
                   WebGLTexelFormat srcFormat, bool srcPremultiplied,
                   void* dstBegin, size_t dstStride, gl::OriginPos dstOrigin,
                   WebGLTexelFormat dstFormat, bool dstPremultiplied,
--- a/dom/events/EventStateManager.h
+++ b/dom/events/EventStateManager.h
@@ -1074,18 +1074,18 @@ class EventStateManager : public nsSuppo
    * aData - information pertaining to a drag started in a child process
    * aPrincipal - the triggering principal of the drag, or null if it's from
    *              browser chrome or OS
    */
   MOZ_CAN_RUN_SCRIPT
   bool DoDefaultDragStart(nsPresContext* aPresContext,
                           WidgetDragEvent* aDragEvent,
                           dom::DataTransfer* aDataTransfer,
-                          bool aAllowEmptyDataTransfer,
-                          nsIContent* aDragTarget, dom::Selection* aSelection,
+                          bool aAllowEmptyDataTransfer, nsIContent* aDragTarget,
+                          dom::Selection* aSelection,
                           dom::RemoteDragStartData* aDragStartData,
                           nsIPrincipal* aPrincipal,
                           nsIContentSecurityPolicy* aCsp);
 
   bool IsTrackingDragGesture() const { return mGestureDownContent != nullptr; }
   /**
    * Set the fields of aEvent to reflect the mouse position and modifier keys
    * that were set when the user first pressed the mouse button (stored by
--- a/dom/fetch/FetchDriver.cpp
+++ b/dom/fetch/FetchDriver.cpp
@@ -256,17 +256,17 @@ AlternativeDataStreamListener::OnStartRe
 
 NS_IMETHODIMP
 AlternativeDataStreamListener::OnDataAvailable(nsIRequest* aRequest,
                                                nsIInputStream* aInputStream,
                                                uint64_t aOffset,
                                                uint32_t aCount) {
   if (mStatus == AlternativeDataStreamListener::LOADING) {
     MOZ_ASSERT(mPipeAlternativeOutputStream);
-    uint32_t read;
+    uint32_t read = 0;
     return aInputStream->ReadSegments(
         NS_CopySegmentToStream, mPipeAlternativeOutputStream, aCount, &read);
   }
   if (mStatus == AlternativeDataStreamListener::FALLBACK) {
     MOZ_ASSERT(mFetchDriver);
     return mFetchDriver->OnDataAvailable(aRequest, aInputStream, aOffset,
                                          aCount);
   }
--- a/dom/media/ForwardedInputTrack.cpp
+++ b/dom/media/ForwardedInputTrack.cpp
@@ -81,18 +81,19 @@ void ForwardedInputTrack::SetInput(Media
     TRACK_LOG(LogLevel::Debug, ("ForwardedInputTrack %p adding direct listener "
                                 "%p. Forwarding to input track %p.",
                                 this, listener.get(), aPort->GetSource()));
     source->AddDirectListenerImpl(do_AddRef(listener));
   }
 }
 
 void ForwardedInputTrack::ProcessInputImpl(MediaTrack* aSource,
-                                           MediaSegment* aSegment, GraphTime aFrom,
-                                           GraphTime aTo, uint32_t aFlags) {
+                                           MediaSegment* aSegment,
+                                           GraphTime aFrom, GraphTime aTo,
+                                           uint32_t aFlags) {
   GraphTime next;
   for (GraphTime t = aFrom; t < aTo; t = next) {
     MediaInputPort::InputInterval interval =
         MediaInputPort::GetNextInputInterval(mInputPort, t);
     interval.mEnd = std::min(interval.mEnd, aTo);
 
     const bool inputEnded =
         !aSource ||
--- a/dom/media/MediaDevices.cpp
+++ b/dom/media/MediaDevices.cpp
@@ -160,17 +160,16 @@ already_AddRefed<Promise> MediaDevices::
               }
               infos.AppendElement(MakeRefPtr<MediaDeviceInfo>(
                   device->mID, device->mKind, label, device->mGroupID));
             }
             p->MaybeResolve(std::move(infos));
           },
           [this, self, p](const RefPtr<MediaMgrError>& error) {
             nsPIDOMWindowInner* window = GetWindowIfCurrent();
-            MOZ_DIAGNOSTIC_ASSERT(!window, "Should only fail after navigation");
             if (!window) {
               return;  // Leave Promise pending after navigation by design.
             }
             p->MaybeReject(MakeRefPtr<MediaStreamError>(window, *error));
           });
   return p.forget();
 }
 
--- a/dom/media/Tracing.h
+++ b/dom/media/Tracing.h
@@ -50,19 +50,20 @@ void StartAudioCallbackTracing();
 #  define TRACE_AUDIO_CALLBACK()                                              \
     AutoTracer trace(gAudioCallbackTraceLogger, FUNCTION_SIGNATURE, getpid(), \
                      0);
 #  define TRACE_AUDIO_CALLBACK_BUDGET(aFrames, aSampleRate)                    \
     AutoTracer budget(gAudioCallbackTraceLogger, "Real-time budget", getpid(), \
                       1, AutoTracer::EventType::BUDGET, aFrames, aSampleRate);
 #  define TRACE_AUDIO_CALLBACK_COMMENT(aFmt, ...)                             \
     AutoTracer trace(gAudioCallbackTraceLogger, FUNCTION_SIGNATURE, getpid(), \
-                     0, AutoTracer::EventType::DURATION, aFmt, ## __VA_ARGS__);
+                     0, AutoTracer::EventType::DURATION, aFmt, ##__VA_ARGS__);
 #  define TRACE()                                                \
-      AutoTracer trace(gAudioCallbackTraceLogger, FUNCTION_SIGNATURE, getpid(),\
+    AutoTracer trace(                                            \
+        gAudioCallbackTraceLogger, FUNCTION_SIGNATURE, getpid(), \
         std::hash<std::thread::id>{}(std::this_thread::get_id()));
 #  define TRACE_COMMENT(aFmt, ...)                                             \
     AutoTracer trace(gAudioCallbackTraceLogger, FUNCTION_SIGNATURE, getpid(),  \
                      std::hash<std::thread::id>{}(std::this_thread::get_id()), \
                      AutoTracer::EventType::DURATION, aFmt, ##__VA_ARGS__);
 #else
 #  define TRACE_AUDIO_CALLBACK()
 #  define TRACE_AUDIO_CALLBACK_BUDGET(aFrames, aSampleRate)
--- a/dom/media/VideoFrameContainer.cpp
+++ b/dom/media/VideoFrameContainer.cpp
@@ -2,17 +2,17 @@
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "VideoFrameContainer.h"
 
 #ifdef MOZ_WIDGET_ANDROID
-#include "GLImages.h"  // for SurfaceTextureImage
+#  include "GLImages.h"  // for SurfaceTextureImage
 #endif
 #include "MediaDecoderOwner.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/AbstractThread.h"
 
 using namespace mozilla::layers;
 
 namespace mozilla {
@@ -97,17 +97,17 @@ static void NotifySetCurrent(Image* aIma
   image->OnSetCurrent();
 }
 #endif
 
 void VideoFrameContainer::SetCurrentFrame(const gfx::IntSize& aIntrinsicSize,
                                           Image* aImage,
                                           const TimeStamp& aTargetTime) {
 #ifdef MOZ_WIDGET_ANDROID
-    NotifySetCurrent(aImage);
+  NotifySetCurrent(aImage);
 #endif
   if (aImage) {
     MutexAutoLock lock(mMutex);
     AutoTArray<ImageContainer::NonOwningImage, 1> imageList;
     imageList.AppendElement(
         ImageContainer::NonOwningImage(aImage, aTargetTime, ++mFrameID));
     SetCurrentFramesLocked(aIntrinsicSize, imageList);
   } else {
--- a/dom/media/ipc/RemoteAudioDecoder.h
+++ b/dom/media/ipc/RemoteAudioDecoder.h
@@ -30,16 +30,17 @@ class RemoteAudioDecoderParent final : p
                            const CreateDecoderParams::OptionSet& aOptions,
                            TaskQueue* aManagerTaskQueue,
                            TaskQueue* aDecodeTaskQueue, bool* aSuccess,
                            nsCString* aErrorDescription);
 
  protected:
   MediaResult ProcessDecodedData(const MediaDataDecoder::DecodedData& aData,
                                  DecodedOutputIPDL& aDecodedData) override;
+
  private:
   // Can only be accessed from the manager thread
   // Note: we can't keep a reference to the original AudioInfo here
   // because unlike in typical MediaDataDecoder situations, we're being
   // passed a deserialized AudioInfo from RecvPRemoteDecoderConstructor
   // which is destroyed when RecvPRemoteDecoderConstructor returns.
   const AudioInfo mAudioInfo;
 };
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -898,17 +898,17 @@ tags=mtg capturestream
 [test_mediarecorder_creation_fail.html]
 tags=mtg
 [test_mediarecorder_fires_start_event_once_when_erroring.html]
 tags=mtg
 [test_mediarecorder_onerror_pause.html]
 scheme=https
 tags=mtg
 [test_mediarecorder_pause_resume_video.html]
-skip-if = toolkit == 'android' # android(bug 1232305)
+skip-if = toolkit == 'android' || (webrender && debug && os == "linux") || (os == "win" && os_version == "10.0" && (debug || asan)) # android(bug 1232305) win&linux bug 1522739
 [test_mediarecorder_playback_can_repeat.html]
 tags=mtg
 [test_mediarecorder_principals.html]
 skip-if = toolkit == 'android' || (os == 'win' && os_version == '10.0' && webrender) # android(bug 1232305), Bug 1453375
 tags=mtg
 [test_mediarecorder_record_4ch_audiocontext.html]
 tags=mtg
 [test_mediarecorder_record_addtracked_stream.html]
--- a/dom/media/webaudio/blink/IIRFilter.cpp
+++ b/dom/media/webaudio/blink/IIRFilter.cpp
@@ -101,18 +101,18 @@ void IIRFilter::process(const float* sou
     // next output.
     m_xBuffer[m_bufferIndex] = sourceP[n];
     m_yBuffer[m_bufferIndex] = yn;
 
     m_bufferIndex = (m_bufferIndex + 1) & (kBufferLength - 1);
 
     // Avoid introducing a stream of subnormals
     destP[n] = WebCore::DenormalDisabler::flushDenormalFloatToZero(yn);
-    MOZ_ASSERT(destP[n] == 0.0 || fabs(destP[n]) > FLT_MIN,
-               "output should not be subnormal");
+    MOZ_ASSERT(destP[n] == 0.0 || fabs(destP[n]) > FLT_MIN || IsNaN(destP[n]),
+               "output should not be subnormal, but can be NaN");
   }
 }
 
 void IIRFilter::getFrequencyResponse(int nFrequencies, const float* frequency,
                                      float* magResponse, float* phaseResponse) {
   // Evaluate the z-transform of the filter at the given normalized frequencies
   // from 0 to 1. (One corresponds to the Nyquist frequency.)
   //
--- a/dom/media/webrtc/MediaEngineTabVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineTabVideoSource.cpp
@@ -238,18 +238,17 @@ void MediaEngineTabVideoSource::SetTrack
   mTrack = aTrack;
   mPrincipalHandle = aPrincipal;
 }
 
 nsresult MediaEngineTabVideoSource::Start() {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == kAllocated);
 
-  NS_DispatchToMainThread(
-      new StartRunnable(this, mTrack, mPrincipalHandle));
+  NS_DispatchToMainThread(new StartRunnable(this, mTrack, mPrincipalHandle));
   mState = kStarted;
 
   return NS_OK;
 }
 
 void MediaEngineTabVideoSource::Draw() {
   MOZ_ASSERT(NS_IsMainThread());
 
--- a/dom/plugins/base/nptypes.h
+++ b/dom/plugins/base/nptypes.h
@@ -1,89 +1,89 @@
 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nptypes_h_
-#  define nptypes_h_
+#define nptypes_h_
 
 /*
  * Header file for ensuring that C99 types ([u]int32_t, [u]int64_t and bool) and
  * true/false macros are available.
  */
 
-#  if defined(WIN32)
+#if defined(WIN32)
 /*
  * Win32 and OS/2 don't know C99, so define [u]int_16/32/64 here. The bool
  * is predefined tho, both in C and C++.
  */
 typedef short int16_t;
 typedef unsigned short uint16_t;
 typedef int int32_t;
 typedef unsigned int uint32_t;
 typedef long long int64_t;
 typedef unsigned long long uint64_t;
-#  elif defined(_AIX) || defined(__sun) || defined(__osf__) || \
-      defined(IRIX) || defined(HPUX)
+#elif defined(_AIX) || defined(__sun) || defined(__osf__) || defined(IRIX) || \
+    defined(HPUX)
 /*
  * AIX and SunOS ship a inttypes.h header that defines [u]int32_t,
  * but not bool for C.
  */
-#    include <inttypes.h>
+#  include <inttypes.h>
 
-#    ifndef __cplusplus
+#  ifndef __cplusplus
 typedef int bool;
-#      define true 1
-#      define false 0
-#    endif
-#  elif defined(bsdi) || defined(FREEBSD) || defined(OPENBSD)
+#    define true 1
+#    define false 0
+#  endif
+#elif defined(bsdi) || defined(FREEBSD) || defined(OPENBSD)
 /*
  * BSD/OS, FreeBSD, and OpenBSD ship sys/types.h that define int32_t and
  * u_int32_t.
  */
-#    include <sys/types.h>
+#  include <sys/types.h>
 
 /*
  * BSD/OS ships no header that defines uint32_t, nor bool (for C)
  */
-#    if defined(bsdi)
+#  if defined(bsdi)
 typedef u_int32_t uint32_t;
 typedef u_int64_t uint64_t;
 
-#      if !defined(__cplusplus)
+#    if !defined(__cplusplus)
 typedef int bool;
-#        define true 1
-#        define false 0
-#      endif
-#    else
+#      define true 1
+#      define false 0
+#    endif
+#  else
 /*
  * FreeBSD and OpenBSD define uint32_t and bool.
  */
-#      include <inttypes.h>
-#      include <stdbool.h>
-#    endif
-#  elif defined(BEOS)
 #    include <inttypes.h>
-#  else
+#    include <stdbool.h>
+#  endif
+#elif defined(BEOS)
+#  include <inttypes.h>
+#else
 /*
  * For those that ship a standard C99 stdint.h header file, include
  * it. Can't do the same for stdbool.h tho, since some systems ship
  * with a stdbool.h file that doesn't compile!
  */
-#    include <stdint.h>
+#  include <stdint.h>
 
-#    ifndef __cplusplus
-#      if !defined(__GNUC__) || (__GNUC__ > 2 || __GNUC_MINOR__ > 95)
-#        include <stdbool.h>
-#      else
+#  ifndef __cplusplus
+#    if !defined(__GNUC__) || (__GNUC__ > 2 || __GNUC_MINOR__ > 95)
+#      include <stdbool.h>
+#    else
 /*
  * GCC 2.91 can't deal with a typedef for bool, but a #define
  * works.
  */
-#        define bool int
-#        define true 1
-#        define false 0
-#      endif
+#      define bool int
+#      define true 1
+#      define false 0
 #    endif
 #  endif
+#endif
 
 #endif /* nptypes_h_ */
new file mode 100644
--- /dev/null
+++ b/dom/security/test/crashtests/1583044.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+<title>Bug 1583044</title>
+<script>
+  function testOpenMozIcon() {
+    window.location.href = "moz-icon://.pdf?size=128";
+  }
+</script>
+</head>
+<body onload="testOpenMozIcon();"></body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/crashtests/crashtests.list
@@ -0,0 +1,1 @@
+load 1583044.html
--- a/dom/serviceworkers/ServiceWorkerPrivateImpl.cpp
+++ b/dom/serviceworkers/ServiceWorkerPrivateImpl.cpp
@@ -60,18 +60,18 @@ ServiceWorkerPrivateImpl::RAIIActorPtrHo
 ServiceWorkerPrivateImpl::RAIIActorPtrHolder::~RAIIActorPtrHolder() {
   AssertIsOnMainThread();
 
   mDestructorPromiseHolder.ResolveIfExists(true, __func__);
 
   mActor->MaybeSendDelete();
 }
 
-RemoteWorkerControllerChild* ServiceWorkerPrivateImpl::RAIIActorPtrHolder::
-operator->() const {
+RemoteWorkerControllerChild*
+    ServiceWorkerPrivateImpl::RAIIActorPtrHolder::operator->() const {
   AssertIsOnMainThread();
 
   return get();
 }
 
 RemoteWorkerControllerChild* ServiceWorkerPrivateImpl::RAIIActorPtrHolder::get()
     const {
   AssertIsOnMainThread();
--- a/dom/serviceworkers/ServiceWorkerRegistrationDescriptor.cpp
+++ b/dom/serviceworkers/ServiceWorkerRegistrationDescriptor.cpp
@@ -62,35 +62,37 @@ ServiceWorkerRegistrationDescriptor::Ser
 ServiceWorkerRegistrationDescriptor::ServiceWorkerRegistrationDescriptor(
     const ServiceWorkerRegistrationDescriptor& aRight) {
   // UniquePtr doesn't have a default copy constructor, so we can't rely
   // on default copy construction.  Use the assignment operator to
   // minimize duplication.
   operator=(aRight);
 }
 
-ServiceWorkerRegistrationDescriptor& ServiceWorkerRegistrationDescriptor::
-operator=(const ServiceWorkerRegistrationDescriptor& aRight) {
+ServiceWorkerRegistrationDescriptor&
+ServiceWorkerRegistrationDescriptor::operator=(
+    const ServiceWorkerRegistrationDescriptor& aRight) {
   if (this == &aRight) {
     return *this;
   }
   mData.reset();
   mData = MakeUnique<IPCServiceWorkerRegistrationDescriptor>(*aRight.mData);
   MOZ_DIAGNOSTIC_ASSERT(IsValid());
   return *this;
 }
 
 ServiceWorkerRegistrationDescriptor::ServiceWorkerRegistrationDescriptor(
     ServiceWorkerRegistrationDescriptor&& aRight)
     : mData(std::move(aRight.mData)) {
   MOZ_DIAGNOSTIC_ASSERT(IsValid());
 }
 
-ServiceWorkerRegistrationDescriptor& ServiceWorkerRegistrationDescriptor::
-operator=(ServiceWorkerRegistrationDescriptor&& aRight) {
+ServiceWorkerRegistrationDescriptor&
+ServiceWorkerRegistrationDescriptor::operator=(
+    ServiceWorkerRegistrationDescriptor&& aRight) {
   mData.reset();
   mData = std::move(aRight.mData);
   MOZ_DIAGNOSTIC_ASSERT(IsValid());
   return *this;
 }
 
 ServiceWorkerRegistrationDescriptor::~ServiceWorkerRegistrationDescriptor() {
   // Non-default destructor to avoid exposing the IPC type in the header.
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -4965,16 +4965,21 @@ const nsAString& WorkerPrivate::Id() {
   MOZ_ASSERT(!mId.IsEmpty());
 
   return mId;
 }
 
 bool WorkerPrivate::CanShareMemory(const nsID& aAgentClusterId) {
   AssertIsOnWorkerThread();
 
+  if (StaticPrefs::
+          dom_postMessage_sharedArrayBuffer_bypassCOOP_COEP_insecure_enabled()) {
+    return true;
+  }
+
   if (!StaticPrefs::dom_postMessage_sharedArrayBuffer_withCOOP_COEP()) {
     return false;
   }
 
   Maybe<ClientInfo> clientInfo = GetClientInfo();
   MOZ_DIAGNOSTIC_ASSERT(clientInfo.isSome());
 
   // Ensure they are on the same agent cluster
--- a/gfx/2d/ScaledFontFontconfig.cpp
+++ b/gfx/2d/ScaledFontFontconfig.cpp
@@ -34,17 +34,19 @@ ScaledFontFontconfig::ScaledFontFontconf
     RefPtr<SharedFTFace>&& aFace, const InstanceData& aInstanceData,
     const RefPtr<UnscaledFont>& aUnscaledFont, Float aSize)
     : ScaledFontBase(aUnscaledFont, aSize),
       mFace(std::move(aFace)),
       mInstanceData(aInstanceData) {}
 
 bool ScaledFontFontconfig::UseSubpixelPosition() const {
   return mInstanceData.mAntialias != AntialiasMode::NONE &&
-         FT_IS_SCALABLE(mFace->GetFace());
+         FT_IS_SCALABLE(mFace->GetFace()) &&
+         (mInstanceData.mHinting == FontHinting::NONE ||
+          mInstanceData.mHinting == FontHinting::LIGHT);
 }
 
 #ifdef USE_SKIA
 SkTypeface* ScaledFontFontconfig::CreateSkTypeface() {
   SkPixelGeometry geo = mInstanceData.mFlags & InstanceData::SUBPIXEL_BGR
                             ? (mInstanceData.mFlags & InstanceData::LCD_VERTICAL
                                    ? kBGR_V_SkPixelGeometry
                                    : kBGR_H_SkPixelGeometry)
--- a/gfx/2d/Swizzle.cpp
+++ b/gfx/2d/Swizzle.cpp
@@ -149,26 +149,28 @@ void SwizzleRow_SSE2(const uint8_t*, uin
     FORMAT_CASE_ROW(                                          \
         aSrcFormat, aDstFormat,                               \
         SwizzleRow_SSE2<ShouldSwapRB(aSrcFormat, aDstFormat), \
                         ShouldForceOpaque(aSrcFormat, aDstFormat)>)
 
 template <bool aSwapRB>
 void UnpackRowRGB24_SSSE3(const uint8_t*, uint8_t*, int32_t);
 
-#define UNPACK_ROW_RGB_SSSE3(aDstFormat)             \
-  FORMAT_CASE_ROW(SurfaceFormat::R8G8B8, aDstFormat, \
-                  UnpackRowRGB24_SSSE3<ShouldSwapRB(SurfaceFormat::R8G8B8, aDstFormat)>)
+#  define UNPACK_ROW_RGB_SSSE3(aDstFormat) \
+    FORMAT_CASE_ROW(                       \
+        SurfaceFormat::R8G8B8, aDstFormat, \
+        UnpackRowRGB24_SSSE3<ShouldSwapRB(SurfaceFormat::R8G8B8, aDstFormat)>)
 
 template <bool aSwapRB>
 void UnpackRowRGB24_AVX2(const uint8_t*, uint8_t*, int32_t);
 
-#define UNPACK_ROW_RGB_AVX2(aDstFormat)              \
-  FORMAT_CASE_ROW(SurfaceFormat::R8G8B8, aDstFormat, \
-                  UnpackRowRGB24_AVX2<ShouldSwapRB(SurfaceFormat::R8G8B8, aDstFormat)>)
+#  define UNPACK_ROW_RGB_AVX2(aDstFormat)  \
+    FORMAT_CASE_ROW(                       \
+        SurfaceFormat::R8G8B8, aDstFormat, \
+        UnpackRowRGB24_AVX2<ShouldSwapRB(SurfaceFormat::R8G8B8, aDstFormat)>)
 
 #endif
 
 #ifdef USE_NEON
 /**
  * ARM NEON optimizations
  */
 
@@ -886,19 +888,20 @@ void UnpackRowRGB24(const uint8_t* aSrc,
     src -= 3;
   }
 }
 
 // Force instantiation of swizzle variants here.
 template void UnpackRowRGB24<false>(const uint8_t*, uint8_t*, int32_t);
 template void UnpackRowRGB24<true>(const uint8_t*, uint8_t*, int32_t);
 
-#define UNPACK_ROW_RGB(aDstFormat)                   \
-  FORMAT_CASE_ROW(SurfaceFormat::R8G8B8, aDstFormat, \
-                  UnpackRowRGB24<ShouldSwapRB(SurfaceFormat::R8G8B8, aDstFormat)>)
+#define UNPACK_ROW_RGB(aDstFormat)       \
+  FORMAT_CASE_ROW(                       \
+      SurfaceFormat::R8G8B8, aDstFormat, \
+      UnpackRowRGB24<ShouldSwapRB(SurfaceFormat::R8G8B8, aDstFormat)>)
 
 bool SwizzleData(const uint8_t* aSrc, int32_t aSrcStride,
                  SurfaceFormat aSrcFormat, uint8_t* aDst, int32_t aDstStride,
                  SurfaceFormat aDstFormat, const IntSize& aSize) {
   if (aSize.IsEmpty()) {
     return true;
   }
   IntSize size = CollapseSize(aSize, aSrcStride, aDstStride);
--- a/gfx/2d/Swizzle.h
+++ b/gfx/2d/Swizzle.h
@@ -40,24 +40,27 @@ GFX2D_API bool SwizzleData(const uint8_t
                            SurfaceFormat aSrcFormat, uint8_t* aDst,
                            int32_t aDstStride, SurfaceFormat aDstFormat,
                            const IntSize& aSize);
 
 /**
  * Swizzles source and writes it to destination. Source and destination may be
  * the same to swizzle in-place.
  */
-typedef void (*SwizzleRowFn)(const uint8_t* aSrc, uint8_t* aDst, int32_t aLength);
+typedef void (*SwizzleRowFn)(const uint8_t* aSrc, uint8_t* aDst,
+                             int32_t aLength);
 
 /**
  * Get a function pointer to perform premultiplication between two formats.
  */
-GFX2D_API SwizzleRowFn PremultiplyRow(SurfaceFormat aSrcFormat, SurfaceFormat aDstFormat);
+GFX2D_API SwizzleRowFn PremultiplyRow(SurfaceFormat aSrcFormat,
+                                      SurfaceFormat aDstFormat);
 
 /**
  * Get a function pointer to perform swizzling between two formats.
  */
-GFX2D_API SwizzleRowFn SwizzleRow(SurfaceFormat aSrcFormat, SurfaceFormat aDstFormat);
+GFX2D_API SwizzleRowFn SwizzleRow(SurfaceFormat aSrcFormat,
+                                  SurfaceFormat aDstFormat);
 
 }  // namespace gfx
 }  // namespace mozilla
 
 #endif /* MOZILLA_GFX_SWIZZLE_H_ */
--- a/gfx/2d/SwizzleNEON.cpp
+++ b/gfx/2d/SwizzleNEON.cpp
@@ -348,18 +348,17 @@ void Swizzle_NEON(const uint8_t* aSrc, i
                   int32_t aDstGap, IntSize aSize) {
   int32_t alignedRow = 4 * (aSize.width & ~3);
   int32_t remainder = aSize.width & 3;
   // Fold remainder into stride gap.
   aSrcGap += 4 * remainder;
   aDstGap += 4 * remainder;
 
   for (int32_t height = aSize.height; height > 0; height--) {
-    SwizzleChunk_NEON<aSwapRB, aOpaqueAlpha>(aSrc, aDst, alignedRow,
-                                             remainder);
+    SwizzleChunk_NEON<aSwapRB, aOpaqueAlpha>(aSrc, aDst, alignedRow, remainder);
     aSrc += aSrcGap;
     aDst += aDstGap;
   }
 }
 
 // Force instantiation of swizzle variants here.
 template void SwizzleRow_NEON<true, false>(const uint8_t*, uint8_t*, int32_t);
 template void SwizzleRow_NEON<true, true>(const uint8_t*, uint8_t*, int32_t);
--- a/gfx/layers/ipc/UiCompositorControllerParent.cpp
+++ b/gfx/layers/ipc/UiCompositorControllerParent.cpp
@@ -9,16 +9,17 @@
 #  include "apz/src/APZCTreeManager.h"
 #  include "mozilla/layers/AsyncCompositionManager.h"
 #endif
 #include "mozilla/layers/Compositor.h"
 #include "mozilla/layers/CompositorBridgeParent.h"
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/layers/LayerManagerComposite.h"
 #include "mozilla/layers/UiCompositorControllerMessageTypes.h"
+#include "mozilla/layers/WebRenderBridgeParent.h"
 #include "mozilla/gfx/Types.h"
 #include "mozilla/Move.h"
 #include "mozilla/Unused.h"
 
 #include "FrameMetrics.h"
 #include "SynchronousTask.h"
 
 namespace mozilla {
@@ -170,16 +171,19 @@ UiCompositorControllerParent::RecvReques
 #if defined(MOZ_WIDGET_ANDROID)
   LayerTreeState* state =
       CompositorBridgeParent::GetIndirectShadowTree(mRootLayerTreeId);
 
   if (state && state->mLayerManager && state->mParent) {
     state->mLayerManager->RequestScreenPixels(this);
     state->mParent->Invalidate();
     state->mParent->ScheduleComposition();
+  } else if (state && state->mWrBridge) {
+    state->mWrBridge->RequestScreenPixels(this);
+    state->mWrBridge->ScheduleForcedGenerateFrame();
   }
 #endif  // defined(MOZ_WIDGET_ANDROID)
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 UiCompositorControllerParent::RecvEnableLayerUpdateNotifications(
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -328,16 +328,19 @@ WebRenderBridgeParent::WebRenderBridgePa
       mCompositorScheduler(aScheduler),
       mAnimStorage(aAnimStorage),
       mVsyncRate(aVsyncRate),
       mChildLayersObserverEpoch{0},
       mParentLayersObserverEpoch{0},
       mWrEpoch{0},
       mIdNamespace(aApis[0]->GetNamespace()),
       mRenderRootRectMutex("WebRenderBridgeParent::mRenderRootRectMutex"),
+#if defined(MOZ_WIDGET_ANDROID)
+      mScreenPixelsTarget(nullptr),
+#endif
       mPaused(false),
       mDestroyed(false),
       mReceivedDisplayList(false),
       mIsFirstPaint(true),
       mSkippedComposite(false) {
   MOZ_ASSERT(mAsyncImageManager);
   MOZ_ASSERT(mAnimStorage);
   mAsyncImageManager->AddPipeline(mPipelineId, this);
@@ -1712,16 +1715,59 @@ void WebRenderBridgeParent::FlushFramePr
   // This sends a message to the render backend thread to send a message
   // to the renderer thread, and waits for that message to be processed. So
   // this effectively blocks on the render backend and renderer threads,
   // following the same codepath that WebRender takes to render and composite
   // a frame.
   mApis[wr::RenderRoot::Default]->WaitFlushed();
 }
 
+#if defined(MOZ_WIDGET_ANDROID)
+void WebRenderBridgeParent::RequestScreenPixels(
+    UiCompositorControllerParent* aController) {
+  mScreenPixelsTarget = aController;
+}
+
+void WebRenderBridgeParent::MaybeCaptureScreenPixels() {
+  if (!mScreenPixelsTarget) {
+    return;
+  }
+
+  if (mDestroyed) {
+    return;
+  }
+  MOZ_ASSERT(!mPaused);
+
+  // This function should only get called in the root WRBP.
+  MOZ_ASSERT(IsRootWebRenderBridgeParent());
+
+  SurfaceFormat format = SurfaceFormat::R8G8B8A8;  // On android we use RGBA8
+  auto client_size = mWidget->GetClientSize();
+  size_t buffer_size =
+      client_size.width * client_size.height * BytesPerPixel(format);
+
+  ipc::Shmem mem;
+  if (!mScreenPixelsTarget->AllocPixelBuffer(buffer_size, &mem)) {
+    // Failed to alloc shmem, Just bail out.
+    return;
+  }
+
+  IntSize size(client_size.width, client_size.height);
+
+  mApis[wr::RenderRoot::Default]->Readback(
+      TimeStamp::Now(), size, format,
+      Range<uint8_t>(mem.get<uint8_t>(), buffer_size));
+
+  Unused << mScreenPixelsTarget->SendScreenPixels(
+      std::move(mem), ScreenIntSize(client_size.width, client_size.height));
+
+  mScreenPixelsTarget = nullptr;
+}
+#endif
+
 mozilla::ipc::IPCResult WebRenderBridgeParent::RecvGetSnapshot(
     PTextureParent* aTexture) {
   if (mDestroyed) {
     return IPC_OK();
   }
   MOZ_ASSERT(!mPaused);
 
   // This function should only get called in the root WRBP. If this function
@@ -2352,16 +2398,20 @@ void WebRenderBridgeParent::MaybeGenerat
     auto renderRoot = api->GetRenderRoot();
     if (generateFrame[renderRoot]) {
       fastTxns[renderRoot]->GenerateFrame();
       generateFrameTxns[renderRoot] = fastTxns[renderRoot].ptr();
     }
   }
   wr::WebRenderAPI::SendTransactions(mApis, generateFrameTxns);
 
+#if defined(MOZ_WIDGET_ANDROID)
+  MaybeCaptureScreenPixels();
+#endif
+
   mMostRecentComposite = TimeStamp::Now();
 }
 
 void WebRenderBridgeParent::HoldPendingTransactionId(
     const wr::Epoch& aWrEpoch, TransactionId aTransactionId,
     bool aContainsSVGGroup, const VsyncId& aVsyncId,
     const TimeStamp& aVsyncStartTime, const TimeStamp& aRefreshStartTime,
     const TimeStamp& aTxnStartTime, const nsCString& aTxnURL,
--- a/gfx/layers/wr/WebRenderBridgeParent.h
+++ b/gfx/layers/wr/WebRenderBridgeParent.h
@@ -293,16 +293,24 @@ class WebRenderBridgeParent final
 
   /**
    * Write the frames collected by the |WebRenderCompositionRecorder| to disk.
    *
    * If there is not currently a recorder, this is a no-op.
    */
   void WriteCollectedFrames();
 
+#if defined(MOZ_WIDGET_ANDROID)
+  /**
+   * Request a screengrab for android
+   */
+  void RequestScreenPixels(UiCompositorControllerParent* aController);
+  void MaybeCaptureScreenPixels();
+#endif
+
  private:
   class ScheduleSharedSurfaceRelease;
 
   explicit WebRenderBridgeParent(const wr::PipelineId& aPipelineId);
   virtual ~WebRenderBridgeParent();
 
   wr::WebRenderAPI* Api(wr::RenderRoot aRenderRoot) {
     if (IsRootWebRenderBridgeParent()) {
@@ -543,16 +551,19 @@ class WebRenderBridgeParent final
   TimeStamp mMostRecentComposite;
 
   // Kind of clunky, but I can't sort out a more elegant way of getting this to
   // work.
   Mutex mRenderRootRectMutex;
   wr::NonDefaultRenderRootArray<ScreenRect> mRenderRootRects;
 
   Maybe<wr::RenderRoot> mRenderRoot;
+#if defined(MOZ_WIDGET_ANDROID)
+  UiCompositorControllerParent* mScreenPixelsTarget;
+#endif
   bool mPaused;
   bool mDestroyed;
   bool mReceivedDisplayList;
   bool mIsFirstPaint;
   bool mSkippedComposite;
 };
 
 }  // namespace layers
--- a/gfx/layers/wr/WebRenderCommandBuilder.cpp
+++ b/gfx/layers/wr/WebRenderCommandBuilder.cpp
@@ -570,19 +570,18 @@ struct DIGroup {
   void EndGroup(WebRenderLayerManager* aWrManager,
                 nsDisplayListBuilder* aDisplayListBuilder,
                 wr::DisplayListBuilder& aBuilder,
                 wr::IpcResourceUpdateQueue& aResources, Grouper* aGrouper,
                 nsDisplayItem* aStartItem, nsDisplayItem* aEndItem) {
     GP("\n\n");
     GP("Begin EndGroup\n");
 
-    mVisibleRect = mVisibleRect.Intersect(
-            ViewAs<LayerPixel>(mActualBounds,
-                               PixelCastJustification::LayerIsImage));
+    mVisibleRect = mVisibleRect.Intersect(ViewAs<LayerPixel>(
+        mActualBounds, PixelCastJustification::LayerIsImage));
 
     if (mVisibleRect.IsEmpty()) {
       return;
     }
 
     // Invalidate any unused items
     GP("mDisplayItems\n");
     for (auto iter = mDisplayItems.Iter(); !iter.Done(); iter.Next()) {
@@ -780,17 +779,18 @@ struct DIGroup {
 
       bool dirty = true;
       auto preservedBounds = bounds.Intersect(mPreservedRect);
       if (!mInvalidRect.Contains(preservedBounds)) {
         GP("Passing\n");
         dirty = false;
         BlobItemData* data = GetBlobItemData(item);
         if (data->mInvalid) {
-          gfxCriticalError() << "DisplayItem" << item->Name() << "-should be invalid";
+          gfxCriticalError()
+              << "DisplayItem" << item->Name() << "-should be invalid";
         }
         // if the item is invalid it needs to be fully contained
         MOZ_RELEASE_ASSERT(!data->mInvalid);
       }
 
       nsDisplayList* children = item->GetChildren();
       if (children) {
         GP("doing children in EndGroup\n");
--- a/gfx/tests/gtest/TestSwizzle.cpp
+++ b/gfx/tests/gtest/TestSwizzle.cpp
@@ -59,17 +59,17 @@ TEST(Moz2D, PremultiplyRow)
       255, 255, 0, 255, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 128,
   };
   // check swizzled output
   const uint8_t check_rgba[5 * 4] = {
       0, 255, 255, 255, 255, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 128,
   };
 
   SwizzleRowFn func =
-    PremultiplyRow(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8A8);
+      PremultiplyRow(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8A8);
   func(in_bgra, out, 5);
   EXPECT_TRUE(ArrayEqual(out, check_bgra));
 
   func = PremultiplyRow(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8);
   func(in_bgra, out, 5);
   EXPECT_TRUE(ArrayEqual(out, check_rgba));
 }
 
@@ -198,36 +198,35 @@ TEST(Moz2D, SwizzleRow)
   // check opaquifying
   const uint8_t check_rgbx[5 * 4] = {
       0, 254, 253, 255, 255, 0,   0, 255, 0,   0,
       0, 255, 3,   2,   1,   255, 9, 0,   127, 255,
   };
   // check unpacking
   uint8_t out_unpack[16 * 4];
   const uint8_t in_rgb[16 * 3] = {
-      0, 254, 253, 255, 0, 0, 0, 0, 0, 3, 2, 1,
-      9, 0, 127, 4, 5, 6, 9, 8, 7, 10, 11, 12,
-      13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
-      25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
+      0,  254, 253, 255, 0,  0,  0,  0,  0,  3,  2,  1,  9,  0,  127, 4,
+      5,  6,   9,   8,   7,  10, 11, 12, 13, 14, 15, 16, 17, 18, 19,  20,
+      21, 22,  23,  24,  25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,  36,
   };
   const uint8_t check_unpack_rgbx[16 * 4] = {
-      0, 254, 253, 255, 255, 0, 0, 255, 0, 0, 0, 255, 3, 2, 1, 255,
-      9, 0, 127, 255, 4, 5, 6, 255, 9, 8, 7, 255, 10, 11, 12, 255,
-      13, 14, 15, 255, 16, 17, 18, 255, 19, 20, 21, 255, 22, 23, 24, 255,
-      25, 26, 27, 255, 28, 29, 30, 255, 31, 32, 33, 255, 34, 35, 36, 255,
+      0,  254, 253, 255, 255, 0,  0,  255, 0,  0,  0,  255, 3,  2,  1,  255,
+      9,  0,   127, 255, 4,   5,  6,  255, 9,  8,  7,  255, 10, 11, 12, 255,
+      13, 14,  15,  255, 16,  17, 18, 255, 19, 20, 21, 255, 22, 23, 24, 255,
+      25, 26,  27,  255, 28,  29, 30, 255, 31, 32, 33, 255, 34, 35, 36, 255,
   };
   const uint8_t check_unpack_bgrx[16 * 4] = {
-      253, 254, 0, 255, 0, 0, 255, 255, 0, 0, 0, 255, 1, 2, 3, 255,
-      127, 0, 9, 255, 6, 5, 4, 255, 7, 8, 9, 255, 12, 11, 10, 255,
-      15, 14, 13, 255, 18, 17, 16, 255, 21, 20, 19, 255, 24, 23, 22, 255,
-      27, 26, 25, 255, 30, 29, 28, 255, 33, 32, 31, 255, 36, 35, 34, 255,
+      253, 254, 0,  255, 0,  0,  255, 255, 0,  0,  0,  255, 1,  2,  3,  255,
+      127, 0,   9,  255, 6,  5,  4,   255, 7,  8,  9,  255, 12, 11, 10, 255,
+      15,  14,  13, 255, 18, 17, 16,  255, 21, 20, 19, 255, 24, 23, 22, 255,
+      27,  26,  25, 255, 30, 29, 28,  255, 33, 32, 31, 255, 36, 35, 34, 255,
   };
 
   SwizzleRowFn func =
-    SwizzleRow(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8);
+      SwizzleRow(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8);
   func(in_bgra, out, 5);
   EXPECT_TRUE(ArrayEqual(out, check_rgba));
 
   func = SwizzleRow(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8X8);
   func(in_bgra, out, 5);
   EXPECT_TRUE(ArrayEqual(out, check_rgbx));
 
   func = SwizzleRow(SurfaceFormat::R8G8B8, SurfaceFormat::B8G8R8X8);
--- a/gfx/thebes/gfxDWriteFontList.cpp
+++ b/gfx/thebes/gfxDWriteFontList.cpp
@@ -935,38 +935,43 @@ gfxFontEntry* gfxDWriteFontList::CreateF
     fontlist::Face* aFace, const fontlist::Family* aFamily) {
   IDWriteFontCollection* collection =
 #ifdef MOZ_BUNDLED_FONTS
       aFamily->IsBundled() ? mBundledFonts : mSystemFonts;
 #else
       mSystemFonts;
 #endif
   RefPtr<IDWriteFontFamily> family;
-  HRESULT hr = collection->GetFontFamily(aFamily->Index(), getter_AddRefs(family));
-  // Check that the family name is what we expected; if not, fall back to search by name.
-  // It's sad we have to do this, but it is possible for Windows to have given different versions
-  // of the system font collection to the parent and child processes.
+  HRESULT hr =
+      collection->GetFontFamily(aFamily->Index(), getter_AddRefs(family));
+  // Check that the family name is what we expected; if not, fall back to search
+  // by name. It's sad we have to do this, but it is possible for Windows to
+  // have given different versions of the system font collection to the parent
+  // and child processes.
   bool foundFamily = false;
-  const nsCString& familyName = aFamily->DisplayName().AsString(SharedFontList());
+  const nsCString& familyName =
+      aFamily->DisplayName().AsString(SharedFontList());
   if (SUCCEEDED(hr) && family) {
     RefPtr<IDWriteLocalizedStrings> names;
     hr = family->GetFamilyNames(getter_AddRefs(names));
     if (SUCCEEDED(hr) && names) {
       nsAutoCString name;
       if (GetEnglishOrFirstName(name, names)) {
         foundFamily = name.Equals(familyName);
       }
     }
   }
   if (!foundFamily) {
-    // Try to get family by name instead of index (to deal with the case of collection mismatch).
+    // Try to get family by name instead of index (to deal with the case of
+    // collection mismatch).
     UINT32 index;
     BOOL exists;
     NS_ConvertUTF8toUTF16 name16(familyName);
-    hr = collection->FindFamilyName(reinterpret_cast<const WCHAR*>(name16.BeginReading()), &index, &exists);
+    hr = collection->FindFamilyName(
+        reinterpret_cast<const WCHAR*>(name16.BeginReading()), &index, &exists);
     if (SUCCEEDED(hr) && exists && index != UINT_MAX) {
       hr = collection->GetFontFamily(index, getter_AddRefs(family));
       if (FAILED(hr) || !family) {
         return nullptr;
       }
     }
   }
   RefPtr<IDWriteFont> font;
--- a/gfx/thebes/gfxFT2FontBase.cpp
+++ b/gfx/thebes/gfxFT2FontBase.cpp
@@ -471,18 +471,23 @@ uint32_t gfxFT2FontBase::GetGlyph(uint32
   return GetGlyph(unicode);
 }
 
 bool gfxFT2FontBase::ShouldRoundXOffset(cairo_t* aCairo) const {
   // Force rounding if outputting to a Cairo context. Otherwise, allow subpixel
   // positioning (no rounding) if rendering a scalable outline font with
   // anti-aliasing. Monochrome rendering or some bitmap fonts can become too
   // distorted with subpixel positioning, so force rounding in those cases.
+  // Also be careful not to use subpixel positioning if the user requests full
+  // hinting via Fontconfig, which we detect by checking that neither hinting
+  // was disabled nor light hinting was requested.
   return aCairo != nullptr || !mFTFace || !FT_IS_SCALABLE(mFTFace->GetFace()) ||
-         (mFTLoadFlags & FT_LOAD_MONOCHROME);
+         (mFTLoadFlags & FT_LOAD_MONOCHROME) ||
+         !((mFTLoadFlags & FT_LOAD_NO_HINTING) ||
+           FT_LOAD_TARGET_MODE(mFTLoadFlags) == FT_RENDER_MODE_LIGHT);
 }
 
 FT_Vector gfxFT2FontBase::GetEmboldenStrength(FT_Face aFace) {
   FT_Vector strength = {0, 0};
   if (!mEmbolden) {
     return strength;
   }
   // This is the embolden "strength" used by FT_GlyphSlot_Embolden.
--- a/image/decoders/icon/nsIconURI.cpp
+++ b/image/decoders/icon/nsIconURI.cpp
@@ -7,16 +7,19 @@
 
 #include "nsIconURI.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/ipc/URIUtils.h"
 #include "mozilla/Sprintf.h"
 
 #include "nsIIOService.h"
+#include "nsISerializable.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
 #include "nsIURL.h"
 #include "nsNetUtil.h"
 #include "plstr.h"
 #include <stdlib.h>
 
 using namespace mozilla;
 using namespace mozilla::ipc;
 
@@ -25,16 +28,20 @@ using namespace mozilla::ipc;
 #if defined(MAX_PATH)
 #  define SANE_FILE_NAME_LEN MAX_PATH
 #elif defined(PATH_MAX)
 #  define SANE_FILE_NAME_LEN PATH_MAX
 #else
 #  define SANE_FILE_NAME_LEN 1024
 #endif
 
+static NS_DEFINE_CID(kThisIconURIImplementationCID,
+                     NS_THIS_ICONURI_IMPLEMENTATION_CID);
+static NS_DEFINE_CID(kIconURICID, NS_ICONURI_CID);
+
 // helper function for parsing out attributes like size, and contentType
 // from the icon url.
 static void extractAttributeValue(const char* aSearchString,
                                   const char* aAttributeName,
                                   nsCString& aResult);
 
 static const char* kSizeStrings[] = {"button", "toolbar", "toolbarsmall",
                                      "menu",   "dnd",     "dialog"};
@@ -47,20 +54,25 @@ nsMozIconURI::nsMozIconURI()
     : mSize(DEFAULT_IMAGE_SIZE), mIconSize(-1), mIconState(-1) {}
 
 nsMozIconURI::~nsMozIconURI() {}
 
 NS_IMPL_ADDREF(nsMozIconURI)
 NS_IMPL_RELEASE(nsMozIconURI)
 
 NS_INTERFACE_MAP_BEGIN(nsMozIconURI)
+  if (aIID.Equals(kThisIconURIImplementationCID))
+    foundInterface = static_cast<nsIURI*>(this);
   NS_INTERFACE_MAP_ENTRY(nsIMozIconURI)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIURI)
   NS_INTERFACE_MAP_ENTRY(nsIURI)
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsINestedURI, mIconURL)
+  NS_INTERFACE_MAP_ENTRY(nsIMozIconURI)
+  NS_INTERFACE_MAP_ENTRY(nsISerializable)
+  NS_INTERFACE_MAP_ENTRY(nsIClassInfo)
 NS_INTERFACE_MAP_END
 
 #define MOZICON_SCHEME "moz-icon:"
 #define MOZICON_SCHEME_LEN (sizeof(MOZICON_SCHEME) - 1)
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsIURI methods:
 
@@ -127,17 +139,18 @@ nsMozIconURI::GetDisplayPrePath(nsACStri
 }
 
 NS_IMETHODIMP
 nsMozIconURI::GetHasRef(bool* result) {
   *result = false;
   return NS_OK;
 }
 
-NS_IMPL_ISUPPORTS(nsMozIconURI::Mutator, nsIURISetters, nsIURIMutator)
+NS_IMPL_NSIURIMUTATOR_ISUPPORTS(nsMozIconURI::Mutator, nsIURISetters,
+                                nsIURIMutator, nsISerializable)
 
 NS_IMETHODIMP
 nsMozIconURI::Mutate(nsIURIMutator** aMutator) {
   RefPtr<nsMozIconURI::Mutator> mutator = new nsMozIconURI::Mutator();
   nsresult rv = mutator->InitFromURI(this);
   if (NS_FAILED(rv)) {
     return rv;
   }
@@ -612,8 +625,79 @@ nsMozIconURI::GetInnerURI(nsIURI** aURI)
   iconURL.forget(aURI);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsMozIconURI::GetInnermostURI(nsIURI** aURI) {
   return NS_ImplGetInnermostURI(this, aURI);
 }
+
+NS_IMETHODIMP
+nsMozIconURI::Read(nsIObjectInputStream* aStream) {
+  MOZ_ASSERT_UNREACHABLE("Use nsIURIMutator.read() instead");
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsMozIconURI::ReadPrivate(nsIObjectInputStream* aStream) {
+  nsAutoCString spec;
+  nsresult rv = aStream->ReadCString(spec);
+  NS_ENSURE_SUCCESS(rv, rv);
+  return SetSpecInternal(spec);
+}
+
+NS_IMETHODIMP
+nsMozIconURI::Write(nsIObjectOutputStream* aStream) {
+  nsAutoCString spec;
+  nsresult rv = GetSpec(spec);
+  NS_ENSURE_SUCCESS(rv, rv);
+  return aStream->WriteStringZ(spec.get());
+}
+
+//----------------------------------------------------------------------------
+// nsSimpleURI::nsIClassInfo
+//----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsMozIconURI::GetInterfaces(nsTArray<nsIID>& array) {
+  array.Clear();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetScriptableHelper(nsIXPCScriptable** _retval) {
+  *_retval = nullptr;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetContractID(nsACString& aContractID) {
+  // Make sure to modify any subclasses as needed if this ever
+  // changes.
+  aContractID.SetIsVoid(true);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMozIconURI::GetClassDescription(nsACString& aClassDescription) {
+  aClassDescription.SetIsVoid(true);
+  return NS_OK;