Merge inbound to mozilla-central. a=merge
authorMargareta Eliza Balazs <ebalazs@mozilla.com>
Wed, 04 Apr 2018 12:43:55 +0300
changeset 411646 ff0efa4132f0efd78af0910762aec7dcc1a8de66
parent 411645 479b6858ea6cf96b4842cf5ae78f3bf9e74d3278 (current diff)
parent 411605 b878c7217f8626bbd211d6be1eaf03a49c0f4b3b (diff)
child 411647 dd31fb345c11732432fa61a7d8a1b727d41b4a1c
child 411676 545bb9b8a294def427902f5faa66b91ca56686b9
push id101712
push userebalazs@mozilla.com
push dateWed, 04 Apr 2018 09:52:15 +0000
treeherdermozilla-inbound@dd31fb345c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone61.0a1
first release with
nightly linux32
ff0efa4132f0 / 61.0a1 / 20180404100127 / files
nightly linux64
ff0efa4132f0 / 61.0a1 / 20180404100127 / files
nightly mac
ff0efa4132f0 / 61.0a1 / 20180404100127 / files
nightly win32
ff0efa4132f0 / 61.0a1 / 20180404100127 / files
nightly win64
ff0efa4132f0 / 61.0a1 / 20180404100127 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
browser/components/enterprisepolicies/Policies.jsm
devtools/client/framework/test/browser_toolbox_minimize.js
gfx/layers/wr/WebRenderCommandBuilder.cpp
layout/generic/nsFrame.cpp
layout/painting/nsDisplayList.h
testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-001.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-002.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-003.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-004.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-radial-gradient-001.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-000.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-001.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-002.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-003.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-004.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-005.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-012.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-013.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-014.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-015.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-016.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-017.html.ini
testing/web-platform/meta/css/css-shapes/shape-outside/shape-image/shape-image-025.html.ini
testing/web-platform/meta/css/css-shapes/spec-examples/shape-outside-010.html.ini
testing/web-platform/meta/css/css-shapes/spec-examples/shape-outside-011.html.ini
testing/web-platform/meta/css/css-shapes/spec-examples/shape-outside-012.html.ini
--- a/browser/base/content/browser-media.js
+++ b/browser/base/content/browser-media.js
@@ -167,17 +167,16 @@ var gEMEHandler = {
     };
     let options = {
       dismissed: true,
       eventCallback: aTopic => aTopic == "swapping",
       learnMoreURL: Services.urlFormatter.formatURLPref("app.support.baseURL") + "drm-content",
     };
     PopupNotifications.show(browser, "drmContentPlaying", message, anchorId, mainAction, null, options);
   },
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIMessageListener])
 };
 
 XPCOMUtils.defineLazyGetter(gEMEHandler, "_brandShortName", function() {
   return document.getElementById("bundle_brand").getString("brandShortName");
 });
 
 const TELEMETRY_DDSTAT_SHOWN = 0;
 const TELEMETRY_DDSTAT_SHOWN_FIRST = 1;
--- a/browser/base/content/test/static/browser_parsable_css.js
+++ b/browser/base/content/test/static/browser_parsable_css.js
@@ -74,27 +74,16 @@ let whitelist = [
 if (!Services.prefs.getBoolPref("full-screen-api.unprefix.enabled")) {
   whitelist.push({
     sourceName: /(?:res|gre-resources)\/(ua|html)\.css$/i,
     errorMessage: /Unknown pseudo-class .*\bfullscreen\b/i,
     isFromDevTools: false
   });
 }
 
-// Platform can be "linux", "macosx" or "win". If omitted, the exception applies to all platforms.
-let allowedImageReferences = [
-  // Bug 1302691
-  {file: "chrome://devtools/skin/images/dock-bottom-minimize@2x.png",
-   from: "chrome://devtools/skin/toolbox.css",
-   isFromDevTools: true},
-  {file: "chrome://devtools/skin/images/dock-bottom-maximize@2x.png",
-   from: "chrome://devtools/skin/toolbox.css",
-   isFromDevTools: true},
-];
-
 let propNameWhitelist = [
   // These are CSS custom properties that we found a definition of but
   // no reference to.
   // Bug 1441837
   {propName: "--in-content-category-text-active",
    isFromDevTools: false},
   // Bug 1441855
   {propName: "--chrome-nav-buttons-background",
@@ -414,28 +403,17 @@ add_task(async function checkAllTheCSS()
 
   // Wait for all the files to have actually loaded:
   await throttledMapPromises(allPromises, loadCSS);
 
   // Check if all the files referenced from CSS actually exist.
   for (let [image, references] of imageURIsToReferencesMap) {
     if (!chromeFileExists(image)) {
       for (let ref of references) {
-        let ignored = false;
-        for (let item of allowedImageReferences) {
-          if (image.endsWith(item.file) && ref.endsWith(item.from) &&
-              isDevtools == item.isFromDevTools &&
-              (!item.platforms || item.platforms.includes(AppConstants.platform))) {
-            item.used = true;
-            ignored = true;
-            break;
-          }
-        }
-        if (!ignored)
-          ok(false, "missing " + image + " referenced from " + ref);
+        ok(false, "missing " + image + " referenced from " + ref);
       }
     }
   }
 
   // Check if all the properties that are defined are referenced.
   for (let [prop, refCount] of customPropsToReferencesMap) {
     if (!refCount) {
       let ignored = false;
@@ -467,17 +445,16 @@ add_task(async function checkAllTheCSS()
       if (!item.used && isDevtools == item.isFromDevTools &&
           (!item.platforms || item.platforms.includes(AppConstants.platform)) &&
           !item.intermittent) {
         ok(false, "Unused whitelist item: " + dumpWhitelistItem(item));
       }
     }
   }
   checkWhitelist(whitelist);
-  checkWhitelist(allowedImageReferences);
   checkWhitelist(propNameWhitelist);
 
   // Clean up to avoid leaks:
   iframe.remove();
   doc.head.innerHTML = "";
   doc = null;
   iframe = null;
   win = null;
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -799,16 +799,17 @@ if (AppConstants.platform == "win") {
 }
 CustomizableWidgets.push(preferencesButton);
 
 if (Services.prefs.getBoolPref("privacy.panicButton.enabled")) {
   CustomizableWidgets.push({
     id: "panic-button",
     type: "view",
     viewId: "PanelUI-panicView",
+    disabled: !Services.policies.isAllowed("panicButton"),
 
     forgetButtonCalled(aEvent) {
       let doc = aEvent.target.ownerDocument;
       let group = doc.getElementById("PanelUI-panic-timeSpan");
       BrowserUITelemetry.countPanicEvent(group.selectedItem.id);
       let itemsToClear = [
         "cookies", "history", "openWindows", "formdata", "sessions", "cache", "downloads"
       ];
--- a/browser/components/enterprisepolicies/EnterprisePoliciesContent.js
+++ b/browser/components/enterprisepolicies/EnterprisePoliciesContent.js
@@ -46,18 +46,17 @@ function EnterprisePoliciesManagerConten
 
   Services.cpmm.addMessageListener("EnterprisePolicies:DisallowFeature", this);
   Services.cpmm.addMessageListener("EnterprisePolicies:Restart", this);
 }
 
 EnterprisePoliciesManagerContent.prototype = {
   // for XPCOM
   classID:          Components.ID("{dc6358f8-d167-4566-bf5b-4350b5e6a7a2}"),
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIMessageListener,
-                                         Ci.nsIEnterprisePolicies]),
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIEnterprisePolicies]),
 
   // redefine the default factory for XPCOMUtils
   _xpcom_factory: EnterprisePoliciesFactory,
 
   _status: Ci.nsIEnterprisePolicies.INACTIVE,
 
   _disallowedFeatures: [],
 
--- a/browser/components/enterprisepolicies/Policies.jsm
+++ b/browser/components/enterprisepolicies/Policies.jsm
@@ -252,16 +252,24 @@ var Policies = {
   "DisableFirefoxStudies": {
     onBeforeAddons(manager, param) {
       if (param) {
         manager.disallowFeature("Shield");
       }
     }
   },
 
+  "DisableForgetButton": {
+    onProfileAfterChange(manager, param) {
+      if (param) {
+        manager.disallowFeature("panicButton");
+      }
+    }
+  },
+
   "DisableFormHistory": {
     onBeforeUIStartup(manager, param) {
       if (param) {
         setAndLockPref("browser.formfill.enable", false);
       }
     }
   },
 
@@ -286,16 +294,24 @@ var Policies = {
   "DisableSafeMode": {
     onBeforeUIStartup(manager, param) {
       if (param) {
         manager.disallowFeature("safeMode");
       }
     }
   },
 
+  "DisableSecurityBypass": {
+    onBeforeUIStartup(manager, param) {
+      if ("SafeBrowsing" in param) {
+        setAndLockPref("browser.safebrowsing.allowOverride", !param.SafeBrowsing);
+      }
+    }
+  },
+
   "DisableSysAddonUpdate": {
     onBeforeAddons(manager, param) {
       if (param) {
         manager.disallowFeature("SysAddonUpdate");
       }
     }
   },
 
@@ -484,17 +500,22 @@ var Policies = {
           Services.prefs.clearUserPref("browser.startup.page");
         });
       }
     }
   },
 
   "InstallAddons": {
     onBeforeUIStartup(manager, param) {
-      addAllowDenyPermissions("install", param.Allow, null);
+      if ("Allow" in param) {
+        addAllowDenyPermissions("install", param.Allow, null);
+      }
+      if ("Default" in param) {
+        setAndLockPref("xpinstall.enabled", param.Default);
+      }
     }
   },
 
   "NoDefaultBookmarks": {
     onProfileAfterChange(manager, param) {
       if (param) {
         manager.disallowFeature("defaultBookmarks");
       }
@@ -503,16 +524,27 @@ var Policies = {
 
   "OverrideFirstRunPage": {
     onProfileAfterChange(manager, param) {
       let url = param ? param.spec : "";
       setAndLockPref("startup.homepage_welcome_url", url);
     }
   },
 
+  "OverridePostUpdatePage": {
+    onProfileAfterChange(manager, param) {
+      let url = param ? param.spec : "";
+      setAndLockPref("startup.homepage_override_url", url);
+      // The pref startup.homepage_override_url is only used
+      // as a fallback when the update.xml file hasn't provided
+      // a specific post-update URL.
+      manager.disallowFeature("postUpdateCustomPage");
+    }
+  },
+
   "PopupBlocking": {
     onBeforeUIStartup(manager, param) {
       addAllowDenyPermissions("popup", param.Allow, null);
 
       if (param.Locked) {
         let blockValue = true;
         if (param.Default !== undefined && !param.Default) {
           blockValue = false;
--- a/browser/components/enterprisepolicies/schemas/policies-schema.json
+++ b/browser/components/enterprisepolicies/schemas/policies-schema.json
@@ -201,16 +201,23 @@
 
     "DisableFirefoxStudies": {
       "description": "Prevents Firefox from running studies.",
       "first_available": "60.0",
 
       "type": "boolean"
     },
 
+    "DisableForgetButton": {
+      "description": "Prevents access to the \"Forget\" button.",
+      "first_available": "60.0",
+
+      "type": "boolean"
+    },
+
     "DisableFormHistory": {
       "description": "Don't remember search and form history.",
       "first_available": "60.0",
 
       "type": "boolean"
     },
 
     "DisablePocket": {
@@ -229,16 +236,28 @@
 
     "DisableSafeMode": {
       "description": "Prevents ability to restart in safe mode.",
       "first_available": "60.0",
 
       "type": "boolean"
     },
 
+    "DisableSecurityBypass": {
+      "description": "Prevents the user from bypassing certain security warnings.",
+      "first_available": "60.0",
+
+      "type": "object",
+      "properties": {
+        "SafeBrowsing": {
+          "type": "boolean"
+        }
+      }
+    },
+
     "DisableSysAddonUpdate": {
       "description": "Prevent the browser from installing and updating system addons.",
       "first_available": "60.0",
       "enterprise_only": true,
 
       "type": "boolean"
     },
 
@@ -374,16 +393,19 @@
 
       "type": "object",
       "properties": {
         "Allow": {
           "type": "array",
           "items": {
             "type": "origin"
           }
+        },
+        "Default": {
+          "type": "boolean"
         }
       }
     },
 
     "NoDefaultBookmarks": {
       "description": "Don't create the default bookmarks bundled with Firefox, nor the Smart Bookmarks (Most Visited, Recent Tags). Note: this policy is only effective if used before the first run of the profile.",
       "first_available": "60.0",
 
@@ -393,16 +415,24 @@
     "OverrideFirstRunPage": {
       "description": "Override the first run page. Set this policy to blank if you want to disable the first run page.",
       "first_available": "60.0",
       "enterprise_only": true,
 
       "type": "URLorEmpty"
     },
 
+    "OverridePostUpdatePage": {
+      "description": "Override the post-update \"What's New\" page. Set this policy to blank if you want to disable the post-update page.",
+      "first_available": "60.0",
+      "enterprise_only": true,
+
+      "type": "URLorEmpty"
+    },
+
     "PopupBlocking": {
       "description": "Allow or deny popup usage.",
       "first_available": "60.0",
 
       "type": "object",
       "properties": {
         "Allow": {
           "type": "array",
--- a/browser/components/enterprisepolicies/tests/browser/browser.ini
+++ b/browser/components/enterprisepolicies/tests/browser/browser.ini
@@ -38,13 +38,14 @@ support-files =
 [browser_policy_disable_popup_blocker.js]
 [browser_policy_disable_privatebrowsing.js]
 [browser_policy_disable_safemode.js]
 [browser_policy_disable_shield.js]
 [browser_policy_disable_telemetry.js]
 [browser_policy_display_bookmarks.js]
 [browser_policy_display_menu.js]
 [browser_policy_extensions.js]
+[browser_policy_override_postupdatepage.js]
 [browser_policy_proxy.js]
 [browser_policy_search_engine.js]
 [browser_policy_searchbar.js]
 [browser_policy_set_homepage.js]
 [browser_policy_websitefilter.js]
--- a/browser/components/enterprisepolicies/tests/browser/browser_policies_simple_pref_policies.js
+++ b/browser/components/enterprisepolicies/tests/browser/browser_policies_simple_pref_policies.js
@@ -34,16 +34,27 @@ const POLICIES_TESTS = [
     policies: { "RememberPasswords": false },
     lockedPrefs: { "signon.rememberSignons": false },
   },
   {
     policies: { "RememberPasswords": true },
     lockedPrefs: { "signon.rememberSignons": true },
   },
 
+  // POLICY: DisableSecurityBypass
+  {
+    policies: {
+      "DisableSecurityBypass": {
+        "SafeBrowsing": true
+      }
+    },
+    lockedPrefs: { "browser.safebrowsing.allowOverride": false },
+  },
+
+
   // POLICY: DisableFormHistory
   {
     policies: { "DisableFormHistory": true },
     lockedPrefs: { "browser.formfill.enable": false },
   },
 
   // POLICY: EnableTrackingProtection
   {
@@ -86,26 +97,40 @@ const POLICIES_TESTS = [
       }
     },
     lockedPrefs: {
       "network.negotiate-auth.trusted-uris": "a.com, b.com",
       "network.negotiate-auth.delegation-uris": "a.com, b.com",
       "network.automatic-ntlm-auth.trusted-uris": "a.com, b.com",
     }
   },
+
+  // POLICY: Certificates
   {
     policies: {
       "Certificates": {
         "ImportEnterpriseRoots": true,
       }
     },
     lockedPrefs: {
       "security.enterprise_roots.enabled": true,
     }
   },
+
+  // POLICY: InstallAddons.Default (block addon installs)
+  {
+    policies: {
+      "InstallAddons": {
+        "Default": false,
+      }
+    },
+    lockedPrefs: {
+      "xpinstall.enabled": false,
+    }
+  },
 ];
 
 add_task(async function test_policy_remember_passwords() {
   for (let test of POLICIES_TESTS) {
     await setupPolicyEngineWithJson({
       "policies": test.policies
     });
 
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_override_postupdatepage.js
@@ -0,0 +1,99 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This test was based on the test browser_bug538331.js
+
+const UPDATE_PROVIDED_PAGE = "https://default.example.com/";
+const POLICY_PROVIDED_PAGE = "https://policy.example.com/";
+
+const PREF_MSTONE = "browser.startup.homepage_override.mstone";
+const PREF_POSTUPDATE = "app.update.postupdate";
+
+/*
+ * The important parts for this test are:
+ *  - actions="showURL"
+ *  - openURL="${UPDATE_PROVIDED_PAGE}"
+ */
+const XML_UPDATE = `<?xml version="1.0"?>
+<updates xmlns="http://www.mozilla.org/2005/app-update">
+  <update appVersion="1.0" buildID="20080811053724" channel="nightly"
+          displayVersion="Version 1.0" installDate="1238441400314"
+          isCompleteUpdate="true" name="Update Test 1.0" type="minor"
+          detailsURL="http://example.com/" previousAppVersion="1.0"
+          serviceURL="https://example.com/" statusText="The Update was successfully installed"
+          foregroundDownload="true"
+          actions="showURL"
+          openURL="${UPDATE_PROVIDED_PAGE}">
+    <patch type="complete" URL="http://example.com/" size="775" selected="true" state="succeeded"/>
+  </update>
+</updates>`;
+
+const XML_EMPTY = `<?xml version="1.0"?>
+<updates xmlns="http://www.mozilla.org/2005/app-update">
+</updates>`;
+
+let gOriginalMStone = null;
+
+add_task(async function test_override_postupdate_page() {
+  // Remember this to clean-up afterwards
+  if (Services.prefs.prefHasUserValue(PREF_MSTONE)) {
+    gOriginalMStone = Services.prefs.getCharPref(PREF_MSTONE);
+  }
+  Services.prefs.setBoolPref(PREF_POSTUPDATE, true);
+
+  writeUpdatesToXMLFile(XML_UPDATE);
+  reloadUpdateManagerData();
+
+  is(getPostUpdatePage(), UPDATE_PROVIDED_PAGE, "Post-update page was provided by update.xml.");
+
+  // Now perform the same action but set the policy to override this page
+  await setupPolicyEngineWithJson({
+    "policies": {
+      "OverridePostUpdatePage": POLICY_PROVIDED_PAGE
+    }
+  });
+
+  is(getPostUpdatePage(), POLICY_PROVIDED_PAGE, "Post-update page was provided by policy.");
+
+  // Clean-up
+  writeUpdatesToXMLFile(XML_EMPTY);
+  if (gOriginalMStone) {
+    Services.prefs.setCharPref(PREF_MSTONE, gOriginalMStone);
+  }
+  Services.prefs.clearUserPref(PREF_POSTUPDATE);
+  reloadUpdateManagerData();
+});
+
+
+function getPostUpdatePage() {
+  Services.prefs.setCharPref(PREF_MSTONE, "PreviousMilestone");
+  return Cc["@mozilla.org/browser/clh;1"]
+           .getService(Ci.nsIBrowserHandler)
+           .defaultArgs;
+}
+
+function reloadUpdateManagerData() {
+  // Reloads the update metadata from disk
+  Cc["@mozilla.org/updates/update-manager;1"].getService(Ci.nsIUpdateManager).
+  QueryInterface(Ci.nsIObserver).observe(null, "um-reload-update-data", "");
+}
+
+
+function writeUpdatesToXMLFile(aText) {
+  const PERMS_FILE = 0o644;
+
+  const MODE_WRONLY   = 0x02;
+  const MODE_CREATE   = 0x08;
+  const MODE_TRUNCATE = 0x20;
+
+  let file = Services.dirsvc.get("UpdRootD", Ci.nsIFile);
+  file.append("updates.xml");
+  let fos = Cc["@mozilla.org/network/file-output-stream;1"].
+            createInstance(Ci.nsIFileOutputStream);
+  if (!file.exists()) {
+    file.create(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
+  }
+  fos.init(file, MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE, PERMS_FILE, 0);
+  fos.write(aText, aText.length);
+  fos.close();
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/tests/browser/disable_forget_button/browser.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+prefs =
+  browser.policies.alternatePath='<test-root>/browser/components/enterprisepolicies/tests/browser/disable_forget_button/forget_button.json'
+support-files =
+  forget_button.json
+
+[browser_policy_disable_forgetbutton.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/tests/browser/disable_forget_button/browser_policy_disable_forgetbutton.js
@@ -0,0 +1,9 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_policy_disable_forget_button() {
+  let widget = CustomizableUI.getWidget("panic-button");
+  is(widget.disabled, true, "Forget Button is disabled");
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/tests/browser/disable_forget_button/forget_button.json
@@ -0,0 +1,5 @@
+{
+  "policies": {
+    "DisableForgetButton": true
+  }
+}
--- a/browser/components/enterprisepolicies/tests/moz.build
+++ b/browser/components/enterprisepolicies/tests/moz.build
@@ -7,14 +7,15 @@
 with Files("**"):
     BUG_COMPONENT = ("Firefox", "General")
 
 BROWSER_CHROME_MANIFESTS += [
     'browser/browser.ini',
     'browser/disable_app_update/browser.ini',
     'browser/disable_default_bookmarks/browser.ini',
     'browser/disable_developer_tools/browser.ini',
+    'browser/disable_forget_button/browser.ini',
     'browser/disable_fxscreenshots/browser.ini',
 ]
 
 TESTING_JS_MODULES += [
     'EnterprisePolicyTesting.jsm',
 ]
--- a/browser/components/nsBrowserContentHandler.js
+++ b/browser/components/nsBrowserContentHandler.js
@@ -140,16 +140,23 @@ function getPostUpdateOverridePage(defau
   if (!actions)
     return defaultOverridePage;
 
   // The existence of silent or the non-existence of showURL in the actions both
   // mean that an override page should not be displayed.
   if (actions.includes("silent") || !actions.includes("showURL"))
     return "";
 
+  // If a policy was set to not allow the update.xml-provided
+  // URL to be used, use the default fallback (which will also
+  // be provided by the policy).
+  if (!Services.policies.isAllowed("postUpdateCustomPage")) {
+    return defaultOverridePage;
+  }
+
   return update.getProperty("openURL") || defaultOverridePage;
 }
 
 /**
  * Open a browser window. If this is the initial launch, this function will
  * attempt to use the navigator:blank window opened by nsBrowserGlue.js during
  * early startup.
  *
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/accessibility-panel.js
@@ -0,0 +1,225 @@
+/* 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 { AccessibilityFront } = require("devtools/shared/fronts/accessibility");
+const EventEmitter = require("devtools/shared/event-emitter");
+
+const { Picker } = require("./picker");
+
+// The panel's window global is an EventEmitter firing the following events:
+const EVENTS = {
+  // When the accessibility inspector has a new accessible front selected.
+  NEW_ACCESSIBLE_FRONT_SELECTED: "Accessibility:NewAccessibleFrontSelected",
+  // When the accessibility inspector has a new accessible front highlighted.
+  NEW_ACCESSIBLE_FRONT_HIGHLIGHTED: "Accessibility:NewAccessibleFrontHighlighted",
+  // When the accessibility inspector has a new accessible front inspected.
+  NEW_ACCESSIBLE_FRONT_INSPECTED: "Accessibility:NewAccessibleFrontInspected",
+  // When the accessibility inspector is updated.
+  ACCESSIBILITY_INSPECTOR_UPDATED: "Accessibility:AccessibilityInspectorUpdated"
+};
+
+/**
+ * This object represents Accessibility panel. It's responsibility is to
+ * render Accessibility Tree of the current debugger target and the sidebar that
+ * displays current relevant accessible details.
+ */
+function AccessibilityPanel(iframeWindow, toolbox) {
+  this.panelWin = iframeWindow;
+  this._toolbox = toolbox;
+
+  this.onTabNavigated = this.onTabNavigated.bind(this);
+  this.onPanelVisibilityChange = this.onPanelVisibilityChange.bind(this);
+  this.onNewAccessibleFrontSelected =
+    this.onNewAccessibleFrontSelected.bind(this);
+  this.onAccessibilityInspectorUpdated =
+    this.onAccessibilityInspectorUpdated.bind(this);
+  this.updatePickerButton = this.updatePickerButton.bind(this);
+
+  EventEmitter.decorate(this);
+}
+
+AccessibilityPanel.prototype = {
+  /**
+   * Open is effectively an asynchronous constructor.
+   */
+  async open() {
+    if (this._opening) {
+      await this._opening;
+      return this._opening;
+    }
+
+    let resolver;
+    this._opening = new Promise(resolve => {
+      resolver = resolve;
+    });
+
+    // Local monitoring needs to make the target remote.
+    if (!this.target.isRemote) {
+      await this.target.makeRemote();
+    }
+
+    this.target.on("navigate", this.onTabNavigated);
+    this._toolbox.on("select", this.onPanelVisibilityChange);
+
+    this.panelWin.EVENTS = EVENTS;
+    EventEmitter.decorate(this.panelWin);
+    this.panelWin.on(EVENTS.NEW_ACCESSIBLE_FRONT_SELECTED,
+      this.onNewAccessibleFrontSelected);
+    this.panelWin.on(EVENTS.ACCESSIBILITY_INSPECTOR_UPDATED,
+      this.onAccessibilityInspectorUpdated);
+
+    this.shouldRefresh = true;
+    this.panelWin.gToolbox = this._toolbox;
+
+    await this._toolbox.initInspector();
+    this._front = new AccessibilityFront(this.target.client,
+                                         this.target.form);
+    this._walker = await this._front.getWalker();
+
+    this._isOldVersion = !(await this.target.actorHasMethod("accessibility", "enable"));
+    if (!this._isOldVersion) {
+      await this._front.bootstrap();
+      this.picker = new Picker(this);
+    }
+
+    this.isReady = true;
+    this.emit("ready");
+    resolver(this);
+    return this._opening;
+  },
+
+  onNewAccessibleFrontSelected(selected) {
+    this.emit("new-accessible-front-selected", selected);
+  },
+
+  onAccessibilityInspectorUpdated() {
+    this.emit("accessibility-inspector-updated");
+  },
+
+  /**
+   * Make sure the panel is refreshed when the page is reloaded. The panel is
+   * refreshed immediatelly if it's currently selected or lazily when the user
+   * actually selects it.
+   */
+  onTabNavigated() {
+    this.shouldRefresh = true;
+    this._opening.then(() => this.refresh());
+  },
+
+  /**
+   * Make sure the panel is refreshed (if needed) when it's selected.
+   */
+  onPanelVisibilityChange() {
+    this._opening.then(() => this.refresh());
+  },
+
+  refresh() {
+    this.cancelPicker();
+
+    if (this.isVisible) {
+      this._front.on("init", this.updatePickerButton);
+      this._front.on("shutdown", this.updatePickerButton);
+    } else {
+      this._front.off("init", this.updatePickerButton);
+      this._front.off("shutdown", this.updatePickerButton);
+      // Do not refresh if the panel isn't visible.
+      return;
+    }
+
+    // Do not refresh if it isn't necessary.
+    if (!this.shouldRefresh) {
+      return;
+    }
+    // Alright reset the flag we are about to refresh the panel.
+    this.shouldRefresh = false;
+    this.postContentMessage("initialize", this._front, this._walker, this._isOldVersion);
+  },
+
+  selectAccessible(accessibleFront) {
+    this.postContentMessage("selectAccessible", this._walker, accessibleFront);
+  },
+
+  highlightAccessible(accessibleFront) {
+    this.postContentMessage("highlightAccessible", this._walker, accessibleFront);
+  },
+
+  postContentMessage(type, ...args) {
+    const event = new this.panelWin.MessageEvent("devtools/chrome/message", {
+      bubbles: true,
+      cancelable: true,
+      data: { type, args }
+    });
+
+    this.panelWin.dispatchEvent(event);
+  },
+
+  updatePickerButton() {
+    this.picker && this.picker.updateButton();
+  },
+
+  togglePicker(focus) {
+    this.picker && this.picker.toggle();
+  },
+
+  cancelPicker() {
+    this.picker && this.picker.cancel();
+  },
+
+  stopPicker() {
+    this.picker && this.picker.stop();
+  },
+
+  get walker() {
+    return this._walker;
+  },
+
+  /**
+   * Return true if the Accessibility panel is currently selected.
+   */
+  get isVisible() {
+    return this._toolbox.currentToolId === "accessibility";
+  },
+
+  get target() {
+    return this._toolbox.target;
+  },
+
+  async destroy() {
+    if (this._destroying) {
+      await this._destroying;
+      return;
+    }
+
+    let resolver;
+    this._destroying = new Promise(resolve => {
+      resolver = resolve;
+    });
+
+    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;
+
+    if (this._front) {
+      await this._front.destroy();
+    }
+
+    this._front = null;
+    this.panelWin.gToolbox = null;
+
+    this.emit("destroyed");
+
+    resolver();
+  }
+};
+
+// Exports from this module
+exports.AccessibilityPanel = AccessibilityPanel;
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/accessibility-view.js
@@ -0,0 +1,97 @@
+/* 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 EVENTS */
+
+// React & Redux
+const { createFactory, createElement } = require("devtools/client/shared/vendor/react");
+const ReactDOM = require("devtools/client/shared/vendor/react-dom");
+const { Provider } = require("devtools/client/shared/vendor/react-redux");
+const { combineReducers } = require("devtools/client/shared/vendor/redux");
+
+// Accessibility Panel
+const MainFrame = createFactory(require("./components/MainFrame"));
+const OldVersionDescription =
+  createFactory(require("./components/Description").OldVersionDescription);
+
+// Store
+const createStore = require("devtools/client/shared/redux/create-store")();
+
+// Reducers
+const { reducers } = require("./reducers/index");
+const store = createStore(combineReducers(reducers));
+
+// Actions
+const { reset } = require("./actions/ui");
+const { select, highlight } = require("./actions/accessibles");
+
+/**
+ * This object represents view of the Accessibility panel and is responsible
+ * for rendering the content. It renders the top level ReactJS
+ * component: the MainFrame.
+ */
+function AccessibilityView(localStore) {
+  addEventListener("devtools/chrome/message", this.onMessage.bind(this), true);
+  this.store = localStore;
+}
+
+AccessibilityView.prototype = {
+  /**
+   * Initialize accessibility view, create its top level component and set the
+   * data store.
+   *
+   * @param {Object} accessibility  front that can initialize accessibility
+   *                                walker and enable/disable accessibility
+   *                                services.
+   */
+  async initialize(accessibility, walker, isOldVersion) {
+    // Make sure state is reset every time accessibility panel is initialized.
+    await this.store.dispatch(reset(accessibility));
+    const container = document.getElementById("content");
+
+    if (isOldVersion) {
+      ReactDOM.render(OldVersionDescription(), container);
+      return;
+    }
+
+    const mainFrame = MainFrame({ accessibility, walker });
+    // Render top level component
+    const provider = createElement(Provider, { store: this.store }, mainFrame);
+    this.mainFrame = ReactDOM.render(provider, container);
+  },
+
+  async selectAccessible(walker, accessible) {
+    await this.store.dispatch(select(walker, accessible));
+    window.emit(EVENTS.NEW_ACCESSIBLE_FRONT_INSPECTED);
+  },
+
+  async highlightAccessible(walker, accessible) {
+    await this.store.dispatch(highlight(walker, accessible));
+    window.emit(EVENTS.NEW_ACCESSIBLE_FRONT_HIGHLIGHTED);
+  },
+
+  async selectNodeAccessible(walker, node) {
+    const accessible = await walker.getAccessibleFor(node);
+
+    await this.store.dispatch(select(walker, accessible));
+    window.emit(EVENTS.NEW_ACCESSIBLE_FRONT_HIGHLIGHTED);
+  },
+
+  /**
+   * Process message from accessibility panel.
+   *
+   * @param {Object} event  message type and data.
+   */
+  onMessage(event) {
+    const data = event.data;
+    const method = data.type;
+
+    if (typeof this[method] === "function") {
+      this[method](...data.args);
+    }
+  },
+};
+
+window.view = new AccessibilityView(store);
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/accessibility.css
@@ -0,0 +1,332 @@
+/* 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/. */
+
+:root {
+  --accessibility-font-size: 12px;
+  --accessibility-toolbar-height: 24px;
+  --accessibility-toolbar-height-tall: 35px;
+  --accessibility-toolbar-focus: var(--blue-50);
+  --accessibility-toolbar-focus-alpha30: rgba(10, 132, 255, 0.3);
+  --accessibility-full-length-minus-splitter: calc(100% - 1px);
+  --accessibility-horizontal-padding: 5px;
+  --accessibility-arrow-horizontal-padding: 4px;
+  --accessibility-tree-row-height: 21px;
+}
+
+/* General */
+html,
+body {
+  height: 100%;
+  margin: 0;
+  padding: 0;
+  width: 100%;
+}
+
+:root .flash-out {
+  animation: flash-out 0.5s ease-out;
+}
+
+@keyframes flash-out {
+  from {
+    background: var(--theme-contrast-background);
+  }
+}
+
+.accessible .tree .node.focused .theme-twisty,
+.treeTable .treeRow.selected .theme-twisty {
+  background-position: -28px -14px;
+}
+
+.accessible .tree .node.focused .theme-twisty.open,
+.treeTable .treeRow.selected .theme-twisty.open {
+  background-position: -42px -14px;
+}
+
+.mainFrame .main-panel {
+  flex: 1 1 auto;
+  overflow: auto;
+}
+
+.mainFrame {
+  height: 100%;
+  color: var(--theme-toolbar-color);
+}
+
+.split-box.horz {
+  height: calc(100vh - var(--accessibility-toolbar-height));
+}
+
+.mainFrame .devtools-button,
+.description .devtools-button {
+  padding: unset;
+}
+
+.mainFrame .devtools-button > .btn-content {
+  padding: 2px var(--accessibility-horizontal-padding);
+}
+
+.description .devtools-button > .btn-content {
+  padding: 7px var(--accessibility-horizontal-padding);
+}
+
+.devtools-button:focus,
+.devtools-button > .btn-content:focus,
+.devtools-button::-moz-focus-inner {
+  border: 1px solid transparent;
+  outline: none;
+}
+
+.devtools-button:focus > .btn-content:not(.devtools-throbber) {
+  outline: 2px solid var(--accessibility-toolbar-focus);
+  outline-offset: -2px;
+  box-shadow: 0 0 0 2px var(--accessibility-toolbar-focus-alpha30);
+  border-radius: 2px;
+  -moz-outline-radius: 2px;
+}
+
+/* Description */
+.description {
+  color: var(--theme-toolbar-color);
+  font: message-box;
+  font-size: calc(var(--accessibility-font-size) + 1px);
+  margin: auto;
+  padding-top: 15vh;
+  width: 50vw;
+}
+
+.description .general {
+  display: flex;
+  align-items: center;
+  margin-bottom: 1.7em;
+}
+
+.description img {
+  margin-right: 12px;
+  flex-basis: 42px;
+  -moz-context-properties: fill;
+  fill: var(--grey-40);
+}
+
+.description .devtools-button {
+  display: flex;
+  align-items: center;
+  margin: auto;
+}
+
+/* TreeView Customization */
+.split-box:not(.horz) .main-panel {
+  height: calc(100vh - var(--accessibility-toolbar-height));
+}
+
+.treeTable > thead {
+  position: sticky;
+  top: 0;
+}
+
+.split-box:not(.horz) .treeTable {
+  /* To compenstate for 1px splitter between the tree and sidebar. */
+  width: var(--accessibility-full-length-minus-splitter);
+}
+
+.split-box.horz .treeTable {
+  width: 100%;
+}
+
+.treeTable .treeRow.highlighted:not(.selected) {
+  background-color: var(--theme-selection-background-hover);
+}
+
+.treeTable .treeLabelCell {
+  min-width: 50%;
+}
+
+.treeTable:focus,
+.treeTable > tbody:focus {
+  outline: 0;
+}
+
+.treeTable::-moz-focus-inner,
+.treeTable > tbody::-moz-focus-inner {
+  border: 0;
+}
+
+.treeTable:focus > tbody {
+  outline: var(--theme-focus-outline);
+  outline-offset: -1px;
+}
+
+.treeTable > thead {
+  pointer-events: none;
+}
+
+.treeTable > tbody tr {
+  height: var(--accessibility-tree-row-height);
+}
+
+.treeTable > tbody td {
+  -moz-user-select: none;
+}
+
+.treeTable > tbody td > span {
+  -moz-user-select: text;
+}
+
+.mainFrame .treeTable .treeRow.hasChildren > .treeLabelCell > .treeLabel:hover {
+  cursor: unset;
+  text-decoration: none;
+}
+
+.mainFrame .treeTable .treeHeaderRow > .treeHeaderCell:first-child > .treeHeaderCellBox,
+.mainFrame .treeTable .treeHeaderRow > .treeHeaderCell > .treeHeaderCellBox {
+  padding: 0;
+  padding-inline-start: var(--accessibility-arrow-horizontal-padding);
+}
+
+.mainFrame .treeTable .treeHeaderCell {
+  border-bottom: 1px solid var(--theme-splitter-color);
+  background: var(--theme-toolbar-background);
+  font: message-box;
+  font-size: var(--accessibility-font-size);
+  height: var(--accessibility-toolbar-height);
+  color: var(--theme-toolbar-color);
+}
+
+/* Right Sidebar */
+.right-sidebar {
+  display: flex;
+  flex-direction: column;
+  flex: 1;
+  white-space: nowrap;
+  font: message-box;
+  font-size: var(--accessibility-font-size);
+}
+
+.split-box:not(.horz) .right-sidebar {
+  position: fixed;
+  width: inherit;
+  height: calc(100vh - (var(--accessibility-toolbar-height)));
+}
+
+.right-sidebar ._header {
+  background-color: var(--theme-toolbar-background);
+  border-bottom: 1px solid var(--theme-splitter-color);
+  height: var(--accessibility-toolbar-height);
+  line-height: var(--accessibility-toolbar-height);
+  padding-inline-start: 14px;
+  padding-inline-end: var(--accessibility-arrow-horizontal-padding);
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+}
+
+.right-sidebar ._content {
+  font-size: var(--accessibility-font-size);
+  flex: 2 0;
+  overflow: auto;
+}
+
+/* Tree customization */
+.accessible .tree {
+  flex: 1;
+  height: 100%;
+  white-space: nowrap;
+  overflow: auto;
+  display: block;
+}
+
+.split-box.horz .accessible .tree {
+  width: 100vw;
+}
+
+.accessible .tree button {
+  display: block;
+}
+
+/* NOTE: total height of the node (height + padding + border + margin) must
+   be exactly the same as the value of TREE_ROW_HEIGHT constant in
+   devtools/client/accessibility/constants.js */
+.accessible .tree .node {
+  padding: 0 var(--accessibility-horizontal-padding);
+  position: relative;
+  display: flex;
+  height: var(--accessibility-tree-row-height);
+  width: calc(100% - var(--accessibility-horizontal-padding));
+  cursor: default;
+  align-items: center;
+}
+
+.accessible .tree .node.focused {
+  background-color: var(--theme-selection-background);
+}
+
+.accessible .tree .tree-node:hover:not(.focused) {
+  background-color: var(--theme-selection-background-hover);
+}
+
+.accessible .tree .node.focused * {
+  color: var(--theme-selection-color);
+}
+
+.accessible .tree .node.focused .open-inspector {
+  background-color: var(--grey-30);
+}
+
+.accessible .tree .node.focused:hover .open-inspector {
+  background-color: var(--theme-selection-color);
+}
+
+.accessible .tree .arrow {
+  flex-shrink: 0;
+}
+
+.accessible .tree .object-value {
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+.accessible .tree .object-delimiter {
+  padding-inline-end: var(--accessibility-arrow-horizontal-padding);
+}
+
+.accessible .tree .object-label {
+  color: var(--theme-highlight-blue);
+}
+
+.accessible .tree .objectBox-node {
+  width: 100%;
+  display: flex;
+}
+
+.accessible .tree .objectBox-node .attrName {
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+.accessible .tree .objectBox-node .open-inspector{
+  width: 17px;
+}
+
+.accessible .tree .objectBox-object,
+.accessible .tree .objectBox-string,
+.accessible .tree .objectBox-text,
+.accessible .tree .objectBox-table,
+.accessible .tree .objectLink-textNode,
+.accessible .tree .objectLink-event,
+.accessible .tree .objectLink-eventLog,
+.accessible .tree .objectLink-regexp,
+.accessible .tree .objectLink-object,
+.accessible .tree .objectLink-Date,
+.theme-dark .accessible .tree .objectBox-object,
+.theme-light .accessible .tree .objectBox-object {
+  white-space: nowrap;
+}
+
+/* Styling for accessible details panel when accessible is not available */
+.accessible .info {
+  color: var(--theme-body-color);
+  font-size: 110%;
+  padding-inline-start: var(--accessibility-arrow-horizontal-padding);
+  height: var(--accessibility-toolbar-height-tall);
+  line-height: var(--accessibility-toolbar-height-tall);
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/accessibility.html
@@ -0,0 +1,21 @@
+<?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>
+<html dir="">
+<head>
+  <meta charset="utf-8"/>
+
+  <link href="resource://devtools/client/accessibility/accessibility.css" rel="stylesheet"/>
+  <link href="resource://devtools/client/shared/components/splitter/SplitBox.css" rel="stylesheet" />
+  <link href="resource://devtools/client/shared/components/tree/TreeView.css" rel="stylesheet" />
+
+  <script type="application/javascript"
+          src="chrome://devtools/content/shared/theme-switching.js"></script>
+</head>
+<body class="theme-body devtools-monospace" role="application">
+  <div id="content" role="presentation"></div>
+  <script type="text/javascript" src="./main.js"></script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/actions/accessibles.js
@@ -0,0 +1,28 @@
+/* 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 { FETCH_CHILDREN, SELECT, HIGHLIGHT, UNHIGHLIGHT } = require("../constants");
+
+/**
+ * Fetch child accessibles for a given accessible object.
+ * @param {Object} accessible front
+ */
+exports.fetchChildren = accessible =>
+  dispatch => accessible.children()
+    .then(response => dispatch({ accessible, type: FETCH_CHILDREN, response }))
+    .catch(error => dispatch({ accessible, type: FETCH_CHILDREN, error }));
+
+exports.select = (walker, accessible) =>
+  dispatch => walker.getAncestry(accessible)
+    .then(response => dispatch({ accessible, type: SELECT, response }))
+    .catch(error => dispatch({ accessible, type: SELECT, error }));
+
+exports.highlight = (walker, accessible) =>
+  dispatch => walker.getAncestry(accessible)
+    .then(response => dispatch({ accessible, type: HIGHLIGHT, response }))
+    .catch(error => dispatch({ accessible, type: HIGHLIGHT, error }));
+
+exports.unhighlight = () =>
+  dispatch => dispatch({ type: UNHIGHLIGHT });
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/actions/details.js
@@ -0,0 +1,18 @@
+/* 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 { UPDATE_DETAILS } = require("../constants");
+
+/**
+ * Update details with the given accessible object.
+ *
+ * @param {Object} dom walker front
+ * @param {Object} accessible front
+ */
+exports.updateDetails = (domWalker, accessible) =>
+  dispatch =>
+    domWalker.getNodeFromActor(accessible.actorID, ["rawAccessible", "DOMNode"])
+      .then(response => dispatch({ accessible, type: UPDATE_DETAILS, response }))
+      .catch(error => dispatch({ accessible, type: UPDATE_DETAILS, error }));
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/actions/moz.build
@@ -0,0 +1,9 @@
+# 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/.
+
+DevToolsModules(
+    'accessibles.js',
+    'details.js',
+    'ui.js'
+)
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/actions/ui.js
@@ -0,0 +1,46 @@
+/* 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 {
+  ENABLE,
+  DISABLE,
+  RESET,
+  UPDATE_CAN_BE_DISABLED,
+  UPDATE_CAN_BE_ENABLED
+} = require("../constants");
+
+/**
+ * Reset accessibility panel UI.
+ */
+exports.reset = accessibility =>
+  dispatch => dispatch({ accessibility, type: RESET });
+
+/**
+ * Update a "canBeDisabled" flag for accessibility service.
+ */
+exports.updateCanBeDisabled = canBeDisabled =>
+  dispatch => dispatch({ canBeDisabled, type: UPDATE_CAN_BE_DISABLED });
+
+/**
+ * Update a "canBeEnabled" flag for accessibility service.
+ */
+exports.updateCanBeEnabled = canBeEnabled =>
+  dispatch => dispatch({ canBeEnabled, type: UPDATE_CAN_BE_ENABLED });
+
+/**
+ * Enable accessibility services in order to view accessible tree.
+ */
+exports.enable = accessibility =>
+  dispatch => accessibility.enable()
+    .then(() => dispatch({ type: ENABLE }))
+    .catch(error => dispatch({ error, type: ENABLE }));
+
+/**
+ * Enable accessibility services in order to view accessible tree.
+ */
+exports.disable = accessibility =>
+  dispatch => accessibility.disable()
+    .then(() => dispatch({ type: DISABLE }))
+    .catch(error => dispatch({ type: DISABLE, error }));
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/components/AccessibilityRow.js
@@ -0,0 +1,156 @@
+/* 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 */
+
+// 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 { VALUE_FLASHING_DURATION, VALUE_HIGHLIGHT_DURATION } = require("../constants");
+
+// Actions
+const { updateDetails } = require("../actions/details");
+const { unhighlight } = require("../actions/accessibles");
+
+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;
+      }
+    }
+
+    if (nextProps.highlighted !== this.props.highlighted) {
+      return true;
+    }
+
+    return false;
+  }
+}
+
+const HighlightableTreeRow = createFactory(HighlightableTreeRowClass);
+
+// Component that expands TreeView's own TreeRow and is responsible for
+// rendering an accessible object.
+class AccessibilityRow extends Component {
+  static get propTypes() {
+    return {
+      ...TreeRow.propTypes,
+      dispatch: PropTypes.func.isRequired,
+      walker: PropTypes.object
+    };
+  }
+
+  componentDidMount() {
+    const { selected, object } = this.props.member;
+    if (selected) {
+      this.updateAndScrollIntoViewIfNeeded();
+      this.highlight(object, { duration: VALUE_HIGHLIGHT_DURATION });
+    }
+
+    if (this.props.highlighted) {
+      this.scrollIntoView();
+    }
+  }
+
+  /**
+   * Update accessible object details that are going to be rendered inside the
+   * accessible panel sidebar.
+   */
+  componentDidUpdate(prevProps) {
+    const { selected, object } = this.props.member;
+    // If row is selected, update corresponding accessible details.
+    if (!prevProps.member.selected && selected) {
+      this.updateAndScrollIntoViewIfNeeded();
+      this.highlight(object, { duration: VALUE_HIGHLIGHT_DURATION });
+    }
+
+    if (this.props.highlighted) {
+      this.scrollIntoView();
+    }
+
+    if (!selected && prevProps.member.value !== this.props.member.value) {
+      this.flashValue();
+    }
+  }
+
+  scrollIntoView() {
+    const row = findDOMNode(this);
+    row.scrollIntoView({ block: "center" });
+  }
+
+  updateAndScrollIntoViewIfNeeded() {
+    const { dispatch, member } = this.props;
+    if (gToolbox) {
+      dispatch(updateDetails(gToolbox.walker, member.object));
+    }
+
+    this.scrollIntoView();
+    window.emit(EVENTS.NEW_ACCESSIBLE_FRONT_SELECTED, member.object);
+  }
+
+  flashValue() {
+    const row = findDOMNode(this);
+    const value = row.querySelector(".objectBox");
+
+    flashElementOn(value);
+    if (this._flashMutationTimer) {
+      clearTimeout(this._flashMutationTimer);
+      this._flashMutationTimer = null;
+    }
+    this._flashMutationTimer = setTimeout(() => {
+      flashElementOff(value);
+    }, VALUE_FLASHING_DURATION);
+  }
+
+  highlight(accessible, options) {
+    const { walker, dispatch } = this.props;
+    dispatch(unhighlight());
+
+    if (!accessible || !walker) {
+      return;
+    }
+
+    walker.highlightAccessible(accessible, options).catch(error =>
+      console.warn(error));
+  }
+
+  unhighlight() {
+    const { walker, dispatch } = this.props;
+    dispatch(unhighlight());
+
+    if (!walker) {
+      return;
+    }
+
+    walker.unhighlight().catch(error => console.warn(error));
+  }
+
+  /**
+   * Render accessible row component.
+   * @returns acecssible-row React component.
+   */
+  render() {
+    const { object } = this.props.member;
+    const props = Object.assign({}, this.props, {
+      onMouseOver: () => this.highlight(object),
+      onMouseOut: () => this.unhighlight()
+    });
+
+    return (HighlightableTreeRow(props));
+  }
+}
+
+module.exports = connect()(AccessibilityRow);
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/components/AccessibilityTree.js
@@ -0,0 +1,195 @@
+/* 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 EVENTS */
+
+// React & Redux
+const { Component, createFactory } = require("devtools/client/shared/vendor/react");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+
+const TreeView = createFactory(require("devtools/client/shared/components/tree/TreeView"));
+// Reps
+const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
+const Rep = createFactory(REPS.Rep);
+const Grip = REPS.Grip;
+
+const { fetchChildren } = require("../actions/accessibles");
+
+const { L10N } = require("../utils/l10n");
+const AccessibilityRow = createFactory(require("./AccessibilityRow"));
+const { Provider } = require("../provider");
+
+/**
+ * Renders Accessibility panel tree.
+ */
+class AccessibilityTree extends Component {
+  static get propTypes() {
+    return {
+      walker: PropTypes.object,
+      dispatch: PropTypes.func.isRequired,
+      accessibles: PropTypes.object,
+      expanded: PropTypes.object,
+      selected: PropTypes.string,
+      highlighted: PropTypes.object
+    };
+  }
+
+  constructor(props) {
+    super(props);
+
+    this.onNameChange = this.onNameChange.bind(this);
+    this.onReorder = this.onReorder.bind(this);
+    this.onTextChange = this.onTextChange.bind(this);
+  }
+
+  /**
+   * Add accessibility walker front event listeners that affect tree rendering
+   * and updates.
+   */
+  componentWillMount() {
+    let { walker } = this.props;
+    walker.on("reorder", this.onReorder);
+    walker.on("name-change", this.onNameChange);
+    walker.on("text-change", this.onTextChange);
+    return null;
+  }
+
+  componentDidUpdate() {
+    window.emit(EVENTS.ACCESSIBILITY_INSPECTOR_UPDATED);
+  }
+
+  /**
+   * Remove accessible walker front event listeners.
+   */
+  componentWillUnmount() {
+    let { walker } = this.props;
+    walker.off("reorder", this.onReorder);
+    walker.off("name-change", this.onNameChange);
+    walker.off("text-change", this.onTextChange);
+  }
+
+  /**
+   * Handle accessible reorder event. If the accessible is cached and rendered
+   * within the accessibility tree, re-fetch its children and re-render the
+   * corresponding subtree.
+   * @param {Object} accessible accessible object that had its subtree
+   *                            reordered.
+   */
+  onReorder(accessible) {
+    if (this.props.accessibles.has(accessible.actorID)) {
+      this.props.dispatch(fetchChildren(accessible));
+    }
+  }
+
+  /**
+   * Handle accessible name change event. If the name of an accessible changes
+   * and that accessible is cached and rendered within the accessibility tree,
+   * re-fetch its parent's children and re-render the corresponding subtree.
+   * @param {Object} accessible accessible object that had its name changed.
+   * @param {Object} parent     optional parent accessible object. Note: if it
+   *                            parent is not present, we assume that the top
+   *                            level document's name has changed and use
+   *                            accessible walker as a parent.
+   */
+  onNameChange(accessible, parent) {
+    let { accessibles, walker, dispatch } = this.props;
+    parent = parent || walker;
+
+    if (accessibles.has(accessible.actorID) ||
+        accessibles.has(parent.actorID)) {
+      dispatch(fetchChildren(parent));
+    }
+  }
+
+  /**
+   * Handle accessible text change (change/insert/remove) event. If the text of
+   * an accessible changes and that accessible is cached and rendered within the
+   * accessibility tree, re-fetch its children and re-render the corresponding
+   * subtree.
+   * @param  {Object} accessible  accessible object that had its child text
+   *                              changed.
+   */
+  onTextChange(accessible) {
+    let { accessibles, dispatch } = this.props;
+    if (accessibles.has(accessible.actorID)) {
+      dispatch(fetchChildren(accessible));
+    }
+  }
+
+  /**
+   * Render Accessibility panel content
+   */
+  render() {
+    let columns = [{
+      "id": "default",
+      "title": L10N.getStr("accessibility.role")
+    }, {
+      "id": "value",
+      "title": L10N.getStr("accessibility.name")
+    }];
+
+    let {
+      accessibles,
+      dispatch,
+      expanded,
+      selected,
+      highlighted: highlightedItem,
+      walker
+    } = this.props;
+
+    let renderValue = props => {
+      return Rep(Object.assign({}, props, {
+        defaultRep: Grip,
+        cropLimit: 50,
+      }));
+    };
+
+    let renderRow = rowProps => {
+      let { object } = rowProps.member;
+      let highlighted = object === highlightedItem;
+      return AccessibilityRow(Object.assign({}, rowProps, {
+        walker,
+        highlighted,
+        decorator: {
+          getRowClass: function() {
+            return highlighted ? ["highlighted"] : [];
+          }
+        }
+      }));
+    };
+
+    return (
+      TreeView({
+        object: walker,
+        mode: MODE.SHORT,
+        provider: new Provider(accessibles, dispatch),
+        columns: columns,
+        renderValue,
+        renderRow,
+        label: L10N.getStr("accessibility.treeName"),
+        header: true,
+        expandedNodes: expanded,
+        selected,
+        onClickRow(nodePath, event) {
+          event.stopPropagation();
+          if (event.target.classList.contains("theme-twisty")) {
+            this.toggle(nodePath);
+          }
+          this.selectRow(event.currentTarget);
+        }
+      })
+    );
+  }
+}
+
+const mapStateToProps = ({ accessibles, ui }) => ({
+  accessibles,
+  expanded: ui.expanded,
+  selected: ui.selected,
+  highlighted: ui.highlighted
+});
+// Exports from this module
+module.exports = connect(mapStateToProps)(AccessibilityTree);
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/components/Accessible.js
@@ -0,0 +1,355 @@
+/* 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 EVENTS, gToolbox */
+
+// React & Redux
+const { createFactory, Component } = require("devtools/client/shared/vendor/react");
+const { div, span } = require("devtools/client/shared/vendor/react-dom-factories");
+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 { TREE_ROW_HEIGHT, ORDERED_PROPS, ACCESSIBLE_EVENTS, VALUE_FLASHING_DURATION } =
+  require("../constants");
+const { L10N } = require("../utils/l10n");
+const {flashElementOn, flashElementOff} =
+      require("devtools/client/inspector/markup/utils");
+const { updateDetails } = require("../actions/details");
+
+const Tree = createFactory(require("devtools/client/shared/components/VirtualizedTree"));
+// Reps
+const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
+const { Rep, ElementNode } = REPS;
+
+class AccessiblePropertyClass extends Component {
+  static get propTypes() {
+    return {
+      accessible: PropTypes.string,
+      object: PropTypes.any,
+      focused: PropTypes.bool,
+      children: PropTypes.func
+    };
+  }
+
+  componentDidUpdate({ object: prevObject, accessible: prevAccessible }) {
+    let { accessible, object, focused } = this.props;
+    // Fast check if row is focused or if the value did not update.
+    if (focused || accessible !== prevAccessible || prevObject === object ||
+        (object && prevObject && typeof object === "object")) {
+      return;
+    }
+
+    this.flashRow();
+  }
+
+  flashRow() {
+    let row = findDOMNode(this);
+    flashElementOn(row);
+    if (this._flashMutationTimer) {
+      clearTimeout(this._flashMutationTimer);
+      this._flashMutationTimer = null;
+    }
+    this._flashMutationTimer = setTimeout(() => {
+      flashElementOff(row);
+    }, VALUE_FLASHING_DURATION);
+  }
+
+  render() {
+    return this.props.children();
+  }
+}
+
+const AccessibleProperty = createFactory(AccessiblePropertyClass);
+
+class Accessible extends Component {
+  static get propTypes() {
+    return {
+      accessible: PropTypes.object,
+      dispatch: PropTypes.func.isRequired,
+      DOMNode: PropTypes.object,
+      items: PropTypes.array,
+      labelledby: PropTypes.string.isRequired,
+      parents: PropTypes.object
+    };
+  }
+
+  constructor(props) {
+    super(props);
+
+    this.state = {
+      expanded: new Set(),
+      focused: null
+    };
+
+    this.onAccessibleInspected = this.onAccessibleInspected.bind(this);
+    this.renderItem = this.renderItem.bind(this);
+    this.update = this.update.bind(this);
+  }
+
+  componentWillMount() {
+    window.on(EVENTS.NEW_ACCESSIBLE_FRONT_INSPECTED, this.onAccessibleInspected);
+  }
+
+  componentWillReceiveProps({ accessible }) {
+    let oldAccessible = this.props.accessible;
+
+    if (oldAccessible) {
+      if (accessible && accessible.actorID === oldAccessible.actorID) {
+        return;
+      }
+      ACCESSIBLE_EVENTS.forEach(event => oldAccessible.off(event, this.update));
+    }
+
+    if (accessible) {
+      ACCESSIBLE_EVENTS.forEach(event => accessible.on(event, this.update));
+    }
+  }
+
+  componentWillUnmount() {
+    window.off(EVENTS.NEW_ACCESSIBLE_FRONT_INSPECTED, this.onAccessibleInspected);
+
+    let { accessible } = this.props;
+    if (accessible) {
+      ACCESSIBLE_EVENTS.forEach(event => accessible.off(event, this.update));
+    }
+  }
+
+  onAccessibleInspected() {
+    let { props } = this.refs;
+    if (props) {
+      props.refs.tree.focus();
+    }
+  }
+
+  update() {
+    let { dispatch, accessible } = this.props;
+    if (gToolbox) {
+      dispatch(updateDetails(gToolbox.walker, accessible));
+    }
+  }
+
+  setExpanded(item, isExpanded) {
+    const { expanded } = this.state;
+
+    if (isExpanded) {
+      expanded.add(item.path);
+    } else {
+      expanded.delete(item.path);
+    }
+
+    this.setState({ expanded });
+  }
+
+  showHighlighter(nodeFront) {
+    if (!gToolbox) {
+      return;
+    }
+
+    gToolbox.highlighterUtils.highlightNodeFront(nodeFront);
+  }
+
+  hideHighlighter() {
+    if (!gToolbox) {
+      return;
+    }
+
+    gToolbox.highlighterUtils.unhighlight();
+  }
+
+  selectNode(nodeFront, reason = "accessibility") {
+    if (!gToolbox) {
+      return;
+    }
+
+    gToolbox.selectTool("inspector").then(() =>
+      gToolbox.selection.setNodeFront(nodeFront, reason));
+  }
+
+  renderItem(item, depth, focused, arrow, expanded) {
+    const object = item.contents;
+    let valueProps = { object, mode: MODE.TINY, title: "Object" };
+    if (isNode(object)) {
+      valueProps.defaultRep = ElementNode;
+      valueProps.onDOMNodeMouseOut = () => this.hideHighlighter();
+      valueProps.onDOMNodeMouseOver = () => this.showHighlighter(this.props.DOMNode);
+      valueProps.onInspectIconClick = () => this.selectNode(this.props.DOMNode);
+    }
+
+    let classList = [ "node", "object-node" ];
+    if (focused) {
+      classList.push("focused");
+    }
+
+    return AccessibleProperty(
+      { object, focused, accessible: this.props.accessible.actorID },
+      () => div({
+        className: classList.join(" "),
+        style: { paddingInlineStart: depth * 15 },
+        onClick: e => {
+          if (e.target.classList.contains("theme-twisty")) {
+            this.setExpanded(item, !expanded);
+          }
+        }
+      },
+        arrow,
+        span({ className: "object-label" }, item.name),
+        span({ className: "object-delimiter" }, ":"),
+        span({ className: "object-value" }, Rep(valueProps) || "")
+      )
+    );
+  }
+
+  render() {
+    const { expanded, focused } = this.state;
+    const { items, parents, accessible, labelledby } = this.props;
+
+    if (accessible) {
+      return Tree({
+        ref: "props",
+        key: "accessible-properties",
+        itemHeight: TREE_ROW_HEIGHT,
+        getRoots: () => items,
+        getKey: item => item.path,
+        getParent: item => parents.get(item),
+        getChildren: item => item.children,
+        isExpanded: item => expanded.has(item.path),
+        onExpand: item => this.setExpanded(item, true),
+        onCollapse: item => this.setExpanded(item, false),
+        onFocus: item => {
+          if (this.state.focused !== item.path) {
+            this.setState({ focused: item.path });
+          }
+        },
+        onActivate: ({ contents }) => {
+          if (isNode(contents)) {
+            this.selectNode(this.props.DOMNode, "accessibility-keyboard");
+          }
+        },
+        focused: findFocused(focused, items),
+        renderItem: this.renderItem,
+        labelledby
+      });
+    }
+
+    return div({ className: "info" },
+               L10N.getStr("accessibility.accessible.notAvailable"));
+  }
+}
+
+/**
+ * Find currently focused item.
+ * @param  {String} focused Key of the currently focused item.
+ * @param  {Array}  items   Accessibility properties array.
+ * @return {Object?}        Possibly found focused item.
+ */
+const findFocused = (focused, items) => {
+  for (let item of items) {
+    if (item.path === focused) {
+      return item;
+    }
+    let found = findFocused(focused, item.children);
+    if (found) {
+      return found;
+    }
+  }
+  return null;
+};
+
+/**
+ * Check if a given property is a DOMNode actor.
+ * @param  {Object?} value A property to check for being a DOMNode.
+ * @return {Boolean}       A flag that indicates whether a property is a DOMNode.
+ */
+const isNode = value => value && value.typeName === "domnode";
+
+/**
+ * While waiting for a reps fix in https://github.com/devtools-html/reps/issues/92,
+ * translate nodeFront to a grip-like object that can be used with an ElementNode rep.
+ *
+ * @params  {NodeFront} nodeFront
+ *          The NodeFront for which we want to create a grip-like object.
+ * @returns {Object} a grip-like object that can be used with Reps.
+ */
+const translateNodeFrontToGrip = nodeFront => {
+  let { attributes, actorID, typeName, nodeName, nodeType } = nodeFront;
+
+  // The main difference between NodeFront and grips is that attributes are treated as
+  // a map in grips and as an array in NodeFronts.
+  let attributesMap = {};
+  for (let { name, value } of attributes) {
+    attributesMap[name] = value;
+  }
+
+  return {
+    actor: actorID,
+    typeName,
+    preview: {
+      attributes: attributesMap,
+      attributesLength: attributes.length,
+      // All the grid containers are assumed to be in the DOM tree.
+      isConnected: true,
+      // nodeName is already lowerCased in Node grips
+      nodeName: nodeName.toLowerCase(),
+      nodeType
+    }
+  };
+};
+
+/**
+ * Build props ingestible by Tree component.
+ * @param  {Object} props      Component properties to be processed.
+ * @param  {String} parentPath Unique path that is used to identify a Tree Node.
+ * @return {Object}            Processed properties.
+ */
+const makeItemsForDetails = (props, parentPath) =>
+  Object.getOwnPropertyNames(props).map(name => {
+    let children = [];
+    let path = `${parentPath}/${name}`;
+    let contents = props[name];
+
+    if (contents) {
+      if (isNode(contents)) {
+        contents = translateNodeFrontToGrip(contents);
+      } else if (Array.isArray(contents) || typeof contents === "object") {
+        children = makeItemsForDetails(contents, path);
+      }
+    }
+
+    return { name, path, contents, children };
+  });
+
+const makeParentMap = (items) => {
+  const map = new WeakMap();
+
+  function _traverse(item) {
+    if (item.children.length > 0) {
+      for (const child of item.children) {
+        map.set(child, item);
+        _traverse(child);
+      }
+    }
+  }
+
+  items.forEach(_traverse);
+  return map;
+};
+
+const mapStateToProps = ({ details }) => {
+  let { accessible, DOMNode } = details;
+  if (!accessible || !DOMNode) {
+    return {};
+  }
+
+  let items = makeItemsForDetails(ORDERED_PROPS.reduce((props, key) => {
+    props[key] = key === "DOMNode" ? DOMNode : accessible[key];
+    return props;
+  }, {}), "");
+  let parents = makeParentMap(items);
+
+  return { accessible, DOMNode, items, parents };
+};
+
+module.exports = connect(mapStateToProps)(Accessible);
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/components/Button.js
@@ -0,0 +1,63 @@
+/* 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 { Component } = require("devtools/client/shared/vendor/react");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const { button, span } = require("devtools/client/shared/vendor/react-dom-factories");
+
+const defaultProps = {
+  disabled: false,
+  busy: false,
+  title: null,
+  children: null,
+  className: ""
+};
+
+/**
+ * Button component that handles keyboard in an accessible way. When user
+ * uses the mouse to hover/click on the button, there is no focus
+ * highlighting. However if the user uses a keyboard to focus on the button,
+ * it will have focus highlighting in the form of an outline.
+ */
+class Button extends Component {
+  static get propTypes() {
+    return {
+      disabled: PropTypes.bool,
+      busy: PropTypes.bool,
+      title: PropTypes.string,
+      children: PropTypes.string,
+      className: PropTypes.string
+    };
+  }
+
+  static get defaultProps() {
+    return defaultProps;
+  }
+
+  render() {
+    let className = [
+      ...this.props.className.split(" "),
+      "devtools-button"
+    ].join(" ");
+    let props = Object.assign({}, this.props, {
+      className,
+      "aria-busy": this.props.busy
+    });
+
+    let classList = ["btn-content"];
+    if (this.props.busy) {
+      classList.push("devtools-throbber");
+    }
+
+    return (button(props, span({
+      className: classList.join(" "),
+      tabIndex: -1
+    }, this.props.children)));
+  }
+}
+
+// Exports from this module
+module.exports = Button;
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/components/Description.js
@@ -0,0 +1,119 @@
+/* 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";
+
+// React & Redux
+const { createFactory, Component } = require("devtools/client/shared/vendor/react");
+const { div, p, img } = require("devtools/client/shared/vendor/react-dom-factories");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+
+const Button = createFactory(require("./Button"));
+const { enable, updateCanBeEnabled } = require("../actions/ui");
+
+// Localization
+const { L10N } = require("../utils/l10n");
+
+class OldVersionDescription extends Component {
+  render() {
+    return (
+      div({ className: "description" },
+        p({ className: "general" },
+          img({
+            src: "chrome://devtools/skin/images/accessibility.svg",
+            alt: L10N.getStr("accessibility.logo")
+          }), L10N.getStr("accessibility.description.oldVersion")))
+    );
+  }
+}
+
+/**
+ * Landing UI for the accessibility panel when Accessibility features are
+ * deactivated.
+ */
+class Description extends Component {
+  static get propTypes() {
+    return {
+      accessibility: PropTypes.object.isRequired,
+      canBeEnabled: PropTypes.bool,
+      dispatch: PropTypes.func.isRequired
+    };
+  }
+
+  constructor(props) {
+    super(props);
+
+    this.state = {
+      enabling: false
+    };
+
+    this.onEnable = this.onEnable.bind(this);
+    this.onCanBeEnabledChange = this.onCanBeEnabledChange.bind(this);
+  }
+
+  componentWillMount() {
+    this.props.accessibility.on("can-be-enabled-change",
+      this.onCanBeEnabledChange);
+  }
+
+  componentWillUnmount() {
+    this.props.accessibility.off("can-be-enabled-change",
+      this.onCanBeEnabledChange);
+  }
+
+  onEnable() {
+    let { accessibility, dispatch } = this.props;
+    this.setState({ enabling: true });
+    dispatch(enable(accessibility))
+      .then(() => this.setState({ enabling: false }))
+      .catch(() => this.setState({ enabling: false }));
+  }
+
+  onCanBeEnabledChange(canBeEnabled) {
+    this.props.dispatch(updateCanBeEnabled(canBeEnabled));
+  }
+
+  render() {
+    let { canBeEnabled } = this.props;
+    let { enabling } = this.state;
+    let enableButtonStr = enabling ? "accessibility.enabling" : "accessibility.enable";
+
+    let title;
+    let disableButton = false;
+
+    if (canBeEnabled) {
+      title = L10N.getStr("accessibility.enable.enabledTitle");
+    } else {
+      disableButton = true;
+      title = L10N.getStr("accessibility.enable.disabledTitle");
+    }
+
+    return (
+      div({ className: "description" },
+        p({ className: "general" },
+          img({
+            src: "chrome://devtools/skin/images/accessibility.svg",
+            alt: L10N.getStr("accessibility.logo")
+          }),
+          L10N.getStr("accessibility.description.general")),
+        Button({
+          id: "accessibility-enable-button",
+          onClick: this.onEnable,
+          disabled: enabling || disableButton,
+          busy: enabling,
+          "data-standalone": true,
+          title
+        }, L10N.getStr(enableButtonStr))
+      )
+    );
+  }
+}
+
+const mapStateToProps = ({ ui }) => ({
+  canBeEnabled: ui.canBeEnabled
+});
+
+// Exports from this module
+exports.Description = connect(mapStateToProps)(Description);
+exports.OldVersionDescription = OldVersionDescription;
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/components/MainFrame.js
@@ -0,0 +1,126 @@
+/* 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 */
+
+// React & Redux
+const { Component, createFactory } = require("devtools/client/shared/vendor/react");
+const { div } = require("devtools/client/shared/vendor/react-dom-factories");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+const { reset } = require("../actions/ui");
+
+// Constants
+const { SIDEBAR_WIDTH, PORTRAIT_MODE_WIDTH } = require("../constants");
+
+// Accessibility Panel
+const AccessibilityTree = createFactory(require("./AccessibilityTree"));
+const Description = createFactory(require("./Description").Description);
+const RightSidebar = createFactory(require("./RightSidebar"));
+const Toolbar = createFactory(require("./Toolbar"));
+const SplitBox = createFactory(require("devtools/client/shared/components/splitter/SplitBox"));
+
+/**
+ * Renders basic layout of the Accessibility panel. The Accessibility panel
+ * content consists of two main parts: tree and sidebar.
+ */
+class MainFrame extends Component {
+  static get propTypes() {
+    return {
+      accessibility: PropTypes.object.isRequired,
+      walker: PropTypes.object.isRequired,
+      enabled: PropTypes.bool.isRequired,
+      dispatch: PropTypes.func.isRequired
+    };
+  }
+
+  constructor(props) {
+    super(props);
+
+    this.resetAccessibility = this.resetAccessibility.bind(this);
+    this.onPanelWindowResize = this.onPanelWindowResize.bind(this);
+  }
+
+  componentWillMount() {
+    // Need inspector for many things such as dom node preview etc.
+    gToolbox.loadTool("inspector");
+    this.props.accessibility.on("init", this.resetAccessibility);
+    this.props.accessibility.on("shutdown", this.resetAccessibility);
+    this.props.walker.on("document-ready", this.resetAccessibility);
+
+    window.addEventListener("resize", this.onPanelWindowResize, true);
+  }
+
+  componentWillReceiveProps({ enabled }) {
+    if (this.props.enabled && !enabled) {
+      this.resetAccessibility();
+    }
+  }
+
+  componentWillUnmount() {
+    this.props.accessibility.off("init", this.resetAccessibility);
+    this.props.accessibility.off("shutdown", this.resetAccessibility);
+    this.props.walker.off("document-ready", this.resetAccessibility);
+
+    window.removeEventListener("resize", this.onPanelWindowResize, true);
+  }
+
+  resetAccessibility() {
+    let { dispatch, accessibility } = this.props;
+    dispatch(reset(accessibility));
+  }
+
+  get useLandscapeMode() {
+    let { clientWidth } = document.getElementById("content");
+    return clientWidth > PORTRAIT_MODE_WIDTH;
+  }
+
+  /**
+   * If panel width is less than PORTRAIT_MODE_WIDTH px, the splitter changes
+   * its mode to `horizontal` to support portrait view.
+   */
+  onPanelWindowResize() {
+    if (this.refs.splitBox) {
+      this.refs.splitBox.setState({ vert: this.useLandscapeMode });
+    }
+  }
+
+  /**
+   * Render Accessibility panel content
+   */
+  render() {
+    let { accessibility, walker, enabled } = this.props;
+
+    if (!enabled) {
+      return Description({ accessibility });
+    }
+
+    return (
+      div({ className: "mainFrame", role: "presentation" },
+        Toolbar({ accessibility, walker }),
+        SplitBox({
+          ref: "splitBox",
+          initialSize: SIDEBAR_WIDTH,
+          minSize: "10px",
+          maxSize: "80%",
+          splitterSize: 1,
+          endPanelControl: true,
+          startPanel: div({
+            className: "main-panel",
+            role: "presentation"
+          }, AccessibilityTree({ walker })),
+          endPanel: RightSidebar(),
+          vert: this.useLandscapeMode,
+        })
+      ));
+  }
+}
+
+const mapStateToProps = ({ ui }) => ({
+  enabled: ui.enabled
+});
+
+// Exports from this module
+module.exports = connect(mapStateToProps)(MainFrame);
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/components/RightSidebar.js
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+// React
+const { Component, createFactory } = require("devtools/client/shared/vendor/react");
+const { div } = require("devtools/client/shared/vendor/react-dom-factories");
+
+const { L10N } = require("../utils/l10n");
+const Accessible = createFactory(require("./Accessible"));
+
+// Component that is responsible for rendering accessible panel's sidebar.
+class RightSidebar extends Component {
+  /**
+   * Render the sidebar component.
+   * @returns Sidebar React component.
+   */
+  render() {
+    const headerID = "accessibility-right-sidebar-header";
+    return (
+      div({
+        className: "right-sidebar",
+        role: "presentation"
+      },
+        div({
+          className: "_header",
+          id: headerID,
+          role: "heading"
+        }, L10N.getStr("accessibility.properties")),
+        div({
+          className: "_content accessible",
+          role: "presentation"
+        }, Accessible({ labelledby: headerID }))
+      )
+    );
+  }
+}
+
+module.exports = RightSidebar;
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/components/Toolbar.js
@@ -0,0 +1,94 @@
+/* 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";
+
+// React
+const { createFactory, Component } = require("devtools/client/shared/vendor/react");
+const { div } = require("devtools/client/shared/vendor/react-dom-factories");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const { L10N } = require("../utils/l10n");
+const Button = createFactory(require("./Button"));
+
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+const { disable, updateCanBeDisabled } = require("../actions/ui");
+
+class Toolbar extends Component {
+  static get propTypes() {
+    return {
+      dispatch: PropTypes.func.isRequired,
+      accessibility: PropTypes.object.isRequired,
+      canBeDisabled: PropTypes.bool.isRequired
+    };
+  }
+
+  constructor(props) {
+    super(props);
+
+    this.state = {
+      disabling: false
+    };
+
+    this.onDisable = this.onDisable.bind(this);
+    this.onCanBeDisabledChange = this.onCanBeDisabledChange.bind(this);
+  }
+
+  componentWillMount() {
+    this.props.accessibility.on("can-be-disabled-change",
+      this.onCanBeDisabledChange);
+  }
+
+  componentWillUnmount() {
+    this.props.accessibility.off("can-be-disabled-change",
+      this.onCanBeDisabledChange);
+  }
+
+  onCanBeDisabledChange(canBeDisabled) {
+    this.props.dispatch(updateCanBeDisabled(canBeDisabled));
+  }
+
+  onDisable() {
+    let { accessibility, dispatch } = this.props;
+    this.setState({ disabling: true });
+    dispatch(disable(accessibility))
+      .then(() => this.setState({ disabling: false }))
+      .catch(() => this.setState({ disabling: false }));
+  }
+
+  render() {
+    let { canBeDisabled } = this.props;
+    let { disabling } = this.state;
+    let disableButtonStr = disabling ?
+      "accessibility.disabling" : "accessibility.disable";
+    let title;
+    let isDisabled = false;
+
+    if (canBeDisabled) {
+      title = L10N.getStr("accessibility.disable.enabledTitle");
+    } else {
+      isDisabled = true;
+      title = L10N.getStr("accessibility.disable.disabledTitle");
+    }
+
+    return (
+      div({
+        className: "devtools-toolbar",
+        role: "toolbar"
+      }, Button({
+        className: "disable",
+        id: "accessibility-disable-button",
+        onClick: this.onDisable,
+        disabled: disabling || isDisabled,
+        busy: disabling,
+        title
+      }, L10N.getStr(disableButtonStr)))
+    );
+  }
+}
+
+const mapStateToProps = ({ ui }) => ({
+  canBeDisabled: ui.canBeDisabled
+});
+
+// Exports from this module
+module.exports = connect(mapStateToProps)(Toolbar);
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/components/moz.build
@@ -0,0 +1,14 @@
+# 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/.
+
+DevToolsModules(
+    'AccessibilityRow.js',
+    'AccessibilityTree.js',
+    'Accessible.js',
+    'Button.js',
+    'Description.js',
+    'MainFrame.js',
+    'RightSidebar.js',
+    'Toolbar.js'
+)
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/constants.js
@@ -0,0 +1,63 @@
+/* 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";
+
+// Used in accessible component for properties tree rendering.
+exports.TREE_ROW_HEIGHT = 21;
+
+// Initial sidebar width.
+exports.SIDEBAR_WIDTH = "350px";
+
+// When value is updated either in the tree or sidebar.
+exports.VALUE_FLASHING_DURATION = 500;
+// When new row is selected, flash highlighter.
+exports.VALUE_HIGHLIGHT_DURATION = 1000;
+
+// If the panel width is smaller than given amount of pixels,
+// the sidebar automatically switches from 'landscape' to 'portrait' mode.
+exports.PORTRAIT_MODE_WIDTH = 700;
+
+// Names for Redux actions.
+exports.FETCH_CHILDREN = "FETCH_CHILDREN";
+exports.UPDATE_DETAILS = "UPDATE_DETAILS";
+exports.RESET = "RESET";
+exports.SELECT = "SELECT";
+exports.HIGHLIGHT = "HIGHLIGHT";
+exports.UNHIGHLIGHT = "UNHIGHLIGHT";
+exports.ENABLE = "ENABLE";
+exports.DISABLE = "DISABLE";
+exports.UPDATE_CAN_BE_DISABLED = "UPDATE_CAN_BE_DISABLED";
+exports.UPDATE_CAN_BE_ENABLED = "UPDATE_CAN_BE_ENABLED";
+
+// Ordered accessible properties to be displayed by the accessible component.
+exports.ORDERED_PROPS = [
+  "name",
+  "role",
+  "actions",
+  "value",
+  "DOMNode",
+  "description",
+  "help",
+  "keyboardShortcut",
+  "childCount",
+  "indexInParent",
+  "states",
+  "attributes"
+];
+
+// Accessible events (emitted by accessible front) that the accessible component
+// listens to for a current accessible.
+exports.ACCESSIBLE_EVENTS = [
+  "actions-change",
+  "attributes-change",
+  "description-change",
+  "help-change",
+  "name-change",
+  "reorder",
+  "shortcut-change",
+  "states-change",
+  "text-change",
+  "value-change",
+  "index-in-parent-change"
+];
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/main.js
@@ -0,0 +1,16 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { utils: Cu } = Components;
+const { BrowserLoader } = Cu.import("resource://devtools/client/shared/browser-loader.js", {});
+
+// Module Loader
+const require = BrowserLoader({
+  baseURI: "resource://devtools/client/accessibility/",
+  window
+}).require;
+
+// Load accessibility panel content
+require("./accessibility-view.js");
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/moz.build
@@ -0,0 +1,24 @@
+# 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/.
+
+BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
+
+DIRS += [
+    'actions',
+    'components',
+    'reducers',
+    'utils'
+]
+
+DevToolsModules(
+    'accessibility-panel.js',
+    'accessibility-view.js',
+    'accessibility.css',
+    'constants.js',
+    'picker.js',
+    'provider.js',
+)
+
+with Files('**'):
+    BUG_COMPONENT = ('Firefox', 'Developer Tools: Accessibility')
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/picker.js
@@ -0,0 +1,184 @@
+/* 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 { L10N } = require("./utils/l10n");
+
+class Picker {
+  constructor(panel) {
+    this._panel = panel;
+
+    this.isPicking = false;
+
+    this.onPickerAccessibleHovered = this.onPickerAccessibleHovered.bind(this);
+    this.onPickerAccessiblePicked = this.onPickerAccessiblePicked.bind(this);
+    this.onPickerAccessiblePreviewed = this.onPickerAccessiblePreviewed.bind(this);
+    this.onPickerAccessibleCanceled = this.onPickerAccessibleCanceled.bind(this);
+  }
+
+  get toolbox() {
+    return this._panel._toolbox;
+  }
+
+  get walker() {
+    return this._panel._walker;
+  }
+
+  get pickerButton() {
+    return this.toolbox.pickerButton;
+  }
+
+  release() {
+    this._panel = null;
+  }
+
+  emit(event, data) {
+    this.toolbox.emit(event, data);
+  }
+
+  /**
+   * Select accessible object in the tree.
+   * @param  {Object} accessible
+   *         Accessiblle object to be selected in the inspector tree.
+   */
+  select(accessible) {
+    this._panel.selectAccessible(accessible);
+  }
+
+  /**
+   * Highlight accessible object in the tree.
+   * @param  {Object} accessible
+   *         Accessiblle object to be selected in the inspector tree.
+   */
+  highlight(accessible) {
+    this._panel.highlightAccessible(accessible);
+  }
+
+  getStr(key) {
+    return L10N.getStr(key);
+  }
+
+  /**
+   * Override the default presentation of the picker button in toolbox's top
+   * level toolbar.
+   */
+  updateButton() {
+    this.pickerButton.description = this.getStr("accessibility.pick");
+    this.pickerButton.className = "accessibility";
+    this.pickerButton.disabled = !this._panel._front.enabled;
+    if (!this._panel._front.enabled && this.isPicking) {
+      this.cancel();
+    }
+  }
+
+  /**
+   * Handle an event when a new accessible object is hovered over.
+   * @param  {Object} accessible
+   *         newly hovered accessible object
+   */
+  onPickerAccessibleHovered(accessible) {
+    if (accessible) {
+      this.emit("picker-accessible-hovered", accessible);
+      this.highlight(accessible);
+    }
+  }
+
+  /**
+   * Handle an event when a new accessible is picked by the user.
+   * @param  {Object} accessible
+   *         newly picked accessible object
+   */
+  onPickerAccessiblePicked(accessible) {
+    if (accessible) {
+      this.select(accessible);
+    }
+    this.stop();
+  }
+
+  /**
+   * Handle an event when a new accessible is previewed by the user.
+   * @param  {Object} accessible
+   *         newly previewed accessible object
+   */
+  onPickerAccessiblePreviewed(accessible) {
+    if (accessible) {
+      this.select(accessible);
+    }
+  }
+
+  /**
+   * Handle an event when picking is cancelled by the user.s
+   */
+  onPickerAccessibleCanceled() {
+    this.cancel();
+    this.emit("picker-accessible-canceled");
+  }
+
+  /**
+   * Cancel picking.
+   */
+  async cancel() {
+    await this.stop();
+  }
+
+  /**
+   * Stop picking and remove all walker listeners.
+   */
+  async stop() {
+    if (!this.isPicking) {
+      return;
+    }
+
+    this.isPicking = false;
+
+    this.pickerButton.isChecked = false;
+
+    await this.walker.cancelPick();
+    this.walker.off("picker-accessible-hovered", this.onPickerAccessibleHovered);
+    this.walker.off("picker-accessible-picked", this.onPickerAccessiblePicked);
+    this.walker.off("picker-accessible-previewed", this.onPickerAccessiblePreviewed);
+    this.walker.off("picker-accessible-canceled", this.onPickerAccessibleCanceled);
+
+    this.emit("picker-stopped");
+  }
+
+  /**
+   * Start picking and add walker listeners.
+   * @param  {Boolean} doFocus
+   *         If true, move keyboard focus into content.
+   */
+  async start(doFocus = true) {
+    if (this.isPicking) {
+      return;
+    }
+
+    this.isPicking = true;
+
+    this.pickerButton.isChecked = true;
+
+    this.walker.on("picker-accessible-hovered", this.onPickerAccessibleHovered);
+    this.walker.on("picker-accessible-picked", this.onPickerAccessiblePicked);
+    this.walker.on("picker-accessible-previewed", this.onPickerAccessiblePreviewed);
+    this.walker.on("picker-accessible-canceled", this.onPickerAccessibleCanceled);
+
+    await this.walker.pick(doFocus);
+    this.emit("picker-started");
+  }
+
+  /**
+   * Toggle between starting and canceling the picker.
+   * @param  {Boolean} doFocus
+   *         If true, move keyboard focus into content.
+   */
+  toggle(doFocus) {
+    if (this.isPicking) {
+      return this.cancel();
+    }
+
+    return this.start(doFocus);
+  }
+}
+
+exports.Picker = Picker;
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/provider.js
@@ -0,0 +1,92 @@
+/* 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 { fetchChildren } = require("./actions/accessibles");
+
+/**
+ * Data provider that is responsible for mapping of an accessibles cache to the
+ * data format that is supported by the TreeView component.
+ * @param {Map}      accessibles accessibles object cache
+ * @param {Function} dispatch    react dispatch function that triggers a redux
+ *                               action.
+ */
+
+class Provider {
+  constructor(accessibles, dispatch) {
+    this.accessibles = accessibles;
+    this.dispatch = dispatch;
+  }
+
+  /**
+   * Get accessible's cached children if available, if not fetch them from
+   * backend.
+   * @param {Object}  accessible accessible object whose children to get.
+   * @returns {Array} arraof of accessible children.
+   */
+  getChildren(accessible) {
+    if (!accessible || !accessible.actor || accessible.childCount === 0) {
+      return [];
+    }
+
+    let obj = this.accessibles.get(accessible.actorID);
+    if (!obj || !obj.children) {
+      return this.dispatch(fetchChildren(accessible));
+    }
+
+    return obj.children;
+  }
+
+  /**
+   * Return a flag indicating if an accessible object has any children.
+   * @param {Object}    accessible accessible object whose children to get.
+   * @returns {Boolean} idicator of whether accessible object has children.
+   */
+  hasChildren(accessible) {
+    return accessible.childCount > 0;
+  }
+
+  /**
+   * Get a value for an accessible object. Used to render the second (value)
+   * column of the accessible tree. Corresponds to an accesible object name, if
+   * available.
+   * @param {Object}   accessible accessible object
+   * @returns {String} accessible object value.
+   */
+  getValue(accessible) {
+    return accessible.name || "";
+  }
+
+  /**
+   * Get a label for an accessible object. Used to render the first column of
+   * the accessible tree. Corresponds to an accessible object role.
+   * @param {Object}   accessible accessible object
+   * @returns {String} accessible object label.
+   */
+  getLabel(accessible) {
+    return accessible.role;
+  }
+
+  /**
+   * Get a unique key for an accessible object. Corresponds to an accessible
+   * front's actorID.
+   * @param {Object}   accessible accessible object
+   * @returns {String} a key for an accessible object.
+   */
+  getKey(accessible) {
+    return accessible.actorID;
+  }
+
+  /**
+   * Get a type of an accesible object. Corresponds to the type of an accessible
+   * front.
+   * @param {Object}   accessible accessible object
+   * @returns {String} accessible object type
+   */
+  getType(accessible) {
+    return accessible.typeName;
+  }
+}
+
+exports.Provider = Provider;
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/reducers/accessibles.js
@@ -0,0 +1,124 @@
+/* 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 { FETCH_CHILDREN, RESET, SELECT, HIGHLIGHT } = require("../constants");
+
+/**
+ * Initial state definition
+ */
+function getInitialState() {
+  return new Map();
+}
+
+/**
+ * Maintain a cache of received accessibles responses from the backend.
+ */
+function accessibles(state = getInitialState(), action) {
+  switch (action.type) {
+    case FETCH_CHILDREN:
+      return onReceiveChildren(state, action);
+    case HIGHLIGHT:
+    case SELECT:
+      return onReceiveAncestry(state, action);
+    case RESET:
+      return getInitialState();
+    default:
+      return state;
+  }
+}
+
+/**
+ * If accessible is cached recursively remove all its children and remove itself
+ * from cache.
+ * @param {Map}    cache      Previous state maintaining a cache of previously
+ *                            fetched accessibles.
+ * @param {Object} accessible Accessible object to remove from cache.
+ */
+function cleanupChild(cache, accessible) {
+  let cached = cache.get(accessible.actorID);
+  if (!cached) {
+    return;
+  }
+
+  for (let child of cached.children) {
+    cleanupChild(cache, child);
+  }
+
+  cache.delete(accessible.actorID);
+}
+
+/**
+ * Determine if accessible in cache is stale. Accessible object is stale if its
+ * cached children array has the size other than the value of its childCount
+ * property that updates on accessible actor event.
+ * @param {Map}    cache      Previous state maintaining a cache of previously
+ *                             fetched accessibles.
+ * @param {Object} accessible Accessible object to test for staleness.
+ */
+function staleChildren(cache, accessible) {
+  let cached = cache.get(accessible.actorID);
+  if (!cached) {
+    return false;
+  }
+
+  return cached.children.length !== accessible.childCount;
+}
+
+function updateChildrenCache(cache, accessible, children) {
+  let { actorID } = accessible;
+
+  if (cache.has(actorID)) {
+    let cached = cache.get(actorID);
+    for (let child of cached.children) {
+      // If exhisting children cache includes an accessible that is not present
+      // any more or if child accessible is stale remove it and all its children
+      // from cache.
+      if (!children.includes(child) || staleChildren(cache, child)) {
+        cleanupChild(cache, child);
+      }
+    }
+    cached.children = children;
+    cache.set(actorID, cached);
+  } else {
+    cache.set(actorID, { children });
+  }
+
+  return cache;
+}
+
+/**
+ * Handles fetching of accessible children.
+ * @param {Map}     cache  Previous state maintaining a cache of previously
+ *                         fetched accessibles.
+ * @param {Object}  action Redux action object.
+ * @return {Object} updated state
+ */
+function onReceiveChildren(cache, action) {
+  let { accessible, response: children, error } = action;
+
+  if (error) {
+    console.warn("Error fetching children", accessible, error);
+    return cache;
+  }
+
+  return updateChildrenCache(new Map(cache), accessible, children);
+}
+
+function onReceiveAncestry(cache, action) {
+  let { accessible: acc, response: ancestry, error } = action;
+
+  if (error) {
+    console.warn("Error fetching ancestry", acc, error);
+    return cache;
+  }
+
+  let newCache = new Map(cache);
+  ancestry.forEach(({ accessible, children }) =>
+    updateChildrenCache(newCache, accessible, children));
+
+  return newCache;
+}
+
+exports.accessibles = accessibles;
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/reducers/details.js
@@ -0,0 +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";
+
+const { UPDATE_DETAILS, RESET } = require("../constants");
+
+/**
+ * Initial state definition
+ */
+function getInitialState() {
+  return {};
+}
+
+/**
+ * Maintain details of a current relevant accessible.
+ */
+function details(state = getInitialState(), action) {
+  switch (action.type) {
+    case UPDATE_DETAILS:
+      return onUpdateDetails(state, action);
+    case RESET:
+      return getInitialState();
+    default:
+      return state;
+  }
+}
+
+/**
+ * Handle details update for an accessible object
+ * @param {Object} state  Current accessible object details.
+ * @param {Object} action Redux action object
+ * @return {Object}  updated state
+ */
+function onUpdateDetails(state, action) {
+  let { accessible, response: DOMNode, error } = action;
+  if (error) {
+    console.warn("Error fetching DOMNode for accessible", accessible, error);
+    return state;
+  }
+
+  return { accessible, DOMNode };
+}
+
+exports.details = details;
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/reducers/index.js
@@ -0,0 +1,14 @@
+/* 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 { accessibles } = require("./accessibles");
+const { details } = require("./details");
+const { ui } = require("./ui");
+
+exports.reducers = {
+  accessibles,
+  details,
+  ui
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/reducers/moz.build
@@ -0,0 +1,10 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+    'accessibles.js',
+    'details.js',
+    'index.js',
+    'ui.js'
+)
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/reducers/ui.js
@@ -0,0 +1,168 @@
+/* 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 */
+
+const {
+  ENABLE,
+  DISABLE,
+  RESET,
+  SELECT,
+  HIGHLIGHT,
+  UNHIGHLIGHT,
+  UPDATE_CAN_BE_DISABLED,
+  UPDATE_CAN_BE_ENABLED,
+  UPDATE_DETAILS
+} = require("../constants");
+
+const TreeView = require("devtools/client/shared/components/tree/TreeView");
+
+/**
+ * Initial state definition
+ */
+function getInitialState() {
+  return {
+    enabled: false,
+    canBeDisabled: true,
+    canBeEnabled: true,
+    selected: null,
+    highlighted: null,
+    expanded: new Set()
+  };
+}
+
+/**
+ * Maintain ui components of the accessibility panel.
+ */
+function ui(state = getInitialState(), action) {
+  switch (action.type) {
+    case ENABLE:
+      return onToggle(state, action, true);
+    case DISABLE:
+      return onToggle(state, action, false);
+    case UPDATE_CAN_BE_DISABLED:
+      return onCanBeDisabledChange(state, action);
+    case UPDATE_CAN_BE_ENABLED:
+      return onCanBeEnabledChange(state, action);
+    case UPDATE_DETAILS:
+      return onUpdateDetails(state, action);
+    case HIGHLIGHT:
+      return onHighlight(state, action);
+    case UNHIGHLIGHT:
+      return onUnhighlight(state, action);
+    case SELECT:
+      return onSelect(state, action);
+    case RESET:
+      return onReset(state, action);
+    default:
+      return state;
+  }
+}
+
+function onUpdateDetails(state) {
+  if (!state.selected) {
+    return state;
+  }
+
+  // Clear selected state that should only be set when select action is
+  // performed.
+  return Object.assign({}, state, { selected: null });
+}
+
+function onUnhighlight(state) {
+  return Object.assign({}, state, { highlighted: null });
+}
+
+function updateExpandedNodes(state, ancestry) {
+  let expanded = new Set(state.expanded);
+  let path = ancestry.reduceRight((accPath, { accessible }) => {
+    accPath = TreeView.subPath(accPath, accessible.actorID);
+    expanded.add(accPath);
+    return accPath;
+  }, "");
+
+  return { path, expanded };
+}
+
+function onHighlight(state, { accessible, response: ancestry, error }) {
+  if (error) {
+    console.warn("Error fetching ancestry", accessible, error);
+    return state;
+  }
+
+  let { expanded } = updateExpandedNodes(state, ancestry);
+  return Object.assign({}, state, { expanded, highlighted: accessible });
+}
+
+function onSelect(state, { accessible, response: ancestry, error }) {
+  if (error) {
+    console.warn("Error fetching ancestry", accessible, error);
+    return state;
+  }
+
+  let { path, expanded } = updateExpandedNodes(state, ancestry);
+  let selected = TreeView.subPath(path, accessible.actorID);
+
+  return Object.assign({}, state, { expanded, selected });
+}
+
+/**
+ * Handle "canBeDisabled" flag update for accessibility service
+ * @param  {Object}  state   Current ui state
+ * @param  {Object}  action  Redux action object
+ * @return {Object}  updated state
+ */
+function onCanBeDisabledChange(state, { canBeDisabled }) {
+  return Object.assign({}, state, { canBeDisabled });
+}
+
+/**
+ * Handle "canBeEnabled" flag update for accessibility service
+ * @param  {Object}  state   Current ui state.
+ * @param  {Object}  action  Redux action object
+ * @return {Object}  updated state
+ */
+function onCanBeEnabledChange(state, { canBeEnabled }) {
+  return Object.assign({}, state, { canBeEnabled });
+}
+
+/**
+ * Handle reset action for the accessibility panel UI.
+ * @param  {Object}  state   Current ui state.
+ * @param  {Object}  action  Redux action object
+ * @return {Object}  updated state
+ */
+function onReset(state, { accessibility }) {
+  let { enabled, canBeDisabled, canBeEnabled } = accessibility;
+  toggleHighlightTool(enabled);
+  return Object.assign({}, state, { enabled, canBeDisabled, canBeEnabled });
+}
+
+/**
+ * Handle accessibilty service enabling/disabling.
+ * @param {Object}  state   Current accessibility services enabled state.
+ * @param {Object}  action  Redux action object
+ * @param {Boolean} enabled New enabled state.
+ * @return {Object}  updated state
+ */
+function onToggle(state, { error }, enabled) {
+  if (error) {
+    console.warn("Error enabling accessibility service: ", error);
+    return state;
+  }
+
+  toggleHighlightTool(enabled);
+  return Object.assign({}, state, { enabled });
+}
+
+function toggleHighlightTool(enabled) {
+  if (enabled) {
+    gToolbox.highlightTool("accessibility");
+  } else {
+    gToolbox.unhighlightTool("accessibility");
+  }
+}
+
+exports.ui = ui;
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/test/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+  // Extend from the shared list of defined globals for mochitests.
+  "extends": "../../../.eslintrc.mochitests.js"
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/test/browser.ini
@@ -0,0 +1,14 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+support-files =
+  head.js
+  !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/inspector/test/shared-head.js
+  !/devtools/client/shared/test/shared-redux-head.js
+
+[browser_accessibility_mutations.js]
+[browser_accessibility_reload.js]
+[browser_accessibility_sidebar.js]
+[browser_accessibility_tree.js]
+[browser_accessibility_tree_nagivation.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/test/browser_accessibility_mutations.js
@@ -0,0 +1,118 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URI = `<html>
+  <head>
+    <meta charset="utf-8"/>
+    <title>Accessibility Panel Test</title>
+  </head>
+  <body>
+    <h1 id="h1">Top level header</h1>
+    <p id="p">This is a paragraph.</p>
+  </body>
+</html>`;
+
+/**
+ * Test data has the format of:
+ * {
+ *   desc     {String}    description for better logging
+ *   action   {Function}  An optional action that needs to be performed before
+ *                        the state of the tree and the sidebar can be checked.
+ *   expected {JSON}      An expected states for the tree and the sidebar.
+ * }
+ */
+const tests = [{
+  desc: "Expand first and second rows, select third row.",
+  action: async ({ doc }) => {
+    await toggleRow(doc, 0);
+    await toggleRow(doc, 1);
+    selectRow(doc, 3);
+  },
+  expected: {
+    tree: [{
+      role: "document",
+      name: `"Accessibility Panel Test"`
+    }, {
+      role: "heading",
+      name: `"Top level header"`
+    }, {
+      role: "text leaf",
+      name: `"Top level header"`
+    }, {
+      role: "paragraph",
+      name: `""`
+    }],
+    sidebar: {
+      name: null,
+      role: "paragraph",
+      actions: [],
+      value: "",
+      description: "",
+      help: "",
+      keyboardShortcut: "",
+      childCount: 1,
+      indexInParent: 1,
+      states: ["selectable text", "opaque", "enabled", "sensitive"]
+    }
+  }
+}, {
+  desc: "Remove a child from a document.",
+  action: async ({ doc, browser }) => {
+    is(doc.querySelectorAll(".treeRow").length, 4, "Tree size is correct.");
+    await ContentTask.spawn(browser, {}, () =>
+      content.document.getElementById("p").remove());
+    await BrowserTestUtils.waitForCondition(() =>
+      doc.querySelectorAll(".treeRow").length === 3, "Tree updated.");
+  },
+  expected: {
+    tree: [{
+      role: "document",
+      name: `"Accessibility Panel Test"`
+    }, {
+      role: "heading",
+      name: `"Top level header"`
+    }, {
+      role: "text leaf",
+      name: `"Top level header"`
+    }],
+    sidebar: {
+      name: "Top level header",
+      role: "text leaf"
+    }
+  }
+}, {
+  desc: "Update child's text content.",
+  action: async ({ browser }) => {
+    await ContentTask.spawn(browser, {}, () => {
+      content.document.getElementById("h1").textContent = "New Header";
+    });
+  },
+  expected: {
+    tree: [{
+      role: "document",
+      name: `"Accessibility Panel Test"`
+    }, {
+      role: "heading",
+      name: `"New Header"`
+    }, {
+      role: "text leaf",
+      name: `"New Header"`
+    }]
+  }
+}, {
+  desc: "Select third row in the tree.",
+  action: ({ doc }) => selectRow(doc, 1),
+  expected: {
+    sidebar: {
+      name: "New Header"
+    }
+  }
+}];
+
+/**
+ * Test that checks the Accessibility panel after DOM tree mutations.
+ */
+addA11yPanelTestsTask(tests, TEST_URI,
+  "Test Accessibility panel after DOM tree mutations.");
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/test/browser_accessibility_reload.js
@@ -0,0 +1,84 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URI_1 = `<html>
+  <head>
+    <meta charset="utf-8"/>
+    <title>Accessibility Panel Test</title>
+  </head>
+  <body>
+    <h1>Top level header</h1>
+    <p>This is a paragraph.</p>
+  </body>
+</html>`;
+
+const TEST_URI_2 = `<html>
+  <head>
+    <meta charset="utf-8"/>
+    <title>Navigation Accessibility Panel</title>
+  </head>
+  <body></body>
+</html>`;
+
+/**
+ * Test data has the format of:
+ * {
+ *   desc     {String}    description for better logging
+ *   action   {Function}  An optional action that needs to be performed before
+ *                        the state of the tree and the sidebar can be checked.
+ *   expected {JSON}      An expected states for the tree and the sidebar.
+ * }
+ */
+const tests = [{
+  desc: "Test the initial accessibility tree state after first row is expanded.",
+  action: async ({ doc }) => toggleRow(doc, 0),
+  expected: {
+    tree: [{
+      role: "document",
+      name: `"Accessibility Panel Test"`
+    }, {
+      role: "heading",
+      name: `"Top level header"`
+    }, {
+      role: "paragraph",
+      name: `""`
+    }],
+    sidebar: {
+      name: "Accessibility Panel Test",
+      role: "document"
+    }
+  }
+}, {
+  desc: "Reload the page.",
+  action: async ({ panel }) => reload(panel.target),
+  expected: {
+    tree: [{
+      role: "document",
+      name: `"Accessibility Panel Test"`
+    }],
+    sidebar: {
+      name: "Accessibility Panel Test",
+      role: "document"
+    }
+  }
+}, {
+  desc: "Navigate to a new page.",
+  action: async ({ panel }) => navigate(panel.target, buildURL(TEST_URI_2)),
+  expected: {
+    tree: [{
+      role: "document",
+      name: `"Navigation Accessibility Panel"`
+    }],
+    sidebar: {
+      name: "Navigation Accessibility Panel",
+      role: "document"
+    }
+  }
+}];
+
+/**
+ * Simple test that checks content of the Accessibility panel tree on reload.
+ */
+addA11yPanelTestsTask(tests, TEST_URI_1, "Test Accessibility panel tree on reload.");
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/test/browser_accessibility_sidebar.js
@@ -0,0 +1,66 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URI = `<html>
+  <head>
+    <meta charset="utf-8"/>
+    <title>Accessibility Panel Test</title>
+  </head>
+  <body></body>
+</html>`;
+
+/**
+ * Test data has the format of:
+ * {
+ *   desc     {String}    description for better logging
+ *   action   {Function}  An optional action that needs to be performed before
+ *                        the state of the tree and the sidebar can be checked.
+ *   expected {JSON}      An expected states for the tree and the sidebar.
+ * }
+ */
+const tests = [{
+  desc: "Test the initial accessibility sidebar state.",
+  expected: {
+    sidebar: {
+      name: "Accessibility Panel Test",
+      role: "document",
+      actions: [],
+      value: "",
+      description: "",
+      help: "",
+      keyboardShortcut: "",
+      childCount: 0,
+      indexInParent: 0,
+      states: ["readonly", "focusable", "opaque", "enabled", "sensitive"]
+    }
+  }
+}, {
+  desc: "Mark document as disabled for accessibility.",
+  action: async ({ browser }) => ContentTask.spawn(browser, {}, () =>
+    content.document.body.setAttribute("aria-disabled", true)),
+  expected: {
+    sidebar: {
+      states: ["unavailable", "readonly", "focusable", "opaque"]
+    }
+  }
+}, {
+  desc: "Append a new child to the document.",
+  action: async ({ browser }) => ContentTask.spawn(browser, {}, () => {
+    let doc = content.document;
+    let button = doc.createElement("button");
+    button.textContent = "Press Me!";
+    doc.body.appendChild(button);
+  }),
+  expected: {
+    sidebar: {
+      childCount: 1
+    }
+  }
+}];
+
+/**
+ * Test that checks the Accessibility panel sidebar.
+ */
+addA11yPanelTestsTask(tests, TEST_URI, "Test Accessibility panel sidebar.");
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/test/browser_accessibility_tree.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URI = `<html>
+  <head>
+    <meta charset="utf-8"/>
+    <title>Accessibility Panel Test</title>
+  </head>
+  <body>
+    <h1>Top level header</h1>
+    <p>This is a paragraph.</p>
+  </body>
+</html>`;
+
+/**
+ * Test data has the format of:
+ * {
+ *   desc     {String}    description for better logging
+ *   action   {Function}  An optional action that needs to be performed before
+ *                        the state of the tree and the sidebar can be checked.
+ *   expected {JSON}      An expected states for the tree and the sidebar.
+ * }
+ */
+const tests = [{
+  desc: "Test the initial accessibility tree state.",
+  expected: {
+    tree: [{
+      role: "document",
+      name: `"Accessibility Panel Test"`
+    }]
+  }
+}, {
+  desc: "Expand first tree node.",
+  action: async ({ doc }) => toggleRow(doc, 0),
+  expected: {
+    tree: [{
+      role: "document",
+      name: `"Accessibility Panel Test"`
+    }, {
+      role: "heading",
+      name: `"Top level header"`
+    }, {
+      role: "paragraph",
+      name: `""`
+    }]
+  }
+}, {
+  desc: "Collapse first tree node.",
+  action: async ({ doc }) => toggleRow(doc, 0),
+  expected: {
+    tree: [{
+      role: "document",
+      name: `"Accessibility Panel Test"`
+    }]
+  }
+}];
+
+/**
+ * Simple test that checks content of the Accessibility panel tree.
+ */
+addA11yPanelTestsTask(tests, TEST_URI, "Test Accessibility panel tree.");
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/test/browser_accessibility_tree_nagivation.js
@@ -0,0 +1,152 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URI = `<html>
+  <head>
+    <meta charset="utf-8"/>
+    <title>Accessibility Panel Test</title>
+  </head>
+  <body>
+    <h1>Top level header</h1>
+    <p>This is a paragraph.</p>
+  </body>
+</html>`;
+
+/**
+ * Test data has the format of:
+ * {
+ *   desc     {String}    description for better logging
+ *   action   {Function}  An optional action that needs to be performed before
+ *                        the state of the tree and the sidebar can be checked.
+ *   expected {JSON}      An expected states for the tree and the sidebar.
+ * }
+ */
+const tests = [{
+  desc: "Test the initial accessibility tree and sidebar states.",
+  expected: {
+    tree: [{
+      role: "document",
+      name: `"Accessibility Panel Test"`
+    }],
+    sidebar: {
+      name: "Accessibility Panel Test",
+      role: "document",
+      actions: [],
+      value: "",
+      description: "",
+      help: "",
+      keyboardShortcut: "",
+      childCount: 2,
+      indexInParent: 0,
+      states: ["readonly", "focusable", "opaque", "enabled", "sensitive"]
+    }
+  }
+}, {
+  desc: "Expand first tree node.",
+  action: async ({ doc }) => toggleRow(doc, 0),
+  expected: {
+    tree: [{
+      role: "document",
+      name: `"Accessibility Panel Test"`
+    }, {
+      role: "heading",
+      name: `"Top level header"`
+    }, {
+      role: "paragraph",
+      name: `""`
+    } ]
+  }
+}, {
+  desc: "Expand second tree node.",
+  action: async ({ doc }) => toggleRow(doc, 1),
+  expected: {
+    tree: [{
+      role: "document",
+      name: `"Accessibility Panel Test"`
+    }, {
+      role: "heading",
+      name: `"Top level header"`
+    }, {
+      role: "text leaf",
+      name: `"Top level header"`
+    }, {
+      role: "paragraph",
+      name: `""`
+    }],
+    sidebar: {
+      name: "Top level header",
+      role: "heading",
+      actions: [],
+      value: "",
+      description: "",
+      help: "",
+      keyboardShortcut: "",
+      childCount: 1,
+      indexInParent: 0,
+      states: ["selectable text", "opaque", "enabled", "sensitive"]
+    }
+  }
+}, {
+  desc: "Select third tree node.",
+  action: ({ doc }) => selectRow(doc, 2),
+  expected: {
+    sidebar: {
+      name: "Top level header",
+      role: "text leaf",
+      actions: [],
+      value: "",
+      description: "",
+      help: "",
+      keyboardShortcut: "",
+      childCount: 0,
+      indexInParent: 0,
+      states: ["opaque", "enabled", "sensitive"]
+    }
+  }
+}, {
+  desc: "Collapse first tree node.",
+  action: async ({ doc }) => toggleRow(doc, 0),
+  expected: {
+    tree: [{
+      role: "document",
+      name: `"Accessibility Panel Test"`
+    }],
+    sidebar: {
+      name: "Accessibility Panel Test",
+      role: "document",
+      actions: [],
+      value: "",
+      description: "",
+      help: "",
+      keyboardShortcut: "",
+      childCount: 2,
+      indexInParent: 0,
+      states: ["readonly", "focusable", "opaque", "enabled", "sensitive"]
+    }
+  }
+}, {
+  desc: "Expand first tree node again.",
+  action: async ({ doc }) => toggleRow(doc, 0),
+  expected: {
+    tree: [{
+      role: "document",
+      name: `"Accessibility Panel Test"`
+    }, {
+      role: "heading",
+      name: `"Top level header"`
+    }, {
+      role: "text leaf",
+      name: `"Top level header"`
+    }, {
+      role: "paragraph",
+      name: `""`
+    }]
+  }
+}];
+
+/**
+ * Check navigation within the tree.
+ */
+addA11yPanelTestsTask(tests, TEST_URI, "Test Accessibility panel tree navigation.");
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/test/head.js
@@ -0,0 +1,294 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* import-globals-from ../../shared/test/shared-head.js */
+/* import-globals-from ../../inspector/test/shared-head.js */
+
+/* global waitUntilState, gBrowser */
+/* exported addTestTab, checkTreeState, checkSidebarState, selectRow,
+            toggleRow, addA11yPanelTestsTask, reload, navigate */
+
+"use strict";
+
+// Import framework's shared head.
+Services.scriptloader.loadSubScript(
+  "chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js",
+  this);
+
+// Import inspector's shared head.
+Services.scriptloader.loadSubScript(
+  "chrome://mochitests/content/browser/devtools/client/inspector/test/shared-head.js",
+  this);
+
+// Load the shared Redux helpers into this compartment.
+Services.scriptloader.loadSubScript(
+  "chrome://mochitests/content/browser/devtools/client/shared/test/shared-redux-head.js",
+  this);
+
+const { ORDERED_PROPS } = require("devtools/client/accessibility/constants");
+
+// Enable the Accessibility panel
+Services.prefs.setBoolPref("devtools.accessibility.enabled", true);
+
+/**
+ * Wait for accessibility service to shut down. We consider it shut down when
+ * an "a11y-init-or-shutdown" event is received with a value of "0".
+ */
+function shutdownA11y() {
+  if (!Services.appinfo.accessibilityEnabled) {
+    return Promise.resolve();
+  }
+
+  // Force collections to speed up accessibility service shutdown.
+  Cu.forceGC();
+  Cu.forceCC();
+  Cu.forceShrinkingGC();
+
+  return new Promise(resolve => {
+    let observe = (subject, topic, data) => {
+      if (data === "0") {
+        Services.obs.removeObserver(observe, "a11y-init-or-shutdown");
+        resolve();
+      }
+    };
+    // This event is coming from Gecko accessibility module when the
+    // accessibility service is shutdown or initialzied. We attempt to shutdown
+    // accessibility service naturally if there are no more XPCOM references to
+    // a11y related objects (after GC/CC).
+    Services.obs.addObserver(observe, "a11y-init-or-shutdown");
+  });
+}
+
+registerCleanupFunction(async () => {
+  info("Cleaning up...");
+  await shutdownA11y();
+  Services.prefs.clearUserPref("devtools.accessibility.enabled");
+});
+
+const EXPANDABLE_PROPS = ["actions", "states", "attributes"];
+
+/**
+ * Add a new test tab in the browser and load the given url.
+ * @param {String} url
+ *        The url to be loaded in the new tab
+ * @return a promise that resolves to the tab object when
+ *        the url is loaded
+ */
+async function addTestTab(url) {
+  info("Adding a new test tab with URL: '" + url + "'");
+
+  let tab = await addTab(url);
+  let panel = await initAccessibilityPanel(tab);
+  let win = panel.panelWin;
+  let doc = win.document;
+  let store = win.view.store;
+
+  EventUtils.sendMouseEvent({ type: "click" },
+    doc.getElementById("accessibility-enable-button"), win);
+
+  await waitUntilState(store, state =>
+    state.accessibles.size === 1 && state.details.accessible &&
+    state.details.accessible.role === "document");
+
+  // Wait for inspector load here to avoid protocol errors on shutdown, since
+  // accessibility panel test can be too fast.
+  await win.gToolbox.loadTool("inspector");
+
+  return {
+    tab,
+    browser: tab.linkedBrowser,
+    panel,
+    win,
+    doc,
+    store
+  };
+}
+
+/**
+ * Turn off accessibility features from within the panel. We call it before the
+ * cleanup function to make sure that the panel is still present.
+ */
+function disableAccessibilityInspector(env) {
+  let { doc, win } = env;
+  EventUtils.sendMouseEvent({ type: "click" },
+    doc.getElementById("accessibility-disable-button"), win);
+}
+
+/**
+ * Open the Accessibility panel for the given tab.
+ *
+ * @param {nsIDOMElement} tab
+ *        Optional tab element for which you want open the Accessibility panel.
+ *        The default tab is taken from the global variable |tab|.
+ * @return a promise that is resolved once the panel is open.
+ */
+async function initAccessibilityPanel(tab = gBrowser.selectedTab) {
+  let target = TargetFactory.forTab(tab);
+  let toolbox = await gDevTools.showToolbox(target, "accessibility");
+  return toolbox.getCurrentPanel();
+}
+
+/**
+ * Check the state of the accessibility tree.
+ * @param  {document} doc       panel documnent.
+ * @param  {Array}    expected  an array that represents an expected row list.
+ */
+async function checkTreeState(doc, expected) {
+  info("Checking tree state.");
+  let hasExpectedStructure = await BrowserTestUtils.waitForCondition(() =>
+    [...doc.querySelectorAll(".treeRow")].every((row, i) =>
+      row.querySelector(".treeLabelCell").textContent === expected[i].role &&
+      row.querySelector(".treeValueCell").textContent === expected[i].name),
+    "Wait for the right tree update.");
+
+  ok(hasExpectedStructure, "Tree structure is correct.");
+}
+
+/**
+ * Check the state of the accessibility sidebar.
+ * @param  {Object} store         React store for the panel (includes store for
+ *                                the sidebar).
+ * @param  {Object} expectedState Expected state of the sidebar.
+ */
+async function checkSidebarState(store, expectedState) {
+  info("Checking sidebar state.");
+  await waitUntilState(store, ({ details }) => {
+    for (let key of ORDERED_PROPS) {
+      let expected = expectedState[key];
+      if (expected === undefined) {
+        continue;
+      }
+
+      if (EXPANDABLE_PROPS.includes(key)) {
+        if (JSON.stringify(details.accessible[key]) !== JSON.stringify(expected)) {
+          return false;
+        }
+      } else if (details.accessible && details.accessible[key] !== expected) {
+        return false;
+      }
+    }
+
+    ok(true, "Sidebar state is correct.");
+    return true;
+  });
+}
+
+/**
+ * Select tree row.
+ * @param  {document} doc       panel documnent.
+ * @param  {Number}   rowNumber number of the row/tree node to be selected.
+ */
+function selectRow(doc, rowNumber) {
+  info(`Selecting row ${rowNumber}.`);
+  EventUtils.sendMouseEvent({ type: "click" },
+    doc.querySelectorAll(".treeRow")[rowNumber], doc.defaultView);
+}
+
+/**
+ * Toggle an expandable tree row.
+ * @param  {document} doc       panel documnent.
+ * @param  {Number}   rowNumber number of the row/tree node to be toggled.
+ */
+async function toggleRow(doc, rowNumber) {
+  let win = doc.defaultView;
+  let twisty = doc.querySelectorAll(".theme-twisty")[rowNumber];
+  let expected = !twisty.classList.contains("open");
+
+  info(`${expected ? "Expanding" : "Collapsing"} row ${rowNumber}.`);
+
+  EventUtils.sendMouseEvent({ type: "click" }, twisty, win);
+  await BrowserTestUtils.waitForCondition(() =>
+    !twisty.classList.contains("devtools-throbber") &&
+    expected === twisty.classList.contains("open"), "Twisty updated.");
+}
+
+/**
+ * Iterate over actions/tests structure and test the state of the
+ * accessibility panel.
+ * @param  {JSON}   tests test data that has the format of:
+ *                    {
+ *                      desc     {String}    description for better logging
+ *                      action   {Function}  An optional action that needs to be
+ *                                           performed before the state of the
+ *                                           tree and the sidebar can be checked
+ *                      expected {JSON}      An expected states for the tree and
+ *                                           the sidebar
+ *                    }
+ * @param  {Object} env  contains all relevant environment objects (same
+ *                       structure as the return value of 'addTestTab' funciton)
+ */
+async function runA11yPanelTests(tests, env) {
+  for (let { desc, action, expected } of tests) {
+    info(desc);
+
+    if (action) {
+      await action(env);
+    }
+
+    let { tree, sidebar } = expected;
+    if (tree) {
+      await checkTreeState(env.doc, tree);
+    }
+
+    if (sidebar) {
+      await checkSidebarState(env.store, sidebar);
+    }
+  }
+}
+
+/**
+ * Build a valid URL from an HTML snippet.
+ * @param  {String} uri HTML snippet
+ * @return {String}     built URL
+ */
+function buildURL(uri) {
+  return `data:text/html,${encodeURIComponent(uri)}`;
+}
+
+/**
+ * Add a test task based on the test structure and a test URL.
+ * @param  {JSON}   tests  test data that has the format of:
+ *                    {
+ *                      desc     {String}    description for better logging
+ *                      action   {Function}  An optional action that needs to be
+ *                                           performed before the state of the
+ *                                           tree and the sidebar can be checked
+ *                      expected {JSON}      An expected states for the tree and
+ *                                           the sidebar
+ *                    }
+ * @param {String}  uri    test URL
+ * @param {String}  msg    a message that is printed for the test
+ */
+function addA11yPanelTestsTask(tests, uri, msg) {
+  tests.push({
+    desc: "Disable accessibility inspector.",
+    action: env => disableAccessibilityInspector(env),
+    expected: {}
+  });
+  add_task(async function a11yPanelTests() {
+    info(msg);
+    let env = await addTestTab(buildURL(uri));
+    await runA11yPanelTests(tests, env);
+  });
+}
+
+/**
+ * Reload panel target.
+ * @param  {Object} target             Panel target.
+ * @param  {String} waitForTargetEvent Event to wait for after reload.
+ */
+function reload(target, waitForTargetEvent = "navigate") {
+  executeSoon(() => target.activeTab.reload());
+  return once(target, waitForTargetEvent);
+}
+
+/**
+ * Navigate to a new URL within the panel target.
+ * @param  {Object} target             Panel target.
+ * @param  {Srting} url                URL to navigate to.
+ * @param  {String} waitForTargetEvent Event to wait for after reload.
+ */
+function navigate(target, url, waitForTargetEvent = "navigate") {
+  executeSoon(() => target.activeTab.navigateTo(url));
+  return once(target, waitForTargetEvent);
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/utils/l10n.js
@@ -0,0 +1,11 @@
+/* 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 { LocalizationHelper } = require("devtools/shared/l10n");
+
+const A11Y_STRINGS_URI = "devtools/client/locales/accessibility.properties";
+
+exports.L10N = new LocalizationHelper(A11Y_STRINGS_URI);
new file mode 100644
--- /dev/null
+++ b/devtools/client/accessibility/utils/moz.build
@@ -0,0 +1,7 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+    'l10n.js'
+)
--- a/devtools/client/definitions.js
+++ b/devtools/client/definitions.js
@@ -19,16 +19,17 @@ loader.lazyGetter(this, "CanvasDebuggerP
 loader.lazyGetter(this, "WebAudioEditorPanel", () => require("devtools/client/webaudioeditor/panel").WebAudioEditorPanel);
 loader.lazyGetter(this, "MemoryPanel", () => require("devtools/client/memory/panel").MemoryPanel);
 loader.lazyGetter(this, "PerformancePanel", () => require("devtools/client/performance/panel").PerformancePanel);
 loader.lazyGetter(this, "NewPerformancePanel", () => require("devtools/client/performance-new/panel").PerformancePanel);
 loader.lazyGetter(this, "NetMonitorPanel", () => require("devtools/client/netmonitor/panel").NetMonitorPanel);
 loader.lazyGetter(this, "StoragePanel", () => require("devtools/client/storage/panel").StoragePanel);
 loader.lazyGetter(this, "ScratchpadPanel", () => require("devtools/client/scratchpad/scratchpad-panel").ScratchpadPanel);
 loader.lazyGetter(this, "DomPanel", () => require("devtools/client/dom/dom-panel").DomPanel);
+loader.lazyGetter(this, "AccessibilityPanel", () => require("devtools/client/accessibility/accessibility-panel").AccessibilityPanel);
 
 // Other dependencies
 loader.lazyRequireGetter(this, "CommandUtils", "devtools/client/shared/developer-toolbar", true);
 loader.lazyRequireGetter(this, "CommandState", "devtools/shared/gcli/command-state", true);
 loader.lazyRequireGetter(this, "ResponsiveUIManager", "devtools/client/responsive.html/manager", true);
 loader.lazyImporter(this, "ScratchpadManager", "resource://devtools/client/scratchpad/scratchpad-manager.jsm");
 
 const {MultiLocalizationHelper} = require("devtools/shared/l10n");
@@ -433,31 +434,58 @@ Tools.dom = {
     return target.getTrait("webConsoleCommands");
   },
 
   build: function(iframeWindow, toolbox) {
     return new DomPanel(iframeWindow, toolbox);
   }
 };
 
+Tools.accessibility = {
+  id: "accessibility",
+  accesskey: l10n("accessibility.accesskey"),
+  ordinal: 14,
+  modifiers: osString == "Darwin" ? "accel,alt" : "accel,shift",
+  visibilityswitch: "devtools.accessibility.enabled",
+  icon: "chrome://devtools/skin/images/tool-accessibility.svg",
+  url: "chrome://devtools/content/accessibility/accessibility.html",
+  label: l10n("accessibility.label"),
+  panelLabel: l10n("accessibility.panelLabel"),
+  get tooltip() {
+    return l10n("accessibility.tooltip",
+      (osString == "Darwin" ? "Cmd+Opt+" : "Ctrl+Shift+") +
+      l10n("accessibility.commandkey"));
+  },
+  inMenu: true,
+
+  isTargetSupported(target) {
+    return target.hasActor("accessibility");
+  },
+
+  build(iframeWindow, toolbox) {
+    return new AccessibilityPanel(iframeWindow, toolbox);
+  }
+};
+
 var defaultTools = [
   Tools.options,
   Tools.webConsole,
   Tools.inspector,
   Tools.jsdebugger,
   Tools.styleEditor,
   Tools.shaderEditor,
   Tools.canvasDebugger,
   Tools.webAudioEditor,
   Tools.performance,
   Tools.netMonitor,
   Tools.storage,
   Tools.scratchpad,
   Tools.memory,
   Tools.dom,
+  Tools.accessibility,
 ];
 
 exports.defaultTools = defaultTools;
 
 Tools.darkTheme = {
   id: "dark",
   label: l10n("options.darkTheme.label2"),
   ordinal: 1,
--- a/devtools/client/framework/components/toolbox-controller.js
+++ b/devtools/client/framework/components/toolbox-controller.js
@@ -43,17 +43,16 @@ class ToolboxController extends Componen
     this.setOptionsPanel = this.setOptionsPanel.bind(this);
     this.highlightTool = this.highlightTool.bind(this);
     this.unhighlightTool = this.unhighlightTool.bind(this);
     this.setDockButtonsEnabled = this.setDockButtonsEnabled.bind(this);
     this.setHostTypes = this.setHostTypes.bind(this);
     this.setCanCloseToolbox = this.setCanCloseToolbox.bind(this);
     this.setPanelDefinitions = this.setPanelDefinitions.bind(this);
     this.setToolboxButtons = this.setToolboxButtons.bind(this);
-    this.setCanMinimize = this.setCanMinimize.bind(this);
   }
 
   shouldComponentUpdate() {
     return this.state.canRender;
   }
 
   componentWillUnmount() {
     this.state.toolboxButtons.forEach(button => {
@@ -157,21 +156,14 @@ class ToolboxController extends Componen
     });
     toolboxButtons.forEach(button => {
       button.on("updatechecked", this.state.checkedButtonsUpdated);
     });
 
     this.setState({ toolboxButtons }, this.updateButtonIds);
   }
 
-  setCanMinimize(canMinimize) {
-    /* Bug 1177463 - The minimize button is currently hidden until we agree on
-       the UI for it, and until bug 1173849 is fixed too. */
-
-    // this.setState({ canMinimize });
-  }
-
   render() {
     return ToolboxToolbar(Object.assign({}, this.props, this.state));
   }
 }
 
 module.exports = ToolboxController;
--- a/devtools/client/framework/test/browser.ini
+++ b/devtools/client/framework/test/browser.ini
@@ -76,18 +76,16 @@ skip-if = os == 'win' || debug # Bug 128
 [browser_toolbox_dynamic_registration.js]
 [browser_toolbox_getpanelwhenready.js]
 [browser_toolbox_highlight.js]
 [browser_toolbox_hosts.js]
 [browser_toolbox_hosts_size.js]
 [browser_toolbox_hosts_telemetry.js]
 [browser_toolbox_keyboard_navigation.js]
 skip-if = os == "mac" # Full keyboard navigation on OSX only works if Full Keyboard Access setting is set to All Control in System Keyboard Preferences
-[browser_toolbox_minimize.js]
-skip-if = true # Bug 1177463 - Temporarily hide the minimize button
 [browser_toolbox_options.js]
 [browser_toolbox_options_disable_buttons.js]
 [browser_toolbox_options_disable_cache-01.js]
 [browser_toolbox_options_disable_cache-02.js]
 [browser_toolbox_options_disable_js.js]
 [browser_toolbox_options_enable_serviceworkers_testing.js]
 [browser_toolbox_raise.js]
 disabled=Bug 962258
deleted file mode 100644
--- a/devtools/client/framework/test/browser_toolbox_minimize.js
+++ /dev/null
@@ -1,106 +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";
-
-// Test that when the toolbox is displayed in a bottom host, that host can be
-// minimized to just the tabbar height, and maximized again.
-// Also test that while minimized, switching to a tool, clicking on the
-// settings, or clicking on the selected tool's tab maximizes the toolbox again.
-// Finally test that the minimize button doesn't exist in other host types.
-
-const URL = "data:text/html;charset=utf8,test page";
-const {Toolbox} = require("devtools/client/framework/toolbox");
-
-add_task(async function () {
-  info("Create a test tab and open the toolbox");
-  let tab = await addTab(URL);
-  let target = TargetFactory.forTab(tab);
-  let toolbox = await gDevTools.showToolbox(target, "webconsole");
-
-  let button = toolbox.doc.querySelector("#toolbox-dock-bottom-minimize");
-  ok(button, "The minimize button exists in the default bottom host");
-
-  info("Try to minimize the toolbox");
-  await minimize(toolbox);
-  ok(parseInt(toolbox._host.frame.style.marginBottom, 10) < 0,
-     "The toolbox host has been hidden away with a negative-margin");
-
-  info("Try to maximize again the toolbox");
-  await maximize(toolbox);
-  ok(parseInt(toolbox._host.frame.style.marginBottom, 10) == 0,
-     "The toolbox host is shown again");
-
-  info("Try to minimize again using the keyboard shortcut");
-  await minimizeWithShortcut(toolbox);
-  ok(parseInt(toolbox._host.frame.style.marginBottom, 10) < 0,
-     "The toolbox host has been hidden away with a negative-margin");
-
-  info("Try to maximize again using the keyboard shortcut");
-  await maximizeWithShortcut(toolbox);
-  ok(parseInt(toolbox._host.frame.style.marginBottom, 10) == 0,
-     "The toolbox host is shown again");
-
-  info("Minimize again and switch to another tool");
-  await minimize(toolbox);
-  let onMaximized = toolbox._host.once("maximized");
-  await toolbox.selectTool("inspector");
-  await onMaximized;
-
-  info("Minimize again and click on the tab of the current tool");
-  await minimize(toolbox);
-  onMaximized = toolbox._host.once("maximized");
-  let tabButton = toolbox.doc.querySelector("#toolbox-tab-inspector");
-  EventUtils.synthesizeMouseAtCenter(tabButton, {}, toolbox.win);
-  await onMaximized;
-
-  info("Minimize again and click on the settings tab");
-  await minimize(toolbox);
-  onMaximized = toolbox._host.once("maximized");
-  let settingsButton = toolbox.doc.querySelector("#toolbox-tab-options");
-  EventUtils.synthesizeMouseAtCenter(settingsButton, {}, toolbox.win);
-  await onMaximized;
-
-  info("Switch to a different host");
-  await toolbox.switchHost(Toolbox.HostType.SIDE);
-  button = toolbox.doc.querySelector("#toolbox-dock-bottom-minimize");
-  ok(!button, "The minimize button doesn't exist in the side host");
-
-  Services.prefs.clearUserPref("devtools.toolbox.host");
-  await toolbox.destroy();
-  gBrowser.removeCurrentTab();
-});
-
-async function minimize(toolbox) {
-  let button = toolbox.doc.querySelector("#toolbox-dock-bottom-minimize");
-  let onMinimized = toolbox._host.once("minimized");
-  EventUtils.synthesizeMouseAtCenter(button, {}, toolbox.win);
-  await onMinimized;
-}
-
-async function minimizeWithShortcut(toolbox) {
-  let key = toolbox.doc.getElementById("toolbox-minimize-key")
-                       .getAttribute("key");
-  let onMinimized = toolbox._host.once("minimized");
-  EventUtils.synthesizeKey(key, {accelKey: true, shiftKey: true},
-                           toolbox.win);
-  await onMinimized;
-}
-
-async function maximize(toolbox) {
-  let button = toolbox.doc.querySelector("#toolbox-dock-bottom-minimize");
-  let onMaximized = toolbox._host.once("maximized");
-  EventUtils.synthesizeMouseAtCenter(button, {}, toolbox.win);
-  await onMaximized;
-}
-
-async function maximizeWithShortcut(toolbox) {
-  let key = toolbox.doc.getElementById("toolbox-minimize-key")
-                       .getAttribute("key");
-  let onMaximized = toolbox._host.once("maximized");
-  EventUtils.synthesizeKey(key, {accelKey: true, shiftKey: true},
-                           toolbox.win);
-  await onMaximized;
-}
--- a/devtools/client/framework/toolbox-host-manager.js
+++ b/devtools/client/framework/toolbox-host-manager.js
@@ -18,51 +18,38 @@ loader.lazyRequireGetter(this, "Hosts", 
  * This component handles iframe creation within Firefox, in which we are loading
  * the toolbox document. Then both the chrome and the toolbox document communicate
  * via "message" events.
  *
  * Messages sent by the toolbox to the chrome:
  * - switch-host:
  *   Order to display the toolbox in another host (side, bottom, window, or the
  *   previously used one)
- * - toggle-minimize-mode:
- *   When using the bottom host, the toolbox can be miximized to only display
- *   the tool titles
- * - maximize-host:
- *   When using the bottom host in minimized mode, revert back to regular mode
- *   in order to see tool titles and the tools
  * - raise-host:
  *   Focus the tools
  * - set-host-title:
  *   When using the window host, update the window title
  *
  * Messages sent by the chrome to the toolbox:
- * - host-minimized:
- *   The bottom host is done minimizing (after animation end)
- * - host-maximized:
- *   The bottom host is done switching back to regular mode (after animation
- *   end)
  * - switched-host:
  *   The `switch-host` command sent by the toolbox is done
  */
 
 const LAST_HOST = "devtools.toolbox.host";
 const PREVIOUS_HOST = "devtools.toolbox.previousHost";
 let ID_COUNTER = 1;
 
 function ToolboxHostManager(target, hostType, hostOptions) {
   this.target = target;
 
   this.frameId = ID_COUNTER++;
 
   if (!hostType) {
     hostType = Services.prefs.getCharPref(LAST_HOST);
   }
-  this.onHostMinimized = this.onHostMinimized.bind(this);
-  this.onHostMaximized = this.onHostMaximized.bind(this);
   this.host = this.createHost(hostType, hostOptions);
   this.hostType = hostType;
 }
 
 ToolboxHostManager.prototype = {
   async create(toolId) {
     await this.host.create();
 
@@ -116,25 +103,19 @@ ToolboxHostManager.prototype = {
     // origin via event.source as it is null. So use a custom id.
     if (event.data.frameId != this.frameId) {
       return;
     }
     switch (event.data.name) {
       case "switch-host":
         this.switchHost(event.data.hostType);
         break;
-      case "maximize-host":
-        this.host.maximize();
-        break;
       case "raise-host":
         this.host.raise();
         break;
-      case "toggle-minimize-mode":
-        this.host.toggleMinimizeMode(event.data.toolbarHeight);
-        break;
       case "set-host-title":
         this.host.setTitle(event.data.title);
         break;
     }
   },
 
   postMessage(data) {
     let window = this.host.frame.contentWindow;
@@ -162,34 +143,19 @@ ToolboxHostManager.prototype = {
    *        The created host object
    */
   createHost(hostType, options) {
     if (!Hosts[hostType]) {
       throw new Error("Unknown hostType: " + hostType);
     }
 
     let newHost = new Hosts[hostType](this.target.tab, options);
-    // Update the label and icon when the state changes.
-    newHost.on("minimized", this.onHostMinimized);
-    newHost.on("maximized", this.onHostMaximized);
     return newHost;
   },
 
-  onHostMinimized() {
-    this.postMessage({
-      name: "host-minimized"
-    });
-  },
-
-  onHostMaximized() {
-    this.postMessage({
-      name: "host-maximized"
-    });
-  },
-
   async switchHost(hostType) {
     if (hostType == "previous") {
       // Switch to the last used host for the toolbox UI.
       // This is determined by the devtools.toolbox.previousHost pref.
       hostType = Services.prefs.getCharPref(PREVIOUS_HOST);
 
       // Handle the case where the previous host happens to match the current
       // host. If so, switch to bottom if it's not already used, and side if not.
@@ -238,14 +204,12 @@ ToolboxHostManager.prototype = {
   destroyHost() {
     // When Firefox toplevel is closed, the frame may already be detached and
     // the top level document gone
     if (this.host.frame.ownerDocument.defaultView) {
       this.host.frame.ownerDocument.defaultView.removeEventListener("message", this);
     }
     this.host.frame.removeEventListener("unload", this, true);
 
-    this.host.off("minimized", this.onHostMinimized);
-    this.host.off("maximized", this.onHostMaximized);
     return this.host.destroy();
   }
 };
 exports.ToolboxHostManager = ToolboxHostManager;
--- a/devtools/client/framework/toolbox-hosts.js
+++ b/devtools/client/framework/toolbox-hosts.js
@@ -97,73 +97,16 @@ BottomHost.prototype = {
   /**
    * Raise the host.
    */
   raise: function() {
     focusTab(this.hostTab);
   },
 
   /**
-   * Minimize this host so that only the toolbox tabbar remains visible.
-   * @param {Number} height The height to minimize to. Defaults to 0, which
-   * means that the toolbox won't be visible at all once minimized.
-   */
-  minimize: function(height = 0) {
-    if (this.isMinimized) {
-      return;
-    }
-    this.isMinimized = true;
-
-    let onTransitionEnd = event => {
-      if (event.propertyName !== "margin-bottom") {
-        // Ignore transitionend on unrelated properties.
-        return;
-      }
-
-      this.frame.removeEventListener("transitionend", onTransitionEnd);
-      this.emit("minimized");
-    };
-    this.frame.addEventListener("transitionend", onTransitionEnd);
-    this.frame.style.marginBottom = -this.frame.height + height + "px";
-    this._splitter.classList.add("disabled");
-  },
-
-  /**
-   * If the host was minimized before, maximize it again (the host will be
-   * maximized to the height it previously had).
-   */
-  maximize: function() {
-    if (!this.isMinimized) {
-      return;
-    }
-    this.isMinimized = false;
-
-    let onTransitionEnd = event => {
-      if (event.propertyName !== "margin-bottom") {
-        // Ignore transitionend on unrelated properties.
-        return;
-      }
-
-      this.frame.removeEventListener("transitionend", onTransitionEnd);
-      this.emit("maximized");
-    };
-    this.frame.addEventListener("transitionend", onTransitionEnd);
-    this.frame.style.marginBottom = "0";
-    this._splitter.classList.remove("disabled");
-  },
-
-  /**
-   * Toggle the minimize mode.
-   * @param {Number} minHeight The height to minimize to.
-   */
-  toggleMinimizeMode: function(minHeight) {
-    this.isMinimized ? this.maximize() : this.minimize(minHeight);
-  },
-
-  /**
    * Set the toolbox title.
    * Nothing to do for this host type.
    */
   setTitle: function() {},
 
   /**
    * Destroy the bottom dock.
    */
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -136,22 +136,17 @@ function Toolbox(target, selectedTool, h
   this._applyCacheSettings = this._applyCacheSettings.bind(this);
   this._applyServiceWorkersTestingSettings =
     this._applyServiceWorkersTestingSettings.bind(this);
   this._saveSplitConsoleHeight = this._saveSplitConsoleHeight.bind(this);
   this._onFocus = this._onFocus.bind(this);
   this._onBrowserMessage = this._onBrowserMessage.bind(this);
   this._showDevEditionPromo = this._showDevEditionPromo.bind(this);
   this._updateTextBoxMenuItems = this._updateTextBoxMenuItems.bind(this);
-  this._onBottomHostMinimized = this._onBottomHostMinimized.bind(this);
-  this._onBottomHostMaximized = this._onBottomHostMaximized.bind(this);
-  this._onToolSelectWhileMinimized = this._onToolSelectWhileMinimized.bind(this);
   this._onPerformanceFrontEvent = this._onPerformanceFrontEvent.bind(this);
-  this._onBottomHostWillChange = this._onBottomHostWillChange.bind(this);
-  this._toggleMinimizeMode = this._toggleMinimizeMode.bind(this);
   this._onToolbarFocus = this._onToolbarFocus.bind(this);
   this._onToolbarArrowKeypress = this._onToolbarArrowKeypress.bind(this);
   this._onPickerClick = this._onPickerClick.bind(this);
   this._onPickerKeypress = this._onPickerKeypress.bind(this);
   this._onPickerStarted = this._onPickerStarted.bind(this);
   this._onPickerStopped = this._onPickerStopped.bind(this);
   this._onInspectObject = this._onInspectObject.bind(this);
   this._onNewSelectedNodeFront = this._onNewSelectedNodeFront.bind(this);
@@ -886,21 +881,16 @@ Toolbox.prototype = {
                    this.selectNextTool();
                    event.preventDefault();
                  });
     this.shortcuts.on(L10N.getStr("toolbox.previousTool.key"),
                  event => {
                    this.selectPreviousTool();
                    event.preventDefault();
                  });
-    this.shortcuts.on(L10N.getStr("toolbox.minimize.key"),
-                 event => {
-                   this._toggleMinimizeMode();
-                   event.preventDefault();
-                 });
     this.shortcuts.on(L10N.getStr("toolbox.toggleHost.key"),
                  event => {
                    this.switchToPreviousHost();
                    event.preventDefault();
                  });
 
     this.doc.addEventListener("keypress", this._splitConsoleOnKeypress);
     this.doc.addEventListener("focus", this._onFocus, true);
@@ -915,33 +905,18 @@ Toolbox.prototype = {
       this.doc.removeEventListener("focus", this._onFocus, true);
       this.win.removeEventListener("unload", this.destroy);
       this.win.removeEventListener("message", this._onBrowserMessage, true);
     }
   },
 
   // Called whenever the chrome send a message
   _onBrowserMessage: function(event) {
-    if (!event.data) {
-      return;
-    }
-    switch (event.data.name) {
-      case "switched-host":
-        this._onSwitchedHost(event.data);
-        break;
-      case "host-minimized":
-        if (this.hostType == Toolbox.HostType.BOTTOM) {
-          this._onBottomHostMinimized();
-        }
-        break;
-      case "host-maximized":
-        if (this.hostType == Toolbox.HostType.BOTTOM) {
-          this._onBottomHostMaximized();
-        }
-        break;
+    if (event.data && event.data.name === "switched-host") {
+      this._onSwitchedHost(event.data);
     }
   },
 
   _registerOverlays: function() {
     registerHarOverlay(this);
   },
 
   _saveSplitConsoleHeight: function() {
@@ -1077,26 +1052,16 @@ Toolbox.prototype = {
    * the host changes.
    */
   _buildDockButtons: function() {
     if (!this._target.isLocalTab) {
       this.component.setDockButtonsEnabled(false);
       return;
     }
 
-    // Bottom-type host can be minimized, add a button for this.
-    if (this.hostType == Toolbox.HostType.BOTTOM) {
-      this.component.setCanMinimize(true);
-
-      // Maximize again when a tool gets selected.
-      this.on("before-select", this._onToolSelectWhileMinimized);
-      // Maximize and stop listening before the host type changes.
-      this.once("host-will-change", this._onBottomHostWillChange);
-    }
-
     this.component.setDockButtonsEnabled(true);
     this.component.setCanCloseToolbox(this.hostType !== Toolbox.HostType.WINDOW);
 
     let sideEnabled = Services.prefs.getBoolPref(this._prefs.SIDE_ENABLED);
 
     let hostTypes = [];
     for (let type in Toolbox.HostType) {
       let position = Toolbox.HostType[type];
@@ -1110,64 +1075,27 @@ Toolbox.prototype = {
         position,
         switchHost: this.switchHost.bind(this, position)
       });
     }
 
     this.component.setHostTypes(hostTypes);
   },
 
-  _onBottomHostMinimized: function() {
-    this.component.setMinimizeState("minimized");
-  },
-
-  _onBottomHostMaximized: function() {
-    this.component.setMinimizeState("maximized");
-  },
-
-  _onToolSelectWhileMinimized: function() {
-    this.postMessage({
-      name: "maximize-host"
-    });
-  },
-
   postMessage: function(msg) {
     // We sometime try to send messages in middle of destroy(), where the
     // toolbox iframe may already be detached and no longer have a parent.
     if (this.win.parent) {
       // Toolbox document is still chrome and disallow identifying message
       // origin via event.source as it is null. So use a custom id.
       msg.frameId = this.frameId;
       this.win.parent.postMessage(msg, "*");
     }
   },
 
-  _onBottomHostWillChange: function() {
-    this.postMessage({
-      name: "maximize-host"
-    });
-
-    this.off("before-select", this._onToolSelectWhileMinimized);
-  },
-
-  _toggleMinimizeMode: function() {
-    if (this.hostType !== Toolbox.HostType.BOTTOM) {
-      return;
-    }
-
-    // Calculate the height to which the host should be minimized so the
-    // tabbar is still visible.
-    let toolbarHeight = this._componentMount.getBoxQuads({box: "content"})[0].bounds
-                                                                             .height;
-    this.postMessage({
-      name: "toggle-minimize-mode",
-      toolbarHeight
-    });
-  },
-
   /**
    * Initiate ToolboxTabs React component and all it's properties. Do the initial render.
    */
   _buildTabs: function() {
     // Get the initial list of tab definitions. This list can be amended at a later time
     // by tools registering themselves.
     const definitions = gDevTools.getToolDefinitionArray();
     definitions.forEach(definition => this._buildPanelForTool(definition));
@@ -1184,17 +1112,16 @@ Toolbox.prototype = {
   _mountReactComponent: function() {
     // Ensure the toolbar doesn't try to render until the tool is ready.
     const element = this.React.createElement(this.ToolboxController, {
       L10N,
       currentToolId: this.currentToolId,
       selectTool: this.selectTool,
       closeToolbox: this.destroy,
       focusButton: this._onToolbarFocus,
-      toggleMinimizeMode: this._toggleMinimizeMode,
       toolbox: this
     });
 
     this.component = this.ReactDOM.render(element, this._componentMount);
   },
 
   /**
    * Reset tabindex attributes across all focusable elements inside the toolbar.
@@ -1866,18 +1793,16 @@ Toolbox.prototype = {
 
   /**
    * Switch to the tool with the given id
    *
    * @param {string} id
    *        The id of the tool to switch to
    */
   selectTool: function(id) {
-    this.emit("before-select", id);
-
     if (this.currentToolId == id) {
       let panel = this._toolPanels.get(id);
       if (panel) {
         // We have a panel instance, so the tool is already fully loaded.
 
         // re-focus tool to get key events again
         this.focusTool(id);
 
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -101,24 +101,27 @@ devtools.jar:
     content/shared/widgets/spectrum.css (shared/widgets/spectrum.css)
     content/aboutdebugging/aboutdebugging.xhtml (aboutdebugging/aboutdebugging.xhtml)
     content/aboutdebugging/aboutdebugging.css (aboutdebugging/aboutdebugging.css)
     content/aboutdebugging/initializer.js (aboutdebugging/initializer.js)
     content/responsive.html/index.xhtml (responsive.html/index.xhtml)
     content/responsive.html/index.js (responsive.html/index.js)
     content/dom/dom.html (dom/dom.html)
     content/dom/main.js (dom/main.js)
+    content/accessibility/accessibility.html (accessibility/accessibility.html)
+    content/accessibility/main.js (accessibility/main.js)
 %   skin devtools classic/1.0 %skin/
     skin/devtools-browser.css (themes/devtools-browser.css)
     skin/dark-theme.css (themes/dark-theme.css)
     skin/light-theme.css (themes/light-theme.css)
     skin/firebug-theme.css (themes/firebug-theme.css)
     skin/toolbars.css (themes/toolbars.css)
     skin/toolbox.css (themes/toolbox.css)
     skin/tooltips.css (themes/tooltips.css)
+    skin/images/accessibility.svg (themes/images/accessibility.svg)
     skin/images/add.svg (themes/images/add.svg)
     skin/images/breadcrumbs-divider.svg (themes/images/breadcrumbs-divider.svg)
     skin/images/filters.svg (themes/images/filters.svg)
     skin/images/filter-swatch.svg (themes/images/filter-swatch.svg)
     skin/images/font-swatch.svg (themes/images/font-swatch.svg)
     skin/images/grid.svg (themes/images/grid.svg)
     skin/images/angle-swatch.svg (themes/images/angle-swatch.svg)
     skin/images/pseudo-class.svg (themes/images/pseudo-class.svg)
@@ -140,16 +143,17 @@ devtools.jar:
     skin/images/alerticon-warning.png (themes/images/alerticon-warning.png)
     skin/images/alerticon-warning@2x.png (themes/images/alerticon-warning@2x.png)
     skin/rules.css (themes/rules.css)
     skin/commandline.css (themes/commandline.css)
     skin/images/command-paintflashing.svg (themes/images/command-paintflashing.svg)
     skin/images/command-screenshot.svg (themes/images/command-screenshot.svg)
     skin/images/command-responsivemode.svg (themes/images/command-responsivemode.svg)
     skin/images/command-pick.svg (themes/images/command-pick.svg)
+    skin/images/command-pick-accessibility.svg (themes/images/command-pick-accessibility.svg)
     skin/images/command-frames.svg (themes/images/command-frames.svg)
     skin/images/command-console.svg (themes/images/command-console.svg)
     skin/images/command-eyedropper.svg (themes/images/command-eyedropper.svg)
     skin/images/command-rulers.svg (themes/images/command-rulers.svg)
     skin/images/command-measure.svg (themes/images/command-measure.svg)
     skin/images/command-noautohide.svg (themes/images/command-noautohide.svg)
     skin/markup.css (themes/markup.css)
     skin/images/editor-error.png (themes/images/editor-error.png)
@@ -219,16 +223,17 @@ devtools.jar:
     skin/images/tool-styleeditor.svg (themes/images/tool-styleeditor.svg)
     skin/images/tool-storage.svg (themes/images/tool-storage.svg)
     skin/images/tool-profiler.svg (themes/images/tool-profiler.svg)
     skin/images/tool-network.svg (themes/images/tool-network.svg)
     skin/images/tool-scratchpad.svg (themes/images/tool-scratchpad.svg)
     skin/images/tool-webaudio.svg (themes/images/tool-webaudio.svg)
     skin/images/tool-memory.svg (themes/images/tool-memory.svg)
     skin/images/tool-dom.svg (themes/images/tool-dom.svg)
+    skin/images/tool-accessibility.svg (themes/images/tool-accessibility.svg)
     skin/images/close.svg (themes/images/close.svg)
     skin/images/clear.svg (themes/images/clear.svg)
     skin/images/vview-delete.png (themes/images/vview-delete.png)
     skin/images/vview-delete@2x.png (themes/images/vview-delete@2x.png)
     skin/images/vview-edit.png (themes/images/vview-edit.png)
     skin/images/vview-edit@2x.png (themes/images/vview-edit@2x.png)
     skin/images/vview-lock.png (themes/images/vview-lock.png)
     skin/images/vview-lock@2x.png (themes/images/vview-lock@2x.png)
new file mode 100644
--- /dev/null
+++ b/devtools/client/locales/en-US/accessibility.properties
@@ -0,0 +1,88 @@
+# 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/.
+
+# LOCALIZATION NOTE These strings are used inside the Accessibility panel
+# which is available from the Web Developer sub-menu -> 'Accessibility'.
+# The correct localization of this file might be to keep it in
+# English, or another language commonly spoken among web developers.
+# You want to make that choice consistent across the developer tools.
+# A good criteria is the language in which you'd find the best
+# documentation on web development on the web.
+
+# LOCALIZATION NOTE (accessibility.role): A title text used for Accessibility
+# tree header column that represents accessible element role.
+accessibility.role=Role
+
+# LOCALIZATION NOTE (accessibility.name): A title text used for Accessibility
+# tree header column that represents accessible element name.
+accessibility.name=Name
+
+# LOCALIZATION NOTE (accessibility.logo): A title text used for Accessibility
+# logo used on the accessibility panel landing page.
+accessibility.logo=Accessibility Logo
+
+# LOCALIZATION NOTE (accessibility.properties): A title text used for header
+# for Accessibility details sidebar.
+accessibility.properties=Properties
+
+# LOCALIZATION NOTE (accessibility.treeName): A title text used for
+# Accessibility tree (that represents accessible element name) container.
+accessibility.treeName=Accessibility Tree
+
+# LOCALIZATION NOTE (accessibility.accessible.notAvailable): A title text
+# displayed when accessible sidebar panel does not have an accessible object to
+# display.
+accessibility.accessible.notAvailable=Accessible Information Unavailable
+
+# LOCALIZATION NOTE (accessibility.enable): A title text for Enable
+# accessibility button used to enable accessibility service.
+accessibility.enable=Turn On Accessibility Features
+
+# LOCALIZATION NOTE (accessibility.enabling): A title text for Enable
+# accessibility button used when accessibility service is being enabled.
+accessibility.enabling=Turning on accessibility features…
+
+# LOCALIZATION NOTE (accessibility.disable): A title text for Disable
+# accessibility button used to disable accessibility service.
+accessibility.disable=Turn Off Accessibility Features
+
+# LOCALIZATION NOTE (accessibility.disabling): A title text for Disable
+# accessibility button used when accessibility service is being
+# disabled.
+accessibility.disabling=Turning off accessibility features…
+
+# LOCALIZATION NOTE (accessibility.pick): A title text for Picker button
+# button used to pick accessible objects from the page.
+accessibility.pick=Pick accessible object from the page
+
+# LOCALIZATION NOTE (accessibility.disable.disabledTitle): A title text used for
+# a tooltip for Disable accessibility button when accessibility service can not
+# be disabled. It is the case when a user is using a 3rd party accessibility
+# tool such as screen reader.
+accessibility.disable.disabledTitle=Accessibility service can not be turned off. It is used outside Developer Tools.
+
+# LOCALIZATION NOTE (accessibility.disable.enabledTitle): A title text used for
+# a tooltip for Disable accessibility button when accessibility service can be
+# disabled.
+accessibility.disable.enabledTitle=Accessibility service will be turned off for all tabs and windows.
+
+# LOCALIZATION NOTE (accessibility.enable.disabledTitle): A title text used for
+# a tooltip for Enabled accessibility button when accessibility service can not
+# be enabled.
+accessibility.enable.disabledTitle=Accessibility service can not be turned on. It is turned off via accessibility services privacy preference.
+
+# LOCALIZATION NOTE (accessibility.enable.enabledTitle): A title text used for
+# a tooltip for Enabled accessibility button when accessibility service can be
+# enabled.
+accessibility.enable.enabledTitle=Accessibility service will be turned on for all tabs and windows.
+
+# LOCALIZATION NOTE (accessibility.description.general): A title text used when
+# accessibility service description is provided before accessibility inspector
+# is enabled.
+accessibility.description.general=Accessibility features are deactivated by default because they negatively impact performance. Consider turning off accessibility features before using other Developer Tools panels.
+
+# 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.
--- a/devtools/client/locales/en-US/startup.properties
+++ b/devtools/client/locales/en-US/startup.properties
@@ -242,16 +242,35 @@ dom.panelLabel=DOM Panel
 dom.accesskey=D
 
 # LOCALIZATION NOTE (dom.tooltip):
 # This string is displayed in the tooltip of the tab when the DOM is
 # displayed inside the developer tools window.
 # Keyboard shortcut for DOM panel will be shown inside the brackets.
 dom.tooltip=DOM (%S)
 
+# LOCALIZATION NOTE (accessibility.label):
+# This string is displayed in the title of the tab when the Accessibility panel
+# is displayed inside the developer tools window and in the Developer Tools Menu.
+accessibility.label=Accessibility
+
+# LOCALIZATION NOTE (accessibility.panelLabel):
+# This is used as the label for the toolbox panel.
+accessibility.panelLabel=Accessibility Panel
+
+# LOCALIZATION NOTE (accessibility.accesskey)
+# Used for the menuitem in the tool menu
+accessibility.accesskey=y
+
+# LOCALIZATION NOTE (accessibility.tooltip):
+# This string is displayed in the tooltip of the tab when the Accessibility is
+# displayed inside the developer tools window.
+# Keyboard shortcut for Accessibility panel will be shown inside the brackets.
+accessibility.tooltip=Accessibility (%S)
+
 # LOCALIZATION NOTE (toolbox.buttons.splitconsole):
 # This is the tooltip of the button in the toolbox toolbar used to toggle
 # the split console.
 # Keyboard shortcut will be shown inside brackets.
 toolbox.buttons.splitconsole = Toggle split console (%S)
 
 # LOCALIZATION NOTE (toolbox.buttons.responsive):
 # This is the tooltip of the button in the toolbox toolbar that toggles
--- a/devtools/client/locales/en-US/toolbox.properties
+++ b/devtools/client/locales/en-US/toolbox.properties
@@ -1,30 +1,16 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 toolboxDockButtons.bottom.tooltip=Dock to bottom of browser window
 toolboxDockButtons.side.tooltip=Dock to side of browser window
 toolboxDockButtons.window.tooltip=Show in separate window
 
-# LOCALIZATION NOTE (toolboxDockButtons.bottom.minimize): This string is shown
-# as a tooltip that appears in the toolbox when it is in "bottom host" mode and
-# when hovering over the minimize button in the toolbar. When clicked, the
-# button minimizes the toolbox so that just the toolbar is visible at the
-# bottom.
-toolboxDockButtons.bottom.minimize=Minimize the toolbox
-
-# LOCALIZATION NOTE (toolboxDockButtons.bottom.maximize): This string is shown
-# as a tooltip that appears in the toolbox when it is in "bottom host" mode and
-# when hovering over the maximize button in the toolbar. When clicked, the
-# button maximizes the toolbox again (if it had been minimized before) so that
-# the whole toolbox is visible again.
-toolboxDockButtons.bottom.maximize=Maximize the toolbox
-
 # LOCALIZATION NOTE (toolboxToggleButton.errors): Semi-colon list of plural
 # forms.
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # #1 number of errors in the current web page
 toolboxToggleButton.errors=#1 error;#1 errors
 
 # LOCALIZATION NOTE (toolboxToggleButton.warnings): Semi-colon list of plural
 # forms.
@@ -146,20 +132,16 @@ toolbox.zoomReset2.key=
 toolbox.reload.key=CmdOrCtrl+R
 toolbox.reload2.key=F5
 
 # LOCALIZATION NOTE (toolbox.forceReload*.key)
 # Key shortcuts used to force reload of the page by bypassing caches
 toolbox.forceReload.key=CmdOrCtrl+Shift+R
 toolbox.forceReload2.key=CmdOrCtrl+F5
 
-# LOCALIZATION NOTE (toolbox.minimize.key)
-# Key shortcut used to minimize the toolbox
-toolbox.minimize.key=CmdOrCtrl+Shift+U
-
 # LOCALIZATION NOTE (toolbox.toggleHost.key)
 # Key shortcut used to move the toolbox in bottom or side of the browser window
 toolbox.toggleHost.key=CmdOrCtrl+Shift+D
 
 # LOCALIZATION NOTE (toolbox.frames.tooltip): This is the label for
 # the iframes menu list that appears only when the document has some.
 # It allows you to switch the context of the whole toolbox.
 toolbox.frames.tooltip=Select an iframe as the currently targeted document
--- a/devtools/client/moz.build
+++ b/devtools/client/moz.build
@@ -3,16 +3,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/.
 
 include('../templates.mozbuild')
 
 DIRS += [
     'aboutdebugging',
+    'accessibility',
     'animationinspector',
     'canvasdebugger',
     'commandline',
     'debugger',
     'dom',
     'framework',
     'inspector',
     'jsonview',
--- a/devtools/client/preferences/devtools.js
+++ b/devtools/client/preferences/devtools.js
@@ -225,16 +225,19 @@ pref("devtools.canvasdebugger.enabled", 
 pref("devtools.webaudioeditor.enabled", false);
 
 // Enable Scratchpad
 pref("devtools.scratchpad.enabled", false);
 
 // Make sure the DOM panel is hidden by default
 pref("devtools.dom.enabled", false);
 
+// Make sure the Accessibility panel is hidden by default
+pref("devtools.accessibility.enabled", false);
+
 // Web Audio Editor Inspector Width should be a preference
 pref("devtools.webaudioeditor.inspectorWidth", 300);
 
 // Web console filters
 pref("devtools.webconsole.filter.error", true);
 pref("devtools.webconsole.filter.warn", true);
 pref("devtools.webconsole.filter.info", true);
 pref("devtools.webconsole.filter.log", true);
--- a/devtools/client/shared/autocomplete-popup.js
+++ b/devtools/client/shared/autocomplete-popup.js
@@ -249,20 +249,26 @@ AutocompletePopup.prototype = {
   __maxLabelLength: -1,
 
   get _maxLabelLength() {
     if (this.__maxLabelLength !== -1) {
       return this.__maxLabelLength;
     }
 
     let max = 0;
-    for (let {label, count} of this.items) {
+
+    for (let {label, postLabel, count} of this.items) {
       if (count) {
         label += count + "";
       }
+
+      if (postLabel) {
+        label += postLabel;
+      }
+
       max = Math.max(label.length, max);
     }
 
     this.__maxLabelLength = max;
     return this.__maxLabelLength;
   },
 
   /**
@@ -417,39 +423,54 @@ AutocompletePopup.prototype = {
    *        The item object can have the following properties:
    *        - label {String} Property which is used as the displayed value.
    *        - preLabel {String} [Optional] The String that will be displayed
    *                   before the label indicating that this is the already
    *                   present text in the input box, and label is the text
    *                   that will be auto completed. When this property is
    *                   present, |preLabel.length| starting characters will be
    *                   removed from label.
+   *        - postLabel {String} [Optional] The string that will be displayed
+   *                  after the label. Currently used to display the value of
+   *                  a desired variable.
    *        - count {Number} [Optional] The number to represent the count of
    *                autocompleted label.
    */
   appendItem: function(item) {
     let listItem = this._document.createElementNS(HTML_NS, "li");
     // Items must have an id for accessibility.
     listItem.setAttribute("id", "autocomplete-item-" + itemIdCounter++);
     listItem.className = "autocomplete-item";
     listItem.setAttribute("data-index", this.items.length);
+
     if (this.direction) {
       listItem.setAttribute("dir", this.direction);
     }
+
     let label = this._document.createElementNS(HTML_NS, "span");
     label.textContent = item.label;
     label.className = "autocomplete-value";
+
     if (item.preLabel) {
       let preDesc = this._document.createElementNS(HTML_NS, "span");
       preDesc.textContent = item.preLabel;
       preDesc.className = "initial-value";
       listItem.appendChild(preDesc);
       label.textContent = item.label.slice(item.preLabel.length);
     }
+
     listItem.appendChild(label);
+
+    if (item.postLabel) {
+      let postDesc = this._document.createElementNS(HTML_NS, "span");
+      postDesc.textContent = item.postLabel;
+      postDesc.className = "autocomplete-postlabel";
+      listItem.appendChild(postDesc);
+    }
+
     if (item.count && item.count > 1) {
       let countDesc = this._document.createElementNS(HTML_NS, "span");
       countDesc.textContent = item.count;
       countDesc.setAttribute("flex", "1");
       countDesc.className = "autocomplete-count";
       listItem.appendChild(countDesc);
     }
 
--- a/devtools/client/shared/inplace-editor.js
+++ b/devtools/client/shared/inplace-editor.js
@@ -1327,17 +1327,20 @@ InplaceEditor.prototype = {
         // Check if the next character is a valid word character, no suggestion should be
         // provided when preceeding a word.
         if (/[\w-]/.test(nextChar)) {
           // This emit is mainly to make the test flow simpler.
           this.emit("after-suggest", "nothing to autocomplete");
           return;
         }
       }
+
       let list = [];
+      let postLabelValues = [];
+
       if (this.contentType == CONTENT_TYPES.CSS_PROPERTY) {
         list = this._getCSSPropertyList();
       } else if (this.contentType == CONTENT_TYPES.CSS_VALUE) {
         // Get the last query to be completed before the caret.
         let match = /([^\s,.\/]+$)/.exec(query);
         if (match) {
           startCheckQuery = match[0];
         } else {
@@ -1345,16 +1348,17 @@ InplaceEditor.prototype = {
         }
 
         // Check if the query to be completed is a CSS variable.
         let varMatch = /^var\(([^\s]+$)/.exec(startCheckQuery);
 
         if (varMatch && varMatch.length == 2) {
           startCheckQuery = varMatch[1];
           list = this._getCSSVariableNames();
+          postLabelValues = list.map(varName => this._getCSSVariableValue(varName));
         } else {
           list = ["!important",
                   ...this._getCSSValuesForPropertyName(this.property.name)];
         }
 
         if (query == "") {
           // Do not suggest '!important' without any manually typed character.
           list.splice(0, 1);
@@ -1410,17 +1414,18 @@ InplaceEditor.prototype = {
 
       let finalList = [];
       let length = list.length;
       for (let i = 0, count = 0; i < length && count < MAX_POPUP_ENTRIES; i++) {
         if (startCheckQuery != null && list[i].startsWith(startCheckQuery)) {
           count++;
           finalList.push({
             preLabel: startCheckQuery,
-            label: list[i]
+            label: list[i],
+            postLabel: postLabelValues[i] ? postLabelValues[i] : ""
           });
         } else if (count > 0) {
           // Since count was incremented, we had already crossed the entries
           // which would have started with query, assuming that list is sorted.
           break;
         } else if (startCheckQuery != null && list[i][0] > startCheckQuery[0]) {
           // We have crossed all possible matches alphabetically.
           break;
@@ -1513,16 +1518,27 @@ InplaceEditor.prototype = {
   /**
    * Returns the list of all CSS variables to use for the autocompletion.
    *
    * @return {Array} array of CSS variable names (Strings)
    */
   _getCSSVariableNames: function() {
     return Array.from(this.cssVariables.keys()).sort();
   },
+
+  /**
+  * Returns the variable's value for the given CSS variable name.
+  *
+  * @param {String} varName
+  *        The variable name to retrieve the value of
+  * @return {String} the variable value to the given CSS variable name
+  */
+  _getCSSVariableValue: function(varName) {
+    return this.cssVariables.get(varName);
+  },
 };
 
 /**
  * Copy text-related styles from one element to another.
  */
 function copyTextStyles(from, to) {
   let win = from.ownerDocument.defaultView;
   let style = win.getComputedStyle(from);
--- a/devtools/client/shared/test/browser_inplace-editor_autocomplete_css_variable.js
+++ b/devtools/client/shared/test/browser_inplace-editor_autocomplete_css_variable.js
@@ -16,69 +16,72 @@ loadHelperScript("helper_inplace_editor.
 // typing in "var"
 
 // format :
 //  [
 //    what key to press,
 //    expected input box value after keypress,
 //    selected suggestion index (-1 if popup is hidden),
 //    number of suggestions in the popup (0 if popup is hidden),
+//    expected post label corresponding with the input box value,
 //  ]
 const testData = [
-  ["v", "v", -1, 0],
-  ["a", "va", -1, 0],
-  ["r", "var", -1, 0],
-  ["(", "var(", -1, 0],
-  ["-", "var(--abc", 0, 2],
-  ["VK_BACK_SPACE", "var(-", -1, 0],
-  ["-", "var(--abc", 0, 2],
-  ["VK_DOWN", "var(--def", 1, 2],
-  ["VK_DOWN", "var(--abc", 0, 2],
-  ["VK_LEFT", "var(--abc", -1, 0],
+  ["v", "v", -1, 0, null],
+  ["a", "va", -1, 0, null],
+  ["r", "var", -1, 0, null],
+  ["(", "var(", -1, 0, null],
+  ["-", "var(--abc", 0, 4, "blue"],
+  ["VK_BACK_SPACE", "var(-", -1, 0, null],
+  ["-", "var(--abc", 0, 4, "blue"],
+  ["VK_DOWN", "var(--def", 1, 4, "red"],
+  ["VK_DOWN", "var(--ghi", 2, 4, "green"],
+  ["VK_DOWN", "var(--jkl", 3, 4, "yellow"],
+  ["VK_DOWN", "var(--abc", 0, 4, "blue"],
+  ["VK_DOWN", "var(--def", 1, 4, "red"],
+  ["VK_LEFT", "var(--def", -1, 0, null],
+];
+
+const CSS_VARIABLES = [
+  ["--abc", "blue"],
+  ["--def", "red"],
+  ["--ghi", "green"],
+  ["--jkl", "yellow"]
 ];
 
 const mockGetCSSValuesForPropertyName = function(propertyName) {
   return [];
 };
 
-const mockGetCSSVariableNames = function() {
-  return [
-    "--abc",
-    "--def",
-  ];
-};
-
 add_task(async function() {
-  await addTab("data:text/html;charset=utf-8," +
-    "inplace editor CSS variable autocomplete");
+  await addTab("data:text/html;charset=utf-8,inplace editor CSS variable autocomplete");
   let [host, win, doc] = await createHost();
 
   let xulDocument = win.top.document;
   let popup = new AutocompletePopup(xulDocument, { autoSelect: true });
 
   await new Promise(resolve => {
     createInplaceEditorAndClick({
       start: runAutocompletionTest,
       contentType: InplaceEditor.CONTENT_TYPES.CSS_VALUE,
       property: {
         name: "color"
       },
+      cssVariables: new Map(CSS_VARIABLES),
       done: resolve,
       popup: popup
     }, doc);
   });
 
   popup.destroy();
   host.destroy();
   gBrowser.removeCurrentTab();
 });
 
 let runAutocompletionTest = async function(editor) {
   info("Starting to test for css variable completion");
   editor._getCSSValuesForPropertyName = mockGetCSSValuesForPropertyName;
-  editor._getCSSVariableNames = mockGetCSSVariableNames;
 
   for (let data of testData) {
     await testCompletion(data, editor);
   }
 
   EventUtils.synthesizeKey("VK_RETURN", {}, editor.input.defaultView);
 };
--- a/devtools/client/shared/test/helper_inplace_editor.js
+++ b/devtools/client/shared/test/helper_inplace_editor.js
@@ -67,20 +67,21 @@ function createSpan(doc) {
  * Test helper simulating a key event in an InplaceEditor and checking that the
  * autocompletion works as expected.
  *
  * @param {Array} testData
  *        - {String} key, the key to send
  *        - {String} completion, the expected value of the auto-completion
  *        - {Number} index, the index of the selected suggestion in the popup
  *        - {Number} total, the total number of suggestions in the popup
+ *        - {String} postLabel, the expected post label for the selected suggestion
  * @param {InplaceEditor} editor
  *        The InplaceEditor instance being tested
  */
-async function testCompletion([key, completion, index, total], editor) {
+async function testCompletion([key, completion, index, total, postLabel], editor) {
   info("Pressing key " + key);
   info("Expecting " + completion);
 
   let onVisibilityChange = null;
   let open = total > 0;
   if (editor.popup.isOpen != open) {
     onVisibilityChange = editor.popup.once(open ? "popup-opened" : "popup-closed");
   }
@@ -100,16 +101,24 @@ async function testCompletion([key, comp
   await onSuggest;
   await onVisibilityChange;
   await waitForTime(5);
 
   info("Checking the state");
   if (completion !== null) {
     is(editor.input.value, completion, "Correct value is autocompleted");
   }
+
+  if (postLabel) {
+    let selectedItem = editor.popup.getItems()[index];
+    let selectedElement = editor.popup.elements.get(selectedItem);
+    ok(selectedElement.textContent.includes(postLabel),
+      "Selected popup element contains the expected post-label");
+  }
+
   if (total === 0) {
     ok(!(editor.popup && editor.popup.isOpen), "Popup is closed");
   } else {
     ok(editor.popup.isOpen, "Popup is open");
     is(editor.popup.getItems().length, total, "Number of suggestions match");
     is(editor.popup.selectedIndex, index, "Expected item is selected");
   }
 }
--- a/devtools/client/themes/common.css
+++ b/devtools/client/themes/common.css
@@ -96,16 +96,22 @@ html|button, html|select {
   white-space: pre;
   overflow: hidden;
 }
 
 .devtools-autocomplete-listbox .autocomplete-item > .initial-value,
 .devtools-autocomplete-listbox .autocomplete-item > .autocomplete-value {
   margin: 0;
   padding: 0;
+  float: left;
+}
+
+.devtools-autocomplete-listbox .autocomplete-item > .autocomplete-postlabel {
+  font-style: italic;
+  float: right;
 }
 
 .devtools-autocomplete-listbox .autocomplete-item > .autocomplete-count {
   text-align: end;
 }
 
 /* Rest of the dark and light theme */
 
--- a/devtools/client/themes/devtools-browser.css
+++ b/devtools/client/themes/devtools-browser.css
@@ -1,20 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 @import url("resource://devtools/client/themes/splitters.css");
 @import url("resource://devtools/client/themes/commandline-browser.css");
 
-/* Bottom-docked toolbox minimize transition */
-.devtools-toolbox-bottom-iframe {
-  transition: margin-bottom .1s;
-}
-
 .devtools-toolbox-side-iframe {
   min-width: 465px;
 }
 
 /* Eyedropper Widget */
 /* <panel> added to mainPopupSet */
 
 .devtools-eyedropper-panel {
new file mode 100644
--- /dev/null
+++ b/devtools/client/themes/images/accessibility.svg
@@ -0,0 +1,9 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="context-fill">
+<path d="M8,0C3.6,0,0,3.6,0,8s3.6,8,8,8s8-3.6,8-8S12.4,0,8,0z M8,2.7c0.7,0,1.3,0.6,1.3,1.3S8.7,5.3,8,5.3
+	S6.7,4.7,6.7,4S7.3,2.7,8,2.7z M12,6.9H9.7v3v2.7c0,0.4-0.3,0.7-0.7,0.7S8.3,13,8.3,12.6V9.9H7.7v2.7c0,0.4-0.3,0.7-0.7,0.7
+	S6.3,13,6.3,12.6V7.4V7H4C3.6,7,3.3,6.7,3.3,6.3c0-0.4,0.3-0.7,0.7-0.7h2.3h3.3h2.4c0.3,0,0.6,0.2,0.6,0.5C12.7,6.6,12.4,6.9,12,6.9
+	z"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/devtools/client/themes/images/command-pick-accessibility.svg
@@ -0,0 +1,9 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="context-fill #0b0b0b">
+  <path d="M15,7.5V2.8c0-0.6-0.5-1-1-1H2c-0.6,0-1,0.4-1,1v10c0,0.5,0.5,1,1,1h6.8l-0.4-1H2v-10h12v4.3L15,7.5z"/>
+  <circle cx="11.5" cy="7.1" r="1"/>
+  <path d="M14.6,8.4H8.5C8.2,8.4,8,8.6,8,8.9s0.2,0.5,0.5,0.5h1.8v4.2c0,0.3,0.2,0.6,0.5,0.6s0.5-0.3,0.5-0.6v-2.1
+   h0.5v2.1c0,0.3,0.2,0.6,0.5,0.6s0.5-0.3,0.5-0.6V9.3h1.8c0.3,0,0.5-0.2,0.5-0.5C15,8.5,14.8,8.4,14.6,8.4z"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/devtools/client/themes/images/tool-accessibility.svg
@@ -0,0 +1,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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="16px" height="16px" fill="none" stroke="context-fill #0b0b0b" stroke-width="0.8">
+<path d="M8,2.7c0.7,0,1.3,0.6,1.3,1.3S8.7,5.3,8,5.3S6.7,4.7,6.7,4S7.3,2.7,8,2.7z"/>
+<path d="M12,6.9H9.7v3v2.7c0,0.4-0.3,0.7-0.7,0.7S8.3,13,8.3,12.6V9.9H7.7v2.7c0,0.4-0.3,0.7-0.7,0.7S6.3,13,6.3,12.6
+       V7.4V7H4C3.6,7,3.3,6.7,3.3,6.3c0-0.4,0.3-0.7,0.7-0.7h2.3h3.3H12c0.3,0,0.6,0.2,0.6,0.5C12.7,6.6,12.4,6.9,12,6.9z"/>
+</svg>
--- a/devtools/client/themes/toolbox.css
+++ b/devtools/client/themes/toolbox.css
@@ -9,16 +9,17 @@
   --dock-side-image: url(chrome://devtools/skin/images/dock-side.svg);
   --dock-undock-image: url(chrome://devtools/skin/images/dock-undock.svg);
 
   --command-paintflashing-image: url(images/command-paintflashing.svg);
   --command-screenshot-image: url(images/command-screenshot.svg);
   --command-responsive-image: url(images/command-responsivemode.svg);
   --command-scratchpad-image: url(images/tool-scratchpad.svg);
   --command-pick-image: url(images/command-pick.svg);
+  --command-pick-accessibility-image: url(images/command-pick-accessibility.svg);
   --command-frames-image: url(images/command-frames.svg);
   --command-splitconsole-image: url(images/command-console.svg);
   --command-noautohide-image: url(images/command-noautohide.svg);
   --command-rulers-image: url(images/command-rulers.svg);
   --command-measure-image: url(images/command-measure.svg);
 }
 
 .theme-firebug {
@@ -226,24 +227,16 @@
 #toolbox-dock-side::before {
   background-image: var(--dock-side-image);
 }
 
 #toolbox-dock-window::before {
   background-image: var(--dock-undock-image);
 }
 
-#toolbox-dock-bottom-minimize::before {
-  background-image: url("chrome://devtools/skin/images/dock-bottom-minimize@2x.png");
-}
-
-#toolbox-dock-bottom-minimize.minimized::before {
-  background-image: url("chrome://devtools/skin/images/dock-bottom-maximize@2x.png");
-}
-
 /* Command buttons */
 
 .command-button,
 #toolbox-controls > button,
 #toolbox-dock-buttons > button {
   /* !important is needed to override .devtools-button rules in common.css */
   padding: 0 !important;
   margin: 0 !important;
@@ -274,16 +267,20 @@
 #command-button-scratchpad::before {
   background-image: var(--command-scratchpad-image);
 }
 
 #command-button-pick::before {
   background-image: var(--command-pick-image);
 }
 
+#command-button-pick.accessibility::before {
+  background-image: var(--command-pick-accessibility-image);
+}
+
 #command-button-splitconsole::before {
   background-image: var(--command-splitconsole-image);
 }
 
 #command-button-noautohide::before {
   background-image: var(--command-noautohide-image);
 }
 
--- a/devtools/startup/devtools-startup.js
+++ b/devtools/startup/devtools-startup.js
@@ -162,16 +162,22 @@ XPCOMUtils.defineLazyGetter(this, "KeySh
       modifiers: "shift"
     },
     // Key for opening the DOM Panel
     {
       toolId: "dom",
       shortcut: KeyShortcutsBundle.GetStringFromName("dom.commandkey"),
       modifiers
     },
+    // Key for opening the Accessibility Panel
+    {
+      toolId: "accessibility",
+      shortcut: KeyShortcutsBundle.GetStringFromName("accessibility.commandkey"),
+      modifiers
+    },
   ];
 });
 
 function DevToolsStartup() {
   this.onEnabledPrefChanged = this.onEnabledPrefChanged.bind(this);
   this.onWindowReady = this.onWindowReady.bind(this);
 }
 
--- a/devtools/startup/locales/en-US/key-shortcuts.properties
+++ b/devtools/startup/locales/en-US/key-shortcuts.properties
@@ -60,8 +60,12 @@ performance.commandkey=VK_F5
 
 # LOCALIZATION NOTE (storage.commandkey):
 # Key pressed to open a toolbox with the storage panel selected
 storage.commandkey=VK_F9
 
 # LOCALIZATION NOTE (dom.commandkey):
 # Key pressed to open a toolbox with the DOM panel selected
 dom.commandkey=W
+
+# LOCALIZATION NOTE (accessibility.commandkey):
+# Key pressed to open a toolbox with the accessibility panel selected
+accessibility.commandkey=Y
--- a/dom/base/ChromeMessageBroadcaster.cpp
+++ b/dom/base/ChromeMessageBroadcaster.cpp
@@ -2,22 +2,23 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/ChromeMessageBroadcaster.h"
 #include "AccessCheck.h"
 #include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/MessageManagerBinding.h"
 
 namespace mozilla {
 namespace dom {
 
-ChromeMessageBroadcaster::ChromeMessageBroadcaster(nsFrameMessageManager* aParentManager,
+ChromeMessageBroadcaster::ChromeMessageBroadcaster(ChromeMessageBroadcaster* aParentManager,
                                                    MessageManagerFlags aFlags)
   : MessageListenerManager(nullptr, aParentManager,
                            aFlags |
                            MessageManagerFlags::MM_BROADCASTER |
                            MessageManagerFlags::MM_CHROME)
 {
   if (mIsProcessManager) {
     mozilla::HoldJSObjects(this);
@@ -38,10 +39,33 @@ JSObject*
 ChromeMessageBroadcaster::WrapObject(JSContext* aCx,
                                      JS::Handle<JSObject*> aGivenProto)
 {
   MOZ_ASSERT(nsContentUtils::IsSystemCaller(aCx));
 
   return ChromeMessageBroadcasterBinding::Wrap(aCx, this, aGivenProto);
 }
 
+void
+ChromeMessageBroadcaster::ReleaseCachedProcesses()
+{
+  ContentParent::ReleaseCachedProcesses();
+}
+
+void
+ChromeMessageBroadcaster::AddChildManager(MessageListenerManager* aManager)
+{
+  mChildManagers.AppendElement(aManager);
+
+  RefPtr<nsFrameMessageManager> kungfuDeathGrip = this;
+  RefPtr<nsFrameMessageManager> kungfuDeathGrip2 = aManager;
+
+  LoadPendingScripts(this, aManager);
+}
+
+void
+ChromeMessageBroadcaster::RemoveChildManager(MessageListenerManager* aManager)
+{
+  mChildManagers.RemoveElement(aManager);
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/ChromeMessageBroadcaster.h
+++ b/dom/base/ChromeMessageBroadcaster.h
@@ -17,79 +17,91 @@ class ChromeMessageBroadcaster final : p
 public:
   explicit ChromeMessageBroadcaster(MessageManagerFlags aFlags)
     : ChromeMessageBroadcaster(nullptr, aFlags)
   {
     MOZ_ASSERT(!(aFlags & ~(MessageManagerFlags::MM_GLOBAL |
                             MessageManagerFlags::MM_PROCESSMANAGER |
                             MessageManagerFlags::MM_OWNSCALLBACK)));
   }
-  explicit ChromeMessageBroadcaster(nsFrameMessageManager* aParentManager)
+  explicit ChromeMessageBroadcaster(ChromeMessageBroadcaster* aParentManager)
     : ChromeMessageBroadcaster(aParentManager, MessageManagerFlags::MM_NONE)
   {}
 
+  static ChromeMessageBroadcaster* From(nsFrameMessageManager* aManager)
+  {
+    if (aManager->IsBroadcaster() && aManager->IsChrome()) {
+      return static_cast<ChromeMessageBroadcaster*>(aManager);
+    }
+    return nullptr;
+  }
+
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
-  using nsFrameMessageManager::BroadcastAsyncMessage;
   void BroadcastAsyncMessage(JSContext* aCx, const nsAString& aMessageName,
                              JS::Handle<JS::Value> aObj,
                              JS::Handle<JSObject*> aObjects,
                              mozilla::ErrorResult& aError)
   {
     DispatchAsyncMessage(aCx, aMessageName, aObj, aObjects, nullptr,
                          JS::UndefinedHandleValue, aError);
   }
   uint32_t ChildCount()
   {
     return mChildManagers.Length();
   }
-  using nsFrameMessageManager::GetChildAt;
   MessageListenerManager* GetChildAt(uint32_t aIndex)
   {
     return mChildManagers.SafeElementAt(aIndex);
   }
-  // XPCOM ReleaseCachedProcesses is OK
+  void ReleaseCachedProcesses();
 
   // ProcessScriptLoader
-  using nsFrameMessageManager::LoadProcessScript;
   void LoadProcessScript(const nsAString& aUrl, bool aAllowDelayedLoad,
                          mozilla::ErrorResult& aError)
   {
     LoadScript(aUrl, aAllowDelayedLoad, false, aError);
   }
-  // XPCOM RemoveDelayedProcessScript is OK
-  using nsFrameMessageManager::GetDelayedProcessScripts;
+  void RemoveDelayedProcessScript(const nsAString& aURL)
+  {
+    RemoveDelayedScript(aURL);
+  }
   void GetDelayedProcessScripts(JSContext* aCx,
                                 nsTArray<nsTArray<JS::Value>>& aScripts,
                                 mozilla::ErrorResult& aError)
   {
     GetDelayedScripts(aCx, aScripts, aError);
   }
 
   // GlobalProcessScriptLoader
-  // XPCOM GetInitialProcessData is OK
+  using nsFrameMessageManager::GetInitialProcessData;
 
   // FrameScriptLoader
-  using nsFrameMessageManager::LoadFrameScript;
   void LoadFrameScript(const nsAString& aUrl, bool aAllowDelayedLoad,
                        bool aRunInGlobalScope, mozilla::ErrorResult& aError)
   {
     LoadScript(aUrl, aAllowDelayedLoad, aRunInGlobalScope, aError);
   }
-  using nsFrameMessageManager::GetDelayedFrameScripts;
+  void RemoveDelayedFrameScript(const nsAString& aURL)
+  {
+    RemoveDelayedScript(aURL);
+  }
   void GetDelayedFrameScripts(JSContext* aCx,
                               nsTArray<nsTArray<JS::Value>>& aScripts,
                               mozilla::ErrorResult& aError)
   {
     GetDelayedScripts(aCx, aScripts, aError);
   }
 
+  void AddChildManager(MessageListenerManager* aManager);
+  void RemoveChildManager(MessageListenerManager* aManager);
+
 private:
-  ChromeMessageBroadcaster(nsFrameMessageManager* aParentManager,
+  ChromeMessageBroadcaster(ChromeMessageBroadcaster* aParentManager,
                            MessageManagerFlags aFlags);
   virtual ~ChromeMessageBroadcaster();
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_ChromeMessageBroadcaster_h
--- a/dom/base/ChromeMessageSender.cpp
+++ b/dom/base/ChromeMessageSender.cpp
@@ -6,17 +6,17 @@
 
 #include "mozilla/dom/ChromeMessageSender.h"
 #include "mozilla/dom/MessageManagerBinding.h"
 
 namespace mozilla {
 namespace dom {
 
 ChromeMessageSender::ChromeMessageSender(ipc::MessageManagerCallback* aCallback,
-                                         nsFrameMessageManager* aParentManager,
+                                         ChromeMessageBroadcaster* aParentManager,
                                          MessageManagerFlags aFlags)
   : MessageSender(aCallback, aParentManager, aFlags | MessageManagerFlags::MM_CHROME)
 {
   MOZ_ASSERT(!(aFlags & ~(MessageManagerFlags::MM_GLOBAL |
                           MessageManagerFlags::MM_PROCESSMANAGER |
                           MessageManagerFlags::MM_OWNSCALLBACK)));
 
   // This is a bit hackish. We attach to the parent, but only if we have a callback. We
--- a/dom/base/ChromeMessageSender.h
+++ b/dom/base/ChromeMessageSender.h
@@ -7,50 +7,55 @@
 #ifndef mozilla_dom_ChromeMessageSender_h
 #define mozilla_dom_ChromeMessageSender_h
 
 #include "mozilla/dom/MessageSender.h"
 
 namespace mozilla {
 namespace dom {
 
+class ChromeMessageBroadcaster;
+
 class ChromeMessageSender final : public MessageSender
 {
 public:
   ChromeMessageSender(ipc::MessageManagerCallback* aCallback,
-                      nsFrameMessageManager* aParentManager,
+                      ChromeMessageBroadcaster* aParentManager,
                       MessageManagerFlags aFlags=MessageManagerFlags::MM_NONE);
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
   // ProcessScriptLoader
-  using nsFrameMessageManager::LoadProcessScript;
   void LoadProcessScript(const nsAString& aUrl, bool aAllowDelayedLoad,
                          mozilla::ErrorResult& aError)
   {
     LoadScript(aUrl, aAllowDelayedLoad, false, aError);
   }
-  // XPCOM RemoveDelayedProcessScript is OK
-  using nsFrameMessageManager::GetDelayedProcessScripts;
+  void RemoveDelayedProcessScript(const nsAString& aURL)
+  {
+    RemoveDelayedScript(aURL);
+  }
   void GetDelayedProcessScripts(JSContext* aCx,
                                 nsTArray<nsTArray<JS::Value>>& aScripts,
                                 mozilla::ErrorResult& aError)
   {
     GetDelayedScripts(aCx, aScripts, aError);
   }
 
   // FrameScriptLoader
-  using nsFrameMessageManager::LoadFrameScript;
   void LoadFrameScript(const nsAString& aUrl, bool aAllowDelayedLoad,
                        bool aRunInGlobalScope, mozilla::ErrorResult& aError)
   {
     LoadScript(aUrl, aAllowDelayedLoad, aRunInGlobalScope, aError);
   }
-  using nsFrameMessageManager::GetDelayedFrameScripts;
+  void RemoveDelayedFrameScript(const nsAString& aURL)
+  {
+    RemoveDelayedScript(aURL);
+  }
   void GetDelayedFrameScripts(JSContext* aCx,
                               nsTArray<nsTArray<JS::Value>>& aScripts,
                               mozilla::ErrorResult& aError)
   {
     GetDelayedScripts(aCx, aScripts, aError);
   }
 };
 
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -1698,20 +1698,19 @@ Element::BindToTree(nsIDocument* aDocume
   if (IsHTMLElement()) {
     SetDirOnBind(this, aParent);
   }
 
   uint32_t editableDescendantCount = 0;
 
   UpdateEditableState(false);
 
-  // If we had a pre-existing XBL binding,
-  // we might have anonymous children that also need to be told that they are
-  // moving.
-  if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR) && !GetShadowRoot()) {
+  // If we had a pre-existing XBL binding, we might have anonymous children that
+  // also need to be told that they are moving.
+  if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR)) {
     nsXBLBinding* binding =
       OwnerDoc()->BindingManager()->GetBindingWithContent(this);
 
     if (binding) {
       binding->BindAnonymousContent(
         binding->GetAnonymousContent(),
         this,
         binding->PrototypeBinding()->ChromeOnlyContent());
@@ -1763,18 +1762,17 @@ Element::BindToTree(nsIDocument* aDocume
   if (MayHaveStyle() && !IsXULElement()) {
     // XXXbz if we already have a style attr parsed, this won't do
     // anything... need to fix that.
     // If MayHaveStyle() is true, we must be an nsStyledElement
     static_cast<nsStyledElement*>(this)->ReparseStyleAttribute(false, false);
   }
 
   // Call BindToTree on shadow root children.
-  ShadowRoot* shadowRoot = GetShadowRoot();
-  if (shadowRoot) {
+  if (ShadowRoot* shadowRoot = GetShadowRoot()) {
     shadowRoot->SetIsComposedDocParticipant(IsInComposedDoc());
     for (nsIContent* child = shadowRoot->GetFirstChild(); child;
          child = child->GetNextSibling()) {
       rv = child->BindToTree(nullptr, shadowRoot,
                              shadowRoot->GetBindingParent(),
                              aCompileEventHandlers);
       NS_ENSURE_SUCCESS(rv, rv);
     }
@@ -1984,21 +1982,19 @@ Element::UnbindFromTree(bool aDeep, bool
       slots->mBindingParent = nullptr;
     }
     if (aNullParent || !mParent->IsInShadowTree()) {
       slots->mContainingShadow = nullptr;
     }
   }
 
   if (document) {
-    // Notify XBL- & nsIAnonymousContentCreator-generated
-    // anonymous content that the document is changing.
-    // Unlike XBL, bindings for web components shadow DOM
-    // do not get uninstalled.
-    if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR) && !GetShadowRoot()) {
+    if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR)) {
+      // Notify XBL- & nsIAnonymousContentCreator-generated anonymous content
+      // that the document is changing.
       nsContentUtils::AddScriptRunner(
         new RemoveFromBindingManagerRunnable(
           document->BindingManager(), this, document));
       nsXBLBinding* binding =
         document->BindingManager()->GetBindingWithContent(this);
       if (binding) {
         nsXBLBinding::UnbindAnonymousContent(
           document,
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -718,34 +718,47 @@ public:
   // aParsedValue receives the old value of the attribute. That's useful if
   // either the input or output value of aParsedValue is StoresOwnData.
   nsresult SetParsedAttr(int32_t aNameSpaceID, nsAtom* aName, nsAtom* aPrefix,
                          nsAttrValue& aParsedValue, bool aNotify);
   /**
    * Get the current value of the attribute. This returns a form that is
    * suitable for passing back into SetAttr.
    *
-   * @param aNameSpaceID the namespace of the attr
+   * @param aNameSpaceID the namespace of the attr (defaults to
+                         kNameSpaceID_None in the overload that omits this arg)
    * @param aName the name of the attr
    * @param aResult the value (may legitimately be the empty string) [OUT]
    * @returns true if the attribute was set (even when set to empty string)
    *          false when not set.
    * GetAttr is not inlined on purpose, to keep down codesize from all the
    * inlined nsAttrValue bits for C++ callers.
    */
   bool GetAttr(int32_t aNameSpaceID, nsAtom* aName, nsAString& aResult) const;
 
+  bool GetAttr(nsAtom* aName, nsAString& aResult) const
+  {
+    return GetAttr(kNameSpaceID_None, aName, aResult);
+  }
+
   /**
    * Determine if an attribute has been set (empty string or otherwise).
    *
-   * @param aNameSpaceId the namespace id of the attribute
+   * @param aNameSpaceId the namespace id of the attribute (defaults to
+                         kNameSpaceID_None in the overload that omits this arg)
    * @param aAttr the attribute name
    * @return whether an attribute exists
    */
   inline bool HasAttr(int32_t aNameSpaceID, nsAtom* aName) const;
+
+  bool HasAttr(nsAtom* aAttr) const
+  {
+    return HasAttr(kNameSpaceID_None, aAttr);
+  }
+
   /**
    * Test whether this Element's given attribute has the given value.  If the
    * attribute is not set at all, this will return false.
    *
    * @param aNameSpaceID The namespace ID of the attribute.  Must not
    *                     be kNameSpaceID_Unknown.
    * @param aName The name atom of the attribute.  Must not be null.
    * @param aValue The value to compare to.
--- a/dom/base/MessageListenerManager.cpp
+++ b/dom/base/MessageListenerManager.cpp
@@ -5,17 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/MessageListenerManager.h"
 
 namespace mozilla {
 namespace dom {
 
 MessageListenerManager::MessageListenerManager(ipc::MessageManagerCallback* aCallback,
-                                               nsFrameMessageManager* aParentManager,
+                                               ChromeMessageBroadcaster* aParentManager,
                                                ipc::MessageManagerFlags aFlags)
   : nsFrameMessageManager(aCallback, aFlags),
     mParentManager(aParentManager)
 {
 }
 
 MessageListenerManager::~MessageListenerManager()
 {
--- a/dom/base/MessageListenerManager.h
+++ b/dom/base/MessageListenerManager.h
@@ -17,37 +17,37 @@ namespace dom {
 class MessageListenerManager : public nsFrameMessageManager,
                                public nsWrapperCache
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(MessageListenerManager,
                                                          nsFrameMessageManager)
 
-  nsISupports* GetParentObject()
+  ChromeMessageBroadcaster* GetParentObject()
   {
-    return ToSupports(mParentManager.get());
+    return mParentManager;
   }
 
-  virtual nsFrameMessageManager* GetParentManager() override
+  virtual ChromeMessageBroadcaster* GetParentManager() override
   {
     return mParentManager;
   }
 
   /**
    * If aRemove is true then RemoveChildManager(this) will be called on the parent manager
    * first.
    */
   virtual void ClearParentManager(bool aRemove) override;
 
 protected:
   MessageListenerManager(ipc::MessageManagerCallback* aCallback,
-                         nsFrameMessageManager* aParentManager,
+                         ChromeMessageBroadcaster* aParentManager,
                          MessageManagerFlags aFlags);
   virtual ~MessageListenerManager();
 
-  RefPtr<nsFrameMessageManager> mParentManager;
+  RefPtr<ChromeMessageBroadcaster> mParentManager;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_MessageListenerManager_h
new file mode 100644
--- /dev/null
+++ b/dom/base/MessageManagerGlobal.cpp
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/MessageManagerGlobal.h"
+#include "mozilla/IntentionalCrash.h"
+#include "mozilla/dom/DOMPrefs.h"
+#include "nsContentUtils.h"
+
+#ifdef ANDROID
+#include <android/log.h>
+#endif
+#ifdef XP_WIN
+#include <windows.h>
+#endif
+
+namespace mozilla {
+namespace dom {
+
+void
+MessageManagerGlobal::Dump(const nsAString& aStr)
+{
+  if (!DOMPrefs::DumpEnabled()) {
+    return;
+  }
+
+#ifdef ANDROID
+  __android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", NS_ConvertUTF16toUTF8(aStr).get());
+#endif
+#ifdef XP_WIN
+  if (IsDebuggerPresent()) {
+    OutputDebugStringW(PromiseFlatString(aStr).get());
+  }
+#endif
+  fputs(NS_ConvertUTF16toUTF8(aStr).get(), stdout);
+  fflush(stdout);
+}
+
+void
+MessageManagerGlobal::PrivateNoteIntentionalCrash(ErrorResult& aError)
+{
+  if (XRE_IsContentProcess()) {
+    NoteIntentionalCrash("tab");
+    return;
+  }
+  aError.Throw(NS_ERROR_NOT_IMPLEMENTED);
+}
+
+void
+MessageManagerGlobal::Atob(const nsAString& aAsciiString, nsAString& aBase64Data,
+                           ErrorResult& aError)
+{
+  aError = nsContentUtils::Atob(aAsciiString, aBase64Data);
+}
+
+void
+MessageManagerGlobal::Btoa(const nsAString& aBase64Data, nsAString& aAsciiString,
+                           ErrorResult& aError)
+{
+  aError = nsContentUtils::Btoa(aBase64Data, aAsciiString);
+}
+
+} // namespace dom
+} // namespace mozilla
--- a/dom/base/MessageManagerGlobal.h
+++ b/dom/base/MessageManagerGlobal.h
@@ -73,17 +73,17 @@ public:
   {
     if (!mMessageManager) {
       aError.Throw(NS_ERROR_NULL_POINTER);
       return;
     }
     mMessageManager->SendAsyncMessage(aCx, aMessageName, aObj, aObjects,
                                       aPrincipal, aTransfers, aError);
   }
-  already_AddRefed<nsIMessageSender> GetProcessMessageManager(mozilla::ErrorResult& aError)
+  already_AddRefed<ChromeMessageSender> GetProcessMessageManager(mozilla::ErrorResult& aError)
   {
     if (!mMessageManager) {
       aError.Throw(NS_ERROR_NULL_POINTER);
       return nullptr;
     }
     return mMessageManager->GetProcessMessageManager(aError);
   }
 
@@ -122,54 +122,26 @@ public:
       aError.Throw(NS_ERROR_NULL_POINTER);
       return;
     }
     mMessageManager->SendRpcMessage(aCx, aMessageName, aObj, aObjects,
                                     aPrincipal, aResult, aError);
   }
 
   // MessageManagerGlobal
-  void Dump(const nsAString& aStr, ErrorResult& aError)
-  {
-    if (!mMessageManager) {
-      aError.Throw(NS_ERROR_NULL_POINTER);
-      return;
-    }
-    aError = mMessageManager->Dump(aStr);
-  }
-  void PrivateNoteIntentionalCrash(ErrorResult& aError)
-  {
-    if (!mMessageManager) {
-      aError.Throw(NS_ERROR_NULL_POINTER);
-      return;
-    }
-    aError = mMessageManager->PrivateNoteIntentionalCrash();
-  }
-  void Atob(const nsAString& aAsciiString, nsAString& aBase64Data,
-            ErrorResult& aError)
+  void Dump(const nsAString& aStr);
+  void PrivateNoteIntentionalCrash(ErrorResult& aError);
+  void Atob(const nsAString& aAsciiString, nsAString& aBase64Data, ErrorResult& aError);
+  void Btoa(const nsAString& aBase64Data, nsAString& aAsciiString, ErrorResult& aError);
+
+  void MarkForCC()
   {
-    if (!mMessageManager) {
-      aError.Throw(NS_ERROR_NULL_POINTER);
-      return;
+    if (mMessageManager) {
+      mMessageManager->MarkForCC();
     }
-    aError = mMessageManager->Atob(aAsciiString, aBase64Data);
-  }
-  void Btoa(const nsAString& aBase64Data, nsAString& aAsciiString,
-            ErrorResult& aError)
-  {
-    if (!mMessageManager) {
-      aError.Throw(NS_ERROR_NULL_POINTER);
-      return;
-    }
-    aError = mMessageManager->Btoa(aBase64Data, aAsciiString);
-  }
-
-  bool MarkForCC()
-  {
-    return mMessageManager && mMessageManager->MarkForCC();
   }
 
 protected:
   explicit MessageManagerGlobal(nsFrameMessageManager* aMessageManager)
     : mMessageManager(aMessageManager)
   {}
 
   RefPtr<nsFrameMessageManager> mMessageManager;
--- a/dom/base/MessageSender.cpp
+++ b/dom/base/MessageSender.cpp
@@ -20,14 +20,14 @@ MessageSender::InitWithCallback(ipc::Mes
   SetCallback(aCallback);
 
   // First load parent scripts by adding this to parent manager.
   if (mParentManager) {
     mParentManager->AddChildManager(this);
   }
 
   for (uint32_t i = 0; i < mPendingScripts.Length(); ++i) {
-    LoadFrameScript(mPendingScripts[i], false, mPendingScriptsGlobalStates[i]);
+    LoadScript(mPendingScripts[i], false, mPendingScriptsGlobalStates[i], IgnoreErrors());
   }
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/MessageSender.h
+++ b/dom/base/MessageSender.h
@@ -14,17 +14,17 @@ namespace dom {
 
 class MessageSender : public MessageListenerManager
 {
 public:
   void InitWithCallback(ipc::MessageManagerCallback* aCallback);
 
 protected:
   MessageSender(ipc::MessageManagerCallback* aCallback,
-                nsFrameMessageManager* aParentManager,
+                ChromeMessageBroadcaster* aParentManager,
                 MessageManagerFlags aFlags)
     : MessageListenerManager(aCallback, aParentManager, aFlags)
   {}
 };
 
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/base/PostMessageEvent.cpp
+++ b/dom/base/PostMessageEvent.cpp
@@ -69,17 +69,17 @@ PostMessageEvent::Run()
   sourceDocument.swap(mSourceDocument);
 
   // If we bailed before this point we're going to leak mMessage, but
   // that's probably better than crashing.
 
   RefPtr<nsGlobalWindowInner> targetWindow;
   if (mTargetWindow->IsClosedOrClosing() ||
       !(targetWindow = mTargetWindow->GetCurrentInnerWindowInternal()) ||
-      targetWindow->IsClosedOrClosing())
+      targetWindow->IsDying())
     return NS_OK;
 
   JSAutoCompartment ac(cx, targetWindow->GetWrapper());
 
   // Ensure that any origin which might have been provided is the origin of this
   // window's document.  Note that we do this *now* instead of when postMessage
   // is called because the target window might have been navigated to a
   // different location between then and now.  If this check happened when
--- a/dom/base/ProcessGlobal.cpp
+++ b/dom/base/ProcessGlobal.cpp
@@ -54,30 +54,28 @@ ProcessGlobal::GetOwnPropertyNames(JSCon
 {
   JS::Rooted<JSObject*> thisObj(aCx, GetWrapper());
   GetSystemBindingNames(aCx, thisObj, aNames, aEnumerableOnly, aRv);
 }
 
 ProcessGlobal*
 ProcessGlobal::Get()
 {
-  nsCOMPtr<nsISyncMessageSender> service = do_GetService(NS_CHILDPROCESSMESSAGEMANAGER_CONTRACTID);
+  nsCOMPtr<nsIGlobalObject> service = do_GetService(NS_CHILDPROCESSMESSAGEMANAGER_CONTRACTID);
   if (!service) {
     return nullptr;
   }
   return static_cast<ProcessGlobal*>(service.get());
 }
 
-// This method isn't automatically forwarded safely because it's notxpcom, so
-// the IDL binding doesn't know what value to return.
-NS_IMETHODIMP_(bool)
+void
 ProcessGlobal::MarkForCC()
 {
   MarkScopesForCC();
-  return MessageManagerGlobal::MarkForCC();
+  MessageManagerGlobal::MarkForCC();
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(ProcessGlobal)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ProcessGlobal)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMessageManager)
   tmp->TraverseHostObjectURIs(cb);
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
@@ -91,21 +89,18 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Pr
   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mMessageManager)
   tmp->nsMessageManagerScriptExecutor::Unlink();
   tmp->UnlinkHostObjectURIs();
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ProcessGlobal)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentProcessMessageManager)
-  NS_INTERFACE_MAP_ENTRY(nsIMessageListenerManager)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMessageSender)
   NS_INTERFACE_MAP_ENTRY(nsIMessageSender)
-  NS_INTERFACE_MAP_ENTRY(nsISyncMessageSender)
-  NS_INTERFACE_MAP_ENTRY(nsIContentProcessMessageManager)
   NS_INTERFACE_MAP_ENTRY(nsIScriptObjectPrincipal)
   NS_INTERFACE_MAP_ENTRY(nsIGlobalObject)
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(ProcessGlobal)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(ProcessGlobal)
 
--- a/dom/base/ProcessGlobal.h
+++ b/dom/base/ProcessGlobal.h
@@ -21,17 +21,17 @@
 #include "nsServiceManagerUtils.h"
 #include "nsWeakReference.h"
 #include "nsWrapperCache.h"
 
 namespace mozilla {
 namespace dom {
 
 class ProcessGlobal :
-  public nsIContentProcessMessageManager,
+  public nsIMessageSender,
   public nsMessageManagerScriptExecutor,
   public nsIGlobalObject,
   public nsIScriptObjectPrincipal,
   public nsSupportsWeakReference,
   public ipc::MessageManagerCallback,
   public MessageManagerGlobal,
   public nsWrapperCache
 {
@@ -41,64 +41,54 @@ public:
   bool DoResolve(JSContext* aCx, JS::Handle<JSObject*> aObj,
                  JS::Handle<jsid> aId,
                  JS::MutableHandle<JS::PropertyDescriptor> aDesc);
   static bool MayResolve(jsid aId);
   void GetOwnPropertyNames(JSContext* aCx, JS::AutoIdVector& aNames,
                            bool aEnumerableOnly, ErrorResult& aRv);
 
   using ipc::MessageManagerCallback::GetProcessMessageManager;
+  using MessageManagerGlobal::GetProcessMessageManager;
 
   bool Init();
 
   static ProcessGlobal* Get();
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(ProcessGlobal, nsIContentProcessMessageManager)
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(ProcessGlobal, nsIMessageSender)
+
+  void MarkForCC();
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override
   {
     MOZ_CRASH("We should never get here!");
   }
   virtual bool WrapGlobalObject(JSContext* aCx,
                                 JS::CompartmentOptions& aOptions,
                                 JS::MutableHandle<JSObject*> aReflector) override;
 
   using MessageManagerGlobal::AddMessageListener;
   using MessageManagerGlobal::RemoveMessageListener;
   using MessageManagerGlobal::AddWeakMessageListener;
   using MessageManagerGlobal::RemoveWeakMessageListener;
-  using MessageManagerGlobal::SendAsyncMessage;
-  using MessageManagerGlobal::GetProcessMessageManager;
-  using MessageManagerGlobal::GetRemoteType;
-  using MessageManagerGlobal::SendSyncMessage;
-  using MessageManagerGlobal::SendRpcMessage;
-  using MessageManagerGlobal::Dump;
-  using MessageManagerGlobal::PrivateNoteIntentionalCrash;
-  using MessageManagerGlobal::Atob;
-  using MessageManagerGlobal::Btoa;
 
   // ContentProcessMessageManager
   void GetInitialProcessData(JSContext* aCx,
                              JS::MutableHandle<JS::Value> aInitialProcessData,
                              ErrorResult& aError)
   {
     if (!mMessageManager) {
       aError.Throw(NS_ERROR_NULL_POINTER);
       return;
     }
     mMessageManager->GetInitialProcessData(aCx, aInitialProcessData, aError);
   }
 
-  NS_FORWARD_SAFE_NSIMESSAGELISTENERMANAGER(mMessageManager)
   NS_FORWARD_SAFE_NSIMESSAGESENDER(mMessageManager)
-  NS_FORWARD_SAFE_NSISYNCMESSAGESENDER(mMessageManager)
-  NS_FORWARD_SAFE_NSIMESSAGEMANAGERGLOBAL(mMessageManager)
-  NS_FORWARD_SAFE_NSICONTENTPROCESSMESSAGEMANAGER(mMessageManager)
 
   virtual void LoadScript(const nsAString& aURL);
 
   virtual JSObject* GetGlobalJSObject() override
   {
     return GetWrapper();
   }
   virtual nsIPrincipal* GetPrincipal() override { return mPrincipal; }
--- a/dom/base/ShadowRoot.cpp
+++ b/dom/base/ShadowRoot.cpp
@@ -76,22 +76,45 @@ ShadowRoot::ShadowRoot(Element* aElement
 
 ShadowRoot::~ShadowRoot()
 {
   if (auto* host = GetHost()) {
     // mHost may have been unlinked.
     host->RemoveMutationObserver(this);
   }
 
+  if (IsComposedDocParticipant()) {
+    OwnerDoc()->RemoveComposedDocShadowRoot(*this);
+  }
+
+  MOZ_DIAGNOSTIC_ASSERT(!OwnerDoc()->IsComposedDocShadowRoot(*this));
+
   UnsetFlags(NODE_IS_IN_SHADOW_TREE);
 
   // nsINode destructor expects mSubtreeRoot == this.
   SetSubtreeRootPointer(this);
 }
 
+void
+ShadowRoot::SetIsComposedDocParticipant(bool aIsComposedDocParticipant)
+{
+  bool changed = mIsComposedDocParticipant != aIsComposedDocParticipant;
+  mIsComposedDocParticipant = aIsComposedDocParticipant;
+  if (!changed) {
+    return;
+  }
+
+  nsIDocument* doc = OwnerDoc();
+  if (IsComposedDocParticipant()) {
+    doc->AddComposedDocShadowRoot(*this);
+  } else {
+    doc->RemoveComposedDocShadowRoot(*this);
+  }
+}
+
 JSObject*
 ShadowRoot::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return mozilla::dom::ShadowRootBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void
 ShadowRoot::CloneInternalDataFrom(ShadowRoot* aOther)
--- a/dom/base/ShadowRoot.h
+++ b/dom/base/ShadowRoot.h
@@ -181,20 +181,17 @@ public:
   void GetInnerHTML(nsAString& aInnerHTML);
   void SetInnerHTML(const nsAString& aInnerHTML, ErrorResult& aError);
 
   bool IsComposedDocParticipant() const
   {
     return mIsComposedDocParticipant;
   }
 
-  void SetIsComposedDocParticipant(bool aIsComposedDocParticipant)
-  {
-    mIsComposedDocParticipant = aIsComposedDocParticipant;
-  }
+  void SetIsComposedDocParticipant(bool aIsComposedDocParticipant);
 
   nsresult GetEventTargetParent(EventChainPreVisitor& aVisitor) override;
 
 protected:
   // FIXME(emilio): This will need to become more fine-grained.
   void ApplicableRulesChanged();
 
   virtual ~ShadowRoot();
--- a/dom/base/TimeoutManager.cpp
+++ b/dom/base/TimeoutManager.cpp
@@ -413,17 +413,17 @@ TimeoutManager::TimeoutManager(nsGlobalW
 {
   MOZ_LOG(gLog, LogLevel::Debug,
           ("TimeoutManager %p created, tracking bucketing %s\n",
            this, gAnnotateTrackingChannels ? "enabled" : "disabled"));
 }
 
 TimeoutManager::~TimeoutManager()
 {
-  MOZ_DIAGNOSTIC_ASSERT(mWindow.AsInner()->InnerObjectsFreed());
+  MOZ_DIAGNOSTIC_ASSERT(mWindow.IsDying());
   MOZ_DIAGNOSTIC_ASSERT(!mThrottleTimeoutsTimer);
 
   mExecutor->Shutdown();
 
   MOZ_LOG(gLog, LogLevel::Debug,
           ("TimeoutManager %p destroyed\n", this));
 }
 
@@ -1292,17 +1292,17 @@ TimeoutManager::OnDocumentLoaded()
     MaybeStartThrottleTimeout();
   }
 }
 
 void
 TimeoutManager::MaybeStartThrottleTimeout()
 {
   if (gTimeoutThrottlingDelay <= 0 ||
-      mWindow.AsInner()->InnerObjectsFreed() || mWindow.IsSuspended()) {
+      mWindow.IsDying() || mWindow.IsSuspended()) {
     return;
   }
 
   MOZ_DIAGNOSTIC_ASSERT(!mThrottleTimeouts);
 
   MOZ_LOG(gLog, LogLevel::Debug,
           ("TimeoutManager %p delaying tracking timeout throttling by %dms\n",
            this, gTimeoutThrottlingDelay));
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -273,16 +273,17 @@ UNIFIED_SOURCES += [
     'IdleRequest.cpp',
     'IDTracker.cpp',
     'ImageEncoder.cpp',
     'ImageTracker.cpp',
     'IntlUtils.cpp',
     'Link.cpp',
     'Location.cpp',
     'MessageListenerManager.cpp',
+    'MessageManagerGlobal.cpp',
     'MessageSender.cpp',
     'Navigator.cpp',
     'NodeInfo.cpp',
     'NodeIterator.cpp',
     'nsAttrAndChildArray.cpp',
     'nsAttrValue.cpp',
     'nsAttrValueOrString.cpp',
     'nsCCUncollectableMarker.cpp',
--- a/dom/base/nsCCUncollectableMarker.cpp
+++ b/dom/base/nsCCUncollectableMarker.cpp
@@ -81,50 +81,46 @@ nsCCUncollectableMarker::Init()
   NS_ENSURE_SUCCESS(rv, rv);
 
   sInited = true;
 
   return NS_OK;
 }
 
 static void
-MarkChildMessageManagers(nsIMessageBroadcaster* aMM)
+MarkChildMessageManagers(ChromeMessageBroadcaster* aMM)
 {
   aMM->MarkForCC();
 
-  uint32_t tabChildCount = 0;
-  aMM->GetChildCount(&tabChildCount);
+  uint32_t tabChildCount = aMM->ChildCount();
   for (uint32_t j = 0; j < tabChildCount; ++j) {
-    nsCOMPtr<nsIMessageListenerManager> childMM;
-    aMM->GetChildAt(j, getter_AddRefs(childMM));
+    RefPtr<MessageListenerManager> childMM = aMM->GetChildAt(j);
     if (!childMM) {
       continue;
     }
 
-    nsCOMPtr<nsIMessageBroadcaster> strongNonLeafMM = do_QueryInterface(childMM);
-    nsIMessageBroadcaster* nonLeafMM = strongNonLeafMM;
+    RefPtr<ChromeMessageBroadcaster> strongNonLeafMM =
+      ChromeMessageBroadcaster::From(childMM);
+    ChromeMessageBroadcaster* nonLeafMM = strongNonLeafMM;
 
-    nsCOMPtr<nsIMessageSender> strongTabMM = do_QueryInterface(childMM);
-    nsIMessageSender* tabMM = strongTabMM;
+    MessageListenerManager* tabMM = childMM;
 
     strongNonLeafMM = nullptr;
-    strongTabMM = nullptr;
     childMM = nullptr;
 
     if (nonLeafMM) {
       MarkChildMessageManagers(nonLeafMM);
       continue;
     }
 
     tabMM->MarkForCC();
 
     //XXX hack warning, but works, since we know that
     //    callback is frameloader.
-    mozilla::dom::ipc::MessageManagerCallback* cb =
-      static_cast<nsFrameMessageManager*>(tabMM)->GetCallback();
+    mozilla::dom::ipc::MessageManagerCallback* cb = tabMM->GetCallback();
     if (cb) {
       nsFrameLoader* fl = static_cast<nsFrameLoader*>(cb);
       EventTarget* et = fl->GetTabChildGlobalAsEventTarget();
       if (!et) {
         continue;
       }
       static_cast<nsInProcessTabChildGlobal*>(et)->MarkForCC();
       EventListenerManager* elm = et->GetExistingListenerManager();
@@ -145,37 +141,35 @@ MarkMessageManagers()
       pg->MarkForCC();
     }
   }
 
   // The global message manager only exists in the root process.
   if (!XRE_IsParentProcess()) {
     return;
   }
-  nsCOMPtr<nsIMessageBroadcaster> strongGlobalMM =
-    do_GetService("@mozilla.org/globalmessagemanager;1");
+  RefPtr<ChromeMessageBroadcaster> strongGlobalMM =
+    nsFrameMessageManager::GetGlobalMessageManager();
   if (!strongGlobalMM) {
     return;
   }
-  nsIMessageBroadcaster* globalMM = strongGlobalMM;
+  ChromeMessageBroadcaster* globalMM = strongGlobalMM;
   strongGlobalMM = nullptr;
   MarkChildMessageManagers(globalMM);
 
   if (nsFrameMessageManager::sParentProcessManager) {
     nsFrameMessageManager::sParentProcessManager->MarkForCC();
-    uint32_t childCount = 0;
-    nsFrameMessageManager::sParentProcessManager->GetChildCount(&childCount);
+    uint32_t childCount = nsFrameMessageManager::sParentProcessManager->ChildCount();
     for (uint32_t i = 0; i < childCount; ++i) {
-      nsCOMPtr<nsIMessageListenerManager> childMM;
-      nsFrameMessageManager::sParentProcessManager->
-        GetChildAt(i, getter_AddRefs(childMM));
+      RefPtr<MessageListenerManager> childMM =
+        nsFrameMessageManager::sParentProcessManager->GetChildAt(i);
       if (!childMM) {
         continue;
       }
-      nsIMessageListenerManager* child = childMM;
+      MessageListenerManager* child = childMM;
       childMM = nullptr;
       child->MarkForCC();
     }
   }
   if (nsFrameMessageManager::sSameProcessParentManager) {
     nsFrameMessageManager::sSameProcessParentManager->MarkForCC();
   }
 }
@@ -291,17 +285,17 @@ MarkWindowList(nsISimpleEnumerator* aWin
          iter) {
     if (nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(iter)) {
       nsCOMPtr<nsIDocShell> rootDocShell = window->GetDocShell();
 
       MarkDocShell(rootDocShell, aCleanupJS);
 
       RefPtr<TabChild> tabChild = TabChild::GetFrom(rootDocShell);
       if (tabChild) {
-        nsCOMPtr<nsIContentFrameMessageManager> mm = tabChild->GetMessageManager();
+        RefPtr<TabChildGlobal> mm = tabChild->GetMessageManager();
         if (mm) {
           // MarkForCC ends up calling UnmarkGray on message listeners, which
           // TraceBlackJS can't do yet.
           mm->MarkForCC();
         }
       }
     }
   }
@@ -472,19 +466,19 @@ mozilla::dom::TraceBlackJS(JSTracer* aTr
   }
 #endif
 
   if (!nsCCUncollectableMarker::sGeneration) {
     return;
   }
 
   if (nsFrameMessageManager::GetChildProcessManager()) {
-    nsIContentProcessMessageManager* pg = ProcessGlobal::Get();
+    ProcessGlobal* pg = ProcessGlobal::Get();
     if (pg) {
-      mozilla::TraceScriptHolder(pg, aTrc);
+      mozilla::TraceScriptHolder(ToSupports(pg), aTrc);
     }
   }
 
   // Mark globals of active windows black.
   nsGlobalWindowOuter::OuterWindowByIdTable* windowsById =
     nsGlobalWindowOuter::GetWindowsTable();
   if (windowsById) {
     for (auto iter = windowsById->Iter(); !iter.Done(); iter.Next()) {
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -7773,41 +7773,36 @@ nsContentUtils::GetHostOrIPv6WithBracket
   if (NS_FAILED(rv)) {
     return rv;
   }
   CopyUTF8toUTF16(hostname, aHost);
   return NS_OK;
 }
 
 bool
-nsContentUtils::CallOnAllRemoteChildren(nsIMessageBroadcaster* aManager,
+nsContentUtils::CallOnAllRemoteChildren(ChromeMessageBroadcaster* aManager,
                                         CallOnRemoteChildFunction aCallback,
                                         void* aArg)
 {
-  uint32_t tabChildCount = 0;
-  aManager->GetChildCount(&tabChildCount);
+  uint32_t tabChildCount = aManager->ChildCount();
   for (uint32_t j = 0; j < tabChildCount; ++j) {
-    nsCOMPtr<nsIMessageListenerManager> childMM;
-    aManager->GetChildAt(j, getter_AddRefs(childMM));
+    RefPtr<MessageListenerManager> childMM = aManager->GetChildAt(j);
     if (!childMM) {
       continue;
     }
 
-    nsCOMPtr<nsIMessageBroadcaster> nonLeafMM = do_QueryInterface(childMM);
+    RefPtr<ChromeMessageBroadcaster> nonLeafMM = ChromeMessageBroadcaster::From(childMM);
     if (nonLeafMM) {
       if (CallOnAllRemoteChildren(nonLeafMM, aCallback, aArg)) {
         return true;
       }
       continue;
     }
 
-    nsCOMPtr<nsIMessageSender> tabMM = do_QueryInterface(childMM);
-
-    mozilla::dom::ipc::MessageManagerCallback* cb =
-     static_cast<nsFrameMessageManager*>(tabMM.get())->GetCallback();
+    mozilla::dom::ipc::MessageManagerCallback* cb = childMM->GetCallback();
     if (cb) {
       nsFrameLoader* fl = static_cast<nsFrameLoader*>(cb);
       TabParent* remote = TabParent::GetFrom(fl);
       if (remote && aCallback) {
         if (aCallback(remote, aArg)) {
           return true;
         }
       }
@@ -7817,20 +7812,19 @@ nsContentUtils::CallOnAllRemoteChildren(
   return false;
 }
 
 void
 nsContentUtils::CallOnAllRemoteChildren(nsPIDOMWindowOuter* aWindow,
                                         CallOnRemoteChildFunction aCallback,
                                         void* aArg)
 {
-  nsCOMPtr<nsIDOMChromeWindow> chromeWindow(do_QueryInterface(aWindow));
-  if (chromeWindow) {
-    nsCOMPtr<nsIMessageBroadcaster> windowMM;
-    chromeWindow->GetMessageManager(getter_AddRefs(windowMM));
+  nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(aWindow);
+  if (window->IsChromeWindow()) {
+    RefPtr<ChromeMessageBroadcaster> windowMM = window->GetMessageManager();
     if (windowMM) {
       CallOnAllRemoteChildren(windowMM, aCallback, aArg);
     }
   }
 }
 
 struct UIStateChangeInfo {
   UIStateChangeType mShowAccelerators;
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -77,17 +77,16 @@ class nsIDragSession;
 class nsIEventTarget;
 class nsIFragmentContentSink;
 class nsIFrame;
 class nsIImageLoadingContent;
 class nsIInterfaceRequestor;
 class nsIIOService;
 class nsILoadInfo;
 class nsILoadGroup;
-class nsIMessageBroadcaster;
 class nsNameSpaceManager;
 class nsIObserver;
 class nsIParser;
 class nsIPluginTag;
 class nsIPresShell;
 class nsIPrincipal;
 class nsIRequest;
 class nsIRunnable;
@@ -124,16 +123,17 @@ template<class T> class nsReadingIterato
 
 namespace mozilla {
 class Dispatcher;
 class ErrorResult;
 class EventListenerManager;
 class HTMLEditor;
 
 namespace dom {
+class ChromeMessageBroadcaster;
 struct CustomElementDefinition;
 class DocumentFragment;
 class Element;
 class EventTarget;
 class HTMLInputElement;
 class IPCDataTransfer;
 class IPCDataTransferItem;
 struct LifecycleCallbackArgs;
@@ -3338,17 +3338,17 @@ private:
   static mozilla::EventClassID
   GetEventClassIDFromMessage(mozilla::EventMessage aEventMessage);
 
   // Fills in aInfo with the tokens from the supplied autocomplete attribute.
   static AutocompleteAttrState InternalSerializeAutocompleteAttribute(const nsAttrValue* aAttrVal,
                                                                       mozilla::dom::AutocompleteInfo& aInfo,
                                                                       bool aGrantAllValidValue = false);
 
-  static bool CallOnAllRemoteChildren(nsIMessageBroadcaster* aManager,
+  static bool CallOnAllRemoteChildren(mozilla::dom::ChromeMessageBroadcaster* aManager,
                                       CallOnRemoteChildFunction aCallback,
                                       void* aArg);
 
   /**
    * Gets the current cookie lifetime policy and cookie behavior for a given
    * principal by checking with preferences and the permission manager.
    *
    * Used in the implementation of InternalStorageAllowedForPrincipal.
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -8633,16 +8633,18 @@ nsIDocument::MutationEventDispatched(nsI
 void
 nsIDocument::DestroyElementMaps()
 {
 #ifdef DEBUG
   mStyledLinksCleared = true;
 #endif
   mStyledLinks.Clear();
   mIdentifierMap.Clear();
+  mComposedShadowRoots.Clear();
+  mResponsiveContent.Clear();
   IncrementExpandoGeneration(*this);
 }
 
 void
 nsIDocument::RefreshLinkHrefs()
 {
   // Get a list of all links we know about.  We will reset them, which will
   // remove them from the document, so we need a copy of what is in the
--- a/dom/base/nsFocusManager.h
+++ b/dom/base/nsFocusManager.h
@@ -19,17 +19,16 @@
 #define FOCUSMETHOD_MASK 0xF000
 #define FOCUSMETHODANDRING_MASK 0xF0F000
 
 #define FOCUSMANAGER_CONTRACTID "@mozilla.org/focus-manager;1"
 
 class nsIContent;
 class nsIDocShellTreeItem;
 class nsPIDOMWindowOuter;
-class nsIMessageBroadcaster;
 
 namespace mozilla {
 namespace dom {
 class TabParent;
 }
 }
 
 struct nsDelayedBlurOrFocusEvent;
@@ -174,22 +173,16 @@ protected:
 
   /**
    * Ensure that the widget associated with the currently focused window is
    * focused at the widget level.
    */
   void EnsureCurrentWidgetFocused();
 
   /**
-   * Iterate over the children of the message broadcaster and notify them
-   * of the activation change.
-   */
-  void ActivateOrDeactivateChildren(nsIMessageBroadcaster* aManager, bool aActive);
-
-  /**
    * Activate or deactivate the window and send the activate/deactivate events.
    */
   void ActivateOrDeactivate(nsPIDOMWindowOuter* aWindow, bool aActive);
 
   /**
    * Blur whatever is currently focused and focus aNewContent. aFlags is a
    * bitmask of the flags defined in nsIFocusManager. If aFocusChanged is
    * true, then the focus has actually shifted and the caret position will be
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -2878,17 +2878,17 @@ nsFrameLoader::DoSendAsyncMessage(JSCont
     }
     return rv;
   }
 
   // We don't have any targets to send our asynchronous message to.
   return NS_ERROR_UNEXPECTED;
 }
 
-already_AddRefed<nsIMessageSender>
+already_AddRefed<MessageSender>
 nsFrameLoader::GetMessageManager()
 {
   EnsureMessageManager();
   return do_AddRef(mMessageManager);
 }
 
 nsresult
 nsFrameLoader::EnsureMessageManager()
@@ -2904,38 +2904,37 @@ nsFrameLoader::EnsureMessageManager()
       !IsRemoteFrame() &&
       !(mOwnerContent->IsXULElement() &&
         mOwnerContent->AttrValueIs(kNameSpaceID_None,
                                    nsGkAtoms::forcemessagemanager,
                                    nsGkAtoms::_true, eCaseMatters))) {
     return NS_OK;
   }
 
-  nsCOMPtr<nsIDOMChromeWindow> chromeWindow =
-    do_QueryInterface(GetOwnerDoc()->GetWindow());
-  nsCOMPtr<nsIMessageBroadcaster> parentManager;
-
-  if (chromeWindow) {
+  RefPtr<nsGlobalWindowOuter> window =
+    nsGlobalWindowOuter::Cast(GetOwnerDoc()->GetWindow());
+  RefPtr<ChromeMessageBroadcaster> parentManager;
+
+  if (window && window->IsChromeWindow()) {
     nsAutoString messagemanagergroup;
     if (mOwnerContent->IsXULElement() &&
         mOwnerContent->GetAttr(kNameSpaceID_None,
                                nsGkAtoms::messagemanagergroup,
                                messagemanagergroup)) {
-      chromeWindow->GetGroupMessageManager(messagemanagergroup, getter_AddRefs(parentManager));
+      parentManager = window->GetGroupMessageManager(messagemanagergroup);
     }
 
     if (!parentManager) {
-      chromeWindow->GetMessageManager(getter_AddRefs(parentManager));
+      parentManager = window->GetMessageManager();
     }
   } else {
-    parentManager = do_GetService("@mozilla.org/globalmessagemanager;1");
+    parentManager = nsFrameMessageManager::GetGlobalMessageManager();
   }
 
-  mMessageManager = new ChromeMessageSender(nullptr,
-                                            static_cast<nsFrameMessageManager*>(parentManager.get()));
+  mMessageManager = new ChromeMessageSender(nullptr, parentManager);
   if (!IsRemoteFrame()) {
     nsresult rv = MaybeCreateDocShell();
     if (NS_FAILED(rv)) {
       return rv;
     }
     NS_ASSERTION(mDocShell,
                  "MaybeCreateDocShell succeeded, but null mDocShell");
     if (!mDocShell) {
@@ -3201,17 +3200,18 @@ nsFrameLoader::InitializeBrowserAPI()
     nsresult rv = EnsureMessageManager();
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return;
     }
     if (mMessageManager) {
       mMessageManager->LoadFrameScript(
         NS_LITERAL_STRING("chrome://global/content/BrowserElementChild.js"),
         /* allowDelayedLoad = */ true,
-        /* aRunInGlobalScope */ true);
+        /* aRunInGlobalScope */ true,
+        IgnoreErrors());
     }
   }
   nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwnerContent);
   if (browserFrame) {
     browserFrame->InitializeBrowserAPI();
   }
 }
 
@@ -3376,17 +3376,17 @@ nsFrameLoader::PopulateUserContextIdFrom
       aAttr.mUserContextId = userContextIdStr.ToInteger(&rv);
       NS_ENSURE_SUCCESS(rv, rv);
     }
   }
 
   return NS_OK;
 }
 
-nsIMessageSender*
+ChromeMessageSender*
 nsFrameLoader::GetProcessMessageManager() const
 {
   return mRemoteBrowser ? mRemoteBrowser->Manager()->GetMessageManager()
                         : nullptr;
 };
 
 JSObject*
 nsFrameLoader::WrapObject(JSContext* cx, JS::Handle<JSObject*> aGivenProto)
--- a/dom/base/nsFrameLoader.h
+++ b/dom/base/nsFrameLoader.h
@@ -45,16 +45,17 @@ class nsIWebProgressListener;
 
 namespace mozilla {
 
 class OriginAttributes;
 
 namespace dom {
 class ChromeMessageSender;
 class ContentParent;
+class MessageSender;
 class PBrowserParent;
 class Promise;
 class TabParent;
 class MutableTabContext;
 
 namespace ipc {
 class StructuredCloneData;
 } // namespace ipc
@@ -166,17 +167,17 @@ public:
              mozilla::ErrorResult& aRv);
 
   void StartPersistence(uint64_t aOuterWindowID,
                         nsIWebBrowserPersistDocumentReceiver* aRecv,
                         mozilla::ErrorResult& aRv);
 
   // WebIDL getters
 
-  already_AddRefed<nsIMessageSender> GetMessageManager();
+  already_AddRefed<mozilla::dom::MessageSender> GetMessageManager();
 
   already_AddRefed<Element> GetOwnerElement();
 
   uint32_t LazyWidth() const;
 
   uint32_t LazyHeight() const;
 
   uint64_t ChildID() const { return mChildID; }
@@ -321,17 +322,17 @@ public:
    */
   void ApplySandboxFlags(uint32_t sandboxFlags);
 
   void GetURL(nsString& aURL, nsIPrincipal** aTriggeringPrincipal);
 
   // Properly retrieves documentSize of any subdocument type.
   nsresult GetWindowDimensions(nsIntRect& aRect);
 
-  virtual nsIMessageSender* GetProcessMessageManager() const override;
+  virtual mozilla::dom::ChromeMessageSender* GetProcessMessageManager() const override;
 
   // public because a callback needs these.
   RefPtr<mozilla::dom::ChromeMessageSender> mMessageManager;
   RefPtr<nsInProcessTabChildGlobal> mChildMessageManager;
 
   virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override;
 
 private:
--- a/dom/base/nsFrameMessageManager.cpp
+++ b/dom/base/nsFrameMessageManager.cpp
@@ -24,22 +24,21 @@
 #include "nsIInputStream.h"
 #include "nsIXULRuntime.h"
 #include "nsIScriptError.h"
 #include "nsIConsoleService.h"
 #include "nsIMemoryReporter.h"
 #include "nsIProtocolHandler.h"
 #include "nsIScriptSecurityManager.h"
 #include "xpcpublic.h"
+#include "mozilla/ClearOnShutdown.h"
 #include "mozilla/CycleCollectedJSContext.h"
-#include "mozilla/IntentionalCrash.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ScriptPreloader.h"
 #include "mozilla/Telemetry.h"
-#include "mozilla/dom/DOMPrefs.h"
 #include "mozilla/dom/ChildProcessMessageManager.h"
 #include "mozilla/dom/ChromeMessageBroadcaster.h"
 #include "mozilla/dom/ChromeMessageSender.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/MessageManagerBinding.h"
 #include "mozilla/dom/MessagePort.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/PermissionMessageUtils.h"
@@ -53,21 +52,17 @@
 #include "mozilla/jsipc/CrossProcessObjectWrappers.h"
 #include "nsPrintfCString.h"
 #include "nsXULAppAPI.h"
 #include "nsQueryObject.h"
 #include "xpcprivate.h"
 #include <algorithm>
 #include "chrome/common/ipc_channel.h" // for IPC::Channel::kMaximumMessageSize
 
-#ifdef ANDROID
-#include <android/log.h>
-#endif
 #ifdef XP_WIN
-#include <windows.h>
 # if defined(SendMessage)
 #  undef SendMessage
 # endif
 #endif
 
 #ifdef FUZZING
 #include "MessageManagerFuzzer.h"
 #endif
@@ -109,16 +104,26 @@ nsFrameMessageManager::~nsFrameMessageMa
       delete mozilla::dom::SameProcessMessageQueue::Get();
     }
     if (this == sSameProcessParentManager) {
       sSameProcessParentManager = nullptr;
     }
   }
 }
 
+inline void
+ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
+                            nsMessageListenerInfo& aField,
+                            const char* aName,
+                            uint32_t aFlags = 0)
+{
+  ImplCycleCollectionTraverse(aCallback, aField.mStrongListener, aName, aFlags);
+  ImplCycleCollectionTraverse(aCallback, aField.mWeakListener, aName, aFlags);
+}
+
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsFrameMessageManager)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsFrameMessageManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListeners)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildManagers)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsFrameMessageManager)
@@ -133,56 +138,44 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildManagers)
   tmp->mInitialProcessData.setNull();
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFrameMessageManager)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentFrameMessageManager)
 
-  /* nsFrameMessageManager implements nsIMessageSender and nsIMessageBroadcaster,
-   * both of which descend from nsIMessageListenerManager. QI'ing to
-   * nsIMessageListenerManager is therefore ambiguous and needs explicit casts
-   * depending on which child interface applies. */
-  NS_INTERFACE_MAP_ENTRY_AGGREGATED(nsIMessageListenerManager,
-                                    (mIsBroadcaster ?
-                                       static_cast<nsIMessageListenerManager*>(
-                                         static_cast<nsIMessageBroadcaster*>(this)) :
-                                       static_cast<nsIMessageListenerManager*>(
-                                         static_cast<nsIMessageSender*>(this))))
-
-  /* Message managers in child process implement nsIMessageSender and
-     nsISyncMessageSender.  Message managers in the chrome process are
+  /* Message managers in child process implement nsIMessageSender.
+     Message managers in the chrome process are
      either broadcasters (if they have subordinate/child message
      managers) or they're simple message senders. */
-  NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISyncMessageSender, !mChrome)
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIMessageSender, !mChrome || !mIsBroadcaster)
-  NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIMessageBroadcaster, mChrome && mIsBroadcaster)
 
   /* nsIContentFrameMessageManager is accessible only in TabChildGlobal. */
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIContentFrameMessageManager,
                                      !mChrome && !mIsProcessManager)
-
-  /* Frame message managers (non-process message managers) support nsIFrameScriptLoader. */
-  NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIFrameScriptLoader,
-                                     mChrome && !mIsProcessManager)
-
-  /* Process message managers (process message managers) support nsIProcessScriptLoader. */
-  NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIProcessScriptLoader,
-                                     mChrome && mIsProcessManager)
-
-  /* Global process message managers (process message managers) support nsIGlobalProcessScriptLoader. */
-  NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIGlobalProcessScriptLoader,
-                                     mChrome && mIsProcessManager && mIsBroadcaster)
-
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFrameMessageManager)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFrameMessageManager)
 
+nsresult 
+MessageManagerCallback::DoGetRemoteType(nsAString& aRemoteType) const
+{
+  aRemoteType.Truncate();
+  mozilla::dom::ChromeMessageSender* parent = GetProcessMessageManager();
+  if (!parent) {
+    return NS_OK;
+  }
+
+  ErrorResult rv;
+  parent->GetRemoteType(aRemoteType, rv);
+  return rv.StealNSResult();
+}
+
 bool
 MessageManagerCallback::BuildClonedMessageDataForParent(nsIContentParent* aParent,
                                                         StructuredCloneData& aData,
                                                         ClonedMessageData& aClonedData)
 {
   return aData.BuildClonedMessageDataForParent(aParent, aClonedData);
 }
 
@@ -215,191 +208,137 @@ SameProcessCpowHolder::ToObject(JSContex
   if (!mObj) {
     return true;
   }
 
   aObjp.set(mObj);
   return JS_WrapObject(aCx, aObjp);
 }
 
-// nsIMessageListenerManager
-
 void
 nsFrameMessageManager::AddMessageListener(const nsAString& aMessageName,
                                           MessageListener& aListener,
                                           bool aListenWhenClosed,
                                           ErrorResult& aError)
 {
-  AddMessageListener(aMessageName, MessageListenerHolder(&aListener), aListenWhenClosed);
-}
-
-NS_IMETHODIMP
-nsFrameMessageManager::AddMessageListener(const nsAString& aMessage,
-                                          nsIMessageListener* aListener,
-                                          bool aListenWhenClosed)
-{
-  AddMessageListener(aMessage, MessageListenerHolder(aListener), aListenWhenClosed);
-  return NS_OK;
-}
-
-void
-nsFrameMessageManager::AddMessageListener(const nsAString& aMessage,
-                                          MessageListenerHolder&& aListener,
-                                          bool aListenWhenClosed)
-{
-  auto listeners = mListeners.LookupForAdd(aMessage).OrInsert([]() {
+  auto listeners = mListeners.LookupForAdd(aMessageName).OrInsert([]() {
       return new nsAutoTObserverArray<nsMessageListenerInfo, 1>();
     });
   uint32_t len = listeners->Length();
   for (uint32_t i = 0; i < len; ++i) {
-    if (listeners->ElementAt(i).mStrongListener == aListener) {
+    MessageListener* strongListener = listeners->ElementAt(i).mStrongListener;
+    if (strongListener && *strongListener == aListener) {
       return;
     }
   }
 
   nsMessageListenerInfo* entry = listeners->AppendElement();
-  entry->mStrongListener = Move(aListener);
+  entry->mStrongListener = &aListener;
   entry->mListenWhenClosed = aListenWhenClosed;
 }
 
 void
 nsFrameMessageManager::RemoveMessageListener(const nsAString& aMessageName,
                                              MessageListener& aListener,
                                              ErrorResult& aError)
 {
-  RemoveMessageListener(aMessageName, MessageListenerHolder(&aListener));
-}
-
-NS_IMETHODIMP
-nsFrameMessageManager::RemoveMessageListener(const nsAString& aMessage,
-                                             nsIMessageListener* aListener)
-{
-  RemoveMessageListener(aMessage, MessageListenerHolder(aListener));
-  return NS_OK;
-}
-
-void
-nsFrameMessageManager::RemoveMessageListener(const nsAString& aMessage,
-                                             const MessageListenerHolder& aListener)
-{
   nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners =
-    mListeners.Get(aMessage);
+    mListeners.Get(aMessageName);
   if (listeners) {
     uint32_t len = listeners->Length();
     for (uint32_t i = 0; i < len; ++i) {
-      if (listeners->ElementAt(i).mStrongListener == aListener) {
+      MessageListener* strongListener = listeners->ElementAt(i).mStrongListener;
+      if (strongListener && *strongListener == aListener) {
         listeners->RemoveElementAt(i);
         return;
       }
     }
   }
 }
 
-static already_AddRefed<nsIMessageListener>
+static already_AddRefed<nsISupports>
 ToXPCOMMessageListener(MessageListener& aListener)
 {
-  return MessageListenerHolder(&aListener).ToXPCOMCallback();
+  return CallbackObjectHolder<mozilla::dom::MessageListener,
+                              nsISupports>(&aListener).ToXPCOMCallback();
 }
 
 void
 nsFrameMessageManager::AddWeakMessageListener(const nsAString& aMessageName,
                                               MessageListener& aListener,
                                               ErrorResult& aError)
 {
-  nsCOMPtr<nsIMessageListener> listener(ToXPCOMMessageListener(aListener));
-  if (!listener) {
-    aError.Throw(NS_ERROR_FAILURE);
+  nsCOMPtr<nsISupports> listener(ToXPCOMMessageListener(aListener));
+  nsWeakPtr weak = do_GetWeakReference(listener);
+  if (!weak) {
+    aError.Throw(NS_ERROR_NO_INTERFACE);
     return;
   }
 
-  aError = AddWeakMessageListener(aMessageName, listener);
-}
-
-NS_IMETHODIMP
-nsFrameMessageManager::AddWeakMessageListener(const nsAString& aMessage,
-                                              nsIMessageListener* aListener)
-{
-  nsWeakPtr weak = do_GetWeakReference(aListener);
-  NS_ENSURE_TRUE(weak, NS_ERROR_NO_INTERFACE);
-
 #ifdef DEBUG
   // It's technically possible that one object X could give two different
   // nsIWeakReference*'s when you do_GetWeakReference(X).  We really don't want
   // this to happen; it will break e.g. RemoveWeakMessageListener.  So let's
   // check that we're not getting ourselves into that situation.
-  nsCOMPtr<nsISupports> canonical = do_QueryInterface(aListener);
+  nsCOMPtr<nsISupports> canonical = do_QueryInterface(listener);
   for (auto iter = mListeners.Iter(); !iter.Done(); iter.Next()) {
     nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners = iter.UserData();
     uint32_t count = listeners->Length();
     for (uint32_t i = 0; i < count; i++) {
       nsWeakPtr weakListener = listeners->ElementAt(i).mWeakListener;
       if (weakListener) {
         nsCOMPtr<nsISupports> otherCanonical = do_QueryReferent(weakListener);
         MOZ_ASSERT((canonical == otherCanonical) == (weak == weakListener));
       }
     }
   }
 #endif
 
-  auto listeners = mListeners.LookupForAdd(aMessage).OrInsert([]() {
+  auto listeners = mListeners.LookupForAdd(aMessageName).OrInsert([]() {
       return new nsAutoTObserverArray<nsMessageListenerInfo, 1>();
     });
   uint32_t len = listeners->Length();
   for (uint32_t i = 0; i < len; ++i) {
     if (listeners->ElementAt(i).mWeakListener == weak) {
-      return NS_OK;
+      return;
     }
   }
 
   nsMessageListenerInfo* entry = listeners->AppendElement();
   entry->mWeakListener = weak;
   entry->mListenWhenClosed = false;
-  return NS_OK;
 }
 
 void
 nsFrameMessageManager::RemoveWeakMessageListener(const nsAString& aMessageName,
                                                  MessageListener& aListener,
                                                  ErrorResult& aError)
 {
-  nsCOMPtr<nsIMessageListener> listener(ToXPCOMMessageListener(aListener));
-  if (!listener) {
-    aError.Throw(NS_ERROR_FAILURE);
+  nsCOMPtr<nsISupports> listener(ToXPCOMMessageListener(aListener));
+  nsWeakPtr weak = do_GetWeakReference(listener);
+  if (!weak) {
+    aError.Throw(NS_ERROR_NO_INTERFACE);
     return;
   }
 
-  aError = RemoveWeakMessageListener(aMessageName, listener);
-}
-
-NS_IMETHODIMP
-nsFrameMessageManager::RemoveWeakMessageListener(const nsAString& aMessage,
-                                                 nsIMessageListener* aListener)
-{
-  nsWeakPtr weak = do_GetWeakReference(aListener);
-  NS_ENSURE_TRUE(weak, NS_OK);
-
   nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners =
-    mListeners.Get(aMessage);
+    mListeners.Get(aMessageName);
   if (!listeners) {
-    return NS_OK;
+    return;
   }
 
   uint32_t len = listeners->Length();
   for (uint32_t i = 0; i < len; ++i) {
     if (listeners->ElementAt(i).mWeakListener == weak) {
       listeners->RemoveElementAt(i);
-      return NS_OK;
+      return;
     }
   }
-
-  return NS_OK;
 }
 
-// nsIFrameScriptLoader
-
 void
 nsFrameMessageManager::LoadScript(const nsAString& aURL,
                                   bool aAllowDelayedLoad,
                                   bool aRunInGlobalScope,
                                   ErrorResult& aError)
 {
   if (aAllowDelayedLoad) {
     // Cache for future windows or frames
@@ -484,65 +423,16 @@ nsFrameMessageManager::GetDelayedScripts
 
   if (!ToJSValue(aCx, list, aList)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   return NS_OK;
 }
 
-// nsIFrameScriptLoader
-
-NS_IMETHODIMP
-nsFrameMessageManager::LoadFrameScript(const nsAString& aURL,
-                                       bool aAllowDelayedLoad,
-                                       bool aRunInGlobalScope)
-{
-  ErrorResult rv;
-  LoadScript(aURL, aAllowDelayedLoad, aRunInGlobalScope, rv);
-  return rv.StealNSResult();
-}
-
-NS_IMETHODIMP
-nsFrameMessageManager::RemoveDelayedFrameScript(const nsAString& aURL)
-{
-  RemoveDelayedScript(aURL);
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsFrameMessageManager::GetDelayedFrameScripts(JSContext* aCx, JS::MutableHandle<JS::Value> aList)
-{
-  return GetDelayedScripts(aCx, aList);
-}
-
-// nsIProcessScriptLoader
-
-NS_IMETHODIMP
-nsFrameMessageManager::LoadProcessScript(const nsAString& aURL,
-                                         bool aAllowDelayedLoad)
-{
-  ErrorResult rv;
-  LoadScript(aURL, aAllowDelayedLoad, false, rv);
-  return rv.StealNSResult();
-}
-
-NS_IMETHODIMP
-nsFrameMessageManager::RemoveDelayedProcessScript(const nsAString& aURL)
-{
-  RemoveDelayedScript(aURL);
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsFrameMessageManager::GetDelayedProcessScripts(JSContext* aCx, JS::MutableHandle<JS::Value> aList)
-{
-  return GetDelayedScripts(aCx, aList);
-}
-
 static bool
 JSONCreator(const char16_t* aBuf, uint32_t aLen, void* aData)
 {
   nsAString* result = static_cast<nsAString*>(aData);
   result->Append(static_cast<const char16_t*>(aBuf),
                  static_cast<uint32_t>(aLen));
   return true;
 }
@@ -595,46 +485,19 @@ GetParamsForMessage(JSContext* aCx,
   if (NS_WARN_IF(rv.Failed())) {
     rv.SuppressException();
     return false;
   }
 
   return true;
 }
 
-// nsISyncMessageSender
 
 static bool sSendingSyncMessage = false;
 
-NS_IMETHODIMP
-nsFrameMessageManager::SendSyncMessage(const nsAString& aMessageName,
-                                       JS::Handle<JS::Value> aJSON,
-                                       JS::Handle<JS::Value> aObjects,
-                                       nsIPrincipal* aPrincipal,
-                                       JSContext* aCx,
-                                       uint8_t aArgc,
-                                       JS::MutableHandle<JS::Value> aRetval)
-{
-  return SendMessage(aMessageName, aJSON, aObjects, aPrincipal, aCx, aArgc,
-                     aRetval, true);
-}
-
-NS_IMETHODIMP
-nsFrameMessageManager::SendRpcMessage(const nsAString& aMessageName,
-                                      JS::Handle<JS::Value> aJSON,
-                                      JS::Handle<JS::Value> aObjects,
-                                      nsIPrincipal* aPrincipal,
-                                      JSContext* aCx,
-                                      uint8_t aArgc,
-                                      JS::MutableHandle<JS::Value> aRetval)
-{
-  return SendMessage(aMessageName, aJSON, aObjects, aPrincipal, aCx, aArgc,
-                     aRetval, false);
-}
-
 static bool
 AllowMessage(size_t aDataLength, const nsAString& aMessageName)
 {
   // A message includes more than structured clone data, so subtract
   // 20KB to make it more likely that a message within this bound won't
   // result in an overly large IPC message.
   static const size_t kMaxMessageSize = IPC::Channel::kMaximumMessageSize - 20 * 1024;
   if (aDataLength < kMaxMessageSize) {
@@ -645,63 +508,16 @@ AllowMessage(size_t aDataLength, const n
   messageName.StripTaggedASCII(ASCIIMask::Mask0to9());
 
   Telemetry::Accumulate(Telemetry::REJECTED_MESSAGE_MANAGER_MESSAGE,
                         messageName);
 
   return false;
 }
 
-nsresult
-nsFrameMessageManager::SendMessage(const nsAString& aMessageName,
-                                   JS::Handle<JS::Value> aJSON,
-                                   JS::Handle<JS::Value> aObjects,
-                                   nsIPrincipal* aPrincipal,
-                                   JSContext* aCx,
-                                   uint8_t aArgc,
-                                   JS::MutableHandle<JS::Value> aRetval,
-                                   bool aIsSync)
-{
-  AUTO_PROFILER_LABEL_DYNAMIC_LOSSY_NSSTRING(
-    "nsFrameMessageManager::SendMessage", EVENTS, aMessageName);
-
-  aRetval.setUndefined();
-
-  if (sSendingSyncMessage && aIsSync) {
-    // No kind of blocking send should be issued on top of a sync message.
-    return NS_ERROR_UNEXPECTED;
-  }
-
-  StructuredCloneData data;
-  if (aArgc >= 2 && !GetParamsForMessage(aCx, aJSON, JS::UndefinedHandleValue, data)) {
-    return NS_ERROR_DOM_DATA_CLONE_ERR;
-  }
-
-  JS::Rooted<JSObject*> objects(aCx);
-  if (aArgc >= 3 && aObjects.isObject()) {
-    objects = &aObjects.toObject();
-  }
-
-  nsTArray<JS::Value> result;
-  SequenceRooter<JS::Value> resultRooter(aCx, &result);
-  ErrorResult rv;
-  SendMessage(aCx, aMessageName, data, objects, aPrincipal, aIsSync, result, rv);
-  rv.WouldReportJSException();
-  if (rv.Failed()) {
-    return rv.StealNSResult();
-  }
-
-  JS::Rooted<JSObject*> dataArray(aCx);
-  if (!ToJSValue(aCx, result, aRetval)) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return NS_OK;
-}
-
 void
 nsFrameMessageManager::SendMessage(JSContext* aCx,
                                    const nsAString& aMessageName,
                                    JS::Handle<JS::Value> aObj,
                                    JS::Handle<JSObject*> aObjects,
                                    nsIPrincipal* aPrincipal,
                                    bool aIsSync,
                                    nsTArray<JS::Value>& aResult,
@@ -874,139 +690,16 @@ nsFrameMessageManager::DispatchAsyncMess
     aError.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
     return;
   }
 
   aError = DispatchAsyncMessageInternal(aCx, aMessageName, data, aObjects,
                                         aPrincipal);
 }
 
-// nsIMessageSender
-
-NS_IMETHODIMP
-nsFrameMessageManager::SendAsyncMessage(const nsAString& aMessageName,
-                                        JS::Handle<JS::Value> aJSON,
-                                        JS::Handle<JS::Value> aObjects,
-                                        nsIPrincipal* aPrincipal,
-                                        JS::Handle<JS::Value> aTransfers,
-                                        JSContext* aCx,
-                                        uint8_t aArgc)
-{
-  return DispatchAsyncMessage(aMessageName, aJSON, aObjects, aPrincipal,
-                              aTransfers, aCx, aArgc);
-}
-
-
-// nsIMessageBroadcaster
-
-NS_IMETHODIMP
-nsFrameMessageManager::BroadcastAsyncMessage(const nsAString& aMessageName,
-                                             JS::Handle<JS::Value> aJSON,
-                                             JS::Handle<JS::Value> aObjects,
-                                             JSContext* aCx,
-                                             uint8_t aArgc)
-{
-  return DispatchAsyncMessage(aMessageName, aJSON, aObjects, nullptr,
-                              JS::UndefinedHandleValue, aCx, aArgc);
-}
-
-NS_IMETHODIMP
-nsFrameMessageManager::GetChildCount(uint32_t* aChildCount)
-{
-  *aChildCount = mChildManagers.Length();
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsFrameMessageManager::GetChildAt(uint32_t aIndex,
-                                  nsIMessageListenerManager** aMM)
-{
-  MessageListenerManager* mm = mChildManagers.SafeElementAt(aIndex);
-  if (mm) {
-    CallQueryInterface(mm, aMM);
-  } else {
-    *aMM = nullptr;
-  }
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsFrameMessageManager::ReleaseCachedProcesses()
-{
-  ContentParent::ReleaseCachedProcesses();
-  return NS_OK;
-}
-
-// nsIContentFrameMessageManager
-
-NS_IMETHODIMP
-nsFrameMessageManager::Dump(const nsAString& aStr)
-{
-  if (!DOMPrefs::DumpEnabled()) {
-    return NS_OK;
-  }
-
-#ifdef ANDROID
-  __android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", NS_ConvertUTF16toUTF8(aStr).get());
-#endif
-#ifdef XP_WIN
-  if (IsDebuggerPresent()) {
-    OutputDebugStringW(PromiseFlatString(aStr).get());
-  }
-#endif
-  fputs(NS_ConvertUTF16toUTF8(aStr).get(), stdout);
-  fflush(stdout);
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsFrameMessageManager::PrivateNoteIntentionalCrash()
-{
-  if (XRE_IsContentProcess()) {
-    mozilla::NoteIntentionalCrash("tab");
-    return NS_OK;
-  }
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-NS_IMETHODIMP
-nsFrameMessageManager::GetContent(mozIDOMWindowProxy** aContent)
-{
-  *aContent = nullptr;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsFrameMessageManager::GetDocShell(nsIDocShell** aDocShell)
-{
-  *aDocShell = nullptr;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsFrameMessageManager::GetTabEventTarget(nsIEventTarget** aTarget)
-{
-  *aTarget = nullptr;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsFrameMessageManager::Btoa(const nsAString& aBinaryData,
-                            nsAString& aAsciiBase64String)
-{
-  return nsContentUtils::Btoa(aBinaryData, aAsciiBase64String);
-}
-
-NS_IMETHODIMP
-nsFrameMessageManager::Atob(const nsAString& aAsciiString,
-                            nsAString& aBinaryData)
-{
-  return nsContentUtils::Atob(aAsciiString, aBinaryData);
-}
-
 class MMListenerRemover
 {
 public:
   explicit MMListenerRemover(nsFrameMessageManager* aMM)
     : mWasHandlingMessage(aMM->mHandlingMessage)
     , mMM(aMM)
   {
     mMM->mHandlingMessage = true;
@@ -1021,42 +714,27 @@ public:
     }
   }
 
   bool mWasHandlingMessage;
   RefPtr<nsFrameMessageManager> mMM;
 };
 
 
-// nsIMessageListener
-
-nsresult
-nsFrameMessageManager::ReceiveMessage(nsISupports* aTarget,
-                                      nsFrameLoader* aTargetFrameLoader,
-                                      const nsAString& aMessage,
-                                      bool aIsSync,
-                                      StructuredCloneData* aCloneData,
-                                      mozilla::jsipc::CpowHolder* aCpows,
-                                      nsIPrincipal* aPrincipal,
-                                      nsTArray<StructuredCloneData>* aRetVal)
-{
-  return ReceiveMessage(aTarget, aTargetFrameLoader, mClosed, aMessage, aIsSync,
-                        aCloneData, aCpows, aPrincipal, aRetVal);
-}
-
-nsresult
+void
 nsFrameMessageManager::ReceiveMessage(nsISupports* aTarget,
                                       nsFrameLoader* aTargetFrameLoader,
                                       bool aTargetClosed,
                                       const nsAString& aMessage,
                                       bool aIsSync,
                                       StructuredCloneData* aCloneData,
                                       mozilla::jsipc::CpowHolder* aCpows,
                                       nsIPrincipal* aPrincipal,
-                                      nsTArray<StructuredCloneData>* aRetVal)
+                                      nsTArray<StructuredCloneData>* aRetVal,
+                                      ErrorResult& aError)
 {
   MOZ_ASSERT(aTarget);
 
   nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners =
     mListeners.Get(aMessage);
   if (listeners) {
 
     MMListenerRemover lr(this);
@@ -1078,32 +756,21 @@ nsFrameMessageManager::ReceiveMessage(ns
       if (!listener.mListenWhenClosed && aTargetClosed) {
         continue;
       }
 
       JS::RootingContext* rcx = RootingCx();
       JS::Rooted<JSObject*> object(rcx);
 
       RefPtr<MessageListener> webIDLListener;
-      if (!weakListener && listener.mStrongListener.HasWebIDLCallback()) {
-        webIDLListener = listener.mStrongListener.GetWebIDLCallback();
-      } else {
-        webIDLListener = nullptr;
-      }
-
-      if (webIDLListener) {
+      if (!weakListener) {
+        webIDLListener = listener.mStrongListener;
         object = webIDLListener->CallbackOrNull();
       } else {
-        nsCOMPtr<nsIXPConnectWrappedJS> wrappedJS;
-        if (weakListener) {
-          wrappedJS = do_QueryInterface(weakListener);
-        } else {
-          wrappedJS = do_QueryInterface(listener.mStrongListener.GetXPCOMCallback());
-        }
-
+        nsCOMPtr<nsIXPConnectWrappedJS> wrappedJS = do_QueryInterface(weakListener);
         if (!wrappedJS) {
           continue;
         }
 
         object = wrappedJS->GetJSObject();
       }
 
       if (!object) {
@@ -1111,48 +778,48 @@ nsFrameMessageManager::ReceiveMessage(ns
       }
 
       AutoEntryScript aes(object, "message manager handler");
       JSContext* cx = aes.cx();
 
       RootedDictionary<ReceiveMessageArgument> argument(cx);
 
       JS::Rooted<JSObject*> cpows(cx);
-      if (aCpows) {
-        if (!aCpows->ToObject(cx, &cpows)) {
-          return NS_ERROR_UNEXPECTED;
-        }
+      if (aCpows && !aCpows->ToObject(cx, &cpows)) {
+        aError.Throw(NS_ERROR_UNEXPECTED);
+        return;
       }
 
       if (!cpows) {
         cpows = JS_NewPlainObject(cx);
         if (!cpows) {
-          return NS_ERROR_UNEXPECTED;
+          aError.Throw(NS_ERROR_UNEXPECTED);
+          return;
         }
       }
       argument.mObjects = cpows;
 
       JS::Rooted<JS::Value> json(cx, JS::NullValue());
       if (aCloneData && aCloneData->DataLength()) {
-        ErrorResult rv;
-        aCloneData->Read(cx, &json, rv);
-        if (NS_WARN_IF(rv.Failed())) {
-          rv.SuppressException();
+        aCloneData->Read(cx, &json, aError);
+        if (NS_WARN_IF(aError.Failed())) {
+          aError.SuppressException();
           JS_ClearPendingException(cx);
-          return NS_OK;
+          return;
         }
       }
       argument.mData = json;
       argument.mJson = json;
 
       // Get cloned MessagePort from StructuredCloneData.
       if (aCloneData) {
         Sequence<OwningNonNull<MessagePort>> ports;
         if (!aCloneData->TakeTransferredPortsAsSequence(ports)) {
-          return NS_ERROR_FAILURE;
+          aError.Throw(NS_ERROR_FAILURE);
+          return;
         }
         argument.mPorts.Construct(Move(ports));
       }
 
       argument.mName = aMessage;
       argument.mPrincipal = aPrincipal;
       argument.mSync = aIsSync;
       argument.mTarget = aTarget;
@@ -1167,70 +834,75 @@ nsFrameMessageManager::ReceiveMessage(ns
         // messageManager is wrapped in TabChildGlobal.
         nsCOMPtr<nsISupports> defaultThisValue;
         if (mChrome) {
           defaultThisValue = do_QueryObject(this);
         } else {
           defaultThisValue = aTarget;
         }
         js::AssertSameCompartment(cx, object);
-        nsresult rv = nsContentUtils::WrapNative(cx, defaultThisValue, &thisValue);
-        NS_ENSURE_SUCCESS(rv, rv);
+        aError = nsContentUtils::WrapNative(cx, defaultThisValue, &thisValue);
+        if (aError.Failed()) {
+          return;
+        }
       }
 
       JS::Rooted<JS::Value> rval(cx, JS::UndefinedValue());
       if (webIDLListener) {
-        ErrorResult rv;
-        webIDLListener->ReceiveMessage(thisValue, argument, &rval, rv);
-        if (rv.Failed()) {
+        webIDLListener->ReceiveMessage(thisValue, argument, &rval, aError);
+        if (aError.Failed()) {
           // At this point the call to ReceiveMessage will have reported any exceptions
           // (we kept the default of eReportExceptions). We suppress the failure in the
           // ErrorResult and continue.
-          rv.SuppressException();
+          aError.SuppressException();
           continue;
         }
       } else {
         JS::Rooted<JS::Value> funval(cx);
         if (JS::IsCallable(object)) {
           // If the listener is a JS function:
           funval.setObject(*object);
         } else {
           // If the listener is a JS object which has receiveMessage function:
           if (!JS_GetProperty(cx, object, "receiveMessage", &funval) ||
               !funval.isObject()) {
-            return NS_ERROR_UNEXPECTED;
+            aError.Throw(NS_ERROR_UNEXPECTED);
+            return;
           }
 
           // Check if the object is even callable.
-          NS_ENSURE_STATE(JS::IsCallable(&funval.toObject()));
+          if (!JS::IsCallable(&funval.toObject())) {
+            aError.Throw(NS_ERROR_UNEXPECTED);
+            return;
+          }
           thisValue.setObject(*object);
         }
 
         JS::Rooted<JS::Value> argv(cx);
         if (!ToJSValue(cx, argument, &argv)) {
-          return NS_ERROR_UNEXPECTED;
+          aError.Throw(NS_ERROR_UNEXPECTED);
+          return;
         }
 
         {
           JS::Rooted<JSObject*> thisObject(cx, thisValue.toObjectOrNull());
           js::AssertSameCompartment(cx, thisObject);
           if (!JS_CallFunctionValue(cx, thisObject, funval,
                                     JS::HandleValueArray(argv), &rval)) {
             // Because the AutoEntryScript is inside the loop this continue will make us
             // report any exceptions (after which we'll move on to the next listener).
             continue;
           }
         }
       }
 
       if (aRetVal) {
-        ErrorResult rv;
         StructuredCloneData* data = aRetVal->AppendElement();
-        data->Write(cx, rval, rv);
-        if (NS_WARN_IF(rv.Failed())) {
+        data->Write(cx, rval, aError);
+        if (NS_WARN_IF(aError.Failed())) {
           aRetVal->RemoveLastElement();
           nsString msg = aMessage + NS_LITERAL_STRING(": message reply cannot be cloned. Are you trying to send an XPCOM object?");
 
           nsCOMPtr<nsIConsoleService> console(do_GetService(NS_CONSOLESERVICE_CONTRACTID));
           if (console) {
             nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
             error->Init(msg, EmptyString(), EmptyString(),
                         0, 0, nsIScriptError::warningFlag, "chrome javascript",
@@ -1242,59 +914,40 @@ nsFrameMessageManager::ReceiveMessage(ns
           continue;
         }
       }
     }
   }
 
   RefPtr<nsFrameMessageManager> kungFuDeathGrip = GetParentManager();
   if (kungFuDeathGrip) {
-    return kungFuDeathGrip->ReceiveMessage(aTarget, aTargetFrameLoader,
-                                           aTargetClosed, aMessage,
-                                           aIsSync, aCloneData,
-                                           aCpows, aPrincipal,
-                                           aRetVal);
+    kungFuDeathGrip->ReceiveMessage(aTarget, aTargetFrameLoader, aTargetClosed, aMessage,
+                                    aIsSync, aCloneData, aCpows, aPrincipal, aRetVal,
+                                    aError);
   }
-  return NS_OK;
-}
-
-void
-nsFrameMessageManager::AddChildManager(MessageListenerManager* aManager)
-{
-  mChildManagers.AppendElement(aManager);
-
-  RefPtr<nsFrameMessageManager> kungfuDeathGrip = this;
-  RefPtr<nsFrameMessageManager> kungfuDeathGrip2 = aManager;
-
-  LoadPendingScripts(this, aManager);
-}
-
-void
-nsFrameMessageManager::RemoveChildManager(MessageListenerManager* aManager)
-{
-  mChildManagers.RemoveElement(aManager);
 }
 
 void
 nsFrameMessageManager::LoadPendingScripts(nsFrameMessageManager* aManager,
                                           nsFrameMessageManager* aChildMM)
 {
   // We have parent manager if we're a message broadcaster.
   // In that case we want to load the pending scripts from all parent
   // message managers in the hierarchy. Process the parent first so
   // that pending scripts higher up in the hierarchy are loaded before others.
   nsFrameMessageManager* parentManager = aManager->GetParentManager();
   if (parentManager) {
     LoadPendingScripts(parentManager, aChildMM);
   }
 
   for (uint32_t i = 0; i < aManager->mPendingScripts.Length(); ++i) {
-    aChildMM->LoadFrameScript(aManager->mPendingScripts[i],
-                              false,
-                              aManager->mPendingScriptsGlobalStates[i]);
+    aChildMM->LoadScript(aManager->mPendingScripts[i],
+                         false,
+                         aManager->mPendingScriptsGlobalStates[i],
+                         IgnoreErrors());
   }
 }
 
 void
 nsFrameMessageManager::LoadPendingScripts()
 {
   RefPtr<nsFrameMessageManager> kungfuDeathGrip = this;
   LoadPendingScripts(this, this);
@@ -1381,72 +1034,52 @@ nsFrameMessageManager::GetInitialProcess
     }
 
     mInitialProcessData.setObject(*obj);
     init.setObject(*obj);
   }
 
   if (!mChrome && XRE_IsParentProcess()) {
     // This is the cpmm in the parent process. We should use the same object as the ppmm.
-    nsCOMPtr<nsIGlobalProcessScriptLoader> ppmm =
-      do_GetService("@mozilla.org/parentprocessmessagemanager;1");
-    ppmm->GetInitialProcessData(aCx, &init);
+    // Create it first through do_GetService and use the cached pointer in
+    // sParentProcessManager.
+    nsCOMPtr<nsISupports> ppmm = do_GetService("@mozilla.org/parentprocessmessagemanager;1");
+    sParentProcessManager->GetInitialProcessData(aCx, &init, aError);
+    if (aError.Failed()) {
+      return;
+    }
     mInitialProcessData = init;
   }
 
   if (!JS_WrapValue(aCx, &init)) {
     aError.NoteJSContextException(aCx);
     return;
   }
   aInitialProcessData.set(init);
 }
 
-NS_IMETHODIMP
-nsFrameMessageManager::GetInitialProcessData(JSContext* aCx, JS::MutableHandleValue aResult)
-{
-  ErrorResult rv;
-  GetInitialProcessData(aCx, aResult, rv);
-  return rv.StealNSResult();
-}
-
-already_AddRefed<nsIMessageSender>
+already_AddRefed<ChromeMessageSender>
 nsFrameMessageManager::GetProcessMessageManager(ErrorResult& aError)
 {
-  nsCOMPtr<nsIMessageSender> pmm;
+  RefPtr<ChromeMessageSender> pmm;
   if (mCallback) {
     pmm = mCallback->GetProcessMessageManager();
   }
   return pmm.forget();
 }
 
-NS_IMETHODIMP
-nsFrameMessageManager::GetProcessMessageManager(nsIMessageSender** aPMM)
-{
-  ErrorResult rv;
-  *aPMM = GetProcessMessageManager(rv).take();
-  return rv.StealNSResult();
-}
-
 void
 nsFrameMessageManager::GetRemoteType(nsAString& aRemoteType, ErrorResult& aError) const
 {
   aRemoteType.Truncate();
   if (mCallback) {
     aError = mCallback->DoGetRemoteType(aRemoteType);
   }
 }
 
-NS_IMETHODIMP
-nsFrameMessageManager::GetRemoteType(nsAString& aRemoteType)
-{
-  ErrorResult rv;
-  GetRemoteType(aRemoteType, rv);
-  return rv.StealNSResult();
-}
-
 namespace {
 
 struct MessageManagerReferentCount
 {
   MessageManagerReferentCount() : mStrong(0), mWeakAlive(0), mWeakDead(0) {}
   size_t mStrong;
   size_t mWeakAlive;
   size_t mWeakDead;
@@ -1562,30 +1195,26 @@ ReportReferentCount(const char* aManager
            nsPrintfCString("A message in the %s message manager with a "
                            "suspiciously large number of referents (symptom "
                            "of a leak).", aManagerType));
   }
 
 #undef REPORT
 }
 
+static StaticRefPtr<ChromeMessageBroadcaster> sGlobalMessageManager;
+
 NS_IMETHODIMP
 MessageManagerReporter::CollectReports(nsIHandleReportCallback* aHandleReport,
                                        nsISupports* aData, bool aAnonymize)
 {
-  if (XRE_IsParentProcess()) {
-    nsCOMPtr<nsIMessageBroadcaster> globalmm =
-      do_GetService("@mozilla.org/globalmessagemanager;1");
-    if (globalmm) {
-      RefPtr<nsFrameMessageManager> mm =
-        static_cast<nsFrameMessageManager*>(globalmm.get());
-      MessageManagerReferentCount count;
-      CountReferents(mm, &count);
-      ReportReferentCount("global-manager", count, aHandleReport, aData);
-    }
+  if (XRE_IsParentProcess() && sGlobalMessageManager) {
+    MessageManagerReferentCount count;
+    CountReferents(sGlobalMessageManager, &count);
+    ReportReferentCount("global-manager", count, aHandleReport, aData);
   }
 
   if (nsFrameMessageManager::sParentProcessManager) {
     MessageManagerReferentCount count;
     CountReferents(nsFrameMessageManager::sParentProcessManager, &count);
     ReportReferentCount("parent-process-manager", count, aHandleReport, aData);
   }
 
@@ -1596,25 +1225,35 @@ MessageManagerReporter::CollectReports(n
   }
 
   return NS_OK;
 }
 
 } // namespace dom
 } // namespace mozilla
 
-nsresult
-NS_NewGlobalMessageManager(nsIMessageBroadcaster** aResult)
+already_AddRefed<ChromeMessageBroadcaster>
+nsFrameMessageManager::GetGlobalMessageManager()
 {
-  NS_ENSURE_TRUE(XRE_IsParentProcess(),
-                 NS_ERROR_NOT_AVAILABLE);
-  RefPtr<nsFrameMessageManager> mm =
-    new ChromeMessageBroadcaster(MessageManagerFlags::MM_GLOBAL);
-  RegisterStrongMemoryReporter(new MessageManagerReporter());
-  mm.forget(aResult);
+  RefPtr<ChromeMessageBroadcaster> mm;
+  if (sGlobalMessageManager) {
+    mm = sGlobalMessageManager;
+  } else {
+    sGlobalMessageManager = mm =
+      new ChromeMessageBroadcaster(MessageManagerFlags::MM_GLOBAL);
+    ClearOnShutdown(&sGlobalMessageManager);
+    RegisterStrongMemoryReporter(new MessageManagerReporter());
+  }
+  return mm.forget();
+}
+
+nsresult
+NS_NewGlobalMessageManager(nsISupports** aResult)
+{
+  *aResult = nsFrameMessageManager::GetGlobalMessageManager().take();
   return NS_OK;
 }
 
 nsDataHashtable<nsStringHashKey, nsMessageManagerScriptHolder*>*
   nsMessageManagerScriptExecutor::sCachedScripts = nullptr;
 StaticRefPtr<nsScriptCacheCleaner> nsMessageManagerScriptExecutor::sScriptCacheCleaner;
 
 void
@@ -1854,17 +1493,17 @@ nsMessageManagerScriptExecutor::MarkScop
   for (uint32_t i = 0; i < mAnonymousGlobalScopes.Length(); ++i) {
     mAnonymousGlobalScopes[i].exposeToActiveJS();
   }
 }
 
 NS_IMPL_ISUPPORTS(nsScriptCacheCleaner, nsIObserver)
 
 ChildProcessMessageManager* nsFrameMessageManager::sChildProcessManager = nullptr;
-nsFrameMessageManager* nsFrameMessageManager::sParentProcessManager = nullptr;
+ChromeMessageBroadcaster* nsFrameMessageManager::sParentProcessManager = nullptr;
 nsFrameMessageManager* nsFrameMessageManager::sSameProcessParentManager = nullptr;
 
 class nsAsyncMessageToSameProcessChild : public nsSameProcessAsyncMessageBase,
                                          public Runnable
 {
 public:
   nsAsyncMessageToSameProcessChild(JS::RootingContext* aRootingCx,
                                    JS::Handle<JSObject*> aCpows)
@@ -2040,18 +1679,18 @@ public:
                              bool aIsSync) override
   {
     SameProcessMessageQueue* queue = SameProcessMessageQueue::Get();
     queue->Flush();
 
     if (nsFrameMessageManager::sSameProcessParentManager) {
       SameProcessCpowHolder cpows(JS::RootingContext::get(aCx), aCpows);
       RefPtr<nsFrameMessageManager> ppm = nsFrameMessageManager::sSameProcessParentManager;
-      ppm->ReceiveMessage(static_cast<nsIContentFrameMessageManager*>(ppm.get()), nullptr, aMessage,
-                          true, &aData, &cpows, aPrincipal, aRetVal);
+      ppm->ReceiveMessage(ppm, nullptr, aMessage, true, &aData, &cpows, aPrincipal,
+                          aRetVal, IgnoreErrors());
     }
     return true;
   }
 
   nsresult DoSendAsyncMessage(JSContext* aCx,
                               const nsAString& aMessage,
                               StructuredCloneData& aData,
                               JS::Handle<JSObject *> aCpows,
@@ -2070,34 +1709,34 @@ public:
     return NS_OK;
   }
 
 };
 
 
 // This creates the global parent process message manager.
 nsresult
-NS_NewParentProcessMessageManager(nsIMessageBroadcaster** aResult)
+NS_NewParentProcessMessageManager(nsISupports** aResult)
 {
   NS_ASSERTION(!nsFrameMessageManager::sParentProcessManager,
                "Re-creating sParentProcessManager");
-  RefPtr<nsFrameMessageManager> mm =
+  RefPtr<ChromeMessageBroadcaster> mm =
     new ChromeMessageBroadcaster(MessageManagerFlags::MM_PROCESSMANAGER);
   nsFrameMessageManager::sParentProcessManager = mm;
   nsFrameMessageManager::NewProcessMessageManager(false); // Create same process message manager.
   mm.forget(aResult);
   return NS_OK;
 }
 
 
 ChromeMessageSender*
 nsFrameMessageManager::NewProcessMessageManager(bool aIsRemote)
 {
   if (!nsFrameMessageManager::sParentProcessManager) {
-     nsCOMPtr<nsIMessageBroadcaster> dummy =
+     nsCOMPtr<nsISupports> dummy =
        do_GetService("@mozilla.org/parentprocessmessagemanager;1");
   }
 
   MOZ_ASSERT(nsFrameMessageManager::sParentProcessManager,
              "parent process manager not created");
   ChromeMessageSender* mm;
   if (aIsRemote) {
     // Callback is set in ContentParent::InitInternal so that the process has
@@ -2111,58 +1750,52 @@ nsFrameMessageManager::NewProcessMessage
                                  MessageManagerFlags::MM_PROCESSMANAGER |
                                  MessageManagerFlags::MM_OWNSCALLBACK);
     sSameProcessParentManager = mm;
   }
   return mm;
 }
 
 nsresult
-NS_NewChildProcessMessageManager(nsISyncMessageSender** aResult)
+NS_NewChildProcessMessageManager(nsISupports** aResult)
 {
   NS_ASSERTION(!nsFrameMessageManager::GetChildProcessManager(),
                "Re-creating sChildProcessManager");
 
   MessageManagerCallback* cb;
   if (XRE_IsParentProcess()) {
     cb = new SameChildProcessMessageManagerCallback();
   } else {
     cb = new ChildProcessMessageManagerCallback();
     RegisterStrongMemoryReporter(new MessageManagerReporter());
   }
   auto* mm = new ChildProcessMessageManager(cb);
   nsFrameMessageManager::SetChildProcessManager(mm);
   RefPtr<ProcessGlobal> global = new ProcessGlobal(mm);
   NS_ENSURE_TRUE(global->Init(), NS_ERROR_UNEXPECTED);
-  global.forget(aResult);
-  return NS_OK;
+  return CallQueryInterface(global, aResult);
 }
 
-bool
+void
 nsFrameMessageManager::MarkForCC()
 {
   for (auto iter = mListeners.Iter(); !iter.Done(); iter.Next()) {
     nsAutoTObserverArray<nsMessageListenerInfo, 1>* listeners = iter.UserData();
     uint32_t count = listeners->Length();
     for (uint32_t i = 0; i < count; i++) {
-      MessageListenerHolder& strongListener = listeners->ElementAt(i).mStrongListener;
+      MessageListener* strongListener = listeners->ElementAt(i).mStrongListener;
       if (strongListener) {
-        if (strongListener.HasWebIDLCallback()) {
-          strongListener.GetWebIDLCallback()->MarkForCC();
-        } else {
-          xpc_TryUnmarkWrappedGrayObject(strongListener.GetXPCOMCallback());
-        }
+        strongListener->MarkForCC();
       }
     }
   }
 
   if (mRefCnt.IsPurple()) {
     mRefCnt.RemovePurple();
   }
-  return true;
 }
 
 nsSameProcessAsyncMessageBase::nsSameProcessAsyncMessageBase(JS::RootingContext* aRootingCx,
                                                              JS::Handle<JSObject*> aCpows)
   : mCpows(aRootingCx, aCpows)
 #ifdef DEBUG
   , mCalledInit(false)
 #endif
@@ -2195,11 +1828,11 @@ nsSameProcessAsyncMessageBase::ReceiveMe
 {
   // Make sure that we have called Init() and it has succeeded.
   MOZ_ASSERT(mCalledInit);
   if (aManager) {
     SameProcessCpowHolder cpows(RootingCx(), mCpows);
 
     RefPtr<nsFrameMessageManager> mm = aManager;
     mm->ReceiveMessage(aTarget, aTargetFrameLoader, mMessage, false, &mData,
-                       &cpows, mPrincipal, nullptr);
+                       &cpows, mPrincipal, nullptr, IgnoreErrors());
   }
 }
--- a/dom/base/nsFrameMessageManager.h
+++ b/dom/base/nsFrameMessageManager.h
@@ -37,16 +37,17 @@
 class nsFrameLoader;
 
 namespace mozilla {
 namespace dom {
 
 class nsIContentParent;
 class nsIContentChild;
 class ChildProcessMessageManager;
+class ChromeMessageBroadcaster;
 class ChromeMessageSender;
 class ClonedMessageData;
 class MessageListener;
 class MessageListenerManager;
 class MessageManagerReporter;
 template<typename T> class Optional;
 
 namespace ipc {
@@ -90,30 +91,22 @@ public:
                                       const nsAString& aMessage,
                                       StructuredCloneData& aData,
                                       JS::Handle<JSObject*> aCpows,
                                       nsIPrincipal* aPrincipal)
   {
     return NS_OK;
   }
 
-  virtual nsIMessageSender* GetProcessMessageManager() const
+  virtual mozilla::dom::ChromeMessageSender* GetProcessMessageManager() const
   {
     return nullptr;
   }
 
-  virtual nsresult DoGetRemoteType(nsAString& aRemoteType) const
-  {
-    aRemoteType.Truncate();
-    nsIMessageSender* parent = GetProcessMessageManager();
-    if (parent) {
-      return parent->GetRemoteType(aRemoteType);
-    }
-    return NS_OK;
-  }
+  virtual nsresult DoGetRemoteType(nsAString& aRemoteType) const;
 
 protected:
   bool BuildClonedMessageDataForParent(nsIContentParent* aParent,
                                        StructuredCloneData& aData,
                                        ClonedMessageData& aClonedData);
   bool BuildClonedMessageDataForChild(nsIContentChild* aChild,
                                       StructuredCloneData& aData,
                                       ClonedMessageData& aClonedData);
@@ -121,66 +114,49 @@ protected:
 
 void UnpackClonedMessageDataForParent(const ClonedMessageData& aClonedData,
                                       StructuredCloneData& aData);
 
 void UnpackClonedMessageDataForChild(const ClonedMessageData& aClonedData,
                                      StructuredCloneData& aData);
 
 } // namespace ipc
-
-typedef CallbackObjectHolder<mozilla::dom::MessageListener,
-                             nsIMessageListener> MessageListenerHolder;
-
 } // namespace dom
 } // namespace mozilla
 
 struct nsMessageListenerInfo
 {
   bool operator==(const nsMessageListenerInfo& aOther) const
   {
     return &aOther == this;
   }
 
-  // If mWeakListener is null then mStrongListener holds either a MessageListener or an
-  // nsIMessageListener. If mWeakListener is non-null then mStrongListener contains null.
-  mozilla::dom::MessageListenerHolder mStrongListener;
+  // If mWeakListener is null then mStrongListener holds a MessageListener.
+  // If mWeakListener is non-null then mStrongListener contains null.
+  RefPtr<mozilla::dom::MessageListener> mStrongListener;
   nsWeakPtr mWeakListener;
   bool mListenWhenClosed;
 };
 
-inline void
-ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
-                            nsMessageListenerInfo& aField,
-                            const char* aName,
-                            uint32_t aFlags = 0)
-{
-  ImplCycleCollectionTraverse(aCallback, aField.mStrongListener, aName, aFlags);
-  ImplCycleCollectionTraverse(aCallback, aField.mWeakListener, aName, aFlags);
-}
-
 class MOZ_STACK_CLASS SameProcessCpowHolder : public mozilla::jsipc::CpowHolder
 {
 public:
   SameProcessCpowHolder(JS::RootingContext* aRootingCx, JS::Handle<JSObject*> aObj)
     : mObj(aRootingCx, aObj)
   {
   }
 
   virtual bool ToObject(JSContext* aCx, JS::MutableHandle<JSObject*> aObjp)
     override;
 
 private:
   JS::Rooted<JSObject*> mObj;
 };
 
-class nsFrameMessageManager : public nsIContentFrameMessageManager,
-                              public nsIMessageBroadcaster,
-                              public nsIFrameScriptLoader,
-                              public nsIGlobalProcessScriptLoader
+class nsFrameMessageManager : public nsIContentFrameMessageManager
 {
   friend class mozilla::dom::MessageManagerReporter;
   typedef mozilla::dom::ipc::StructuredCloneData StructuredCloneData;
 
 protected:
   typedef mozilla::dom::ipc::MessageManagerFlags MessageManagerFlags;
 
   nsFrameMessageManager(mozilla::dom::ipc::MessageManagerCallback* aCallback,
@@ -192,16 +168,18 @@ public:
   explicit nsFrameMessageManager(mozilla::dom::ipc::MessageManagerCallback* aCallback)
     : nsFrameMessageManager(aCallback, MessageManagerFlags::MM_NONE)
   {}
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsFrameMessageManager,
                                                          nsIContentFrameMessageManager)
 
+  void MarkForCC();
+
   // MessageListenerManager
   void AddMessageListener(const nsAString& aMessageName,
                           mozilla::dom::MessageListener& aListener,
                           bool aListenWhenClosed,
                           mozilla::ErrorResult& aError);
   void RemoveMessageListener(const nsAString& aMessageName,
                              mozilla::dom::MessageListener& aListener,
                              mozilla::ErrorResult& aError);
@@ -218,17 +196,17 @@ public:
                         JS::Handle<JSObject*> aObjects,
                         nsIPrincipal* aPrincipal,
                         JS::Handle<JS::Value> aTransfers,
                         mozilla::ErrorResult& aError)
   {
     DispatchAsyncMessage(aCx, aMessageName, aObj, aObjects, aPrincipal, aTransfers,
                          aError);
   }
-  already_AddRefed<nsIMessageSender>
+  already_AddRefed<mozilla::dom::ChromeMessageSender>
     GetProcessMessageManager(mozilla::ErrorResult& aError);
   void GetRemoteType(nsAString& aRemoteType, mozilla::ErrorResult& aError) const;
 
   // SyncMessageSender
   void SendSyncMessage(JSContext* aCx, const nsAString& aMessageName,
                        JS::Handle<JS::Value> aObj,
                        JS::Handle<JSObject*> aObjects,
                        nsIPrincipal* aPrincipal,
@@ -247,37 +225,32 @@ public:
     SendMessage(aCx, aMessageName, aObj, aObjects, aPrincipal, false, aResult, aError);
   }
 
   // GlobalProcessScriptLoader
   void GetInitialProcessData(JSContext* aCx,
                              JS::MutableHandle<JS::Value> aInitialProcessData,
                              mozilla::ErrorResult& aError);
 
-  NS_DECL_NSIMESSAGELISTENERMANAGER
   NS_DECL_NSIMESSAGESENDER
-  NS_DECL_NSIMESSAGEBROADCASTER
-  NS_DECL_NSISYNCMESSAGESENDER
-  NS_DECL_NSIMESSAGEMANAGERGLOBAL
   NS_DECL_NSICONTENTFRAMEMESSAGEMANAGER
-  NS_DECL_NSIFRAMESCRIPTLOADER
-  NS_DECL_NSIPROCESSSCRIPTLOADER
-  NS_DECL_NSIGLOBALPROCESSSCRIPTLOADER
 
   static mozilla::dom::ChromeMessageSender*
   NewProcessMessageManager(bool aIsRemote);
 
-  nsresult ReceiveMessage(nsISupports* aTarget, nsFrameLoader* aTargetFrameLoader,
-                          const nsAString& aMessage,
-                          bool aIsSync, StructuredCloneData* aCloneData,
-                          mozilla::jsipc::CpowHolder* aCpows, nsIPrincipal* aPrincipal,
-                          nsTArray<StructuredCloneData>* aRetVal);
+  void ReceiveMessage(nsISupports* aTarget, nsFrameLoader* aTargetFrameLoader,
+                      const nsAString& aMessage, bool aIsSync,
+                      StructuredCloneData* aCloneData, mozilla::jsipc::CpowHolder* aCpows,
+                      nsIPrincipal* aPrincipal, nsTArray<StructuredCloneData>* aRetVal,
+                      mozilla::ErrorResult& aError)
+  {
+    ReceiveMessage(aTarget, aTargetFrameLoader, mClosed, aMessage, aIsSync, aCloneData,
+                   aCpows, aPrincipal, aRetVal, aError);
+  }
 
-  void AddChildManager(mozilla::dom::MessageListenerManager* aManager);
-  void RemoveChildManager(mozilla::dom::MessageListenerManager* aManager);
   void Disconnect(bool aRemoveFromParent = true);
   void Close();
 
   void SetCallback(mozilla::dom::ipc::MessageManagerCallback* aCallback);
 
   mozilla::dom::ipc::MessageManagerCallback* GetCallback()
   {
     return mCallback;
@@ -293,18 +266,22 @@ public:
 
   nsresult DispatchAsyncMessageInternal(JSContext* aCx,
                                         const nsAString& aMessage,
                                         StructuredCloneData& aData,
                                         JS::Handle<JSObject*> aCpows,
                                         nsIPrincipal* aPrincipal);
   bool IsGlobal() { return mGlobal; }
   bool IsBroadcaster() { return mIsBroadcaster; }
+  bool IsChrome() { return mChrome; }
 
-  static nsFrameMessageManager* GetParentProcessManager()
+  // GetGlobalMessageManager creates the global message manager if it hasn't been yet.
+  static already_AddRefed<mozilla::dom::ChromeMessageBroadcaster>
+    GetGlobalMessageManager();
+  static mozilla::dom::ChromeMessageBroadcaster* GetParentProcessManager()
   {
     return sParentProcessManager;
   }
   static mozilla::dom::ChildProcessMessageManager* GetChildProcessManager()
   {
     return sChildProcessManager;
   }
   static void SetChildProcessManager(mozilla::dom::ChildProcessMessageManager* aManager)
@@ -314,17 +291,17 @@ public:
 
   void SetInitialProcessData(JS::HandleValue aInitialData);
 
   void LoadPendingScripts();
 
 protected:
   friend class MMListenerRemover;
 
-  virtual nsFrameMessageManager* GetParentManager()
+  virtual mozilla::dom::ChromeMessageBroadcaster* GetParentManager()
   {
     return nullptr;
   }
   virtual void ClearParentManager(bool aRemove)
   {
   }
 
   void DispatchAsyncMessage(JSContext* aCx, const nsAString& aMessageName,
@@ -346,21 +323,21 @@ protected:
                    JS::Handle<JS::Value> aObj, JS::Handle<JSObject*> aObjects,
                    nsIPrincipal* aPrincipal, bool aIsSync, nsTArray<JS::Value>& aResult,
                    mozilla::ErrorResult& aError);
   void SendMessage(JSContext* aCx, const nsAString& aMessageName,
                    StructuredCloneData& aData, JS::Handle<JSObject*> aObjects,
                    nsIPrincipal* aPrincipal, bool aIsSync,
                    nsTArray<JS::Value>& aResult, mozilla::ErrorResult& aError);
 
-  nsresult ReceiveMessage(nsISupports* aTarget, nsFrameLoader* aTargetFrameLoader,
-                          bool aTargetClosed, const nsAString& aMessage,
-                          bool aIsSync, StructuredCloneData* aCloneData,
-                          mozilla::jsipc::CpowHolder* aCpows, nsIPrincipal* aPrincipal,
-                          nsTArray<StructuredCloneData>* aRetVal);
+  void ReceiveMessage(nsISupports* aTarget, nsFrameLoader* aTargetFrameLoader,
+                      bool aTargetClosed, const nsAString& aMessage, bool aIsSync,
+                      StructuredCloneData* aCloneData, mozilla::jsipc::CpowHolder* aCpows,
+                      nsIPrincipal* aPrincipal, nsTArray<StructuredCloneData>* aRetVal,
+                      mozilla::ErrorResult& aError);
 
   void LoadScript(const nsAString& aURL, bool aAllowDelayedLoad,
                   bool aRunInGlobalScope, mozilla::ErrorResult& aError);
   void RemoveDelayedScript(const nsAString& aURL);
   nsresult GetDelayedScripts(JSContext* aCx,
                              JS::MutableHandle<JS::Value> aList);
   void GetDelayedScripts(JSContext* aCx, nsTArray<nsTArray<JS::Value>>& aList,
                          mozilla::ErrorResult& aError);
@@ -391,26 +368,20 @@ protected:
   nsAutoPtr<mozilla::dom::ipc::MessageManagerCallback> mOwnedCallback;
   nsTArray<nsString> mPendingScripts;
   nsTArray<bool> mPendingScriptsGlobalStates;
   JS::Heap<JS::Value> mInitialProcessData;
 
   void LoadPendingScripts(nsFrameMessageManager* aManager,
                           nsFrameMessageManager* aChildMM);
 public:
-  static nsFrameMessageManager* sParentProcessManager;
+  static mozilla::dom::ChromeMessageBroadcaster* sParentProcessManager;
   static nsFrameMessageManager* sSameProcessParentManager;
   static nsTArray<nsCOMPtr<nsIRunnable> >* sPendingSameProcessAsyncMessages;
 private:
-  void AddMessageListener(const nsAString& aMessageName,
-                          mozilla::dom::MessageListenerHolder&& aListener,
-                          bool aListenWhenClosed);
-  void RemoveMessageListener(const nsAString& aMessageName,
-                             const mozilla::dom::MessageListenerHolder&  aListener);
-
   static mozilla::dom::ChildProcessMessageManager* sChildProcessManager;
 };
 
 /* A helper class for taking care of many details for async message sending
    within a single process.  Intended to be used like so:
 
    class MyAsyncMessage : public nsSameProcessAsyncMessageBase, public Runnable
    {
--- a/dom/base/nsGlobalWindowInner.cpp
+++ b/dom/base/nsGlobalWindowInner.cpp
@@ -403,17 +403,17 @@ public:
   {
     return mWindow ? mWindow->EventTargetFor(TaskCategory::Other) : nullptr;
   }
 
 private:
   ~nsGlobalWindowObserver() = default;
 
   // This reference is non-owning and safe because it's cleared by
-  // nsGlobalWindowInner::CleanUp().
+  // nsGlobalWindowInner::FreeInnerObjects().
   nsGlobalWindowInner* MOZ_NON_OWNING_REF mWindow;
 };
 
 NS_IMPL_ISUPPORTS(nsGlobalWindowObserver, nsIObserver, nsIInterfaceRequestor)
 
 class IdleRequestExecutor;
 
 class IdleRequestExecutorTimeoutHandler final : public TimeoutHandler
@@ -464,17 +464,17 @@ public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(IdleRequestExecutor, nsIRunnable)
 
   NS_DECL_NSIRUNNABLE
   NS_DECL_NSINAMED
   nsresult Cancel() override;
   void SetDeadline(TimeStamp aDeadline) override;
 
-  bool IsCancelled() const { return !mWindow || mWindow->InnerObjectsFreed(); }
+  bool IsCancelled() const { return !mWindow || mWindow->IsDying(); }
   // Checks if aRequest shouldn't execute in the current idle period
   // since it has been queued from a chained call to
   // requestIdleCallback from within a running idle callback.
   bool IneligibleForCurrentIdlePeriod(IdleRequest* aRequest) const
   {
     return aRequest->Handle() >= mIdlePeriodLimit.mLastRequestIdInIdlePeriod &&
            TimeStamp::Now() <= mIdlePeriodLimit.mEndOfIdlePeriod;
   }
@@ -785,17 +785,17 @@ NS_INTERFACE_MAP_END_INHERITING(TimeoutH
 uint32_t
 nsGlobalWindowInner::RequestIdleCallback(JSContext* aCx,
                                          IdleRequestCallback& aCallback,
                                          const IdleRequestOptions& aOptions,
                                          ErrorResult& aError)
 {
   AssertIsOnMainThread();
 
-  if (mInnerObjectsFreed) {
+  if (IsDying()) {
    return 0;
   }
 
   uint32_t handle = mIdleRequestCallbackCounter++;
 
   RefPtr<IdleRequest> request =
     new IdleRequest(&aCallback, handle);
 
@@ -912,17 +912,16 @@ nsGlobalWindowInner::nsGlobalWindowInner
     mHasVRDisplayActivateEvents(false),
     mHasSeenGamepadInput(false),
     mSuspendDepth(0),
     mFreezeDepth(0),
     mFocusMethod(0),
     mSerial(0),
     mIdleRequestCallbackCounter(1),
     mIdleRequestExecutor(nullptr),
-    mCleanedUp(false),
     mDialogAbuseCount(0),
     mAreDialogsEnabled(true),
     mObservingDidRefresh(false),
     mIteratingDocumentFlushedResolvers(false),
     mCanSkipCCGeneration(0),
     mBeforeUnloadListenerCount(0)
 {
   AssertIsOnMainThread();
@@ -1059,17 +1058,19 @@ nsGlobalWindowInner::~nsGlobalWindowInne
     if (mChromeFields.mMessageManager) {
       static_cast<nsFrameMessageManager *>(
         mChromeFields.mMessageManager.get())->Disconnect();
     }
 
     mCleanMessageManager = false;
   }
 
-  DisconnectEventTargetObjects();
+  // In most cases this should already have been called, but call it again
+  // here to catch any corner cases.
+  FreeInnerObjects();
 
   if (sInnerWindowsById) {
     MOZ_ASSERT(sInnerWindowsById->Get(mWindowID),
                 "This window should be in the hash table");
     sInnerWindowsById->Remove(mWindowID);
   }
 
   nsContentUtils::InnerOrOuterWindowDestroyed();
@@ -1098,41 +1099,30 @@ nsGlobalWindowInner::~nsGlobalWindowInne
   }
 #endif
 
   MOZ_LOG(gDOMLeakPRLogInner, LogLevel::Debug, ("DOMWINDOW %p destroyed", this));
 
   Telemetry::Accumulate(Telemetry::INNERWINDOWS_WITH_MUTATION_LISTENERS,
                         mMutationBits ? 1 : 0);
 
-  if (mListenerManager) {
-    mListenerManager->Disconnect();
-    mListenerManager = nullptr;
-  }
-
   // An inner window is destroyed, pull it out of the outer window's
   // list if inner windows.
 
   PR_REMOVE_LINK(this);
 
   // If our outer window's inner window is this window, null out the
   // outer window's reference to this window that's being deleted.
   nsGlobalWindowOuter *outer = GetOuterWindowInternal();
   if (outer) {
     outer->MaybeClearInnerWindow(this);
   }
 
   // We don't have to leave the tab group if we are an inner window.
 
-  // While CleanUp generally seems to be intended to clean up outers, we've
-  // historically called it for both. Changing this would probably involve
-  // auditing all of the references that inners and outers can have, and
-  // separating the handling into CleanUp() and FreeInnerObjects.
-  CleanUp();
-
   nsCOMPtr<nsIDeviceSensors> ac = do_GetService(NS_DEVICE_SENSORS_CONTRACTID);
   if (ac)
     ac->RemoveWindowAsListener(this);
 
   nsLayoutStatics::Release();
 }
 
 // static
@@ -1156,130 +1146,31 @@ nsGlobalWindowInner::CleanupCachedXBLHan
 {
   if (mCachedXBLPrototypeHandlers &&
       mCachedXBLPrototypeHandlers->Count() > 0) {
     mCachedXBLPrototypeHandlers->Clear();
   }
 }
 
 void
-nsGlobalWindowInner::CleanUp()
-{
-  // Guarantee idempotence.
-  if (mCleanedUp)
+nsGlobalWindowInner::FreeInnerObjects()
+{
+  if (IsDying()) {
     return;
-  mCleanedUp = true;
-
+  }
   StartDying();
 
-  DisconnectEventTargetObjects();
-
-  if (mObserver) {
-    nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
-    if (os) {
-      os->RemoveObserver(mObserver, NS_IOSERVICE_OFFLINE_STATUS_TOPIC);
-      os->RemoveObserver(mObserver, MEMORY_PRESSURE_OBSERVER_TOPIC);
-    }
-
-    RefPtr<StorageNotifierService> sns = StorageNotifierService::GetOrCreate();
-    if (sns) {
-     sns->Unregister(mObserver);
-    }
-
-    if (mIdleService) {
-      mIdleService->RemoveIdleObserver(mObserver, MIN_IDLE_NOTIFICATION_TIME_S);
-    }
-
-    Preferences::RemoveObserver(mObserver, "intl.accept_languages");
-
-    // Drop its reference to this dying window, in case for some bogus reason
-    // the object stays around.
-    mObserver->Forget();
-  }
-
-  if (mNavigator) {
-    mNavigator->Invalidate();
-    mNavigator = nullptr;
-  }
-
-  mScreen = nullptr;
-  mMenubar = nullptr;
-  mToolbar = nullptr;
-  mLocationbar = nullptr;
-  mPersonalbar = nullptr;
-  mStatusbar = nullptr;
-  mScrollbars = nullptr;
-  mHistory = nullptr;
-  mCustomElements = nullptr;
-  mApplicationCache = nullptr;
-  mIndexedDB = nullptr;
-
-  mConsole = nullptr;
-
-  mAudioWorklet = nullptr;
-  mPaintWorklet = nullptr;
-
-  mExternal = nullptr;
-  mInstallTrigger = nullptr;
-
-  mPerformance = nullptr;
-
-#ifdef MOZ_WEBSPEECH
-  mSpeechSynthesis = nullptr;
-#endif
-
-#if defined(MOZ_WIDGET_ANDROID)
-  mOrientationChangeObserver = nullptr;
-#endif
-
-  mChromeEventHandler = nullptr; // Forces Release
-  mParentTarget = nullptr;
-
-  DisableGamepadUpdates();
-  mHasGamepad = false;
-  DisableVRUpdates();
-  mHasVREvents = false;
-  mHasVRDisplayActivateEvents = false;
-  DisableIdleCallbackRequests();
-
-  if (mCleanMessageManager) {
-    MOZ_ASSERT(mIsChrome, "only chrome should have msg manager cleaned");
-    if (mChromeFields.mMessageManager) {
-      mChromeFields.mMessageManager->Disconnect();
-    }
-  }
-
-  CleanupCachedXBLHandlers();
-
-  for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) {
-    mAudioContexts[i]->Shutdown();
-  }
-  mAudioContexts.Clear();
-
-  if (mIdleTimer) {
-    mIdleTimer->Cancel();
-    mIdleTimer = nullptr;
-  }
-
-  mIntlUtils = nullptr;
-}
-
-void
-nsGlobalWindowInner::FreeInnerObjects()
-{
   // Make sure that this is called before we null out the document and
   // other members that the window destroyed observers could
   // re-create.
   NotifyDOMWindowDestroyed(this);
   if (auto* reporter = nsWindowMemoryReporter::Get()) {
     reporter->ObserveDOMWindowDetached(this);
   }
 
-  mInnerObjectsFreed = true;
-
   // Kill all of the workers for this window.
   CancelWorkersForWindow(this);
 
   if (mTimeoutManager) {
     mTimeoutManager->ClearAllTimeouts();
   }
 
   if (mIdleTimer) {
@@ -1302,19 +1193,17 @@ nsGlobalWindowInner::FreeInnerObjects()
   mCustomElements = nullptr;
 
   if (mNavigator) {
     mNavigator->OnNavigation();
     mNavigator->Invalidate();
     mNavigator = nullptr;
   }
 
-  if (mScreen) {
-    mScreen = nullptr;
-  }
+  mScreen = nullptr;
 
 #if defined(MOZ_WIDGET_ANDROID)
   mOrientationChangeObserver = nullptr;
 #endif
 
   if (mDoc) {
     // Remember the document's principal and URI.
     mDocumentPrincipal = mDoc->NodePrincipal();
@@ -1373,32 +1262,72 @@ nsGlobalWindowInner::FreeInnerObjects()
     mBeforeUnloadListenerCount = 0;
   }
 
   // If we have any promiseDocumentFlushed callbacks, fire them now so
   // that the Promises can resolve.
   CallDocumentFlushedResolvers();
   mObservingDidRefresh = false;
 
-  // Disconnect service worker objects in FreeInnerObjects().  This is normally
-  // done from CleanUp().  In the future we plan to unify CleanUp() and
-  // FreeInnerObjects().  See bug 1450266.
-  ForEachEventTargetObject([&] (DOMEventTargetHelper* aTarget, bool* aDoneOut) {
-    RefPtr<ServiceWorkerRegistration> swr = do_QueryObject(aTarget);
-    if (swr) {
-      aTarget->DisconnectFromOwner();
-      return;
-    }
-
-    RefPtr<ServiceWorker> sw = do_QueryObject(aTarget);
-    if (sw) {
-      aTarget->DisconnectFromOwner();
-      return;
-    }
-  });
+  DisconnectEventTargetObjects();
+
+  if (mObserver) {
+    nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+    if (os) {
+      os->RemoveObserver(mObserver, NS_IOSERVICE_OFFLINE_STATUS_TOPIC);
+      os->RemoveObserver(mObserver, MEMORY_PRESSURE_OBSERVER_TOPIC);
+    }
+
+    RefPtr<StorageNotifierService> sns = StorageNotifierService::GetOrCreate();
+    if (sns) {
+     sns->Unregister(mObserver);
+    }
+
+    if (mIdleService) {
+      mIdleService->RemoveIdleObserver(mObserver, MIN_IDLE_NOTIFICATION_TIME_S);
+    }
+
+    Preferences::RemoveObserver(mObserver, "intl.accept_languages");
+
+    // Drop its reference to this dying window, in case for some bogus reason
+    // the object stays around.
+    mObserver->Forget();
+  }
+
+  mMenubar = nullptr;
+  mToolbar = nullptr;
+  mLocationbar = nullptr;
+  mPersonalbar = nullptr;
+  mStatusbar = nullptr;
+  mScrollbars = nullptr;
+
+  mConsole = nullptr;
+
+  mAudioWorklet = nullptr;
+  mPaintWorklet = nullptr;
+
+  mExternal = nullptr;
+  mInstallTrigger = nullptr;
+
+  mPerformance = nullptr;
+
+#ifdef MOZ_WEBSPEECH
+  mSpeechSynthesis = nullptr;
+#endif
+
+  mParentTarget = nullptr;
+
+  if (mCleanMessageManager) {
+    MOZ_ASSERT(mIsChrome, "only chrome should have msg manager cleaned");
+    if (mChromeFields.mMessageManager) {
+      mChromeFields.mMessageManager->Disconnect();
+    }
+  }
+
+  mIntlUtils = nullptr;
 }
 
 //*****************************************************************************
 // nsGlobalWindowInner::nsISupports
 //*****************************************************************************
 
 // QueryInterface implementation for nsGlobalWindowInner
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsGlobalWindowInner)
@@ -2636,22 +2565,22 @@ nsPIDOMWindowInner::IsRunningTimeout()
 
 void
 nsPIDOMWindowInner::TryToCacheTopInnerWindow()
 {
   if (mHasTriedToCacheTopInnerWindow) {
     return;
   }
 
-  MOZ_ASSERT(!mInnerObjectsFreed);
+  nsGlobalWindowInner* window = nsGlobalWindowInner::Cast(this);
+
+  MOZ_ASSERT(!window->IsDying());
 
   mHasTriedToCacheTopInnerWindow = true;
 
-  nsGlobalWindowInner* window = nsGlobalWindowInner::Cast(this);
-
   MOZ_ASSERT(window);
 
   if (nsCOMPtr<nsPIDOMWindowOuter> topOutter = window->GetScriptableTop()) {
     mTopInnerWindow = topOutter->GetCurrentInnerWindow();
   }
 }
 
 void
@@ -4791,17 +4720,17 @@ nsGlobalWindowInner::SetFocusedNode(nsIC
                                     uint32_t aFocusMethod,
                                     bool aNeedsFocus)
 {
   if (aNode && aNode->GetComposedDoc() != mDoc) {
     NS_WARNING("Trying to set focus to a node from a wrong document");
     return;
   }
 
-  if (mCleanedUp) {
+  if (IsDying()) {
     NS_ASSERTION(!aNode, "Trying to focus cleaned up window!");
     aNode = nullptr;
     aNeedsFocus = false;
   }
   if (mFocusedNode != aNode) {
     UpdateCanvasFocus(false, aNode);
     mFocusedNode = aNode;
     mFocusMethod = aFocusMethod & FOCUSMETHOD_MASK;
@@ -4846,17 +4775,17 @@ nsGlobalWindowInner::ShouldShowFocusRing
 
   nsCOMPtr<nsPIWindowRoot> root = GetTopWindowRoot();
   return root ? root->ShowFocusRings() : false;
 }
 
 bool
 nsGlobalWindowInner::TakeFocus(bool aFocus, uint32_t aFocusMethod)
 {
-  if (mCleanedUp) {
+  if (IsDying()) {
     return false;
   }
 
   if (aFocus)
     mFocusMethod = aFocusMethod & FOCUSMETHOD_MASK;
 
   if (mHasFocus != aFocus) {
     mHasFocus = aFocus;
@@ -7763,59 +7692,37 @@ nsGlobalWindowInner::NotifyDefaultButton
   if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED) {
     aError.Throw(rv);
   }
 #else
   aError.Throw(NS_ERROR_NOT_IMPLEMENTED);
 #endif
 }
 
-NS_IMETHODIMP
-nsGlobalWindowInner::GetMessageManager(nsIMessageBroadcaster** aManager)
-{
-  ErrorResult rv;
-  NS_IF_ADDREF(*aManager = GetMessageManager(rv));
-  return rv.StealNSResult();
-}
-
 ChromeMessageBroadcaster*
-nsGlobalWindowInner::GetMessageManager(ErrorResult& aError)
+nsGlobalWindowInner::MessageManager()
 {
   MOZ_ASSERT(IsChromeWindow());
   if (!mChromeFields.mMessageManager) {
-    nsCOMPtr<nsIMessageBroadcaster> globalMM =
-      do_GetService("@mozilla.org/globalmessagemanager;1");
-    mChromeFields.mMessageManager =
-      new ChromeMessageBroadcaster(static_cast<nsFrameMessageManager*>(globalMM.get()));
+    RefPtr<ChromeMessageBroadcaster> globalMM =
+      nsFrameMessageManager::GetGlobalMessageManager();
+    mChromeFields.mMessageManager = new ChromeMessageBroadcaster(globalMM);
   }
   return mChromeFields.mMessageManager;
 }
 
-NS_IMETHODIMP
-nsGlobalWindowInner::GetGroupMessageManager(const nsAString& aGroup,
-                                            nsIMessageBroadcaster** aManager)
-{
-  MOZ_RELEASE_ASSERT(IsChromeWindow());
-  ErrorResult rv;
-  NS_IF_ADDREF(*aManager = GetGroupMessageManager(aGroup, rv));
-  return rv.StealNSResult();
-}
-
 ChromeMessageBroadcaster*
-nsGlobalWindowInner::GetGroupMessageManager(const nsAString& aGroup,
-                                            ErrorResult& aError)
+nsGlobalWindowInner::GetGroupMessageManager(const nsAString& aGroup)
 {
   MOZ_ASSERT(IsChromeWindow());
 
   RefPtr<ChromeMessageBroadcaster> messageManager =
     mChromeFields.mGroupMessageManagers.LookupForAdd(aGroup).OrInsert(
-      [this, &aError] () {
-        nsFrameMessageManager* parent = GetMessageManager(aError);
-
-        return new ChromeMessageBroadcaster(parent);
+      [this] () {
+        return new ChromeMessageBroadcaster(MessageManager());
       });
   return messageManager;
 }
 
 void
 nsGlobalWindowInner::InitWasOffline()
 {
   mWasOffline = NS_IsOffline();
@@ -8315,17 +8222,16 @@ NextWindowID();
 
 nsPIDOMWindowInner::nsPIDOMWindowInner(nsPIDOMWindowOuter *aOuterWindow)
 : mMutationBits(0), mActivePeerConnections(0), mIsDocumentLoaded(false),
   mIsHandlingResizeEvent(false),
   mMayHavePaintEventListener(false), mMayHaveTouchEventListener(false),
   mMayHaveSelectionChangeEventListener(false),
   mMayHaveMouseEnterLeaveEventListener(false),
   mMayHavePointerEnterLeaveEventListener(false),
-  mInnerObjectsFreed(false),
   mAudioCaptured(false),
   mOuterWindow(aOuterWindow),
   // Make sure no actual window ends up with mWindowID == 0
   mWindowID(NextWindowID()), mHasNotifiedGlobalCreated(false),
   mMarkedCCGeneration(0),
   mHasTriedToCacheTopInnerWindow(false),
   mNumOfIndexedDBDatabases(0),
   mNumOfOpenWebSockets(0)
--- a/dom/base/nsGlobalWindowInner.h
+++ b/dom/base/nsGlobalWindowInner.h
@@ -499,26 +499,16 @@ public:
   virtual void EnableDeviceSensor(uint32_t aType) override;
   virtual void DisableDeviceSensor(uint32_t aType) override;
 
 #if defined(MOZ_WIDGET_ANDROID)
   virtual void EnableOrientationChangeListener() override;
   virtual void DisableOrientationChangeListener() override;
 #endif
 
-  bool IsClosedOrClosing() {
-    return mCleanedUp;
-  }
-
-  bool
-  IsCleanedUp() const
-  {
-    return mCleanedUp;
-  }
-
   virtual uint32_t GetSerial() override {
     return mSerial;
   }
 
   void AddSizeOfIncludingThis(nsWindowSizes& aWindowSizes) const;
 
   void NotifyIdleObserver(IdleObserverHolder* aIdleObserverHolder,
                           bool aCallOnidle);
@@ -942,21 +932,18 @@ public:
   void GetAttentionWithCycleCount(int32_t aCycleCount,
                                   mozilla::ErrorResult& aError);
   void SetCursor(const nsAString& aCursor, mozilla::ErrorResult& aError);
   void Maximize();
   void Minimize();
   void Restore();
   void NotifyDefaultButtonLoaded(mozilla::dom::Element& aDefaultButton,
                                  mozilla::ErrorResult& aError);
-  mozilla::dom::ChromeMessageBroadcaster*
-    GetMessageManager(mozilla::ErrorResult& aError);
-  mozilla::dom::ChromeMessageBroadcaster*
-    GetGroupMessageManager(const nsAString& aGroup,
-                           mozilla::ErrorResult& aError);
+  mozilla::dom::ChromeMessageBroadcaster* MessageManager();
+  mozilla::dom::ChromeMessageBroadcaster* GetGroupMessageManager(const nsAString& aGroup);
   void BeginWindowMove(mozilla::dom::Event& aMouseDownEvent,
                        mozilla::dom::Element* aPanel,
                        mozilla::ErrorResult& aError);
 
   already_AddRefed<mozilla::dom::Promise>
   PromiseDocumentFlushed(mozilla::dom::PromiseDocumentFlushedCallback& aCallback,
                          mozilla::ErrorResult& aError);
 
@@ -1087,17 +1074,16 @@ protected:
 
   RefPtr<mozilla::dom::WakeLock> mWakeLock;
 
   friend class HashchangeCallback;
   friend class mozilla::dom::BarProp;
 
   // Object Management
   virtual ~nsGlobalWindowInner();
-  void CleanUp();
 
   void FreeInnerObjects();
   nsGlobalWindowInner *CallerInnerWindow();
 
   // Only to be called on an inner window.
   // aDocument must not be null.
   void InnerSetNewDocument(JSContext* aCx, nsIDocument* aDocument);
 
@@ -1419,18 +1405,16 @@ protected:
   uint32_t mIdleRequestCallbackCounter;
   IdleRequests mIdleRequestCallbacks;
   RefPtr<IdleRequestExecutor> mIdleRequestExecutor;
 
 #ifdef DEBUG
   nsCOMPtr<nsIURI> mLastOpenedURI;
 #endif
 
-  bool mCleanedUp;
-
   nsCOMPtr<nsIDOMOfflineResourceList> mApplicationCache;
 
   using XBLPrototypeHandlerTable = nsJSThingHashtable<nsPtrHashKey<nsXBLPrototypeHandler>, JSObject*>;
   mozilla::UniquePtr<XBLPrototypeHandlerTable> mCachedXBLPrototypeHandlers;
 
   RefPtr<mozilla::dom::IDBFactory> mIndexedDB;
 
   // This counts the number of windows that have been opened in rapid succession
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -1049,21 +1049,16 @@ nsGlobalWindowOuter::CleanUp()
 
   mOpener = nullptr;             // Forces Release
   if (mContext) {
     mContext = nullptr;            // Forces Release
   }
   mChromeEventHandler = nullptr; // Forces Release
   mParentTarget = nullptr;
 
-  nsGlobalWindowInner* inner = GetCurrentInnerWindowInternal();
-  if (inner) {
-    inner->CleanUp();
-  }
-
   mArguments = nullptr;
 
   if (mIdleTimer) {
     mIdleTimer->Cancel();
     mIdleTimer = nullptr;
   }
 }
 
@@ -1850,16 +1845,25 @@ nsGlobalWindowOuter::SetNewDocument(nsID
         currentInner->AsInner()->CreatePerformanceObjectIfNeeded();
         if (currentInner->mPerformance) {
           newInnerWindow->mPerformance =
             Performance::CreateForMainThread(newInnerWindow->AsInner(),
                                              aDocument->NodePrincipal(),
                                              currentInner->mPerformance->GetDOMTiming(),
                                              currentInner->mPerformance->GetChannel());
         }
+
+        // Rebind DETH objects to the new global created by document.open().
+        // XXX: Is this correct?  We should consider if the spec and our
+        //      implementation should change to match other browsers by
+        //      just reusing the current window.  (Bug 1449992)
+        currentInner->ForEachEventTargetObject(
+          [&] (DOMEventTargetHelper* aDETH, bool* aDoneOut) {
+            aDETH->BindToOwner(newInnerWindow->AsGlobal());
+          });
       }
 
       // Don't free objects on our current inner window if it's going to be
       // held in the bfcache.
       if (!currentInner->IsFrozen()) {
         currentInner->FreeInnerObjects();
       }
     }
@@ -7529,28 +7533,34 @@ nsGlobalWindowOuter::GetBrowserDOMWindow
 
 void
 nsGlobalWindowOuter::SetBrowserDOMWindowOuter(nsIBrowserDOMWindow* aBrowserWindow)
 {
   MOZ_ASSERT(IsChromeWindow());
   mChromeFields.mBrowserDOMWindow = aBrowserWindow;
 }
 
-NS_IMETHODIMP
-nsGlobalWindowOuter::GetMessageManager(nsIMessageBroadcaster** aManager)
-{
-  FORWARD_TO_INNER(GetMessageManager, (aManager), NS_ERROR_UNEXPECTED);
-}
-
-NS_IMETHODIMP
-nsGlobalWindowOuter::GetGroupMessageManager(const nsAString& aGroup,
-                                            nsIMessageBroadcaster** aManager)
-{
-  MOZ_RELEASE_ASSERT(IsChromeWindow());
-  FORWARD_TO_INNER(GetGroupMessageManager, (aGroup, aManager), NS_ERROR_UNEXPECTED);
+ChromeMessageBroadcaster*
+nsGlobalWindowOuter::GetMessageManager()
+{
+  if (!mInnerWindow) {
+    NS_WARNING("No inner window available!");
+    return nullptr;
+  }
+  return GetCurrentInnerWindowInternal()->MessageManager();
+}
+
+ChromeMessageBroadcaster*
+nsGlobalWindowOuter::GetGroupMessageManager(const nsAString& aGroup)
+{
+  if (!mInnerWindow) {
+    NS_WARNING("No inner window available!");
+    return nullptr;
+  }
+  return GetCurrentInnerWindowInternal()->GetGroupMessageManager(aGroup);
 }
 
 void
 nsPIDOMWindowOuter::SetOpenerForInitialContentBrowser(nsPIDOMWindowOuter* aOpenerWindow)
 {
   MOZ_ASSERT(!mOpenerForInitialContentBrowser,
              "Don't set OpenerForInitialContentBrowser twice!");
   mOpenerForInitialContentBrowser = aOpenerWindow;
--- a/dom/base/nsGlobalWindowOuter.h
+++ b/dom/base/nsGlobalWindowOuter.h
@@ -260,16 +260,19 @@ public:
   virtual nsIPrincipal* GetPrincipal() override;
 
   // nsIDOMWindow
   NS_DECL_NSIDOMWINDOW
 
   // nsIDOMChromeWindow (only implemented on chrome windows)
   NS_DECL_NSIDOMCHROMEWINDOW
 
+  mozilla::dom::ChromeMessageBroadcaster* GetMessageManager();
+  mozilla::dom::ChromeMessageBroadcaster* GetGroupMessageManager(const nsAString& aGroup);
+
   nsresult
   OpenJS(const nsAString& aUrl, const nsAString& aName,
          const nsAString& aOptions, nsPIDOMWindowOuter **_retval);
 
   // nsIDOMEventTarget
   NS_DECL_NSIDOMEVENTTARGET
 
   virtual mozilla::EventListenerManager*
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -2962,16 +2962,43 @@ public:
   // Removes an element from mResponsiveContent when the element is
   // removed from the tree.
   void RemoveResponsiveContent(mozilla::dom::HTMLImageElement* aContent)
   {
     MOZ_ASSERT(aContent);
     mResponsiveContent.RemoveEntry(aContent);
   }
 
+  void AddComposedDocShadowRoot(mozilla::dom::ShadowRoot& aShadowRoot)
+  {
+    MOZ_ASSERT(IsShadowDOMEnabled());
+    mComposedShadowRoots.PutEntry(&aShadowRoot);
+  }
+
+  using ShadowRootSet = nsTHashtable<nsPtrHashKey<mozilla::dom::ShadowRoot>>;
+
+  void RemoveComposedDocShadowRoot(mozilla::dom::ShadowRoot& aShadowRoot)
+  {
+    MOZ_ASSERT(IsShadowDOMEnabled());
+    mComposedShadowRoots.RemoveEntry(&aShadowRoot);
+  }
+
+  // If you're considering using this, you probably want to use
+  // ShadowRoot::IsComposedDocParticipant instead. This is just for
+  // sanity-checking.
+  bool IsComposedDocShadowRoot(mozilla::dom::ShadowRoot& aShadowRoot)
+  {
+    return mComposedShadowRoots.Contains(&aShadowRoot);
+  }
+
+  const ShadowRootSet& ComposedShadowRoots() const
+  {
+    return mComposedShadowRoots;
+  }
+
   // Notifies any responsive content added by AddResponsiveContent upon media
   // features values changing.
   void NotifyMediaFeatureValuesChanged();
 
   nsresult GetStateObject(nsIVariant** aResult);
 
   nsDOMNavigationTiming* GetNavigationTiming() const
   {
@@ -3753,16 +3780,21 @@ protected:
   RefPtr<mozilla::css::Loader> mCSSLoader;
   RefPtr<mozilla::css::ImageLoader> mStyleImageLoader;
   RefPtr<nsHTMLStyleSheet> mAttrStyleSheet;
   RefPtr<nsHTMLCSSStyleSheet> mStyleAttrStyleSheet;
 
   // Tracking for images in the document.
   RefPtr<mozilla::dom::ImageTracker> mImageTracker;
 
+  // A hashtable of ShadowRoots belonging to the composed doc.
+  //
+  // See ShadowRoot::SetIsComposedDocParticipant.
+  ShadowRootSet mComposedShadowRoots;
+
   // The set of all object, embed, video/audio elements or
   // nsIObjectLoadingContent or nsIDocumentActivity for which this is the owner
   // document. (They might not be in the document.)
   //
   // These are non-owning pointers, the elements are responsible for removing
   // themselves when they go away.
   nsAutoPtr<nsTHashtable<nsPtrHashKey<nsISupports> > > mActivityObservers;
 
--- a/dom/base/nsIMessageManager.idl
+++ b/dom/base/nsIMessageManager.idl
@@ -1,508 +1,24 @@
 /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
-interface mozIDOMWindowProxy;
-interface nsIDocShell;
 interface nsIContent;
-interface nsIEventTarget;
-interface nsIPrincipal;
-interface nsFrameLoader;
-
-// We define a "native" type for nsFrameLoader so that the rust xpidl
-// codegen doesn't try to do anything with it.
-native nativeFrameLoader(nsFrameLoader*);
-
-/**
- * Message managers provide a way for chrome-privileged JS code to
- * communicate with each other, even across process boundaries.
- *
- * Message managers are separated into "parent side" and "child side".
- * These don't always correspond to process boundaries, but can.  For
- * each child-side message manager, there is always exactly one
- * corresponding parent-side message manager that it sends messages
- * to.  However, for each parent-side message manager, there may be
- * either one or many child-side managers it can message.
- *
- * Message managers that always have exactly one "other side" are of
- * type nsIMessageSender.  Parent-side message managers that have many
- * "other sides" are of type nsIMessageBroadcaster.
- *
- * Child-side message managers can send synchronous messages to their
- * parent side, but not the other way around.
- *
- * There are two realms of message manager hierarchies.  One realm
- * approximately corresponds to DOM elements, the other corresponds to
- * process boundaries.
- *
- * Message managers corresponding to DOM elements
- * ==============================================
- *
- * In this realm of message managers, there are
- *  - "frame message managers" which correspond to frame elements
- *  - "window message managers" which correspond to top-level chrome
- *    windows
- *  - "group message managers" which correspond to named message
- *    managers with a specific window MM as the parent
- *  - the "global message manager", on the parent side.  See below.
- *
- * The DOM-realm message managers can communicate in the ways shown by
- * the following diagram.  The parent side and child side can
- * correspond to process boundaries, but don't always.
- *
- *  Parent side                         Child side
- * -------------                       ------------
- *  global MMg
- *   |
- *   +-->window MMw1
- *   |    |
- *   |    +-->frame MMp1_1<------------>frame MMc1_1
- *   |    |
- *   |    +-->frame MMp1_2<------------>frame MMc1_2
- *   |    |
- *   |    +-->group MMgr1
- *   |    |    |
- *   |    |    +-->frame MMp2_1<------->frame MMc2_1
- *   |    |    |
- *   |    |    +-->frame MMp2_2<------->frame MMc2_2
- *   |    |
- *   |    +-->group MMgr2
- *   |    |    ...
- *   |    |
- *   |    ...
- *   |
- *   +-->window MMw2
- *   ...
- *
- * For example: a message sent from MMc1_1, from the child side, is
- * sent only to MMp1_1 on the parent side.  However, note that all
- * message managers in the hierarchy above MMp1_1, in this diagram
- * MMw1 and MMg, will also notify their message listeners when the
- * message arrives.
- *
- * A message sent from MMc2_1 will be sent to MMp2_1 and also notify
- * all message managers in the hierarchy above that, including the
- * group message manager MMgr1.
 
- * For example: a message broadcast through the global MMg on the
- * parent side would be broadcast to MMw1, which would transitively
- * broadcast it to MMp1_1, MM1p_2.  The message would next be
- * broadcast to MMgr1, which would broadcast it to MMp2_1 and MMp2_2.
- * After that it would broadcast to MMgr2 and then to MMw2, and so
- * on down the hierarchy.
- *
- *   ***** PERFORMANCE AND SECURITY WARNING *****
- * Messages broadcast through the global MM and window or group MMs
- * can result in messages being dispatched across many OS processes,
- * and to many processes with different permissions.  Great care
- * should be taken when broadcasting.
- *
- * Interfaces
- * ----------
- *
- * The global MMg and window MMw's are message broadcasters implementing
- * nsIMessageBroadcaster while the frame MMp's are simple message senders
- * (nsIMessageSender). Their counterparts in the content processes are
- * message senders implementing nsIContentFrameMessageManager.
- *
- *                    nsIMessageListenerManager
- *                  /                           \
- * nsIMessageSender                               nsIMessageBroadcaster
- *       |
- * nsISyncMessageSender (content process/in-process only)
- *       |
- * nsIContentFrameMessageManager (content process/in-process only)
- *       |
- * nsIInProcessContentFrameMessageManager (in-process only)
- *
- *
- * Message managers in the chrome process can also be QI'ed to nsIFrameScriptLoader.
- *
- *
- * Message managers corresponding to process boundaries
- * ====================================================
- *
- * The second realm of message managers is the "process message
- * managers".  With one exception, these always correspond to process
- * boundaries.  The picture looks like
- *
- *  Parent process                      Child processes
- * ----------------                    -----------------
- *  global (GPPMM)
- *   |
- *   +-->parent in-process PIPMM<-->child in-process CIPPMM
- *   |
- *   +-->parent (PPMM1)<------------------>child (CPMM1)
- *   |
- *   +-->parent (PPMM2)<------------------>child (CPMM2)
- *   ...
- *
- * Note, PIPMM and CIPPMM both run in the parent process.
- *
- * For example: the parent-process PPMM1 sends messages to the
- * child-process CPMM1.
- *
- * For example: CPMM1 sends messages directly to PPMM1. The global GPPMM
- * will also notify their message listeners when the message arrives.
- *
- * For example: messages sent through the global GPPMM will be
- * dispatched to the listeners of the same-process, CIPPMM, CPMM1,
- * CPMM2, etc.
- *
- *   ***** PERFORMANCE AND SECURITY WARNING *****
- * Messages broadcast through the GPPMM can result in messages
- * being dispatched across many OS processes, and to many processes
- * with different permissions.  Great care should be taken when
- * broadcasting.
- *
- * Requests sent to parent-process message listeners should usually
- * have replies scoped to the requesting CPMM.  The following pattern
- * is common
- *
- *  const ParentProcessListener = {
- *    receiveMessage: function(aMessage) {
- *      let childMM = aMessage.target.QueryInterface(Ci.nsIMessageSender);
- *      switch (aMessage.name) {
- *      case "Foo:Request":
- *        // service request
- *        childMM.sendAsyncMessage("Foo:Response", { data });
- *      }
- *    }
- *  };
- */
-
-[scriptable, function, uuid(2b44eb57-a9c6-4773-9a1e-fe0818739a4c)]
-interface nsIMessageListener : nsISupports
+[uuid(bb5d79e4-e73c-45e7-9651-4d718f4b994c)]
+interface nsIMessageSender : nsISupports
 {
-  /**
-   * This is for JS only.
-   * receiveMessage is called with one parameter, which has the following
-   * properties:
-   *   {
-   *     target:    %the target of the message. Either an element owning
-   *                 the message manager, or message manager itself if no
-   *                 element owns it%
-   *     name:      %message name%,
-   *     sync:      %true or false%.
-   *     data:      %structured clone of the sent message data%,
-   *     json:      %same as .data, deprecated%,
-   *     objects:   %named table of jsvals/objects, or null%
-   *     principal: %principal for the window app
-   *   }
-   *
-   * Each listener is invoked with its own copy of the message
-   * parameter.
-   *
-   * When the listener is called, 'this' value is the target of the message.
-   *
-   * If the message is synchronous, the possible return value is
-   * returned as JSON (will be changed to use structured clones).
-   * When there are multiple listeners to sync messages, each
-   * listener's return value is sent back as an array.  |undefined|
-   * return values show up as undefined values in the array.
-   */
-  void receiveMessage();
-};
-
-[uuid(b949bfec-bb7d-47bc-b387-ac6a9b655072)]
-interface nsIMessageListenerManager : nsISupports
-{
-  /**
-   * Register |listener| to receive |messageName|.  All listener
-   * callbacks for a particular message are invoked when that message
-   * is received.
-   *
-   * The message manager holds a strong ref to |listener|.
-   *
-   * If the same listener registers twice for the same message, the
-   * second registration is ignored.
-   *
-   * Pass true for listenWhenClosed if you want to receive messages
-   * during the short period after a frame has been removed from the
-   * DOM and before its frame script has finished unloading. This
-   * parameter only has an effect for frame message managers in
-   * the main process. Default is false.
-   */
-  void addMessageListener(in AString messageName,
-                          in nsIMessageListener listener,
-                          [optional] in boolean listenWhenClosed);
-
-  /**
-   * Undo an |addMessageListener| call -- that is, calling this causes us to no
-   * longer invoke |listener| when |messageName| is received.
-   *
-   * removeMessageListener does not remove a message listener added via
-   * addWeakMessageListener; use removeWeakMessageListener for that.
-   */
-  void removeMessageListener(in AString messageName,
-                             in nsIMessageListener listener);
-
-  /**
-   * This is just like addMessageListener, except the message manager holds a
-   * weak ref to |listener|.
-   *
-   * If you have two weak message listeners for the same message, they may be
-   * called in any order.
-   */
-  void addWeakMessageListener(in AString messageName,
-                              in nsIMessageListener listener);
-
-  /**
-   * This undoes an |addWeakMessageListener| call.
-   */
-  void removeWeakMessageListener(in AString messageName,
-                                 in nsIMessageListener listener);
-
-  [notxpcom] boolean markForCC();
-};
-
-/**
- * Message "senders" have a single "other side" to which messages are
- * sent.  For example, a child-process message manager will send
- * messages that are only delivered to its one parent-process message
- * manager.
- */
-[uuid(bb5d79e4-e73c-45e7-9651-4d718f4b994c)]
-interface nsIMessageSender : nsIMessageListenerManager
-{
-  /**
-   * Send |messageName| and |obj| to the "other side" of this message
-   * manager.  This invokes listeners who registered for
-   * |messageName|.
-   *
-   * See nsIMessageListener::receiveMessage() for the format of the
-   * data delivered to listeners.
-   * @throws NS_ERROR_NOT_INITIALIZED if the sender is not initialized.  For
-   *         example, we will throw NS_ERROR_NOT_INITIALIZED if we try to send
-   *         a message to a cross-process frame but the other process has not
-   *         yet been set up.
-   * @throws NS_ERROR_FAILURE when the message receiver cannot be found.  For
-   *         example, we will throw NS_ERROR_FAILURE if we try to send a message
-   *         to a cross-process frame whose process has crashed.
-   */
-  [implicit_jscontext, optional_argc]
-  void sendAsyncMessage([optional] in AString messageName,
-                        [optional] in jsval obj,
-                        [optional] in jsval objects,
-                        [optional] in nsIPrincipal principal,
-                        [optional] in jsval transfers);
-
- /**
-  * For remote browsers there is always a corresponding process message
-  * manager. The intention of this attribute is to link leaf level frame
-  * message managers on the parent side with the corresponding process
-  * message managers (if there is one). For any other cases this property
-  * is null.
-  */
-  readonly attribute nsIMessageSender processMessageManager;
-
- /**
-  * For remote browsers, this contains the remoteType of the content child.
-  * Otherwise, it is empty.
-  */
-  readonly attribute AString remoteType;
-};
-
-/**
- * Message "broadcasters" don't have a single "other side" that they
- * send messages to, but rather a set of subordinate message managers.
- * For example, broadcasting a message through a window message
- * manager will broadcast the message to all frame message managers
- * within its window.
- */
-[uuid(4d7d62ad-4725-4f39-86cf-8fb22bf9c1d8)]
-interface nsIMessageBroadcaster : nsIMessageListenerManager
-{
-  /**
-   * Like |sendAsyncMessage()|, but also broadcasts this message to
-   * all "child" message managers of this message manager.  See long
-   * comment above for details.
-   *
-   * WARNING: broadcasting messages can be very expensive and leak
-   * sensitive data.  Use with extreme caution.
-   */
-  [implicit_jscontext, optional_argc]
-  void broadcastAsyncMessage([optional] in AString messageName,
-                             [optional] in jsval obj,
-                             [optional] in jsval objects);
-
-  /**
-   * Number of subordinate message managers.
-   */
-  readonly attribute unsigned long childCount;
-
-  /**
-   * Return a single subordinate message manager.
-   */
-  nsIMessageListenerManager getChildAt(in unsigned long aIndex);
-
-  /**
-   * Some processes are kept alive after their last tab/window are closed for testing
-   * (see dom.ipc.keepProcessesAlive). This function releases those.
-   */
-   void releaseCachedProcesses();
-};
-
-[uuid(0e602c9e-1977-422a-a8e4-fe0d4a4f78d0)]
-interface nsISyncMessageSender : nsIMessageSender
-{
-  /**
-   * Like |sendAsyncMessage()|, except blocks the sender until all
-   * listeners of the message have been invoked.  Returns an array
-   * containing return values from each listener invoked.
-   */
-  [implicit_jscontext, optional_argc]
-  jsval sendSyncMessage([optional] in AString messageName,
-                        [optional] in jsval obj,
-                        [optional] in jsval objects,
-                        [optional] in nsIPrincipal principal);
-
-  /**
-   * Like |sendSyncMessage()|, except re-entrant. New RPC messages may be
-   * issued even if, earlier on the call stack, we are waiting for a reply
-   * to an earlier sendRpcMessage() call.
-   *
-   * Both sendSyncMessage and sendRpcMessage will block until a reply is
-   * received, but they may be temporarily interrupted to process an urgent
-   * incoming message (such as a CPOW request).
-   */
-  [implicit_jscontext, optional_argc]
-  jsval sendRpcMessage([optional] in AString messageName,
-                       [optional] in jsval obj,
-                       [optional] in jsval objects,
-                       [optional] in nsIPrincipal principal);
-};
-
-[uuid(13f3555f-769e-44ea-b607-5239230c3162)]
-interface nsIMessageManagerGlobal : nsISyncMessageSender
-{
-  /**
-   * Print a string to stdout.
-   */
-  void dump(in DOMString aStr);
-
-  /**
-   * If leak detection is enabled, print a note to the leak log that this
-   * process will intentionally crash.
-   */
-  void privateNoteIntentionalCrash();
-
-  /**
-   * Ascii base64 data to binary data and vice versa
-   */
-  DOMString atob(in DOMString aAsciiString);
-  DOMString btoa(in DOMString aBase64Data);
 };
 
 [uuid(694e367c-aa25-4446-8499-2c527c4bd838)]
-interface nsIContentFrameMessageManager : nsIMessageManagerGlobal
+interface nsIContentFrameMessageManager : nsIMessageSender
 {
-  /**
-   * The current top level window in the frame or null.
-   */
-  readonly attribute mozIDOMWindowProxy content;
-
-  /**
-   * The top level docshell or null.
-   */
-  readonly attribute nsIDocShell docShell;
-
-  /**
-   * Returns the SchedulerEventTarget corresponding to the TabGroup
-   * for this frame.
-   */
-  readonly attribute nsIEventTarget tabEventTarget;
 };
 
 [uuid(b39a3324-b574-4f85-8cdb-274d04f807ef)]
 interface nsIInProcessContentFrameMessageManager : nsIContentFrameMessageManager
 {
   [notxpcom] nsIContent getOwnerContent();
-  [notxpcom] void cacheFrameLoader(in nativeFrameLoader aFrameLoader);
 };
-
-[uuid(6d12e467-2446-46db-9965-e4e93cb87ca5)]
-interface nsIContentProcessMessageManager : nsIMessageManagerGlobal
-{
-  /**
-   * Read out a copy of the object that was initialized in the parent
-   * process via nsIProcessScriptLoader.initialProcessData.
-   */
-  [implicit_jscontext]
-  readonly attribute jsval initialProcessData;
-};
-
-[uuid(bf61446b-ba24-4b1d-88c7-4f94724b9ce1)]
-interface nsIFrameScriptLoader : nsISupports
-{
-  /**
-   * Load a script in the (remote) frame. aURL must be the absolute URL.
-   * data: URLs are also supported. For example data:,dump("foo\n");
-   * If aAllowDelayedLoad is true, script will be loaded when the
-   * remote frame becomes available. Otherwise the script will be loaded
-   * only if the frame is already available.
-   */
-  void loadFrameScript(in AString aURL, in boolean aAllowDelayedLoad,
-                       [optional] in boolean aRunInGlobalScope);
-
-  /**
-   * Removes aURL from the list of scripts which support delayed load.
-   */
-  void removeDelayedFrameScript(in AString aURL);
-
-  /**
-   * Returns all delayed scripts that will be loaded once a (remote)
-   * frame becomes available. The return value is a list of pairs
-   * [<URL>, <WasLoadedInGlobalScope>].
-   */
-  [implicit_jscontext]
-  jsval getDelayedFrameScripts();
-};
-
-[uuid(7e1e1a20-b24f-11e4-ab27-0800200c9a66)]
-interface nsIProcessScriptLoader : nsISupports
-{
-  /**
-   * Load a script in the (possibly remote) process. aURL must be the absolute URL.
-   * data: URLs are also supported. For example data:,dump("foo\n");
-   * If aAllowDelayedLoad is true, script will be loaded when the
-   * remote frame becomes available. Otherwise the script will be loaded
-   * only if the frame is already available.
-   */
-  void loadProcessScript(in AString aURL, in boolean aAllowDelayedLoad);
-
-  /**
-   * Removes aURL from the list of scripts which support delayed load.
-   */
-  void removeDelayedProcessScript(in AString aURL);
-
-  /**
-   * Returns all delayed scripts that will be loaded once a (remote)
-   * frame becomes available. The return value is a list of URLs.
-   */
-  [implicit_jscontext]
-  jsval getDelayedProcessScripts();
-};
-
-[uuid(5b390753-abb3-49b0-ae3b-b803dab58144)]
-interface nsIGlobalProcessScriptLoader : nsIProcessScriptLoader
-{
-  /**
-   * Allows the parent process to set the initial process data for
-   * new, not-yet-created child processes. This attribute should only
-   * be used by the global parent process message manager. When a new
-   * process is created, it gets a copy of this data (via structured
-   * cloning). It can access the data via the initialProcessData
-   * attribute of its childprocessmessagemanager.
-   *
-   * This value will always be a JS object. Different users are
-   * expected to set properties on this object. The property name
-   * should be unique enough that other Gecko consumers won't
-   * accidentally choose it.
-   */
-  [implicit_jscontext]
-  readonly attribute jsval initialProcessData;
-};
--- a/dom/base/nsInProcessTabChildGlobal.cpp
+++ b/dom/base/nsInProcessTabChildGlobal.cpp
@@ -36,17 +36,17 @@ nsInProcessTabChildGlobal::DoSendBlockin
   SameProcessMessageQueue* queue = SameProcessMessageQueue::Get();
   queue->Flush();
 
   if (mChromeMessageManager) {
     SameProcessCpowHolder cpows(JS::RootingContext::get(aCx), aCpows);
     RefPtr<nsFrameMessageManager> mm = mChromeMessageManager;
     RefPtr<nsFrameLoader> fl = GetFrameLoader();
     mm->ReceiveMessage(mOwner, fl, aMessage, true, &aData, &cpows, aPrincipal,
-                       aRetVal);
+                       aRetVal, IgnoreErrors());
   }
   return true;
 }
 
 class nsAsyncMessageToParent : public nsSameProcessAsyncMessageBase,
                                public SameProcessMessageQueue::Runnable
 {
 public:
@@ -111,21 +111,21 @@ nsInProcessTabChildGlobal::nsInProcessTa
 nsInProcessTabChildGlobal::~nsInProcessTabChildGlobal()
 {
   mAnonymousGlobalScopes.Clear();
   mozilla::DropJSObjects(this);
 }
 
 // This method isn't automatically forwarded safely because it's notxpcom, so
 // the IDL binding doesn't know what value to return.
-NS_IMETHODIMP_(bool)
+void
 nsInProcessTabChildGlobal::MarkForCC()
 {
   MarkScopesForCC();
-  return MessageManagerGlobal::MarkForCC();
+  MessageManagerGlobal::MarkForCC();
 }
 
 nsresult
 nsInProcessTabChildGlobal::Init()
 {
 #ifdef DEBUG
   nsresult rv =
 #endif
@@ -155,19 +155,17 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_IN
                                                 DOMEventTargetHelper)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mMessageManager)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocShell)
   tmp->nsMessageManagerScriptExecutor::Unlink();
   tmp->UnlinkHostObjectURIs();
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsInProcessTabChildGlobal)
-  NS_INTERFACE_MAP_ENTRY(nsIMessageListenerManager)
   NS_INTERFACE_MAP_ENTRY(nsIMessageSender)
-  NS_INTERFACE_MAP_ENTRY(nsISyncMessageSender)
   NS_INTERFACE_MAP_ENTRY(nsIContentFrameMessageManager)
   NS_INTERFACE_MAP_ENTRY(nsIInProcessContentFrameMessageManager)
   NS_INTERFACE_MAP_ENTRY(nsIScriptObjectPrincipal)
   NS_INTERFACE_MAP_ENTRY(nsIGlobalObject)
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(nsInProcessTabChildGlobal, DOMEventTargetHelper)
@@ -199,46 +197,23 @@ nsInProcessTabChildGlobal::GetContent(Er
 {
   nsCOMPtr<nsPIDOMWindowOuter> content;
   if (mDocShell) {
     content = mDocShell->GetWindow();
   }
   return content.forget();
 }
 
-NS_IMETHODIMP
-nsInProcessTabChildGlobal::GetContent(mozIDOMWindowProxy** aContent)
-{
-  ErrorResult rv;
-  *aContent = GetContent(rv).take();
-  return rv.StealNSResult();
-}
-
-NS_IMETHODIMP
-nsInProcessTabChildGlobal::GetDocShell(nsIDocShell** aDocShell)
-{
-  ErrorResult rv;
-  *aDocShell = GetDocShell(rv).take();
-  return rv.StealNSResult();
-}
-
 already_AddRefed<nsIEventTarget>
 nsInProcessTabChildGlobal::GetTabEventTarget()
 {
   nsCOMPtr<nsIEventTarget> target = GetMainThreadEventTarget();
   return target.forget();
 }
 
-NS_IMETHODIMP
-nsInProcessTabChildGlobal::GetTabEventTarget(nsIEventTarget** aTarget)
-{
-  *aTarget = GetTabEventTarget().take();
-  return NS_OK;
-}
-
 void
 nsInProcessTabChildGlobal::FireUnloadEvent()
 {
   // We're called from nsDocument::MaybeInitializeFinalizeFrameLoaders, so it
   // should be safe to run script.
   MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
 
   // Don't let the unload event propagate to chrome event handlers.
--- a/dom/base/nsInProcessTabChildGlobal.h
+++ b/dom/base/nsInProcessTabChildGlobal.h
@@ -33,25 +33,25 @@ class nsInProcessTabChildGlobal : public
                                   public nsIInProcessContentFrameMessageManager,
                                   public nsIGlobalObject,
                                   public nsIScriptObjectPrincipal,
                                   public nsSupportsWeakReference,
                                   public mozilla::dom::ipc::MessageManagerCallback
 {
   typedef mozilla::dom::ipc::StructuredCloneData StructuredCloneData;
 
-  using mozilla::dom::ipc::MessageManagerCallback::GetProcessMessageManager;
-
 public:
   nsInProcessTabChildGlobal(nsIDocShell* aShell, nsIContent* aOwner,
                             nsFrameMessageManager* aChrome);
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(nsInProcessTabChildGlobal,
                                                          mozilla::DOMEventTargetHelper)
 
+  void MarkForCC();
+
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override
   {
     MOZ_CRASH("We should never get here!");
   }
   virtual bool WrapGlobalObject(JSContext* aCx,
                                 JS::CompartmentOptions& aOptions,
                                 JS::MutableHandle<JSObject*> aReflector) override;
@@ -61,24 +61,23 @@ public:
   virtual already_AddRefed<nsIDocShell>
     GetDocShell(mozilla::ErrorResult& aError) override
   {
     nsCOMPtr<nsIDocShell> docShell(mDocShell);
     return docShell.forget();
   }
   virtual already_AddRefed<nsIEventTarget> GetTabEventTarget() override;
 
-  NS_FORWARD_SAFE_NSIMESSAGELISTENERMANAGER(mMessageManager)
   NS_FORWARD_SAFE_NSIMESSAGESENDER(mMessageManager)
-  NS_FORWARD_SAFE_NSISYNCMESSAGESENDER(mMessageManager);
-  NS_FORWARD_SAFE_NSIMESSAGEMANAGERGLOBAL(mMessageManager)
   NS_DECL_NSICONTENTFRAMEMESSAGEMANAGER
 
   NS_DECL_NSIINPROCESSCONTENTFRAMEMESSAGEMANAGER
 
+  void CacheFrameLoader(nsFrameLoader* aFrameLoader);
+
   /**
    * MessageManagerCallback methods that we override.
    */
   virtual bool DoSendBlockingMessage(JSContext* aCx,
                                       const nsAString& aMessage,
                                       StructuredCloneData& aData,
                                       JS::Handle<JSObject *> aCpows,
                                       nsIPrincipal* aPrincipal,
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -238,17 +238,17 @@ namespace xpc {
 JSObject*
 FindExceptionStackForConsoleReport(nsPIDOMWindowInner* win,
                                    JS::HandleValue exceptionValue)
 {
   if (!exceptionValue.isObject()) {
     return nullptr;
   }
 
-  if (win && win->InnerObjectsFreed()) {
+  if (win && win->AsGlobal()->IsDying()) {
     // Pretend like we have no stack, so we don't end up keeping the global
     // alive via the stack.
     return nullptr;
   }
 
   JS::RootingContext* rcx = RootingCx();
   JS::RootedObject exceptionObject(rcx, &exceptionValue.toObject());
   JSObject* stackObject = JS::ExceptionStackOrNull(exceptionObject);
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -244,24 +244,16 @@ public:
    * or content in that document) has a Pointerenter/leave event listener.
    */
   void SetHasPointerEnterLeaveEventListeners()
   {
     mMayHavePointerEnterLeaveEventListener = true;
   }
 
   /**
-   * Check whether this has had inner objects freed.
-   */
-  bool InnerObjectsFreed() const
-  {
-    return mInnerObjectsFreed;
-  }
-
-  /**
    * Check whether this window is a secure context.
    */
   bool IsSecureContext() const;
   bool IsSecureContextIfOpenerIgnored() const;
 
   // Calling suspend should prevent any asynchronous tasks from
   // executing javascript for this window.  This means setTimeout,
   // requestAnimationFrame, and events should not be fired. Suspending
@@ -656,20 +648,16 @@ protected:
   bool mIsDocumentLoaded;
   bool mIsHandlingResizeEvent;
   bool mMayHavePaintEventListener;
   bool mMayHaveTouchEventListener;
   bool mMayHaveSelectionChangeEventListener;
   bool mMayHaveMouseEnterLeaveEventListener;
   bool mMayHavePointerEnterLeaveEventListener;
 
-  // Used to detect whether we have called FreeInnerObjects() (e.g. to ensure
-  // that a call to ResumeTimeouts() after FreeInnerObjects() does nothing).
-  bool mInnerObjectsFreed;
-
   bool mAudioCaptured;
 
   // Our inner window's outer window.
   nsCOMPtr<nsPIDOMWindowOuter> mOuterWindow;
 
   // the element within the document that is currently focused when this
   // window is active
   nsCOMPtr<nsIContent> mFocusedNode;
--- a/dom/base/nsXHTMLContentSerializer.cpp
+++ b/dom/base/nsXHTMLContentSerializer.cpp
@@ -28,16 +28,18 @@
 #include "nsCRT.h"
 #include "nsContentUtils.h"
 #include "nsIScriptElement.h"
 #include "nsStubMutationObserver.h"
 #include "nsAttrName.h"
 #include "nsComputedDOMStyle.h"
 #include "mozilla/dom/Element.h"
 
+using namespace mozilla;
+
 static const int32_t kLongLineLen = 128;
 
 #define kXMLNS "xmlns"
 
 nsresult
 NS_NewXHTMLContentSerializer(n