Merge autoland to mozilla-central a=merge
authorCoroiu Cristina <ccoroiu@mozilla.com>
Sat, 01 Dec 2018 04:29:26 +0200
changeset 505461 22425b629a9d1484ebda8b03bb0cf3019305207f
parent 505390 950f6d29da967b9999ce709e94bf35b244f79100 (current diff)
parent 505460 847bc008e00b9348a07b87b4c8063782c1d7ab11 (diff)
child 505489 925a2727cc3755bb0d0683d696b8d965b16e7236
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge autoland to mozilla-central a=merge
browser/locales/en-US/chrome/browser/aboutSearchReset.dtd
devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_addons_usb_runtime.js
devtools/client/aboutdebugging-new/test/browser/mocks/head-usb-mocks.js
devtools/client/aboutdebugging/test/addons/unpacked/bootstrap.js
devtools/client/aboutdebugging/test/addons/unpacked/install.rdf
devtools/client/aboutdebugging/test/browser_addons_debug_bootstrapped.js
devtools/client/shared/test/addon1.xpi
devtools/client/shared/test/addon2.xpi
devtools/client/shared/test/addon4.xpi
devtools/client/shared/test/browser_dbg_addon-console.js
testing/web-platform/meta/shadow-dom/scroll-to-the-fragment-in-shadow-tree.html.ini
toolkit/locales/en-US/chrome/global/aboutNetworking.dtd
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -794,31 +794,31 @@ dependencies = [
  "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "encoding_c"
 version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "encoding_rs 0.8.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "encoding_rs 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "encoding_glue"
 version = "0.1.0"
 dependencies = [
- "encoding_rs 0.8.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "encoding_rs 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)",
  "nserror 0.1.0",
  "nsstring 0.1.0",
 ]
 
 [[package]]
 name = "encoding_rs"
-version = "0.8.12"
+version = "0.8.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "simd 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "env_logger"
@@ -1699,17 +1699,17 @@ dependencies = [
  "nsstring 0.1.0",
 ]
 
 [[package]]
 name = "nsstring"
 version = "0.1.0"
 dependencies = [
  "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "encoding_rs 0.8.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "encoding_rs 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "nsstring-gtest"
 version = "0.1.0"
 dependencies = [
  "nsstring 0.1.0",
 ]
@@ -3193,17 +3193,17 @@ dependencies = [
 "checksum dirs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "88972de891f6118092b643d85a0b28e0678e0f948d7f879aa32f2d5aafe97d2a"
 "checksum docopt 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d8acd393692c503b168471874953a2531df0e9ab77d0b6bbc582395743300a4a"
 "checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab"
 "checksum dtoa-short 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "068d4026697c1a18f0b0bb8cfcad1b0c151b90d8edb9bf4c235ad68128920d1d"
 "checksum dwrote 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7b46afd0d0bbbea88fc083ea293e40865e26a75ec9d38cf5d05a23ac3e2ffe02"
 "checksum either 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18785c1ba806c258137c937e44ada9ee7e69a37e3c72077542cd2f069d78562a"
 "checksum ena 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "88dc8393b3c7352f94092497f6b52019643e493b6b890eb417cdb7c46117e621"
 "checksum encoding_c 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "769ecb8b33323998e482b218c0d13cd64c267609023b4b7ec3ee740714c318ee"
-"checksum encoding_rs 0.8.12 (registry+https://github.com/rust-lang/crates.io-index)" = "ca20350a7cb5aab5b9034731123d6d412caf3e92d4985e739e411ba0955fd0eb"
+"checksum encoding_rs 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1a8fa54e6689eb2549c4efed8d00d7f3b2b994a064555b0e8df4ae3764bcc4be"
 "checksum env_logger 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0561146661ae44c579e993456bc76d11ce1e0c7d745e57b2fa7146b6e49fa2ad"
 "checksum error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff511d5dc435d703f4971bc399647c9bc38e20cb41452e3b9feb4765419ed3f3"
 "checksum euclid 0.19.3 (registry+https://github.com/rust-lang/crates.io-index)" = "600657e7e5c03bfbccdc68721bc3b5abcb761553973387124eae9c9e4f02c210"
 "checksum failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6dd377bcc1b1b7ce911967e3ec24fa19c3224394ec05b54aa7b083d498341ac7"
 "checksum failure_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "64c2d913fe8ed3b6c6518eedf4538255b989945c14c2a7d5cbff62a5e2120596"
 "checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
 "checksum fixedbitset 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "85cb8fec437468d86dc7c83ca7cfc933341d561873275f22dd5eedefa63a6478"
 "checksum flate2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9fac2277e84e5e858483756647a9d0aa8d9a2b7cba517fd84325a0aaa69a0909"
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1023,17 +1023,17 @@ pref("security.sandbox.gpu.level", 0);
 
 // Controls whether we disable win32k for the GMP processes.
 // true means that win32k system calls are not permitted.
 // Note: win32k is currently _not_ disabled due to intermittent test failures,
 // where the GMP process fails very early. See bug 1449348.
 pref("security.sandbox.gmp.win32k-disable", false);
 #endif
 
-#if defined(NIGHTLY_BUILD) && defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
 // Start the Mac sandbox early during child process startup instead
 // of when messaged by the parent after the message loop is running.
 pref("security.sandbox.content.mac.earlyinit", true);
 #endif
 
 #if defined(XP_MACOSX) && defined(MOZ_SANDBOX) && defined(MOZ_CONTENT_SANDBOX)
 // This pref is discussed in bug 1083344, the naming is inspired from its
 // Windows counterpart, but on Mac it's an integer which means:
--- a/browser/base/content/browser-contentblocking.js
+++ b/browser/base/content/browser-contentblocking.js
@@ -313,17 +313,17 @@ var ContentBlocking = {
 
     this.updateAnimationsEnabled();
 
     Services.prefs.addObserver(this.PREF_ANIMATIONS_ENABLED, this.updateAnimationsEnabled);
 
     XPCOMUtils.defineLazyPreferenceGetter(this, "reportBreakageEnabled",
       this.PREF_REPORT_BREAKAGE_ENABLED, false);
 
-    this.appMenuLabel.setAttribute("label", this.strings.appMenuTitle);
+    this.appMenuLabel.setAttribute("value", this.strings.appMenuTitle);
     this.appMenuLabel.setAttribute("tooltiptext", this.strings.appMenuTooltip);
 
     this.activeTooltipText =
       gNavigatorBundle.getString("trackingProtection.icon.activeTooltip");
     this.disabledTooltipText =
       gNavigatorBundle.getString("trackingProtection.icon.disabledTooltip");
     this.updateCBCategoryLabel = this.updateCBCategoryLabel.bind(this);
     this.updateCBCategoryLabel();
@@ -342,29 +342,31 @@ var ContentBlocking = {
   },
 
   updateCBCategoryLabel() {
     if (!Services.prefs.prefHasUserValue(this.PREF_CB_CATEGORY)) {
       // Fallback to not setting a label, it's preferable to not set a label than to set an incorrect one.
       return;
     }
     let button = document.getElementById("tracking-protection-preferences-button");
+    let appMenuCategoryLabel = document.getElementById("appMenu-tp-category");
     let label;
     let category = Services.prefs.getStringPref(this.PREF_CB_CATEGORY);
     switch (category) {
     case ("standard"):
       label = gNavigatorBundle.getString("contentBlocking.category.standard");
       break;
     case ("strict"):
       label = gNavigatorBundle.getString("contentBlocking.category.strict");
       break;
     case ("custom"):
       label = gNavigatorBundle.getString("contentBlocking.category.custom");
       break;
     }
+    appMenuCategoryLabel.value = label;
     button.label = label;
   },
 
   hideIdentityPopupAndReload() {
     this.identityPopup.hidePopup();
     BrowserReload();
   },
 
--- a/browser/base/content/test/static/browser_misused_characters_in_strings.js
+++ b/browser/base/content/test/static/browser_misused_characters_in_strings.js
@@ -148,20 +148,16 @@ let gWhitelist = [{
     file: "netErrorApp.dtd",
     key: "securityOverride.warningContent",
     type: "single-quote",
   }, {
     file: "pocket.properties",
     key: "tos",
     type: "double-quote",
   }, {
-    file: "aboutNetworking.dtd",
-    key: "aboutNetworking.logTutorial",
-    type: "single-quote",
-  }, {
     file: "browser.dtd",
     key: "addonPostInstallMessage.label",
     type: "single-quote",
   },
 ];
 
 /**
  * Check if an error should be ignored due to matching one of the whitelist
--- a/browser/base/content/test/trackingUI/browser_trackingUI_categories.js
+++ b/browser/base/content/test/trackingUI/browser_trackingUI_categories.js
@@ -1,14 +1,16 @@
 const CAT_PREF = "browser.contentblocking.category";
 const TP_PREF = "privacy.trackingprotection.enabled";
 const TP_PB_PREF = "privacy.trackingprotection.pbmode.enabled";
 const TPC_PREF = "network.cookie.cookieBehavior";
 const TT_PREF = "urlclassifier.trackingTable";
 
+ChromeUtils.import("resource://testing-common/CustomizableUITestUtils.jsm", this);
+
 registerCleanupFunction(function() {
   Services.prefs.clearUserPref(TP_PREF);
   Services.prefs.clearUserPref(TP_PB_PREF);
   Services.prefs.clearUserPref(TPC_PREF);
   Services.prefs.clearUserPref(TT_PREF);
   Services.prefs.clearUserPref(CAT_PREF);
 });
 
@@ -35,8 +37,36 @@ add_task(async function testCategoryLabe
 
     Services.prefs.setStringPref(CAT_PREF, "custom");
     await TestUtils.waitForCondition(() => preferencesButton.label ==
       gNavigatorBundle.getString("contentBlocking.category.custom"));
     is(preferencesButton.label, gNavigatorBundle.getString("contentBlocking.category.custom"),
       "The preferencesButton label has been changed to custom");
   });
 });
+
+add_task(async function testCategoryLabelsInAppMenu() {
+  await BrowserTestUtils.withNewTab("http://www.example.com", async function() {
+    let cuiTestUtils = new CustomizableUITestUtils(window);
+    await cuiTestUtils.openMainMenu();
+
+    let appMenuCategoryLabel = document.getElementById("appMenu-tp-category");
+    ok(appMenuCategoryLabel.value, "The appMenuCategory label exists");
+
+    Services.prefs.setStringPref(CAT_PREF, "strict");
+    await TestUtils.waitForCondition(() => appMenuCategoryLabel.value ==
+      gNavigatorBundle.getString("contentBlocking.category.strict"));
+    is(appMenuCategoryLabel.value, gNavigatorBundle.getString("contentBlocking.category.strict"),
+      "The appMenuCategory label has been changed to strict");
+
+    Services.prefs.setStringPref(CAT_PREF, "standard");
+    await TestUtils.waitForCondition(() => appMenuCategoryLabel.value ==
+      gNavigatorBundle.getString("contentBlocking.category.standard"));
+    is(appMenuCategoryLabel.value, gNavigatorBundle.getString("contentBlocking.category.standard"),
+      "The appMenuCategory label has been changed to standard");
+
+    Services.prefs.setStringPref(CAT_PREF, "custom");
+    await TestUtils.waitForCondition(() => appMenuCategoryLabel.value ==
+      gNavigatorBundle.getString("contentBlocking.category.custom"));
+    is(appMenuCategoryLabel.value, gNavigatorBundle.getString("contentBlocking.category.custom"),
+      "The appMenuCategory label has been changed to custom");
+  });
+});
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -220,19 +220,23 @@
           <toolbarbutton id="appMenu-fxa-icon"
                          class="subviewbutton subviewbutton-iconic"
                          onmouseover="gSync.refreshSyncButtonsTooltip();"
                          oncommand="gSync.doSync();"
                          closemenu="none"/>
         </toolbaritem>
         <toolbarseparator class="sync-ui-item"/>
         <toolbaritem>
-          <toolbarbutton id="appMenu-tp-label"
-                         class="subviewbutton subviewbutton-iconic"
-                         oncommand="ContentBlocking.openPreferences('appMenu-trackingprotection');"/>
+          <toolbarbutton id="appMenu-tp-button"
+               class="subviewbutton subviewbutton-iconic"
+               oncommand="ContentBlocking.openPreferences('appMenu-trackingprotection');">
+            <image id="appMenu-tp-icon" class="toolbarbutton-icon"/>
+            <label id="appMenu-tp-label" class="toolbarbutton-text"/>
+            <label id="appMenu-tp-category"/>
+          </toolbarbutton>
         </toolbaritem>
         <toolbarseparator id="appMenu-tp-separator"/>
         <toolbarbutton id="appMenu-new-window-button"
                        class="subviewbutton subviewbutton-iconic"
                        label="&newNavigatorCmd.label;"
                        key="key_newNavigator"
                        command="cmd_newNavigator"/>
         <toolbarbutton id="appMenu-private-window-button"
--- a/browser/components/search/content/searchReset.js
+++ b/browser/components/search/content/searchReset.js
@@ -10,19 +10,21 @@ const TELEMETRY_RESULT_ENUM = {
   RESTORED_DEFAULT: 0,
   KEPT_CURRENT: 1,
   CHANGED_ENGINE: 2,
   CLOSED_PAGE: 3,
   OPENED_SETTINGS: 4,
 };
 
 window.onload = function() {
-  let defaultEngine = document.getElementById("defaultEngine");
+  let defaultEngineParagraph = document.getElementById("defaultEngineParagraph");
   let originalDefault = Services.search.originalDefaultEngine;
-  defaultEngine.textContent = originalDefault.name;
+  document.l10n.setAttributes(defaultEngineParagraph, "page-info-new-search-engine",
+                              { searchEngine: originalDefault.name });
+  let defaultEngine = document.getElementById("defaultEngine");
   defaultEngine.style.backgroundImage =
     'url("' + originalDefault.iconURI.spec + '")';
 
   document.getElementById("searchResetChangeEngine").focus();
   window.addEventListener("unload", recordPageClosed);
   document.getElementById("linkSettingsPage")
           .addEventListener("click", openingSettings);
 };
--- a/browser/components/search/content/searchReset.xhtml
+++ b/browser/components/search/content/searchReset.xhtml
@@ -1,61 +1,56 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <!-- 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/. -->
 
-<!DOCTYPE html [
-  <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
-  %htmlDTD;
-  <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
-  %globalDTD;
-  <!ENTITY % searchresetDTD SYSTEM "chrome://browser/locale/aboutSearchReset.dtd">
-  %searchresetDTD;
-  <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
-  %brandDTD;
-]>
+<!DOCTYPE html>
 
 <html xmlns="http://www.w3.org/1999/xhtml"
       xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
   <head>
-    <title>&searchreset.tabtitle;</title>
+    <title data-l10n-id="tab-title"/>
     <link rel="stylesheet" type="text/css" media="all"
           href="chrome://global/skin/in-content/info-pages.css"/>
     <link rel="stylesheet" type="text/css" media="all"
           href="chrome://browser/skin/searchReset.css"/>
     <link rel="icon" type="image/png"
           href="chrome://browser/skin/favicon-search-16.svg"/>
 
     <script type="application/javascript"
             src="chrome://browser/content/search/searchReset.js"/>
+    <link rel="localization" href="browser/aboutSearchReset.ftl"/>
+    <link rel="localization" href="branding/brand.ftl"/>
   </head>
 
-  <body dir="&locale.dir;">
+  <body>
 
     <div class="container">
       <div class="title">
-        <h1 class="title-text">&searchreset.pageTitle;</h1>
+        <h1 class="title-text" data-l10n-id="page-title"/>
       </div>
 
       <div class="description">
-        <p>&searchreset.pageInfo1;</p>
-        <p>&searchreset.selector.label;<span id="defaultEngine"/></p>
+        <p data-l10n-id="page-info-outofdate"/>
+        <p id="defaultEngineParagraph">
+          <span id="defaultEngine" data-l10n-name="default-engine"/>
+        </p>
 
-        <p>&searchreset.beforelink.pageInfo2;<a id="linkSettingsPage" href="about:preferences#search">&searchreset.link.pageInfo2;</a>&searchreset.afterlink.pageInfo2;</p>
+        <p data-l10n-id="page-info-how-to-change">
+          <a id="linkSettingsPage" href="about:preferences#search" data-l10n-name="link"></a>
+        </p>
       </div>
 
       <div class="button-container">
         <xul:button id="searchResetKeepCurrent"
-                    label="&searchreset.noChangeButton;"
-                    accesskey="&searchreset.noChangeButton.access;"
+                    data-l10n-id="no-change-button"
                     oncommand="keepCurrentEngine();"/>
         <xul:button class="primary"
                     id="searchResetChangeEngine"
-                    label="&searchreset.changeEngineButton;"
-                    accesskey="&searchreset.changeEngineButton.access;"
+                    data-l10n-id="change-engine-button"
                     oncommand="changeSearchEngine();"/>
       </div>
     </div>
 
   </body>
 </html>
--- a/browser/components/urlbar/tests/browser/browser_UrlbarInput_tooltip.js
+++ b/browser/components/urlbar/tests/browser/browser_UrlbarInput_tooltip.js
@@ -1,56 +1,64 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 function synthesizeMouseOver(element) {
   window.windowUtils.disableNonTestMouseEvents(true);
 
+  let promise = BrowserTestUtils.waitForEvent(gURLBar.inputField, "mouseover");
+
   EventUtils.synthesizeMouse(element, 1, 1, {type: "mouseover"});
   EventUtils.synthesizeMouse(element, 2, 2, {type: "mousemove"});
   EventUtils.synthesizeMouse(element, 3, 3, {type: "mousemove"});
   EventUtils.synthesizeMouse(element, 4, 4, {type: "mousemove"});
+
+  return promise;
 }
 
 function synthesizeMouseOut(element) {
+  let promise = BrowserTestUtils.waitForEvent(gURLBar.inputField, "mouseout");
+
   EventUtils.synthesizeMouse(element, 0, 0, {type: "mouseout"});
   EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"});
   EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"});
   EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"});
 
   window.windowUtils.disableNonTestMouseEvents(false);
+
+  return promise;
 }
 
 async function expectTooltip(text) {
   ok(gURLBar.hasAttribute("textoverflow"), "Urlbar is overflowing");
 
   let tooltip = document.getElementById("aHTMLTooltip");
   let element = gURLBar.inputField;
 
   let popupShownPromise = BrowserTestUtils.waitForEvent(tooltip, "popupshown");
-  synthesizeMouseOver(element);
+  await synthesizeMouseOver(element);
   await popupShownPromise;
 
   is(element.getAttribute("title"), text, "title attribute has expected text");
   is(tooltip.textContent, text, "tooltip shows expected text");
 
-  synthesizeMouseOut(element);
+  await synthesizeMouseOut(element);
 }
 
 async function expectNoTooltip() {
   ok(!gURLBar.hasAttribute("textoverflow"), "Urlbar isn't overflowing");
 
   let element = gURLBar.inputField;
-  synthesizeMouseOver(element);
+  await synthesizeMouseOver(element);
 
   is(element.getAttribute("title"), null, "title attribute shouldn't be set");
 
-  synthesizeMouseOut(element);
+  await synthesizeMouseOut(element);
 }
 
 add_task(async function() {
   // Ensure the URL bar isn't focused.
   gBrowser.selectedBrowser.focus();
 
   gURLBar.value = "short string";
   await expectNoTooltip();
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/browser/aboutSearchReset.ftl
@@ -0,0 +1,17 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+tab-title = Restore Search Settings
+page-title = Restore your search settings?
+page-info-outofdate = Your search settings might be out-of-date. { -brand-short-name } can help you restore the default search settings.
+# Variables:
+#   $searchEngine (String) - Name of the default search engine e.g. Google
+page-info-new-search-engine = This will set your default search engine to <span data-l10n-name="default-engine">{ $searchEngine }</span>
+page-info-how-to-change = You can change these settings at any time from the <a data-l10n-name="link">Settings page</a>.
+no-change-button =
+    .label = Don’t Change
+    .accesskey = D
+change-engine-button =
+    .label = Change Search Engine
+    .accesskey = C
deleted file mode 100644
--- a/browser/locales/en-US/chrome/browser/aboutSearchReset.dtd
+++ /dev/null
@@ -1,30 +0,0 @@
-<!-- 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/. -->
-
-<!ENTITY searchreset.tabtitle       "Restore Search Settings">
-
-<!ENTITY searchreset.pageTitle      "Restore your search settings?">
-
-<!ENTITY searchreset.pageInfo1      "Your search settings might be out-of-date. &brandShortName; can help you restore the default search settings.">
-
-
-<!-- LOCALIZATION NOTE (searchreset.selector.label): this string is
-followed by a dropdown of all the built-in search engines. -->
-<!ENTITY searchreset.selector.label "This will set your default search engine to">
-
-<!-- LOCALIZATION NOTE (searchreset.beforelink.pageInfo,
-searchreset.afterlink.pageInfo): these two string are used respectively
-before and after the "Settings page" link (searchreset.link.pageInfo2).
-Localizers can use one of them, or both, to better adapt this sentence to
-their language. -->
-<!ENTITY searchreset.beforelink.pageInfo2 "You can change these settings at any time from the ">
-<!ENTITY searchreset.afterlink.pageInfo2  ".">
-
-<!ENTITY searchreset.link.pageInfo2       "Settings page">
-
-<!ENTITY searchreset.noChangeButton        "Don’t Change">
-<!ENTITY searchreset.noChangeButton.access "D">
-
-<!ENTITY searchreset.changeEngineButton        "Change Search Engine">
-<!ENTITY searchreset.changeEngineButton.access "C">
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -1014,24 +1014,24 @@ you can use these alternative items. Oth
 <!ENTITY contentBlocking.breakageReportView.collection.url.label "URL">
 <!ENTITY contentBlocking.breakageReportView.collection.comments.label "What problems did you have? (Optional)">
 <!ENTITY contentBlocking.breakageReportView.sendReport.label "Send Report">
 <!ENTITY contentBlocking.breakageReportView.cancel.label "Cancel">
 
 <!-- LOCALIZATION NOTE (trackingProtection.unblock5.label, trackingProtection.unblock5.accesskey):
      The associated button with this label and accesskey is only shown when opening the control
      center while looking at a site with trackers in NON-private browsing mode. -->
-<!ENTITY trackingProtection.unblock5.label "Turn off blocking for this site">
+<!ENTITY trackingProtection.unblock5.label "Turn off Blocking for This Site">
 <!ENTITY trackingProtection.unblock5.accesskey "T">
 <!-- LOCALIZATION NOTE (trackingProtection.unblockPrivate6.label, trackingProtection.unblockPrivate6.accesskey):
      The associated button with this label and accesskey is only shown when opening the control
      center while looking at a site with trackers in PRIVATE browsing mode. -->
-<!ENTITY trackingProtection.unblockPrivate5.label "Turn off blocking temporarily">
+<!ENTITY trackingProtection.unblockPrivate5.label "Turn off Blocking Temporarily">
 <!ENTITY trackingProtection.unblockPrivate5.accesskey "T">
-<!ENTITY trackingProtection.block6.label "Turn on blocking for this site">
+<!ENTITY trackingProtection.block6.label "Turn on Blocking for This Site">
 <!ENTITY trackingProtection.block6.accesskey "T">
 <!ENTITY trackingProtection.reload2.label "Reload Page">
 <!ENTITY trackingProtection.reload2.accesskey "R">
 
 <!ENTITY pluginNotification.showAll.label "Show All">
 <!ENTITY pluginNotification.showAll.accesskey "S">
 
 <!-- LOCALIZATION NOTE (pluginActivateNow.label, pluginActivateAlways.label, pluginBlockNow.label): These should be the same as the matching strings in browser.properties -->
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -12,17 +12,16 @@
 
 @AB_CD@.jar:
 % locale browser @AB_CD@ %locale/browser/
 # bookmarks.html is produced by LOCALIZED_GENERATED_FILES.
     locale/browser/bookmarks.html                  (bookmarks.html)
     locale/browser/aboutPrivateBrowsing.dtd        (%chrome/browser/aboutPrivateBrowsing.dtd)
     locale/browser/aboutRobots.dtd                 (%chrome/browser/aboutRobots.dtd)
     locale/browser/accounts.properties             (%chrome/browser/accounts.properties)
-    locale/browser/aboutSearchReset.dtd            (%chrome/browser/aboutSearchReset.dtd)
     locale/browser/aboutTabCrashed.dtd             (%chrome/browser/aboutTabCrashed.dtd)
     locale/browser/browser.dtd                     (%chrome/browser/browser.dtd)
     locale/browser/baseMenuOverlay.dtd             (%chrome/browser/baseMenuOverlay.dtd)
     locale/browser/browser.properties              (%chrome/browser/browser.properties)
     locale/browser/customizableui/customizableWidgets.properties (%chrome/browser/customizableui/customizableWidgets.properties)
     locale/browser/lightweightThemes.properties    (%chrome/browser/lightweightThemes.properties)
     locale/browser/uiDensity.properties            (%chrome/browser/uiDensity.properties)
     locale/browser/pageInfo.dtd                    (%chrome/browser/pageInfo.dtd)
--- a/browser/modules/PermissionUI.jsm
+++ b/browser/modules/PermissionUI.jsm
@@ -1046,16 +1046,23 @@ StorageAccessPermissionPrompt.prototype 
   },
 
   getOriginsThirdPartyHasAccessTo(thirdPartyOrigin) {
     let prefix = `3rdPartyStorage^${thirdPartyOrigin}`;
     let perms = Services.perms.getAllWithTypePrefix(prefix);
     let origins = new Set();
     while (perms.length) {
       let perm = perms.shift();
+      // Let's make sure that we're not looking at a permission for
+      // https://exampletracker.company when we mean to look for the
+      // permisison for https://exampletracker.com!
+      if (perm.type != prefix &&
+          !perm.type.startsWith(`${prefix}^`)) {
+        continue;
+      }
       origins.add(perm.principal.origin);
     }
     return origins.size;
   },
 
   onBeforeShow() {
     let thirdPartyOrigin = this.request.principal.origin;
     if (this._autoGrants &&
--- a/browser/themes/shared/controlcenter/panel.inc.css
+++ b/browser/themes/shared/controlcenter/panel.inc.css
@@ -140,17 +140,17 @@
 .identity-popup-preferences-button {
   min-width: 32px;
   background: url(chrome://browser/skin/settings.svg) center right 8px no-repeat;
   padding: 5px 8px !important;
   margin-bottom: 2px !important;
 }
 
 .identity-popup-preferences-button:-moz-locale-dir(rtl) {
-  background-position: center left;
+  background-position: center left 8px;
 }
 
 .identity-popup-preferences-button > .toolbarbutton-text {
   display: none;
 }
 
 #tracking-protection-preferences-button > .toolbarbutton-text {
   display: inline;
--- a/browser/themes/shared/customizableui/panelUI.inc.css
+++ b/browser/themes/shared/customizableui/panelUI.inc.css
@@ -591,20 +591,35 @@ toolbarbutton[constrain-size="true"][cui
   background-color: @appmenuWarningBackgroundColorActive@;
 }
 
 /* Tracking Protection Button & Toggle */
 
 #appMenu-tp-label {
   -moz-context-properties: fill;
   fill: currentColor;
+  -moz-box-flex: 1;
+  padding: 0;
+  padding-inline-start: 8px;
+  margin: 0;
+}
+
+#appMenu-tp-icon {
   list-style-image: url(chrome://browser/skin/tracking-protection.svg);
+}
+
+#appMenu-tp-button {
   -moz-box-flex: 1;
 }
 
+#appMenu-tp-category {
+  color: var(--panel-disabled-color);
+  margin-inline-end: 0;
+}
+
 .addon-banner-item > .toolbarbutton-text,
 .panel-banner-item > .toolbarbutton-text {
   margin: 0;
   padding: 0 6px;
   text-align: start;
 }
 
 .addon-banner-item > .toolbarbutton-icon,
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -1576,27 +1576,29 @@ def security_hardening_cflags(hardening_
         if compiler_is_gccish and optimize and not asan:
             # Don't enable FORTIFY_SOURCE on Android on the top-level, but do enable in js/
             if target.os != 'Android':
                 flags.append("-U_FORTIFY_SOURCE")
                 flags.append("-D_FORTIFY_SOURCE=2")
             js_flags.append("-U_FORTIFY_SOURCE")
             js_flags.append("-D_FORTIFY_SOURCE=2")
 
+        # fstack-protector ------------------------------------
+        # Enable only if hardening is not disabled and ASAN is
+        # not on as ASAN will catch the crashes for us
+        if compiler_is_gccish and not asan:
+            # mingw-clang cross-compile toolchain has bugs with stack protector
+            if target.os != 'WINNT' or c_compiler == 'gcc':
+                flags.append("-fstack-protector-strong")
+
     # If ASAN _is_ on, undefine FOTIFY_SOURCE just to be safe
     if asan:
         flags.append("-U_FORTIFY_SOURCE")
         js_flags.append("-U_FORTIFY_SOURCE")
 
-    # fstack-protector ------------------------------------
-    # Enable only if --enable-hardening is passed and ASAN is
-    # not on as ASAN will catch the crashes for us
-    if hardening_flag and compiler_is_gccish and not asan:
-        flags.append("-fstack-protector-strong")
-
     # fno-common -----------------------------------------
     # Do not merge variables for ASAN; can detect some subtle bugs
     if asan:
         # clang-cl does not recognize the flag, it must be passed down to clang
         if c_compiler.type == 'clang-cl':
             flags.append("-Xclang")
         flags.append("-fno-common")
 
--- a/build/moz.configure/warnings.configure
+++ b/build/moz.configure/warnings.configure
@@ -9,21 +9,25 @@ js_option('--enable-warnings-as-errors',
           help='{Enable|Disable} treating warnings as errors')
 
 add_old_configure_assignment(
     'MOZ_ENABLE_WARNINGS_AS_ERRORS',
     depends('--enable-warnings-as-errors')(lambda x: bool(x)))
 
 
 # GCC/Clang warnings:
-# https://gcc.gnu.org/onlinedocs/gcc-4.7.2/gcc/Warning-Options.html
+# https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html
+# https://clang.llvm.org/docs/DiagnosticsReference.html
 
 # lots of useful warnings
 add_gcc_warning('-Wall')
 
+# catch implicit truncation of enum values assigned to smaller bit fields
+check_and_add_gcc_warning('-Wbitfield-enum-conversion')
+
 # catches bugs, e.g. "if (c); foo();", few false positives
 add_gcc_warning('-Wempty-body')
 
 # catches return types with qualifiers like const
 add_gcc_warning('-Wignored-qualifiers')
 
 # function declaration hides virtual function from base class
 add_gcc_warning('-Woverloaded-virtual', cxx_compiler)
--- a/devtools/client/aboutdebugging-new/src/components/App.js
+++ b/devtools/client/aboutdebugging-new/src/components/App.js
@@ -75,17 +75,20 @@ class App extends PureComponent {
     const isDeviceFirstPage =
       !this.props.selectedPage &&
       match.params.runtimeId !== RUNTIMES.THIS_FIREFOX;
     if (!match.params.runtimeId || isDeviceFirstPage) {
       return Redirect({ to: `/runtime/${RUNTIMES.THIS_FIREFOX}` });
     }
 
     const isRuntimeAvailable = id => {
-      const runtimes = this.props.usbRuntimes;
+      const runtimes = [
+        ...this.props.networkRuntimes,
+        ...this.props.usbRuntimes,
+      ];
       return !!runtimes.find(x => x.id === id);
     };
 
     const { dispatch } = this.props;
 
     let runtimeId = match.params.runtimeId || RUNTIMES.THIS_FIREFOX;
     if (match.params.runtimeId !== RUNTIMES.THIS_FIREFOX) {
       const rawId = decodeURIComponent(match.params.runtimeId);
--- a/devtools/client/aboutdebugging-new/src/modules/debug-target-support.js
+++ b/devtools/client/aboutdebugging-new/src/modules/debug-target-support.js
@@ -14,16 +14,17 @@ const ALL_DEBUG_TARGETS = [
 
 const SUPPORTED_TARGET_BY_RUNTIME = {
   [RUNTIMES.THIS_FIREFOX]: ALL_DEBUG_TARGETS,
   [RUNTIMES.USB]: [
     DEBUG_TARGETS.EXTENSION,
     DEBUG_TARGETS.TAB,
   ],
   [RUNTIMES.NETWORK]: [
+    DEBUG_TARGETS.EXTENSION,
     DEBUG_TARGETS.TAB,
   ],
 };
 
 function isSupportedDebugTarget(runtimeType, debugTargetType) {
   return SUPPORTED_TARGET_BY_RUNTIME[runtimeType].includes(debugTargetType);
 }
 exports.isSupportedDebugTarget = isSupportedDebugTarget;
@@ -39,16 +40,17 @@ const ALL_DEBUG_TARGET_PANES = [
 
 const SUPPORTED_TARGET_PANE_BY_RUNTIME = {
   [RUNTIMES.THIS_FIREFOX]: ALL_DEBUG_TARGET_PANES,
   [RUNTIMES.USB]: [
     DEBUG_TARGET_PANE.INSTALLED_EXTENSION,
     DEBUG_TARGET_PANE.TAB,
   ],
   [RUNTIMES.NETWORK]: [
+    DEBUG_TARGET_PANE.INSTALLED_EXTENSION,
     DEBUG_TARGET_PANE.TAB,
   ],
 };
 
 /**
  * A debug target pane is more specialized than a debug target. For instance EXTENSION is
  * a DEBUG_TARGET but INSTALLED_EXTENSION and TEMPORARY_EXTENSION are DEBUG_TARGET_PANES.
  */
--- a/devtools/client/aboutdebugging-new/test/browser/browser.ini
+++ b/devtools/client/aboutdebugging-new/test/browser/browser.ini
@@ -3,38 +3,40 @@ tags = devtools
 subsuite = devtools
 prefs =
   # showSystemAddons has different values depending on the build flags,
   # ensure consistent test behavior by always setting this to false.
   devtools.aboutdebugging.showSystemAddons=false
 support-files =
   debug-target-pane_collapsibilities_head.js
   head-addons-script.js
+  head-mocks.js
   head.js
   mocks/*
   resources/test-adb-extension/*
   resources/test-temporary-extension/*
   test-tab-favicons.html
   !/devtools/client/shared/test/shared-head.js
   !/devtools/client/shared/test/shared-redux-head.js
   !/devtools/client/shared/test/telemetry-test-helpers.js
 
-[browser_aboutdebugging_addons_usb_runtime.js]
+[browser_aboutdebugging_addons_remote_runtime.js]
 [browser_aboutdebugging_connect_networklocations.js]
 [browser_aboutdebugging_connect_toggle_usb_devices.js]
 skip-if = (os == 'linux' && bits == 32) # ADB start() fails on linux 32, see Bug 1499638
 [browser_aboutdebugging_connection_prompt_setting.js]
 [browser_aboutdebugging_debug-target-pane_collapsibilities_interaction.js]
 [browser_aboutdebugging_debug-target-pane_collapsibilities_preference.js]
 [browser_aboutdebugging_debug-target-pane_empty.js]
 [browser_aboutdebugging_debug-target-pane_usb_runtime.js]
 [browser_aboutdebugging_devtools.js]
 [browser_aboutdebugging_navigate.js]
 [browser_aboutdebugging_persist_connection.js]
 [browser_aboutdebugging_routes.js]
+[browser_aboutdebugging_select_network_runtime.js]
 [browser_aboutdebugging_sidebar_network_runtimes.js]
 [browser_aboutdebugging_sidebar_usb_runtime.js]
 [browser_aboutdebugging_sidebar_usb_runtime_connect.js]
 [browser_aboutdebugging_sidebar_usb_runtime_refresh.js]
 [browser_aboutdebugging_sidebar_usb_status.js]
 skip-if = (os == 'linux' && bits == 32) # ADB start() fails on linux 32, see Bug 1499638
 [browser_aboutdebugging_system_addons.js]
 [browser_aboutdebugging_tab_favicons.js]
rename from devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_addons_usb_runtime.js
rename to devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_addons_remote_runtime.js
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_addons_usb_runtime.js
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_addons_remote_runtime.js
@@ -1,73 +1,91 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-const RUNTIME_ID = "test-runtime-id";
-const RUNTIME_DEVICE_NAME = "test device name";
-const RUNTIME_APP_NAME = "TestApp";
+const NETWORK_RUNTIME_HOST = "localhost:6080";
+const NETWORK_RUNTIME_APP_NAME = "TestNetworkApp";
+const USB_RUNTIME_ID = "test-runtime-id";
+const USB_RUNTIME_DEVICE_NAME = "test device name";
+const USB_RUNTIME_APP_NAME = "TestUsbApp";
 
-/* import-globals-from mocks/head-usb-mocks.js */
-Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "mocks/head-usb-mocks.js", this);
+/* import-globals-from head-mocks.js */
+Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "head-mocks.js", this);
 
 // Test that addons are displayed and updated for USB runtimes when expected.
 add_task(async function() {
-  const usbMocks = new UsbMocks();
-  usbMocks.enableMocks();
-  registerCleanupFunction(() => usbMocks.disableMocks());
+  const mocks = new Mocks();
 
   const { document, tab } = await openAboutDebugging();
 
-  const usbClient = usbMocks.createRuntime(RUNTIME_ID, {
-    deviceName: RUNTIME_DEVICE_NAME,
-    name: RUNTIME_APP_NAME,
+  info("Prepare USB client mock");
+  const usbClient = mocks.createUSBRuntime(USB_RUNTIME_ID, {
+    deviceName: USB_RUNTIME_DEVICE_NAME,
+    name: USB_RUNTIME_APP_NAME,
   });
-  usbMocks.emitUpdate();
+  mocks.emitUSBUpdate();
+
+  info("Test addons in runtime page for USB client");
+  await connectToRuntime(USB_RUNTIME_DEVICE_NAME, document);
+  await selectRuntime(USB_RUNTIME_DEVICE_NAME, USB_RUNTIME_APP_NAME, document);
+  await testAddonsOnMockedRemoteClient(usbClient, mocks.thisFirefoxClient, document);
+
+  info("Prepare Network client mock");
+  const networkClient = mocks.createNetworkRuntime(NETWORK_RUNTIME_HOST, {
+    name: NETWORK_RUNTIME_APP_NAME,
+  });
 
-  await connectToRuntime(RUNTIME_DEVICE_NAME, document);
-  await selectRuntime(RUNTIME_DEVICE_NAME, RUNTIME_APP_NAME, document);
+  info("Test addons in runtime page for Network client");
+  await connectToRuntime(NETWORK_RUNTIME_HOST, document);
+  await selectRuntime(NETWORK_RUNTIME_HOST, NETWORK_RUNTIME_APP_NAME, document);
+  await testAddonsOnMockedRemoteClient(networkClient, mocks.thisFirefoxClient, document);
 
+  await removeTab(tab);
+});
+
+/**
+ * Check that addons are visible in the runtime page for a remote client (USB or network).
+ */
+async function testAddonsOnMockedRemoteClient(remoteClient, firefoxClient, document) {
   const extensionPane = getDebugTargetPane("Extensions", document);
   info("Check an empty target pane message is displayed");
   ok(extensionPane.querySelector(".js-debug-target-list-empty"),
     "Extensions list is empty");
 
   info("Add an extension to the remote client");
   const addon = { name: "Test extension name", debuggable: true };
-  usbClient.listAddons = () => [addon];
-  usbClient._eventEmitter.emit("addonListChanged");
+  remoteClient.listAddons = () => [addon];
+  remoteClient._eventEmitter.emit("addonListChanged");
 
   info("Wait until the extension appears");
   await waitUntil(() => !extensionPane.querySelector(".js-debug-target-list-empty"));
 
   const extensionTarget = findDebugTargetByText("Test extension name", document);
-  ok(extensionTarget, "Extension target appeared for the USB runtime");
+  ok(extensionTarget, "Extension target appeared for the remote runtime");
 
-  // The goal here is to check that USB runtimes addons are only updated when the USB
+  // The goal here is to check that runtimes addons are only updated when the remote
   // runtime is sending addonListChanged events. The reason for this test is because the
-  // previous implementation was updating the USB runtime extensions list when the _local_
-  // AddonManager was updated.
+  // previous implementation was updating the remote runtime extensions list when the
+  // _local_ AddonManager was updated.
   info("Remove the extension from the remote client WITHOUT sending an event");
-  usbClient.listAddons = () => [];
+  remoteClient.listAddons = () => [];
 
   info("Simulate an addon update on the ThisFirefox client");
-  usbMocks.thisFirefoxClient._eventEmitter.emit("addonListChanged");
+  firefoxClient._eventEmitter.emit("addonListChanged");
 
   // To avoid wait for a set period of time we trigger another async update, adding a new
   // tab. We assume that if the addon update mechanism had started, it would also be done
   // when the new tab was processed.
   info("Wait until the tab target for 'http://some.random/url.com' appears");
   const testTab = { outerWindowID: 0, url: "http://some.random/url.com" };
-  usbClient.listTabs = () => ({ tabs: [testTab] });
-  usbClient._eventEmitter.emit("tabListChanged");
+  remoteClient.listTabs = () => ({ tabs: [testTab] });
+  remoteClient._eventEmitter.emit("tabListChanged");
   await waitUntil(() => findDebugTargetByText("http://some.random/url.com", document));
 
   ok(findDebugTargetByText("Test extension name", document),
     "The test extension is still visible");
 
-  info("Emit `addonListChanged` on usbClient and wait for the target list to update");
-  usbClient._eventEmitter.emit("addonListChanged");
+  info("Emit `addonListChanged` on remoteClient and wait for the target list to update");
+  remoteClient._eventEmitter.emit("addonListChanged");
   await waitUntil(() => !findDebugTargetByText("Test extension name", document));
-
-  await removeTab(tab);
-});
+}
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_debug-target-pane_usb_runtime.js
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_debug-target-pane_usb_runtime.js
@@ -2,34 +2,30 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 const RUNTIME_ID = "test-runtime-id";
 const RUNTIME_DEVICE_NAME = "test device name";
 const RUNTIME_APP_NAME = "TestApp";
 
-/* import-globals-from mocks/head-usb-mocks.js */
-Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "mocks/head-usb-mocks.js", this);
+/* import-globals-from head-mocks.js */
+Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "head-mocks.js", this);
 
 // Test that the expected supported categories are displayed for USB runtimes.
 add_task(async function() {
-  const usbMocks = new UsbMocks();
-  usbMocks.enableMocks();
-  registerCleanupFunction(() => {
-    usbMocks.disableMocks();
-  });
+  const mocks = new Mocks();
 
   const { document, tab } = await openAboutDebugging();
 
-  usbMocks.createRuntime(RUNTIME_ID, {
+  mocks.createUSBRuntime(RUNTIME_ID, {
     deviceName: RUNTIME_DEVICE_NAME,
     name: RUNTIME_APP_NAME,
   });
-  usbMocks.emitUpdate();
+  mocks.emitUSBUpdate();
 
   await connectToRuntime(RUNTIME_DEVICE_NAME, document);
   await selectRuntime(RUNTIME_DEVICE_NAME, RUNTIME_APP_NAME, document);
 
   const SUPPORTED_TARGET_PANES = [
     "Extensions",
     "Tabs",
   ];
@@ -39,16 +35,16 @@ add_task(async function() {
     if (SUPPORTED_TARGET_PANES.includes(title)) {
       ok(debugTargetPaneEl, `Supported target pane [${title}] is displayed`);
     } else {
       ok(!debugTargetPaneEl, `Unsupported target pane [${title}] is hidden`);
     }
   }
 
   info("Remove USB runtime");
-  usbMocks.removeRuntime(RUNTIME_ID);
-  usbMocks.emitUpdate();
+  mocks.removeUSBRuntime(RUNTIME_ID);
+  mocks.emitUSBUpdate();
 
   info("Wait until the USB sidebar item disappears");
   await waitUntil(() => !findSidebarItemByText(RUNTIME_DEVICE_NAME, document));
 
   await removeTab(tab);
 });
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_persist_connection.js
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_persist_connection.js
@@ -2,50 +2,48 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 const RUNTIME_ID = "test-runtime-id";
 const RUNTIME_DEVICE_NAME = "test device name";
 const RUNTIME_APP_NAME = "TestApp";
 
-/* import-globals-from mocks/head-usb-mocks.js */
-Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "mocks/head-usb-mocks.js", this);
+/* import-globals-from head-mocks.js */
+Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "head-mocks.js", this);
 
 // Test that remote runtime connections are persisted across about:debugging reloads.
 add_task(async function() {
-  const usbMocks = new UsbMocks();
-  usbMocks.enableMocks();
-  registerCleanupFunction(() => usbMocks.disableMocks());
+  const mocks = new Mocks();
 
   let { document, tab } = await openAboutDebugging();
 
-  const usbClient = usbMocks.createRuntime(RUNTIME_ID, {
+  const usbClient = mocks.createUSBRuntime(RUNTIME_ID, {
     name: RUNTIME_APP_NAME,
     deviceName: RUNTIME_DEVICE_NAME,
   });
-  usbMocks.emitUpdate();
+  mocks.emitUSBUpdate();
 
   await connectToRuntime(RUNTIME_DEVICE_NAME, document);
   await selectRuntime(RUNTIME_DEVICE_NAME, RUNTIME_APP_NAME, document);
 
   info("Reload about:debugging");
   document = await reloadAboutDebugging(tab);
-  usbMocks.emitUpdate();
+  mocks.emitUSBUpdate();
 
   info("Wait until the remote runtime appears as connected");
   await waitUntil(() => {
     const sidebarItem = findSidebarItemByText(RUNTIME_DEVICE_NAME, document);
     return sidebarItem && !sidebarItem.querySelector(".js-connect-button");
   });
 
   // Remove the runtime without emitting an update.
   // This is what happens today when we simply close Firefox for Android.
   info("Remove the runtime from the list of USB runtimes");
-  usbMocks.removeRuntime(RUNTIME_ID);
+  mocks.removeUSBRuntime(RUNTIME_ID);
 
   info("Emit 'closed' on the client and wait for the sidebar item to disappear");
   usbClient._eventEmitter.emit("closed");
   await waitUntil(() => !findSidebarItemByText(RUNTIME_DEVICE_NAME, document));
 
   info("Remove the tab");
   await removeTab(tab);
 });
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_routes.js
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_routes.js
@@ -1,15 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-/* import-globals-from mocks/head-usb-mocks.js */
-Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "mocks/head-usb-mocks.js", this);
+/* import-globals-from head-mocks.js */
+Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "head-mocks.js", this);
 
 /**
  * Test that the initial route is /runtime/this-firefox
  */
 add_task(async function() {
   info("Check root route redirects to 'This Firefox'");
   const { document, tab } = await openAboutDebugging();
   is(document.location.hash, "#/runtime/this-firefox");
@@ -17,19 +17,17 @@ add_task(async function() {
   await removeTab(tab);
 });
 
 /**
  * Test that the routes in about:debugging show the proper views
  */
 add_task(async function() {
   // enable USB devices mocks
-  const usbMocks = new UsbMocks();
-  usbMocks.enableMocks();
-  registerCleanupFunction(() => usbMocks.disableMocks());
+  const mocks = new Mocks();
 
   const { document, tab } = await openAboutDebugging();
 
   info("Check 'This Firefox' route");
   document.location.hash = "#/runtime/this-firefox";
   await waitUntil(() => document.querySelector(".js-runtime-page"));
   const infoLabel = document.querySelector(".js-runtime-info").textContent;
   // NOTE: when using USB Mocks, we see only "Firefox" as the device name
@@ -38,21 +36,21 @@ add_task(async function() {
 
   info("Check 'Connect' page");
   document.location.hash = "#/connect";
   await waitUntil(() => document.querySelector(".js-connect-page"));
   ok(true, "Connect page has been shown");
 
   info("Check 'USB device runtime' page");
   // connect to a mocked USB runtime
-  usbMocks.createRuntime("1337id", {
+  mocks.createUSBRuntime("1337id", {
     deviceName: "Fancy Phone",
     name: "Lorem ipsum",
   });
-  usbMocks.emitUpdate();
+  mocks.emitUSBUpdate();
   await connectToRuntime("Fancy Phone", document);
   // navigate to it via URL
   document.location.hash = "#/runtime/1337id";
   await waitUntil(() => document.querySelector(".js-runtime-page"));
   const runtimeLabel = document.querySelector(".js-runtime-info").textContent;
   ok(runtimeLabel.includes("Lorem ipsum"), "Runtime is displayed with the mocked name");
 
   await removeTab(tab);
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_select_network_runtime.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const NETWORK_RUNTIME_HOST = "localhost:6080";
+const NETWORK_RUNTIME_APP_NAME = "TestNetworkApp";
+const NETWORK_RUNTIME_CHANNEL = "SomeChannel";
+const NETWORK_RUNTIME_VERSION = "12.3";
+
+/* import-globals-from head-mocks.js */
+Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "head-mocks.js", this);
+
+// Test that network runtimes can be selected.
+add_task(async function() {
+  const mocks = new Mocks();
+
+  const { document, tab } = await openAboutDebugging();
+
+  info("Prepare Network client mock");
+  const networkClient = mocks.createNetworkRuntime(NETWORK_RUNTIME_HOST, {
+    name: NETWORK_RUNTIME_APP_NAME,
+  });
+  networkClient.getDeviceDescription = () => {
+    return {
+      name: NETWORK_RUNTIME_APP_NAME,
+      channel: NETWORK_RUNTIME_CHANNEL,
+      version: NETWORK_RUNTIME_VERSION,
+    };
+  };
+
+  info("Test addons in runtime page for Network client");
+  await connectToRuntime(NETWORK_RUNTIME_HOST, document);
+  await selectRuntime(NETWORK_RUNTIME_HOST, NETWORK_RUNTIME_APP_NAME, document);
+
+  info("Check that the network runtime mock is properly displayed");
+  const thisFirefoxRuntimeInfo = document.querySelector(".js-runtime-info");
+  ok(thisFirefoxRuntimeInfo, "Runtime info for this-firefox runtime is displayed");
+  const runtimeInfoText = thisFirefoxRuntimeInfo.textContent;
+
+  ok(runtimeInfoText.includes(NETWORK_RUNTIME_APP_NAME),
+    "network runtime info shows the correct runtime name: " + runtimeInfoText);
+  ok(runtimeInfoText.includes(NETWORK_RUNTIME_VERSION),
+    "network runtime info shows the correct version number: " + runtimeInfoText);
+
+  await removeTab(tab);
+});
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_sidebar_usb_runtime_connect.js
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_sidebar_usb_runtime_connect.js
@@ -1,43 +1,39 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-/* import-globals-from mocks/head-usb-mocks.js */
-Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "mocks/head-usb-mocks.js", this);
+/* import-globals-from head-mocks.js */
+Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "head-mocks.js", this);
 
 const RUNTIME_ID = "test-runtime-id";
 const RUNTIME_DEVICE_NAME = "test device name";
 
 // Test that USB runtimes appear and disappear from the sidebar.
 add_task(async function() {
-  const usbMocks = new UsbMocks();
-  usbMocks.enableMocks();
-  registerCleanupFunction(() => {
-    usbMocks.disableMocks();
-  });
+  const mocks = new Mocks();
 
   const { document, tab } = await openAboutDebugging();
 
-  usbMocks.createRuntime(RUNTIME_ID, { deviceName: RUNTIME_DEVICE_NAME });
-  usbMocks.emitUpdate();
+  mocks.createUSBRuntime(RUNTIME_ID, { deviceName: RUNTIME_DEVICE_NAME });
+  mocks.emitUSBUpdate();
 
   info("Wait until the USB sidebar item appears");
   await waitUntil(() => findSidebarItemByText(RUNTIME_DEVICE_NAME, document));
   const usbRuntimeSidebarItem = findSidebarItemByText(RUNTIME_DEVICE_NAME, document);
   const connectButton = usbRuntimeSidebarItem.querySelector(".js-connect-button");
   ok(connectButton, "Connect button is displayed for the USB runtime");
 
   info("Click on the connect button and wait until it disappears");
   connectButton.click();
   await waitUntil(() => !usbRuntimeSidebarItem.querySelector(".js-connect-button"));
 
   info("Remove all USB runtimes");
-  usbMocks.removeRuntime(RUNTIME_ID);
-  usbMocks.emitUpdate();
+  mocks.removeUSBRuntime(RUNTIME_ID);
+  mocks.emitUSBUpdate();
 
   info("Wait until the USB sidebar item disappears");
   await waitUntil(() => !findSidebarItemByText(RUNTIME_DEVICE_NAME, document));
 
   await removeTab(tab);
 });
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_sidebar_usb_runtime_refresh.js
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_sidebar_usb_runtime_refresh.js
@@ -5,44 +5,42 @@
 
 const RUNTIME_ID = "test-runtime-id";
 const RUNTIME_DEVICE_NAME = "test device name";
 const RUNTIME_APP_NAME = "TestApp";
 
 const OTHER_RUNTIME_ID = "other-runtime-id";
 const OTHER_RUNTIME_APP_NAME = "OtherApp";
 
-/* import-globals-from mocks/head-usb-mocks.js */
-Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "mocks/head-usb-mocks.js", this);
+/* import-globals-from head-mocks.js */
+Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "head-mocks.js", this);
 
 // Test that USB runtimes are ot disconnected on refresh.
 add_task(async function() {
-  const usbMocks = new UsbMocks();
-  usbMocks.enableMocks();
-  registerCleanupFunction(() => usbMocks.disableMocks());
+  const mocks = new Mocks();
 
   const { document, tab } = await openAboutDebugging();
 
   info("Create a first runtime and connect to it");
-  usbMocks.createRuntime(RUNTIME_ID, {
+  mocks.createUSBRuntime(RUNTIME_ID, {
     deviceName: RUNTIME_DEVICE_NAME,
     name: RUNTIME_APP_NAME,
   });
-  usbMocks.emitUpdate();
+  mocks.emitUSBUpdate();
 
   await connectToRuntime(RUNTIME_DEVICE_NAME, document);
   await selectRuntime(RUNTIME_DEVICE_NAME, RUNTIME_APP_NAME, document);
 
   info("Create a second runtime and click on Refresh Devices");
-  usbMocks.createRuntime(OTHER_RUNTIME_ID, {
+  mocks.createUSBRuntime(OTHER_RUNTIME_ID, {
     deviceName: OTHER_RUNTIME_APP_NAME,
   });
 
   // Mock the refreshUSBRuntimes to emit an update.
-  usbMocks.usbRuntimesMock.refreshUSBRuntimes = () => usbMocks.emitUpdate();
+  mocks.usbRuntimesMock.refreshUSBRuntimes = () => mocks.emitUSBUpdate();
   document.querySelector(".js-refresh-devices-button").click();
 
   info(`Wait until the sidebar item for ${OTHER_RUNTIME_APP_NAME} appears`);
   await waitUntil(() => findSidebarItemByText(OTHER_RUNTIME_APP_NAME, document));
 
   const sidebarItem = findSidebarItemByText(RUNTIME_DEVICE_NAME, document);
   ok(!sidebarItem.querySelector(".js-connect-button"),
     "Original USB runtime is still connected");
rename from devtools/client/aboutdebugging-new/test/browser/mocks/head-usb-mocks.js
rename to devtools/client/aboutdebugging-new/test/browser/head-mocks.js
--- a/devtools/client/aboutdebugging-new/test/browser/mocks/head-usb-mocks.js
+++ b/devtools/client/aboutdebugging-new/test/browser/head-mocks.js
@@ -2,74 +2,114 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 const MOCKS_ROOT = CHROME_URL_ROOT + "mocks/";
 
 const { RUNTIMES } = require("devtools/client/aboutdebugging-new/src/constants");
 
-/* import-globals-from head-client-wrapper-mock.js */
+/* import-globals-from mocks/head-client-wrapper-mock.js */
 Services.scriptloader.loadSubScript(MOCKS_ROOT + "head-client-wrapper-mock.js", this);
-/* import-globals-from head-runtime-client-factory-mock.js */
+/* import-globals-from mocks/head-runtime-client-factory-mock.js */
 Services.scriptloader.loadSubScript(MOCKS_ROOT + "head-runtime-client-factory-mock.js",
   this);
-/* import-globals-from head-usb-runtimes-mock.js */
+/* import-globals-from mocks/head-usb-runtimes-mock.js */
 Services.scriptloader.loadSubScript(MOCKS_ROOT + "head-usb-runtimes-mock.js", this);
 
 /**
- * This wrapper around the USB mocks used in about:debugging tests provides helpers to
- * quickly setup mocks for typical USB runtime tests.
+ * This wrapper around the mocks used in about:debugging tests provides helpers to
+ * quickly setup mocks for runtime tests involving USB, network or wifi runtimes that can
+ * are difficult to setup in a test environment.
  */
-class UsbMocks {
+class Mocks {
   constructor() {
-    // Setup the usb-runtimes mock to rely on the internal _runtimes array.
+    // Setup the usb-runtimes mock to rely on the internal _usbRuntimes array.
     this.usbRuntimesMock = createUsbRuntimesMock();
-    this._runtimes = [];
+    this._usbRuntimes = [];
     this.usbRuntimesMock.getUSBRuntimes = () => {
-      return this._runtimes;
+      return this._usbRuntimes;
     };
 
     // refreshUSBRuntimes normally starts scan, which should ultimately fire the
     // "runtime-list-updated" event.
     this.usbRuntimesMock.refreshUSBRuntimes = () => {
-      this.emitUpdate();
+      this.emitUSBUpdate();
     };
 
     // Prepare a fake observer to be able to emit events from this mock.
     this._observerMock = addObserverMock(this.usbRuntimesMock);
 
     // Setup the runtime-client-factory mock to rely on the internal _clients map.
     this.runtimeClientFactoryMock = createRuntimeClientFactoryMock();
-    this._clients = {};
+    this._clients = {
+      [RUNTIMES.NETWORK]: {},
+      [RUNTIMES.THIS_FIREFOX]: {},
+      [RUNTIMES.USB]: {},
+    };
     this.runtimeClientFactoryMock.createClientForRuntime = runtime => {
-      return this._clients[runtime.id];
+      return this._clients[runtime.type][runtime.id];
     };
 
     // Add a client for THIS_FIREFOX, since about:debugging will start on the This Firefox
     // page.
     this._thisFirefoxClient = createThisFirefoxClientMock();
-    this._clients[RUNTIMES.THIS_FIREFOX] = this._thisFirefoxClient;
+    this._clients[RUNTIMES.THIS_FIREFOX][RUNTIMES.THIS_FIREFOX] = this._thisFirefoxClient;
+
+    // Enable mocks and remove them after the test.
+    this.enableMocks();
+    registerCleanupFunction(() => this.disableMocks());
   }
 
   get thisFirefoxClient() {
     return this._thisFirefoxClient;
   }
 
   enableMocks() {
     enableUsbRuntimesMock(this.usbRuntimesMock);
     enableRuntimeClientFactoryMock(this.runtimeClientFactoryMock);
   }
 
   disableMocks() {
     disableUsbRuntimesMock();
     disableRuntimeClientFactoryMock();
+
+    for (const host of Object.keys(this._clients[RUNTIMES.NETWORK])) {
+      this.removeNetworkRuntime(host);
+    }
   }
 
-  emitUpdate() {
+  createNetworkRuntime(host, runtimeInfo) {
+    const { addNetworkLocation } =
+      require("devtools/client/aboutdebugging-new/src/modules/network-locations");
+    addNetworkLocation(host);
+
+    // Add a valid client that can be returned for this particular runtime id.
+    const mockNetworkClient = createClientMock();
+    mockNetworkClient.getDeviceDescription = () => {
+      return {
+        name: runtimeInfo.name || "TestBrand",
+        channel: runtimeInfo.channel || "release",
+        version: runtimeInfo.version || "1.0",
+      };
+    };
+    this._clients[RUNTIMES.NETWORK][host] = mockNetworkClient;
+
+    return mockNetworkClient;
+  }
+
+  removeNetworkRuntime(host) {
+    const { removeNetworkLocation } =
+      require("devtools/client/aboutdebugging-new/src/modules/network-locations");
+    removeNetworkLocation(host);
+
+    delete this._clients[RUNTIMES.NETWORK][host];
+  }
+
+  emitUSBUpdate() {
     this._observerMock.emit("runtime-list-updated");
   }
 
   /**
    * Creates a USB runtime for which a client conenction can be established.
    * @param {String} id
    *        The id of the runtime.
    * @param {Object} optional object used to create the fake runtime & device
@@ -77,36 +117,36 @@ class UsbMocks {
    *        - shortName: {String} Short name for the device
    *        - name: {String} Application name, for instance "Firefox"
    *        - channel: {String} Release channel, for instance "release", "nightly"
    *        - version: {String} Version, for instance "63.0a"
    *        - socketPath: {String} (should only be used for connecting, so not here)
    * @return {Object} Returns the mock client created for this runtime so that methods
    * can be overridden on it.
    */
-  createRuntime(id, runtimeInfo = {}) {
+  createUSBRuntime(id, runtimeInfo = {}) {
     // Add a new runtime to the list of scanned runtimes.
-    this._runtimes.push({
+    this._usbRuntimes.push({
       id: id,
       _socketPath: runtimeInfo.socketPath || "test/path",
       deviceName: runtimeInfo.deviceName || "test device name",
       shortName: runtimeInfo.shortName || "testshort",
     });
 
     // Add a valid client that can be returned for this particular runtime id.
     const mockUsbClient = createClientMock();
     mockUsbClient.getDeviceDescription = () => {
       return {
         name: runtimeInfo.name || "TestBrand",
         channel: runtimeInfo.channel || "release",
         version: runtimeInfo.version || "1.0",
       };
     };
-    this._clients[id] = mockUsbClient;
+    this._clients[RUNTIMES.USB][id] = mockUsbClient;
 
     return mockUsbClient;
   }
 
-  removeRuntime(id) {
-    this._runtimes = this._runtimes.filter(runtime => runtime.id !== id);
-    delete this._clients[id];
+  removeUSBRuntime(id) {
+    this._usbRuntimes = this._usbRuntimes.filter(runtime => runtime.id !== id);
+    delete this._clients[RUNTIMES.USB][id];
   }
 }
--- a/devtools/client/aboutdebugging-new/test/browser/head.js
+++ b/devtools/client/aboutdebugging-new/test/browser/head.js
@@ -178,11 +178,11 @@ async function connectToRuntime(deviceNa
 }
 
 async function selectRuntime(deviceName, name, document) {
   const sidebarItem = findSidebarItemByText(deviceName, document);
   sidebarItem.querySelector(".js-sidebar-link").click();
 
   await waitUntil(() => {
     const runtimeInfo = document.querySelector(".js-runtime-info");
-    return runtimeInfo.textContent.includes(name);
+    return runtimeInfo && runtimeInfo.textContent.includes(name);
   });
 }
deleted file mode 100644
--- a/devtools/client/aboutdebugging/test/addons/unpacked/bootstrap.js
+++ /dev/null
@@ -1,22 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/* eslint-env browser */
-/* exported startup, shutdown, install, uninstall */
-
-"use strict";
-
-ChromeUtils.import("resource://gre/modules/Services.jsm");
-
-// This function is called from the webconsole test:
-// browser_addons_debug_bootstrapped.js
-function myBootstrapAddonFunction() { // eslint-disable-line no-unused-vars
-  Services.obs.notifyObservers(null, "addon-console-works");
-}
-
-function startup() {
-  Services.obs.notifyObservers(null, "test-devtools");
-}
-function shutdown() {}
-function install() {}
-function uninstall() {}
deleted file mode 100644
--- a/devtools/client/aboutdebugging/test/addons/unpacked/install.rdf
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0"?>
-<!--
-# 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/.
--->
-
-<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
-  <Description about="urn:mozilla:install-manifest"
-               em:id="test-devtools@mozilla.org"
-               em:name="test-devtools"
-               em:version="1.0"
-               em:type="2"
-               em:creator="Mozilla">
-
-    <em:bootstrap>true</em:bootstrap>
-    <em:targetApplication>
-      <Description>
-        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
-        <em:minVersion>44.0a1</em:minVersion>
-        <em:maxVersion>*</em:maxVersion>
-      </Description>
-    </em:targetApplication>
-  </Description>
-</RDF>
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/addons/unpacked/manifest.json
@@ -0,0 +1,10 @@
+{
+  "manifest_version": 2,
+  "name": "test-devtools",
+  "version": "1.0",
+  "applications": {
+    "gecko": {
+      "id": "test-devtools@mozilla.org"
+    }
+  }
+}
--- a/devtools/client/aboutdebugging/test/browser.ini
+++ b/devtools/client/aboutdebugging/test/browser.ini
@@ -1,15 +1,14 @@
 [DEFAULT]
 tags = devtools
 subsuite = devtools
 support-files =
   head.js
-  addons/unpacked/bootstrap.js
-  addons/unpacked/install.rdf
+  addons/unpacked/manifest.json
   addons/bad/manifest.json
   addons/bug1273184.xpi
   addons/test-devtools-webextension/*
   addons/test-devtools-webextension-nobg/*
   addons/test-devtools-webextension-noid/*
   addons/test-devtools-webextension-unknown-prop/*
   service-workers/delay-sw.html
   service-workers/delay-sw.js
@@ -17,40 +16,32 @@ support-files =
   service-workers/empty-sw.js
   service-workers/fetch-sw.html
   service-workers/fetch-sw.js
   service-workers/push-sw.html
   service-workers/push-sw.js
   !/devtools/client/shared/test/shared-head.js
   !/devtools/client/shared/test/telemetry-test-helpers.js
 
-[browser_addons_debug_bootstrapped.js]
-# To be removed in bug 1497264
-skip-if = true
 [browser_addons_debug_info.js]
 [browser_addons_debug_webextension.js]
 tags = webextensions
 [browser_addons_debug_webextension_inspector.js]
 tags = webextensions
 [browser_addons_debug_webextension_nobg.js]
 tags = webextensions
 [browser_addons_debug_webextension_popup.js]
 skip-if = (verify && debug) || (debug && os == "linux" && bits == 64) # verify: crashes on shutdown, timeouts linux debug Bug 1299001
 tags = webextensions
 [browser_addons_debugging_initial_state.js]
-# To be removed or updated in bug 1497264
-skip-if = true
 [browser_addons_install.js]
-# To be updated in bug 1497264 (was "verify && debug")
-skip-if = true
+skip-if = verify && debug
 [browser_addons_reload.js]
 [browser_addons_remove.js]
 [browser_addons_toggle_debug.js]
-# To be removed or updated in bug 1497264
-skip-if = true
 [browser_page_not_found.js]
 [browser_service_workers.js]
 [browser_service_workers_fetch_flag.js]
 skip-if = os == 'mac' # bug 1333759
 [browser_service_workers_multi_content_process.js]
 skip-if = !e10s # This test is only valid in e10s
 [browser_service_workers_not_compatible.js]
 [browser_service_workers_push.js]
deleted file mode 100644
--- a/devtools/client/aboutdebugging/test/browser_addons_debug_bootstrapped.js
+++ /dev/null
@@ -1,88 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-"use strict";
-
-// There are shutdown issues for which multiple rejections are left uncaught.
-// See bug 1018184 for resolving these issues.
-const { PromiseTestUtils } = scopedCuImport("resource://testing-common/PromiseTestUtils.jsm");
-PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
-
-// Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
-requestLongerTimeout(2);
-
-const ADDON_ID = "test-devtools@mozilla.org";
-const ADDON_NAME = "test-devtools";
-
-const { BrowserToolboxProcess } = ChromeUtils.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
-
-add_task(async function() {
-  await new Promise(resolve => {
-    const options = {"set": [
-      // Force enabling of addons debugging
-      ["devtools.chrome.enabled", true],
-      ["devtools.debugger.remote-enabled", true],
-      // Disable security prompt
-      ["devtools.debugger.prompt-connection", false],
-      // Enable Browser toolbox test script execution via env variable
-      ["devtools.browser-toolbox.allow-unsafe-script", true],
-    ]};
-    SpecialPowers.pushPrefEnv(options, resolve);
-  });
-
-  const { tab, document } = await openAboutDebugging("addons");
-  await waitForInitialAddonList(document);
-  await installAddon({
-    document,
-    path: "addons/unpacked/install.rdf",
-    name: ADDON_NAME,
-  });
-
-  // Retrieve the DEBUG button for the addon
-  const names = getInstalledAddonNames(document);
-  const name = names.filter(element => element.textContent === ADDON_NAME)[0];
-  ok(name, "Found the addon in the list");
-  const targetElement = name.parentNode.parentNode;
-  const debugBtn = targetElement.querySelector(".debug-button");
-  ok(debugBtn, "Found its debug button");
-
-  // Wait for a notification sent by a script evaluated the test addon via
-  // the web console.
-  const onCustomMessage = new Promise(done => {
-    Services.obs.addObserver(function listener() {
-      Services.obs.removeObserver(listener, "addon-console-works");
-      done();
-    }, "addon-console-works");
-  });
-
-  // Be careful, this JS function is going to be executed in the addon toolbox,
-  // which lives in another process. So do not try to use any scope variable!
-  const env = Cc["@mozilla.org/process/environment;1"]
-              .getService(Ci.nsIEnvironment);
-  const testScript = function() {
-    /* eslint-disable no-undef */
-    toolbox.selectTool("webconsole")
-      .then(console => {
-        const { jsterm } = console.hud;
-        return jsterm.execute("myBootstrapAddonFunction()");
-      })
-      .then(() => toolbox.destroy());
-    /* eslint-enable no-undef */
-  };
-  env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + testScript);
-  registerCleanupFunction(() => {
-    env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
-  });
-
-  const onToolboxClose = BrowserToolboxProcess.once("close");
-
-  debugBtn.click();
-
-  await onCustomMessage;
-  ok(true, "Received the notification message from the bootstrap.js function");
-
-  await onToolboxClose;
-  ok(true, "Addon toolbox closed");
-
-  await uninstallAddon({document, id: ADDON_ID, name: ADDON_NAME});
-  await closeAboutDebugging(tab);
-});
--- a/devtools/client/aboutdebugging/test/browser_addons_debug_info.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_info.js
@@ -8,39 +8,16 @@ const SHOW_SYSTEM_ADDONS_PREF = "devtool
 function testFilePath(container, expectedFilePath) {
   // Verify that the path to the install location is shown next to its label.
   const filePath = container.querySelector(".file-path");
   ok(filePath, "file path is in DOM");
   ok(filePath.textContent.endsWith(expectedFilePath), "file path is set correctly");
   is(filePath.previousElementSibling.textContent, "Location", "file path has label");
 }
 
-// Remove in Bug 1497264
-/*
-add_task(async function testLegacyAddon() {
-  const addonId = "test-devtools@mozilla.org";
-  const addonName = "test-devtools";
-  const { tab, document } = await openAboutDebugging("addons");
-  await waitForInitialAddonList(document);
-
-  await installAddon({
-    document,
-    path: "addons/unpacked/install.rdf",
-    name: addonName,
-  });
-
-  const container = document.querySelector(`[data-addon-id="${addonId}"]`);
-  testFilePath(container, "browser/devtools/client/aboutdebugging/test/addons/unpacked/");
-
-  await uninstallAddon({document, id: addonId, name: addonName});
-
-  await closeAboutDebugging(tab);
-});
-*/
-
 add_task(async function testWebExtension() {
   const addonId = "test-devtools-webextension-nobg@mozilla.org";
   const addonName = "test-devtools-webextension-nobg";
   const { tab, document } = await openAboutDebugging("addons");
 
   await waitForInitialAddonList(document);
 
   const addonFile = ExtensionTestCommon.generateXPI({
@@ -52,17 +29,16 @@ add_task(async function testWebExtension
     },
   });
   registerCleanupFunction(() => addonFile.remove(false));
 
   await installAddon({
     document,
     file: addonFile,
     name: addonName,
-    isWebExtension: true,
   });
 
   const container = document.querySelector(`[data-addon-id="${addonId}"]`);
 
   testFilePath(container, addonFile.leafName);
 
   const extensionID = container.querySelector(".extension-id span");
   ok(extensionID.textContent === "test-devtools-webextension-nobg@mozilla.org");
@@ -90,17 +66,16 @@ add_task(async function testTemporaryWeb
     },
   });
   registerCleanupFunction(() => addonFile.remove(false));
 
   await installAddon({
     document,
     file: addonFile,
     name: addonName,
-    isWebExtension: true,
   });
 
   const addons =
     document.querySelectorAll("#temporary-extensions .addon-target-container");
   // Assuming that our temporary add-on is now at the top.
   const container = addons[addons.length - 1];
   const addonId = container.dataset.addonId;
 
@@ -133,17 +108,16 @@ add_task(async function testUnknownManif
     },
   });
   registerCleanupFunction(() => addonFile.remove(false));
 
   await installAddon({
     document,
     file: addonFile,
     name: addonName,
-    isWebExtension: true,
   });
 
   info("Wait until the addon appears in about:debugging");
   const container = await waitUntilAddonContainer(addonName, document);
 
   info("Wait until the installation message appears for the new addon");
   await waitUntilElement(".addon-target-messages", container);
 
--- a/devtools/client/aboutdebugging/test/browser_addons_debugging_initial_state.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_debugging_initial_state.js
@@ -47,17 +47,17 @@ async function testCheckboxState(testDat
   });
 
   const { tab, document } = await openAboutDebugging("addons");
   await waitForInitialAddonList(document);
 
   info("Install a test addon.");
   await installAddon({
     document,
-    path: "addons/unpacked/install.rdf",
+    path: "addons/unpacked/manifest.json",
     name: ADDON_NAME,
   });
 
   info("Test checkbox checked state.");
   const addonDebugCheckbox = document.querySelector("#enable-addon-debugging");
   is(addonDebugCheckbox.checked, testData.expected,
     "Addons debugging checkbox should be in expected state.");
 
--- a/devtools/client/aboutdebugging/test/browser_addons_install.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_install.js
@@ -26,32 +26,33 @@ function mockFilePicker(window, file) {
 function promiseWriteWebManifestForExtension(manifest, dir) {
   const files = {
     "manifest.json": JSON.stringify(manifest),
   };
   return AddonTestUtils.promiseWriteFilesToExtension(
     dir.path, manifest.applications.gecko.id, files, true);
 }
 
-add_task(async function testLegacyInstallSuccess() {
-  const ADDON_ID = "test-devtools@mozilla.org";
-  const ADDON_NAME = "test-devtools";
-
+add_task(async function testWebextensionInstallSuccess() {
   const { tab, document } = await openAboutDebugging("addons");
   await waitForInitialAddonList(document);
 
   // Install this add-on, and verify that it appears in the about:debugging UI
   await installAddon({
     document,
-    path: "addons/unpacked/install.rdf",
-    name: ADDON_NAME,
+    path: "addons/unpacked/manifest.json",
+    name: "test-devtools",
   });
 
   // Install the add-on, and verify that it disappears in the about:debugging UI
-  await uninstallAddon({document, id: ADDON_ID, name: ADDON_NAME});
+  await uninstallAddon({
+    document,
+    id: "test-devtools@mozilla.org",
+    name: "test-devtools",
+  });
 
   await closeAboutDebugging(tab);
 });
 
 add_task(async function testWebextensionInstallError() {
   const { tab, document, window } = await openAboutDebugging("addons");
   await waitForInitialAddonList(document);
 
--- a/devtools/client/aboutdebugging/test/browser_addons_reload.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_reload.js
@@ -63,43 +63,39 @@ class TempWebExt {
 /*
 add_task(async function reloadButtonReloadsAddon() {
   const ADDON_NAME = "test-devtools";
   const { tab, document, window } = await openAboutDebugging("addons");
   const { AboutDebugging } = window;
   await waitForInitialAddonList(document);
   await installAddon({
     document,
-    path: "addons/unpacked/install.rdf",
+    path: "addons/unpacked/manifest.json",
     name: ADDON_NAME,
   });
 
   const reloadButton = getReloadButton(document, ADDON_NAME);
   is(reloadButton.title, "", "Reload button should not have a tooltip");
   const onInstalled = promiseAddonEvent("onInstalled");
 
-  const onBootstrapInstallCalled = new Promise(done => {
-    Services.obs.addObserver(function listener() {
-      Services.obs.removeObserver(listener, ADDON_NAME);
-      info("Add-on was re-installed: " + ADDON_NAME);
-      done();
-    }, ADDON_NAME);
-  });
+  const reloaded = once(AboutDebugging, "addon-reload");
 
-  const reloaded = once(AboutDebugging, "addon-reload");
-  const onListUpdated = once(AboutDebugging, "addons-updated");
+  // The list is updated twice:
+  // - AddonManager's onInstalled event
+  // - WebExtension's Management's startup event
+  const onListUpdated = waitForNEvents(AboutDebugging, "addons-updated", 2);
+
   reloadButton.click();
   await reloaded;
   await onListUpdated;
 
   const [reloadedAddon] = await onInstalled;
   is(reloadedAddon.name, ADDON_NAME,
      "Add-on was reloaded: " + reloadedAddon.name);
 
-  await onBootstrapInstallCalled;
   await tearDownAddon(AboutDebugging, reloadedAddon);
   await closeAboutDebugging(tab);
 });
 */
 
 add_task(async function reloadButtonRefreshesMetadata() {
   const { tab, document, window } = await openAboutDebugging("addons");
   const { AboutDebugging } = window;
@@ -114,34 +110,36 @@ add_task(async function reloadButtonRefr
         "id": ADDON_ID,
       },
     },
   };
 
   const tempExt = new TempWebExt(ADDON_ID);
   tempExt.writeManifest(manifestBase);
 
-  // The list is updated twice. On AddonManager's onInstalled event as well
-  // as WebExtension's Management's startup event.
+  // List updated twice:
+  // - AddonManager's onInstalled event
+  // - WebExtension's Management's startup event.
   let onListUpdated = waitForNEvents(AboutDebugging, "addons-updated", 2);
   const onInstalled = promiseAddonEvent("onInstalled");
   await AddonManager.installTemporaryAddon(tempExt.sourceDir);
   await onListUpdated;
 
   info("Wait until addon onInstalled event is received");
   await onInstalled;
 
   info("Wait until addon appears in about:debugging#addons");
   await waitUntilAddonContainer("Temporary web extension", document);
 
   const newName = "Temporary web extension (updated)";
   tempExt.writeManifest(Object.assign({}, manifestBase, {name: newName}));
 
-  // The list is updated twice, once for uninstall of the old
-  // and another one for install of the new
+  // List updated twice:
+  // - AddonManager's onInstalled event
+  // - WebExtension's Management's startup event.
   onListUpdated = waitForNEvents(AboutDebugging, "addons-updated", 2);
   // Wait for the add-on list to be updated with the reloaded name.
   const onReInstall = promiseAddonEvent("onInstalled");
   const reloadButton = getReloadButton(document, manifestBase.name);
   const reloaded = once(AboutDebugging, "addon-reload");
   reloadButton.click();
   await reloaded;
   await onListUpdated;
@@ -157,18 +155,19 @@ add_task(async function reloadButtonRefr
   await closeAboutDebugging(tab);
 });
 
 add_task(async function onlyTempInstalledAddonsCanBeReloaded() {
   const { tab, document, window } = await openAboutDebugging("addons");
   const { AboutDebugging } = window;
   await waitForInitialAddonList(document);
 
-  // The list is updated twice. On AddonManager's onInstalled event as well
-  // as WebExtension's Management's startup event.
+  // List updated twice:
+  // - AddonManager's onInstalled event
+  // - WebExtension's Management's startup event.
   const onListUpdated = waitForNEvents(AboutDebugging, "addons-updated", 2);
   await installAddonWithManager(getSupportsFile("addons/bug1273184.xpi").file);
   await onListUpdated;
 
   info("Wait until addon appears in about:debugging#addons");
   await waitUntilAddonContainer(PACKAGED_ADDON_NAME, document);
 
   info("Retrieved the installed addon from the addon manager");
--- a/devtools/client/aboutdebugging/test/browser_addons_remove.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_remove.js
@@ -7,44 +7,16 @@ const PACKAGED_ADDON_NAME = "bug 1273184
 function getTargetEl(document, id) {
   return document.querySelector(`[data-addon-id="${id}"]`);
 }
 
 function getRemoveButton(document, id) {
   return document.querySelector(`[data-addon-id="${id}"] .uninstall-button`);
 }
 
-// Remove in Bug 1497264
-/*
-add_task(async function removeLegacyExtension() {
-  const addonID = "test-devtools@mozilla.org";
-  const addonName = "test-devtools";
-
-  const { tab, document } = await openAboutDebugging("addons");
-  await waitForInitialAddonList(document);
-
-  // Install this add-on, and verify that it appears in the about:debugging UI
-  await installAddon({
-    document,
-    path: "addons/unpacked/install.rdf",
-    name: addonName,
-  });
-
-  ok(getTargetEl(document, addonID), "add-on is shown");
-
-  info("Click on the remove button and wait until the addon container is removed");
-  getRemoveButton(document, addonID).click();
-  await waitUntil(() => !getTargetEl(document, addonID), 100);
-
-  info("add-on is not shown");
-
-  await closeAboutDebugging(tab);
-});
-*/
-
 add_task(async function removeWebextension() {
   const addonID = "test-devtools-webextension@mozilla.org";
   const addonName = "test-devtools-webextension";
 
   const { tab, document } = await openAboutDebugging("addons");
   await waitForInitialAddonList(document);
 
   const addonFile = ExtensionTestCommon.generateXPI({
@@ -57,17 +29,16 @@ add_task(async function removeWebextensi
   });
   registerCleanupFunction(() => addonFile.remove(false));
 
   // Install this add-on, and verify that it appears in the about:debugging UI
   await installAddon({
     document,
     file: addonFile,
     name: addonName,
-    isWebExtension: true,
   });
 
   ok(getTargetEl(document, addonID), "add-on is shown");
 
   info("Click on the remove button and wait until the addon container is removed");
   getRemoveButton(document, addonID).click();
   await waitUntil(() => !getTargetEl(document, addonID), 100);
 
@@ -76,18 +47,19 @@ add_task(async function removeWebextensi
   await closeAboutDebugging(tab);
 });
 
 add_task(async function onlyTempInstalledAddonsCanBeRemoved() {
   const { tab, document, window } = await openAboutDebugging("addons");
   const { AboutDebugging } = window;
   await waitForInitialAddonList(document);
 
-  // The list is updated twice. On AddonManager's onInstalled event as well
-  // as WebExtension's Management's startup event.
+  // List updated twice:
+  // - AddonManager's onInstalled event
+  // - WebExtension's Management's startup event.
   const onListUpdated = waitForNEvents(AboutDebugging, "addons-updated", 2);
   await installAddonWithManager(getSupportsFile("addons/bug1273184.xpi").file);
   await onListUpdated;
   const addon = await getAddonByID("bug1273184@tests");
 
   info("Wait until addon appears in about:debugging#addons");
   await waitUntilAddonContainer(PACKAGED_ADDON_NAME, document);
 
--- a/devtools/client/aboutdebugging/test/browser_addons_toggle_debug.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_toggle_debug.js
@@ -20,17 +20,17 @@ add_task(async function() {
   });
 
   const { tab, document } = await openAboutDebugging("addons");
   await waitForInitialAddonList(document);
 
   info("Install a test addon.");
   await installAddon({
     document,
-    path: "addons/unpacked/install.rdf",
+    path: "addons/unpacked/manifest.json",
     name: ADDON_NAME,
   });
 
   const addonDebugCheckbox = document.querySelector("#enable-addon-debugging");
   ok(!addonDebugCheckbox.checked, "Addons debugging should be disabled.");
 
   info("Check all debug buttons are disabled.");
   const debugButtons = [...document.querySelectorAll("#addons .debug-button")];
--- a/devtools/client/aboutdebugging/test/head.js
+++ b/devtools/client/aboutdebugging/test/head.js
@@ -161,53 +161,48 @@ async function waitUntilElement(selector
  * @param  {DOMDocument}  document   #tabs section container document
  * @return {DOMNode}                 target list or container element
  */
 function getTabList(document) {
   return document.querySelector("#tabs .target-list") ||
     document.querySelector("#tabs.targets");
 }
 
-async function installAddon({document, path, file, name, isWebExtension}) {
+async function installAddon({document, path, file, name}) {
   // Mock the file picker to select a test addon
   const MockFilePicker = SpecialPowers.MockFilePicker;
   MockFilePicker.init(window);
   if (path) {
     file = getSupportsFile(path);
     MockFilePicker.setFiles([file.file]);
   } else {
     MockFilePicker.setFiles([file]);
   }
 
-  let onAddonInstalled;
-
-  if (isWebExtension) {
-    onAddonInstalled = new Promise(done => {
-      Management.on("startup", function listener(event, extension) {
-        if (extension.name != name) {
-          return;
-        }
+  const onAddonInstalled = new Promise(done => {
+    Management.on("startup", function listener(event, extension) {
+      if (extension.name != name) {
+        return;
+      }
 
-        Management.off("startup", listener);
-        done();
-      });
+      Management.off("startup", listener);
+      done();
     });
-  } else {
-    // Wait for a "test-devtools" message sent by the addon's bootstrap.js file
-    onAddonInstalled = new Promise(done => {
-      Services.obs.addObserver(function listener() {
-        Services.obs.removeObserver(listener, "test-devtools");
+  });
+  const AboutDebugging = document.defaultView.AboutDebugging;
 
-        done();
-      }, "test-devtools");
-    });
-  }
+  // List updated twice:
+  // - AddonManager's onInstalled event
+  // - WebExtension's Management's startup event.
+  const onListUpdated = waitForNEvents(AboutDebugging, "addons-updated", 2);
+
   // Trigger the file picker by clicking on the button
   document.getElementById("load-addon-from-file").click();
 
+  await onListUpdated;
   await onAddonInstalled;
   ok(true, "Addon installed and running its bootstrap.js file");
 
   info("Wait for the addon to appear in the UI");
   await waitUntilAddonContainer(name, document);
 }
 
 async function uninstallAddon({document, id, name}) {
@@ -362,17 +357,16 @@ async function setupTestAboutDebuggingWe
 
   const { tab, document } = await openAboutDebugging("addons");
   await waitForInitialAddonList(document);
 
   await installAddon({
     document,
     file,
     name,
-    isWebExtension: true,
   });
 
   // Retrieve the DEBUG button for the addon
   const names = getInstalledAddonNames(document);
   const nameEl = names.filter(element => element.textContent === name)[0];
   ok(name, "Found the addon in the list");
   const targetElement = nameEl.parentNode.parentNode;
   const debugBtn = targetElement.querySelector(".debug-button");
--- a/devtools/client/accessibility/accessibility-startup.js
+++ b/devtools/client/accessibility/accessibility-startup.js
@@ -47,18 +47,21 @@ class AccessibilityStartup {
     this._walker = await this._accessibility.getWalker();
 
     this._supports = {};
     // Only works with FF61+ targets
     this._supports.enableDisable =
       await this.target.actorHasMethod("accessibility", "enable");
 
     if (this._supports.enableDisable) {
-      this._supports.relations =
-        await this.target.actorHasMethod("accessible", "getRelations");
+      ([ this._supports.relations, this._supports.snapshot ] = await Promise.all([
+        this.target.actorHasMethod("accessible", "getRelations"),
+        this.target.actorHasMethod("accessible", "snapshot"),
+      ]));
+
       await this._accessibility.bootstrap();
     }
 
     return true;
   }
 
   /**
    * Fully initialize accessibility front. Also add listeners for accessibility
--- a/devtools/client/accessibility/components/AccessibilityRow.js
+++ b/devtools/client/accessibility/components/AccessibilityRow.js
@@ -1,32 +1,45 @@
 /* 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";
 
-/* global gToolbox, EVENTS */
+/* global gTelemetry, gToolbox, EVENTS */
 
 // React & Redux
 const { Component, createFactory } = require("devtools/client/shared/vendor/react");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const { findDOMNode } = require("devtools/client/shared/vendor/react-dom");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 
 const TreeRow = require("devtools/client/shared/components/tree/TreeRow");
 
 // Utils
 const {flashElementOn, flashElementOff} =
   require("devtools/client/inspector/markup/utils");
+const { openDocLink } = require("devtools/client/shared/link");
 const { VALUE_FLASHING_DURATION, VALUE_HIGHLIGHT_DURATION } = require("../constants");
 
 // Actions
 const { updateDetails } = require("../actions/details");
 const { unhighlight } = require("../actions/accessibles");
 
+const { L10N } = require("../utils/l10n");
+
+loader.lazyRequireGetter(this, "Menu", "devtools/client/framework/menu");
+loader.lazyRequireGetter(this, "MenuItem", "devtools/client/framework/menu-item");
+
+const JSON_URL_PREFIX = "data:application/json;charset=UTF-8,";
+
+const TELEMETRY_ACCESSIBLE_CONTEXT_MENU_OPENED =
+  "devtools.accessibility.accessible_context_menu_opened";
+const TELEMETRY_ACCESSIBLE_CONTEXT_MENU_ITEM_ACTIVATED =
+  "devtools.accessibility.accessible_context_menu_item_activated";
+
 class HighlightableTreeRowClass extends TreeRow {
   shouldComponentUpdate(nextProps) {
     const props = ["name", "open", "value", "loading", "selected", "hasChildren"];
 
     for (const p of props) {
       if (nextProps.member[p] !== this.props.member[p]) {
         return true;
       }
@@ -133,23 +146,71 @@ class AccessibilityRow extends Component
 
     if (!walker) {
       return;
     }
 
     walker.unhighlight().catch(error => console.warn(error));
   }
 
+  async printToJSON() {
+    const { member, supports } = this.props;
+    if (!supports.snapshot) {
+      // Debugger server does not support Accessible actor snapshots.
+      return;
+    }
+
+    if (gTelemetry) {
+      gTelemetry.keyedScalarAdd(TELEMETRY_ACCESSIBLE_CONTEXT_MENU_ITEM_ACTIVATED,
+                                "print-to-json", 1);
+    }
+
+    const snapshot = await member.object.snapshot();
+    openDocLink(`${JSON_URL_PREFIX}${encodeURIComponent(JSON.stringify(snapshot))}`);
+  }
+
+  onContextMenu(e) {
+    e.stopPropagation();
+    e.preventDefault();
+
+    if (!gToolbox) {
+      return;
+    }
+
+    const menu = new Menu({ id: "accessibility-row-contextmenu" });
+    const { supports } = this.props;
+
+    if (supports.snapshot) {
+      menu.append(new MenuItem({
+        id: "menu-printtojson",
+        label: L10N.getStr("accessibility.tree.menu.printToJSON"),
+        click: () => this.printToJSON(),
+      }));
+    }
+
+    menu.popup(e.screenX, e.screenY, gToolbox);
+
+    if (gTelemetry) {
+      gTelemetry.scalarAdd(TELEMETRY_ACCESSIBLE_CONTEXT_MENU_OPENED, 1);
+    }
+  }
+
+  get hasContextMenu() {
+    const { supports } = this.props;
+    return supports.snapshot;
+  }
+
   /**
    * Render accessible row component.
    * @returns acecssible-row React component.
    */
   render() {
     const { object } = this.props.member;
     const props = Object.assign({}, this.props, {
+      onContextMenu: this.hasContextMenu && (e => this.onContextMenu(e)),
       onMouseOver: () => this.highlight(object),
       onMouseOut: () => this.unhighlight(),
     });
 
     return (HighlightableTreeRow(props));
   }
 }
 
--- a/devtools/client/accessibility/panel.js
+++ b/devtools/client/accessibility/panel.js
@@ -235,18 +235,21 @@ AccessibilityPanel.prototype = {
     this.target.off("navigate", this.onTabNavigated);
     this._toolbox.off("select", this.onPanelVisibilityChange);
 
     this.panelWin.off(EVENTS.NEW_ACCESSIBLE_FRONT_SELECTED,
       this.onNewAccessibleFrontSelected);
     this.panelWin.off(EVENTS.ACCESSIBILITY_INSPECTOR_UPDATED,
       this.onAccessibilityInspectorUpdated);
 
-    this.picker.release();
-    this.picker = null;
+    // Older versions of debugger server do not support picker functionality.
+    if (this.picker) {
+      this.picker.release();
+      this.picker = null;
+    }
 
     if (this.front) {
       this.front.off("init", this.updateA11YServiceDurationTimer);
       this.front.off("shutdown", this.updateA11YServiceDurationTimer);
 
       this.front.off("init", this.forceUpdatePickerButton);
       this.front.off("shutdown", this.forceUpdatePickerButton);
     }
--- a/devtools/client/accessibility/test/mochitest/chrome.ini
+++ b/devtools/client/accessibility/test/mochitest/chrome.ini
@@ -1,7 +1,8 @@
 [DEFAULT]
 support-files =
   head.js
 
 [test_accessible_learnMoreLink.html]
 [test_accessible_openLink.html]
 [test_accessible_relations.html]
+[test_accessible_row_context_menu.html]
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/test/mochitest/test_accessible_row_context_menu.html
@@ -0,0 +1,164 @@
+<!-- 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/. -->
+<!DOCTYPE HTML>
+<html>
+<!--
+Test that openLink function is called if accessible object property is rendered as a link.
+-->
+<head>
+  <meta charset="utf-8">
+  <title>AccessibilityRow context menu test</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+  <link rel="stylesheet" href="chrome://devtools/skin/light-theme.css" type="text/css">
+</head>
+<body>
+<pre id="test">
+<script src="head.js" type="application/javascript"></script>
+<script type="application/javascript">
+
+"use strict";
+
+window.onload = async function() {
+  try {
+    const { gDevTools } = require("devtools/client/framework/devtools");
+    const Services = browserRequire("Services");
+    const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
+    const { createFactory, createElement } =
+      browserRequire("devtools/client/shared/vendor/react");
+    const { Provider } = require("devtools/client/shared/vendor/react-redux");
+    const createStore = require("devtools/client/shared/redux/create-store")();
+    const { Simulate } =
+      browserRequire("devtools/client/shared/vendor/react-dom-test-utils");
+    const AccessibilityRow = createFactory(
+      browserRequire("devtools/client/accessibility/components/AccessibilityRow"));
+
+    async function withMockEnv(func) {
+      const { gToolbox: originalToolbox, gTelemetry: originalTelemetry } = window;
+      window.gToolbox = { doc: document };
+      window.gTelemetry = null;
+
+      await func();
+
+      window.gToolbox = originalToolbox;
+      window.gTelemetry = originalTelemetry;
+    }
+
+    function renderAccessibilityRow(newProps, newState) {
+      let container = document.getElementById("container");
+      if (container) {
+        container.remove();
+      }
+
+      const accRow = AccessibilityRow(newProps);
+      const mockStore = createStore((state, action) =>
+        action ? { ...state, ...action } : state, newState);
+      const provider = createElement(Provider, { store: mockStore }, accRow);
+
+      container = document.createElement("div");
+      container.id = "container";
+      document.body.appendChild(container);
+      return ReactDOM.render(provider, container);
+    }
+
+    const checker = Symbol();
+    let isCalled;
+
+    const ROW_ID = "test-row";
+    const JSON_URL_PREFIX = "data:application/json;charset=UTF-8,";
+    const SNAPSHOT = { "snapshot": true };
+    const defaultProps = {
+      id: ROW_ID,
+      member: {
+        object: {
+          name: "test",
+          value: "test",
+          loading: false,
+          selected: false,
+          hasChildren: false,
+          snapshot: async () => SNAPSHOT,
+        },
+      },
+      columns: [
+        { "id": "default", "title": "role" },
+        { "id": "value", "title": "name" },
+      ],
+      provider: {
+        getValue: (object, id) => object[id],
+      },
+    };
+
+    const mockProps = {
+      ...defaultProps,
+      onContextMenu: () => {
+        isCalled = true;
+      },
+    };
+
+    const defaultState = { ui: { supports: { snapshot: true }}};
+    const mockState = { ui: { supports: {}}};
+
+    info("Check contextmenu default behaviour.");
+    renderAccessibilityRow(defaultProps, defaultState);
+    let row = document.getElementById(ROW_ID);
+
+    await withMockEnv(async function() {
+      Simulate.contextMenu(row);
+
+      const menu = document.getElementById("accessibility-row-contextmenu");
+      const printtojsonMenuItem = document.getElementById("menu-printtojson");
+
+      ok(menu, "Accessibility row context menu is open");
+      ok(printtojsonMenuItem, "Print to JSON menu item is visible");
+
+      const browserWindow = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
+      const defaultOpenWebLinkIn = browserWindow.openWebLinkIn;
+
+      let openWebLinkInCalled;
+      const openWebLinkInPromise = new Promise(resolve => {
+        openWebLinkInCalled = resolve;
+      });
+
+      // Mock top chrome window's @openWebLinkIn method.
+      browserWindow.openWebLinkIn = (...args) => {
+        openWebLinkInCalled(args);
+      };
+      printtojsonMenuItem.click();
+
+      const [ url, where ] = await openWebLinkInPromise;
+      is(url, `${JSON_URL_PREFIX}${encodeURIComponent(JSON.stringify(SNAPSHOT))}`,
+         "Correct URL is opened");
+      is(where, "tab", "URL was opened correctly");
+
+      // Reset @openWebLinkIn to default.
+      browserWindow.openWebLinkIn = defaultOpenWebLinkIn;
+    });
+
+    info("Check accessibility row when context menu is not supported.");
+    renderAccessibilityRow(mockProps, mockState);
+    row = document.getElementById(ROW_ID);
+
+    info("Check contextmenu listener is not called when context menu is not supported.");
+    isCalled = checker;
+    Simulate.contextMenu(row);
+    ok(isCalled === checker, "contextmenu event handler was never called.");
+
+    info("Check accessibility row when context menu is supported.");
+    renderAccessibilityRow(mockProps, defaultState);
+    row = document.getElementById(ROW_ID);
+
+    info("Check contextmenu listener is called when context menu is supported.");
+    isCalled = checker;
+    Simulate.contextMenu(row);
+    ok(isCalled, "contextmenu event handler was called correctly.");
+  } catch (e) {
+    ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
+  } finally {
+    SimpleTest.finish();
+  }
+};
+</script>
+</pre>
+</body>
+</html>
--- a/devtools/client/debugger/new/dist/debugger.css
+++ b/devtools/client/debugger/new/dist/debugger.css
@@ -278,18 +278,18 @@ button.jump-definition {
 
 /******************************************************************************/
 /* Invoke getter button */
 
 button.invoke-getter {
   mask: url("resource://devtools/client/shared/components/reps/images/input.svg") no-repeat;
   display: inline-block;
   background-color: var(--comment-node-color);
-  height: 12px;
-  vertical-align:bottom;
+  height: 10px;
+  vertical-align: middle;
   border:none;
 }
 
 .invoke-getter:hover {
   background-color: var(--theme-highlight-blue);
 }
 
 /******************************************************************************/
--- a/devtools/client/locales/en-US/accessibility.properties
+++ b/devtools/client/locales/en-US/accessibility.properties
@@ -96,8 +96,13 @@ accessibility.description.general.p1=Accessibility Inspector lets you examine the current page’s accessibility tree, which is used by screen readers and other assistive technologies. %S
 # paragraph, used when accessibility service description is provided before accessibility
 # inspector is enabled.
 accessibility.description.general.p2=Accessibility features may affect the performance of other developer tools panels and should be turned off when not in use.
 
 # LOCALIZATION NOTE (accessibility.description.oldVersion): A title text used
 # when accessibility service description is provided when a client is connected
 # to an older version of accessibility actor.
 accessibility.description.oldVersion=You are connected to a debugger server that is too old. To use Accessibility panel, please connect to the latest debugger server version.
+
+# LOCALIZATION NOTE (accessibility.tree.menu.printToJSON): A title text used when a
+# context menu item for printing an accessible tree to JSON is rendered after triggering a
+# context menu for an accessible tree row.
+accessibility.tree.menu.printToJSON=Print to JSON
--- a/devtools/client/shared/components/reps/reps.css
+++ b/devtools/client/shared/components/reps/reps.css
@@ -278,18 +278,18 @@ button.jump-definition {
 
 /******************************************************************************/
 /* Invoke getter button */
 
 button.invoke-getter {
   mask: url("resource://devtools/client/shared/components/reps/images/input.svg") no-repeat;
   display: inline-block;
   background-color: var(--comment-node-color);
-  height: 12px;
-  vertical-align:bottom;
+  height: 10px;
+  vertical-align: middle;
   border:none;
 }
 
 .invoke-getter:hover {
   background-color: var(--theme-highlight-blue);
 }
 
 /******************************************************************************/
@@ -431,11 +431,12 @@ html[dir="rtl"] .tree-node button.arrow 
 }
 
 /* Focused styles */
 .tree.object-inspector .tree-node.focused * {
   color: inherit;
 }
 
 .tree-node.focused button.jump-definition,
-.tree-node.focused button.open-inspector {
+.tree-node.focused button.open-inspector,
+.tree-node.focused button.invoke-getter {
   background-color: currentColor;
 }
--- a/devtools/client/shared/components/reps/reps.js
+++ b/devtools/client/shared/components/reps/reps.js
@@ -1,18 +1,18 @@
 (function webpackUniversalModuleDefinition(root, factory) {
 	if(typeof exports === 'object' && typeof module === 'object')
-		module.exports = factory(require("devtools/client/shared/vendor/react"), require("Services"), require("devtools/client/shared/vendor/react-redux"), require("devtools/client/shared/vendor/react-prop-types"), require("devtools/client/shared/vendor/react-dom-factories"));
+		module.exports = factory(require("devtools/client/shared/vendor/react"), require("Services"), require("devtools/client/shared/vendor/react-prop-types"), require("devtools/client/shared/vendor/react-dom-factories"), require("devtools/client/shared/vendor/react-redux"));
 	else if(typeof define === 'function' && define.amd)
-		define(["devtools/client/shared/vendor/react", "Services", "devtools/client/shared/vendor/react-redux", "devtools/client/shared/vendor/react-prop-types", "devtools/client/shared/vendor/react-dom-factories"], factory);
+		define(["devtools/client/shared/vendor/react", "Services", "devtools/client/shared/vendor/react-prop-types", "devtools/client/shared/vendor/react-dom-factories", "devtools/client/shared/vendor/react-redux"], factory);
 	else {
-		var a = typeof exports === 'object' ? factory(require("devtools/client/shared/vendor/react"), require("Services"), require("devtools/client/shared/vendor/react-redux"), require("devtools/client/shared/vendor/react-prop-types"), require("devtools/client/shared/vendor/react-dom-factories")) : factory(root["devtools/client/shared/vendor/react"], root["Services"], root["devtools/client/shared/vendor/react-redux"], root["devtools/client/shared/vendor/react-prop-types"], root["devtools/client/shared/vendor/react-dom-factories"]);
+		var a = typeof exports === 'object' ? factory(require("devtools/client/shared/vendor/react"), require("Services"), require("devtools/client/shared/vendor/react-prop-types"), require("devtools/client/shared/vendor/react-dom-factories"), require("devtools/client/shared/vendor/react-redux")) : factory(root["devtools/client/shared/vendor/react"], root["Services"], root["devtools/client/shared/vendor/react-prop-types"], root["devtools/client/shared/vendor/react-dom-factories"], root["devtools/client/shared/vendor/react-redux"]);
 		for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i];
 	}
-})(typeof self !== 'undefined' ? self : this, function(__WEBPACK_EXTERNAL_MODULE_0__, __WEBPACK_EXTERNAL_MODULE_22__, __WEBPACK_EXTERNAL_MODULE_3592__, __WEBPACK_EXTERNAL_MODULE_3642__, __WEBPACK_EXTERNAL_MODULE_3643__) {
+})(typeof self !== 'undefined' ? self : this, function(__WEBPACK_EXTERNAL_MODULE_0__, __WEBPACK_EXTERNAL_MODULE_22__, __WEBPACK_EXTERNAL_MODULE_1758__, __WEBPACK_EXTERNAL_MODULE_1759__, __WEBPACK_EXTERNAL_MODULE_1763__) {
 return /******/ (function(modules) { // webpackBootstrap
 /******/ 	// The module cache
 /******/ 	var installedModules = {};
 /******/
 /******/ 	// The require function
 /******/ 	function __webpack_require__(moduleId) {
 /******/
 /******/ 		// Check if module is in cache
@@ -65,17 +65,17 @@ return /******/ (function(modules) { // 
 /******/
 /******/ 	// Object.prototype.hasOwnProperty.call
 /******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
 /******/
 /******/ 	// __webpack_public_path__
 /******/ 	__webpack_require__.p = "/assets/build";
 /******/
 /******/ 	// Load entry module and return exports
-/******/ 	return __webpack_require__(__webpack_require__.s = 3730);
+/******/ 	return __webpack_require__(__webpack_require__.s = 2105);
 /******/ })
 /************************************************************************/
 /******/ ({
 
 /***/ 0:
 /***/ (function(module, exports) {
 
 module.exports = __WEBPACK_EXTERNAL_MODULE_0__;
@@ -137,59 +137,45 @@ var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBP
 	} else {
 		window.classNames = classNames;
 	}
 }());
 
 
 /***/ }),
 
-/***/ 22:
+/***/ 1758:
 /***/ (function(module, exports) {
 
-module.exports = __WEBPACK_EXTERNAL_MODULE_22__;
-
-/***/ }),
-
-/***/ 3592:
-/***/ (function(module, exports) {
-
-module.exports = __WEBPACK_EXTERNAL_MODULE_3592__;
+module.exports = __WEBPACK_EXTERNAL_MODULE_1758__;
 
 /***/ }),
 
-/***/ 3642:
+/***/ 1759:
 /***/ (function(module, exports) {
 
-module.exports = __WEBPACK_EXTERNAL_MODULE_3642__;
+module.exports = __WEBPACK_EXTERNAL_MODULE_1759__;
 
 /***/ }),
 
-/***/ 3643:
-/***/ (function(module, exports) {
-
-module.exports = __WEBPACK_EXTERNAL_MODULE_3643__;
-
-/***/ }),
-
-/***/ 3644:
+/***/ 1760:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/>. */
 
 // Dependencies
 const validProtocols = /(http|https|ftp|data|resource|chrome):/i;
 const tokenSplitRegex = /(\s|\'|\"|\\)+/;
 const ELLIPSIS = "\u2026";
-const dom = __webpack_require__(3643);
+const dom = __webpack_require__(1759);
 const { span } = dom;
 
 /**
  * Returns true if the given object is a grip (see RDP protocol)
  */
 function isGrip(object) {
   return object && object.actor;
 }
@@ -598,17 +584,17 @@ module.exports = {
   getGripType,
   tokenSplitRegex,
   ellipsisElement,
   ELLIPSIS
 };
 
 /***/ }),
 
-/***/ 3645:
+/***/ 1762:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/>. */
@@ -618,62 +604,69 @@ module.exports = {
     TINY: Symbol("TINY"),
     SHORT: Symbol("SHORT"),
     LONG: Symbol("LONG")
   }
 };
 
 /***/ }),
 
-/***/ 3647:
+/***/ 1763:
+/***/ (function(module, exports) {
+
+module.exports = __WEBPACK_EXTERNAL_MODULE_1763__;
+
+/***/ }),
+
+/***/ 1767:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/>. */
 
-__webpack_require__(3672);
+__webpack_require__(1831);
 
 // Load all existing rep templates
-const Undefined = __webpack_require__(3673);
-const Null = __webpack_require__(3674);
-const StringRep = __webpack_require__(3648);
-const Number = __webpack_require__(3675);
-const ArrayRep = __webpack_require__(3649);
-const Obj = __webpack_require__(3676);
-const SymbolRep = __webpack_require__(3677);
-const InfinityRep = __webpack_require__(3678);
-const NaNRep = __webpack_require__(3679);
-const Accessor = __webpack_require__(3680);
+const Undefined = __webpack_require__(1832);
+const Null = __webpack_require__(1833);
+const StringRep = __webpack_require__(1770);
+const Number = __webpack_require__(1834);
+const ArrayRep = __webpack_require__(1774);
+const Obj = __webpack_require__(1835);
+const SymbolRep = __webpack_require__(1836);
+const InfinityRep = __webpack_require__(1837);
+const NaNRep = __webpack_require__(1838);
+const Accessor = __webpack_require__(1839);
 
 // DOM types (grips)
-const Accessible = __webpack_require__(3787);
-const Attribute = __webpack_require__(3681);
-const DateTime = __webpack_require__(3682);
-const Document = __webpack_require__(3683);
-const DocumentType = __webpack_require__(3684);
-const Event = __webpack_require__(3685);
-const Func = __webpack_require__(3658);
-const PromiseRep = __webpack_require__(3686);
-const RegExp = __webpack_require__(3687);
-const StyleSheet = __webpack_require__(3688);
-const CommentNode = __webpack_require__(3689);
-const ElementNode = __webpack_require__(3690);
-const TextNode = __webpack_require__(3691);
-const ErrorRep = __webpack_require__(3660);
-const Window = __webpack_require__(3692);
-const ObjectWithText = __webpack_require__(3693);
-const ObjectWithURL = __webpack_require__(3694);
-const GripArray = __webpack_require__(3661);
-const GripMap = __webpack_require__(3663);
-const GripMapEntry = __webpack_require__(3664);
-const Grip = __webpack_require__(3656);
+const Accessible = __webpack_require__(1840);
+const Attribute = __webpack_require__(1841);
+const DateTime = __webpack_require__(1842);
+const Document = __webpack_require__(1843);
+const DocumentType = __webpack_require__(1844);
+const Event = __webpack_require__(1845);
+const Func = __webpack_require__(1795);
+const PromiseRep = __webpack_require__(1846);
+const RegExp = __webpack_require__(1847);
+const StyleSheet = __webpack_require__(1848);
+const CommentNode = __webpack_require__(1849);
+const ElementNode = __webpack_require__(1850);
+const TextNode = __webpack_require__(1851);
+const ErrorRep = __webpack_require__(1797);
+const Window = __webpack_require__(1852);
+const ObjectWithText = __webpack_require__(1853);
+const ObjectWithURL = __webpack_require__(1854);
+const GripArray = __webpack_require__(1798);
+const GripMap = __webpack_require__(1800);
+const GripMapEntry = __webpack_require__(1801);
+const Grip = __webpack_require__(1784);
 
 // List of all registered template.
 // XXX there should be a way for extensions to register a new
 // or modify an existing rep.
 const reps = [RegExp, StyleSheet, Event, DateTime, CommentNode, Accessible, ElementNode, TextNode, Attribute, Func, PromiseRep, ArrayRep, Document, DocumentType, Window, ObjectWithText, ObjectWithURL, ErrorRep, GripArray, GripMap, GripMapEntry, Grip, Undefined, Null, StringRep, Number, SymbolRep, InfinityRep, NaNRep, Accessor, Obj];
 
 /**
  * Generic rep that is used for rendering native JS types or an object.
@@ -758,43 +751,43 @@ module.exports = {
     Window
   },
   // Exporting for tests
   getRep
 };
 
 /***/ }),
 
-/***/ 3648:
+/***/ 1770:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/>. */
 
 // Dependencies
-const PropTypes = __webpack_require__(3642);
+const PropTypes = __webpack_require__(1758);
 
 const {
   containsURL,
   isURL,
   escapeString,
   getGripType,
   rawCropString,
   sanitizeString,
   wrapRender,
   isGrip,
   tokenSplitRegex,
   ELLIPSIS
-} = __webpack_require__(3644);
-
-const dom = __webpack_require__(3643);
+} = __webpack_require__(1760);
+
+const dom = __webpack_require__(1759);
 const { a, span } = dom;
 
 /**
  * Renders a string. String value is enclosed within quotes.
  */
 StringRep.propTypes = {
   useQuotes: PropTypes.bool,
   escapeWhitespace: PropTypes.bool,
@@ -1032,33 +1025,33 @@ function supportsObject(object, noGrip =
 module.exports = {
   rep: wrapRender(StringRep),
   supportsObject,
   isLongString
 };
 
 /***/ }),
 
-/***/ 3649:
+/***/ 1774:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
 
 /* 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/>. */
 
 // Dependencies
-const dom = __webpack_require__(3643);
-const PropTypes = __webpack_require__(3642);
-const { wrapRender } = __webpack_require__(3644);
-const { MODE } = __webpack_require__(3645);
+const dom = __webpack_require__(1759);
+const PropTypes = __webpack_require__(1758);
+const { wrapRender } = __webpack_require__(1760);
+const { MODE } = __webpack_require__(1762);
 const { span } = dom;
 
 const ModePropType = PropTypes.oneOf(
 // @TODO Change this to Object.values when supported in Node's version of V8
 Object.keys(MODE).map(key => MODE[key]));
 
 /**
  * Renders an array. The array is enclosed by left and right bracket
@@ -1140,17 +1133,17 @@ function arrayIterator(props, array, max
  */
 ItemRep.propTypes = {
   object: PropTypes.any.isRequired,
   delim: PropTypes.string.isRequired,
   mode: ModePropType
 };
 
 function ItemRep(props) {
-  const { Rep } = __webpack_require__(3647);
+  const { Rep } = __webpack_require__(1767);
 
   const { object, delim, mode } = props;
   return span({}, Rep(_extends({}, props, {
     object: object,
     mode: mode
   })), delim);
 }
 
@@ -1172,34 +1165,34 @@ module.exports = {
   supportsObject,
   maxLengthMap,
   getLength,
   ModePropType
 };
 
 /***/ }),
 
-/***/ 3650:
+/***/ 1775:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
 
 /* 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/>. */
 
 // Dependencies
-const PropTypes = __webpack_require__(3642);
-const { maybeEscapePropertyName, wrapRender } = __webpack_require__(3644);
-const { MODE } = __webpack_require__(3645);
-
-const { span } = __webpack_require__(3643);
+const PropTypes = __webpack_require__(1758);
+const { maybeEscapePropertyName, wrapRender } = __webpack_require__(1760);
+const { MODE } = __webpack_require__(1762);
+
+const { span } = __webpack_require__(1759);
 
 /**
  * Property for Obj (local JS objects), Grip (remote JS objects)
  * and GripMap (remote JS maps and weakmaps) reps.
  * It's used to render object properties.
  */
 PropRep.propTypes = {
   // Property name.
@@ -1220,18 +1213,18 @@ PropRep.propTypes = {
 /**
  * Function that given a name, a delimiter and an object returns an array
  * of React elements representing an object property (e.g. `name: value`)
  *
  * @param {Object} props
  * @return {Array} Array of React elements.
  */
 function PropRep(props) {
-  const Grip = __webpack_require__(3656);
-  const { Rep } = __webpack_require__(3647);
+  const Grip = __webpack_require__(1784);
+  const { Rep } = __webpack_require__(1767);
 
   let { name, mode, equal, suppressQuotes } = props;
 
   let key;
   // The key can be a simple string, for plain objects,
   // or another object for maps and weakmaps.
   if (typeof name === "string") {
     if (!suppressQuotes) {
@@ -1252,71 +1245,71 @@ function PropRep(props) {
   }, equal), Rep(_extends({}, props))];
 }
 
 // Exports from this module
 module.exports = wrapRender(PropRep);
 
 /***/ }),
 
-/***/ 3655:
+/***/ 1780:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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 { MODE } = __webpack_require__(3645);
-const { REPS, getRep } = __webpack_require__(3647);
-const objectInspector = __webpack_require__(3695);
+const { MODE } = __webpack_require__(1762);
+const { REPS, getRep } = __webpack_require__(1767);
+const objectInspector = __webpack_require__(1855);
 
 const {
   parseURLEncodedText,
   parseURLParams,
   maybeEscapePropertyName,
   getGripPreviewItems
-} = __webpack_require__(3644);
+} = __webpack_require__(1760);
 
 module.exports = {
   REPS,
   getRep,
   MODE,
   maybeEscapePropertyName,
   parseURLEncodedText,
   parseURLParams,
   getGripPreviewItems,
   objectInspector
 };
 
 /***/ }),
 
-/***/ 3656:
+/***/ 1784:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
 
 /* 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/>. */
 
 // ReactJS
-const PropTypes = __webpack_require__(3642);
+const PropTypes = __webpack_require__(1758);
 
 // Dependencies
-const { interleave, isGrip, wrapRender } = __webpack_require__(3644);
-const PropRep = __webpack_require__(3650);
-const { MODE } = __webpack_require__(3645);
-
-const dom = __webpack_require__(3643);
+const { interleave, isGrip, wrapRender } = __webpack_require__(1760);
+const PropRep = __webpack_require__(1775);
+const { MODE } = __webpack_require__(1762);
+
+const dom = __webpack_require__(1759);
 const { span } = dom;
 
 /**
  * Renders generic grip. Grip is client representation
  * of remote JS object and is used as an input object
  * for this rep component.
  */
 GripRep.propTypes = {
@@ -1402,17 +1395,17 @@ function safePropIterator(props, object,
   } catch (err) {
     console.error(err);
   }
   return [];
 }
 
 function propIterator(props, object, max) {
   if (object.preview && Object.keys(object.preview).includes("wrappedValue")) {
-    const { Rep } = __webpack_require__(3647);
+    const { Rep } = __webpack_require__(1767);
 
     return [Rep({
       object: object.preview.wrappedValue,
       mode: props.mode || MODE.TINY,
       defaultRep: Grip
     })];
   }
 
@@ -1591,1337 +1584,35 @@ const Grip = {
   maxLengthMap
 };
 
 // Exports from this module
 module.exports = Grip;
 
 /***/ }),
 
-/***/ 3657:
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
-
-/* 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 client = __webpack_require__(3665);
-const loadProperties = __webpack_require__(3666);
-const node = __webpack_require__(3667);
-const { nodeIsError, nodeIsPrimitive } = node;
-const selection = __webpack_require__(3698);
-
-const { MODE } = __webpack_require__(3645);
-const {
-  REPS: { Rep, Grip }
-} = __webpack_require__(3647);
-
-
-function shouldRenderRootsInReps(roots) {
-  if (roots.length > 1) {
-    return false;
-  }
-
-  const root = roots[0];
-  const name = root && root.name;
-  return (name === null || typeof name === "undefined") && (nodeIsPrimitive(root) || nodeIsError(root));
-}
-
-function renderRep(item, props) {
-  return Rep(_extends({}, props, {
-    object: node.getValue(item),
-    mode: props.mode || MODE.TINY,
-    defaultRep: Grip
-  }));
-}
-
-module.exports = {
-  client,
-  loadProperties,
-  node,
-  renderRep,
-  selection,
-  shouldRenderRootsInReps
-};
-
-/***/ }),
-
-/***/ 3658:
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-/* 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/>. */
-
-// ReactJS
-const PropTypes = __webpack_require__(3642);
-
-// Reps
-const { getGripType, isGrip, cropString, wrapRender } = __webpack_require__(3644);
-const { MODE } = __webpack_require__(3645);
-
-const dom = __webpack_require__(3643);
-const { span } = dom;
-
-const IGNORED_SOURCE_URLS = ["debugger eval code"];
-
-/**
- * This component represents a template for Function objects.
- */
-FunctionRep.propTypes = {
-  object: PropTypes.object.isRequired,
-  parameterNames: PropTypes.array,
-  onViewSourceInDebugger: PropTypes.func
-};
-
-function FunctionRep(props) {
-  const { object: grip, onViewSourceInDebugger, recordTelemetryEvent } = props;
-
-  let jumpToDefinitionButton;
-  if (onViewSourceInDebugger && grip.location && grip.location.url && !IGNORED_SOURCE_URLS.includes(grip.location.url)) {
-    jumpToDefinitionButton = dom.button({
-      className: "jump-definition",
-      draggable: false,
-      title: "Jump to definition",
-      onClick: e => {
-        // Stop the event propagation so we don't trigger ObjectInspector
-        // expand/collapse.
-        e.stopPropagation();
-        if (recordTelemetryEvent) {
-          recordTelemetryEvent("jump_to_definition");
-        }
-        onViewSourceInDebugger(grip.location);
-      }
-    });
-  }
-
-  return span({
-    "data-link-actor-id": grip.actor,
-    className: "objectBox objectBox-function",
-    // Set dir="ltr" to prevent function parentheses from
-    // appearing in the wrong direction
-    dir: "ltr"
-  }, getTitle(grip, props), getFunctionName(grip, props), "(", ...renderParams(props), ")", jumpToDefinitionButton);
-}
-
-function getTitle(grip, props) {
-  const { mode } = props;
-
-  if (mode === MODE.TINY && !grip.isGenerator && !grip.isAsync) {
-    return null;
-  }
-
-  let title = mode === MODE.TINY ? "" : "function ";
-
-  if (grip.isGenerator) {
-    title = mode === MODE.TINY ? "* " : "function* ";
-  }
-
-  if (grip.isAsync) {
-    title = `${"async" + " "}${title}`;
-  }
-
-  return span({
-    className: "objectTitle"
-  }, title);
-}
-
-/**
- * Returns a ReactElement representing the function name.
- *
- * @param {Object} grip : Function grip
- * @param {Object} props: Function rep props
- */
-function getFunctionName(grip, props = {}) {
-  let { functionName } = props;
-  let name;
-
-  if (functionName) {
-    const end = functionName.length - 1;
-    functionName = functionName.startsWith('"') && functionName.endsWith('"') ? functionName.substring(1, end) : functionName;
-  }
-
-  if (grip.displayName != undefined && functionName != undefined && grip.displayName != functionName) {
-    name = `${functionName}:${grip.displayName}`;
-  } else {
-    name = cleanFunctionName(grip.userDisplayName || grip.displayName || grip.name || props.functionName || "");
-  }
-
-  return cropString(name, 100);
-}
-
-const objectProperty = /([\w\d]+)$/;
-const arrayProperty = /\[(.*?)\]$/;
-const functionProperty = /([\w\d]+)[\/\.<]*?$/;
-const annonymousProperty = /([\w\d]+)\(\^\)$/;
-
-/**
- * Decodes an anonymous naming scheme that
- * spider monkey implements based on "Naming Anonymous JavaScript Functions"
- * http://johnjbarton.github.io/nonymous/index.html
- *
- * @param {String} name : Function name to clean up
- * @returns String
- */
-function cleanFunctionName(name) {
-  for (const reg of [objectProperty, arrayProperty, functionProperty, annonymousProperty]) {
-    const match = reg.exec(name);
-    if (match) {
-      return match[1];
-    }
-  }
-
-  return name;
-}
-
-function renderParams(props) {
-  const { parameterNames = [] } = props;
-
-  return parameterNames.filter(param => param).reduce((res, param, index, arr) => {
-    res.push(span({ className: "param" }, param));
-    if (index < arr.length - 1) {
-      res.push(span({ className: "delimiter" }, ", "));
-    }
-    return res;
-  }, []);
-}
-
-// Registration
-function supportsObject(grip, noGrip = false) {
-  const type = getGripType(grip, noGrip);
-  if (noGrip === true || !isGrip(grip)) {
-    return type == "function";
-  }
-
-  return type == "Function";
-}
-
-// Exports from this module
-
-module.exports = {
-  rep: wrapRender(FunctionRep),
-  supportsObject,
-  cleanFunctionName,
-  // exported for testing purpose.
-  getFunctionName
-};
-
-/***/ }),
-
-/***/ 3659:
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-/* 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/>. */
-
-module.exports = {
-  ELEMENT_NODE: 1,
-  ATTRIBUTE_NODE: 2,
-  TEXT_NODE: 3,
-  CDATA_SECTION_NODE: 4,
-  ENTITY_REFERENCE_NODE: 5,
-  ENTITY_NODE: 6,
-  PROCESSING_INSTRUCTION_NODE: 7,
-  COMMENT_NODE: 8,
-  DOCUMENT_NODE: 9,
-  DOCUMENT_TYPE_NODE: 10,
-  DOCUMENT_FRAGMENT_NODE: 11,
-  NOTATION_NODE: 12,
-
-  // DocumentPosition
-  DOCUMENT_POSITION_DISCONNECTED: 0x01,
-  DOCUMENT_POSITION_PRECEDING: 0x02,
-  DOCUMENT_POSITION_FOLLOWING: 0x04,
-  DOCUMENT_POSITION_CONTAINS: 0x08,
-  DOCUMENT_POSITION_CONTAINED_BY: 0x10,
-  DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC: 0x20
-};
-
-/***/ }),
-
-/***/ 3660:
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-/* 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/>. */
-
-// ReactJS
-const PropTypes = __webpack_require__(3642);
-// Utils
-const { getGripType, isGrip, wrapRender } = __webpack_require__(3644);
-const { cleanFunctionName } = __webpack_require__(3658);
-const { isLongString } = __webpack_require__(3648);
-const { MODE } = __webpack_require__(3645);
-
-const dom = __webpack_require__(3643);
-const { span } = dom;
-const IGNORED_SOURCE_URLS = ["debugger eval code"];
-
-/**
- * Renders Error objects.
- */
-ErrorRep.propTypes = {
-  object: PropTypes.object.isRequired,
-  // @TODO Change this to Object.values when supported in Node's version of V8
-  mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
-  // An optional function that will be used to render the Error stacktrace.
-  renderStacktrace: PropTypes.func
-};
-
-function ErrorRep(props) {
-  const object = props.object;
-  const preview = object.preview;
-
-  let name;
-  if (preview && preview.name && preview.kind) {
-    switch (preview.kind) {
-      case "Error":
-        name = preview.name;
-        break;
-      case "DOMException":
-        name = preview.kind;
-        break;
-      default:
-        throw new Error("Unknown preview kind for the Error rep.");
-    }
-  } else {
-    name = "Error";
-  }
-
-  const content = [];
-
-  if (props.mode === MODE.TINY) {
-    content.push(name);
-  } else {
-    content.push(`${name}: "${preview.message}"`);
-  }
-
-  if (preview.stack && props.mode !== MODE.TINY) {
-    const stacktrace = props.renderStacktrace ? props.renderStacktrace(parseStackString(preview.stack)) : getStacktraceElements(props, preview);
-    content.push("\n", stacktrace);
-  }
-
-  return span({
-    "data-link-actor-id": object.actor,
-    className: "objectBox-stackTrace"
-  }, content);
-}
-
-/**
- * Returns a React element reprensenting the Error stacktrace, i.e.
- * transform error.stack from:
- *
- * semicolon@debugger eval code:1:109
- * jkl@debugger eval code:1:63
- * asdf@debugger eval code:1:28
- * @debugger eval code:1:227
- *
- * Into a column layout:
- *
- * semicolon  (<anonymous>:8:10)
- * jkl        (<anonymous>:5:10)
- * asdf       (<anonymous>:2:10)
- *            (<anonymous>:11:1)
- */
-function getStacktraceElements(props, preview) {
-  const stack = [];
-  if (!preview.stack) {
-    return stack;
-  }
-
-  parseStackString(preview.stack).forEach((frame, index, frames) => {
-    let onLocationClick;
-    const {
-      filename,
-      lineNumber,
-      columnNumber,
-      functionName,
-      location
-    } = frame;
-
-    if (props.onViewSourceInDebugger && !IGNORED_SOURCE_URLS.includes(filename)) {
-      onLocationClick = e => {
-        // Don't trigger ObjectInspector expand/collapse.
-        e.stopPropagation();
-        props.onViewSourceInDebugger({
-          url: filename,
-          line: lineNumber,
-          column: columnNumber
-        });
-      };
-    }
-
-    stack.push("\t", span({
-      key: `fn${index}`,
-      className: "objectBox-stackTrace-fn"
-    }, cleanFunctionName(functionName)), " ", span({
-      key: `location${index}`,
-      className: "objectBox-stackTrace-location",
-      onClick: onLocationClick,
-      title: onLocationClick ? `View source in debugger → ${location}` : undefined
-    }, location), "\n");
-  });
-
-  return span({
-    key: "stack",
-    className: "objectBox-stackTrace-grid"
-  }, stack);
-}
-
-/**
- * Parse a string that should represent a stack trace and returns an array of
- * the frames. The shape of the frames are extremely important as they can then
- * be processed here or in the toolbox by other components.
- * @param {String} stack
- * @returns {Array} Array of frames, which are object with the following shape:
- *                  - {String} filename
- *                  - {String} functionName
- *                  - {String} location
- *                  - {Number} columnNumber
- *                  - {Number} lineNumber
- */
-function parseStackString(stack) {
-  const res = [];
-  if (!stack) {
-    return res;
-  }
-
-  const isStacktraceALongString = isLongString(stack);
-  const stackString = isStacktraceALongString ? stack.initial : stack;
-
-  stackString.split("\n").forEach((frame, index, frames) => {
-    if (!frame) {
-      // Skip any blank lines
-      return;
-    }
-
-    // If the stacktrace is a longString, don't include the last frame in the
-    // array, since it is certainly incomplete.
-    // Can be removed when https://bugzilla.mozilla.org/show_bug.cgi?id=1448833
-    // is fixed.
-    if (isStacktraceALongString && index === frames.length - 1) {
-      return;
-    }
-
-    let functionName;
-    let location;
-
-    // Given the input: "functionName@scriptLocation:2:100"
-    // Result: [
-    //   "functionName@scriptLocation:2:100",
-    //   "functionName",
-    //   "scriptLocation:2:100"
-    // ]
-    const result = frame.match(/^(.*)@(.*)$/);
-    if (result && result.length === 3) {
-      functionName = result[1];
-
-      // If the resource was loaded by base-loader.js, the location looks like:
-      // resource://devtools/shared/base-loader.js -> resource://path/to/file.js .
-      // What's needed is only the last part after " -> ".
-      location = result[2].split(" -> ").pop();
-    }
-
-    if (!functionName) {
-      functionName = "<anonymous>";
-    }
-
-    // Given the input: "scriptLocation:2:100"
-    // Result:
-    // ["scriptLocation:2:100", "scriptLocation", "2", "100"]
-    const locationParts = location.match(/^(.*):(\d+):(\d+)$/);
-
-    if (location && locationParts) {
-      const [, filename, line, column] = locationParts;
-      res.push({
-        filename,
-        functionName,
-        location,
-        columnNumber: Number(column),
-        lineNumber: Number(line)
-      });
-    }
-  });
-
-  return res;
-}
-
-// Registration
-function supportsObject(object, noGrip = false) {
-  if (noGrip === true || !isGrip(object)) {
-    return false;
-  }
-  return object.preview && getGripType(object, noGrip) === "Error" || object.class === "DOMException";
-}
-
-// Exports from this module
-module.exports = {
-  rep: wrapRender(ErrorRep),
-  supportsObject
-};
-
-/***/ }),
-
-/***/ 3661:
+/***/ 1785:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
 
 /* 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/>. */
 
-// Dependencies
-const PropTypes = __webpack_require__(3642);
-
-const { lengthBubble } = __webpack_require__(3662);
-const {
-  interleave,
-  getGripType,
-  isGrip,
-  wrapRender,
-  ellipsisElement
-} = __webpack_require__(3644);
-const { MODE } = __webpack_require__(3645);
-
-const dom = __webpack_require__(3643);
-const { span } = dom;
-const { ModePropType } = __webpack_require__(3649);
-const DEFAULT_TITLE = "Array";
-
-/**
- * Renders an array. The array is enclosed by left and right bracket
- * and the max number of rendered items depends on the current mode.
- */
-GripArray.propTypes = {
-  object: PropTypes.object.isRequired,
-  // @TODO Change this to Object.values when supported in Node's version of V8
-  mode: ModePropType,
-  provider: PropTypes.object,
-  onDOMNodeMouseOver: PropTypes.func,
-  onDOMNodeMouseOut: PropTypes.func,
-  onInspectIconClick: PropTypes.func
-};
-
-function GripArray(props) {
-  const { object, mode = MODE.SHORT } = props;
-
-  let brackets;
-  const needSpace = function (space) {
-    return space ? { left: "[ ", right: " ]" } : { left: "[", right: "]" };
-  };
-
-  const config = {
-    "data-link-actor-id": object.actor,
-    className: "objectBox objectBox-array"
-  };
-
-  const title = getTitle(props, object);
-
-  if (mode === MODE.TINY) {
-    const isEmpty = getLength(object) === 0;
-
-    // Omit bracketed ellipsis for non-empty non-Array arraylikes (f.e: Sets).
-    if (!isEmpty && object.class !== "Array") {
-      return span(config, title);
-    }
-
-    brackets = needSpace(false);
-    return span(config, title, span({
-      className: "arrayLeftBracket"
-    }, brackets.left), isEmpty ? null : ellipsisElement, span({
-      className: "arrayRightBracket"
-    }, brackets.right));
-  }
-
-  const max = maxLengthMap.get(mode);
-  const items = arrayIterator(props, object, max);
-  brackets = needSpace(items.length > 0);
-
-  return span({
-    "data-link-actor-id": object.actor,
-    className: "objectBox objectBox-array"
-  }, title, span({
-    className: "arrayLeftBracket"
-  }, brackets.left), ...interleave(items, ", "), span({
-    className: "arrayRightBracket"
-  }, brackets.right), span({
-    className: "arrayProperties",
-    role: "group"
-  }));
-}
-
-function getLength(grip) {
-  if (!grip.preview) {
-    return 0;
-  }
-
-  return grip.preview.length || grip.preview.childNodesLength || 0;
-}
-
-function getTitle(props, object) {
-  const objectLength = getLength(object);
-  const isEmpty = objectLength === 0;
-
-  let title = props.title || object.class || DEFAULT_TITLE;
-
-  const length = lengthBubble({
-    object,
-    mode: props.mode,
-    maxLengthMap,
-    getLength
-  });
-
-  if (props.mode === MODE.TINY) {
-    if (isEmpty) {
-      if (object.class === DEFAULT_TITLE) {
-        return null;
-      }
-
-      return span({ className: "objectTitle" }, `${title} `);
-    }
-
-    let trailingSpace;
-    if (object.class === DEFAULT_TITLE) {
-      title = null;
-      trailingSpace = " ";
-    }
-
-    return span({ className: "objectTitle" }, title, length, trailingSpace);
-  }
-
-  return span({ className: "objectTitle" }, title, length, " ");
-}
-
-function getPreviewItems(grip) {
-  if (!grip.preview) {
-    return null;
-  }
-
-  return grip.preview.items || grip.preview.childNodes || [];
-}
-
-function arrayIterator(props, grip, max) {
-  const { Rep } = __webpack_require__(3647);
-
-  let items = [];
-  const gripLength = getLength(grip);
-
-  if (!gripLength) {
-    return items;
-  }
-
-  const previewItems = getPreviewItems(grip);
-  const provider = props.provider;
-
-  let emptySlots = 0;
-  let foldedEmptySlots = 0;
-  items = previewItems.reduce((res, itemGrip) => {
-    if (res.length >= max) {
-      return res;
-    }
-
-    let object;
-    try {
-      if (!provider && itemGrip === null) {
-        emptySlots++;
-        return res;
-      }
-
-      object = provider ? provider.getValue(itemGrip) : itemGrip;
-    } catch (exc) {
-      object = exc;
-    }
-
-    if (emptySlots > 0) {
-      res.push(getEmptySlotsElement(emptySlots));
-      foldedEmptySlots = foldedEmptySlots + emptySlots - 1;
-      emptySlots = 0;
-    }
-
-    if (res.length < max) {
-      res.push(Rep(_extends({}, props, {
-        object,
-        mode: MODE.TINY,
-        // Do not propagate title to array items reps
-        title: undefined
-      })));
-    }
-
-    return res;
-  }, []);
-
-  // Handle trailing empty slots if there are some.
-  if (items.length < max && emptySlots > 0) {
-    items.push(getEmptySlotsElement(emptySlots));
-    foldedEmptySlots = foldedEmptySlots + emptySlots - 1;
-  }
-
-  const itemsShown = items.length + foldedEmptySlots;
-  if (gripLength > itemsShown) {
-    items.push(ellipsisElement);
-  }
-
-  return items;
-}
-
-function getEmptySlotsElement(number) {
-  // TODO: Use l10N - See https://github.com/devtools-html/reps/issues/141
-  return `<${number} empty slot${number > 1 ? "s" : ""}>`;
-}
-
-function supportsObject(grip, noGrip = false) {
-  if (noGrip === true || !isGrip(grip)) {
-    return false;
-  }
-
-  return grip.preview && (grip.preview.kind == "ArrayLike" || getGripType(grip, noGrip) === "DocumentFragment");
-}
-
-const maxLengthMap = new Map();
-maxLengthMap.set(MODE.SHORT, 3);
-maxLengthMap.set(MODE.LONG, 10);
-
-// Exports from this module
-module.exports = {
-  rep: wrapRender(GripArray),
-  supportsObject,
-  maxLengthMap,
-  getLength
-};
-
-/***/ }),
-
-/***/ 3662:
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-/* 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 PropTypes = __webpack_require__(3642);
-
-const { wrapRender } = __webpack_require__(3644);
-const { MODE } = __webpack_require__(3645);
-const { ModePropType } = __webpack_require__(3649);
-
-const dom = __webpack_require__(3643);
-const { span } = dom;
-
-GripLengthBubble.propTypes = {
-  object: PropTypes.object.isRequired,
-  maxLengthMap: PropTypes.instanceOf(Map).isRequired,
-  getLength: PropTypes.func.isRequired,
-  mode: ModePropType,
-  visibilityThreshold: PropTypes.number
-};
-
-function GripLengthBubble(props) {
-  const {
-    object,
-    mode = MODE.SHORT,
-    visibilityThreshold = 2,
-    maxLengthMap,
-    getLength,
-    showZeroLength = false
-  } = props;
-
-  const length = getLength(object);
-  const isEmpty = length === 0;
-  const isObvious = [MODE.SHORT, MODE.LONG].includes(mode) && length > 0 && length <= maxLengthMap.get(mode) && length <= visibilityThreshold;
-  if (isEmpty && !showZeroLength || isObvious) {
-    return "";
-  }
-
-  return span({
-    className: "objectLengthBubble"
-  }, `(${length})`);
-}
-
-module.exports = {
-  lengthBubble: wrapRender(GripLengthBubble)
-};
-
-/***/ }),
-
-/***/ 3663:
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-/* 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/>. */
-
-// Dependencies
-
-const { lengthBubble } = __webpack_require__(3662);
-const PropTypes = __webpack_require__(3642);
-const {
-  interleave,
-  isGrip,
-  wrapRender,
-  ellipsisElement
-} = __webpack_require__(3644);
-const PropRep = __webpack_require__(3650);
-const { MODE } = __webpack_require__(3645);
-const { ModePropType } = __webpack_require__(3649);
-
-const { span } = __webpack_require__(3643);
-
-/**
- * Renders an map. A map is represented by a list of its
- * entries enclosed in curly brackets.
- */
-GripMap.propTypes = {
-  object: PropTypes.object,
-  // @TODO Change this to Object.values when supported in Node's version of V8
-  mode: ModePropType,
-  isInterestingEntry: PropTypes.func,
-  onDOMNodeMouseOver: PropTypes.func,
-  onDOMNodeMouseOut: PropTypes.func,
-  onInspectIconClick: PropTypes.func,
-  title: PropTypes.string
-};
-
-function GripMap(props) {
-  const { mode, object } = props;
-
-  const config = {
-    "data-link-actor-id": object.actor,
-    className: "objectBox objectBox-object"
-  };
-
-  const title = getTitle(props, object);
-  const isEmpty = getLength(object) === 0;
-
-  if (isEmpty || mode === MODE.TINY) {
-    return span(config, title);
-  }
-
-  const propsArray = safeEntriesIterator(props, object, maxLengthMap.get(mode));
-
-  return span(config, title, span({
-    className: "objectLeftBrace"
-  }, " { "), ...interleave(propsArray, ", "), span({
-    className: "objectRightBrace"
-  }, " }"));
-}
-
-function getTitle(props, object) {
-  const title = props.title || (object && object.class ? object.class : "Map");
-  return span({
-    className: "objectTitle"
-  }, title, lengthBubble({
-    object,
-    mode: props.mode,
-    maxLengthMap,
-    getLength,
-    showZeroLength: true
-  }));
-}
-
-function safeEntriesIterator(props, object, max) {
-  max = typeof max === "undefined" ? 3 : max;
-  try {
-    return entriesIterator(props, object, max);
-  } catch (err) {
-    console.error(err);
-  }
-  return [];
-}
-
-function entriesIterator(props, object, max) {
-  // Entry filter. Show only interesting entries to the user.
-  const isInterestingEntry = props.isInterestingEntry || ((type, value) => {
-    return type == "boolean" || type == "number" || type == "string" && value.length != 0;
-  });
-
-  const mapEntries = object.preview && object.preview.entries ? object.preview.entries : [];
-
-  let indexes = getEntriesIndexes(mapEntries, max, isInterestingEntry);
-  if (indexes.length < max && indexes.length < mapEntries.length) {
-    // There are not enough entries yet, so we add uninteresting entries.
-    indexes = indexes.concat(getEntriesIndexes(mapEntries, max - indexes.length, (t, value, name) => {
-      return !isInterestingEntry(t, value, name);
-    }));
-  }
-
-  const entries = getEntries(props, mapEntries, indexes);
-  if (entries.length < getLength(object)) {
-    // There are some undisplayed entries. Then display "…".
-    entries.push(ellipsisElement);
-  }
-
-  return entries;
-}
-
-/**
- * Get entries ordered by index.
- *
- * @param {Object} props Component props.
- * @param {Array} entries Entries array.
- * @param {Array} indexes Indexes of entries.
- * @return {Array} Array of PropRep.
- */
-function getEntries(props, entries, indexes) {
-  const { onDOMNodeMouseOver, onDOMNodeMouseOut, onInspectIconClick } = props;
-
-  // Make indexes ordered by ascending.
-  indexes.sort(function (a, b) {
-    return a - b;
-  });
-
-  return indexes.map((index, i) => {
-    const [key, entryValue] = entries[index];
-    const value = entryValue.value !== undefined ? entryValue.value : entryValue;
-
-    return PropRep({
-      name: key,
-      equal: " \u2192 ",
-      object: value,
-      mode: MODE.TINY,
-      onDOMNodeMouseOver,
-      onDOMNodeMouseOut,
-      onInspectIconClick
-    });
-  });
-}
-
-/**
- * Get the indexes of entries in the map.
- *
- * @param {Array} entries Entries array.
- * @param {Number} max The maximum length of indexes array.
- * @param {Function} filter Filter the entry you want.
- * @return {Array} Indexes of filtered entries in the map.
- */
-function getEntriesIndexes(entries, max, filter) {
-  return entries.reduce((indexes, [key, entry], i) => {
-    if (indexes.length < max) {
-      const value = entry && entry.value !== undefined ? entry.value : entry;
-      // Type is specified in grip's "class" field and for primitive
-      // values use typeof.
-      const type = (value && value.class ? value.class : typeof value).toLowerCase();
-
-      if (filter(type, value, key)) {
-        indexes.push(i);
-      }
-    }
-
-    return indexes;
-  }, []);
-}
-
-function getLength(grip) {
-  return grip.preview.size || 0;
-}
-
-function supportsObject(grip, noGrip = false) {
-  if (noGrip === true || !isGrip(grip)) {
-    return false;
-  }
-  return grip.preview && grip.preview.kind == "MapLike";
-}
-
-const maxLengthMap = new Map();
-maxLengthMap.set(MODE.SHORT, 3);
-maxLengthMap.set(MODE.LONG, 10);
-
-// Exports from this module
-module.exports = {
-  rep: wrapRender(GripMap),
-  supportsObject,
-  maxLengthMap,
-  getLength
-};
-
-/***/ }),
-
-/***/ 3664:
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
-
-/* 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/>. */
-
-// Dependencies
-const PropTypes = __webpack_require__(3642);
-// Shortcuts
-const dom = __webpack_require__(3643);
-const { span } = dom;
-const { wrapRender } = __webpack_require__(3644);
-const PropRep = __webpack_require__(3650);
-const { MODE } = __webpack_require__(3645);
-/**
- * Renders an map entry. A map entry is represented by its key,
- * a column and its value.
- */
-GripMapEntry.propTypes = {
-  object: PropTypes.object,
-  // @TODO Change this to Object.values when supported in Node's version of V8
-  mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
-  onDOMNodeMouseOver: PropTypes.func,
-  onDOMNodeMouseOut: PropTypes.func,
-  onInspectIconClick: PropTypes.func
-};
-
-function GripMapEntry(props) {
-  const { object } = props;
-
-  const { key, value } = object.preview;
-
-  return span({
-    className: "objectBox objectBox-map-entry"
-  }, PropRep(_extends({}, props, {
-    name: key,
-    object: value,
-    equal: " \u2192 ",
-    title: null,
-    suppressQuotes: false
-  })));
-}
-
-function supportsObject(grip, noGrip = false) {
-  if (noGrip === true) {
-    return false;
-  }
-  return grip && (grip.type === "mapEntry" || grip.type === "storageEntry") && grip.preview;
-}
-
-function createGripMapEntry(key, value) {
-  return {
-    type: "mapEntry",
-    preview: {
-      key,
-      value
-    }
-  };
-}
-
-// Exports from this module
-module.exports = {
-  rep: wrapRender(GripMapEntry),
-  createGripMapEntry,
-  supportsObject
-};
-
-/***/ }),
-
-/***/ 3665:
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-const { getValue, nodeHasFullText } = __webpack_require__(3667); /* 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/>. */
-
-async function enumIndexedProperties(objectClient, start, end) {
-  try {
-    const { iterator } = await objectClient.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) {
-  try {
-    const { iterator } = await objectClient.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) {
-  try {
-    const { iterator } = await objectClient.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) {
-  try {
-    const { iterator } = await objectClient.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");
-    return Promise.resolve({});
-  }
-  return objectClient.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
-  // loadedProperties map.
-  if (nodeHasFullText(item)) {
-    return Promise.resolve({ fullText });
-  }
-
-  return new Promise((resolve, reject) => {
-    longStringClient.substring(initial.length, length, response => {
-      if (response.error) {
-        console.error("LongStringClient.substring", `${response.error}: ${response.message}`);
-        reject({});
-        return;
-      }
-
-      resolve({
-        fullText: initial + response.substring
-      });
-    });
-  });
-}
-
-function iteratorSlice(iterator, start, end) {
-  start = start || 0;
-  const count = end ? end - start + 1 : iterator.count;
-
-  if (count === 0) {
-    return Promise.resolve({});
-  }
-  return iterator.slice(start, count);
-}
-
-module.exports = {
-  enumEntries,
-  enumIndexedProperties,
-  enumNonIndexedProperties,
-  enumSymbols,
-  getPrototype,
-  getFullText
-};
-
-/***/ }),
-
-/***/ 3666:
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
-
-/* 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 {
-  enumEntries,
-  enumIndexedProperties,
-  enumNonIndexedProperties,
-  getPrototype,
-  enumSymbols,
-  getFullText
-} = __webpack_require__(3665);
-
-const {
-  getClosestGripNode,
-  getClosestNonBucketNode,
-  getValue,
-  nodeHasAccessors,
-  nodeHasAllEntriesInPreview,
-  nodeHasProperties,
-  nodeIsBucket,
-  nodeIsDefaultProperties,
-  nodeIsEntries,
-  nodeIsMapEntry,
-  nodeIsPrimitive,
-  nodeIsProxy,
-  nodeNeedsNumericalBuckets,
-  nodeIsLongString
-} = __webpack_require__(3667);
-
-function loadItemProperties(item, createObjectClient, createLongStringClient, loadedProperties) {
-  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 || createObjectClient(value);
-
-  if (shouldLoadItemIndexedProperties(item, loadedProperties)) {
-    promises.push(enumIndexedProperties(getObjectClient(), start, end));
-  }
-
-  if (shouldLoadItemNonIndexedProperties(item, loadedProperties)) {
-    promises.push(enumNonIndexedProperties(getObjectClient(), start, end));
-  }
-
-  if (shouldLoadItemEntries(item, loadedProperties)) {
-    promises.push(enumEntries(getObjectClient(), start, end));
-  }
-
-  if (shouldLoadItemPrototype(item, loadedProperties)) {
-    promises.push(getPrototype(getObjectClient()));
-  }
-
-  if (shouldLoadItemSymbols(item, loadedProperties)) {
-    promises.push(enumSymbols(getObjectClient(), start, end));
-  }
-
-  if (shouldLoadItemFullText(item, loadedProperties)) {
-    promises.push(getFullText(createLongStringClient(value), item));
-  }
-
-  return Promise.all(promises).then(mergeResponses);
-}
-
-function mergeResponses(responses) {
-  const data = {};
-
-  for (const response of responses) {
-    if (response.hasOwnProperty("ownProperties")) {
-      data.ownProperties = _extends({}, data.ownProperties, response.ownProperties);
-    }
-
-    if (response.ownSymbols && response.ownSymbols.length > 0) {
-      data.ownSymbols = response.ownSymbols;
-    }
-
-    if (response.prototype) {
-      data.prototype = response.prototype;
-    }
-
-    if (response.fullText) {
-      data.fullText = response.fullText;
-    }
-  }
-
-  return data;
-}
-
-function shouldLoadItemIndexedProperties(item, loadedProperties = new Map()) {
-  const gripItem = getClosestGripNode(item);
-  const value = getValue(gripItem);
-
-  return value && nodeHasProperties(gripItem) && !loadedProperties.has(item.path) && !nodeIsProxy(item) && !nodeNeedsNumericalBuckets(item) && !nodeIsEntries(getClosestNonBucketNode(item)) &&
-  // The data is loaded when expanding the window node.
-  !nodeIsDefaultProperties(item);
-}
-
-function shouldLoadItemNonIndexedProperties(item, loadedProperties = new Map()) {
-  const gripItem = getClosestGripNode(item);
-  const value = getValue(gripItem);
-
-  return value && nodeHasProperties(gripItem) && !loadedProperties.has(item.path) && !nodeIsProxy(item) && !nodeIsEntries(getClosestNonBucketNode(item)) && !nodeIsBucket(item) &&
-  // The data is loaded when expanding the window node.
-  !nodeIsDefaultProperties(item);
-}
-
-function shouldLoadItemEntries(item, loadedProperties = new Map()) {
-  const gripItem = getClosestGripNode(item);
-  const value = getValue(gripItem);
-
-  return value && nodeIsEntries(getClosestNonBucketNode(item)) && !nodeHasAllEntriesInPreview(gripItem) && !loadedProperties.has(item.path) && !nodeNeedsNumericalBuckets(item);
-}
-
-function shouldLoadItemPrototype(item, loadedProperties = new Map()) {
-  const value = getValue(item);
-
-  return value && !loadedProperties.has(item.path) && !nodeIsBucket(item) && !nodeIsMapEntry(item) && !nodeIsEntries(item) && !nodeIsDefaultProperties(item) && !nodeHasAccessors(item) && !nodeIsPrimitive(item) && !nodeIsLongString(item);
-}
-
-function shouldLoadItemSymbols(item, loadedProperties = new Map()) {
-  const value = getValue(item);
-
-  return value && !loadedProperties.has(item.path) && !nodeIsBucket(item) && !nodeIsMapEntry(item) && !nodeIsEntries(item) && !nodeIsDefaultProperties(item) && !nodeHasAccessors(item) && !nodeIsPrimitive(item) && !nodeIsLongString(item) && !nodeIsProxy(item);
-}
-
-function shouldLoadItemFullText(item, loadedProperties = new Map()) {
-  return !loadedProperties.has(item.path) && nodeIsLongString(item);
-}
-
-module.exports = {
-  loadItemProperties,
-  mergeResponses,
-  shouldLoadItemEntries,
-  shouldLoadItemIndexedProperties,
-  shouldLoadItemNonIndexedProperties,
-  shouldLoadItemPrototype,
-  shouldLoadItemSymbols,
-  shouldLoadItemFullText
-};
-
-/***/ }),
-
-/***/ 3667:
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
-
-/* 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 { maybeEscapePropertyName } = __webpack_require__(3644);
-const ArrayRep = __webpack_require__(3649);
-const GripArrayRep = __webpack_require__(3661);
-const GripMap = __webpack_require__(3663);
-const GripMapEntryRep = __webpack_require__(3664);
-const ErrorRep = __webpack_require__(3660);
-const { isLongString } = __webpack_require__(3648);
+const { maybeEscapePropertyName } = __webpack_require__(1760);
+const ArrayRep = __webpack_require__(1774);
+const GripArrayRep = __webpack_require__(1798);
+const GripMap = __webpack_require__(1800);
+const GripMapEntryRep = __webpack_require__(1801);
+const ErrorRep = __webpack_require__(1797);
+const { isLongString } = __webpack_require__(1770);
 
 const MAX_NUMERICAL_PROPERTIES = 100;
 
 const NODE_TYPES = {
   BUCKET: Symbol("[n…m]"),
   DEFAULT_PROPERTIES: Symbol("<default properties>"),
   ENTRIES: Symbol("<entries>"),
   GET: Symbol("<get>"),
@@ -3117,18 +1808,28 @@ function nodeIsLongString(item) {
   return isLongString(getValue(item));
 }
 
 function nodeHasFullText(item) {
   const value = getValue(item);
   return nodeIsLongString(item) && value.hasOwnProperty("fullText");
 }
 
+function nodeHasGetter(item) {
+  const getter = getNodeGetter(item);
+  return getter && getter.type !== "undefined";
+}
+
+function nodeHasSetter(item) {
+  const setter = getNodeSetter(item);
+  return setter && setter.type !== "undefined";
+}
+
 function nodeHasAccessors(item) {
-  return !!getNodeGetter(item) || !!getNodeSetter(item);
+  return nodeHasGetter(item) || nodeHasSetter(item);
 }
 
 function nodeSupportsNumericalBucketing(item) {
   // We exclude elements with entries since it's the <entries> node
   // itself that can have buckets.
   return nodeIsArrayLike(item) && !nodeHasEntries(item) || nodeIsEntries(item) || nodeIsBucket(item);
 }
 
@@ -3519,18 +2220,48 @@ function getSymbolDescriptor(symbol) {
   return symbol.toString().replace(/^(Symbol\()(.*)(\))$/, "$2");
 }
 
 function setNodeChildren(node, children) {
   node.contents = children;
   return node;
 }
 
+function getEvaluatedItem(item, evaluations) {
+  if (!evaluations.has(item.path)) {
+    return item;
+  }
+
+  return _extends({}, item, {
+    contents: evaluations.get(item.path)
+  });
+}
+
+function getChildrenWithEvaluations(options) {
+  const { item, loadedProperties, cachedNodes, evaluations } = options;
+
+  const children = getChildren({
+    loadedProperties,
+    cachedNodes,
+    item
+  });
+
+  if (Array.isArray(children)) {
+    return children.map(i => getEvaluatedItem(i, evaluations));
+  }
+
+  if (children) {
+    return getEvaluatedItem(children, evaluations);
+  }
+
+  return [];
+}
+
 function getChildren(options) {
-  const { cachedNodes, loadedProperties = new Map(), item } = options;
+  const { cachedNodes, item, loadedProperties = new Map() } = options;
 
   const key = item.path;
   if (cachedNodes && cachedNodes.has(key)) {
     return cachedNodes.get(key);
   }
 
   const loadedProps = loadedProperties.get(key);
   const hasLoadedProps = loadedProperties.has(key);
@@ -3640,36 +2371,54 @@ function getClosestNonBucketNode(item) {
   const parent = getParent(item);
   if (!parent) {
     return null;
   }
 
   return getClosestNonBucketNode(parent);
 }
 
+function getParentGripValue(item) {
+  const parentNode = getParent(item);
+  if (!parentNode) {
+    return null;
+  }
+
+  const parentGripNode = getClosestGripNode(parentNode);
+  if (!parentGripNode) {
+    return null;
+  }
+
+  return getValue(parentGripNode);
+}
+
 module.exports = {
   createNode,
   createGetterNode,
   createSetterNode,
   getActor,
   getChildren,
+  getChildrenWithEvaluations,
   getClosestGripNode,
   getClosestNonBucketNode,
   getParent,
+  getParentGripValue,
   getNumericalPropertiesCount,
   getValue,
   makeNodesForEntries,
   makeNodesForPromiseProperties,
   makeNodesForProperties,
   makeNumericalBuckets,
   nodeHasAccessors,
   nodeHasAllEntriesInPreview,
   nodeHasChildren,
   nodeHasEntries,
   nodeHasProperties,
+  nodeHasGetter,
+  nodeHasSetter,
   nodeIsBlock,
   nodeIsBucket,
   nodeIsDefaultProperties,
   nodeIsEntries,
   nodeIsError,
   nodeIsLongString,
   nodeHasFullText,
   nodeIsFunction,
@@ -3691,65 +2440,1208 @@ module.exports = {
   nodeSupportsNumericalBucketing,
   setNodeChildren,
   sortProperties,
   NODE_TYPES
 };
 
 /***/ }),
 
-/***/ 3669:
+/***/ 1786:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
-var _tree = __webpack_require__(3670);
+var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+function initialState() {
+  return {
+    expandedPaths: new Set(),
+    loadedProperties: new Map(),
+    evaluations: new Map(),
+    actors: new Set()
+  };
+} /* 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 reducer(state = initialState(), action = {}) {
+  const { type, data } = action;
+
+  const cloneState = overrides => _extends({}, state, overrides);
+
+  if (type === "NODE_EXPAND") {
+    return cloneState({
+      expandedPaths: new Set(state.expandedPaths).add(data.node.path)
+    });
+  }
+
+  if (type === "NODE_COLLAPSE") {
+    const expandedPaths = new Set(state.expandedPaths);
+    expandedPaths.delete(data.node.path);
+    return cloneState({ expandedPaths });
+  }
+
+  if (type === "NODE_PROPERTIES_LOADED") {
+    return cloneState({
+      actors: data.actor ? new Set(state.actors || []).add(data.actor) : state.actors,
+      loadedProperties: new Map(state.loadedProperties).set(data.node.path, action.data.properties)
+    });
+  }
+
+  if (type === "ROOTS_CHANGED") {
+    return cloneState();
+  }
+
+  if (type === "GETTER_INVOKED") {
+    return cloneState({
+      actors: data.actor ? new Set(state.actors || []).add(data.result.from) : state.actors,
+      evaluations: new Map(state.evaluations).set(data.node.path, {
+        getterValue: data.result && data.result.value && (data.result.value.return || data.result.value.throw)
+      })
+    });
+  }
+
+  return state;
+}
+
+function getObjectInspectorState(state) {
+  return state.objectInspector;
+}
+
+function getExpandedPaths(state) {
+  return getObjectInspectorState(state).expandedPaths;
+}
+
+function getExpandedPathKeys(state) {
+  return [...getExpandedPaths(state).keys()];
+}
+
+function getActors(state) {
+  return getObjectInspectorState(state).actors;
+}
+
+function getLoadedProperties(state) {
+  return getObjectInspectorState(state).loadedProperties;
+}
+
+function getLoadedPropertyKeys(state) {
+  return [...getLoadedProperties(state).keys()];
+}
+
+function getEvaluations(state) {
+  return getObjectInspectorState(state).evaluations;
+}
+
+const selectors = {
+  getActors,
+  getEvaluations,
+  getExpandedPathKeys,
+  getExpandedPaths,
+  getLoadedProperties,
+  getLoadedPropertyKeys
+};
+
+Object.defineProperty(module.exports, "__esModule", {
+  value: true
+});
+module.exports = selectors;
+module.exports.default = reducer;
+
+/***/ }),
+
+/***/ 1787:
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+/* 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 client = __webpack_require__(1805);
+const loadProperties = __webpack_require__(1804);
+const node = __webpack_require__(1785);
+const { nodeIsError, nodeIsPrimitive } = node;
+const selection = __webpack_require__(1860);
+
+const { MODE } = __webpack_require__(1762);
+const {
+  REPS: { Rep, Grip }
+} = __webpack_require__(1767);
+
+
+function shouldRenderRootsInReps(roots) {
+  if (roots.length > 1) {
+    return false;
+  }
+
+  const root = roots[0];
+  const name = root && root.name;
+  return (name === null || typeof name === "undefined") && (nodeIsPrimitive(root) || nodeIsError(root));
+}
+
+function renderRep(item, props) {
+  return Rep(_extends({}, props, {
+    object: node.getValue(item),
+    mode: props.mode || MODE.TINY,
+    defaultRep: Grip
+  }));
+}
+
+module.exports = {
+  client,
+  loadProperties,
+  node,
+  renderRep,
+  selection,
+  shouldRenderRootsInReps
+};
+
+/***/ }),
+
+/***/ 1792:
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+var _tree = __webpack_require__(1802);
 
 var _tree2 = _interopRequireDefault(_tree);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 module.exports = {
   Tree: _tree2.default
 }; /* 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/. */
 
 /***/ }),
 
-/***/ 3670:
+/***/ 1795:
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+/* 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/>. */
+
+// ReactJS
+const PropTypes = __webpack_require__(1758);
+
+// Reps
+const { getGripType, isGrip, cropString, wrapRender } = __webpack_require__(1760);
+const { MODE } = __webpack_require__(1762);
+
+const dom = __webpack_require__(1759);
+const { span } = dom;
+
+const IGNORED_SOURCE_URLS = ["debugger eval code"];
+
+/**
+ * This component represents a template for Function objects.
+ */
+FunctionRep.propTypes = {
+  object: PropTypes.object.isRequired,
+  parameterNames: PropTypes.array,
+  onViewSourceInDebugger: PropTypes.func
+};
+
+function FunctionRep(props) {
+  const { object: grip, onViewSourceInDebugger, recordTelemetryEvent } = props;
+
+  let jumpToDefinitionButton;
+  if (onViewSourceInDebugger && grip.location && grip.location.url && !IGNORED_SOURCE_URLS.includes(grip.location.url)) {
+    jumpToDefinitionButton = dom.button({
+      className: "jump-definition",
+      draggable: false,
+      title: "Jump to definition",
+      onClick: e => {
+        // Stop the event propagation so we don't trigger ObjectInspector
+        // expand/collapse.
+        e.stopPropagation();
+        if (recordTelemetryEvent) {
+          recordTelemetryEvent("jump_to_definition");
+        }
+        onViewSourceInDebugger(grip.location);
+      }
+    });
+  }
+
+  return span({
+    "data-link-actor-id": grip.actor,
+    className: "objectBox objectBox-function",
+    // Set dir="ltr" to prevent function parentheses from
+    // appearing in the wrong direction
+    dir: "ltr"
+  }, getTitle(grip, props), getFunctionName(grip, props), "(", ...renderParams(props), ")", jumpToDefinitionButton);
+}
+
+function getTitle(grip, props) {
+  const { mode } = props;
+
+  if (mode === MODE.TINY && !grip.isGenerator && !grip.isAsync) {
+    return null;
+  }
+
+  let title = mode === MODE.TINY ? "" : "function ";
+
+  if (grip.isGenerator) {
+    title = mode === MODE.TINY ? "* " : "function* ";
+  }
+
+  if (grip.isAsync) {
+    title = `${"async" + " "}${title}`;
+  }
+
+  return span({
+    className: "objectTitle"
+  }, title);
+}
+
+/**
+ * Returns a ReactElement representing the function name.
+ *
+ * @param {Object} grip : Function grip
+ * @param {Object} props: Function rep props
+ */
+function getFunctionName(grip, props = {}) {
+  let { functionName } = props;
+  let name;
+
+  if (functionName) {
+    const end = functionName.length - 1;
+    functionName = functionName.startsWith('"') && functionName.endsWith('"') ? functionName.substring(1, end) : functionName;
+  }
+
+  if (grip.displayName != undefined && functionName != undefined && grip.displayName != functionName) {
+    name = `${functionName}:${grip.displayName}`;
+  } else {
+    name = cleanFunctionName(grip.userDisplayName || grip.displayName || grip.name || props.functionName || "");
+  }
+
+  return cropString(name, 100);
+}
+
+const objectProperty = /([\w\d\$]+)$/;
+const arrayProperty = /\[(.*?)\]$/;
+const functionProperty = /([\w\d]+)[\/\.<]*?$/;
+const annonymousProperty = /([\w\d]+)\(\^\)$/;
+
+/**
+ * Decodes an anonymous naming scheme that
+ * spider monkey implements based on "Naming Anonymous JavaScript Functions"
+ * http://johnjbarton.github.io/nonymous/index.html
+ *
+ * @param {String} name : Function name to clean up
+ * @returns String
+ */
+function cleanFunctionName(name) {
+  for (const reg of [objectProperty, arrayProperty, functionProperty, annonymousProperty]) {
+    const match = reg.exec(name);
+    if (match) {
+      return match[1];
+    }
+  }
+
+  return name;
+}
+
+function renderParams(props) {
+  const { parameterNames = [] } = props;
+
+  return parameterNames.filter(param => param).reduce((res, param, index, arr) => {
+    res.push(span({ className: "param" }, param));
+    if (index < arr.length - 1) {
+      res.push(span({ className: "delimiter" }, ", "));
+    }
+    return res;
+  }, []);
+}
+
+// Registration
+function supportsObject(grip, noGrip = false) {
+  const type = getGripType(grip, noGrip);
+  if (noGrip === true || !isGrip(grip)) {
+    return type == "function";
+  }
+
+  return type == "Function";
+}
+
+// Exports from this module
+
+module.exports = {
+  rep: wrapRender(FunctionRep),
+  supportsObject,
+  cleanFunctionName,
+  // exported for testing purpose.
+  getFunctionName
+};
+
+/***/ }),
+
+/***/ 1796:
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+/* 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/>. */
+
+module.exports = {
+  ELEMENT_NODE: 1,
+  ATTRIBUTE_NODE: 2,
+  TEXT_NODE: 3,
+  CDATA_SECTION_NODE: 4,
+  ENTITY_REFERENCE_NODE: 5,
+  ENTITY_NODE: 6,
+  PROCESSING_INSTRUCTION_NODE: 7,
+  COMMENT_NODE: 8,
+  DOCUMENT_NODE: 9,
+  DOCUMENT_TYPE_NODE: 10,
+  DOCUMENT_FRAGMENT_NODE: 11,
+  NOTATION_NODE: 12,
+
+  // DocumentPosition
+  DOCUMENT_POSITION_DISCONNECTED: 0x01,
+  DOCUMENT_POSITION_PRECEDING: 0x02,
+  DOCUMENT_POSITION_FOLLOWING: 0x04,
+  DOCUMENT_POSITION_CONTAINS: 0x08,
+  DOCUMENT_POSITION_CONTAINED_BY: 0x10,
+  DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC: 0x20
+};
+
+/***/ }),
+
+/***/ 1797:
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+/* 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/>. */
+
+// ReactJS
+const PropTypes = __webpack_require__(1758);
+// Utils
+const { getGripType, isGrip, wrapRender } = __webpack_require__(1760);
+const { cleanFunctionName } = __webpack_require__(1795);
+const { isLongString } = __webpack_require__(1770);
+const { MODE } = __webpack_require__(1762);
+
+const dom = __webpack_require__(1759);
+const { span } = dom;
+const IGNORED_SOURCE_URLS = ["debugger eval code"];
+
+/**
+ * Renders Error objects.
+ */
+ErrorRep.propTypes = {
+  object: PropTypes.object.isRequired,
+  // @TODO Change this to Object.values when supported in Node's version of V8
+  mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
+  // An optional function that will be used to render the Error stacktrace.
+  renderStacktrace: PropTypes.func
+};
+
+function ErrorRep(props) {
+  const object = props.object;
+  const preview = object.preview;
+
+  let name;
+  if (preview && preview.name && preview.kind) {
+    switch (preview.kind) {
+      case "Error":
+        name = preview.name;
+        break;
+      case "DOMException":
+        name = preview.kind;
+        break;
+      default:
+        throw new Error("Unknown preview kind for the Error rep.");
+    }
+  } else {
+    name = "Error";
+  }
+
+  const content = [];
+
+  if (props.mode === MODE.TINY) {
+    content.push(name);
+  } else {
+    content.push(`${name}: "${preview.message}"`);
+  }
+
+  if (preview.stack && props.mode !== MODE.TINY) {
+    const stacktrace = props.renderStacktrace ? props.renderStacktrace(parseStackString(preview.stack)) : getStacktraceElements(props, preview);
+    content.push("\n", stacktrace);
+  }
+
+  return span({
+    "data-link-actor-id": object.actor,
+    className: "objectBox-stackTrace"
+  }, content);
+}
+
+/**
+ * Returns a React element reprensenting the Error stacktrace, i.e.
+ * transform error.stack from:
+ *
+ * semicolon@debugger eval code:1:109
+ * jkl@debugger eval code:1:63
+ * asdf@debugger eval code:1:28
+ * @debugger eval code:1:227
+ *
+ * Into a column layout:
+ *
+ * semicolon  (<anonymous>:8:10)
+ * jkl        (<anonymous>:5:10)
+ * asdf       (<anonymous>:2:10)
+ *            (<anonymous>:11:1)
+ */
+function getStacktraceElements(props, preview) {
+  const stack = [];
+  if (!preview.stack) {
+    return stack;
+  }
+
+  parseStackString(preview.stack).forEach((frame, index, frames) => {
+    let onLocationClick;
+    const {
+      filename,
+      lineNumber,
+      columnNumber,
+      functionName,
+      location
+    } = frame;
+
+    if (props.onViewSourceInDebugger && !IGNORED_SOURCE_URLS.includes(filename)) {
+      onLocationClick = e => {
+        // Don't trigger ObjectInspector expand/collapse.
+        e.stopPropagation();
+        props.onViewSourceInDebugger({
+          url: filename,
+          line: lineNumber,
+          column: columnNumber
+        });
+      };
+    }
+
+    stack.push("\t", span({
+      key: `fn${index}`,
+      className: "objectBox-stackTrace-fn"
+    }, cleanFunctionName(functionName)), " ", span({
+      key: `location${index}`,
+      className: "objectBox-stackTrace-location",
+      onClick: onLocationClick,
+      title: onLocationClick ? `View source in debugger → ${location}` : undefined
+    }, location), "\n");
+  });
+
+  return span({
+    key: "stack",
+    className: "objectBox-stackTrace-grid"
+  }, stack);
+}
+
+/**
+ * Parse a string that should represent a stack trace and returns an array of
+ * the frames. The shape of the frames are extremely important as they can then
+ * be processed here or in the toolbox by other components.
+ * @param {String} stack
+ * @returns {Array} Array of frames, which are object with the following shape:
+ *                  - {String} filename
+ *                  - {String} functionName
+ *                  - {String} location
+ *                  - {Number} columnNumber
+ *                  - {Number} lineNumber
+ */
+function parseStackString(stack) {
+  const res = [];
+  if (!stack) {
+    return res;
+  }
+
+  const isStacktraceALongString = isLongString(stack);
+  const stackString = isStacktraceALongString ? stack.initial : stack;
+
+  stackString.split("\n").forEach((frame, index, frames) => {
+    if (!frame) {
+      // Skip any blank lines
+      return;
+    }
+
+    // If the stacktrace is a longString, don't include the last frame in the
+    // array, since it is certainly incomplete.
+    // Can be removed when https://bugzilla.mozilla.org/show_bug.cgi?id=1448833
+    // is fixed.
+    if (isStacktraceALongString && index === frames.length - 1) {
+      return;
+    }
+
+    let functionName;
+    let location;
+
+    // Given the input: "functionName@scriptLocation:2:100"
+    // Result: [
+    //   "functionName@scriptLocation:2:100",
+    //   "functionName",
+    //   "scriptLocation:2:100"
+    // ]
+    const result = frame.match(/^(.*)@(.*)$/);
+    if (result && result.length === 3) {
+      functionName = result[1];
+
+      // If the resource was loaded by base-loader.js, the location looks like:
+      // resource://devtools/shared/base-loader.js -> resource://path/to/file.js .
+      // What's needed is only the last part after " -> ".
+      location = result[2].split(" -> ").pop();
+    }
+
+    if (!functionName) {
+      functionName = "<anonymous>";
+    }
+
+    // Given the input: "scriptLocation:2:100"
+    // Result:
+    // ["scriptLocation:2:100", "scriptLocation", "2", "100"]
+    const locationParts = location.match(/^(.*):(\d+):(\d+)$/);
+
+    if (location && locationParts) {
+      const [, filename, line, column] = locationParts;
+      res.push({
+        filename,
+        functionName,
+        location,
+        columnNumber: Number(column),
+        lineNumber: Number(line)
+      });
+    }
+  });
+
+  return res;
+}
+
+// Registration
+function supportsObject(object, noGrip = false) {
+  if (noGrip === true || !isGrip(object)) {
+    return false;
+  }
+  return object.preview && getGripType(object, noGrip) === "Error" || object.class === "DOMException";
+}
+
+// Exports from this module
+module.exports = {
+  rep: wrapRender(ErrorRep),
+  supportsObject
+};
+
+/***/ }),
+
+/***/ 1798:
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+/* 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/>. */
+
+// Dependencies
+const PropTypes = __webpack_require__(1758);
+
+const { lengthBubble } = __webpack_require__(1799);
+const {
+  interleave,
+  getGripType,
+  isGrip,
+  wrapRender,
+  ellipsisElement
+} = __webpack_require__(1760);
+const { MODE } = __webpack_require__(1762);
+
+const dom = __webpack_require__(1759);
+const { span } = dom;
+const { ModePropType } = __webpack_require__(1774);
+const DEFAULT_TITLE = "Array";
+
+/**
+ * Renders an array. The array is enclosed by left and right bracket
+ * and the max number of rendered items depends on the current mode.
+ */
+GripArray.propTypes = {
+  object: PropTypes.object.isRequired,
+  // @TODO Change this to Object.values when supported in Node's version of V8
+  mode: ModePropType,
+  provider: PropTypes.object,
+  onDOMNodeMouseOver: PropTypes.func,
+  onDOMNodeMouseOut: PropTypes.func,
+  onInspectIconClick: PropTypes.func
+};
+
+function GripArray(props) {
+  const { object, mode = MODE.SHORT } = props;
+
+  let brackets;
+  const needSpace = function (space) {
+    return space ? { left: "[ ", right: " ]" } : { left: "[", right: "]" };
+  };
+
+  const config = {
+    "data-link-actor-id": object.actor,
+    className: "objectBox objectBox-array"
+  };
+
+  const title = getTitle(props, object);
+
+  if (mode === MODE.TINY) {
+    const isEmpty = getLength(object) === 0;
+
+    // Omit bracketed ellipsis for non-empty non-Array arraylikes (f.e: Sets).
+    if (!isEmpty && object.class !== "Array") {
+      return span(config, title);
+    }
+
+    brackets = needSpace(false);
+    return span(config, title, span({
+      className: "arrayLeftBracket"
+    }, brackets.left), isEmpty ? null : ellipsisElement, span({
+      className: "arrayRightBracket"
+    }, brackets.right));
+  }
+
+  const max = maxLengthMap.get(mode);
+  const items = arrayIterator(props, object, max);
+  brackets = needSpace(items.length > 0);
+
+  return span({
+    "data-link-actor-id": object.actor,
+    className: "objectBox objectBox-array"
+  }, title, span({
+    className: "arrayLeftBracket"
+  }, brackets.left), ...interleave(items, ", "), span({
+    className: "arrayRightBracket"
+  }, brackets.right), span({
+    className: "arrayProperties",
+    role: "group"
+  }));
+}
+
+function getLength(grip) {
+  if (!grip.preview) {
+    return 0;
+  }
+
+  return grip.preview.length || grip.preview.childNodesLength || 0;
+}
+
+function getTitle(props, object) {
+  const objectLength = getLength(object);
+  const isEmpty = objectLength === 0;
+
+  let title = props.title || object.class || DEFAULT_TITLE;
+
+  const length = lengthBubble({
+    object,
+    mode: props.mode,
+    maxLengthMap,
+    getLength
+  });
+
+  if (props.mode === MODE.TINY) {
+    if (isEmpty) {
+      if (object.class === DEFAULT_TITLE) {
+        return null;
+      }
+
+      return span({ className: "objectTitle" }, `${title} `);
+    }
+
+    let trailingSpace;
+    if (object.class === DEFAULT_TITLE) {
+      title = null;
+      trailingSpace = " ";
+    }
+
+    return span({ className: "objectTitle" }, title, length, trailingSpace);
+  }
+
+  return span({ className: "objectTitle" }, title, length, " ");
+}
+
+function getPreviewItems(grip) {
+  if (!grip.preview) {
+    return null;
+  }
+
+  return grip.preview.items || grip.preview.childNodes || [];
+}
+
+function arrayIterator(props, grip, max) {
+  const { Rep } = __webpack_require__(1767);
+
+  let items = [];
+  const gripLength = getLength(grip);
+
+  if (!gripLength) {
+    return items;
+  }
+
+  const previewItems = getPreviewItems(grip);
+  const provider = props.provider;
+
+  let emptySlots = 0;
+  let foldedEmptySlots = 0;
+  items = previewItems.reduce((res, itemGrip) => {
+    if (res.length >= max) {
+      return res;
+    }
+
+    let object;
+    try {
+      if (!provider && itemGrip === null) {
+        emptySlots++;
+        return res;
+      }
+
+      object = provider ? provider.getValue(itemGrip) : itemGrip;
+    } catch (exc) {
+      object = exc;
+    }
+
+    if (emptySlots > 0) {
+      res.push(getEmptySlotsElement(emptySlots));
+      foldedEmptySlots = foldedEmptySlots + emptySlots - 1;
+      emptySlots = 0;
+    }
+
+    if (res.length < max) {
+      res.push(Rep(_extends({}, props, {
+        object,
+        mode: MODE.TINY,
+        // Do not propagate title to array items reps
+        title: undefined
+      })));
+    }
+
+    return res;
+  }, []);
+
+  // Handle trailing empty slots if there are some.
+  if (items.length < max && emptySlots > 0) {
+    items.push(getEmptySlotsElement(emptySlots));
+    foldedEmptySlots = foldedEmptySlots + emptySlots - 1;
+  }
+
+  const itemsShown = items.length + foldedEmptySlots;
+  if (gripLength > itemsShown) {
+    items.push(ellipsisElement);
+  }
+
+  return items;
+}
+
+function getEmptySlotsElement(number) {
+  // TODO: Use l10N - See https://github.com/devtools-html/reps/issues/141
+  return `<${number} empty slot${number > 1 ? "s" : ""}>`;
+}
+
+function supportsObject(grip, noGrip = false) {
+  if (noGrip === true || !isGrip(grip)) {
+    return false;
+  }
+
+  return grip.preview && (grip.preview.kind == "ArrayLike" || getGripType(grip, noGrip) === "DocumentFragment");
+}
+
+const maxLengthMap = new Map();
+maxLengthMap.set(MODE.SHORT, 3);
+maxLengthMap.set(MODE.LONG, 10);
+
+// Exports from this module
+module.exports = {
+  rep: wrapRender(GripArray),
+  supportsObject,
+  maxLengthMap,
+  getLength
+};
+
+/***/ }),
+
+/***/ 1799:
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+/* 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 PropTypes = __webpack_require__(1758);
+
+const { wrapRender } = __webpack_require__(1760);
+const { MODE } = __webpack_require__(1762);
+const { ModePropType } = __webpack_require__(1774);
+
+const dom = __webpack_require__(1759);
+const { span } = dom;
+
+GripLengthBubble.propTypes = {
+  object: PropTypes.object.isRequired,
+  maxLengthMap: PropTypes.instanceOf(Map).isRequired,
+  getLength: PropTypes.func.isRequired,
+  mode: ModePropType,
+  visibilityThreshold: PropTypes.number
+};
+
+function GripLengthBubble(props) {
+  const {
+    object,
+    mode = MODE.SHORT,
+    visibilityThreshold = 2,
+    maxLengthMap,
+    getLength,
+    showZeroLength = false
+  } = props;
+
+  const length = getLength(object);
+  const isEmpty = length === 0;
+  const isObvious = [MODE.SHORT, MODE.LONG].includes(mode) && length > 0 && length <= maxLengthMap.get(mode) && length <= visibilityThreshold;
+  if (isEmpty && !showZeroLength || isObvious) {
+    return "";
+  }
+
+  return span({
+    className: "objectLengthBubble"
+  }, `(${length})`);
+}
+
+module.exports = {
+  lengthBubble: wrapRender(GripLengthBubble)
+};
+
+/***/ }),
+
+/***/ 1800:
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+/* 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/>. */
+
+// Dependencies
+
+const { lengthBubble } = __webpack_require__(1799);
+const PropTypes = __webpack_require__(1758);
+const {
+  interleave,
+  isGrip,
+  wrapRender,
+  ellipsisElement
+} = __webpack_require__(1760);
+const PropRep = __webpack_require__(1775);
+const { MODE } = __webpack_require__(1762);
+const { ModePropType } = __webpack_require__(1774);
+
+const { span } = __webpack_require__(1759);
+
+/**
+ * Renders an map. A map is represented by a list of its
+ * entries enclosed in curly brackets.
+ */
+GripMap.propTypes = {
+  object: PropTypes.object,
+  // @TODO Change this to Object.values when supported in Node's version of V8
+  mode: ModePropType,
+  isInterestingEntry: PropTypes.func,
+  onDOMNodeMouseOver: PropTypes.func,
+  onDOMNodeMouseOut: PropTypes.func,
+  onInspectIconClick: PropTypes.func,
+  title: PropTypes.string
+};
+
+function GripMap(props) {
+  const { mode, object } = props;
+
+  const config = {
+    "data-link-actor-id": object.actor,
+    className: "objectBox objectBox-object"
+  };
+
+  const title = getTitle(props, object);
+  const isEmpty = getLength(object) === 0;
+
+  if (isEmpty || mode === MODE.TINY) {
+    return span(config, title);
+  }
+
+  const propsArray = safeEntriesIterator(props, object, maxLengthMap.get(mode));
+
+  return span(config, title, span({
+    className: "objectLeftBrace"
+  }, " { "), ...interleave(propsArray, ", "), span({
+    className: "objectRightBrace"
+  }, " }"));
+}
+
+function getTitle(props, object) {
+  const title = props.title || (object && object.class ? object.class : "Map");
+  return span({
+    className: "objectTitle"
+  }, title, lengthBubble({
+    object,
+    mode: props.mode,
+    maxLengthMap,
+    getLength,
+    showZeroLength: true
+  }));
+}
+
+function safeEntriesIterator(props, object, max) {
+  max = typeof max === "undefined" ? 3 : max;
+  try {
+    return entriesIterator(props, object, max);
+  } catch (err) {
+    console.error(err);
+  }
+  return [];
+}
+
+function entriesIterator(props, object, max) {
+  // Entry filter. Show only interesting entries to the user.
+  const isInterestingEntry = props.isInterestingEntry || ((type, value) => {
+    return type == "boolean" || type == "number" || type == "string" && value.length != 0;
+  });
+
+  const mapEntries = object.preview && object.preview.entries ? object.preview.entries : [];
+
+  let indexes = getEntriesIndexes(mapEntries, max, isInterestingEntry);
+  if (indexes.length < max && indexes.length < mapEntries.length) {
+    // There are not enough entries yet, so we add uninteresting entries.
+    indexes = indexes.concat(getEntriesIndexes(mapEntries, max - indexes.length, (t, value, name) => {
+      return !isInterestingEntry(t, value, name);
+    }));
+  }
+
+  const entries = getEntries(props, mapEntries, indexes);
+  if (entries.length < getLength(object)) {
+    // There are some undisplayed entries. Then display "…".
+    entries.push(ellipsisElement);
+  }
+
+  return entries;
+}
+
+/**
+ * Get entries ordered by index.
+ *
+ * @param {Object} props Component props.
+ * @param {Array} entries Entries array.
+ * @param {Array} indexes Indexes of entries.
+ * @return {Array} Array of PropRep.
+ */
+function getEntries(props, entries, indexes) {
+  const { onDOMNodeMouseOver, onDOMNodeMouseOut, onInspectIconClick } = props;
+
+  // Make indexes ordered by ascending.
+  indexes.sort(function (a, b) {
+    return a - b;
+  });
+
+  return indexes.map((index, i) => {
+    const [key, entryValue] = entries[index];
+    const value = entryValue.value !== undefined ? entryValue.value : entryValue;
+
+    return PropRep({
+      name: key,
+      equal: " \u2192 ",
+      object: value,
+      mode: MODE.TINY,
+      onDOMNodeMouseOver,
+      onDOMNodeMouseOut,
+      onInspectIconClick
+    });
+  });
+}
+
+/**
+ * Get the indexes of entries in the map.
+ *
+ * @param {Array} entries Entries array.
+ * @param {Number} max The maximum length of indexes array.
+ * @param {Function} filter Filter the entry you want.
+ * @return {Array} Indexes of filtered entries in the map.
+ */
+function getEntriesIndexes(entries, max, filter) {
+  return entries.reduce((indexes, [key, entry], i) => {
+    if (indexes.length < max) {
+      const value = entry && entry.value !== undefined ? entry.value : entry;
+      // Type is specified in grip's "class" field and for primitive
+      // values use typeof.
+      const type = (value && value.class ? value.class : typeof value).toLowerCase();
+
+      if (filter(type, value, key)) {
+        indexes.push(i);
+      }
+    }
+
+    return indexes;
+  }, []);
+}
+
+function getLength(grip) {
+  return grip.preview.size || 0;
+}
+
+function supportsObject(grip, noGrip = false) {
+  if (noGrip === true || !isGrip(grip)) {
+    return false;
+  }
+  return grip.preview && grip.preview.kind == "MapLike";
+}
+
+const maxLengthMap = new Map();
+maxLengthMap.set(MODE.SHORT, 3);
+maxLengthMap.set(MODE.LONG, 10);
+
+// Exports from this module
+module.exports = {
+  rep: wrapRender(GripMap),
+  supportsObject,
+  maxLengthMap,
+  getLength
+};
+
+/***/ }),
+
+/***/ 1801:
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+/* 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/>. */
+
+// Dependencies
+const PropTypes = __webpack_require__(1758);
+// Shortcuts
+const dom = __webpack_require__(1759);
+const { span } = dom;
+const { wrapRender } = __webpack_require__(1760);
+const PropRep = __webpack_require__(1775);
+const { MODE } = __webpack_require__(1762);
+/**
+ * Renders an map entry. A map entry is represented by its key,
+ * a column and its value.
+ */
+GripMapEntry.propTypes = {
+  object: PropTypes.object,
+  // @TODO Change this to Object.values when supported in Node's version of V8
+  mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
+  onDOMNodeMouseOver: PropTypes.func,
+  onDOMNodeMouseOut: PropTypes.func,
+  onInspectIconClick: PropTypes.func
+};
+
+function GripMapEntry(props) {
+  const { object } = props;
+
+  const { key, value } = object.preview;
+
+  return span({
+    className: "objectBox objectBox-map-entry"
+  }, PropRep(_extends({}, props, {
+    name: key,
+    object: value,
+    equal: " \u2192 ",
+    title: null,
+    suppressQuotes: false
+  })));
+}
+
+function supportsObject(grip, noGrip = false) {
+  if (noGrip === true) {
+    return false;
+  }
+  return grip && (grip.type === "mapEntry" || grip.type === "storageEntry") && grip.preview;
+}
+
+function createGripMapEntry(key, value) {
+  return {
+    type: "mapEntry",
+    preview: {
+      key,
+      value
+    }
+  };
+}
+
+// Exports from this module
+module.exports = {
+  rep: wrapRender(GripMapEntry),
+  createGripMapEntry,
+  supportsObject
+};
+
+/***/ }),
+
+/***/ 1802:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
 
 var _react = __webpack_require__(0);
 
 var _react2 = _interopRequireDefault(_react);
 
-var _reactDomFactories = __webpack_require__(3643);
+var _reactDomFactories = __webpack_require__(1759);
 
 var _reactDomFactories2 = _interopRequireDefault(_reactDomFactories);
 
-var _propTypes = __webpack_require__(3642);
+var _propTypes = __webpack_require__(1758);
 
 var _propTypes2 = _interopRequireDefault(_propTypes);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 const { Component, createFactory } = _react2.default; /* 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/>. */
 
-__webpack_require__(3671);
+__webpack_require__(1803);
 
 // depth
 const AUTO_EXPAND_DEPTH = 0;
 
 /**
  * An arrow that displays whether its node is expanded (▼) or collapsed
  * (▶). When its node has no children, it is hidden.
  */
@@ -4537,44 +4429,308 @@ class Tree extends Component {
     }, nodes);
   }
 }
 
 exports.default = Tree;
 
 /***/ }),
 
-/***/ 3671:
+/***/ 1803:
 /***/ (function(module, exports) {
 
 // removed by extract-text-webpack-plugin
 
 /***/ }),
 
-/***/ 3672:
+/***/ 1804:
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+/* 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 {
+  enumEntries,
+  enumIndexedProperties,
+  enumNonIndexedProperties,
+  getPrototype,
+  enumSymbols,
+  getFullText
+} = __webpack_require__(1805);
+
+const {
+  getClosestGripNode,
+  getClosestNonBucketNode,
+  getValue,
+  nodeHasAccessors,
+  nodeHasAllEntriesInPreview,
+  nodeHasProperties,
+  nodeIsBucket,
+  nodeIsDefaultProperties,
+  nodeIsEntries,
+  nodeIsMapEntry,
+  nodeIsPrimitive,
+  nodeIsProxy,
+  nodeNeedsNumericalBuckets,
+  nodeIsLongString
+} = __webpack_require__(1785);
+
+function loadItemProperties(item, createObjectClient, createLongStringClient, loadedProperties) {
+  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 || createObjectClient(value);
+
+  if (shouldLoadItemIndexedProperties(item, loadedProperties)) {
+    promises.push(enumIndexedProperties(getObjectClient(), start, end));
+  }
+
+  if (shouldLoadItemNonIndexedProperties(item, loadedProperties)) {
+    promises.push(enumNonIndexedProperties(getObjectClient(), start, end));
+  }
+
+  if (shouldLoadItemEntries(item, loadedProperties)) {
+    promises.push(enumEntries(getObjectClient(), start, end));
+  }
+
+  if (shouldLoadItemPrototype(item, loadedProperties)) {
+    promises.push(getPrototype(getObjectClient()));
+  }
+
+  if (shouldLoadItemSymbols(item, loadedProperties)) {
+    promises.push(enumSymbols(getObjectClient(), start, end));
+  }
+
+  if (shouldLoadItemFullText(item, loadedProperties)) {
+    promises.push(getFullText(createLongStringClient(value), item));
+  }
+
+  return Promise.all(promises).then(mergeResponses);
+}
+
+function mergeResponses(responses) {
+  const data = {};
+
+  for (const response of responses) {
+    if (response.hasOwnProperty("ownProperties")) {
+      data.ownProperties = _extends({}, data.ownProperties, response.ownProperties);
+    }
+
+    if (response.ownSymbols && response.ownSymbols.length > 0) {
+      data.ownSymbols = response.ownSymbols;
+    }
+
+    if (response.prototype) {
+      data.prototype = response.prototype;
+    }
+
+    if (response.fullText) {
+      data.fullText = response.fullText;
+    }
+  }
+
+  return data;
+}
+
+function shouldLoadItemIndexedProperties(item, loadedProperties = new Map()) {
+  const gripItem = getClosestGripNode(item);
+  const value = getValue(gripItem);
+
+  return value && nodeHasProperties(gripItem) && !loadedProperties.has(item.path) && !nodeIsProxy(item) && !nodeNeedsNumericalBuckets(item) && !nodeIsEntries(getClosestNonBucketNode(item)) &&
+  // The data is loaded when expanding the window node.
+  !nodeIsDefaultProperties(item);
+}
+
+function shouldLoadItemNonIndexedProperties(item, loadedProperties = new Map()) {
+  const gripItem = getClosestGripNode(item);
+  const value = getValue(gripItem);
+
+  return value && nodeHasProperties(gripItem) && !loadedProperties.has(item.path) && !nodeIsProxy(item) && !nodeIsEntries(getClosestNonBucketNode(item)) && !nodeIsBucket(item) &&
+  // The data is loaded when expanding the window node.
+  !nodeIsDefaultProperties(item);
+}
+
+function shouldLoadItemEntries(item, loadedProperties = new Map()) {
+  const gripItem = getClosestGripNode(item);
+  const value = getValue(gripItem);
+
+  return value && nodeIsEntries(getClosestNonBucketNode(item)) && !nodeHasAllEntriesInPreview(gripItem) && !loadedProperties.has(item.path) && !nodeNeedsNumericalBuckets(item);
+}
+
+function shouldLoadItemPrototype(item, loadedProperties = new Map()) {
+  const value = getValue(item);
+
+  return value && !loadedProperties.has(item.path) && !nodeIsBucket(item) && !nodeIsMapEntry(item) && !nodeIsEntries(item) && !nodeIsDefaultProperties(item) && !nodeHasAccessors(item) && !nodeIsPrimitive(item) && !nodeIsLongString(item);
+}
+
+function shouldLoadItemSymbols(item, loadedProperties = new Map()) {
+  const value = getValue(item);
+
+  return value && !loadedProperties.has(item.path) && !nodeIsBucket(item) && !nodeIsMapEntry(item) && !nodeIsEntries(item) && !nodeIsDefaultProperties(item) && !nodeHasAccessors(item) && !nodeIsPrimitive(item) && !nodeIsLongString(item) && !nodeIsProxy(item);
+}
+
+function shouldLoadItemFullText(item, loadedProperties = new Map()) {
+  return !loadedProperties.has(item.path) && nodeIsLongString(item);
+}
+
+module.exports = {
+  loadItemProperties,
+  mergeResponses,
+  shouldLoadItemEntries,
+  shouldLoadItemIndexedProperties,
+  shouldLoadItemNonIndexedProperties,
+  shouldLoadItemPrototype,
+  shouldLoadItemSymbols,
+  shouldLoadItemFullText
+};
+
+/***/ }),
+
+/***/ 1805:
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+const { getValue, nodeHasFullText } = __webpack_require__(1785); /* 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/>. */
+
+async function enumIndexedProperties(objectClient, start, end) {
+  try {
+    const { iterator } = await objectClient.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) {
+  try {
+    const { iterator } = await objectClient.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) {
+  try {
+    const { iterator } = await objectClient.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) {
+  try {
+    const { iterator } = await objectClient.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");
+    return Promise.resolve({});
+  }
+  return objectClient.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
+  // loadedProperties map.
+  if (nodeHasFullText(item)) {
+    return Promise.resolve({ fullText });
+  }
+
+  return new Promise((resolve, reject) => {
+    longStringClient.substring(initial.length, length, response => {
+      if (response.error) {
+        console.error("LongStringClient.substring", `${response.error}: ${response.message}`);
+        reject({});
+        return;
+      }
+
+      resolve({
+        fullText: initial + response.substring
+      });
+    });
+  });
+}
+
+function iteratorSlice(iterator, start, end) {
+  start = start || 0;
+  const count = end ? end - start + 1 : iterator.count;
+
+  if (count === 0) {
+    return Promise.resolve({});
+  }
+  return iterator.slice(start, count);
+}
+
+module.exports = {
+  enumEntries,
+  enumIndexedProperties,
+  enumNonIndexedProperties,
+  enumSymbols,
+  getPrototype,
+  getFullText
+};
+
+/***/ }),
+
+/***/ 1831:
 /***/ (function(module, exports) {
 
 // removed by extract-text-webpack-plugin
 
 /***/ }),
 
-/***/ 3673:
+/***/ 1832:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/>. */
 
 // Dependencies
-const { getGripType, wrapRender } = __webpack_require__(3644);
-
-const dom = __webpack_require__(3643);
+const { getGripType, wrapRender } = __webpack_require__(1760);
+
+const dom = __webpack_require__(1759);
 const { span } = dom;
 
 /**
  * Renders undefined value
  */
 const Undefined = function () {
   return span({ className: "objectBox objectBox-undefined" }, "undefined");
 };
@@ -4591,29 +4747,29 @@ function supportsObject(object, noGrip =
 
 module.exports = {
   rep: wrapRender(Undefined),
   supportsObject
 };
 
 /***/ }),
 
-/***/ 3674:
+/***/ 1833:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/>. */
 
 // Dependencies
-const { wrapRender } = __webpack_require__(3644);
-const dom = __webpack_require__(3643);
+const { wrapRender } = __webpack_require__(1760);
+const dom = __webpack_require__(1759);
 const { span } = dom;
 
 /**
  * Renders null value
  */
 function Null(props) {
   return span({ className: "objectBox objectBox-null" }, "null");
 }
@@ -4634,32 +4790,32 @@ function supportsObject(object, noGrip =
 
 module.exports = {
   rep: wrapRender(Null),
   supportsObject
 };
 
 /***/ }),
 
-/***/ 3675:
+/***/ 1834:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/>. */
 
 // Dependencies
-const PropTypes = __webpack_require__(3642);
-
-const { getGripType, wrapRender } = __webpack_require__(3644);
-
-const dom = __webpack_require__(3643);
+const PropTypes = __webpack_require__(1758);
+
+const { getGripType, wrapRender } = __webpack_require__(1760);
+
+const dom = __webpack_require__(1759);
 const { span } = dom;
 
 /**
  * Renders a number
  */
 Number.propTypes = {
   object: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.bool]).isRequired
 };
@@ -4684,35 +4840,35 @@ function supportsObject(object, noGrip =
 
 module.exports = {
   rep: wrapRender(Number),
   supportsObject
 };
 
 /***/ }),
 
-/***/ 3676:
+/***/ 1835:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
 
 /* 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/>. */
 
 // Dependencies
-const PropTypes = __webpack_require__(3642);
-const { wrapRender, ellipsisElement } = __webpack_require__(3644);
-const PropRep = __webpack_require__(3650);
-const { MODE } = __webpack_require__(3645);
-
-const dom = __webpack_require__(3643);
+const PropTypes = __webpack_require__(1758);
+const { wrapRender, ellipsisElement } = __webpack_require__(1760);
+const PropRep = __webpack_require__(1775);
+const { MODE } = __webpack_require__(1762);
+
+const dom = __webpack_require__(1759);
 const { span } = dom;
 
 const DEFAULT_TITLE = "Object";
 
 /**
  * Renders an object. An object is represented by a list of its
  * properties enclosed in curly brackets.
  */
@@ -4856,32 +5012,32 @@ function supportsObject(object, noGrip =
 // Exports from this module
 module.exports = {
   rep: wrapRender(ObjectRep),
   supportsObject
 };
 
 /***/ }),
 
-/***/ 3677:
+/***/ 1836:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/>. */
 
 // Dependencies
-const PropTypes = __webpack_require__(3642);
-
-const { getGripType, wrapRender } = __webpack_require__(3644);
-
-const dom = __webpack_require__(3643);
+const PropTypes = __webpack_require__(1758);
+
+const { getGripType, wrapRender } = __webpack_require__(1760);
+
+const dom = __webpack_require__(1759);
 const { span } = dom;
 
 /**
  * Renders a symbol.
  */
 SymbolRep.propTypes = {
   object: PropTypes.object.isRequired
 };
@@ -4903,32 +5059,32 @@ function supportsObject(object, noGrip =
 // Exports from this module
 module.exports = {
   rep: wrapRender(SymbolRep),
   supportsObject
 };
 
 /***/ }),
 
-/***/ 3678:
+/***/ 1837:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/>. */
 
 // Dependencies
-const PropTypes = __webpack_require__(3642);
-
-const { getGripType, wrapRender } = __webpack_require__(3644);
-
-const dom = __webpack_require__(3643);
+const PropTypes = __webpack_require__(1758);
+
+const { getGripType, wrapRender } = __webpack_require__(1760);
+
+const dom = __webpack_require__(1759);
 const { span } = dom;
 
 /**
  * Renders a Infinity object
  */
 InfinityRep.propTypes = {
   object: PropTypes.object.isRequired
 };
@@ -4947,30 +5103,30 @@ function supportsObject(object, noGrip =
 // Exports from this module
 module.exports = {
   rep: wrapRender(InfinityRep),
   supportsObject
 };
 
 /***/ }),
 
-/***/ 3679:
+/***/ 1838:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/>. */
 
 // Dependencies
-const { getGripType, wrapRender } = __webpack_require__(3644);
-
-const dom = __webpack_require__(3643);
+const { getGripType, wrapRender } = __webpack_require__(1760);
+
+const dom = __webpack_require__(1759);
 const { span } = dom;
 
 /**
  * Renders a NaN object
  */
 function NaNRep(props) {
   return span({ className: "objectBox objectBox-nan" }, "NaN");
 }
@@ -4982,49 +5138,49 @@ function supportsObject(object, noGrip =
 // Exports from this module
 module.exports = {
   rep: wrapRender(NaNRep),
   supportsObject
 };
 
 /***/ }),
 
-/***/ 3680:
+/***/ 1839:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
 
 /* 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/>. */
 
 // Dependencies
-const dom = __webpack_require__(3643);
-const PropTypes = __webpack_require__(3642);
-const { wrapRender } = __webpack_require__(3644);
-const { MODE } = __webpack_require__(3645);
+const dom = __webpack_require__(1759);
+const PropTypes = __webpack_require__(1758);
+const { wrapRender } = __webpack_require__(1760);
+const { MODE } = __webpack_require__(1762);
 const { span } = dom;
 
 /**
  * Renders an object. An object is represented by a list of its
  * properties enclosed in curly brackets.
  */
 Accessor.propTypes = {
   object: PropTypes.object.isRequired,
   mode: PropTypes.oneOf(Object.values(MODE))
 };
 
 function Accessor(props) {
   const { object, evaluation, onInvokeGetterButtonClick } = props;
 
   if (evaluation) {
-    const { Rep, Grip } = __webpack_require__(3647);
+    const { Rep, Grip } = __webpack_require__(1767);
     return span({
       className: "objectBox objectBox-accessor objectTitle"
     }, Rep(_extends({}, props, {
       object: evaluation.getterValue,
       mode: props.mode || MODE.TINY,
       defaultRep: Grip
     })));
   }
@@ -5071,34 +5227,157 @@ function supportsObject(object, noGrip =
 // Exports from this module
 module.exports = {
   rep: wrapRender(Accessor),
   supportsObject
 };
 
 /***/ }),
 
-/***/ 3681:
+/***/ 1840:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/>. */
 
 // ReactJS
-const PropTypes = __webpack_require__(3642);
-const dom = __webpack_require__(3643);
+const PropTypes = __webpack_require__(1758);
+const { button, span } = __webpack_require__(1759);
+
+// Utils
+const { isGrip, wrapRender } = __webpack_require__(1760);
+const { rep: StringRep } = __webpack_require__(1770);
+
+/**
+ * Renders Accessible object.
+ */
+Accessible.propTypes = {
+  object: PropTypes.object.isRequired,
+  inspectIconTitle: PropTypes.string,
+  nameMaxLength: PropTypes.number,
+  onAccessibleClick: PropTypes.func,
+  onAccessibleMouseOver: PropTypes.func,
+  onAccessibleMouseOut: PropTypes.func,
+  onInspectIconClick: PropTypes.func,
+  roleFirst: PropTypes.bool,
+  separatorText: PropTypes.string
+};
+
+function Accessible(props) {
+  const {
+    object,
+    inspectIconTitle,
+    nameMaxLength,
+    onAccessibleClick,
+    onAccessibleMouseOver,
+    onAccessibleMouseOut,
+    onInspectIconClick,
+    roleFirst,
+    separatorText
+  } = props;
+  const elements = getElements(object, nameMaxLength, roleFirst, separatorText);
+  const isInTree = object.preview && object.preview.isConnected === true;
+  const baseConfig = {
+    "data-link-actor-id": object.actor,
+    className: "objectBox objectBox-accessible"
+  };
+
+  let inspectIcon;
+  if (isInTree) {
+    if (onAccessibleClick) {
+      Object.assign(baseConfig, {
+        onClick: _ => onAccessibleClick(object),
+        className: `${baseConfig.className} clickable`
+      });
+    }
+
+    if (onAccessibleMouseOver) {
+      Object.assign(baseConfig, {
+        onMouseOver: _ => onAccessibleMouseOver(object)
+      });
+    }
+
+    if (onAccessibleMouseOut) {
+      Object.assign(baseConfig, {
+        onMouseOut: onAccessibleMouseOut
+      });
+    }
+
+    if (onInspectIconClick) {
+      inspectIcon = button({
+        className: "open-accessibility-inspector",
+        title: inspectIconTitle,
+        onClick: e => {
+          if (onAccessibleClick) {
+            e.stopPropagation();
+          }
+
+          onInspectIconClick(object, e);
+        }
+      });
+    }
+  }
+
+  return span(baseConfig, ...elements, inspectIcon);
+}
+
+function getElements(grip, nameMaxLength, roleFirst = false, separatorText = ": ") {
+  const { name, role } = grip.preview;
+  const elements = [];
+  if (name) {
+    elements.push(StringRep({
+      className: "accessible-name",
+      object: name,
+      cropLimit: nameMaxLength
+    }), span({ className: "separator" }, separatorText));
+  }
+
+  elements.push(span({ className: "accessible-role" }, role));
+  return roleFirst ? elements.reverse() : elements;
+}
+
+// Registration
+function supportsObject(object, noGrip = false) {
+  if (noGrip === true || !isGrip(object)) {
+    return false;
+  }
+
+  return object.preview && object.typeName && object.typeName === "accessible";
+}
+
+// Exports from this module
+module.exports = {
+  rep: wrapRender(Accessible),
+  supportsObject
+};
+
+/***/ }),
+
+/***/ 1841:
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+/* 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/>. */
+
+// ReactJS
+const PropTypes = __webpack_require__(1758);
+const dom = __webpack_require__(1759);
 const { span } = dom;
 
 // Reps
-const { getGripType, isGrip, wrapRender } = __webpack_require__(3644);
-const { rep: StringRep } = __webpack_require__(3648);
+const { getGripType, isGrip, wrapRender } = __webpack_require__(1760);
+const { rep: StringRep } = __webpack_require__(1770);
 
 /**
  * Renders DOM attribute
  */
 Attribute.propTypes = {
   object: PropTypes.object.isRequired
 };
 
@@ -5127,33 +5406,33 @@ function supportsObject(grip, noGrip = f
 
 module.exports = {
   rep: wrapRender(Attribute),
   supportsObject
 };
 
 /***/ }),
 
-/***/ 3682:
+/***/ 1842:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/>. */
 
 // ReactJS
-const PropTypes = __webpack_require__(3642);
+const PropTypes = __webpack_require__(1758);
 
 // Reps
-const { getGripType, isGrip, wrapRender } = __webpack_require__(3644);
-
-const dom = __webpack_require__(3643);
+const { getGripType, isGrip, wrapRender } = __webpack_require__(1760);
+
+const dom = __webpack_require__(1759);
 const { span } = dom;
 
 /**
  * Used to render JS built-in Date() object.
  */
 DateTime.propTypes = {
   object: PropTypes.object.isRequired
 };
@@ -5191,38 +5470,38 @@ function supportsObject(grip, noGrip = f
 // Exports from this module
 module.exports = {
   rep: wrapRender(DateTime),
   supportsObject
 };
 
 /***/ }),
 
-/***/ 3683:
+/***/ 1843:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/>. */
 
 // ReactJS
-const PropTypes = __webpack_require__(3642);
+const PropTypes = __webpack_require__(1758);
 
 // Reps
 const {
   getGripType,
   isGrip,
   getURLDisplayString,
   wrapRender
-} = __webpack_require__(3644);
-
-const dom = __webpack_require__(3643);
+} = __webpack_require__(1760);
+
+const dom = __webpack_require__(1759);
 const { span } = dom;
 
 /**
  * Renders DOM document object.
  */
 Document.propTypes = {
   object: PropTypes.object.isRequired
 };
@@ -5260,32 +5539,32 @@ function supportsObject(object, noGrip =
 // Exports from this module
 module.exports = {
   rep: wrapRender(Document),
   supportsObject
 };
 
 /***/ }),
 
-/***/ 3684:
+/***/ 1844:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/>. */
 
 // ReactJS
-const PropTypes = __webpack_require__(3642);
+const PropTypes = __webpack_require__(1758);
 
 // Reps
-const { getGripType, isGrip, wrapRender } = __webpack_require__(3644);
-const dom = __webpack_require__(3643);
+const { getGripType, isGrip, wrapRender } = __webpack_require__(1760);
+const dom = __webpack_require__(1759);
 const { span } = dom;
 
 /**
  * Renders DOM documentType object.
  */
 DocumentType.propTypes = {
   object: PropTypes.object.isRequired
 };
@@ -5312,36 +5591,36 @@ function supportsObject(object, noGrip =
 // Exports from this module
 module.exports = {
   rep: wrapRender(DocumentType),
   supportsObject
 };
 
 /***/ }),
 
-/***/ 3685:
+/***/ 1845:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
 
 /* 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/>. */
 
 // ReactJS
-const PropTypes = __webpack_require__(3642);
+const PropTypes = __webpack_require__(1758);
 
 // Reps
-const { isGrip, wrapRender } = __webpack_require__(3644);
-
-const { MODE } = __webpack_require__(3645);
-const { rep } = __webpack_require__(3656);
+const { isGrip, wrapRender } = __webpack_require__(1760);
+
+const { MODE } = __webpack_require__(1762);
+const { rep } = __webpack_require__(1784);
 
 /**
  * Renders DOM event objects.
  */
 Event.propTypes = {
   object: PropTypes.object.isRequired,
   // @TODO Change this to Object.values when supported in Node's version of V8
   mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
@@ -5418,37 +5697,37 @@ function supportsObject(grip, noGrip = f
 // Exports from this module
 module.exports = {
   rep: wrapRender(Event),
   supportsObject
 };
 
 /***/ }),
 
-/***/ 3686:
+/***/ 1846:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
 
 /* 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/>. */
 
 // ReactJS
-const PropTypes = __webpack_require__(3642);
+const PropTypes = __webpack_require__(1758);
 // Dependencies
-const { getGripType, isGrip, wrapRender } = __webpack_require__(3644);
-
-const PropRep = __webpack_require__(3650);
-const { MODE } = __webpack_require__(3645);
-
-const dom = __webpack_require__(3643);
+const { getGripType, isGrip, wrapRender } = __webpack_require__(1760);
+
+const PropRep = __webpack_require__(1775);
+const { MODE } = __webpack_require__(1762);
+
+const dom = __webpack_require__(1759);
 const { span } = dom;
 
 /**
  * Renders a DOM Promise object.
  */
 PromiseRep.propTypes = {
   object: PropTypes.object.isRequired,
   // @TODO Change this to Object.values when supported in Node's version of V8
@@ -5463,17 +5742,17 @@ function PromiseRep(props) {
   const { promiseState } = object;
 
   const config = {
     "data-link-actor-id": object.actor,
     className: "objectBox objectBox-object"
   };
 
   if (props.mode === MODE.TINY) {
-    const { Rep } = __webpack_require__(3647);
+    const { Rep } = __webpack_require__(1767);
 
     return span(config, getTitle(object), span({
       className: "objectLeftBrace"
     }, " { "), Rep({ object: promiseState.state }), span({
       className: "objectRightBrace"
     }, " }"));
   }
 
@@ -5527,33 +5806,33 @@ function supportsObject(object, noGrip =
 // Exports from this module
 module.exports = {
   rep: wrapRender(PromiseRep),
   supportsObject
 };
 
 /***/ }),
 
-/***/ 3687:
+/***/ 1847:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/>. */
 
 // ReactJS
-const PropTypes = __webpack_require__(3642);
+const PropTypes = __webpack_require__(1758);
 
 // Reps
-const { getGripType, isGrip, wrapRender } = __webpack_require__(3644);
-
-const dom = __webpack_require__(3643);
+const { getGripType, isGrip, wrapRender } = __webpack_require__(1760);
+
+const dom = __webpack_require__(1759);
 const { span } = dom;
 
 /**
  * Renders a grip object with regular expression.
  */
 RegExp.propTypes = {
   object: PropTypes.object.isRequired
 };
@@ -5583,38 +5862,38 @@ function supportsObject(object, noGrip =
 // Exports from this module
 module.exports = {
   rep: wrapRender(RegExp),
   supportsObject
 };
 
 /***/ }),
 
-/***/ 3688:
+/***/ 1848:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/>. */
 
 // ReactJS
-const PropTypes = __webpack_require__(3642);
+const PropTypes = __webpack_require__(1758);
 
 // Reps
 const {
   getGripType,
   isGrip,
   getURLDisplayString,
   wrapRender
-} = __webpack_require__(3644);
-
-const dom = __webpack_require__(3643);
+} = __webpack_require__(1760);
+
+const dom = __webpack_require__(1759);
 const { span } = dom;
 
 /**
  * Renders a grip representing CSSStyleSheet
  */
 StyleSheet.propTypes = {
   object: PropTypes.object.isRequired
 };
@@ -5652,37 +5931,37 @@ function supportsObject(object, noGrip =
 
 module.exports = {
   rep: wrapRender(StyleSheet),
   supportsObject
 };
 
 /***/ }),
 
-/***/ 3689:
+/***/ 1849:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/>. */
 
 // Dependencies
-const PropTypes = __webpack_require__(3642);
+const PropTypes = __webpack_require__(1758);
 const {
   isGrip,
   cropString,
   cropMultipleLines,
   wrapRender
-} = __webpack_require__(3644);
-const { MODE } = __webpack_require__(3645);
-const nodeConstants = __webpack_require__(3659);
-const dom = __webpack_require__(3643);
+} = __webpack_require__(1760);
+const { MODE } = __webpack_require__(1762);
+const nodeConstants = __webpack_require__(1796);
+const dom = __webpack_require__(1759);
 const { span } = dom;
 
 /**
  * Renders DOM comment node.
  */
 CommentNode.propTypes = {
   object: PropTypes.object.isRequired,
   // @TODO Change this to Object.values when supported in Node's version of V8
@@ -5716,36 +5995,36 @@ function supportsObject(object, noGrip =
 // Exports from this module
 module.exports = {
   rep: wrapRender(CommentNode),
   supportsObject
 };
 
 /***/ }),
 
-/***/ 3690:
+/***/ 1850:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/>. */
 
 // ReactJS
-const PropTypes = __webpack_require__(3642);
+const PropTypes = __webpack_require__(1758);
 
 // Utils
-const { isGrip, wrapRender } = __webpack_require__(3644);
-const { rep: StringRep } = __webpack_require__(3648);
-const { MODE } = __webpack_require__(3645);
-const nodeConstants = __webpack_require__(3659);
-
-const dom = __webpack_require__(3643);
+const { isGrip, wrapRender } = __webpack_require__(1760);
+const { rep: StringRep } = __webpack_require__(1770);
+const { MODE } = __webpack_require__(1762);
+const nodeConstants = __webpack_require__(1796);
+
+const dom = __webpack_require__(1759);
 const { span } = dom;
 
 /**
  * Renders DOM element node.
  */
 ElementNode.propTypes = {
   object: PropTypes.object.isRequired,
   inspectIconTitle: PropTypes.string,
@@ -5871,34 +6150,34 @@ function supportsObject(object, noGrip =
 // Exports from this module
 module.exports = {
   rep: wrapRender(ElementNode),
   supportsObject
 };
 
 /***/ }),
 
-/***/ 3691:
+/***/ 1851:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/>. */
 
 // ReactJS
-const PropTypes = __webpack_require__(3642);
+const PropTypes = __webpack_require__(1758);
 
 // Reps
-const { isGrip, cropString, wrapRender } = __webpack_require__(3644);
-const { MODE } = __webpack_require__(3645);
-
-const dom = __webpack_require__(3643);
+const { isGrip, cropString, wrapRender } = __webpack_require__(1760);
+const { MODE } = __webpack_require__(1762);
+
+const dom = __webpack_require__(1759);
 const { span } = dom;
 
 /**
  * Renders DOM #text node.
  */
 TextNode.propTypes = {
   object: PropTypes.object.isRequired,
   // @TODO Change this to Object.values when supported in Node's version of V8
@@ -5976,40 +6255,40 @@ function supportsObject(grip, noGrip = f
 // Exports from this module
 module.exports = {
   rep: wrapRender(TextNode),
   supportsObject
 };
 
 /***/ }),
 
-/***/ 3692:
+/***/ 1852:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/>. */
 
 // ReactJS
-const PropTypes = __webpack_require__(3642);
+const PropTypes = __webpack_require__(1758);
 
 // Reps
 const {
   getGripType,
   isGrip,
   getURLDisplayString,
   wrapRender
-} = __webpack_require__(3644);
-
-const { MODE } = __webpack_require__(3645);
-
-const dom = __webpack_require__(3643);
+} = __webpack_require__(1760);
+
+const { MODE } = __webpack_require__(1762);
+
+const dom = __webpack_require__(1759);
 const { span } = dom;
 
 /**
  * Renders a grip representing a window.
  */
 WindowRep.propTypes = {
   // @TODO Change this to Object.values when supported in Node's version of V8
   mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
@@ -6055,35 +6334,35 @@ function supportsObject(object, noGrip =
 // Exports from this module
 module.exports = {
   rep: wrapRender(WindowRep),
   supportsObject
 };
 
 /***/ }),
 
-/***/ 3693:
+/***/ 1853:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/>. */
 
 // ReactJS
-const PropTypes = __webpack_require__(3642);
+const PropTypes = __webpack_require__(1758);
 
 // Reps
-const { isGrip, wrapRender } = __webpack_require__(3644);
-
-const String = __webpack_require__(3648).rep;
-
-const dom = __webpack_require__(3643);
+const { isGrip, wrapRender } = __webpack_require__(1760);
+
+const String = __webpack_require__(1770).rep;
+
+const dom = __webpack_require__(1759);
 const { span } = dom;
 
 /**
  * Renders a grip object with textual data.
  */
 ObjectWithText.propTypes = {
   object: PropTypes.object.isRequired
 };
@@ -6118,33 +6397,33 @@ function supportsObject(grip, noGrip = f
 // Exports from this module
 module.exports = {
   rep: wrapRender(ObjectWithText),
   supportsObject
 };
 
 /***/ }),
 
-/***/ 3694:
+/***/ 1854:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/>. */
 
 // ReactJS
-const PropTypes = __webpack_require__(3642);
+const PropTypes = __webpack_require__(1758);
 
 // Reps
-const { isGrip, getURLDisplayString, wrapRender } = __webpack_require__(3644);
-
-const dom = __webpack_require__(3643);
+const { isGrip, getURLDisplayString, wrapRender } = __webpack_require__(1760);
+
+const dom = __webpack_require__(1759);
 const { span } = dom;
 
 /**
  * Renders a grip object with URL data.
  */
 ObjectWithURL.propTypes = {
   object: PropTypes.object.isRequired
 };
@@ -6181,98 +6460,74 @@ function supportsObject(grip, noGrip = f
 // Exports from this module
 module.exports = {
   rep: wrapRender(ObjectWithURL),
   supportsObject
 };
 
 /***/ }),
 
-/***/ 3695:
+/***/ 1855:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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 ObjectInspector = __webpack_require__(3696);
-const utils = __webpack_require__(3657);
-const reducer = __webpack_require__(3703);
+const ObjectInspector = __webpack_require__(1856);
+const utils = __webpack_require__(1787);
+const reducer = __webpack_require__(1786);
 
 module.exports = { ObjectInspector, utils, reducer };
 
 /***/ }),
 
-/***/ 3696:
+/***/ 1856:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
 
-var _devtoolsServices = __webpack_require__(22);
-
-var _devtoolsServices2 = _interopRequireDefault(_devtoolsServices);
-
-var _devtoolsComponents = __webpack_require__(3669);
+var _devtoolsComponents = __webpack_require__(1792);
 
 var _devtoolsComponents2 = _interopRequireDefault(_devtoolsComponents);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 /* 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 { Component, createFactory, createElement } = __webpack_require__(0);
-const dom = __webpack_require__(3643);
-const { connect } = __webpack_require__(3592);
-const actions = __webpack_require__(3699);
-
-const selectors = __webpack_require__(3703);
-
-const { appinfo } = _devtoolsServices2.default;
-const isMacOS = appinfo.OS === "Darwin";
+const { connect } = __webpack_require__(1763);
+const actions = __webpack_require__(1857);
+
+const selectors = __webpack_require__(1786);
 
 const Tree = createFactory(_devtoolsComponents2.default.Tree);
-__webpack_require__(3697);
+__webpack_require__(1858);
+
+const ObjectInspectorItem = createFactory(__webpack_require__(1859));
 
 const classnames = __webpack_require__(175);
-const { MODE } = __webpack_require__(3645);
-
-const Utils = __webpack_require__(3657);
+
+const Utils = __webpack_require__(1787);
 const { renderRep, shouldRenderRootsInReps } = Utils;
-
 const {
-  getChildren,
+  getChildrenWithEvaluations,
   getActor,
   getParent,
-  getValue,
-  nodeHasAccessors,
-  nodeHasProperties,
-  nodeIsBlock,
-  nodeIsDefaultProperties,
-  nodeIsFunction,
-  nodeIsGetter,
-  nodeIsMapEntry,
-  nodeIsMissingArguments,
-  nodeIsOptimizedOut,
   nodeIsPrimitive,
-  nodeIsPrototype,
-  nodeIsSetter,
-  nodeIsUninitializedBinding,
-  nodeIsUnmappedBinding,
-  nodeIsUnscopedBinding,
-  nodeIsWindow,
-  nodeIsLongString,
-  nodeHasFullText
+  nodeHasGetter,
+  nodeHasSetter
 } = Utils.node;
 
 // This implements a component that renders an interactive inspector
 // for looking at JavaScript objects. It expects descriptions of
 // objects from the protocol, and will dynamically fetch children
 // properties as objects are expanded.
 //
 // If you want to inspect a single object, pass the name and the
@@ -6300,80 +6555,113 @@ const {
 class ObjectInspector extends Component {
   constructor(props) {
     super();
     this.cachedNodes = new Map();
 
     const self = this;
 
     self.getItemChildren = this.getItemChildren.bind(this);
-    self.renderTreeItem = this.renderTreeItem.bind(this);
+    self.isNodeExpandable = this.isNodeExpandable.bind(this);
     self.setExpanded = this.setExpanded.bind(this);
     self.focusItem = this.focusItem.bind(this);
     self.getRoots = this.getRoots.bind(this);
+    self.getNodeKey = this.getNodeKey.bind(this);
   }
 
   componentWillMount() {
     this.roots = this.props.roots;
     this.focusedItem = this.props.focusedItem;
   }
 
   componentWillUpdate(nextProps) {
+    this.removeOutdatedNodesFromCache(nextProps);
+
     if (this.roots !== nextProps.roots) {
       // Since the roots changed, we assume the properties did as well,
       // so we need to cleanup the component internal state.
-
-      // We can clear the cachedNodes to avoid bugs and memory leaks.
-      this.cachedNodes.clear();
       this.roots = nextProps.roots;
       this.focusedItem = nextProps.focusedItem;
       if (this.props.rootsChanged) {
         this.props.rootsChanged();
       }
+      return;
+    }
+  }
+
+  removeOutdatedNodesFromCache(nextProps) {
+    // When the roots changes, we can wipe out everything.
+    if (this.roots !== nextProps.roots) {
+      this.cachedNodes.clear();
+      return;
+    }
+
+    // If there are new evaluations, we want to remove the existing cached
+    // nodes from the cache.
+    if (nextProps.evaluations > this.props.evaluations) {
+      for (const key of nextProps.evaluations.keys()) {
+        if (!this.props.evaluations.has(key)) {
+          this.cachedNodes.delete(key);
+        }
+      }
     }
   }
 
   shouldComponentUpdate(nextProps) {
-    const { expandedPaths, loadedProperties } = this.props;
+    const { expandedPaths, loadedProperties, evaluations } = this.props;
 
     // We should update if:
     // - there are new loaded properties
+    // - OR there are new evaluations
     // - OR the expanded paths number changed, and all of them have properties
     //      loaded
     // - OR the expanded paths number did not changed, but old and new sets
     //      differ
     // - OR the focused node changed.
-    return loadedProperties.size !== nextProps.loadedProperties.size || expandedPaths.size !== nextProps.expandedPaths.size && [...nextProps.expandedPaths].every(path => nextProps.loadedProperties.has(path)) || expandedPaths.size === nextProps.expandedPaths.size && [...nextProps.expandedPaths].some(key => !expandedPaths.has(key)) || this.focusedItem !== nextProps.focusedItem || this.roots !== nextProps.roots;
+    return loadedProperties.size !== nextProps.loadedProperties.size || evaluations.size !== nextProps.evaluations.size || expandedPaths.size !== nextProps.expandedPaths.size && [...nextProps.expandedPaths].every(path => nextProps.loadedProperties.has(path)) || expandedPaths.size === nextProps.expandedPaths.size && [...nextProps.expandedPaths].some(key => !expandedPaths.has(key)) || this.focusedItem !== nextProps.focusedItem || this.roots !== nextProps.roots;
   }
 
   componentWillUnmount() {
     this.props.closeObjectInspector();
   }
 
   getItemChildren(item) {
-    const { loadedProperties } = this.props;
+    const { loadedProperties, evaluations } = this.props;
     const { cachedNodes } = this;
 
-    return getChildren({
+    return getChildrenWithEvaluations({
+      evaluations,
       loadedProperties,
       cachedNodes,
       item
     });
   }
 
   getRoots() {
     return this.props.roots;
   }
 
   getNodeKey(item) {
     return item.path && typeof item.path.toString === "function" ? item.path.toString() : JSON.stringify(item);
   }
 
+  isNodeExpandable(item) {
+    if (nodeIsPrimitive(item)) {
+      return false;
+    }
+
+    if (nodeHasSetter(item) || nodeHasGetter(item)) {
+      return false;
+    }
+
+    return true;
+  }
+
   setExpanded(item, expand) {
-    if (nodeIsPrimitive(item)) {
+    if (!this.isNodeExpandable(item)) {
       return;
     }
 
     const {
       nodeExpand,
       nodeCollapse,
       recordTelemetryEvent,
       roots
@@ -6398,175 +6686,16 @@ class ObjectInspector extends Component 
       this.forceUpdate();
 
       if (onFocus) {
         onFocus(item);
       }
     }
   }
 
-  // eslint-disable-next-line complexity
-  getTreeItemLabelAndValue(item, depth, expanded) {
-    const label = item.name;
-    const isPrimitive = nodeIsPrimitive(item);
-
-    if (nodeIsOptimizedOut(item)) {
-      return {
-        label,
-        value: dom.span({ className: "unavailable" }, "(optimized away)")
-      };
-    }
-
-    if (nodeIsUninitializedBinding(item)) {
-      return {
-        label,
-        value: dom.span({ className: "unavailable" }, "(uninitialized)")
-      };
-    }
-
-    if (nodeIsUnmappedBinding(item)) {
-      return {
-        label,
-        value: dom.span({ className: "unavailable" }, "(unmapped)")
-      };
-    }
-
-    if (nodeIsUnscopedBinding(item)) {
-      return {
-        label,
-        value: dom.span({ className: "unavailable" }, "(unscoped)")
-      };
-    }
-
-    const itemValue = getValue(item);
-    const unavailable = isPrimitive && itemValue && itemValue.hasOwnProperty && itemValue.hasOwnProperty("unavailable");
-
-    if (nodeIsMissingArguments(item) || unavailable) {
-      return {
-        label,
-        value: dom.span({ className: "unavailable" }, "(unavailable)")
-      };
-    }
-
-    if (nodeIsFunction(item) && !nodeIsGetter(item) && !nodeIsSetter(item) && (this.props.mode === MODE.TINY || !this.props.mode)) {
-      return {
-        label: Utils.renderRep(item, _extends({}, this.props, {
-          functionName: label
-        }))
-      };
-    }
-
-    if (nodeHasProperties(item) || nodeHasAccessors(item) || nodeIsMapEntry(item) || nodeIsLongString(item) || isPrimitive) {
-      const repProps = _extends({}, this.props);
-      if (depth > 0) {
-        repProps.mode = this.props.mode === MODE.LONG ? MODE.SHORT : MODE.TINY;
-      }
-      if (expanded) {
-        repProps.mode = MODE.TINY;
-      }
-
-      if (nodeIsLongString(item)) {
-        repProps.member = {
-          open: nodeHasFullText(item) && expanded
-        };
-      }
-
-      return {
-        label,
-        value: Utils.renderRep(item, repProps)
-      };
-    }
-
-    return {
-      label
-    };
-  }
-
-  renderTreeItemLabel(label, item, depth, focused, expanded) {
-    if (label === null || typeof label === "undefined") {
-      return null;
-    }
-
-    const { onLabelClick } = this.props;
-
-    return dom.span({
-      className: "object-label",
-      onClick: onLabelClick ? event => {
-        event.stopPropagation();
-
-        // If the user selected text, bail out.
-        if (Utils.selection.documentHasSelection()) {
-          return;
-        }
-
-        onLabelClick(item, {
-          depth,
-          focused,
-          expanded,
-          setExpanded: this.setExpanded
-        });
-      } : undefined
-    }, label);
-  }
-
-  getTreeTopElementProps(item, depth, focused, expanded) {
-    const { onCmdCtrlClick, onDoubleClick, dimTopLevelWindow } = this.props;
-
-    const parentElementProps = {
-      className: classnames("node object-node", {
-        focused,
-        lessen: !expanded && (nodeIsDefaultProperties(item) || nodeIsPrototype(item) || nodeIsGetter(item) || nodeIsSetter(item) || dimTopLevelWindow === true && nodeIsWindow(item) && depth === 0),
-        block: nodeIsBlock(item)
-      }),
-      onClick: e => {
-        if (onCmdCtrlClick && (isMacOS && e.metaKey || !isMacOS && e.ctrlKey)) {
-          onCmdCtrlClick(item, {
-            depth,
-            event: e,
-            focused,
-            expanded
-          });
-          e.stopPropagation();
-          return;
-        }
-
-        // If this click happened because the user selected some text, bail out.
-        // Note that if the user selected some text before and then clicks here,
-        // the previously selected text will be first unselected, unless the
-        // user clicked on the arrow itself. Indeed because the arrow is an
-        // image, clicking on it does not remove any existing text selection.
-        // So we need to also check if the arrow was clicked.
-        if (Utils.selection.documentHasSelection() && !(e.target && e.target.matches && e.target.matches(".arrow"))) {
-          e.stopPropagation();
-        }
-      }
-    };
-
-    if (onDoubleClick) {
-      parentElementProps.onDoubleClick = e => {
-        e.stopPropagation();
-        onDoubleClick(item, {
-          depth,
-          focused,
-          expanded
-        });
-      };
-    }
-
-    return parentElementProps;
-  }
-
-  renderTreeItem(item, depth, focused, arrow, expanded) {
-    const { label, value } = this.getTreeItemLabelAndValue(item, depth, expanded);
-    const labelElement = this.renderTreeItemLabel(label, item, depth, focused, expanded);
-    const delimiter = value && labelElement ? dom.span({ className: "object-delimiter" }, ": ") : null;
-
-    return dom.div(this.getTreeTopElementProps(item, depth, focused, expanded), arrow, labelElement, delimiter, value);
-  }
-
   render() {
     const {
       autoExpandAll = true,
       autoExpandDepth = 1,
       focusable = true,
       disableWrap = false,
       expandedPaths,
       inline
@@ -6578,99 +6707,72 @@ class ObjectInspector extends Component 
         nowrap: disableWrap,
         "object-inspector": true
       }),
 
       autoExpandAll,
       autoExpandDepth,
 
       isExpanded: item => expandedPaths && expandedPaths.has(item.path),
-      // TODO: We don't want property with getters to be expandable until we
-      // do have a mechanism to invoke the getter (See #6140).
-      isExpandable: item => !nodeIsPrimitive(item) && !nodeHasAccessors(item),
+      isExpandable: this.isNodeExpandable,
       focused: this.focusedItem,
 
       getRoots: this.getRoots,
       getParent,
       getChildren: this.getItemChildren,
       getKey: this.getNodeKey,
 
       onExpand: item => this.setExpanded(item, true),
       onCollapse: item => this.setExpanded(item, false),
       onFocus: focusable ? this.focusItem : null,
 
-      renderItem: this.renderTreeItem
+      renderItem: (item, depth, focused, arrow, expanded) => ObjectInspectorItem(_extends({}, this.props, {
+        item,
+        depth,
+        focused,
+        arrow,
+        expanded,
+        setExpanded: this.setExpanded
+      }))
     });
   }
 }
 
 function mapStateToProps(state, props) {
   return {
-    actors: selectors.getActors(state),
     expandedPaths: selectors.getExpandedPaths(state),
-    loadedProperties: selectors.getLoadedProperties(state)
+    loadedProperties: selectors.getLoadedProperties(state),
+    evaluations: selectors.getEvaluations(state)
   };
 }
 
 const OI = connect(mapStateToProps, actions)(ObjectInspector);
 
 module.exports = props => {
   const { roots } = props;
   if (shouldRenderRootsInReps(roots)) {
     return renderRep(roots[0], props);
   }
 
   return createElement(OI, props);
 };
 
 /***/ }),
 
-/***/ 3697:
-/***/ (function(module, exports) {
-
-// removed by extract-text-webpack-plugin
-
-/***/ }),
-
-/***/ 3698:
+/***/ 1857:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
-/* 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 documentHasSelection() {
-  const selection = getSelection();
-  if (!selection) {
-    return false;
-  }
-
-  return selection.type === "Range";
-}
-
-module.exports = {
-  documentHasSelection
-};
-
-/***/ }),
-
-/***/ 3699:
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-const { loadItemProperties } = __webpack_require__(3666); /* This Source Code Form is subject to the terms of the Mozilla Public
+const { loadItemProperties } = __webpack_require__(1804); /* 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 { getLoadedProperties, getActors } = __webpack_require__(3703);
+const { getLoadedProperties, getActors } = __webpack_require__(1786);
 
 /**
  * This action is responsible for expanding a given node, which also means that
  * it will call the action responsible to fetch properties.
  */
 function nodeExpand(node, actor) {
   return async ({ dispatch, getState }) => {
     dispatch({ type: "NODE_EXPAND", data: { node } });
@@ -6687,17 +6789,18 @@ function nodeCollapse(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
  * functions.
  */
 function nodeLoadProperties(node, actor) {
   return async ({ dispatch, client, getState }) => {
-    const loadedProperties = getLoadedProperties(getState());
+    const state = getState();
+    const loadedProperties = getLoadedProperties(state);
     if (loadedProperties.has(node.path)) {
       return;
     }
 
     try {
       const properties = await loadItemProperties(node, client.createObjectClient, client.createLongStringClient, loadedProperties);
 
       dispatch(nodePropertiesLoaded(node, actor, properties));
@@ -6740,242 +6843,325 @@ function rootsChanged(props) {
 
 function releaseActors(state, client) {
   const actors = getActors(state);
   for (const actor of actors) {
     client.releaseActor(actor);
   }
 }
 
+function invokeGetter(node, grip, getterName) {
+  return async ({ dispatch, client, getState }) => {
+    try {
+      const objectClient = client.createObjectClient(grip);
+      const result = await objectClient.getPropertyValue(getterName);
+      dispatch({
+        type: "GETTER_INVOKED",
+        data: {
+          node,
+          result
+        }
+      });
+    } catch (e) {
+      console.error(e);
+    }
+  };
+}
+
 module.exports = {
   closeObjectInspector,
+  invokeGetter,
   nodeExpand,
   nodeCollapse,
   nodeLoadProperties,
   nodePropertiesLoaded,
   rootsChanged
 };
 
 /***/ }),
 
-/***/ 3703:
+/***/ 1858:
+/***/ (function(module, exports) {
+
+// removed by extract-text-webpack-plugin
+
+/***/ }),
+
+/***/ 1859:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
 
-function initialState() {
-  return {
-    expandedPaths: new Set(),
-    loadedProperties: new Map(),
-    actors: new Set()
-  };
-} /* 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 reducer(state = initialState(), action = {}) {
-  const { type, data } = action;
-
-  const cloneState = overrides => _extends({}, state, overrides);
-
-  if (type === "NODE_EXPAND") {
-    return cloneState({
-      expandedPaths: new Set(state.expandedPaths).add(data.node.path)
-    });
-  }
-
-  if (type === "NODE_COLLAPSE") {
-    const expandedPaths = new Set(state.expandedPaths);
-    expandedPaths.delete(data.node.path);
-    return cloneState({ expandedPaths });
-  }
-
-  if (type === "NODE_PROPERTIES_LOADED") {
-    return cloneState({
-      actors: data.actor ? new Set(state.actors || []).add(data.actor) : state.actors,
-      loadedProperties: new Map(state.loadedProperties).set(data.node.path, action.data.properties)
-    });
-  }
-
-  if (type === "ROOTS_CHANGED") {
-    return cloneState();
-  }
-
-  return state;
-}
-
-function getObjectInspectorState(state) {
-  return state.objectInspector;
-}
-
-function getExpandedPaths(state) {
-  return getObjectInspectorState(state).expandedPaths;
-}
-
-function getExpandedPathKeys(state) {
-  return [...getExpandedPaths(state).keys()];
-}
-
-function getActors(state) {
-  return getObjectInspectorState(state).actors;
-}
-
-function getLoadedProperties(state) {
-  return getObjectInspectorState(state).loadedProperties;
-}
-
-function getLoadedPropertyKeys(state) {
-  return [...getLoadedProperties(state).keys()];
-}
-
-const selectors = {
-  getExpandedPaths,
-  getExpandedPathKeys,
-  getActors,
-  getLoadedProperties,
-  getLoadedPropertyKeys
-};
-
-Object.defineProperty(module.exports, "__esModule", {
-  value: true
-});
-module.exports = selectors;
-module.exports.default = reducer;
+var _devtoolsServices = __webpack_require__(22);
+
+var _devtoolsServices2 = _interopRequireDefault(_devtoolsServices);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+/* 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 { Component } = __webpack_require__(0);
+const dom = __webpack_require__(1759);
+
+const { appinfo } = _devtoolsServices2.default;
+const isMacOS = appinfo.OS === "Darwin";
+
+const classnames = __webpack_require__(175);
+const { MODE } = __webpack_require__(1762);
+
+const Utils = __webpack_require__(1787);
+
+const {
+  getValue,
+  nodeHasAccessors,
+  nodeHasProperties,
+  nodeIsBlock,
+  nodeIsDefaultProperties,
+  nodeIsFunction,
+  nodeIsGetter,
+  nodeIsMapEntry,
+  nodeIsMissingArguments,
+  nodeIsOptimizedOut,
+  nodeIsPrimitive,
+  nodeIsPrototype,
+  nodeIsSetter,
+  nodeIsUninitializedBinding,
+  nodeIsUnmappedBinding,
+  nodeIsUnscopedBinding,
+  nodeIsWindow,
+  nodeIsLongString,
+  nodeHasFullText,
+  nodeHasGetter,
+  getParentGripValue
+} = Utils.node;
+
+class ObjectInspectorItem extends Component {
+  // eslint-disable-next-line complexity
+  getLabelAndValue() {
+    const { item, depth, expanded, mode } = this.props;
+
+    const label = item.name;
+    const isPrimitive = nodeIsPrimitive(item);
+
+    if (nodeIsOptimizedOut(item)) {
+      return {
+        label,
+        value: dom.span({ className: "unavailable" }, "(optimized away)")
+      };
+    }
+
+    if (nodeIsUninitializedBinding(item)) {
+      return {
+        label,
+        value: dom.span({ className: "unavailable" }, "(uninitialized)")
+      };
+    }
+
+    if (nodeIsUnmappedBinding(item)) {
+      return {
+        label,
+        value: dom.span({ className: "unavailable" }, "(unmapped)")
+      };
+    }
+
+    if (nodeIsUnscopedBinding(item)) {
+      return {
+        label,
+        value: dom.span({ className: "unavailable" }, "(unscoped)")
+      };
+    }
+
+    const itemValue = getValue(item);
+    const unavailable = isPrimitive && itemValue && itemValue.hasOwnProperty && itemValue.hasOwnProperty("unavailable");
+
+    if (nodeIsMissingArguments(item) || unavailable) {
+      return {
+        label,
+        value: dom.span({ className: "unavailable" }, "(unavailable)")
+      };
+    }
+
+    if (nodeIsFunction(item) && !nodeIsGetter(item) && !nodeIsSetter(item) && (mode === MODE.TINY || !mode)) {
+      return {
+        label: Utils.renderRep(item, _extends({}, this.props, {
+          functionName: label
+        }))
+      };
+    }
+
+    if (nodeHasProperties(item) || nodeHasAccessors(item) || nodeIsMapEntry(item) || nodeIsLongString(item) || isPrimitive) {
+      const repProps = _extends({}, this.props);
+      if (depth > 0) {
+        repProps.mode = mode === MODE.LONG ? MODE.SHORT : MODE.TINY;
+      }
+      if (expanded) {
+        repProps.mode = MODE.TINY;
+      }
+
+      if (nodeIsLongString(item)) {
+        repProps.member = {
+          open: nodeHasFullText(item) && expanded
+        };
+      }
+
+      if (nodeHasGetter(item)) {
+        const parentGrip = getParentGripValue(item);
+        if (parentGrip) {
+          Object.assign(repProps, {
+            onInvokeGetterButtonClick: () => this.props.invokeGetter(item, parentGrip, item.name)
+          });
+        }
+      }
+
+      return {
+        label,
+        value: Utils.renderRep(item, repProps)
+      };
+    }
+
+    return {
+      label
+    };
+  }
+
+  getTreeItemProps() {
+    const {
+      item,
+      depth,
+      focused,
+      expanded,
+      onCmdCtrlClick,
+      onDoubleClick,
+      dimTopLevelWindow
+    } = this.props;
+
+    const parentElementProps = {
+      className: classnames("node object-node", {
+        focused,
+        lessen: !expanded && (nodeIsDefaultProperties(item) || nodeIsPrototype(item) || nodeIsGetter(item) || nodeIsSetter(item) || dimTopLevelWindow === true && nodeIsWindow(item) && depth === 0),
+        block: nodeIsBlock(item)
+      }),
+      onClick: e => {
+        if (onCmdCtrlClick && (isMacOS && e.metaKey || !isMacOS && e.ctrlKey)) {
+          onCmdCtrlClick(item, {
+            depth,
+            event: e,
+            focused,
+            expanded
+          });
+          e.stopPropagation();
+          return;
+        }
+
+        // If this click happened because the user selected some text, bail out.
+        // Note that if the user selected some text before and then clicks here,
+        // the previously selected text will be first unselected, unless the
+        // user clicked on the arrow itself. Indeed because the arrow is an
+        // image, clicking on it does not remove any existing text selection.
+        // So we need to also check if the arrow was clicked.
+        if (Utils.selection.documentHasSelection() && !(e.target && e.target.matches && e.target.matches(".arrow"))) {
+          e.stopPropagation();
+        }
+      }
+    };
+
+    if (onDoubleClick) {
+      parentElementProps.onDoubleClick = e => {
+        e.stopPropagation();
+        onDoubleClick(item, {
+          depth,
+          focused,
+          expanded
+        });
+      };
+    }
+
+    return parentElementProps;
+  }
+
+  renderLabel(label) {
+    if (label === null || typeof label === "undefined") {
+      return null;
+    }
+
+    const { item, depth, focused, expanded, onLabelClick } = this.props;
+    return dom.span({
+      className: "object-label",
+      onClick: onLabelClick ? event => {
+        event.stopPropagation();
+
+        // If the user selected text, bail out.
+        if (Utils.selection.documentHasSelection()) {
+          return;
+        }
+
+        onLabelClick(item, {
+          depth,
+          focused,
+          expanded,
+          setExpanded: this.props.setExpanded
+        });
+      } : undefined
+    }, label);
+  }
+
+  render() {
+    const { arrow } = this.props;
+
+    const { label, value } = this.getLabelAndValue();
+    const labelElement = this.renderLabel(label);
+    const delimiter = value && labelElement ? dom.span({ className: "object-delimiter" }, ": ") : null;
+
+    return dom.div(this.getTreeItemProps(), arrow, labelElement, delimiter, value);
+  }
+}
+
+module.exports = ObjectInspectorItem;
 
 /***/ }),
 
-/***/ 3730:
-/***/ (function(module, exports, __webpack_require__) {
-
-module.exports = __webpack_require__(3655);
-
-
-/***/ }),
-
-/***/ 3787:
+/***/ 1860:
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/>. */
 
-// ReactJS
-const PropTypes = __webpack_require__(3642);
-const { button, span } = __webpack_require__(3643);
-
-// Utils
-const { isGrip, wrapRender } = __webpack_require__(3644);
-const { rep: StringRep } = __webpack_require__(3648);
-
-/**
- * Renders Accessible object.
- */
-Accessible.propTypes = {
-  object: PropTypes.object.isRequired,
-  inspectIconTitle: PropTypes.string,
-  nameMaxLength: PropTypes.number,
-  onAccessibleClick: PropTypes.func,
-  onAccessibleMouseOver: PropTypes.func,
-  onAccessibleMouseOut: PropTypes.func,
-  onInspectIconClick: PropTypes.func,
-  roleFirst: PropTypes.bool,
-  separatorText: PropTypes.string
+function documentHasSelection() {
+  const selection = getSelection();
+  if (!selection) {
+    return false;
+  }
+
+  return selection.type === "Range";
+}
+
+module.exports = {
+  documentHasSelection
 };
 
-function Accessible(props) {
-  const {
-    object,
-    inspectIconTitle,
-    nameMaxLength,
-    onAccessibleClick,
-    onAccessibleMouseOver,
-    onAccessibleMouseOut,
-    onInspectIconClick,
-    roleFirst,
-    separatorText
-  } = props;
-  const elements = getElements(object, nameMaxLength, roleFirst, separatorText);
-  const isInTree = object.preview && object.preview.isConnected === true;
-  const baseConfig = {
-    "data-link-actor-id": object.actor,
-    className: "objectBox objectBox-accessible"
-  };
-
-  let inspectIcon;
-  if (isInTree) {
-    if (onAccessibleClick) {
-      Object.assign(baseConfig, {
-        onClick: _ => onAccessibleClick(object),
-        className: `${baseConfig.className} clickable`
-      });
-    }
-
-    if (onAccessibleMouseOver) {
-      Object.assign(baseConfig, {
-        onMouseOver: _ => onAccessibleMouseOver(object)
-      });
-    }
-
-    if (onAccessibleMouseOut) {
-      Object.assign(baseConfig, {
-        onMouseOut: onAccessibleMouseOut
-      });
-    }
-
-    if (onInspectIconClick) {
-      inspectIcon = button({
-        className: "open-accessibility-inspector",
-        title: inspectIconTitle,
-        onClick: e => {
-          if (onAccessibleClick) {
-            e.stopPropagation();
-          }
-
-          onInspectIconClick(object, e);
-        }
-      });
-    }
-  }
-
-  return span(baseConfig, ...elements, inspectIcon);
-}
-
-function getElements(grip, nameMaxLength, roleFirst = false, separatorText = ": ") {
-  const { name, role } = grip.preview;
-  const elements = [];
-  if (name) {
-    elements.push(StringRep({
-      className: "accessible-name",
-      object: name,
-      cropLimit: nameMaxLength
-    }), span({ className: "separator" }, separatorText));
-  }
-
-  elements.push(span({ className: "accessible-role" }, role));
-  return roleFirst ? elements.reverse() : elements;
-}
-
-// Registration
-function supportsObject(object, noGrip = false) {
-  if (noGrip === true || !isGrip(object)) {
-    return false;
-  }
-
-  return object.preview && object.typeName && object.typeName === "accessible";
-}
-
-// Exports from this module
-module.exports = {
-  rep: wrapRender(Accessible),
-  supportsObject
-};
+/***/ }),
+
+/***/ 2105:
+/***/ (function(module, exports, __webpack_require__) {
+
+module.exports = __webpack_require__(1780);
+
+
+/***/ }),
+
+/***/ 22:
+/***/ (function(module, exports) {
+
+module.exports = __WEBPACK_EXTERNAL_MODULE_22__;
 
 /***/ })
 
 /******/ });
 });
\ No newline at end of file
--- a/devtools/client/shared/components/tree/TreeRow.js
+++ b/devtools/client/shared/components/tree/TreeRow.js
@@ -44,16 +44,17 @@ define(function(require, exports, module
         }),
         decorator: PropTypes.object,
         renderCell: PropTypes.object,
         renderLabelCell: PropTypes.object,
         columns: PropTypes.array.isRequired,
         id: PropTypes.string.isRequired,
         provider: PropTypes.object.isRequired,
         onClick: PropTypes.func.isRequired,
+        onContextMenu: PropTypes.func,
         onMouseOver: PropTypes.func,
         onMouseOut: PropTypes.func,
       };
     }
 
     constructor(props) {
       super(props);
       this.getRowClass = this.getRowClass.bind(this);
@@ -115,22 +116,24 @@ define(function(require, exports, module
       }
 
       return classNames;
     }
 
     render() {
       const member = this.props.member;
       const decorator = this.props.decorator;
+
       const props = {
         id: this.props.id,
         role: "treeitem",
         "aria-level": member.level,
         "aria-selected": !!member.selected,
         onClick: this.props.onClick,
+        onContextMenu: this.props.onContextMenu,
         onMouseOver: this.props.onMouseOver,
         onMouseOut: this.props.onMouseOut,
       };
 
       // Compute class name list for the <tr> element.
       const classNames = this.getRowClass(member.object) || [];
       classNames.push("treeRow");
       classNames.push(member.type + "Row");
--- a/devtools/client/shared/telemetry.js
+++ b/devtools/client/shared/telemetry.js
@@ -718,16 +718,21 @@ function getChartsFromToolId(id) {
     case "APPLICATION":
       timerHist = `DEVTOOLS_${id}_TIME_ACTIVE_SECONDS`;
       countScalar = `devtools.${lowerCaseId}.opened_count`;
       break;
     case "ACCESSIBILITY_PICKER":
       timerHist = `DEVTOOLS_${id}_TIME_ACTIVE_SECONDS`;
       countScalar = `devtools.accessibility.picker_used_count`;
       break;
+    case "CHANGESVIEW":
+      useTimedEvent = true;
+      timerHist = `DEVTOOLS_${id}_TIME_ACTIVE_SECONDS`;
+      countScalar = `devtools.${lowerCaseId}.opened_count`;
+      break;
     case "ANIMATIONINSPECTOR":
     case "COMPUTEDVIEW":
     case "FONTINSPECTOR":
     case "LAYOUTVIEW":
     case "RULEVIEW":
       useTimedEvent = true;
       timerHist = `DEVTOOLS_${id}_TIME_ACTIVE_SECONDS`;
       countHist = `DEVTOOLS_${id}_OPENED_COUNT`;
deleted file mode 100644
index 689689ebe8a87bb743257fbe4d93512ba93f98df..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 8f6ec6dc16722fd3c3d07de677473636ff071fa4..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 1f6f106f3c361729bd200633c783b1652101fbcd..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/test/addons/test-addon-1/manifest.json
@@ -0,0 +1,10 @@
+{
+  "manifest_version": 2,
+  "name": "test-addon-1",
+  "version": "1.0",
+  "applications": {
+    "gecko": {
+      "id": "test-addon-1@mozilla.org"
+    }
+  }
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/test/addons/test-addon-2/manifest.json
@@ -0,0 +1,10 @@
+{
+  "manifest_version": 2,
+  "name": "test-addon-2",
+  "version": "1.0",
+  "applications": {
+    "gecko": {
+      "id": "test-addon-2@mozilla.org"
+    }
+  }
+}
--- a/devtools/client/shared/test/browser.ini
+++ b/devtools/client/shared/test/browser.ini
@@ -1,15 +1,13 @@
 [DEFAULT]
 tags = devtools
 subsuite = devtools
 support-files =
-  addon1.xpi
-  addon2.xpi
-  addon4.xpi
+  addons/*
   browser_devices.json
   code_listworkers-worker1.js
   code_listworkers-worker2.js
   code_WorkerTargetActor.attach-worker1.js
   code_WorkerTargetActor.attach-worker2.js
   code_WorkerTargetActor.attachThread-worker.js
   code_frame-script.js
   doc_cubic-bezier-01.html
@@ -71,20 +69,16 @@ support-files =
 [browser_css_color.js]
 [browser_cubic-bezier-01.js]
 [browser_cubic-bezier-02.js]
 [browser_cubic-bezier-03.js]
 [browser_cubic-bezier-04.js]
 [browser_cubic-bezier-05.js]
 [browser_cubic-bezier-06.js]
 [browser_cubic-bezier-07.js]
-[browser_dbg_addon-console.js]
-# To be removed or updated in bug 1497264
-# previously was: (e10s && debug || os == 'win' || verify)
-skip-if = true 
 tags = addons
 [browser_dbg_debugger-statement.js]
 skip-if = e10s && debug
 [browser_dbg_event-listeners-01.js]
 skip-if = e10s && debug
 [browser_dbg_event-listeners-02.js]
 skip-if = e10s && debug
 [browser_dbg_event-listeners-03.js]
deleted file mode 100644
--- a/devtools/client/shared/test/browser_dbg_addon-console.js
+++ /dev/null
@@ -1,151 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-/* import-globals-from helper_addons.js */
-Services.scriptloader.loadSubScript(
-  "chrome://mochitests/content/browser/devtools/client/shared/test/helper_addons.js",
-  this);
-
-var { DebuggerServer } = require("devtools/server/main");
-var { DebuggerClient } = require("devtools/shared/client/debugger-client");
-var { Toolbox } = require("devtools/client/framework/toolbox");
-var { Task } = require("devtools/shared/task");
-
-// Test that the we can see console messages from the add-on
-
-const ADDON_ID = "browser_dbg_addon4@tests.mozilla.org";
-const ADDON_PATH = "addon4.xpi";
-
-function getCachedMessages(webConsole) {
-  const deferred = getDeferredPromise().defer();
-  webConsole.getCachedMessages(["ConsoleAPI"], response => {
-    if (response.error) {
-      deferred.reject(response.error);
-      return;
-    }
-    deferred.resolve(response.messages);
-  });
-  return deferred.promise;
-}
-
-// Creates an add-on debugger for a given add-on. The returned AddonDebugger
-// object must be destroyed before finishing the test
-function initAddonDebugger(addonId) {
-  const addonDebugger = new AddonDebugger();
-  return addonDebugger.init(addonId).then(() => addonDebugger);
-}
-
-function AddonDebugger() {
-  this._onMessage = this._onMessage.bind(this);
-  this._onConsoleAPICall = this._onConsoleAPICall.bind(this);
-  EventEmitter.decorate(this);
-}
-
-AddonDebugger.prototype = {
-  init: Task.async(function* (addonId) {
-    info("Initializing an addon debugger panel.");
-
-    DebuggerServer.init();
-    DebuggerServer.registerAllActors();
-    DebuggerServer.allowChromeProcess = true;
-
-    this.frame = document.createElement("iframe");
-    this.frame.setAttribute("height", 400);
-    document.documentElement.appendChild(this.frame);
-    window.addEventListener("message", this._onMessage);
-
-    const transport = DebuggerServer.connectPipe();
-    this.client = new DebuggerClient(transport);
-
-    yield this.client.connect();
-
-    const addonTargetFront = yield this.client.mainRoot.getAddon({ id: addonId });
-
-    const targetOptions = {
-      activeTab: addonTargetFront,
-      client: this.client,
-      chrome: true,
-    };
-
-    const toolboxOptions = {
-      customIframe: this.frame,
-    };
-
-    this.target = yield TargetFactory.forRemoteTab(targetOptions);
-    const toolbox = yield gDevTools.showToolbox(
-      this.target, "jsdebugger", Toolbox.HostType.CUSTOM, toolboxOptions);
-
-    info("Addon debugger panel shown successfully.");
-
-    this.debuggerPanel = toolbox.getCurrentPanel();
-    yield this._attachConsole();
-  }),
-
-  destroy: Task.async(function* () {
-    yield this.client.close();
-    this.frame.remove();
-    window.removeEventListener("message", this._onMessage);
-  }),
-
-  _attachConsole: function() {
-    const deferred = getDeferredPromise().defer();
-    this.client.attachConsole(this.target.form.consoleActor, ["ConsoleAPI"])
-      .then(([aResponse, aWebConsoleClient]) => {
-        this.webConsole = aWebConsoleClient;
-        this.client.addListener("consoleAPICall", this._onConsoleAPICall);
-        deferred.resolve();
-      }, e => {
-        deferred.reject(e);
-      });
-    return deferred.promise;
-  },
-
-  _onConsoleAPICall: function(type, packet) {
-    if (packet.from != this.webConsole.actor) {
-      return;
-    }
-    this.emit("console", packet.message);
-  },
-
-  _onMessage: function(event) {
-    if (!event.data) {
-      return;
-    }
-    const msg = event.data;
-    switch (msg.name) {
-      case "toolbox-title":
-        this.title = msg.data.value;
-        break;
-    }
-  },
-};
-
-add_task(async function test() {
-  const addon = await addTemporaryAddon(ADDON_PATH);
-  const addonDebugger = await initAddonDebugger(ADDON_ID);
-
-  const webConsole = addonDebugger.webConsole;
-  const messages = await getCachedMessages(webConsole);
-  is(messages.length, 1, "Should be one cached message");
-  is(messages[0].arguments[0].type, "object", "Should have logged an object");
-  is(messages[0].arguments[0].preview.ownProperties.msg.value,
-    "Hello from the test add-on", "Should have got the right message");
-
-  const consolePromise = addonDebugger.once("console");
-
-  console.log("Bad message");
-  Services.obs.notifyObservers(null, "addon-test-ping");
-
-  const messageGrip = await consolePromise;
-  is(messageGrip.arguments[0].type, "object", "Should have logged an object");
-  is(messageGrip.arguments[0].preview.ownProperties.msg.value,
-    "Hello again", "Should have got the right message");
-
-  await addonDebugger.destroy();
-  await removeAddon(addon);
-  finish();
-});
--- a/devtools/client/shared/test/browser_dbg_listaddons.js
+++ b/devtools/client/shared/test/browser_dbg_listaddons.js
@@ -11,108 +11,51 @@ Services.scriptloader.loadSubScript(
   this);
 
 var { DebuggerServer } = require("devtools/server/main");
 var { DebuggerClient } = require("devtools/shared/client/debugger-client");
 
 /**
  * Make sure the listAddons request works as specified.
  */
-const ADDON1_ID = "jid1-oBAwBoE5rSecNg@jetpack";
-const ADDON1_PATH = "addon1.xpi";
-const ADDON2_ID = "jid1-qjtzNGV8xw5h2A@jetpack";
-const ADDON2_PATH = "addon2.xpi";
+const ADDON1_ID = "test-addon-1@mozilla.org";
+const ADDON1_PATH = "addons/test-addon-1/";
+const ADDON2_ID = "test-addon-2@mozilla.org";
+const ADDON2_PATH = "addons/test-addon-2/";
 
-var gAddon1, gAddon1Front, gAddon2, gClient;
-
-function test() {
+add_task(async function() {
   DebuggerServer.init();
   DebuggerServer.registerAllActors();
 
   const transport = DebuggerServer.connectPipe();
-  gClient = new DebuggerClient(transport);
-  gClient.connect().then(([aType, aTraits]) => {
-    is(aType, "browser",
-      "Root actor should identify itself as a browser.");
+  const client = new DebuggerClient(transport);
 
-    promise.resolve(null)
-      .then(testFirstAddon)
-      .then(testSecondAddon)
-      .then(testRemoveFirstAddon)
-      .then(testRemoveSecondAddon)
-      .then(() => gClient.close())
-      .then(finish)
-      .catch(error => {
-        ok(false, "Got an error: " + error.message + "\n" + error.stack);
-      });
-  });
-}
+  const [type] = await client.connect();
+  is(type, "browser", "Root actor should identify itself as a browser.");
+
+  let addonListChangedEvents = 0;
+  client.mainRoot.on("addonListChanged", () => addonListChangedEvents++);
 
-function testFirstAddon() {
-  let addonListChanged = false;
-  gClient.mainRoot.once("addonListChanged").then(() => {
-    addonListChanged = true;
-  });
-
-  return addTemporaryAddon(ADDON1_PATH).then(addon => {
-    gAddon1 = addon;
+  const addon1 = await addTemporaryAddon(ADDON1_PATH);
+  const addonFront1 = await client.mainRoot.getAddon({ id: ADDON1_ID });
+  is(addonListChangedEvents, 0, "Should not receive addonListChanged yet.");
+  ok(addonFront1, "Should find an addon actor for addon1.");
 
-    return gClient.mainRoot.getAddon({ id: ADDON1_ID }).then(front => {
-      ok(!addonListChanged, "Should not yet be notified that list of addons changed.");
-      ok(front, "Should find an addon actor for addon1.");
-      gAddon1Front = front;
-    });
-  });
-}
-
-function testSecondAddon() {
-  let addonListChanged = false;
-  gClient.mainRoot.once("addonListChanged").then(() => {
-    addonListChanged = true;
-  });
-
-  return addTemporaryAddon(ADDON2_PATH).then(addon => {
-    gAddon2 = addon;
+  const addon2 = await addTemporaryAddon(ADDON2_PATH);
+  const front1AfterAddingAddon2 = await client.mainRoot.getAddon({ id: ADDON1_ID });
+  const addonFront2 = await client.mainRoot.getAddon({ id: ADDON2_ID });
 
-    return gClient.mainRoot.getAddon({ id: ADDON1_ID }).then(front1 => {
-      return gClient.mainRoot.getAddon({ id: ADDON2_ID }).then(front2 => {
-        ok(addonListChanged, "Should be notified that list of addons changed.");
-        is(front1, gAddon1Front, "First addon's actor shouldn't have changed.");
-        ok(front2, "Should find a addon actor for the second addon.");
-      });
-    });
-  });
-}
-
-function testRemoveFirstAddon() {
-  let addonListChanged = false;
-  gClient.mainRoot.once("addonListChanged").then(() => {
-    addonListChanged = true;
-  });
+  is(addonListChangedEvents, 1, "Should have received an addonListChanged event.");
+  ok(addonFront2, "Should find an addon actor for addon2.");
+  is(addonFront1, front1AfterAddingAddon2, "Front for addon1 should be the same");
 
-  return removeAddon(gAddon1).then(() => {
-    return gClient.mainRoot.getAddon({ id: ADDON1_ID }).then(front => {
-      ok(addonListChanged, "Should be notified that list of addons changed.");
-      ok(!front, "Shouldn't find a addon actor for the first addon anymore.");
-    });
-  });
-}
-
-function testRemoveSecondAddon() {
-  let addonListChanged = false;
-  gClient.mainRoot.once("addonListChanged").then(() => {
-    addonListChanged = true;
-  });
+  await removeAddon(addon1);
+  const front1AfterRemove = await client.mainRoot.getAddon({ id: ADDON1_ID });
+  is(addonListChangedEvents, 2, "Should have received an addonListChanged event.");
+  ok(!front1AfterRemove, "Should no longer get a front for addon1");
 
-  return removeAddon(gAddon2).then(() => {
-    return gClient.mainRoot.getAddon({ id: ADDON2_ID }).then(front => {
-      ok(addonListChanged, "Should be notified that list of addons changed.");
-      ok(!front, "Shouldn't find a addon actor for the second addon anymore.");
-    });
-  });
-}
+  await removeAddon(addon2);
+  const front2AfterRemove = await client.mainRoot.getAddon({ id: ADDON2_ID });
+  is(addonListChangedEvents, 3, "Should have received an addonListChanged event.");
+  ok(!front2AfterRemove, "Should no longer get a front for addon1");
 
-registerCleanupFunction(function() {
-  gAddon1 = null;
-  gAddon1Front = null;
-  gAddon2 = null;
-  gClient = null;
+  await client.close();
 });
--- a/devtools/client/shared/test/browser_telemetry_sidebar.js
+++ b/devtools/client/shared/test/browser_telemetry_sidebar.js
@@ -15,16 +15,27 @@ const DATA = [
   {
     timestamp: null,
     category: "devtools.main",
     method: "sidepanel_changed",
     object: "inspector",
     value: null,
     extra: {
       oldpanel: "layoutview",
+      newpanel: "changesview",
+    },
+  },
+  {
+    timestamp: null,
+    category: "devtools.main",
+    method: "sidepanel_changed",
+    object: "inspector",
+    value: null,
+    extra: {
+      oldpanel: "changesview",
       newpanel: "animationinspector",
     },
   },
   {
     timestamp: null,
     category: "devtools.main",
     method: "sidepanel_changed",
     object: "inspector",
@@ -59,16 +70,27 @@ const DATA = [
   {
     timestamp: null,
     category: "devtools.main",
     method: "sidepanel_changed",
     object: "inspector",
     value: null,
     extra: {
       oldpanel: "computedview",
+      newpanel: "changesview",
+    },
+  },
+  {
+    timestamp: null,
+    category: "devtools.main",
+    method: "sidepanel_changed",
+    object: "inspector",
+    value: null,
+    extra: {
+      oldpanel: "changesview",
       newpanel: "animationinspector",
     },
   },
   {
     timestamp: null,
     category: "devtools.main",
     method: "sidepanel_changed",
     object: "inspector",
@@ -101,16 +123,19 @@ const DATA = [
     },
   },
 ];
 
 add_task(async function() {
   // Let's reset the counts.
   Services.telemetry.clearEvents();
 
+  // Ensure the Changes panel is enabled before running the tests.
+  await pushPref("devtools.inspector.changes.enabled", true);
+
   // Ensure no events have been logged
   const snapshot = Services.telemetry.snapshotEvents(OPTOUT, true);
   ok(!snapshot.parent, "No events have been logged for the main process");
 
   await addTab(TEST_URI);
   startTelemetry();
 
   const target = await TargetFactory.forTab(gBrowser.selectedTab);
@@ -125,17 +150,17 @@ add_task(async function() {
   gBrowser.removeCurrentTab();
 });
 
 function testSidebar(toolbox) {
   info("Testing sidebar");
 
   const inspector = toolbox.getCurrentPanel();
   let sidebarTools = ["computedview", "layoutview", "fontinspector",
-                      "animationinspector"];
+                      "animationinspector", "changesview"];
 
   // Concatenate the array with itself so that we can open each tool twice.
   sidebarTools = [...sidebarTools, ...sidebarTools];
 
   return new Promise(resolve => {
     // See TOOL_DELAY for why we need setTimeout here
     setTimeout(function selectSidebarTab() {
       const tool = sidebarTools.pop();
@@ -154,19 +179,21 @@ function testSidebar(toolbox) {
 function checkResults() {
   // For help generating these tests use generateTelemetryTests("DEVTOOLS_")
   // here.
   checkTelemetry("DEVTOOLS_INSPECTOR_OPENED_COUNT", "", {0: 1, 1: 0}, "array");
   checkTelemetry("DEVTOOLS_RULEVIEW_OPENED_COUNT", "", {0: 1, 1: 0}, "array");
   checkTelemetry("DEVTOOLS_COMPUTEDVIEW_OPENED_COUNT", "", {0: 2, 1: 0}, "array");
   checkTelemetry("DEVTOOLS_LAYOUTVIEW_OPENED_COUNT", "", {0: 3, 1: 0}, "array");
   checkTelemetry("DEVTOOLS_FONTINSPECTOR_OPENED_COUNT", "", {0: 2, 1: 0}, "array");
+  checkTelemetry("devtools.changesview.opened_count", "", 2, "scalar");
   checkTelemetry("DEVTOOLS_COMPUTEDVIEW_TIME_ACTIVE_SECONDS", "", null, "hasentries");
   checkTelemetry("DEVTOOLS_LAYOUTVIEW_TIME_ACTIVE_SECONDS", "", null, "hasentries");
   checkTelemetry("DEVTOOLS_FONTINSPECTOR_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+  checkTelemetry("DEVTOOLS_CHANGESVIEW_TIME_ACTIVE_SECONDS", "", null, "hasentries");
 }
 
 function checkEventTelemetry() {
   const snapshot = Services.telemetry.snapshotEvents(OPTOUT, true);
   const events = snapshot.parent.filter(event => event[1] === "devtools.main" &&
                                                   event[2] === "sidepanel_changed" &&
                                                   event[3] === "inspector" &&
                                                   event[4] === null
--- a/devtools/server/actors/accessibility/accessible.js
+++ b/devtools/server/actors/accessibility/accessible.js
@@ -1,30 +1,117 @@
 /* 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 { Ci } = require("chrome");
+const { Ci, Cu } = require("chrome");
 const { Actor, ActorClassWithSpec } = require("devtools/shared/protocol");
 const { accessibleSpec } = require("devtools/shared/specs/accessibility");
 
 loader.lazyRequireGetter(this, "getContrastRatioFor", "devtools/server/actors/utils/accessibility", true);
 loader.lazyRequireGetter(this, "isDefunct", "devtools/server/actors/utils/accessibility", true);
+loader.lazyRequireGetter(this, "findCssSelector", "devtools/shared/inspector/css-logic", true);
 
 const nsIAccessibleRelation = Ci.nsIAccessibleRelation;
 const RELATIONS_TO_IGNORE = new Set([
   nsIAccessibleRelation.RELATION_CONTAINING_APPLICATION,
   nsIAccessibleRelation.RELATION_CONTAINING_TAB_PANE,
   nsIAccessibleRelation.RELATION_CONTAINING_WINDOW,
   nsIAccessibleRelation.RELATION_PARENT_WINDOW_OF,
   nsIAccessibleRelation.RELATION_SUBWINDOW_OF,
 ]);
 
+const STATE_DEFUNCT = Ci.nsIAccessibleStates.EXT_STATE_DEFUNCT;
+const CSS_TEXT_SELECTOR = "#text";
+
+/**
+ * Get node inforamtion such as nodeType and the unique CSS selector for the node.
+ * @param  {DOMNode} node
+ *         Node for which to get the information.
+ * @return {Object}
+ *         Information about the type of the node and how to locate it.
+ */
+function getNodeDescription(node) {
+  if (!node || Cu.isDeadWrapper(node)) {
+    return { nodeType: undefined, nodeCssSelector: "" };
+  }
+
+  const { nodeType } = node;
+  return {
+    nodeType,
+    // If node is a text node, we find a unique CSS selector for its parent and add a
+    // CSS_TEXT_SELECTOR postfix to indicate that it's a text node.
+    nodeCssSelector: nodeType === Node.TEXT_NODE ?
+      `${findCssSelector(node.parentNode)}${CSS_TEXT_SELECTOR}` :
+      findCssSelector(node),
+  };
+}
+
+/**
+ * Get a snapshot of the nsIAccessible object including its subtree. None of the subtree
+ * queried here is cached via accessible walker's refMap.
+ * @param  {nsIAccessible} acc
+ *         Accessible object to take a snapshot of.
+ * @param  {nsIAccessibilityService} a11yService
+ *         Accessibility service instance in the current process, used to get localized
+ *         string representation of various accessible properties.
+ * @return {JSON}
+ *         JSON snapshot of the accessibility tree with root at current accessible.
+ */
+function getSnapshot(acc, a11yService) {
+  if (isDefunct(acc)) {
+    return {
+      states: [ a11yService.getStringStates(0, STATE_DEFUNCT) ],
+    };
+  }
+
+  const actions = [];
+  for (let i = 0; i < acc.actionCount; i++) {
+    actions.push(acc.getActionDescription(i));
+  }
+
+  const attributes = {};
+  if (acc.attributes) {
+    for (const { key, value } of acc.attributes.enumerate()) {
+      attributes[key] = value;
+    }
+  }
+
+  const state = {};
+  const extState = {};
+  acc.getState(state, extState);
+  const states = [
+    ...a11yService.getStringStates(state.value, extState.value),
+  ];
+
+  const children = [];
+  for (let child = acc.firstChild; child; child = child.nextSibling) {
+    children.push(getSnapshot(child, a11yService));
+  }
+
+  const { nodeType, nodeCssSelector } = getNodeDescription(acc.DOMNode);
+  return {
+    name: acc.name,
+    role: a11yService.getStringRole(acc.role),
+    actions,
+    value: acc.value,
+    nodeCssSelector,
+    nodeType,
+    description: acc.description,
+    keyboardShortcut: acc.accessKey || acc.keyboardShortcut,
+    childCount: acc.childCount,
+    indexInParent: acc.indexInParent,
+    states,
+    children,
+    attributes,
+  };
+}
+
 /**
  * The AccessibleActor provides information about a given accessible object: its
  * role, name, states, etc.
  */
 const AccessibleActor = ActorClassWithSpec(accessibleSpec, {
   initialize(walker, rawAccessible) {
     Actor.prototype.initialize.call(this, walker.conn);
     this.walker = walker;
@@ -311,11 +398,15 @@ const AccessibleActor = ActorClassWithSp
    * @return {Object|null}
    *         Audit results for the accessible object.
   */
   get audit() {
     return this.isDefunct ? null : {
       contrastRatio: this._getContrastRatio(),
     };
   },
+
+  snapshot() {
+    return getSnapshot(this.rawAccessible, this.walker.a11yService);
+  },
 });
 
 exports.AccessibleActor = AccessibleActor;
--- a/devtools/server/tests/browser/browser_accessibility_node.js
+++ b/devtools/server/tests/browser/browser_accessibility_node.js
@@ -61,13 +61,49 @@ add_task(async function() {
   is(relations[0].targets[0], controlAccessibleFront,
      "Label is a label for control accessible front");
   is(relations[1].type, "containing document",
      "Label has a containing document relation");
   is(relations[1].targets.length, 1, "Label is contained by just one document");
   is(relations[1].targets[0], docAccessibleFront,
      "Label's containing document is a root document");
 
+  info("Snapshot");
+  const snapshot = await controlAccessibleFront.snapshot();
+  Assert.deepEqual(snapshot, {
+    "name": "Label",
+    "role": "entry",
+    "actions": [ "Activate" ],
+    "value": "",
+    "nodeCssSelector": "#control",
+    "nodeType": 1,
+    "description": "",
+    "keyboardShortcut": "",
+    "childCount": 0,
+    "indexInParent": 1,
+    "states": [
+      "focusable",
+      "autocompletion",
+      "editable",
+      "opaque",
+      "single line",
+      "enabled",
+      "sensitive",
+    ],
+    "children": [],
+    "attributes": {
+      "margin-left": "0px",
+      "text-align": "start",
+      "text-indent": "0px",
+      "id": "control",
+      "tag": "input",
+      "margin-top": "0px",
+      "margin-bottom": "0px",
+      "margin-right": "0px",
+      "display": "inline",
+      "explicit-name": "true",
+    }});
+
   await accessibility.disable();
   await waitForA11yShutdown();
   await target.destroy();
   gBrowser.removeCurrentTab();
 });
--- a/devtools/shared/specs/accessibility.js
+++ b/devtools/shared/specs/accessibility.js
@@ -87,16 +87,22 @@ const accessibleSpec = generateActorSpec
       },
     },
     getRelations: {
       request: {},
       response: {
         relations: RetVal("array:accessibleRelation"),
       },
     },
+    snapshot: {
+      request: {},
+      response: {
+        snapshot: RetVal("json"),
+      },
+    },
   },
 });
 
 const accessibleWalkerSpec = generateActorSpec({
   typeName: "accessiblewalker",
 
   events: {
     "accessible-destroy": {
--- a/dom/base/nsViewportInfo.h
+++ b/dom/base/nsViewportInfo.h
@@ -7,17 +7,17 @@
 
 #include <stdint.h>
 #include "mozilla/Attributes.h"
 #include "Units.h"
 
 /**
  * Default values for the nsViewportInfo class.
  */
-static const mozilla::LayoutDeviceToScreenScale kViewportMinScale(0.1f);
+static const mozilla::LayoutDeviceToScreenScale kViewportMinScale(0.25f);
 static const mozilla::LayoutDeviceToScreenScale kViewportMaxScale(10.0f);
 static const mozilla::CSSIntSize kViewportMinSize(200, 40);
 static const mozilla::CSSIntSize kViewportMaxSize(10000, 10000);
 
 /**
  * Information retrieved from the <meta name="viewport"> tag. See
  * nsIDocument::GetViewportInfo for more information on this functionality.
  */
--- a/dom/base/test/test_meta_viewport0.html
+++ b/dom/base/test/test_meta_viewport0.html
@@ -16,23 +16,23 @@
     function fuzzeq(a, b, msg) {
       ok(Math.abs(a - b) < 1e-6, msg);
     }
 
     add_task(async function test1() {
       await SpecialPowers.pushPrefEnv(scaleRatio(1.0));
 
       let info = getViewportInfo(800, 480);
-      fuzzeq(info.defaultZoom, 0.1, "initial scale is unspecified");
-      fuzzeq(info.minZoom, 0.1,     "minimum scale defaults to the absolute minimum");
-      is(info.maxZoom,     10,      "maximum scale defaults to the absolute maximum");
-      is(info.width,       980,     "width is the default width");
-      is(info.height,      588,     "height is proportional to displayHeight");
-      is(info.autoSize,    false,   "autoSize is disabled by default");
-      is(info.allowZoom,   true,    "zooming is enabled by default");
+      fuzzeq(info.defaultZoom, 0.25, "initial scale is clamped to the default mimumim scale");
+      fuzzeq(info.minZoom, 0.25,     "minimum scale defaults to the absolute minimum");
+      is(info.maxZoom,     10,       "maximum scale defaults to the absolute maximum");
+      is(info.width,       980,      "width is the default width");
+      is(info.height,      588,      "height is proportional to displayHeight");
+      is(info.autoSize,    false,    "autoSize is disabled by default");
+      is(info.allowZoom,   true,     "zooming is enabled by default");
 
       info = getViewportInfo(490, 600);
       is(info.width,       980,     "width is still the default width");
       is(info.height,      1200,    "height is proportional to the new displayHeight");
     });
 
     add_task(async function test2() {
       await SpecialPowers.pushPrefEnv(scaleRatio(1.5));
--- a/dom/tests/mochitest/general/test_interfaces.js
+++ b/dom/tests/mochitest/general/test_interfaces.js
@@ -40,18 +40,18 @@ const isInsecureContext = !window.isSecu
 // IMPORTANT: Do not change this list without review from
 //            a JavaScript Engine peer!
 var ecmaGlobals =
   [
     {name: "Array", insecureContext: true},
     {name: "ArrayBuffer", insecureContext: true},
     {name: "Atomics", insecureContext: true, disabled: true},
     {name: "Boolean", insecureContext: true},
-    {name: "ByteLengthQueuingStrategy", insecureContext: true, nightly: true},
-    {name: "CountQueuingStrategy", insecureContext: true, nightly: true},
+    {name: "ByteLengthQueuingStrategy", insecureContext: true},
+    {name: "CountQueuingStrategy", insecureContext: true},
     {name: "DataView", insecureContext: true},
     {name: "Date", insecureContext: true},
     {name: "Error", insecureContext: true},
     {name: "EvalError", insecureContext: true},
     {name: "Float32Array", insecureContext: true},
     {name: "Float64Array", insecureContext: true},
     {name: "Function", insecureContext: true},
     {name: "Infinity", insecureContext: true},
@@ -64,17 +64,17 @@ var ecmaGlobals =
     {name: "Map", insecureContext: true},
     {name: "Math", insecureContext: true},
     {name: "NaN", insecureContext: true},
     {name: "Number", insecureContext: true},
     {name: "Object", insecureContext: true},
     {name: "Promise", insecureContext: true},
     {name: "Proxy", insecureContext: true},
     {name: "RangeError", insecureContext: true},
-    {name: "ReadableStream", insecureContext: true, nightly: true},
+    {name: "ReadableStream", insecureContext: true},
     {name: "ReferenceError", insecureContext: true},
     {name: "Reflect", insecureContext: true},
     {name: "RegExp", insecureContext: true},
     {name: "Set", insecureContext: true},
     {name: "SharedArrayBuffer", insecureContext: true, disabled: true},
     {name: "String", insecureContext: true},
     {name: "Symbol", insecureContext: true},
     {name: "SyntaxError", insecureContext: true},
--- a/dom/workers/test/test_worker_interfaces.js
+++ b/dom/workers/test/test_worker_interfaces.js
@@ -28,18 +28,18 @@
 // IMPORTANT: Do not change this list without review from
 //            a JavaScript Engine peer!
 var ecmaGlobals =
   [
     {name: "Array", insecureContext: true},
     {name: "ArrayBuffer", insecureContext: true},
     {name: "Atomics", insecureContext: true, disabled: true},
     {name: "Boolean", insecureContext: true},
-    {name: "ByteLengthQueuingStrategy", insecureContext: true, nightly: true},
-    {name: "CountQueuingStrategy", insecureContext: true, nightly: true},
+    {name: "ByteLengthQueuingStrategy", insecureContext: true},
+    {name: "CountQueuingStrategy", insecureContext: true},
     {name: "DataView", insecureContext: true},
     {name: "Date", insecureContext: true},
     {name: "Error", insecureContext: true},
     {name: "EvalError", insecureContext: true},
     {name: "Float32Array", insecureContext: true},
     {name: "Float64Array", insecureContext: true},
     {name: "Function", insecureContext: true},
     {name: "Infinity", insecureContext: true},
@@ -54,17 +54,17 @@ var ecmaGlobals =
     {name: "MediaCapabilitiesInfo", insecureContext: true},
     {name: "Math", insecureContext: true},
     {name: "NaN", insecureContext: true},
     {name: "Number", insecureContext: true},
     {name: "Object", insecureContext: true},
     {name: "Promise", insecureContext: true},
     {name: "Proxy", insecureContext: true},
     {name: "RangeError", insecureContext: true},
-    {name: "ReadableStream", insecureContext: true, nightly: true},
+    {name: "ReadableStream", insecureContext: true},
     {name: "ReferenceError", insecureContext: true},
     {name: "Reflect", insecureContext: true},
     {name: "RegExp", insecureContext: true},
     {name: "Set", insecureContext: true},
     {name: "SharedArrayBuffer", insecureContext: true, disabled: true},
     {name: "String", insecureContext: true},
     {name: "Symbol", insecureContext: true},
     {name: "SyntaxError", insecureContext: true},
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-a2b4202242d937d328eda21c2d9fcfece609283e
+d771bae9f824769c73419fdc3ccffa2bdc47c3e4
--- a/gfx/wr/webrender/src/clip_scroll_tree.rs
+++ b/gfx/wr/webrender/src/clip_scroll_tree.rs
@@ -5,17 +5,17 @@
 use api::{ExternalScrollId, LayoutPoint, LayoutRect, LayoutVector2D};
 use api::{PipelineId, ScrollClamping, ScrollNodeState, ScrollLocation};
 use api::{LayoutSize, LayoutTransform, PropertyBinding, ScrollSensitivity, WorldPoint};
 use gpu_types::TransformPalette;
 use internal_types::{FastHashMap, FastHashSet};
 use print_tree::{PrintTree, PrintTreePrinter};
 use scene::SceneProperties;
 use smallvec::SmallVec;
-use spatial_node::{ScrollFrameInfo, SpatialNode, SpatialNodeType, StickyFrameInfo};
+use spatial_node::{ScrollFrameInfo, SpatialNode, SpatialNodeType, StickyFrameInfo, ScrollFrameKind};
 use util::{LayoutToWorldFastTransform, ScaleOffset};
 
 pub type ScrollStates = FastHashMap<ExternalScrollId, ScrollFrameInfo>;
 
 /// An id that identifies coordinate systems in the ClipScrollTree. Each
 /// coordinate system has an id and those ids will be shared when the coordinates
 /// system are the same or are in the same axis-aligned space. This allows
 /// for optimizing mask generation.
@@ -321,24 +321,26 @@ impl ClipScrollTree {
     pub fn add_scroll_frame(
         &mut self,
         parent_index: SpatialNodeIndex,
         external_id: Option<ExternalScrollId>,
         pipeline_id: PipelineId,
         frame_rect: &LayoutRect,
         content_size: &LayoutSize,
         scroll_sensitivity: ScrollSensitivity,
+        frame_kind: ScrollFrameKind,
     ) -> SpatialNodeIndex {
         let node = SpatialNode::new_scroll_frame(
             pipeline_id,
             parent_index,
             external_id,
             frame_rect,
             content_size,
             scroll_sensitivity,
+            frame_kind,
         );
         self.add_spatial_node(node)
     }
 
     pub fn add_reference_frame(
         &mut self,
         parent_index: Option<SpatialNodeIndex>,
         source_transform: Option<PropertyBinding<LayoutTransform>>,
@@ -430,16 +432,63 @@ impl ClipScrollTree {
         }
     }
 
     pub fn print_with<T: PrintTreePrinter>(&self, pt: &mut T) {
         if !self.spatial_nodes.is_empty() {
             self.print_node(self.root_reference_frame_index(), pt);
         }
     }
+
+    /// Return true if this is a guaranteed identity transform. This
+    /// is conservative, it assumes not identity if a property
+    /// binding animation, or scroll frame is found, for example.
+    pub fn node_is_identity(
+        &self,
+        spatial_node_index: SpatialNodeIndex,
+    ) -> bool {
+        let mut current = spatial_node_index;
+
+        while current != ROOT_SPATIAL_NODE_INDEX {
+            let node = &self.spatial_nodes[current.0];
+
+            match node.node_type {
+                SpatialNodeType::ReferenceFrame(ref info) => {
+                    if !info.source_perspective.is_identity() {
+                        return false;
+                    }
+
+                    match info.source_transform {
+                        PropertyBinding::Value(transform) => {
+                            if transform != LayoutTransform::identity() {
+                                return false;
+                            }
+                        }
+                        PropertyBinding::Binding(..) => {
+                            // Assume not identity since it may change with animation.
+                            return false;
+                        }
+                    }
+                }
+                SpatialNodeType::ScrollFrame(ref info) => {
+                    // Assume not identity since it may change with scrolling.
+                    if let ScrollFrameKind::Explicit = info.frame_kind {
+                        return false;
+                    }
+                }
+                SpatialNodeType::StickyFrame(..) => {
+                    // Assume not identity since it may change with scrolling.
+                    return false;
+                }
+            }
+            current = node.parent.unwrap();
+        }
+
+        true
+    }
 }
 
 #[cfg(test)]
 fn add_reference_frame(
     cst: &mut ClipScrollTree,
     parent: Option<SpatialNodeIndex>,
     transform: LayoutTransform,
     origin_in_parent_reference_frame: LayoutVector2D,
--- a/gfx/wr/webrender/src/display_list_flattener.rs
+++ b/gfx/wr/webrender/src/display_list_flattener.rs
@@ -25,17 +25,17 @@ use picture::{Picture3DContext, PictureC
 use prim_store::{PrimitiveInstance, PrimitiveDataInterner, PrimitiveKeyKind, RadialGradientParams};
 use prim_store::{PrimitiveKey, PrimitiveSceneData, PrimitiveInstanceKind, GradientStopKey, NinePatchDescriptor};
 use prim_store::{PrimitiveDataHandle, PrimitiveStore, PrimitiveStoreStats, LineDecorationCacheKey};
 use prim_store::{ScrollNodeAndClipChain, PictureIndex, register_prim_chase_id, get_line_decoration_sizes};
 use render_backend::{DocumentView};
 use resource_cache::{FontInstanceMap, ImageRequest};
 use scene::{Scene, ScenePipeline, StackingContextHelpers};
 use scene_builder::DocumentResources;
-use spatial_node::{StickyFrameInfo};
+use spatial_node::{StickyFrameInfo, ScrollFrameKind};
 use std::{f32, mem};
 use std::collections::vec_deque::VecDeque;
 use tiling::{CompositeOps};
 use util::{MaxRect, VecHelper};
 
 #[derive(Debug, Copy, Clone)]
 struct ClipNode {
     id: ClipChainId,
@@ -377,16 +377,17 @@ impl<'a> DisplayListFlattener<'a> {
         self.add_scroll_frame(
             info.scroll_frame_id,
             info.clip_id,
             info.external_id,
             pipeline_id,
             &frame_rect,
             &content_rect.size,
             info.scroll_sensitivity,
+            ScrollFrameKind::Explicit,
         );
     }
 
     fn flatten_reference_frame(
         &mut self,
         traversal: &mut BuiltDisplayListIter<'a>,
         pipeline_id: PipelineId,
         item: &DisplayItemRef,
@@ -494,16 +495,17 @@ impl<'a> DisplayListFlattener<'a> {
         self.add_scroll_frame(
             ClipId::root_scroll_node(iframe_pipeline_id),
             ClipId::root_reference_frame(iframe_pipeline_id),
             Some(ExternalScrollId(0, iframe_pipeline_id)),
             iframe_pipeline_id,
             &iframe_rect,
             &pipeline.content_size,
             ScrollSensitivity::ScriptAndInputEvents,
+            ScrollFrameKind::PipelineRoot,
         );
 
         self.flatten_root(
             pipeline,
             &iframe_rect.size,
         );
 
         self.pipeline_clip_chain_stack.pop();
@@ -1073,16 +1075,35 @@ impl<'a> DisplayListFlattener<'a> {
             transform_style,
             context_3d,
         });
     }
 
     pub fn pop_stacking_context(&mut self) {
         let stacking_context = self.sc_stack.pop().unwrap();
 
+        // If we encounter a stacking context that is effectively a no-op, then instead
+        // of creating a picture, just append the primitive list to the parent stacking
+        // context as a short cut. This serves two purposes:
+        // (a) It's an optimization to reduce picture count and allocations, as display lists
+        //     often contain a lot of these stacking contexts that don't require pictures or
+        //     off-screen surfaces.
+        // (b) It's useful for the initial version of picture caching in gecko, by enabling
+        //     is to just look for interesting scroll roots on the root stacking context,
+        //     without having to consider cuts at stacking context boundaries.
+        if let Some(parent_sc) = self.sc_stack.last_mut() {
+            if stacking_context.is_redundant(
+                parent_sc,
+                self.clip_scroll_tree,
+            ) {
+                parent_sc.primitives.extend(stacking_context.primitives);
+                return;
+            }
+        }
+
         // An arbitrary large clip rect. For now, we don't
         // specify a clip specific to the stacking context.
         // However, now that they are represented as Picture
         // primitives, we can apply any kind of clip mask
         // to them, as for a normal primitive. This is needed
         // to correctly handle some CSS cases (see #1957).
         let max_clip = LayoutRect::max_rect();
 
@@ -1339,16 +1360,17 @@ impl<'a> DisplayListFlattener<'a> {
         self.add_scroll_frame(
             ClipId::root_scroll_node(pipeline_id),
             ClipId::root_reference_frame(pipeline_id),
             Some(ExternalScrollId(0, pipeline_id)),
             pipeline_id,
             &LayoutRect::new(LayoutPoint::zero(), *viewport_size),
             content_size,
             ScrollSensitivity::ScriptAndInputEvents,
+            ScrollFrameKind::PipelineRoot,
         );
     }
 
     pub fn add_clip_node<I>(
         &mut self,
         new_node_id: ClipId,
         parent: &ClipAndScrollInfo,
         clip_region: ClipRegion<I>,
@@ -1450,25 +1472,27 @@ impl<'a> DisplayListFlattener<'a> {
         &mut self,
         new_node_id: ClipId,
         parent_id: ClipId,
         external_id: Option<ExternalScrollId>,
         pipeline_id: PipelineId,
         frame_rect: &LayoutRect,
         content_size: &LayoutSize,
         scroll_sensitivity: ScrollSensitivity,
+        frame_kind: ScrollFrameKind,
     ) -> SpatialNodeIndex {
         let parent_node_index = self.id_to_index_mapper.get_spatial_node_index(parent_id);
         let node_index = self.clip_scroll_tree.add_scroll_frame(
             parent_node_index,
             external_id,
             pipeline_id,
             frame_rect,
             content_size,
             scroll_sensitivity,
+            frame_kind,
         );
         self.id_to_index_mapper.map_spatial_node(new_node_id, node_index);
         self.id_to_index_mapper.map_to_parent_clip_chain(new_node_id, &parent_id);
         node_index
     }
 
     pub fn push_shadow(
         &mut self,
@@ -2180,16 +2204,61 @@ struct FlattenedStackingContext {
 }
 
 impl FlattenedStackingContext {
     /// Return true if the stacking context has a valid preserve-3d property
     pub fn is_3d(&self) -> bool {
         self.transform_style == TransformStyle::Preserve3D && self.composite_ops.is_empty()
     }
 
+    /// Return true if the stacking context isn't needed.
+    pub fn is_redundant(
+        &self,
+        parent: &FlattenedStackingContext,
+        clip_scroll_tree: &ClipScrollTree,
+    ) -> bool {
+        // Any 3d context is required
+        if let Picture3DContext::In { .. } = self.context_3d {
+            return false;
+        }
+
+        // If there are filters / mix-blend-mode
+        if !self.composite_ops.is_empty() {
+            return false;
+        }
+
+        // If backface visibility is different
+        if self.primitive_data_handle.uid() != parent.primitive_data_handle.uid() {
+            return false;
+        }
+
+        // If rasterization space is different
+        if self.requested_raster_space != parent.requested_raster_space {
+            return false;
+        }
+
+        // If different clipp chains
+        if self.clip_chain_id != parent.clip_chain_id {
+            return false;
+        }
+
+        // If need to isolate in surface due to clipping / mix-blend-mode
+        if self.should_isolate {
+            return false;
+        }
+
+        // If represents a transform, it may affect backface visibility of children
+        if !clip_scroll_tree.node_is_identity(self.spatial_node_index) {
+            return false;
+        }
+
+        // It is redundant!
+        true
+    }
+
     /// For a Preserve3D context, cut the sequence of the immediate flat children
     /// recorded so far and generate a picture from them.
     pub fn cut_flat_item_sequence(
         &mut self,
         prim_store: &mut PrimitiveStore,
         prim_interner: &PrimitiveDataInterner,
         clip_store: &ClipStore,
     ) -> Option<PrimitiveInstance> {
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -770,17 +770,17 @@ impl TileCache {
 
                 // Try to reuse cached tiles from the previous scene in this new
                 // scene, if possible.
                 if !resource_cache.texture_cache.is_allocated(&tile.handle) {
                     // See if we have a retained tile from last scene that matches the
                     // exact content of this tile.
                     if let Some(handle) = retained_tiles.remove(&tile.descriptor) {
                         // Only use if not evicted from texture cache in the meantime.
-                        if !resource_cache.texture_cache.request(&handle, gpu_cache) {
+                        if resource_cache.texture_cache.is_allocated(&handle) {
                             // We found a matching tile from the previous scene, so use it!
                             tile.handle = handle;
                             tile.is_valid = true;
                             // We know that the hash key of the descriptor validates that
                             // the local transforms in this tile exactly match the value
                             // of the current relative transforms needed for this tile,
                             // so we can mark those transforms as valid to avoid the
                             // retained tile being invalidated below.
@@ -842,22 +842,29 @@ impl TileCache {
                 }
 
                 // Check if this tile is actually visible.
                 let tile_world_rect = world_mapper
                     .map(&tile_rect)
                     .expect("bug: unable to map tile to world coords");
                 tile.is_visible = frame_context.screen_world_rect.intersects(&tile_world_rect);
 
-                // If we have an invalid tile, which is also visible, add it to the
-                // dirty rect we will need to draw.
-                if !tile.is_valid && tile.is_visible && tile.in_use {
-                    dirty_rect = dirty_rect.union(&tile_rect);
-                    tile_offset.x = tile_offset.x.min(x);
-                    tile_offset.y = tile_offset.y.min(y);
+                if tile.is_visible && tile.in_use {
+                    // Ensure we request the texture cache handle for this tile
+                    // each frame it will be used so the texture cache doesn't
+                    // decide to evict tiles that we currently want to use.
+                    resource_cache.texture_cache.request(&tile.handle, gpu_cache);
+
+                    // If we have an invalid tile, which is also visible, add it to the
+                    // dirty rect we will need to draw.
+                    if !tile.is_valid {
+                        dirty_rect = dirty_rect.union(&tile_rect);
+                        tile_offset.x = tile_offset.x.min(x);
+                        tile_offset.y = tile_offset.y.min(y);
+                    }
                 }
             }
         }
 
         self.dirty_region = if dirty_rect.is_empty() {
             None
         } else {
             let dirty_world_rect = world_mapper.map(&dirty_rect).expect("todo");
--- a/gfx/wr/webrender/src/resource_cache.rs
+++ b/gfx/wr/webrender/src/resource_cache.rs
@@ -1058,16 +1058,20 @@ impl ResourceCache {
                         }
                         None => blob_size(template.descriptor.size).into(),
                     },
                     format: template.descriptor.format,
                 };
 
                 assert!(!descriptor.rect.is_empty());
 
+                if !self.blob_image_templates.contains_key(&request.key) {
+                    panic!("already missing blob image key {:?} deleted: {:?}", request, self.deleted_blob_keys);
+                }
+
                 self.missing_blob_images.push(
                     BlobImageParams {
                         request,
                         descriptor,
                         dirty_rect: DirtyRect::All,
                     }
                 );
             }
--- a/gfx/wr/webrender/src/spatial_node.rs
+++ b/gfx/wr/webrender/src/spatial_node.rs
@@ -91,25 +91,27 @@ impl SpatialNode {
 
     pub fn new_scroll_frame(
         pipeline_id: PipelineId,
         parent_index: SpatialNodeIndex,
         external_id: Option<ExternalScrollId>,
         frame_rect: &LayoutRect,
         content_size: &LayoutSize,
         scroll_sensitivity: ScrollSensitivity,
+        frame_kind: ScrollFrameKind,
     ) -> Self {
         let node_type = SpatialNodeType::ScrollFrame(ScrollFrameInfo::new(
                 *frame_rect,
                 scroll_sensitivity,
                 LayoutSize::new(
                     (content_size.width - frame_rect.size.width).max(0.0),
                     (content_size.height - frame_rect.size.height).max(0.0)
                 ),
                 external_id,
+                frame_kind,
             )
         );
 
         Self::new(pipeline_id, Some(parent_index), node_type)
     }
 
     pub fn new_reference_frame(
         parent_index: Option<SpatialNodeIndex>,
@@ -553,16 +555,24 @@ impl SpatialNode {
     pub fn matches_external_id(&self, external_id: ExternalScrollId) -> bool {
         match self.node_type {
             SpatialNodeType::ScrollFrame(info) if info.external_id == Some(external_id) => true,
             _ => false,
         }
     }
 }
 
+/// Defines whether we have an implicit scroll frame for a pipeline root,
+/// or an explicitly defined scroll frame from the display list.
+#[derive(Copy, Clone, Debug)]
+pub enum ScrollFrameKind {
+    PipelineRoot,
+    Explicit,
+}
+
 #[derive(Copy, Clone, Debug)]
 pub struct ScrollFrameInfo {
     /// The rectangle of the viewport of this scroll frame. This is important for
     /// positioning of items inside child StickyFrames.
     pub viewport_rect: LayoutRect,
 
     pub offset: LayoutVector2D,
     pub scroll_sensitivity: ScrollSensitivity,
@@ -570,32 +580,42 @@ pub struct ScrollFrameInfo {
     /// Amount that this ScrollFrame can scroll in both directions.
     pub scrollable_size: LayoutSize,
 
     /// An external id to identify this scroll frame to API clients. This
     /// allows setting scroll positions via the API without relying on ClipsIds
     /// which may change between frames.
     pub external_id: Option<ExternalScrollId>,
 
+    /// Stores whether this is a scroll frame added implicitly by WR when adding
+    /// a pipeline (either the root or an iframe). We need to exclude these
+    /// when searching for scroll roots we care about for picture caching.
+    /// TODO(gw): I think we can actually completely remove the implicit
+    ///           scroll frame being added by WR, and rely on the embedder
+    ///           to define scroll frames. However, that involves API changes
+    ///           so we will use this as a temporary hack!
+    pub frame_kind: ScrollFrameKind,
 }
 
 /// Manages scrolling offset.
 impl ScrollFrameInfo {
     pub fn new(
         viewport_rect: LayoutRect,
         scroll_sensitivity: ScrollSensitivity,
         scrollable_size: LayoutSize,
         external_id: Option<ExternalScrollId>,
+        frame_kind: ScrollFrameKind,
     ) -> ScrollFrameInfo {
         ScrollFrameInfo {
             viewport_rect,
             offset: LayoutVector2D::zero(),
             scroll_sensitivity,
             scrollable_size,
             external_id,
+            frame_kind,
         }
     }
 
     pub fn sensitive_to_input_events(&self) -> bool {
         match self.scroll_sensitivity {
             ScrollSensitivity::ScriptAndInputEvents => true,
             ScrollSensitivity::Script => false,
         }
@@ -606,16 +626,17 @@ impl ScrollFrameInfo {
         old_scroll_info: &ScrollFrameInfo
     ) -> ScrollFrameInfo {
         ScrollFrameInfo {
             viewport_rect: self.viewport_rect,
             offset: old_scroll_info.offset,
             scroll_sensitivity: self.scroll_sensitivity,
             scrollable_size: self.scrollable_size,
             external_id: self.external_id,
+            frame_kind: self.frame_kind,
         }
     }
 }
 
 /// Contains information about reference frames.
 #[derive(Copy, Clone, Debug)]
 pub struct ReferenceFrameInfo {
     /// The source transform and perspective matrices provided by the stacking context
--- a/gfx/wr/webrender/src/util.rs
+++ b/gfx/wr/webrender/src/util.rs
@@ -577,16 +577,28 @@ impl<Src, Dst> FastTransform<Src, Dst> {
 
     pub fn is_invertible(&self) -> bool {
         match *self {
             FastTransform::Offset(..) => true,
             FastTransform::Transform { ref inverse, .. } => inverse.is_some(),
         }
     }
 
+    /// Return true if this is an identity transform
+    pub fn is_identity(&self)-> bool {
+        match *self {
+            FastTransform::Offset(offset) => {
+                offset == TypedVector2D::zero()
+            }
+            FastTransform::Transform { ref transform, .. } => {
+                *transform == TypedTransform3D::identity()
+            }
+        }
+    }
+
     #[inline(always)]
     pub fn pre_mul<NewSrc>(
         &self,
         other: &FastTransform<NewSrc, Src>
     ) -> FastTransform<NewSrc, Dst> {
         match (self, other) {
             (&FastTransform::Offset(ref offset), &FastTransform::Offset(ref other_offset)) => {
                 let offset = TypedVector2D::from_untyped(&offset.to_untyped());
--- a/gfx/wr/wrench/reftests/clip/fixed-position-clipping-ref.yaml
+++ b/gfx/wr/wrench/reftests/clip/fixed-position-clipping-ref.yaml
@@ -1,15 +1,15 @@
 ---
 root:
   items:
     -
       bounds: [10, 10, 100, 100]
       clip-rect: [10, 10, 100, 100]
       type: rect
-      color: 0 256 0 1.0
+      color: 0 255 0 1.0
     -
       bounds: [110, 10, 100, 100]
       clip-rect: [110, 10, 100, 100]
       type: rect
-      color: 0 256 0 1.0
+      color: 0 255 0 1.0
   id: [0, 1]
 pipelines: []
--- a/gfx/wr/wrench/reftests/clip/fixed-position-clipping.yaml
+++ b/gfx/wr/wrench/reftests/clip/fixed-position-clipping.yaml
@@ -17,17 +17,17 @@ root:
         clip-and-scroll: root-reference-frame
         type: stacking-context
         items:
           -
             bounds: [0, 0, 100, 100]
             clip-rect: [0, 0, 100, 100]
             clip-and-scroll: root-reference-frame
             type: rect
-            color: 0 256 0 1.0
+            color: 0 255 0 1.0
     # The same test as above, except this time the stacking context also starts its
     # own reference frame.
     -
       clip-rect: [115, 15, 30, 30]
       type: scroll-frame
       content-size: [60, 60]
       bounds: [115, 15, 30, 30]
       items:
@@ -39,11 +39,11 @@ root:
         type: stacking-context
         transform: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]
         items:
           -
             bounds: [0, 0, 100, 100]
             clip-rect: [0, 0, 100, 100]
             clip-and-scroll: 4
             type: rect
-            color: 0 256 0 1.0
+            color: 0 255 0 1.0
   id: [0, 1]
 pipelines: []
--- a/js/src/gc/Zone.cpp
+++ b/js/src/gc/Zone.cpp
@@ -254,17 +254,17 @@ void Zone::discardJitCode(FreeOp* fop, b
       script->maybeReleaseTypes();
     }
 
     // The optimizedStubSpace will be purged below so make sure ICScript
     // doesn't point into it. We do this after (potentially) releasing types
     // because TypeScript contains the ICScript* and there's no need to
     // purge stubs if we just destroyed the Typescript.
     if (discardBaselineCode && script->hasICScript()) {
-      script->icScript()->purgeOptimizedStubs(script->zone());
+      script->icScript()->purgeOptimizedStubs(script);
     }
   }
 
   /*
    * When scripts contains pointers to nursery things, the store buffer
    * can contain entries that point into the optimized stub space. Since
    * this method can be called outside the context of a GC, this situation
    * could result in us trying to mark invalid store buffer entries.
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/gc/bug1511412.js
@@ -0,0 +1,17 @@
+Object.defineProperty(this, "fuzzutils", {
+      value: {
+          orig_evaluate: evaluate,
+          evaluate: function(c, o) {
+              if (!o) {
+                  o = {};
+              }
+              o.catchTermination = true;
+              return fuzzutils.orig_evaluate(c, o);
+          },
+      }
+  });
+  gczeal(21, 10);
+  fuzzutils.evaluate(`
+enableShellAllocationMetadataBuilder();
+function test() {}
+`);
--- a/js/src/jit/BaselineIC.h
+++ b/js/src/jit/BaselineIC.h
@@ -340,17 +340,17 @@ class ICScript {
     MOZ_ASSERT(index < numICEntries());
     return icEntryList()[index];
   }
 
   void noteAccessedGetter(uint32_t pcOffset);
   void noteHasDenseAdd(uint32_t pcOffset);
 
   void trace(JSTracer* trc);
-  void purgeOptimizedStubs(Zone* zone);
+  void purgeOptimizedStubs(JSScript* script);
 
   ICEntry* maybeICEntryFromPCOffset(uint32_t pcOffset);
   ICEntry* maybeICEntryFromPCOffset(uint32_t pcOffset,
                                     ICEntry* prevLookedUpEntry);
 
   ICEntry& icEntryFromPCOffset(uint32_t pcOffset);
   ICEntry& icEntryFromPCOffset(uint32_t pcOffset, ICEntry* prevLookedUpEntry);
 };
--- a/js/src/jit/BaselineJIT.cpp
+++ b/js/src/jit/BaselineJIT.cpp
@@ -1009,17 +1009,29 @@ void BaselineScript::toggleProfilerInstr
     flags_ |= uint32_t(PROFILER_INSTRUMENTATION_ON);
   } else {
     Assembler::ToggleToJmp(enterToggleLocation);
     Assembler::ToggleToJmp(exitToggleLocation);
     flags_ &= ~uint32_t(PROFILER_INSTRUMENTATION_ON);
   }
 }
 
-void ICScript::purgeOptimizedStubs(Zone* zone) {
+void ICScript::purgeOptimizedStubs(JSScript* script) {
+  MOZ_ASSERT(script->icScript() == this);
+
+  Zone* zone = script->zone();
+  if (zone->isGCSweeping() && IsAboutToBeFinalizedDuringSweep(*script)) {
+    // We're sweeping and the script is dead. Don't purge optimized stubs
+    // because (1) accessing CacheIRStubInfo pointers in ICStubs is invalid
+    // because we may have swept them already when we started (incremental)
+    // sweeping and (2) it's unnecessary because this script will be finalized
+    // soon anyway.
+    return;
+  }
+
   JitSpew(JitSpew_BaselineIC, "Purging optimized stubs");
 
   for (size_t i = 0; i < numICEntries(); i++) {
     ICEntry& entry = icEntry(i);
     ICStub* lastStub = entry.firstStub();
     while (lastStub->next()) {
       lastStub = lastStub->next();
     }
new file mode 100644
--- /dev/null
+++ b/layout/reftests/meta-viewport/clamped-by-default-minimum-scale.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<meta name="viewport" content="width=device-width">
+<style>
+html, body {
+  margin: 0;
+  width: 100%;
+  height: 100%;
+  scrollbar-width: none;
+}
+#container {
+  /*
+   * This value should be greater than reftest viewport width 800px * (1 / 0.25).
+   * 0.25 is the initial scale value used in the reference.
+   */
+  min-width: 4000px;
+  /*
+   * This value should be greater than reftest viewport height
+   * 1000x * (1 / 0.25) to make the initial scale auto-calculation happen.
+   */
+  min-height: 8000px;
+  position: relative;
+}
+#inner {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100px;
+  height: 100px;
+  background: green;
+}
+</style>
+<div id="container">
+  <div id="inner"></div>
+</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/meta-viewport/initial-scale-0_25-ref.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta name="viewport" content="initial-scale=0.25,width=device-width">
+<style>
+html, body {
+  margin: 0;
+  width: 100%;
+  height: 100%;
+}
+#container {
+  position: relative;
+}
+#inner {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100px;
+  height: 100px;
+  background: green;
+}
+</style>
+<div id="container">
+  <div id="inner"></div>
+</div>
--- a/layout/reftests/meta-viewport/reftest.list
+++ b/layout/reftests/meta-viewport/reftest.list
@@ -3,8 +3,9 @@ default-preferences pref(dom.meta-viewpo
 # These tests assume that viewport width in reftest is 800px.
 == box-shadow.html initial-scale-0_5-ref.html
 == initial-scale-0.html initial-scale-0_5-ref.html
 == initial-scale-100.html initial-scale-0_5-ref.html
 == no-viewport.html initial-scale-0_5-ref.html
 == viewport-width.html initial-scale-0_5-ref.html
 == initial-scale-1.html no-zoom-ref.html
 == minimum-scale.html no-zoom-ref.html
+== clamped-by-default-minimum-scale.html initial-scale-0_25-ref.html
--- a/media/webrtc/trunk/webrtc/modules/desktop_capture/mac/window_list_utils.cc
+++ b/media/webrtc/trunk/webrtc/modules/desktop_capture/mac/window_list_utils.cc
@@ -143,29 +143,16 @@ bool GetWindowList(rtc::FunctionView<boo
     }
 
     CFNumberRef window_layer = reinterpret_cast<CFNumberRef>(
         CFDictionaryGetValue(window, kCGWindowLayer));
     if (!window_layer) {
       continue;
     }
 
-    //Skip windows of zero area
-    CFDictionaryRef bounds_ref = reinterpret_cast<CFDictionaryRef>(
-         CFDictionaryGetValue(window, kCGWindowBounds));
-    CGRect bounds_rect;
-    if(!(bounds_ref) ||
-      !(CGRectMakeWithDictionaryRepresentation(bounds_ref, &bounds_rect))){
-      continue;
-    }
-    bounds_rect = CGRectStandardize(bounds_rect);
-    if((bounds_rect.size.width <= 0) || (bounds_rect.size.height <= 0)){
-      continue;
-    }
-
     // Skip windows with layer=0 (menu, dock).
     // TODO(zijiehe): The windows with layer != 0 are skipped, is this a bug in
     // code (not likely) or a bug in comments? What's the meaning of window
     // layer number in the first place.
     int layer;
     if (!CFNumberGetValue(window_layer, kCFNumberIntType, &layer)) {
       continue;
     }
--- a/media/webrtc/trunk/webrtc/modules/desktop_capture/window_capturer_win.cc
+++ b/media/webrtc/trunk/webrtc/modules/desktop_capture/window_capturer_win.cc
@@ -77,23 +77,16 @@ BOOL CALLBACK WindowsEnumerationHandler(
   // Truncate the title if it's longer than kTitleLength.
   GetWindowText(hwnd, window_title, kTitleLength);
   window.title = rtc::ToUtf8(window_title);
 
   // Skip windows when we failed to convert the title or it is empty.
   if (window.title.empty())
     return TRUE;
 
-  // Skip windows of zero visible area, except IconicWindows
-  RECT bounds;
-  if(GetClientRect(hwnd,&bounds) && !IsIconic(hwnd)
-    && IsRectEmpty(&bounds)){
-    return TRUE;
-  }
-
   list->push_back(window);
 
   return TRUE;
 }
 
 // Retrieves the rectangle of the window rect which is drawable by either OS or
 // the owner application. The returned DesktopRect is in system coordinates.
 // This function returns false if native APIs fail.
--- a/media/webrtc/trunk/webrtc/modules/desktop_capture/x11/window_list_utils.cc
+++ b/media/webrtc/trunk/webrtc/modules/desktop_capture/x11/window_list_utils.cc
@@ -204,25 +204,16 @@ bool GetWindowList(XAtomCache* cache,
 
     DeferXFree free_children(children);
 
     for (unsigned int i = 0; i < num_children; i++) {
       // Iterates in reverse order to return windows from front to back.
       ::Window app_window =
           GetApplicationWindow(cache, children[num_children - 1 - i]);
       if (app_window && !IsDesktopElement(cache, app_window)) {
-        XWindowAttributes window_attr;
-        if(!XGetWindowAttributes(display, app_window, &window_attr)) {
-          RTC_LOG(LS_ERROR)<<"Bad request for attributes for window ID:" << app_window;
-          continue;
-        }
-        if((window_attr.width <= 0) || (window_attr.height <=0)){
-          continue;
-        }
-
         if (!on_window(app_window)) {
           return true;
         }
       }
     }
   }
 
   return failed_screens < num_screens;
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/AccessibilityTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/AccessibilityTest.kt
@@ -500,17 +500,18 @@ class AccessibilityTest : BaseSessionTes
         node.getBoundsInScreen(nodeBounds)
         return screenRect.contains(nodeBounds)
     }
 
     @Ignore // Bug 1506276 - We need to reliably wait for APZC here, and it's not trivial.
     @Test fun testScroll() {
         var nodeId = View.NO_ID
         sessionRule.session.loadString(
-                """<body style="margin: 0;">
+                """<meta name="viewport" content="width=device-width initial-scale=1">
+                <body style="margin: 0;">
                         <div style="height: 100vh;"></div>
                         <button>Hello</button>
                         <p style="margin: 0;">Lorem ipsum dolor sit amet, consectetur adipiscing elit,
                             sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
                 </body>""",
                 "text/html")
 
         sessionRule.waitUntilCalled(object : EventDelegate {
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1570,21 +1570,17 @@ pref("javascript.options.spectre.index_m
 pref("javascript.options.spectre.object_mitigations.barriers", true);
 pref("javascript.options.spectre.object_mitigations.misc", true);
 pref("javascript.options.spectre.string_mitigations", true);
 pref("javascript.options.spectre.value_masking", true);
 pref("javascript.options.spectre.jit_to_C++_calls", true);
 #endif
 
 // Streams API
-#ifdef NIGHTLY_BUILD
 pref("javascript.options.streams", true);
-#else
-pref("javascript.options.streams", false);
-#endif
 
 // BigInt API
 pref("javascript.options.bigint", false);
 
 // advanced prefs
 pref("advanced.mailftp",                    false);
 pref("image.animation_mode",                "normal");
 
@@ -5137,17 +5133,17 @@ pref("extensions.webextensions.enablePer
 
 // Maximum age in milliseconds of performance counters in children
 // When reached, the counters are sent to the main process and
 // reset, so we reduce memory footprint.
 pref("extensions.webextensions.performanceCountersMaxAge", 1000);
 
 // Report Site Issue button
 pref("extensions.webcompat-reporter.newIssueEndpoint", "https://webcompat.com/issues/new");
-#if defined(MOZ_DEV_EDITION) || defined(NIGHTLY_BUILD)
+#if MOZ_UPDATE_CHANNEL != release && MOZ_UPDATE_CHANNEL != esr
 pref("extensions.webcompat-reporter.enabled", true);
 #else
 pref("extensions.webcompat-reporter.enabled", false);
 #endif
 
 pref("network.buffer.cache.count", 24);
 pref("network.buffer.cache.size",  32768);
 
new file mode 100644
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1504751_aboutnetworking.py
@@ -0,0 +1,99 @@
+from __future__ import absolute_import
+import fluent.syntax.ast as FTL
+from fluent.migrate.helpers import transforms_from
+from fluent.migrate import CONCAT
+from fluent.migrate import REPLACE
+from fluent.migrate import COPY
+
+
+def migrate(ctx):
+    """ Bug 1504751 - Migrate about:Networking to Fluent, part {index}. """
+
+    ctx.add_transforms(
+        "toolkit/toolkit/about/aboutNetworking.ftl",
+        "toolkit/toolkit/about/aboutNetworking.ftl",
+        transforms_from(
+"""
+title = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.title")}
+warning = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.warning")}
+show-next-time-checkbox = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.showNextTime")}
+ok = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.ok")}
+http = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.HTTP")}
+sockets = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.sockets")}
+dns = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.dns")}
+websockets = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.websockets")}
+refresh = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.refresh")}
+auto-refresh = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.autoRefresh")}
+hostname = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.hostname")}
+port = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.port")}
+http2 = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.http2")}
+ssl = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.ssl")}
+active = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.active")}
+idle = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.idle")}
+host = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.host")}
+tcp = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.tcp")}
+sent = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.sent")}
+received = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.received")}
+family = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.family")}
+trr = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.trr")}
+addresses = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.addresses")}
+expires = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.expires")}
+messages-sent = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.messagesSent")}
+messages-received = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.messagesReceived")}
+bytes-sent = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.bytesSent")}
+bytes-received = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.bytesReceived")}
+logging = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.logging")}
+current-log-file = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.currentLogFile")}
+current-log-modules = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.currentLogModules")}
+set-log-file = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.setLogFile")}
+set-log-modules = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.setLogModules")}
+start-logging = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.startLogging")}
+stop-logging = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.stopLogging")}
+dns-lookup = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.dnsLookup")}
+dns-lookup-button = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.dnsLookupButton")}
+dns-lookup-table-column = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.dnsLookupTableColumn")}
+rcwn = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.rcwn")}
+rcwn-status = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.rcwnStatus")}
+rcwn-cache-won-count = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.rcwnCacheWonCount")}
+rcwn-net-won-count = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.rcwnNetWonCount")}
+total-network-requests = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.totalNetworkRequests")}
+rcwn-operation = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.rcwnOperation")}
+rcwn-perf-open = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.rcwnPerfOpen")}
+rcwn-perf-read = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.rcwnPerfRead")}
+rcwn-perf-write = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.rcwnPerfWrite")}
+rcwn-perf-entry-open = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.rcwnPerfEntryOpen")}
+rcwn-avg-short = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.rcwnAvgShort")}
+rcwn-avg-long = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.rcwnAvgLong")}
+rcwn-std-dev-long = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.rcwnStddevLong")}
+rcwn-cache-slow = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.rcwnCacheSlow")}
+rcwn-cache-not-slow = { COPY("toolkit/chrome/global/aboutNetworking.dtd", "aboutNetworking.rcwnCacheNotSlow")}
+"""
+        )
+    )
+
+    ctx.add_transforms(
+        "toolkit/toolkit/about/aboutNetworking.ftl",
+        "toolkit/toolkit/about/aboutNetworking.ftl",
+        [
+            FTL.Message(
+                id=FTL.Identifier("log-tutorial"),
+                value=REPLACE(
+                    "toolkit/chrome/global/aboutNetworking.dtd",
+                    "aboutNetworking.logTutorial",
+                    {
+                        "href='https://developer.mozilla.org/docs/Mozilla/Debugging/HTTP_logging'": FTL.TextElement('data-l10n-name="logging"')
+                    }
+                )
+            ),
+            FTL.Message(
+                id=FTL.Identifier("dns-domain"),
+                value=CONCAT(
+                    COPY(
+                        "toolkit/chrome/global/aboutNetworking.dtd",
+                        "aboutNetworking.dnsDomain",
+                    ),
+                    FTL.TextElement(":"),
+                )
+            )
+        ]
+    )
new file mode 100644
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1505846_aboutsearchreset.py
@@ -0,0 +1,111 @@
+# coding=utf8
+
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from __future__ import absolute_import
+import fluent.syntax.ast as FTL
+from fluent.migrate.helpers import transforms_from
+from fluent.migrate.helpers import TERM_REFERENCE
+from fluent.migrate import COPY
+from fluent.migrate import REPLACE
+from fluent.migrate import CONCAT
+
+def migrate(ctx):
+    """Bug 1505846 Migrate about:searchreset to Fluent, part {index} """
+
+    ctx.add_transforms(
+
+        "browser/browser/aboutSearchReset.ftl",
+        "browser/browser/aboutSearchReset.ftl",
+        transforms_from(
+"""
+tab-title = { COPY("browser/chrome/browser/aboutSearchReset.dtd", "searchreset.tabtitle") }
+page-title = { COPY("browser/chrome/browser/aboutSearchReset.dtd", "searchreset.pageTitle") }
+"""
+        )
+    )
+
+    ctx.add_transforms(
+        "browser/browser/aboutSearchReset.ftl",
+        "browser/browser/aboutSearchReset.ftl",
+        [
+            FTL.Message(
+                id=FTL.Identifier("page-info-outofdate"),
+                value=REPLACE(
+                    "browser/chrome/browser/aboutSearchReset.dtd",
+                    "searchreset.pageInfo1",
+                    {
+                        "&brandShortName;": TERM_REFERENCE("-brand-short-name"),
+                    }
+                )
+            ),
+            FTL.Message(
+                id=FTL.Identifier("page-info-how-to-change"),
+                value=CONCAT(
+                    COPY(
+                        "browser/chrome/browser/aboutSearchReset.dtd",
+                        "searchreset.beforelink.pageInfo2",
+                    ),
+                    FTL.TextElement('<a data-l10n-name="link">'),
+                    COPY(
+                        "browser/chrome/browser/aboutSearchReset.dtd",
+                        "searchreset.link.pageInfo2",
+                    ),
+                    FTL.TextElement("</a>"),
+                    COPY(
+                        "browser/chrome/browser/aboutSearchReset.dtd",
+                        "searchreset.afterlink.pageInfo2",
+                    )
+                )
+            ),
+            FTL.Message(
+                id=FTL.Identifier("no-change-button"),
+                attributes=[
+                    FTL.Attribute(
+                        id=FTL.Identifier("label"),
+                        value=COPY(
+                            "browser/chrome/browser/aboutSearchReset.dtd",
+                            "searchreset.noChangeButton",
+                        )
+                    ),
+                    FTL.Attribute(
+                        id=FTL.Identifier("accesskey"),
+                        value=COPY(
+                            "browser/chrome/browser/aboutSearchReset.dtd",
+                            "searchreset.noChangeButton.access",
+                        )
+                    ),
+                ]
+            ),
+            FTL.Message(
+                id=FTL.Identifier("change-engine-button"),
+                attributes=[
+                    FTL.Attribute(
+                        id=FTL.Identifier("label"),
+                        value=COPY(
+                            "browser/chrome/browser/aboutSearchReset.dtd",
+                            "searchreset.changeEngineButton",
+                        )
+                    ),
+                    FTL.Attribute(
+                        id=FTL.Identifier("accesskey"),
+                        value=COPY(
+                            "browser/chrome/browser/aboutSearchReset.dtd",
+                            "searchreset.changeEngineButton.access",
+                        )
+                    ),
+                ]
+            ),
+            FTL.Message(
+                id=FTL.Identifier("page-info-new-search-engine"),
+                value=CONCAT(
+                    COPY(
+                        "browser/chrome/browser/aboutSearchReset.dtd",
+                        "searchreset.selector.label",
+                    ),
+                    FTL.TextElement(' <span data-l10n-name="default-engine">{ $searchEngine }</span>'),
+                )
+            )
+        ]
+    )
--- a/python/mozbuild/mozbuild/nodeutil.py
+++ b/python/mozbuild/mozbuild/nodeutil.py
@@ -48,18 +48,18 @@ def find_node_paths():
 
 
 def check_executable_version(exe, wrap_call_with_node=False):
     """Determine the version of a Node executable by invoking it.
 
     May raise ``subprocess.CalledProcessError`` or ``ValueError`` on failure.
     """
     out = None
-    # npm may be a script, so we must call it with node.
-    if wrap_call_with_node:
+    # npm may be a script (Except on Windows), so we must call it with node.
+    if wrap_call_with_node and platform.system() != "Windows":
         binary, _ = find_node_executable()
         if binary:
             out = subprocess.check_output([binary, exe, "--version"]).lstrip('v').rstrip()
 
     # If we can't find node, or we don't need to wrap it, fallback to calling
     # direct.
     if not out:
         out = subprocess.check_output([exe, "--version"]).lstrip('v').rstrip()
--- a/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp
+++ b/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp
@@ -30,16 +30,20 @@
 #include "nsNetCID.h"
 
 #ifdef ANDROID
 #include "cutils/properties.h"
 #endif
 
 #ifdef MOZ_WIDGET_GTK
 #include <glib.h>
+#ifdef MOZ_WAYLAND
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#endif
 #endif
 
 #include <dirent.h>
 #include <sys/stat.h>
 #include <sys/sysmacros.h>
 #include <sys/types.h>
 #ifndef ANDROID
 #include <glob.h>
@@ -381,22 +385,30 @@ SandboxBrokerPolicyFactory::SandboxBroke
   // Allow Primus to contact the Bumblebee daemon to manage GPU
   // switching on NVIDIA Optimus systems.
   const char* bumblebeeSocket = PR_GetEnv("BUMBLEBEE_SOCKET");
   if (bumblebeeSocket == nullptr) {
     bumblebeeSocket = "/var/run/bumblebee.socket";
   }
   policy->AddPath(SandboxBroker::MAY_CONNECT, bumblebeeSocket);
 
+#if defined(MOZ_WIDGET_GTK)
   // Allow local X11 connections, for Primus and VirtualGL to contact
-  // the secondary X server.
+  // the secondary X server. No exception for Wayland.
+#if defined(MOZ_WAYLAND)
+  if (GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
+    policy->AddPrefix(SandboxBroker::MAY_CONNECT, "/tmp/.X11-unix/X");
+  }
+#else
   policy->AddPrefix(SandboxBroker::MAY_CONNECT, "/tmp/.X11-unix/X");
+#endif
   if (const auto xauth = PR_GetEnv("XAUTHORITY")) {
     policy->AddPath(rdonly, xauth);
   }
+#endif
 
   mCommonContentPolicy.reset(policy);
 #endif
 }
 
 #ifdef MOZ_CONTENT_SANDBOX
 UniquePtr<SandboxBroker::Policy> SandboxBrokerPolicyFactory::GetContentPolicy(
     int aPid, bool aFileProcess) {
--- a/security/sandbox/linux/broker/moz.build
+++ b/security/sandbox/linux/broker/moz.build
@@ -27,10 +27,11 @@ include('/ipc/chromium/chromium-config.m
 # Need this for safe_sprintf.h used by SandboxLogging.h,
 # but it has to be after ipc/chromium/src.
 LOCAL_INCLUDES += [
     '/security/sandbox/chromium',
 ]
 
 if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
     CXXFLAGS += CONFIG['GLIB_CFLAGS']
+    CXXFLAGS += CONFIG['TK_CFLAGS']
 
 FINAL_LIBRARY = 'xul'
--- a/services/common/docs/RemoteSettings.rst
+++ b/services/common/docs/RemoteSettings.rst
@@ -27,16 +27,21 @@ The ``get()`` method returns the list of
     */
 
     for(const entry of data) {
       // Do something with entry...
       // await InternalAPI.load(entry.id, entry.label, entry.weight);
     });
 
 .. note::
+    The data updates are managed internally, and ``.get()`` only returns the local data.
+    The data is pulled from the server only if this collection has no local data yet and no JSON dump
+    could be found (see :ref:`services/initial-data` below).
+
+.. note::
     The ``id`` and ``last_modified`` (timestamp) attributes are assigned by the server.
 
 Options
 -------
 
 The list can optionally be filtered or ordered:
 
 .. code-block:: js
@@ -84,28 +89,33 @@ When an entry has a file attached to it,
     data.filter(d => d.attachment)
         .forEach(async ({ attachment: { url, filename, size } }) => {
           if (size < OS.freeDiskSpace) {
             // Planned feature, see Bug 1501214
             await downloadLocally(url, filename);
           }
         });
 
+.. _services/initial-data:
+
 Initial data
 ------------
 
-For newly created user profiles, the list of entries returned by the ``.get()`` method will be empty until the first synchronization happens.
+It is possible to package a dump of the server records that will be loaded into the local database when no synchronization has happened yet.
 
-It is possible to package a dump of the server records that will be loaded into the local database when no synchronization has happened yet. It will thus serve as the default dataset and also reduce the amount of data to be downloaded on the first synchronization.
+The JSON dump will serve as the default dataset for ``.get()``, instead of doing a round-trip to pull the latest data. It will also reduce the amount of data to be downloaded on the first synchronization.
 
 #. Place the JSON dump of the server records in the ``services/settings/dumps/main/`` folder
 #. Add the filename to the ``FINAL_TARGET_FILES`` list in ``services/settings/dumps/main/moz.build``
 
 Now, when ``RemoteSettings("some-key").get()`` is called from an empty profile, the ``some-key.json`` file is going to be loaded before the results are returned.
 
+.. note::
+
+    JSON dumps are not shipped on Android to minimize the installer size.
 
 Targets and A/B testing
 =======================
 
 In order to deliver settings to subsets of the population, you can set targets on entries (platform, language, channel, version range, preferences values, samples, etc.) when editing records on the server.
 
 From the client API standpoint, this is completely transparent: the ``.get()`` method — as well as the event data — will always filter the entries on which the target matches.
 
@@ -165,24 +175,25 @@ Trigger a synchronization manually
 ----------------------------------
 
 The synchronization of every known remote settings clients can be triggered manually with ``pollChanges()``:
 
 .. code-block:: js
 
     await RemoteSettings.pollChanges()
 
-The synchronization of a single client can be forced with ``maybeSync()``:
+The synchronization of a single client can be forced with the ``.sync()`` method:
 
 .. code-block:: js
 
-    const fakeTimestamp = Infinity;
-    const fakeServerTime = Date.now();
+    await RemoteSettings("a-key").sync();
 
-    await RemoteSettings("a-key").maybeSync(fakeTimestamp, fakeServerTime)
+.. important::
+
+    The above methods are only relevant during development or debugging and should never be called in production code.
 
 
 Manipulate local data
 ---------------------
 
 A handle on the local collection can be obtained with ``openCollection()``.
 
 .. code-block:: js
--- a/services/common/tests/unit/test_blocklist_certificates.js
+++ b/services/common/tests/unit/test_blocklist_certificates.js
@@ -46,79 +46,70 @@ add_task(async function test_something()
     } catch (e) {
       info(e);
     }
   }
   server.registerPathHandler(configPath, handleResponse);
   server.registerPathHandler(recordsPath, handleResponse);
 
   // Test an empty db populates
-  await OneCRLBlocklistClient.maybeSync(2000, Date.now());