Merge inbound to mozila-central a=merge
authorCoroiu Cristina <ccoroiu@mozilla.com>
Sat, 01 Dec 2018 07:46:00 +0200
changeset 505489 925a2727cc37
parent 505488 a00c3f230203 (current diff)
parent 505461 22425b629a9d (diff)
child 505518 a5b0559c2372
child 505520 7fc73687c1ef
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
925a2727cc37 / 65.0a1 / 20181201054936 / files
nightly linux64
925a2727cc37 / 65.0a1 / 20181201054936 / files
nightly mac
925a2727cc37 / 65.0a1 / 20181201054936 / files
nightly win32
925a2727cc37 / 65.0a1 / 20181201054936 / files
nightly win64
925a2727cc37 / 65.0a1 / 20181201054936 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozila-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/debugger/new/dist/debugger.css
devtools/client/shared/components/reps/reps.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
js/src/gc/Zone.cpp
js/src/jit/BaselineIC.h
js/src/jit/BaselineJIT.cpp
netwerk/base/nsIOService.cpp
netwerk/base/nsSocketTransport2.cpp
netwerk/protocol/http/nsHttp.h
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpConnectionInfo.cpp
netwerk/protocol/http/nsHttpConnectionInfo.h
netwerk/protocol/http/nsHttpConnectionMgr.cpp
netwerk/protocol/viewsource/nsViewSourceChannel.cpp
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/build/sparse-profiles/docker-image
+++ b/build/sparse-profiles/docker-image
@@ -1,13 +1,17 @@
 %include build/sparse-profiles/mach
 
 [include]
 path:taskcluster/
 
+# Required for loading taskgraph.parameters.
+path:browser/config/version_display.txt
+path:browser/config/version.txt
+
 # Result from `grep -hr %include taskcluster/docker | grep -v " taskcluster/" | sort -u`
 path:python/mozbuild/mozbuild/action/tooltool.py
 path:testing/config/tooltool-manifests/linux64/releng.manifest
 path:testing/mozharness/external_tools/robustcheckout.py
 path:tools/lint/spell/codespell_requirements.txt
 path:tools/lint/eslint/eslint-plugin-mozilla/manifest.tt
 path:tools/lint/eslint/manifest.tt
 path:tools/lint/python/flake8_requirements.txt
--- 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_SANDBO