Backed out 2 changesets (bug 1563062) for Browser-chrome failure in toolkit/mozapps/extensions/test/browser/browser_gmpProvider.js. CLOSED TREE
authorDorel Luca <dluca@mozilla.com>
Wed, 10 Jul 2019 01:29:53 +0300
changeset 482020 2786509371657776451adaf95de7139962318102
parent 482019 4f0a64853706007f23f6b0e65304218e90a6c706
child 482021 9ef7def8e86717cdd3131e9bfafb3ff194748eed
push id113647
push useraciure@mozilla.com
push dateWed, 10 Jul 2019 09:46:39 +0000
treeherdermozilla-inbound@f3a387c13e2c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1563062
milestone70.0a1
backs out59255dd51e9abbdb7d8239cac317a43efe53b495
46ad01c26df8ac85e5062433d420e084556400af
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out 2 changesets (bug 1563062) for Browser-chrome failure in toolkit/mozapps/extensions/test/browser/browser_gmpProvider.js. CLOSED TREE Backed out changeset 59255dd51e9a (bug 1563062) Backed out changeset 46ad01c26df8 (bug 1563062)
browser/base/content/test/webextensions/browser.ini
browser/base/content/test/webextensions/browser_extension_sideloading.js
browser/base/content/test/webextensions/browser_update_checkForUpdates.js
browser/base/content/test/webextensions/browser_update_findUpdates.js
browser/base/content/test/webextensions/head.js
browser/components/enterprisepolicies/tests/browser/browser_policy_disable_flash_plugin.js
browser/components/enterprisepolicies/tests/browser/browser_policy_extensions.js
browser/components/enterprisepolicies/tests/browser/head.js
browser/components/extensions/test/browser/browser_ext_browserAction_contextMenu.js
browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage.js
toolkit/mozapps/extensions/content/abuse-report-frame.js
toolkit/mozapps/extensions/content/extensions.css
toolkit/mozapps/extensions/content/extensions.js
toolkit/mozapps/extensions/content/extensions.xml
toolkit/mozapps/extensions/content/extensions.xul
toolkit/mozapps/extensions/jar.mn
toolkit/mozapps/extensions/test/browser/browser.ini
toolkit/mozapps/extensions/test/browser/browser_CTP_plugins.js
toolkit/mozapps/extensions/test/browser/browser_bug562890.js
toolkit/mozapps/extensions/test/browser/browser_bug562899.js
toolkit/mozapps/extensions/test/browser/browser_bug562992.js
toolkit/mozapps/extensions/test/browser/browser_bug580298.js
toolkit/mozapps/extensions/test/browser/browser_bug587970.js
toolkit/mozapps/extensions/test/browser/browser_bug590347.js
toolkit/mozapps/extensions/test/browser/browser_bug591663.js
toolkit/mozapps/extensions/test/browser/browser_bug618502.js
toolkit/mozapps/extensions/test/browser/browser_details.js
toolkit/mozapps/extensions/test/browser/browser_discovery.js
toolkit/mozapps/extensions/test/browser/browser_discovery_clientid.js
toolkit/mozapps/extensions/test/browser/browser_dragdrop.js
toolkit/mozapps/extensions/test/browser/browser_extension_sideloading_permission.js
toolkit/mozapps/extensions/test/browser/browser_globalwarnings.js
toolkit/mozapps/extensions/test/browser/browser_gmpProvider.js
toolkit/mozapps/extensions/test/browser/browser_history_navigation.js
toolkit/mozapps/extensions/test/browser/browser_html_abuse_report.js
toolkit/mozapps/extensions/test/browser/browser_html_detail_view.js
toolkit/mozapps/extensions/test/browser/browser_html_discover_view.js
toolkit/mozapps/extensions/test/browser/browser_html_discover_view_clientid.js
toolkit/mozapps/extensions/test/browser/browser_html_list_view.js
toolkit/mozapps/extensions/test/browser/browser_html_list_view_recommendations.js
toolkit/mozapps/extensions/test/browser/browser_html_message_bar.js
toolkit/mozapps/extensions/test/browser/browser_html_named_deck.js
toolkit/mozapps/extensions/test/browser/browser_html_options_ui.js
toolkit/mozapps/extensions/test/browser/browser_html_options_ui_in_tab.js
toolkit/mozapps/extensions/test/browser/browser_html_plugins.js
toolkit/mozapps/extensions/test/browser/browser_html_recent_updates.js
toolkit/mozapps/extensions/test/browser/browser_html_recommendations.js
toolkit/mozapps/extensions/test/browser/browser_html_updates.js
toolkit/mozapps/extensions/test/browser/browser_html_warning_messages.js
toolkit/mozapps/extensions/test/browser/browser_inlinesettings_browser.js
toolkit/mozapps/extensions/test/browser/browser_interaction_telemetry.js
toolkit/mozapps/extensions/test/browser/browser_langpack_signing.js
toolkit/mozapps/extensions/test/browser/browser_legacy.js
toolkit/mozapps/extensions/test/browser/browser_legacy_pre57.js
toolkit/mozapps/extensions/test/browser/browser_list.js
toolkit/mozapps/extensions/test/browser/browser_manualupdates.js
toolkit/mozapps/extensions/test/browser/browser_plugin_enabled_state_locked.js
toolkit/mozapps/extensions/test/browser/browser_pluginprefs.js
toolkit/mozapps/extensions/test/browser/browser_pluginprefs_is_not_disabled.js
toolkit/mozapps/extensions/test/browser/browser_reinstall.js
toolkit/mozapps/extensions/test/browser/browser_sorting.js
toolkit/mozapps/extensions/test/browser/browser_sorting_plugins.js
toolkit/mozapps/extensions/test/browser/browser_tabsettings.js
toolkit/mozapps/extensions/test/browser/browser_theme_previews.js
toolkit/mozapps/extensions/test/browser/browser_types.js
toolkit/mozapps/extensions/test/browser/browser_uninstalling.js
toolkit/mozapps/extensions/test/browser/browser_updateid.js
toolkit/mozapps/extensions/test/browser/browser_webext_icon.js
toolkit/mozapps/extensions/test/browser/browser_webext_incognito.js
toolkit/mozapps/extensions/test/browser/browser_webext_options.js
toolkit/mozapps/extensions/test/browser/browser_webext_options_addon_reload.js
toolkit/mozapps/extensions/test/browser/head.js
toolkit/themes/shared/in-content/common.inc.css
--- a/browser/base/content/test/webextensions/browser.ini
+++ b/browser/base/content/test/webextensions/browser.ini
@@ -24,9 +24,10 @@ support-files =
 [browser_permissions_local_file.js]
 [browser_permissions_mozAddonManager.js]
 [browser_permissions_optional.js]
 skip-if = !e10s
 [browser_permissions_pointerevent.js]
 [browser_permissions_unsigned.js]
 skip-if = require_signing
 [browser_update_checkForUpdates.js]
+[browser_update_findUpdates.js]
 [browser_update_interactive_noprompt.js]
--- a/browser/base/content/test/webextensions/browser_extension_sideloading.js
+++ b/browser/base/content/test/webextensions/browser_extension_sideloading.js
@@ -33,46 +33,82 @@ async function createWebExtension(detail
 }
 
 function promiseEvent(eventEmitter, event) {
   return new Promise(resolve => {
     eventEmitter.once(event, resolve);
   });
 }
 
-function getAddonElement(managerWindow, addonId) {
-  const { contentDocument: doc } = managerWindow.document.getElementById(
-    "html-view-browser"
-  );
-  return BrowserTestUtils.waitForCondition(
-    () => doc.querySelector(`addon-card[addon-id="${addonId}"]`),
-    `Found entry for sideload extension addon "${addonId}" in HTML about:addons`
-  );
+async function getAddonElement(managerWindow, addonId) {
+  if (managerWindow.useHtmlViews) {
+    // about:addons is using the new HTML page.
+    const { contentDocument: doc } = managerWindow.document.getElementById(
+      "html-view-browser"
+    );
+    const card = await BrowserTestUtils.waitForCondition(
+      () => doc.querySelector(`addon-card[addon-id="${addonId}"]`),
+      `Found entry for sideload extension addon "${addonId}" in HTML about:addons`
+    );
+
+    return card;
+  }
+
+  // about:addons is using the XUL-based views.
+  let list = managerWindow.document.getElementById("addon-list");
+  // Make sure XBL bindings are applied
+  list.clientHeight;
+  const item = Array.from(list.children).find(_item => _item.value == addonId);
+  ok(item, "Found entry for sideloaded extension in about:addons");
+
+  return item;
 }
 
 function assertDisabledSideloadedAddonElement(managerWindow, addonElement) {
-  const doc = addonElement.ownerDocument;
-  const enableBtn = addonElement.querySelector('[action="toggle-disabled"]');
-  is(
-    doc.l10n.getAttributes(enableBtn).id,
-    "enable-addon-button",
-    "The button has the enable label"
-  );
+  if (managerWindow.useHtmlViews) {
+    // about:addons is using the new HTML page.
+    const doc = addonElement.ownerDocument;
+    const enableBtn = addonElement.querySelector('[action="toggle-disabled"]');
+    is(
+      doc.l10n.getAttributes(enableBtn).id,
+      "enable-addon-button",
+      "The button has the enable label"
+    );
+  } else {
+    addonElement.scrollIntoView({ behavior: "instant" });
+    ok(
+      BrowserTestUtils.is_visible(addonElement._enableBtn),
+      "Enable button is visible for sideloaded extension"
+    );
+    ok(
+      BrowserTestUtils.is_hidden(addonElement._disableBtn),
+      "Disable button is not visible for sideloaded extension"
+    );
+  }
 }
 
 function clickEnableExtension(managerWindow, addonElement) {
-  addonElement.querySelector('[action="toggle-disabled"]').click();
+  if (managerWindow.useHtmlViews) {
+    addonElement.querySelector('[action="toggle-disabled"]').click();
+  } else {
+    BrowserTestUtils.synthesizeMouseAtCenter(
+      addonElement._enableBtn,
+      {},
+      gBrowser.selectedBrowser
+    );
+  }
 }
 
-add_task(async function test_sideloading() {
+async function test_sideloading({ useHtmlViews }) {
   const DEFAULT_ICON_URL =
     "chrome://mozapps/skin/extensions/extensionGeneric.svg";
 
   await SpecialPowers.pushPrefEnv({
     set: [
+      ["extensions.htmlaboutaddons.enabled", useHtmlViews],
       ["xpinstall.signatures.required", false],
       ["extensions.autoDisableScopes", 15],
       ["extensions.ui.ignoreUnsigned", true],
       ["extensions.allowPrivateBrowsingByDefault", false],
     ],
   });
 
   const ID1 = "addon1@tests.mozilla.org";
@@ -395,9 +431,17 @@ add_task(async function test_sideloading
     );
   }
 
   is(
     collectedEventsAddon2.length,
     expectedEventsAddon2.length,
     "Got the expected number of telemetry events for addon2"
   );
+}
+
+add_task(async function test_xul_aboutaddons_sideloading() {
+  await test_sideloading({ useHtmlViews: false });
 });
+
+add_task(async function test_html_aboutaddons_sideloading() {
+  await test_sideloading({ useHtmlViews: true });
+});
--- a/browser/base/content/test/webextensions/browser_update_checkForUpdates.js
+++ b/browser/base/content/test/webextensions/browser_update_checkForUpdates.js
@@ -8,10 +8,34 @@ function checkAll(win) {
         resolve();
       },
     };
     Services.obs.addObserver(observer, "EM-update-check-finished");
   });
 }
 
 // Test "Check for Updates" with both auto-update settings
-add_task(() => interactiveUpdateTest(true, checkAll));
-add_task(() => interactiveUpdateTest(false, checkAll));
+async function test_check_for_updates() {
+  info("Test 'Check for Updates' with auto-update true");
+  await interactiveUpdateTest(true, checkAll);
+  info("Test 'Check for Updates' with auto-update false");
+  await interactiveUpdateTest(false, checkAll);
+}
+
+add_task(async function test_xul_aboutaddons() {
+  await SpecialPowers.pushPrefEnv({
+    set: [["extensions.htmlaboutaddons.enabled", false]],
+  });
+
+  await test_check_for_updates();
+
+  await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function test_html_aboutaddons() {
+  await SpecialPowers.pushPrefEnv({
+    set: [["extensions.htmlaboutaddons.enabled", true]],
+  });
+
+  await test_check_for_updates();
+
+  await SpecialPowers.popPrefEnv();
+});
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/webextensions/browser_update_findUpdates.js
@@ -0,0 +1,32 @@
+// Invoke an invidual extension's "Find Updates" menu item
+function checkOne(win, addon) {
+  win.gViewController.doCommand("cmd_findItemUpdates", addon);
+}
+
+// Test "Find Updates" with both auto-update settings
+async function test_find_updates() {
+  info("Test 'Find Updates' with auto-update true");
+  await interactiveUpdateTest(true, checkOne);
+  info("Test 'Find Updates' with auto-update false");
+  await interactiveUpdateTest(false, checkOne);
+}
+
+add_task(async function test_xul_aboutaddons() {
+  await SpecialPowers.pushPrefEnv({
+    set: [["extensions.htmlaboutaddons.enabled", false]],
+  });
+
+  await test_find_updates();
+
+  await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function test_html_aboutaddons() {
+  await SpecialPowers.pushPrefEnv({
+    set: [["extensions.htmlaboutaddons.enabled", true]],
+  });
+
+  await test_find_updates();
+
+  await SpecialPowers.popPrefEnv();
+});
--- a/browser/base/content/test/webextensions/head.js
+++ b/browser/base/content/test/webextensions/head.js
@@ -465,27 +465,39 @@ async function interactiveUpdateTest(aut
       });
     }
 
     let promise = checkFn(win, addon);
 
     if (manualUpdatePromise) {
       await manualUpdatePromise;
 
-      const availableUpdates = win.document.getElementById(
-        "updates-manualUpdatesFound-btn"
-      );
-      availableUpdates.click();
-      let doc = win.getHtmlBrowser().contentDocument;
-      let card = await BrowserTestUtils.waitForCondition(() => {
-        return doc.querySelector(`addon-card[addon-id="${ID}"]`);
-      }, `Wait addon card for "${ID}"`);
-      let updateBtn = card.querySelector('panel-item[action="install-update"]');
-      ok(updateBtn, `Found update button for "${ID}"`);
-      updateBtn.click();
+      if (win.useHtmlViews) {
+        // about:addons is using the new HTML views.
+        const availableUpdates = win.document.getElementById(
+          "updates-manualUpdatesFound-btn"
+        );
+        availableUpdates.click();
+        let doc = win.getHtmlBrowser().contentDocument;
+        let card = await BrowserTestUtils.waitForCondition(() => {
+          return doc.querySelector(`addon-card[addon-id="${ID}"]`);
+        }, `Wait addon card for "${ID}"`);
+        let updateBtn = card.querySelector(
+          'panel-item[action="install-update"]'
+        );
+        ok(updateBtn, `Found update button for "${ID}"`);
+        updateBtn.click();
+      } else {
+        // about:addons is still using the legacy XUL views.
+        let list = win.document.getElementById("addon-list");
+        // Make sure we have XBL bindings
+        list.clientHeight;
+        let item = list.itemChildren.find(_item => _item.value == ID);
+        EventUtils.synthesizeMouseAtCenter(item._updateBtn, {}, win);
+      }
     }
 
     return { promise };
   }
 
   // Navigate away from the starting page to force about:addons to load
   // in a new tab during the tests below.
   BrowserTestUtils.loadURI(gBrowser.selectedBrowser, "about:robots");
--- a/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_flash_plugin.js
+++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_flash_plugin.js
@@ -6,48 +6,72 @@ const labelTextAskToActivate = "Ask to A
 const labelTextNeverActivate = "Never Activate";
 
 function restore_prefs() {
   Services.prefs.clearUserPref("plugin.state.flash");
 }
 registerCleanupFunction(restore_prefs);
 
 async function assert_flash_locked_status(win, locked, expectedLabelText) {
-  let addonCard = await BrowserTestUtils.waitForCondition(async () => {
-    let doc = win.getHtmlBrowser().contentDocument;
-    await win.htmlBrowserLoaded;
-    return doc.querySelector(`addon-card[addon-id*="Shockwave Flash"]`);
-  }, "Get HTML about:addons card for flash plugin");
+  if (win.useHtmlViews) {
+    // Tests while running on HTML about:addons page.
+    let addonCard = await BrowserTestUtils.waitForCondition(async () => {
+      let doc = win.getHtmlBrowser().contentDocument;
+      await win.htmlBrowserLoaded;
+      return doc.querySelector(`addon-card[addon-id*="Shockwave Flash"]`);
+    }, "Get HTML about:addons card for flash plugin");
 
-  const pluginOptions = addonCard.querySelector("plugin-options");
-  const pluginAction = pluginOptions.querySelector("panel-item[checked]");
-  ok(
-    pluginAction.textContent.includes(expectedLabelText),
-    `Got plugin action "${expectedLabelText}"`
-  );
+    const pluginOptions = addonCard.querySelector("plugin-options");
+    const pluginAction = pluginOptions.querySelector("panel-item[checked]");
+    ok(
+      pluginAction.textContent.includes(expectedLabelText),
+      `Got plugin action "${expectedLabelText}"`
+    );
 
-  // All other buttons (besides the checked one and the expand action)
-  // are expected to be disabled if locked is true.
-  for (const item of pluginOptions.querySelectorAll("panel-item")) {
-    const actionName = item.getAttribute("action");
-    if (actionName.includes("always")) {
-      ok(item.hidden, `Plugin action "${actionName}" should be hidden.`);
-    } else if (
-      !item.hasAttribute("checked") &&
-      actionName !== "expand" &&
-      actionName !== "preferences"
-    ) {
-      is(
-        item.shadowRoot.querySelector("button").disabled,
-        locked,
-        `Plugin action "${actionName}" should be ${
-          locked ? "disabled" : "enabled"
-        }`
-      );
+    // All other buttons (besides the checked one and the expand action)
+    // are expected to be disabled if locked is true.
+    for (const item of pluginOptions.querySelectorAll("panel-item")) {
+      const actionName = item.getAttribute("action");
+      if (actionName.includes("always")) {
+        ok(item.hidden, `Plugin action "${actionName}" should be hidden.`);
+      } else if (
+        !item.hasAttribute("checked") &&
+        actionName !== "expand" &&
+        actionName !== "preferences"
+      ) {
+        is(
+          item.shadowRoot.querySelector("button").disabled,
+          locked,
+          `Plugin action "${actionName}" should be ${
+            locked ? "disabled" : "enabled"
+          }`
+        );
+      }
     }
+  } else {
+    // Tests while running on XUL about:addons page.
+    let list = win.document.getElementById("addon-list");
+    let flashEntry = await BrowserTestUtils.waitForCondition(() => {
+      return list.getElementsByAttribute("name", "Shockwave Flash")[0];
+    }, "Get XUL about:addons entry for flash plugin");
+    let dropDown = win.document.getAnonymousElementByAttribute(
+      flashEntry,
+      "anonid",
+      "state-menulist"
+    );
+    is(
+      dropDown.label,
+      expectedLabelText,
+      "Flash setting text should match the expected value"
+    );
+    is(
+      dropDown.disabled,
+      locked,
+      "Flash controls disabled state should match policy locked state"
+    );
   }
 }
 
 async function test_flash_status({ expectedLabelText, locked }) {
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
   const win = await BrowserOpenAddonsMgr("addons://list/plugin");
 
   await assert_flash_locked_status(win, locked, expectedLabelText);
@@ -65,100 +89,118 @@ add_task(async function test_enabled() {
   await setupPolicyEngineWithJson({
     policies: {
       FlashPlugin: {
         Default: true,
       },
     },
   });
 
-  await test_flash_status({
-    expectedLabelText: labelTextAskToActivate,
-    locked: false,
-  });
+  const testCase = () =>
+    test_flash_status({
+      expectedLabelText: labelTextAskToActivate,
+      locked: false,
+    });
+  await testOnAboutAddonsType("XUL", testCase);
+  await testOnAboutAddonsType("HTML", testCase);
 
   restore_prefs();
 });
 
 add_task(async function test_enabled_locked() {
   await setupPolicyEngineWithJson({
     policies: {
       FlashPlugin: {
         Default: true,
         Locked: true,
       },
     },
   });
 
-  await test_flash_status({
-    expectedLabelText: labelTextAskToActivate,
-    locked: true,
-  });
+  const testCase = () =>
+    test_flash_status({
+      expectedLabelText: labelTextAskToActivate,
+      locked: true,
+    });
+  await testOnAboutAddonsType("XUL", testCase);
+  await testOnAboutAddonsType("HTML", testCase);
 
   restore_prefs();
 });
 
 add_task(async function test_disabled() {
   await setupPolicyEngineWithJson({
     policies: {
       FlashPlugin: {
         Default: false,
       },
     },
   });
 
-  await test_flash_status({
-    expectedLabelText: labelTextNeverActivate,
-    locked: false,
-  });
+  const testCase = () =>
+    test_flash_status({
+      expectedLabelText: labelTextNeverActivate,
+      locked: false,
+    });
+  await testOnAboutAddonsType("XUL", testCase);
+  await testOnAboutAddonsType("HTML", testCase);
 
   restore_prefs();
 });
 
 add_task(async function test_disabled_locked() {
   await setupPolicyEngineWithJson({
     policies: {
       FlashPlugin: {
         Default: false,
         Locked: true,
       },
     },
   });
 
-  await test_flash_status({
-    expectedLabelText: labelTextNeverActivate,
-    locked: true,
-  });
+  const testCase = () =>
+    test_flash_status({
+      expectedLabelText: labelTextNeverActivate,
+      locked: true,
+    });
+  await testOnAboutAddonsType("XUL", testCase);
+  await testOnAboutAddonsType("HTML", testCase);
 
   restore_prefs();
 });
 
 add_task(async function test_ask() {
   await setupPolicyEngineWithJson({
     policies: {
       FlashPlugin: {},
     },
   });
 
-  await test_flash_status({
-    expectedLabelText: labelTextAskToActivate,
-    locked: false,
-  });
+  const testCase = () =>
+    test_flash_status({
+      expectedLabelText: labelTextAskToActivate,
+      locked: false,
+    });
+  await testOnAboutAddonsType("XUL", testCase);
+  await testOnAboutAddonsType("HTML", testCase);
 
   restore_prefs();
 });
 
 add_task(async function test_ask_locked() {
   await setupPolicyEngineWithJson({
     policies: {
       FlashPlugin: {
         Locked: true,
       },
     },
   });
 
-  await test_flash_status({
-    expectedLabelText: labelTextAskToActivate,
-    locked: true,
-  });
+  const testCase = () =>
+    test_flash_status({
+      expectedLabelText: labelTextAskToActivate,
+      locked: true,
+    });
+  await testOnAboutAddonsType("XUL", testCase);
+  await testOnAboutAddonsType("HTML", testCase);
 
   restore_prefs();
 });
--- a/browser/components/enterprisepolicies/tests/browser/browser_policy_extensions.js
+++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_extensions.js
@@ -2,27 +2,60 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
 const ADDON_ID = "policytest@mozilla.com";
 const BASE_URL =
   "http://mochi.test:8888/browser/browser/components/enterprisepolicies/tests/browser";
 
 async function isExtensionLocked(win, addonID) {
-  let addonCard = await BrowserTestUtils.waitForCondition(async () => {
-    let doc = win.getHtmlBrowser().contentDocument;
-    await win.htmlBrowserLoaded;
-    return doc.querySelector(`addon-card[addon-id="${addonID}"]`);
-  }, `Get addon-card for "${addonID}"`);
-  let disableBtn = addonCard.querySelector(
-    'panel-item[action="toggle-disabled"]'
-  );
-  let removeBtn = addonCard.querySelector('panel-item[action="remove"]');
-  ok(removeBtn.hidden, "Remove button should be hidden");
-  ok(disableBtn.hidden, "Disable button should be hidden");
+  if (win.useHtmlViews) {
+    // Test on HTML about:addons page.
+    let addonCard = await BrowserTestUtils.waitForCondition(async () => {
+      let doc = win.getHtmlBrowser().contentDocument;
+      await win.htmlBrowserLoaded;
+      return doc.querySelector(`addon-card[addon-id="${addonID}"]`);
+    }, `Get addon-card for "${addonID}"`);
+    let disableBtn = addonCard.querySelector(
+      'panel-item[action="toggle-disabled"]'
+    );
+    let removeBtn = addonCard.querySelector('panel-item[action="remove"]');
+    ok(removeBtn.hidden, "Remove button should be hidden");
+    ok(disableBtn.hidden, "Disable button should be hidden");
+  } else {
+    // Test on XUL about:addons page.
+    const doc = win.document;
+    let list = doc.getElementById("addon-list");
+    let addonEntry = await BrowserTestUtils.waitForCondition(
+      () => list.getElementsByAttribute("value", addonID)[0],
+      `Get addon entry for "${addonID}"`
+    );
+    let disableBtn = doc.getAnonymousElementByAttribute(
+      addonEntry,
+      "anonid",
+      "disable-btn"
+    );
+    let removeBtn = doc.getAnonymousElementByAttribute(
+      addonEntry,
+      "anonid",
+      "remove-btn"
+    );
+    ok(removeBtn.hidden, "Remove button should be hidden");
+    ok(disableBtn.hidden, "Disable button should be hidden");
+  }
+}
+
+// This test case will run on both the XUL and HTML about:addons views.
+async function test_addon_locked() {
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+  const win = await BrowserOpenAddonsMgr("addons://list/extension");
+
+  await isExtensionLocked(win, ADDON_ID);
+
+  BrowserTestUtils.removeTab(tab);
 }
 
 add_task(async function test_addon_install() {
   let installPromise = wait_for_addon_install();
   await setupPolicyEngineWithJson({
     policies: {
       Extensions: {
         Install: [`${BASE_URL}/policytest_v0.1.xpi`],
@@ -37,23 +70,22 @@ add_task(async function test_addon_insta
 
   Assert.deepEqual(
     addon.installTelemetryInfo,
     { source: "enterprise-policy" },
     "Got the expected addon.installTelemetryInfo"
   );
 });
 
-add_task(async function test_addon_locked() {
-  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
-  const win = await BrowserOpenAddonsMgr("addons://list/extension");
+add_task(async function test_XUL_aboutaddons_addon_locked() {
+  await testOnAboutAddonsType("XUL", test_addon_locked);
+});
 
-  await isExtensionLocked(win, ADDON_ID);
-
-  BrowserTestUtils.removeTab(tab);
+add_task(async function test_HTML_aboutaddons_addon_locked() {
+  await testOnAboutAddonsType("HTML", test_addon_locked);
 });
 
 add_task(async function test_addon_reinstall() {
   // Test that uninstalling and reinstalling the same addon ID works as expected.
   // This can be used to update an addon.
 
   let uninstallPromise = wait_for_addon_uninstall();
   let installPromise = wait_for_addon_install();
--- a/browser/components/enterprisepolicies/tests/browser/head.js
+++ b/browser/components/enterprisepolicies/tests/browser/head.js
@@ -78,8 +78,28 @@ registerCleanupFunction(async function p
     Services.policies.status,
     Ci.nsIEnterprisePolicies.INACTIVE,
     "Engine is inactive at the end of the test"
   );
 
   EnterprisePolicyTesting.resetRunOnceState();
   PoliciesPrefTracker.stop();
 });
+
+async function testOnAboutAddonsType(type, fn) {
+  let useHtmlAboutAddons;
+  switch (type) {
+    case "XUL":
+      useHtmlAboutAddons = false;
+      break;
+    case "HTML":
+      useHtmlAboutAddons = true;
+      break;
+    default:
+      throw new Error(`Unknown about:addons type ${type}`);
+  }
+  await SpecialPowers.pushPrefEnv({
+    set: [["extensions.htmlaboutaddons.enabled", useHtmlAboutAddons]],
+  });
+  info(`Run tests on ${type} about:addons`);
+  await fn();
+  await SpecialPowers.popPrefEnv();
+}
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_contextMenu.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_contextMenu.js
@@ -7,16 +7,22 @@ const { TelemetryTestUtils } = ChromeUti
 );
 
 XPCOMUtils.defineLazyPreferenceGetter(
   this,
   "ABUSE_REPORT_ENABLED",
   "extensions.abuseReport.enabled",
   false
 );
+XPCOMUtils.defineLazyPreferenceGetter(
+  this,
+  "HTML_ABOUTADDONS_ENABLED",
+  "extensions.htmlaboutaddons.enabled",
+  false
+);
 
 let extData = {
   manifest: {
     permissions: ["contextMenus"],
     browser_action: {
       default_popup: "popup.html",
     },
   },
@@ -197,17 +203,17 @@ add_task(async function browseraction_co
     );
     is(
       manageExtension.hidden,
       !visible,
       `Manage Extension should be ${expected}`
     );
     is(
       reportExtension.hidden,
-      !ABUSE_REPORT_ENABLED || !visible,
+      !ABUSE_REPORT_ENABLED || !HTML_ABOUTADDONS_ENABLED || !visible,
       `Report Extension should be ${expected}`
     );
     is(
       separator.hidden,
       !visible,
       `Separator after Manage Extension should be ${expected}`
     );
   }
@@ -223,26 +229,31 @@ add_task(async function browseraction_co
       "about:addons",
       true
     );
     let manageExtension = menu.querySelector(
       ".customize-context-manageExtension"
     );
     await closeChromeContextMenu(menuId, manageExtension, win);
     let managerWindow = (await addonManagerPromise).linkedBrowser.contentWindow;
-
-    // Check the UI to make sure that the correct view is loaded.
-    is(
-      managerWindow.gViewController.currentViewId,
-      `addons://detail/${encodeURIComponent(id)}`,
-      "Expected extension details view in about:addons"
-    );
-    // In HTML about:addons, the default view does not show the inline
-    // options browser, so we should not receive an "options-loaded" event.
-    // (if we do, the test will fail due to the unexpected message).
+    if (managerWindow.useHtmlViews) {
+      // Check the UI to make sure that the correct view is loaded.
+      is(
+        managerWindow.gViewController.currentViewId,
+        `addons://detail/${encodeURIComponent(id)}`,
+        "Expected extension details view in about:addons"
+      );
+      // In HTML about:addons, the default view does not show the inline
+      // options browser, so we should not receive an "options-loaded" event.
+      // (if we do, the test will fail due to the unexpected message).
+    } else {
+      info("Waiting for inline options page in XUL about:addons");
+      // In XUL about:addons, the inline options page is shown by default.
+      await extension.awaitMessage("options-loaded");
+    }
 
     info(
       `Remove the opened tab, and await customize mode to be restored if necessary`
     );
     let tab = win.gBrowser.selectedTab;
     is(tab.linkedBrowser.currentURI.spec, "about:addons");
     if (customizing) {
       let customizationReady = BrowserTestUtils.waitForEvent(
@@ -559,17 +570,20 @@ add_task(async function browseraction_co
 });
 
 // This test case verify reporting an extension from the browserAction
 // context menu (when the browserAction is in the toolbox and in the
 // overwflow menu, and repeat the test with and without the customize
 // mode enabled).
 add_task(async function browseraction_contextmenu_report_extension() {
   SpecialPowers.pushPrefEnv({
-    set: [["extensions.abuseReport.enabled", true]],
+    set: [
+      ["extensions.htmlaboutaddons.enabled", true],
+      ["extensions.abuseReport.enabled", true],
+    ],
   });
   let win = await BrowserTestUtils.openNewBrowserWindow();
   let id = "addon_id@example.com";
   let name = "Bad Add-on";
   let buttonId = `${makeWidgetId(id)}-browser-action`;
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       name,
--- a/browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage.js
+++ b/browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage.js
@@ -57,17 +57,17 @@ async function loadExtension(options) {
     background: options.background,
   });
 
   await extension.startup();
 
   return extension;
 }
 
-add_task(async function run_test_inline_options() {
+async function run_test_inline_options() {
   let tab = await BrowserTestUtils.openNewForegroundTab(
     gBrowser,
     "http://example.com/"
   );
 
   let extension = await loadExtension({
     manifest: {
       applications: { gecko: { id: "inline_options@tests.mozilla.org" } },
@@ -251,16 +251,29 @@ add_task(async function run_test_inline_
 
   extension.sendMessage("ports-done");
 
   await extension.awaitFinish("options-ui");
 
   await extension.unload();
 
   BrowserTestUtils.removeTab(tab);
+}
+
+add_task(async function test_inline_options() {
+  for (let htmlEnabled of [false, true]) {
+    info(
+      `Test options opened inline ${htmlEnabled ? "HTML" : "XUL"} about:addons`
+    );
+    await SpecialPowers.pushPrefEnv({
+      set: [["extensions.htmlaboutaddons.enabled", htmlEnabled]],
+    });
+    await run_test_inline_options();
+    await SpecialPowers.popPrefEnv();
+  }
 });
 
 add_task(async function test_tab_options() {
   info(`Test options opened in a tab`);
 
   let tab = await BrowserTestUtils.openNewForegroundTab(
     gBrowser,
     "http://example.com/"
--- a/toolkit/mozapps/extensions/content/abuse-report-frame.js
+++ b/toolkit/mozapps/extensions/content/abuse-report-frame.js
@@ -1,11 +1,11 @@
 "use strict";
 
-/* globals MozXULElement, Services, getHtmlBrowser, htmlBrowserLoaded */
+/* globals MozXULElement, Services, useHtmlViews, getHtmlBrowser, htmlBrowserLoaded */
 
 {
   const ABUSE_REPORT_ENABLED = Services.prefs.getBoolPref(
     "extensions.abuseReport.enabled",
     false
   );
   const ABUSE_REPORT_FRAME_URL =
     "chrome://mozapps/content/extensions/abuse-report-frame.html";
@@ -261,17 +261,17 @@
     set reportEntryPoint(value) {
       this.setAttribute("report-entry-point", value);
     }
   }
 
   // If the html about:addons and the abuse report are both enabled, register
   // the custom XUL WebComponent and append it to the XUL stack element
   // (if not registered the element will be just a dummy hidden box)
-  if (ABUSE_REPORT_ENABLED) {
+  if (useHtmlViews && ABUSE_REPORT_ENABLED) {
     customElements.define(
       "addon-abuse-report-xulframe",
       AddonAbuseReportsXULFrame
     );
   }
 
   // Helper method exported into the about:addons global, used to open the
   // abuse report panel from outside of the about:addons page
--- a/toolkit/mozapps/extensions/content/extensions.css
+++ b/toolkit/mozapps/extensions/content/extensions.css
@@ -13,25 +13,135 @@ xhtml|link {
   -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#category");
 }
 
 .sidebar-footer-button > .text-link {
   margin-top: 0;
   margin-bottom: 0;
 }
 
+.addon[status="installed"] {
+  -moz-box-orient: vertical;
+  -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#addon-generic");
+}
+
+.addon[status="installing"] {
+  -moz-box-orient: vertical;
+  -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#addon-installing");
+}
+
+.addon[pending="uninstall"] {
+  -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#addon-uninstalled");
+}
+
+.creator {
+  -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#creator-link");
+}
+
+.meta-rating {
+  -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#rating");
+}
+
+.download-progress, .download-progress[mode="undetermined"] {
+  -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#download-progress");
+}
+
+.install-status {
+  -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#install-status");
+}
+
+.detail-row {
+  -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#detail-row");
+}
 
 .text-list {
   white-space: pre-line;
 }
 
+row[unsupported="true"] {
+  display: none;
+}
+
+#addonitem-popup > menuitem[disabled="true"] {
+  display: none;
+}
+
+#addonitem-popup[addontype="theme"] > #menuitem_enableItem,
+#addonitem-popup[addontype="theme"] > #menuitem_disableItem,
+#addonitem-popup:not([addontype="theme"]) > #menuitem_enableTheme,
+#addonitem-popup:not([addontype="theme"]) > #menuitem_disableTheme {
+  display: none;
+}
+
+#show-disabled-unsigned-extensions .button-text {
+  margin-inline-start: 3px !important;
+  margin-inline-end: 2px !important;
+}
+
 #header-searching:not([active]) {
   visibility: hidden;
 }
 
+#detail-view {
+  overflow: auto;
+}
+
+.addon:not([notification="warning"]) .warning,
+.addon:not([notification="error"]) .error,
+.addon:not([notification="info"]) .info,
+.addon:not([pending]) .pending,
+.addon:not([upgrade="true"]) .update-postfix,
+.addon[active="true"] .disabled-postfix,
+.addon[pending="install"] .update-postfix,
+.addon[pending="install"] .disabled-postfix,
+.addon[legacy="false"] .legacy-warning,
+#detail-view:not([notification="warning"]) .warning,
+#detail-view:not([notification="error"]) .error,
+#detail-view:not([notification="info"]) .info,
+#detail-view:not([pending]) .pending,
+#detail-view:not([upgrade="true"]) .update-postfix,
+#detail-view[active="true"] .disabled-postfix,
+#detail-view[legacy="false"] .legacy-warning,
+#detail-view[loading] .detail-view-container,
+#detail-view:not([loading]) .alert-container,
+.detail-row:not([value]),
+#legacy-list .addon .disabled-postfix {
+  display: none;
+}
+
+.addon .privateBrowsing-notice {
+  display: none;
+}
+.addon[privateBrowsing="true"] .privateBrowsing-notice-container {
+  /* 40px is width and margin of .icon-container */
+  margin-inline-start: 40px;
+}
+.addon[privateBrowsing="true"] .privateBrowsing-notice {
+  margin: 4px 0 0;
+  display: inline-block;
+}
+.addon[active="false"] .privateBrowsing-notice {
+  background-color: var(--purple-70-a40);
+}
+
+#addons-page:not([warning]) #list-view > .global-warning-container {
+  display: none;
+}
+#addon-list .date-updated,
+#legacy-list .date-updated {
+  display: none;
+}
+
+.view-pane:not(#updates-view) .addon .relnotes-toggle,
+.view-pane:not(#updates-view) .addon .include-update,
+#updates-view:not([updatetype="available"]) .addon .include-update,
+#updates-view[updatetype="available"] .addon .update-available-notice {
+  display: none;
+}
+
 #addons-page:not([warning]) .global-warning,
 #addons-page:not([warning="safemode"]) .global-warning-safemode,
 #addons-page:not([warning="checkcompatibility"]) .global-warning-checkcompatibility,
 #addons-page:not([warning="updatesecurity"]) .global-warning-updatesecurity {
   display: none;
 }
 
 /* Plugins aren't yet disabled by safemode (bug 342333),
@@ -40,28 +150,65 @@ xhtml|link {
 #addons-page[warning="safemode"] .view-pane[type="plugin"] .global-warning-container,
 #addons-page[warning="safemode"] #detail-view[loading="true"] .global-warning {
   display: none;
 }
 #addons-page .view-pane:not([type="plugin"]) #plugindeprecation-notice {
   display: none;
 }
 
+.list-view-notice {
+  margin-inline-start: 28px;
+  margin-bottom: 16px;
+}
+
+.list-view-notice > .message-bar {
+  width: 664px;
+}
+
 .html-alert-container > .message-bar {
   margin-bottom: 8px;
 }
 
 .html-global-warning-button {
   margin-inline: 0;
 }
 
+.addon .relnotes {
+  -moz-user-select: text;
+}
+#detail-name, #detail-desc, #detail-fulldesc {
+  -moz-user-select: text;
+  word-wrap: break-word;
+}
+
+#detail-name-container {
+  /* Set a max-width on this so the labels inside of this will wrap instead of
+     growing the card horizontally with long names. */
+  max-width: 580px;
+}
+
+/* Make sure we're not animating hidden images. See bug 623739. */
+#view-port:not([selectedIndex="0"]) #discover-view .loading,
+#discover-view:not([selectedIndex="0"]) .loading {
+  display: none;
+}
+
 /* Elements in unselected richlistitems cannot be focused */
 richlistitem:not([selected]) * {
   -moz-user-focus: ignore;
 }
 
 #header-search {
   width: 22em;
 }
 
+.discover-button[disabled="true"] {
+  display: none;
+}
+
+.view-pane:not(#legacy-view) .addon-control.replacement {
+  display: none;
+}
+
 #pluginFlashBlockingCheckbox .checkbox-label-box {
   display: none; /*see bug 1508724*/
 }
--- a/toolkit/mozapps/extensions/content/extensions.js
+++ b/toolkit/mozapps/extensions/content/extensions.js
@@ -2,17 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 /* import-globals-from ../../../content/contentAreaUtils.js */
 /* import-globals-from aboutaddonsCommon.js */
 /* globals ProcessingInstruction */
-/* exported loadView */
+/* exported gBrowser, loadView */
 
 const { DeferredTask } = ChromeUtils.import(
   "resource://gre/modules/DeferredTask.jsm"
 );
 const { AddonManager } = ChromeUtils.import(
   "resource://gre/modules/AddonManager.jsm"
 );
 const { AddonRepository } = ChromeUtils.import(
@@ -21,16 +21,41 @@ const { AddonRepository } = ChromeUtils.
 
 ChromeUtils.defineModuleGetter(
   this,
   "AMTelemetry",
   "resource://gre/modules/AddonManager.jsm"
 );
 ChromeUtils.defineModuleGetter(
   this,
+  "E10SUtils",
+  "resource://gre/modules/E10SUtils.jsm"
+);
+ChromeUtils.defineModuleGetter(
+  this,
+  "ExtensionParent",
+  "resource://gre/modules/ExtensionParent.jsm"
+);
+ChromeUtils.defineModuleGetter(
+  this,
+  "ExtensionPermissions",
+  "resource://gre/modules/ExtensionPermissions.jsm"
+);
+ChromeUtils.defineModuleGetter(
+  this,
+  "PluralForm",
+  "resource://gre/modules/PluralForm.jsm"
+);
+ChromeUtils.defineModuleGetter(
+  this,
+  "Preferences",
+  "resource://gre/modules/Preferences.jsm"
+);
+ChromeUtils.defineModuleGetter(
+  this,
   "ClientID",
   "resource://gre/modules/ClientID.jsm"
 );
 ChromeUtils.defineModuleGetter(
   this,
   "PrivateBrowsingUtils",
   "resource://gre/modules/PrivateBrowsingUtils.jsm"
 );
@@ -39,35 +64,68 @@ XPCOMUtils.defineLazyPreferenceGetter(
   this,
   "XPINSTALL_ENABLED",
   "xpinstall.enabled",
   true
 );
 
 XPCOMUtils.defineLazyPreferenceGetter(
   this,
+  "allowPrivateBrowsingByDefault",
+  "extensions.allowPrivateBrowsingByDefault",
+  true
+);
+
+XPCOMUtils.defineLazyPreferenceGetter(
+  this,
+  "SUPPORT_URL",
+  "app.support.baseURL",
+  "",
+  null,
+  val => Services.urlFormatter.formatURL(val)
+);
+XPCOMUtils.defineLazyPreferenceGetter(
+  this,
+  "useHtmlViews",
+  "extensions.htmlaboutaddons.enabled"
+);
+XPCOMUtils.defineLazyPreferenceGetter(
+  this,
   "useHtmlDiscover",
   "extensions.htmlaboutaddons.discover.enabled"
 );
 XPCOMUtils.defineLazyPreferenceGetter(
   this,
   "useNewAboutDebugging",
   "devtools.aboutdebugging.new-enabled"
 );
 
 const PREF_DISCOVERURL = "extensions.webservice.discoverURL";
 const PREF_DISCOVER_ENABLED = "extensions.getAddons.showPane";
 const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled";
 const PREF_GETADDONS_CACHE_ID_ENABLED =
   "extensions.%ID%.getAddons.cache.enabled";
 const PREF_UI_TYPE_HIDDEN = "extensions.ui.%TYPE%.hidden";
 const PREF_UI_LASTCATEGORY = "extensions.ui.lastCategory";
+const PREF_LEGACY_EXCEPTIONS = "extensions.legacy.exceptions";
+const PREF_LEGACY_ENABLED = "extensions.legacy.enabled";
+
+const LOADING_MSG_DELAY = 100;
+
+const UPDATES_RECENT_TIMESPAN = 2 * 24 * 3600000; // 2 days (in milliseconds)
 
 var gViewDefault = "addons://discover/";
 
+XPCOMUtils.defineLazyGetter(this, "extensionStylesheets", () => {
+  const { ExtensionParent } = ChromeUtils.import(
+    "resource://gre/modules/ExtensionParent.jsm"
+  );
+  return ExtensionParent.extensionStylesheets;
+});
+
 var gStrings = {};
 XPCOMUtils.defineLazyServiceGetter(
   gStrings,
   "bundleSvc",
   "@mozilla.org/intl/stringbundle;1",
   "nsIStringBundleService"
 );
 
@@ -89,16 +147,31 @@ XPCOMUtils.defineLazyGetter(gStrings, "d
 
 XPCOMUtils.defineLazyGetter(gStrings, "brandShortName", function() {
   return this.brand.GetStringFromName("brandShortName");
 });
 XPCOMUtils.defineLazyGetter(gStrings, "appVersion", function() {
   return Services.appinfo.version;
 });
 
+XPCOMUtils.defineLazyPreferenceGetter(
+  this,
+  "legacyWarningExceptions",
+  PREF_LEGACY_EXCEPTIONS,
+  "",
+  raw => raw.split(",")
+);
+XPCOMUtils.defineLazyPreferenceGetter(
+  this,
+  "legacyExtensionsEnabled",
+  PREF_LEGACY_ENABLED,
+  true,
+  () => gLegacyView.refreshVisibility()
+);
+
 document.addEventListener("load", initialize, true);
 window.addEventListener("unload", shutdown);
 
 var gPendingInitializations = 1;
 Object.defineProperty(this, "gIsInitializing", {
   get: () => gPendingInitializations > 0,
 });
 
@@ -110,16 +183,24 @@ function initialize(event) {
   }
   document.removeEventListener("load", initialize, true);
 
   let globalCommandSet = document.getElementById("globalCommandSet");
   globalCommandSet.addEventListener("command", function(event) {
     gViewController.doCommand(event.target.id);
   });
 
+  let viewCommandSet = document.getElementById("viewCommandSet");
+  viewCommandSet.addEventListener("commandupdate", function(event) {
+    gViewController.updateCommands();
+  });
+  viewCommandSet.addEventListener("command", function(event) {
+    gViewController.doCommand(event.target.id);
+  });
+
   let addonPage = document.getElementById("addons-page");
   addonPage.addEventListener("dragenter", function(event) {
     gDragDrop.onDragOver(event);
   });
   addonPage.addEventListener("dragover", function(event) {
     gDragDrop.onDragOver(event);
   });
   addonPage.addEventListener("drop", function(event) {
@@ -267,16 +348,31 @@ function recordSetUpdatePolicyTelemetry(
     updatePolicy.push("enabled");
   }
   recordActionTelemetry({
     action: "setUpdatePolicy",
     value: updatePolicy.join(","),
   });
 }
 
+function recordSetAddonUpdateTelemetry(addon) {
+  let updates = addon.applyBackgroundUpdates;
+  let updatePolicy = "";
+  if (updates == "1") {
+    updatePolicy = "default";
+  } else if (updates == "2") {
+    updatePolicy = "enabled";
+  }
+  recordActionTelemetry({
+    action: "setAddonUpdate",
+    value: updatePolicy,
+    addon,
+  });
+}
+
 function getCurrentViewName() {
   let view = gViewController.currentViewObj;
   let entries = Object.entries(gViewController.viewObjects);
   let viewIndex = entries.findIndex(([name, viewObj]) => {
     return viewObj == view;
   });
   if (viewIndex != -1) {
     return entries[viewIndex][0];
@@ -291,16 +387,43 @@ function loadView(aViewId) {
     // should be the initial history entry
 
     gViewController.loadInitialView(aViewId);
   } else {
     gViewController.loadView(aViewId);
   }
 }
 
+function isLegacyExtension(addon) {
+  let legacy = false;
+  if (addon.type == "extension" && !addon.isWebExtension) {
+    legacy = true;
+  }
+  if (addon.type == "theme") {
+    legacy = false;
+  }
+
+  if (
+    legacy &&
+    (addon.hidden || addon.signedState == AddonManager.SIGNEDSTATE_PRIVILEGED)
+  ) {
+    legacy = false;
+  }
+  // Exceptions that can slip through above: the default theme plus
+  // test pilot addons until we get SIGNEDSTATE_PRIVILEGED deployed.
+  if (legacy && legacyWarningExceptions.includes(addon.id)) {
+    legacy = false;
+  }
+  return legacy;
+}
+
+function isDisabledLegacy(addon) {
+  return !legacyExtensionsEnabled && isLegacyExtension(addon);
+}
+
 function isDiscoverEnabled() {
   if (
     Services.prefs.getPrefType(PREF_DISCOVERURL) == Services.prefs.PREF_INVALID
   ) {
     return false;
   }
 
   try {
@@ -506,16 +629,66 @@ var gEventManager = {
     }
 
     AddonManager.addManagerListener(this);
     AddonManager.addInstallListener(this);
     AddonManager.addAddonListener(this);
 
     this.refreshGlobalWarning();
     this.refreshAutoUpdateDefault();
+
+    var contextMenu = document.getElementById("addonitem-popup");
+    contextMenu.addEventListener("popupshowing", function() {
+      var addon = gViewController.currentViewObj.getSelectedAddon();
+      contextMenu.setAttribute("addontype", addon.type);
+
+      var menuSep = document.getElementById("addonitem-menuseparator");
+      var countMenuItemsBeforeSep = 0;
+      for (let child of contextMenu.children) {
+        if (child == menuSep) {
+          break;
+        }
+        if (
+          child.nodeName == "menuitem" &&
+          gViewController.isCommandEnabled(child.command)
+        ) {
+          countMenuItemsBeforeSep++;
+        }
+      }
+
+      // Hide the separator if there are no visible menu items before it
+      menuSep.hidden = countMenuItemsBeforeSep == 0;
+    });
+
+    let addonTooltip = document.getElementById("addonitem-tooltip");
+    addonTooltip.addEventListener("popupshowing", function() {
+      let addonItem = addonTooltip.triggerNode;
+      // The way the test triggers the tooltip the richlistitem is the
+      // tooltipNode but in normal use it is the anonymous node. This allows
+      // any case
+      if (addonItem.localName != "richlistitem") {
+        addonItem = document.getBindingParent(addonItem);
+      }
+
+      let tiptext = addonItem.getAttribute("name");
+
+      if (addonItem.mAddon) {
+        if (shouldShowVersionNumber(addonItem.mAddon)) {
+          tiptext +=
+            " " +
+            (addonItem.hasAttribute("upgrade")
+              ? addonItem.mManualUpdate.version
+              : addonItem.mAddon.version);
+        }
+      } else if (shouldShowVersionNumber(addonItem.mInstall)) {
+        tiptext += " " + addonItem.mInstall.version;
+      }
+
+      addonTooltip.label = tiptext;
+    });
   },
 
   shutdown() {
     AddonManager.removeManagerListener(this);
     AddonManager.removeInstallListener(this);
     AddonManager.removeAddonListener(this);
   },
 
@@ -657,24 +830,31 @@ var gViewController = {
   backButton: null,
 
   initialize() {
     this.viewPort = document.getElementById("view-port");
     this.headeredViews = document.getElementById("headered-views");
     this.headeredViewsDeck = document.getElementById("headered-views-content");
     this.backButton = document.getElementById("go-back");
 
+    this.viewObjects.legacy = gLegacyView;
     this.viewObjects.shortcuts = gShortcutsView;
 
-    this.viewObjects.list = htmlView("list");
-    this.viewObjects.detail = htmlView("detail");
-    this.viewObjects.updates = htmlView("updates");
-    // gUpdatesView still handles when the Available Updates category is
-    // shown. Include it in viewObjects so it gets initialized and shutdown.
-    this.viewObjects._availableUpdatesSidebar = gUpdatesView;
+    if (useHtmlViews) {
+      this.viewObjects.list = htmlView("list");
+      this.viewObjects.detail = htmlView("detail");
+      this.viewObjects.updates = htmlView("updates");
+      // gUpdatesView still handles when the Available Updates category is
+      // shown. Include it in viewObjects so it gets initialized and shutdown.
+      this.viewObjects._availableUpdatesSidebar = gUpdatesView;
+    } else {
+      this.viewObjects.list = gListView;
+      this.viewObjects.detail = gDetailView;
+      this.viewObjects.updates = gUpdatesView;
+    }
 
     if (useHtmlDiscover && isDiscoverEnabled()) {
       this.viewObjects.discover = htmlView("discover");
     } else {
       this.viewObjects.discover = gDiscoverView;
     }
 
     for (let type in this.viewObjects) {
@@ -1003,16 +1183,29 @@ var gViewController = {
       isEnabled() {
         return true;
       },
       doCommand() {
         gViewController.loadView("addons://updates/available");
       },
     },
 
+    cmd_showItemDetails: {
+      isEnabled(aAddon) {
+        return !!aAddon && gViewController.currentViewObj != gDetailView;
+      },
+      doCommand(aAddon, aScrollToPreferences) {
+        gViewController.loadView(
+          "addons://detail/" +
+            encodeURIComponent(aAddon.id) +
+            (aScrollToPreferences ? "/preferences" : "")
+        );
+      },
+    },
+
     cmd_findAllUpdates: {
       inProgress: false,
       isEnabled() {
         return !this.inProgress;
       },
       async doCommand() {
         this.inProgress = true;
         gViewController.updateCommand("cmd_findAllUpdates");
@@ -1113,16 +1306,192 @@ var gViewController = {
         recordActionTelemetry({ action: "checkForUpdates" });
 
         if (pendingChecks == 0) {
           updateStatus();
         }
       },
     },
 
+    cmd_findItemUpdates: {
+      isEnabled(aAddon) {
+        if (!aAddon) {
+          return false;
+        }
+        return hasPermission(aAddon, "upgrade");
+      },
+      doCommand(aAddon) {
+        var listener = {
+          onUpdateAvailable(aAddon, aInstall) {
+            gEventManager.delegateAddonEvent("onUpdateAvailable", [
+              aAddon,
+              aInstall,
+            ]);
+            attachUpdateHandler(aInstall);
+            if (AddonManager.shouldAutoUpdate(aAddon)) {
+              aInstall.install();
+            }
+          },
+          onNoUpdateAvailable(aAddon) {
+            gEventManager.delegateAddonEvent("onNoUpdateAvailable", [aAddon]);
+          },
+        };
+        gEventManager.delegateAddonEvent("onCheckingUpdate", [aAddon]);
+        aAddon.findUpdates(listener, AddonManager.UPDATE_WHEN_USER_REQUESTED);
+        recordActionTelemetry({ action: "checkForUpdate", addon: aAddon });
+      },
+    },
+
+    cmd_showItemPreferences: {
+      isEnabled(aAddon) {
+        if (!aAddon || (!aAddon.isActive && aAddon.type !== "plugin")) {
+          return false;
+        }
+        if (gViewController.currentViewObj == gDetailView) {
+          return aAddon.optionsType && !hasInlineOptions(aAddon);
+        }
+        return aAddon.type == "plugin" || aAddon.optionsType;
+      },
+      doCommand(aAddon) {
+        let inline = hasInlineOptions(aAddon);
+        let view = getCurrentViewName();
+
+        if (inline) {
+          gViewController.commands.cmd_showItemDetails.doCommand(aAddon, true);
+        } else if (aAddon.optionsType == AddonManager.OPTIONS_TYPE_TAB) {
+          openOptionsInTab(aAddon.optionsURL);
+        }
+
+        let value = inline ? "inline" : "external";
+        recordActionTelemetry({
+          action: "preferences",
+          value,
+          view,
+          addon: aAddon,
+        });
+      },
+    },
+
+    cmd_enableItem: {
+      isEnabled(aAddon) {
+        if (!aAddon) {
+          return false;
+        }
+        let addonType = AddonManager.addonTypes[aAddon.type];
+        return (
+          !(addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) &&
+          hasPermission(aAddon, "enable")
+        );
+      },
+      doCommand(aAddon) {
+        if (shouldShowPermissionsPrompt(aAddon)) {
+          showPermissionsPrompt(aAddon).then(() => {
+            // Record telemetry if the addon has been enabled.
+            recordActionTelemetry({ action: "enable", addon: aAddon });
+          });
+        } else {
+          aAddon.enable();
+          recordActionTelemetry({ action: "enable", addon: aAddon });
+        }
+      },
+      getTooltip(aAddon) {
+        if (!aAddon) {
+          return "";
+        }
+        return gStrings.ext.GetStringFromName("enableAddonTooltip");
+      },
+    },
+
+    cmd_disableItem: {
+      isEnabled(aAddon) {
+        if (!aAddon) {
+          return false;
+        }
+        let addonType = AddonManager.addonTypes[aAddon.type];
+        return (
+          !(addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) &&
+          hasPermission(aAddon, "disable")
+        );
+      },
+      doCommand(aAddon) {
+        aAddon.disable();
+        recordActionTelemetry({ action: "disable", addon: aAddon });
+      },
+      getTooltip(aAddon) {
+        if (!aAddon) {
+          return "";
+        }
+        return gStrings.ext.GetStringFromName("disableAddonTooltip");
+      },
+    },
+
+    cmd_installItem: {
+      isEnabled(aAddon) {
+        if (!aAddon) {
+          return false;
+        }
+        return (
+          aAddon.install && aAddon.install.state == AddonManager.STATE_AVAILABLE
+        );
+      },
+      doCommand(aAddon) {
+        function doInstall() {
+          gViewController.currentViewObj
+            .getListItemForID(aAddon.id)
+            ._installStatus.installRemote();
+        }
+
+        if (gViewController.currentViewObj == gDetailView) {
+          gViewController.popState(doInstall);
+        } else {
+          doInstall();
+        }
+      },
+    },
+
+    cmd_uninstallItem: {
+      isEnabled(aAddon) {
+        if (!aAddon) {
+          return false;
+        }
+        return hasPermission(aAddon, "uninstall");
+      },
+      async doCommand(aAddon) {
+        let view = getCurrentViewName();
+
+        // Make sure we're on the list view, which supports undo.
+        if (gViewController.currentViewObj != gListView) {
+          await new Promise(resolve => {
+            document.addEventListener("ViewChanged", resolve, { once: true });
+            gViewController.loadView(`addons://list/${aAddon.type}`);
+          });
+        }
+        recordActionTelemetry({ action: "uninstall", view, addon: aAddon });
+        gViewController.currentViewObj.getListItemForID(aAddon.id).uninstall();
+      },
+      getTooltip(aAddon) {
+        if (!aAddon) {
+          return "";
+        }
+        return gStrings.ext.GetStringFromName("uninstallAddonTooltip");
+      },
+    },
+
+    cmd_cancelUninstallItem: {
+      isEnabled(aAddon) {
+        if (!aAddon) {
+          return false;
+        }
+        return isPending(aAddon, "uninstall");
+      },
+      doCommand(aAddon) {
+        aAddon.cancelUninstall();
+      },
+    },
+
     cmd_installFromFile: {
       isEnabled() {
         return XPINSTALL_ENABLED;
       },
       doCommand() {
         const nsIFilePicker = Ci.nsIFilePicker;
         var fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
         fp.init(
@@ -1180,16 +1549,109 @@ var gViewController = {
           let path = useNewAboutDebugging ? "/runtime/this-firefox" : "addons";
           mainWindow.switchToTabHavingURI(`about:debugging#${path}`, true, {
             triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
           });
         }
       },
     },
 
+    cmd_cancelOperation: {
+      isEnabled(aAddon) {
+        if (!aAddon) {
+          return false;
+        }
+        return aAddon.pendingOperations != AddonManager.PENDING_NONE;
+      },
+      doCommand(aAddon) {
+        if (isPending(aAddon, "uninstall")) {
+          aAddon.cancelUninstall();
+        }
+      },
+    },
+
+    cmd_contribute: {
+      isEnabled(aAddon) {
+        if (!aAddon) {
+          return false;
+        }
+        return "contributionURL" in aAddon && aAddon.contributionURL;
+      },
+      doCommand(aAddon) {
+        openURL(aAddon.contributionURL);
+        recordActionTelemetry({ action: "contribute", addon: aAddon });
+      },
+    },
+
+    cmd_askToActivateItem: {
+      isEnabled(aAddon) {
+        if (!aAddon) {
+          return false;
+        }
+        let addonType = AddonManager.addonTypes[aAddon.type];
+        return (
+          addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE &&
+          hasPermission(aAddon, "ask_to_activate")
+        );
+      },
+      doCommand(aAddon) {
+        aAddon.userDisabled = AddonManager.STATE_ASK_TO_ACTIVATE;
+      },
+    },
+
+    cmd_alwaysActivateItem: {
+      isEnabled(aAddon) {
+        if (!aAddon) {
+          return false;
+        }
+        let addonType = AddonManager.addonTypes[aAddon.type];
+        return (
+          addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE &&
+          hasPermission(aAddon, "enable")
+        );
+      },
+      doCommand(aAddon) {
+        aAddon.userDisabled = false;
+      },
+    },
+
+    cmd_neverActivateItem: {
+      isEnabled(aAddon) {
+        if (!aAddon) {
+          return false;
+        }
+        let addonType = AddonManager.addonTypes[aAddon.type];
+        return (
+          addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE &&
+          hasPermission(aAddon, "disable")
+        );
+      },
+      doCommand(aAddon) {
+        aAddon.userDisabled = true;
+      },
+    },
+
+    cmd_showUnsignedExtensions: {
+      isEnabled() {
+        return true;
+      },
+      doCommand() {
+        gViewController.loadView("addons://list/extension?unsigned=true");
+      },
+    },
+
+    cmd_showAllExtensions: {
+      isEnabled() {
+        return true;
+      },
+      doCommand() {
+        gViewController.loadView("addons://list/extension");
+      },
+    },
+
     cmd_showShortcuts: {
       isEnabled() {
         return true;
       },
       doCommand() {
         gViewController.loadView("addons://shortcuts/shortcuts");
       },
     },
@@ -1247,21 +1709,281 @@ var gViewController = {
       return;
     }
     cmd.doCommand(aAddon);
   },
 
   onEvent() {},
 };
 
+async function isAddonAllowedInCurrentWindow(aAddon) {
+  if (
+    allowPrivateBrowsingByDefault ||
+    aAddon.type !== "extension" ||
+    !PrivateBrowsingUtils.isContentWindowPrivate(window)
+  ) {
+    return true;
+  }
+
+  const perms = await ExtensionPermissions.get(aAddon.id);
+  return perms.permissions.includes("internal:privateBrowsingAllowed");
+}
+
+function hasInlineOptions(aAddon) {
+  return (
+    aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_BROWSER ||
+    aAddon.type == "plugin"
+  );
+}
+
+function formatDate(aDate) {
+  const dtOptions = { year: "numeric", month: "long", day: "numeric" };
+  return aDate.toLocaleDateString(undefined, dtOptions);
+}
+
+function hasPermission(aAddon, aPerm) {
+  var perm = AddonManager["PERM_CAN_" + aPerm.toUpperCase()];
+  return !!(aAddon.permissions & perm);
+}
+
+function isPending(aAddon, aAction) {
+  var action = AddonManager["PENDING_" + aAction.toUpperCase()];
+  return !!(aAddon.pendingOperations & action);
+}
+
 function isInState(aInstall, aState) {
   var state = AddonManager["STATE_" + aState.toUpperCase()];
   return aInstall.state == state;
 }
 
+function shouldShowVersionNumber(aAddon) {
+  if (!aAddon.version) {
+    return false;
+  }
+
+  // The version number is hidden for lightweight themes.
+  if (aAddon.type == "theme") {
+    return !/@personas\.mozilla\.org$/.test(aAddon.id);
+  }
+
+  return true;
+}
+
+function createItem(aObj, aIsInstall) {
+  let item = document.createXULElement("richlistitem");
+
+  item.setAttribute("class", "addon addon-view card");
+  item.setAttribute("name", aObj.name);
+  item.setAttribute("type", aObj.type);
+
+  if (aIsInstall) {
+    item.mInstall = aObj;
+
+    if (aObj.state != AddonManager.STATE_INSTALLED) {
+      item.setAttribute("status", "installing");
+      return item;
+    }
+    aObj = aObj.addon;
+  }
+
+  item.mAddon = aObj;
+
+  item.setAttribute("status", "installed");
+
+  // set only attributes needed for sorting and XBL binding,
+  // the binding handles the rest
+  item.setAttribute("value", aObj.id);
+
+  return item;
+}
+
+function sortElements(aElements, aSortBy, aAscending) {
+  // aSortBy is an Array of attributes to sort by, in decending
+  // order of priority.
+
+  const DATE_FIELDS = ["updateDate"];
+  const NUMERIC_FIELDS = ["relevancescore"];
+
+  // We're going to group add-ons into the following buckets:
+  //
+  //  enabledInstalled
+  //    * Enabled
+  //    * Incompatible but enabled because compatibility checking is off
+  //    * Waiting to be installed
+  //    * Waiting to be enabled
+  //
+  //  pendingDisable
+  //    * Waiting to be disabled
+  //
+  //  pendingUninstall
+  //    * Waiting to be removed
+  //
+  //  disabledIncompatibleBlocked
+  //    * Disabled
+  //    * Incompatible
+  //    * Blocklisted
+
+  const UISTATE_ORDER = [
+    "enabled",
+    "askToActivate",
+    "pendingDisable",
+    "pendingUninstall",
+    "disabled",
+  ];
+
+  function dateCompare(a, b) {
+    var aTime = a.getTime();
+    var bTime = b.getTime();
+    if (aTime < bTime) {
+      return -1;
+    }
+    if (aTime > bTime) {
+      return 1;
+    }
+    return 0;
+  }
+
+  function numberCompare(a, b) {
+    return a - b;
+  }
+
+  function stringCompare(a, b) {
+    return a.localeCompare(b);
+  }
+
+  function uiStateCompare(a, b) {
+    // If we're in descending order, swap a and b, because
+    // we don't ever want to have descending uiStates
+    if (!aAscending) {
+      [a, b] = [b, a];
+    }
+
+    return UISTATE_ORDER.indexOf(a) - UISTATE_ORDER.indexOf(b);
+  }
+
+  // Prioritize themes that have screenshots.
+  function hasPreview(aHasStr, bHasStr) {
+    let aHas = aHasStr == "true";
+    let bHas = bHasStr == "true";
+    if (aHas == bHas) {
+      return 0;
+    }
+    return aHas ? -1 : 1;
+  }
+
+  function getValue(aObj, aKey) {
+    if (!aObj) {
+      return null;
+    }
+
+    if (aObj.hasAttribute(aKey)) {
+      return aObj.getAttribute(aKey);
+    }
+
+    var addon = aObj.mAddon || aObj.mInstall;
+    var addonType = aObj.mAddon && AddonManager.addonTypes[aObj.mAddon.type];
+
+    if (!addon) {
+      return null;
+    }
+
+    if (aKey == "uiState") {
+      if (addon.pendingOperations == AddonManager.PENDING_DISABLE) {
+        return "pendingDisable";
+      }
+      if (addon.pendingOperations == AddonManager.PENDING_UNINSTALL) {
+        return "pendingUninstall";
+      }
+      if (
+        !addon.isActive &&
+        (addon.pendingOperations != AddonManager.PENDING_ENABLE &&
+          addon.pendingOperations != AddonManager.PENDING_INSTALL)
+      ) {
+        return "disabled";
+      }
+      if (
+        addonType &&
+        addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE &&
+        addon.userDisabled == AddonManager.STATE_ASK_TO_ACTIVATE
+      ) {
+        return "askToActivate";
+      }
+      return "enabled";
+    }
+
+    return addon[aKey];
+  }
+
+  // aSortFuncs will hold the sorting functions that we'll
+  // use per element, in the correct order.
+  var aSortFuncs = [];
+
+  for (let i = 0; i < aSortBy.length; i++) {
+    var sortBy = aSortBy[i];
+
+    aSortFuncs[i] = stringCompare;
+
+    if (sortBy == "uiState") {
+      aSortFuncs[i] = uiStateCompare;
+    } else if (DATE_FIELDS.includes(sortBy)) {
+      aSortFuncs[i] = dateCompare;
+    } else if (NUMERIC_FIELDS.includes(sortBy)) {
+      aSortFuncs[i] = numberCompare;
+    } else if (sortBy == "hasPreview") {
+      aSortFuncs[i] = hasPreview;
+    }
+  }
+
+  aElements.sort(function(a, b) {
+    if (!aAscending) {
+      [a, b] = [b, a];
+    }
+
+    for (let i = 0; i < aSortFuncs.length; i++) {
+      var sortBy = aSortBy[i];
+      var aValue = getValue(a, sortBy);
+      var bValue = getValue(b, sortBy);
+
+      if (!aValue && !bValue) {
+        return 0;
+      }
+      if (!aValue) {
+        return -1;
+      }
+      if (!bValue) {
+        return 1;
+      }
+      if (aValue != bValue) {
+        var result = aSortFuncs[i](aValue, bValue);
+
+        if (result != 0) {
+          return result;
+        }
+      }
+    }
+
+    // If we got here, then all values of a and b
+    // must have been equal.
+    return 0;
+  });
+}
+
+function sortList(aList, aSortBy, aAscending) {
+  var elements = Array.from(aList.childNodes);
+  sortElements(elements, [aSortBy], aAscending);
+
+  while (aList.lastChild) {
+    aList.lastChild.remove();
+  }
+
+  for (let element of elements) {
+    aList.appendChild(element);
+  }
+}
+
 async function getAddonsAndInstalls(aType, aCallback) {
   let addons = null,
     installs = null;
   let types = aType != null ? [aType] : null;
 
   let aAddonsList = await AddonManager.getAddonsByTypes(types);
   addons = aAddonsList.filter(a => !a.hidden);
   if (installs != null) {
@@ -1275,16 +1997,33 @@ async function getAddonsAndInstalls(aTyp
     );
   });
 
   if (addons != null) {
     aCallback(addons, installs);
   }
 }
 
+function doPendingUninstalls(aListBox) {
+  // Uninstalling add-ons can mutate the list so find the add-ons first then
+  // uninstall them
+  var items = [];
+  var listitem = aListBox.firstChild;
+  while (listitem) {
+    if (listitem.getAttribute("pending") == "uninstall") {
+      items.push(listitem.mAddon);
+    }
+    listitem = listitem.nextSibling;
+  }
+
+  for (let addon of items) {
+    addon.uninstall();
+  }
+}
+
 var gCategories = {
   node: null,
 
   initialize() {
     this.node = document.getElementById("categories");
 
     var types = AddonManager.addonTypes;
     for (var type in types) {
@@ -1925,52 +2664,1513 @@ var gDiscoverView = {
     Ci.nsISupportsWeakReference,
   ]),
 
   getSelectedAddon() {
     return null;
   },
 };
 
-var gUpdatesView = {
+var gLegacyView = {
+  node: null,
+  _listBox: null,
   _categoryItem: null,
   isRoot: true,
 
   initialize() {
+    this.node = document.getElementById("legacy-view");
+    this._listBox = document.getElementById("legacy-list");
+    this._categoryItem = gCategories.get("addons://legacy/");
+
+    document.getElementById("legacy-learnmore").href =
+      SUPPORT_URL + "webextensions";
+
+    gEventManager.registerAddonListener(this, "ANY");
+
+    this.refreshVisibility();
+  },
+
+  shutdown() {
+    gEventManager.unregisterAddonListener(this, "ANY");
+  },
+
+  onUninstalled() {
+    this.refreshVisibility();
+  },
+
+  async show(type, request) {
+    let addons = await AddonManager.getAddonsByTypes(["extension", "theme"]);
+    addons = addons.filter(
+      a => !a.hidden && (isDisabledLegacy(a) || isDisabledUnsigned(a))
+    );
+
+    this._listBox.textContent = "";
+
+    let elements = addons.map(a => createItem(a));
+    if (elements.length == 0) {
+      gViewController.loadView("addons://list/extension");
+      return;
+    }
+
+    sortElements(elements, ["uiState", "name"], true);
+    for (let element of elements) {
+      this._listBox.appendChild(element);
+    }
+
+    gViewController.notifyViewChanged();
+  },
+
+  hide() {
+    doPendingUninstalls(this._listBox);
+  },
+
+  getSelectedAddon() {
+    var item = this._listBox.selectedItem;
+    if (item) {
+      return item.mAddon;
+    }
+    return null;
+  },
+
+  async refreshVisibility() {
+    if (legacyExtensionsEnabled) {
+      this._categoryItem.disabled = true;
+      return;
+    }
+
+    let extensions = await AddonManager.getAddonsByTypes([
+      "extension",
+      "theme",
+    ]);
+
+    let haveUnsigned = false;
+    let haveLegacy = false;
+    for (let extension of extensions) {
+      if (isDisabledUnsigned(extension)) {
+        haveUnsigned = true;
+      }
+      if (isLegacyExtension(extension)) {
+        haveLegacy = true;
+      }
+    }
+
+    if (haveLegacy || haveUnsigned) {
+      this._categoryItem.disabled = false;
+      let name = gStrings.ext.GetStringFromName(
+        `type.${haveUnsigned ? "unsupported" : "legacy"}.name`
+      );
+      this._categoryItem.setAttribute("name", name);
+      this._categoryItem.tooltiptext = name;
+    } else {
+      this._categoryItem.disabled = true;
+    }
+  },
+
+  getListItemForID(aId) {
+    var listitem = this._listBox.firstChild;
+    while (listitem) {
+      if (
+        listitem.getAttribute("status") == "installed" &&
+        listitem.mAddon.id == aId
+      ) {
+        return listitem;
+      }
+      listitem = listitem.nextSibling;
+    }
+    return null;
+  },
+};
+
+var gListView = {
+  node: null,
+  _listBox: null,
+  _emptyNotice: null,
+  _type: null,
+  isRoot: true,
+
+  initialize() {
+    this.node = document.getElementById("list-view");
+    this._listBox = document.getElementById("addon-list");
+    this._emptyNotice = document.getElementById("addon-list-empty");
+
+    this._listBox.addEventListener("keydown", aEvent => {
+      if (aEvent.keyCode == aEvent.DOM_VK_RETURN) {
+        var item = this._listBox.selectedItem;
+        if (item) {
+          item.showInDetailView();
+        }
+      }
+    });
+    this._listBox.addEventListener("Uninstall", event =>
+      recordActionTelemetry({
+        action: "uninstall",
+        view: "list",
+        addon: event.target.mAddon,
+      })
+    );
+    this._listBox.addEventListener("Undo", event =>
+      recordActionTelemetry({ action: "undo", addon: event.target.mAddon })
+    );
+
+    document
+      .getElementById("signing-learn-more")
+      .setAttribute("href", SUPPORT_URL + "unsigned-addons");
+    document
+      .getElementById("legacy-extensions-learnmore-link")
+      .addEventListener("click", evt => {
+        gViewController.loadView("addons://legacy/");
+      });
+
+    try {
+      document
+        .getElementById("private-browsing-learnmore-link")
+        .setAttribute("href", SUPPORT_URL + "extensions-pb");
+    } catch (e) {
+      document.getElementById("private-browsing-notice").hidden = true;
+    }
+
+    let findSignedAddonsLink = document.getElementById(
+      "find-alternative-addons"
+    );
+    try {
+      findSignedAddonsLink.setAttribute(
+        "href",
+        Services.urlFormatter.formatURLPref("extensions.getAddons.link.url")
+      );
+    } catch (e) {
+      findSignedAddonsLink.classList.remove("text-link");
+    }
+
+    try {
+      document
+        .getElementById("signing-dev-manual-link")
+        .setAttribute(
+          "href",
+          Services.prefs.getCharPref("xpinstall.signatures.devInfoURL")
+        );
+    } catch (e) {
+      document.getElementById("signing-dev-info").hidden = true;
+    }
+
+    if (Preferences.get("plugin.load_flash_only", true)) {
+      document
+        .getElementById("plugindeprecation-learnmore-link")
+        .setAttribute("href", SUPPORT_URL + "npapi");
+    } else {
+      document.getElementById("plugindeprecation-notice").hidden = true;
+    }
+  },
+
+  show(aType, aRequest) {
+    let showOnlyDisabledUnsigned = false;
+    if (aType.endsWith("?unsigned=true")) {
+      aType = aType.replace(/\?.*/, "");
+      showOnlyDisabledUnsigned = true;
+    }
+
+    if (!(aType in AddonManager.addonTypes)) {
+      throw Components.Exception(
+        "Attempting to show unknown type " + aType,
+        Cr.NS_ERROR_INVALID_ARG
+      );
+    }
+
+    this._type = aType;
+    this.node.setAttribute("type", aType);
+    this.showEmptyNotice(false);
+
+    this._listBox.textContent = "";
+
+    if (aType == "plugin") {
+      navigator.plugins.refresh(false);
+    }
+
+    getAddonsAndInstalls(aType, (aAddonsList, aInstallsList) => {
+      if (gViewController && aRequest != gViewController.currentViewRequest) {
+        return;
+      }
+
+      let showLegacyInfo = false;
+      if (!legacyExtensionsEnabled && aType != "locale") {
+        let preLen = aAddonsList.length;
+        aAddonsList = aAddonsList.filter(
+          addon => !isLegacyExtension(addon) && !isDisabledUnsigned(addon)
+        );
+        if (aAddonsList.length != preLen) {
+          showLegacyInfo = true;
+        }
+      }
+
+      let privateNotice = document.getElementById("private-browsing-notice");
+      privateNotice.hidden =
+        allowPrivateBrowsingByDefault || aType != "extension";
+
+      var elements = [];
+
+      for (let addonItem of aAddonsList) {
+        elements.push(createItem(addonItem));
+      }
+
+      for (let installItem of aInstallsList) {
+        elements.push(createItem(installItem, true));
+      }
+
+      this.showEmptyNotice(elements.length == 0);
+      if (elements.length > 0) {
+        let sortBy;
+        if (aType == "theme") {
+          sortBy = ["uiState", "hasPreview", "name"];
+        } else {
+          sortBy = ["uiState", "name"];
+        }
+        sortElements(elements, sortBy, true);
+        for (let element of elements) {
+          this._listBox.appendChild(element);
+        }
+      }
+
+      this.filterDisabledUnsigned(showOnlyDisabledUnsigned);
+      let legacyNotice = document.getElementById("legacy-extensions-notice");
+      if (showLegacyInfo) {
+        let el = document.getElementById("legacy-extensions-description");
+        if (el.childNodes[0].nodeName == "#text") {
+          el.removeChild(el.childNodes[0]);
+        }
+
+        let descriptionId =
+          aType == "theme"
+            ? "legacyThemeWarning.description"
+            : "legacyWarning.description";
+        let text =
+          gStrings.ext.formatStringFromName(descriptionId, [
+            gStrings.brandShortName,
+          ]) + " ";
+        el.insertBefore(document.createTextNode(text), el.childNodes[0]);
+        legacyNotice.hidden = false;
+      } else {
+        legacyNotice.hidden = true;
+      }
+
+      gEventManager.registerInstallListener(this);
+      gViewController.updateCommands();
+      gViewController.notifyViewChanged();
+    });
+  },
+
+  hide() {
+    gEventManager.unregisterInstallListener(this);
+    doPendingUninstalls(this._listBox);
+  },
+
+  filterDisabledUnsigned(aFilter = true) {
+    let foundDisabledUnsigned = false;
+
+    for (let item of this._listBox.childNodes) {
+      if (isDisabledUnsigned(item.mAddon)) {
+        foundDisabledUnsigned = true;
+      } else {
+        item.hidden = aFilter;
+      }
+    }
+
+    document.getElementById("show-disabled-unsigned-extensions").hidden =
+      aFilter || !foundDisabledUnsigned;
+
+    document.getElementById("show-all-extensions").hidden = !aFilter;
+    document.getElementById("disabled-unsigned-addons-info").hidden = !aFilter;
+  },
+
+  showEmptyNotice(aShow) {
+    this._emptyNotice.hidden = !aShow;
+    this._listBox.hidden = aShow;
+  },
+
+  onSortChanged(aSortBy, aAscending) {
+    sortList(this._listBox, aSortBy, aAscending);
+  },
+
+  onExternalInstall(aAddon, aExistingAddon) {
+    // The existing list item will take care of upgrade installs
+    if (aExistingAddon) {
+      return;
+    }
+
+    if (aAddon.hidden) {
+      return;
+    }
+
+    this.addItem(aAddon);
+  },
+
+  onDownloadStarted(aInstall) {
+    this.addItem(aInstall, true);
+  },
+
+  onInstallStarted(aInstall) {
+    this.addItem(aInstall, true);
+  },
+
+  onDownloadCancelled(aInstall) {
+    this.removeItem(aInstall, true);
+  },
+
+  onInstallCancelled(aInstall) {
+    this.removeItem(aInstall, true);
+  },
+
+  onInstallEnded(aInstall) {
+    // Remove any install entries for upgrades, their status will appear against
+    // the existing item
+    if (aInstall.existingAddon) {
+      this.removeItem(aInstall, true);
+    }
+  },
+
+  addItem(aObj, aIsInstall) {
+    if (aObj.type != this._type) {
+      return;
+    }
+
+    if (aIsInstall && aObj.existingAddon) {
+      return;
+    }
+
+    if (aObj.addon && aObj.addon.hidden) {
+      return;
+    }
+
+    let prop = aIsInstall ? "mInstall" : "mAddon";
+    for (let item of this._listBox.childNodes) {
+      if (item[prop] == aObj) {
+        return;
+      }
+    }
+
+    let item = createItem(aObj, aIsInstall);
+    this._listBox.insertBefore(item, this._listBox.firstChild);
+    this.showEmptyNotice(false);
+  },
+
+  removeItem(aObj, aIsInstall) {
+    let prop = aIsInstall ? "mInstall" : "mAddon";
+
+    for (let item of this._listBox.childNodes) {
+      if (item[prop] == aObj) {
+        this._listBox.removeChild(item);
+        this.showEmptyNotice(this._listBox.itemCount == 0);
+        return;
+      }
+    }
+  },
+
+  getSelectedAddon() {
+    var item = this._listBox.selectedItem;
+    if (item) {
+      return item.mAddon;
+    }
+    return null;
+  },
+
+  getListItemForID(aId) {
+    var listitem = this._listBox.firstChild;
+    while (listitem) {
+      if (
+        listitem.getAttribute("status") == "installed" &&
+        listitem.mAddon.id == aId
+      ) {
+        return listitem;
+      }
+      listitem = listitem.nextSibling;
+    }
+    return null;
+  },
+};
+
+var gDetailView = {
+  node: null,
+  _addon: null,
+  _loadingTimer: null,
+  _autoUpdate: null,
+  isRoot: false,
+  restartingAddon: false,
+
+  initialize() {
+    this.node = document.getElementById("detail-view");
+    this.node.addEventListener("click", this.recordClickTelemetry);
+    this.headingImage = this.node.querySelector(".card-heading-image");
+
+    this._autoUpdate = document.getElementById("detail-autoUpdate");
+    this._autoUpdate.addEventListener(
+      "command",
+      () => {
+        this._addon.applyBackgroundUpdates = this._autoUpdate.value;
+        recordSetAddonUpdateTelemetry(this._addon);
+      },
+      true
+    );
+
+    for (let el of document.getElementsByClassName("private-learnmore")) {
+      el.setAttribute("href", SUPPORT_URL + "extensions-pb");
+    }
+
+    this._privateBrowsing = document.getElementById("detail-privateBrowsing");
+    this._privateBrowsing.addEventListener(
+      "command",
+      async () => {
+        let addon = this._addon;
+        let policy = WebExtensionPolicy.getByID(addon.id);
+        let extension = policy && policy.extension;
+
+        let perms = {
+          permissions: ["internal:privateBrowsingAllowed"],
+          origins: [],
+        };
+        if (this._privateBrowsing.value == "1") {
+          await ExtensionPermissions.add(addon.id, perms, extension);
+          recordActionTelemetry({
+            action: "privateBrowsingAllowed",
+            value: "on",
+            addon,
+          });
+        } else {
+          await ExtensionPermissions.remove(addon.id, perms, extension);
+          recordActionTelemetry({
+            action: "privateBrowsingAllowed",
+            value: "off",
+            addon,
+          });
+        }
+
+        // Reload the extension if it is already enabled.  This ensures any change
+        // on the private browsing permission is properly handled.
+        if (addon.isActive) {
+          try {
+            this.restartingAddon = true;
+            await addon.reload();
+          } finally {
+            this.restartingAddon = false;
+            this.updateState();
+            this._updateView(addon, false);
+          }
+        }
+      },
+      true
+    );
+  },
+
+  shutdown() {
+    AddonManager.removeManagerListener(this);
+  },
+
+  onUpdateModeChanged() {
+    this.onPropertyChanged(["applyBackgroundUpdates"]);
+  },
+
+  recordClickTelemetry(event) {
+    if (event.target.id == "detail-reviews") {
+      recordLinkTelemetry("rating");
+    } else if (event.target.id == "detail-homepage") {
+      recordLinkTelemetry("homepage");
+    } else if (event.originalTarget.getAttribute("anonid") == "creator-link") {
+      recordLinkTelemetry("author");
+    }
+  },
+
+  async _updateView(aAddon, aIsRemote, aScrollToPreferences) {
+    // Skip updates to avoid flickering while restarting the addon.
+    if (this.restartingAddon) {
+      return;
+    }
+
+    setSearchLabel(aAddon.type);
+
+    // Set the preview image for themes, if available.
+    this.headingImage.src = "";
+    if (aAddon.type == "theme") {
+      let previewURL =
+        aAddon.screenshots &&
+        aAddon.screenshots[0] &&
+        aAddon.screenshots[0].url;
+      if (previewURL) {
+        this.headingImage.src = previewURL;
+      }
+    }
+
+    AddonManager.addManagerListener(this);
+    this.clearLoading();
+
+    this._addon = aAddon;
+    gEventManager.registerAddonListener(this, aAddon.id);
+    gEventManager.registerInstallListener(this);
+
+    this.node.setAttribute("type", aAddon.type);
+
+    let legacy = false;
+    if (!aAddon.install) {
+      legacy = isLegacyExtension(aAddon);
+    }
+    this.node.setAttribute("legacy", legacy);
+    document.getElementById("detail-legacy-warning").href =
+      SUPPORT_URL + "webextensions";
+
+    // Make sure to select the correct category
+    let category =
+      isDisabledLegacy(aAddon) || isDisabledUnsigned(aAddon)
+        ? "addons://legacy"
+        : `addons://list/${aAddon.type}`;
+    gCategories.select(category);
+
+    document.getElementById("detail-name").textContent = aAddon.name;
+    var icon = AddonManager.getPreferredIconURL(aAddon, 32, window);
+    document.getElementById("detail-icon").src = icon ? icon : "";
+    document
+      .getElementById("detail-creator")
+      .setCreator(aAddon.creator, aAddon.homepageURL);
+
+    var version = document.getElementById("detail-version");
+    if (shouldShowVersionNumber(aAddon)) {
+      version.hidden = false;
+      version.value = aAddon.version;
+    } else {
+      version.hidden = true;
+    }
+
+    var desc = document.getElementById("detail-desc");
+    desc.textContent = aAddon.description;
+
+    var fullDesc = document.getElementById("detail-fulldesc");
+    if (aAddon.getFullDescription) {
+      fullDesc.textContent = "";
+      fullDesc.append(aAddon.getFullDescription(document));
+      fullDesc.hidden = false;
+    } else if (aAddon.fullDescription) {
+      fullDesc.textContent = aAddon.fullDescription;
+      fullDesc.hidden = false;
+    } else {
+      fullDesc.hidden = true;
+    }
+
+    var contributions = document.getElementById("detail-contributions");
+    if ("contributionURL" in aAddon && aAddon.contributionURL) {
+      contributions.hidden = false;
+    } else {
+      contributions.hidden = true;
+    }
+
+    var updateDateRow = document.getElementById("detail-dateUpdated");
+    if (aAddon.updateDate) {
+      var date = formatDate(aAddon.updateDate);
+      updateDateRow.value = date;
+    } else {
+      updateDateRow.value = null;
+    }
+
+    // TODO if the add-on was downloaded from releases.mozilla.org link to the
+    // AMO profile (bug 590344)
+    if (false) {
+      document.getElementById("detail-repository-row").hidden = false;
+      document.getElementById("detail-homepage-row").hidden = true;
+      var repository = document.getElementById("detail-repository");
+      repository.value = aAddon.homepageURL;
+      repository.href = aAddon.homepageURL;
+    } else if (aAddon.homepageURL) {
+      document.getElementById("detail-repository-row").hidden = true;
+      document.getElementById("detail-homepage-row").hidden = false;
+      var homepage = document.getElementById("detail-homepage");
+      homepage.value = aAddon.homepageURL;
+      homepage.href = aAddon.homepageURL;
+    } else {
+      document.getElementById("detail-repository-row").hidden = true;
+      document.getElementById("detail-homepage-row").hidden = true;
+    }
+
+    var rating = document.getElementById("detail-rating");
+    if (aAddon.averageRating) {
+      rating.averageRating = aAddon.averageRating;
+      rating.hidden = false;
+    } else {
+      rating.hidden = true;
+    }
+
+    var reviews = document.getElementById("detail-reviews");
+    if (aAddon.reviewURL) {
+      var text = gStrings.ext.GetStringFromName("numReviews");
+      text = PluralForm.get(aAddon.reviewCount, text);
+      text = text.replace("#1", aAddon.reviewCount);
+      reviews.value = text;
+      reviews.hidden = false;
+      reviews.href = aAddon.reviewURL;
+    } else {
+      reviews.hidden = true;
+    }
+
+    document.getElementById("detail-rating-row").hidden =
+      !aAddon.averageRating && !aAddon.reviewURL;
+
+    var canUpdate = !aIsRemote && hasPermission(aAddon, "upgrade");
+    document.getElementById("detail-updates-row").hidden = !canUpdate;
+
+    if ("applyBackgroundUpdates" in aAddon) {
+      this._autoUpdate.hidden = false;
+      this._autoUpdate.value = aAddon.applyBackgroundUpdates;
+      let hideFindUpdates = AddonManager.shouldAutoUpdate(this._addon);
+      document.getElementById(
+        "detail-findUpdates-btn"
+      ).hidden = hideFindUpdates;
+    } else {
+      this._autoUpdate.hidden = true;
+      document.getElementById("detail-findUpdates-btn").hidden = false;
+    }
+
+    // Only type = "extension" will ever get privateBrowsingAllowed, other types have
+    // no code that would be affected by the setting.  The permission is read directly
+    // from ExtensionPermissions so we can get it whether or not the extension is
+    // currently active.
+    // Ensure that all private browsing rows are hidden by default, we'll then
+    // unhide what we want.
+    for (let el of document.getElementsByClassName("detail-privateBrowsing")) {
+      el.hidden = true;
+    }
+    if (!allowPrivateBrowsingByDefault && aAddon.type === "extension") {
+      if (
+        aAddon.permissions & AddonManager.PERM_CAN_CHANGE_PRIVATEBROWSING_ACCESS
+      ) {
+        let privateBrowsingRow = document.getElementById(
+          "detail-privateBrowsing-row"
+        );
+        let privateBrowsingFooterRow = document.getElementById(
+          "detail-privateBrowsing-row-footer"
+        );
+        let perms = await ExtensionPermissions.get(aAddon.id);
+        this._privateBrowsing.hidden = false;
+        privateBrowsingRow.hidden = false;
+        privateBrowsingFooterRow.hidden = false;
+        this._privateBrowsing.value = perms.permissions.includes(
+          "internal:privateBrowsingAllowed"
+        )
+          ? "1"
+          : "0";
+      } else if (aAddon.incognito == "spanning") {
+        document.getElementById(
+          "detail-privateBrowsing-required"
+        ).hidden = false;
+        document.getElementById(
+          "detail-privateBrowsing-required-footer"
+        ).hidden = false;
+      } else if (aAddon.incognito == "not_allowed") {
+        document.getElementById(
+          "detail-privateBrowsing-disallowed"
+        ).hidden = false;
+        document.getElementById(
+          "detail-privateBrowsing-disallowed-footer"
+        ).hidden = false;
+      }
+    }
+
+    // While updating the addon details view, also check if the preferences button should be disabled because
+    // we are in a private window and the addon is not allowed to access it.
+    let hidePreferences =
+      (!aIsRemote &&
+        !gViewController.commands.cmd_showItemPreferences.isEnabled(aAddon)) ||
+      !(await isAddonAllowedInCurrentWindow(aAddon));
+    document.getElementById("detail-prefs-btn").hidden = hidePreferences;
+
+    var gridRows = document.querySelectorAll("#detail-grid rows row");
+    let first = true;
+    for (let gridRow of gridRows) {
+      if (
+        first &&
+        window.getComputedStyle(gridRow).getPropertyValue("display") != "none"
+      ) {
+        gridRow.setAttribute("first-row", true);
+        first = false;
+      } else {
+        gridRow.removeAttribute("first-row");
+      }
+    }
+
+    this.fillSettingsRows(aScrollToPreferences, () => {
+      this.updateState();
+      gViewController.notifyViewChanged();
+    });
+  },
+
+  async show(aAddonId, aRequest) {
+    let index = aAddonId.indexOf("/preferences");
+    let scrollToPreferences = false;
+    if (index >= 0) {
+      aAddonId = aAddonId.substring(0, index);
+      scrollToPreferences = true;
+    }
+
+    this._loadingTimer = setTimeout(() => {
+      this.node.setAttribute("loading-extended", true);
+    }, LOADING_MSG_DELAY);
+
+    let aAddon = await AddonManager.getAddonByID(aAddonId);
+    if (gViewController && aRequest != gViewController.currentViewRequest) {
+      return;
+    }
+
+    if (aAddon) {
+      this._updateView(aAddon, false, scrollToPreferences);
+      return;
+    }
+
+    // Look for an add-on pending install
+    let aInstalls = await AddonManager.getAllInstalls();
+    for (let install of aInstalls) {
+      if (
+        install.state == AddonManager.STATE_INSTALLED &&
+        install.addon.id == aAddonId
+      ) {
+        this._updateView(install.addon, false);
+        return;
+      }
+    }
+
+    // This might happen due to session restore restoring us back to an
+    // add-on that doesn't exist but otherwise shouldn't normally happen.
+    // Either way just revert to the default view.
+    gViewController.replaceView(gViewDefault);
+  },
+
+  hide() {
+    AddonManager.removeManagerListener(this);
+    this.clearLoading();
+    if (this._addon) {
+      if (hasInlineOptions(this._addon)) {
+        Services.obs.notifyObservers(
+          document,
+          AddonManager.OPTIONS_NOTIFICATION_HIDDEN,
+          this._addon.id
+        );
+      }
+
+      gEventManager.unregisterAddonListener(this, this._addon.id);
+      gEventManager.unregisterInstallListener(this);
+      this._addon = null;
+
+      // Flush the preferences to disk so they survive any crash
+      if (this.node.getElementsByTagName("setting").length) {
+        Services.prefs.savePrefFile(null);
+      }
+    }
+  },
+
+  updateState() {
+    // Skip updates to avoid flickering while restarting the addon.
+    if (this.restartingAddon) {
+      return;
+    }
+
+    gViewController.updateCommands();
+
+    var pending = this._addon.pendingOperations;
+    if (pending & AddonManager.PENDING_UNINSTALL) {
+      this.node.removeAttribute("notification");
+
+      // We don't care about pending operations other than uninstall.
+      // They're transient, and cannot be undone.
+      this.node.setAttribute("pending", "uninstall");
+      document.getElementById(
+        "detail-pending"
+      ).textContent = gStrings.ext.formatStringFromName(
+        "details.notification.restartless-uninstall",
+        [this._addon.name]
+      );
+    } else {
+      this.node.removeAttribute("pending");
+
+      if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) {
+        this.node.setAttribute("notification", "error");
+        document.getElementById(
+          "detail-error"
+        ).textContent = gStrings.ext.formatStringFromName(
+          "details.notification.blocked",
+          [this._addon.name]
+        );
+        var errorLink = document.getElementById("detail-error-link");
+        errorLink.value = gStrings.ext.GetStringFromName(
+          "details.notification.blocked.link"
+        );
+        this._addon.getBlocklistURL().then(url => {
+          errorLink.href = url;
+          errorLink.hidden = false;
+        });
+      } else if (isDisabledUnsigned(this._addon)) {
+        this.node.setAttribute("notification", "error");
+        document.getElementById(
+          "detail-error"
+        ).textContent = gStrings.ext.formatStringFromName(
+          "details.notification.unsignedAndDisabled",
+          [this._addon.name, gStrings.brandShortName]
+        );
+        let errorLink = document.getElementById("detail-error-link");
+        errorLink.value = gStrings.ext.GetStringFromName(
+          "details.notification.unsigned.link"
+        );
+        errorLink.href = SUPPORT_URL + "unsigned-addons";
+        errorLink.hidden = false;
+      } else if (
+        !this._addon.isCompatible &&
+        (AddonManager.checkCompatibility ||
+          this._addon.blocklistState !=
+            Ci.nsIBlocklistService.STATE_SOFTBLOCKED)
+      ) {
+        this.node.setAttribute("notification", "warning");
+        document.getElementById(
+          "detail-warning"
+        ).textContent = gStrings.ext.formatStringFromName(
+          "details.notification.incompatible",
+          [this._addon.name, gStrings.brandShortName, gStrings.appVersion]
+        );
+        document.getElementById("detail-warning-link").hidden = true;
+      } else if (!isCorrectlySigned(this._addon)) {
+        this.node.setAttribute("notification", "warning");
+        document.getElementById(
+          "detail-warning"
+        ).textContent = gStrings.ext.formatStringFromName(
+          "details.notification.unsigned",
+          [this._addon.name, gStrings.brandShortName]
+        );
+        var warningLink = document.getElementById("detail-warning-link");
+        warningLink.value = gStrings.ext.GetStringFromName(
+          "details.notification.unsigned.link"
+        );
+        warningLink.href = SUPPORT_URL + "unsigned-addons";
+        warningLink.hidden = false;
+      } else if (
+        this._addon.blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED
+      ) {
+        this.node.setAttribute("notification", "warning");
+        document.getElementById(
+          "detail-warning"
+        ).textContent = gStrings.ext.formatStringFromName(
+          "details.notification.softblocked",
+          [this._addon.name]
+        );
+        let warningLink = document.getElementById("detail-warning-link");
+        warningLink.value = gStrings.ext.GetStringFromName(
+          "details.notification.softblocked.link"
+        );
+        this._addon.getBlocklistURL().then(url => {
+          warningLink.href = url;
+          warningLink.hidden = false;
+        });
+      } else if (
+        this._addon.blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED
+      ) {
+        this.node.setAttribute("notification", "warning");
+        document.getElementById(
+          "detail-warning"
+        ).textContent = gStrings.ext.formatStringFromName(
+          "details.notification.outdated",
+          [this._addon.name]
+        );
+        let warningLink = document.getElementById("detail-warning-link");
+        warningLink.value = gStrings.ext.GetStringFromName(
+          "details.notification.outdated.link"
+        );
+        this._addon.getBlocklistURL().then(url => {
+          warningLink.href = url;
+          warningLink.hidden = false;
+        });
+      } else if (
+        this._addon.blocklistState ==
+        Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE
+      ) {
+        this.node.setAttribute("notification", "error");
+        document.getElementById(
+          "detail-error"
+        ).textContent = gStrings.ext.formatStringFromName(
+          "details.notification.vulnerableUpdatable",
+          [this._addon.name]
+        );
+        let errorLink = document.getElementById("detail-error-link");
+        errorLink.value = gStrings.ext.GetStringFromName(
+          "details.notification.vulnerableUpdatable.link"
+        );
+        this._addon.getBlocklistURL().then(url => {
+          errorLink.href = url;
+          errorLink.hidden = false;
+        });
+      } else if (
+        this._addon.blocklistState ==
+        Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE
+      ) {
+        this.node.setAttribute("notification", "error");
+        document.getElementById(
+          "detail-error"
+        ).textContent = gStrings.ext.formatStringFromName(
+          "details.notification.vulnerableNoUpdate",
+          [this._addon.name]
+        );
+        let errorLink = document.getElementById("detail-error-link");
+        errorLink.value = gStrings.ext.GetStringFromName(
+          "details.notification.vulnerableNoUpdate.link"
+        );
+        this._addon.getBlocklistURL().then(url => {
+          errorLink.href = url;
+          errorLink.hidden = false;
+        });
+      } else if (
+        this._addon.isGMPlugin &&
+        !this._addon.isInstalled &&
+        this._addon.isActive
+      ) {
+        this.node.setAttribute("notification", "warning");
+        let warning = document.getElementById("detail-warning");
+        warning.textContent = gStrings.ext.formatStringFromName(
+          "details.notification.gmpPending",
+          [this._addon.name]
+        );
+      } else {
+        this.node.removeAttribute("notification");
+      }
+    }
+
+    let menulist = document.getElementById("detail-state-menulist");
+    let addonType = AddonManager.addonTypes[this._addon.type];
+    if (addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) {
+      let askItem = document.getElementById("detail-ask-to-activate-menuitem");
+      let alwaysItem = document.getElementById(
+        "detail-always-activate-menuitem"
+      );
+      let neverItem = document.getElementById("detail-never-activate-menuitem");
+      let hasActivatePermission = ["ask_to_activate", "enable", "disable"].some(
+        perm => hasPermission(this._addon, perm)
+      );
+
+      if (!this._addon.isActive) {
+        menulist.selectedItem = neverItem;
+      } else if (
+        this._addon.userDisabled == AddonManager.STATE_ASK_TO_ACTIVATE
+      ) {
+        menulist.selectedItem = askItem;
+      } else {
+        menulist.selectedItem = alwaysItem;
+      }
+
+      menulist.disabled = !hasActivatePermission;
+      menulist.hidden = false;
+      menulist.classList.add("no-auto-hide");
+    } else {
+      menulist.hidden = true;
+    }
+
+    this.node.setAttribute("active", this._addon.isActive);
+  },
+
+  clearLoading() {
+    if (this._loadingTimer) {
+      clearTimeout(this._loadingTimer);
+      this._loadingTimer = null;
+    }
+
+    this.node.removeAttribute("loading-extended");
+  },
+
+  emptySettingsRows() {
+    var lastRow = document.getElementById("detail-rating-row");
+    var rows = lastRow.parentNode;
+    while (lastRow.nextSibling) {
+      rows.removeChild(rows.lastChild);
+    }
+  },
+
+  fillSettingsRows(aScrollToPreferences, aCallback) {
+    this.emptySettingsRows();
+    if (!hasInlineOptions(this._addon)) {
+      if (aCallback) {
+        aCallback();
+      }
+      return;
+    }
+
+    // We can't use a promise for this, since some code (especially in tests)
+    // relies on us finishing before the ViewChanged event bubbles up to its
+    // listeners, and promises resolve asynchronously.
+    let whenViewLoaded = callback => {
+      if (gViewController.displayedView.hasAttribute("loading")) {
+        gDetailView.node.addEventListener(
+          "ViewChanged",
+          function() {
+            callback();
+          },
+          { once: true }
+        );
+      } else {
+        callback();
+      }
+    };
+
+    let finish = firstSetting => {
+      // Ensure the page has loaded and force the XBL bindings to be synchronously applied,
+      // then notify observers.
+      whenViewLoaded(() => {
+        if (firstSetting) {
+          firstSetting.clientTop;
+        }
+        Services.obs.notifyObservers(
+          document,
+          AddonManager.OPTIONS_NOTIFICATION_DISPLAYED,
+          this._addon.id
+        );
+        if (aScrollToPreferences) {
+          gDetailView.scrollToPreferencesRows();
+        }
+      });
+    };
+
+    var rows = document.getElementById("detail-rows");
+
+    if (this._addon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_BROWSER) {
+      whenViewLoaded(async () => {
+        const addon = this._addon;
+        await addon.startupPromise;
+
+        // Do not create the inline addon options if about:addons is opened in a private window
+        // and the addon is not allowed to access it.
+        if (!(await isAddonAllowedInCurrentWindow(addon))) {
+          return;
+        }
+
+        const browserContainer = await this.createOptionsBrowser(rows);
+
+        if (browserContainer) {
+          // Make sure the browser is unloaded as soon as we change views,
+          // rather than waiting for the next detail view to load.
+          document.addEventListener(
+            "ViewChanged",
+            function() {
+              // Do not remove the addon options container if the view changed
+              // event is not related to a change to the current selected view
+              // or the current selected addon (e.g. it could be related to the
+              // disco pane view that has completed to load, See Bug 1435705 for
+              // a rationale).
+              if (
+                gViewController.currentViewObj === gDetailView &&
+                gDetailView._addon === addon
+              ) {
+                return;
+              }
+              browserContainer.remove();
+            },
+            { once: true }
+          );
+        }
+
+        finish(browserContainer);
+      });
+    }
+
+    if (aCallback) {
+      aCallback();
+    }
+  },
+
+  scrollToPreferencesRows() {
+    // We find this row, rather than remembering it from above,
+    // in case it has been changed by the observers.
+    let firstRow = gDetailView.node.querySelector('setting[first-row="true"]');
+    if (firstRow) {
+      let top = firstRow.getBoundingClientRect().y;
+      top -= parseInt(
+        window.getComputedStyle(firstRow).getPropertyValue("margin-top")
+      );
+
+      let detailView = gDetailView.node;
+      top -= detailView.getBoundingClientRect().y;
+
+      detailView.scrollTo(0, top);
+    }
+  },
+
+  async createOptionsBrowser(parentNode) {
+    const containerId = "addon-options-prompts-stack";
+
+    let stack = document.getElementById(containerId);
+
+    if (stack) {
+      // Remove the existent options container (if any).
+      stack.remove();
+    }
+
+    stack = document.createXULElement("stack");
+    stack.setAttribute("id", containerId);
+
+    let browser = document.createXULElement("browser");
+    browser.setAttribute("type", "content");
+    browser.setAttribute("disableglobalhistory", "true");
+    browser.setAttribute("id", "addon-options");
+    browser.setAttribute("class", "inline-options-browser");
+    browser.setAttribute("transparent", "true");
+    browser.setAttribute("forcemessagemanager", "true");
+    browser.setAttribute("selectmenulist", "ContentSelectDropdown");
+    browser.setAttribute("autocompletepopup", "PopupAutoComplete");
+
+    // The outer about:addons document listens for key presses to focus
+    // the search box when / is pressed.  But if we're focused inside an
+    // options page, don't let those keypresses steal focus.
+    browser.addEventListener("keypress", event => {
+      event.stopPropagation();
+    });
+
+    let { optionsURL, optionsBrowserStyle } = this._addon;
+    if (this._addon.isWebExtension) {
+      let policy = ExtensionParent.WebExtensionPolicy.getByID(this._addon.id);
+      browser.sameProcessAsFrameLoader = policy.extension.groupFrameLoader;
+    }
+
+    let remoteSubframes = window.docShell.QueryInterface(Ci.nsILoadContext)
+      .useRemoteSubframes;
+
+    let readyPromise;
+    if (
+      E10SUtils.canLoadURIInRemoteType(
+        optionsURL,
+        remoteSubframes,
+        E10SUtils.EXTENSION_REMOTE_TYPE
+      )
+    ) {
+      browser.setAttribute("remote", "true");
+      browser.setAttribute("remoteType", E10SUtils.EXTENSION_REMOTE_TYPE);
+      readyPromise = promiseEvent("XULFrameLoaderCreated", browser);
+
+      readyPromise.then(() => {
+        if (!browser.messageManager) {
+          // Early exit if the the extension page's XUL browser has been destroyed in the meantime
+          // (e.g. because the extension has been reloaded while the options page was still loading).
+          return;
+        }
+        const parentChromeWindow = window.docShell.parent.domWindow;
+        const parentContextMenuPopup = parentChromeWindow.document.getElementById(
+          "contentAreaContextMenu"
+        );
+
+        // Override openPopupAtScreen on the dummy menupopup element, so that we can forward
+        // "nsContextMenu.js openContextMenu"'s calls related to the extensions "options page"
+        // context menu events.
+        document.getElementById("contentAreaContextMenu").openPopupAtScreen = (
+          ...args
+        ) => {
+          return parentContextMenuPopup.openPopupAtScreen(...args);
+        };
+      });
+    } else {
+      readyPromise = promiseEvent("load", browser, true);
+    }
+
+    stack.appendChild(browser);
+    parentNode.appendChild(stack);
+
+    // Force bindings to apply synchronously.
+    browser.clientTop;
+
+    await readyPromise;
+
+    if (!browser.messageManager) {
+      // If the browser.messageManager is undefined, the browser element has been
+      // removed from the document in the meantime (e.g. due to a rapid sequence
+      // of addon reload), ensure that the stack is also removed and return null.
+      stack.remove();
+      return null;
+    }
+
+    ExtensionParent.apiManager.emit("extension-browser-inserted", browser);
+
+    return new Promise(resolve => {
+      let messageListener = {
+        receiveMessage({ name, data }) {
+          if (name === "Extension:BrowserResized") {
+            browser.style.height = `${data.height}px`;
+          } else if (name === "Extension:BrowserContentLoaded") {
+            resolve(stack);
+          }
+        },
+      };
+
+      let mm = browser.messageManager;
+
+      if (!mm) {
+        // If the browser.messageManager is undefined, the browser element has been
+        // removed from the document in the meantime (e.g. due to a rapid sequence
+        // of addon reload), ensure that the stack is also removed and return null.
+        stack.remove();
+        resolve(null);
+        return;
+      }
+
+      mm.loadFrameScript(
+        "chrome://extensions/content/ext-browser-content.js",
+        false,
+        true
+      );
+      mm.loadFrameScript("chrome://browser/content/content.js", false, true);
+      mm.addMessageListener("Extension:BrowserContentLoaded", messageListener);
+      mm.addMessageListener("Extension:BrowserResized", messageListener);
+
+      let browserOptions = {
+        fixedWidth: true,
+        isInline: true,
+      };
+
+      if (optionsBrowserStyle) {
+        browserOptions.stylesheets = extensionStylesheets;
+      }
+
+      mm.sendAsyncMessage("Extension:InitBrowser", browserOptions);
+
+      browser.loadURI(optionsURL, {
+        triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+      });
+    });
+  },
+
+  getSelectedAddon() {
+    return this._addon;
+  },
+
+  onEnabling() {
+    this.updateState();
+  },
+
+  onEnabled() {
+    this.updateState();
+    this.fillSettingsRows();
+  },
+
+  onDisabling() {
+    this.updateState();
+    if (hasInlineOptions(this._addon)) {
+      Services.obs.notifyObservers(
+        document,
+        AddonManager.OPTIONS_NOTIFICATION_HIDDEN,
+        this._addon.id
+      );
+    }
+  },
+
+  onDisabled() {
+    this.updateState();
+    this.emptySettingsRows();
+  },
+
+  onUninstalling() {
+    this.updateState();
+  },
+
+  onUninstalled() {
+    gViewController.popState();
+  },
+
+  onOperationCancelled() {
+    this.updateState();
+  },
+
+  onPropertyChanged(aProperties) {
+    if (aProperties.includes("applyBackgroundUpdates")) {
+      this._autoUpdate.value = this._addon.applyBackgroundUpdates;
+      let hideFindUpdates = AddonManager.shouldAutoUpdate(this._addon);
+      document.getElementById(
+        "detail-findUpdates-btn"
+      ).hidden = hideFindUpdates;
+    }
+
+    if (
+      aProperties.includes("appDisabled") ||
+      aProperties.includes("signedState") ||
+      aProperties.includes("userDisabled")
+    ) {
+      this.updateState();
+    }
+  },
+
+  onExternalInstall(aAddon, aExistingAddon) {
+    // Only care about upgrades for the currently displayed add-on
+    if (!aExistingAddon || aExistingAddon.id != this._addon.id) {
+      return;
+    }
+
+    this._updateView(aAddon, false);
+  },
+
+  onInstallCancelled(aInstall) {
+    if (aInstall.addon.id == this._addon.id) {
+      gViewController.popState();
+    }
+  },
+};
+
+var gUpdatesView = {
+  node: null,
+  _listBox: null,
+  _emptyNotice: null,
+  _updateSelected: null,
+  _categoryItem: null,
+  isRoot: true,
+
+  initialize() {
+    this.node = document.getElementById("updates-view");
+    this._listBox = document.getElementById("updates-list");
+    this._emptyNotice = document.getElementById("updates-list-empty");
+
     this._categoryItem = gCategories.get("addons://updates/available");
+
+    this._updateSelected = document.getElementById("update-selected-btn");
+    this._updateSelected.addEventListener("command", function() {
+      gUpdatesView.installSelected();
+    });
+    this.node.addEventListener("RelNotesShow", event => {
+      recordActionTelemetry({
+        action: "releaseNotes",
+        addon: event.target.mAddon,
+      });
+    });
+
     this.updateAvailableCount(true);
 
     AddonManager.addAddonListener(this);
     AddonManager.addInstallListener(this);
   },
 
   shutdown() {
     AddonManager.removeAddonListener(this);
     AddonManager.removeInstallListener(this);
   },
 
   show(aType, aRequest) {
-    throw new Error(
-      "should not get here (available updates view is in aboutaddons.js"
-    );
+    document.getElementById("empty-availableUpdates-msg").hidden =
+      aType != "available";
+    document.getElementById("empty-recentUpdates-msg").hidden =
+      aType != "recent";
+    this.showEmptyNotice(false);
+
+    this._listBox.textContent = "";
+
+    this.node.setAttribute("updatetype", aType);
+    if (aType == "recent") {
+      this._showRecentUpdates(aRequest);
+    } else {
+      this._showAvailableUpdates(false, aRequest);
+    }
+  },
+
+  hide() {
+    this._updateSelected.hidden = true;
+    this._categoryItem.hidden = this._categoryItem.badgeCount == 0;
+    doPendingUninstalls(this._listBox);
+  },
+
+  async _showRecentUpdates(aRequest) {
+    let aAddonsList = await AddonManager.getAllAddons();
+    if (gViewController && aRequest != gViewController.currentViewRequest) {
+      return;
+    }
+
+    var elements = [];
+    let threshold = Date.now() - UPDATES_RECENT_TIMESPAN;
+    for (let addon of aAddonsList) {
+      if (
+        addon.hidden ||
+        !addon.updateDate ||
+        addon.updateDate.getTime() < threshold
+      ) {
+        continue;
+      }
+
+      elements.push(createItem(addon));
+    }
+
+    this.showEmptyNotice(elements.length == 0);
+    if (elements.length > 0) {
+      sortElements(elements, ["updateDate"], false);
+      for (let element of elements) {
+        this._listBox.appendChild(element);
+      }
+    }
+
+    gViewController.notifyViewChanged();
   },
 
-  hide() {},
+  async _showAvailableUpdates(aIsRefresh, aRequest) {
+    /* Disable the Update Selected button so it can't get clicked
+       before everything is initialized asynchronously.
+       It will get re-enabled by maybeDisableUpdateSelected(). */
+    this._updateSelected.disabled = true;
+
+    let aInstallsList = await AddonManager.getAllInstalls();
+    if (
+      !aIsRefresh &&
+      gViewController &&
+      aRequest &&
+      aRequest != gViewController.currentViewRequest
+    ) {
+      return;
+    }
+
+    if (aIsRefresh) {
+      this.showEmptyNotice(false);
+      this._updateSelected.hidden = true;
+
+      while (this._listBox.childNodes.length > 0) {
+        this._listBox.firstChild.remove();
+      }
+    }
+
+    var elements = [];
+
+    for (let install of aInstallsList) {
+      if (!this.isManualUpdate(install)) {
+        continue;
+      }
+
+      let item = createItem(install.existingAddon);
+      item.setAttribute("upgrade", true);
+      item.addEventListener("IncludeUpdateChanged", () => {
+        this.maybeDisableUpdateSelected();
+      });
+      elements.push(item);
+    }
+
+    this.showEmptyNotice(elements.length == 0);
+    if (elements.length > 0) {
+      this._updateSelected.hidden = false;
+      sortElements(elements, ["updateDate"], false);
+      for (let element of elements) {
+        this._listBox.appendChild(element);
+      }
+    }
+
+    // ensure badge count is in sync
+    this._categoryItem.badgeCount = this._listBox.itemCount;
+
+    gViewController.notifyViewChanged();
+  },
+
+  showEmptyNotice(aShow) {
+    this._emptyNotice.hidden = !aShow;
+    this._listBox.hidden = aShow;
+  },
 
   isManualUpdate(aInstall, aOnlyAvailable) {
     var isManual =
       aInstall.existingAddon &&
       !AddonManager.shouldAutoUpdate(aInstall.existingAddon);
     if (isManual && aOnlyAvailable) {
       return isInState(aInstall, "available");
     }
     return isManual;
   },
 
   maybeRefresh() {
+    if (gViewController.currentViewId == "addons://updates/available") {
+      this._showAvailableUpdates(true);
+    }
     this.updateAvailableCount();
   },
 
   async updateAvailableCount(aInitializing) {
     if (aInitializing) {
       gPendingInitializations++;
     }
     let aInstallsList = await AddonManager.getAllInstalls();
@@ -1981,16 +4181,59 @@ var gUpdatesView = {
       gViewController.currentViewId != "addons://updates/available" &&
       count == 0;
     this._categoryItem.badgeCount = count;
     if (aInitializing) {
       notifyInitialized();
     }
   },
 
+  maybeDisableUpdateSelected() {
+    for (let item of this._listBox.childNodes) {
+      if (item.includeUpdate) {
+        this._updateSelected.disabled = false;
+        return;
+      }
+    }
+    this._updateSelected.disabled = true;
+  },
+
+  installSelected() {
+    for (let item of this._listBox.childNodes) {
+      if (item.includeUpdate) {
+        item.upgrade();
+      }
+    }
+
+    this._updateSelected.disabled = true;
+  },
+
+  getSelectedAddon() {
+    var item = this._listBox.selectedItem;
+    if (item) {
+      return item.mAddon;
+    }
+    return null;
+  },
+
+  getListItemForID(aId) {
+    var listitem = this._listBox.firstChild;
+    while (listitem) {
+      if (listitem.mAddon.id == aId) {
+        return listitem;
+      }
+      listitem = listitem.nextSibling;
+    }
+    return null;
+  },
+
+  onSortChanged(aSortBy, aAscending) {
+    sortList(this._listBox, aSortBy, aAscending);
+  },
+
   onNewInstall(aInstall) {
     if (!this.isManualUpdate(aInstall)) {
       return;
     }
     this.maybeRefresh();
   },
 
   onInstallStarted(aInstall) {
--- a/toolkit/mozapps/extensions/content/extensions.xml
+++ b/toolkit/mozapps/extensions/content/extensions.xml
@@ -13,16 +13,202 @@
 
 <bindings id="addonBindings"
           xmlns="http://www.mozilla.org/xbl"
           xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
           xmlns:xbl="http://www.mozilla.org/xbl"
           xmlns:html="http://www.w3.org/1999/xhtml">
 
 
+  <!-- Rating - displays current/average rating, allows setting user rating -->
+  <binding id="rating">
+    <content>
+      <xul:image class="star"
+                 onmouseover="document.getBindingParent(this)._hover(1);"
+                 onclick="document.getBindingParent(this).userRating = 1;"/>
+      <xul:image class="star"
+                 onmouseover="document.getBindingParent(this)._hover(2);"
+                 onclick="document.getBindingParent(this).userRating = 2;"/>
+      <xul:image class="star"
+                 onmouseover="document.getBindingParent(this)._hover(3);"
+                 onclick="document.getBindingParent(this).userRating = 3;"/>
+      <xul:image class="star"
+                 onmouseover="document.getBindingParent(this)._hover(4);"
+                 onclick="document.getBindingParent(this).userRating = 4;"/>
+      <xul:image class="star"
+                 onmouseover="document.getBindingParent(this)._hover(5);"
+                 onclick="document.getBindingParent(this).userRating = 5;"/>
+    </content>
+
+    <implementation>
+      <constructor><![CDATA[
+        this._updateStars();
+      ]]></constructor>
+
+      <property name="stars" readonly="true">
+        <getter><![CDATA[
+          return document.getAnonymousNodes(this);
+        ]]></getter>
+      </property>
+
+      <property name="averageRating">
+        <getter><![CDATA[
+          if (this.hasAttribute("averagerating"))
+            return this.getAttribute("averagerating");
+          return -1;
+        ]]></getter>
+        <setter><![CDATA[
+          this.setAttribute("averagerating", val);
+          if (this.showRating == "average")
+            this._updateStars();
+        ]]></setter>
+      </property>
+
+      <property name="userRating">
+        <getter><![CDATA[
+          if (this.hasAttribute("userrating"))
+            return this.getAttribute("userrating");
+          return -1;
+        ]]></getter>
+        <setter><![CDATA[
+          if (this.showRating != "user")
+            return;
+          this.setAttribute("userrating", val);
+          if (this.showRating == "user")
+            this._updateStars();
+        ]]></setter>
+      </property>
+
+      <property name="showRating">
+        <getter><![CDATA[
+          if (this.hasAttribute("showrating"))
+            return this.getAttribute("showrating");
+          return "average";
+        ]]></getter>
+        <setter><![CDATA[
+          if (val != "average" || val != "user")
+            throw Components.Exception("Invalid value", Cr.NS_ERROR_ILLEGAL_VALUE);
+          this.setAttribute("showrating", val);
+          this._updateStars();
+        ]]></setter>
+      </property>
+
+      <method name="_updateStars">
+        <body><![CDATA[
+          var stars = this.stars;
+          var rating = this[this.showRating + "Rating"];
+          // average ratings can be non-whole numbers, round them so they
+          // match to their closest star
+          rating = Math.round(rating);
+          for (let i = 0; i < stars.length; i++)
+            stars[i].setAttribute("on", rating > i);
+        ]]></body>
+      </method>
+
+      <method name="_hover">
+        <parameter name="aScore"/>
+        <body><![CDATA[
+          if (this.showRating != "user")
+            return;
+          var stars = this.stars;
+          for (let i = 0; i < stars.length; i++)
+            stars[i].setAttribute("on", i <= (aScore - 1));
+        ]]></body>
+      </method>
+
+    </implementation>
+
+    <handlers>
+      <handler event="mouseout">
+        this._updateStars();
+      </handler>
+    </handlers>
+  </binding>
+
+  <!-- Download progress - shows graphical progress of download and any
+       related status message. -->
+  <binding id="download-progress">
+    <content>
+      <xul:stack flex="1">
+        <xul:hbox flex="1">
+          <xul:hbox class="start-cap"/>
+          <html:progress anonid="progress" class="progress" max="100"/>
+          <xul:hbox class="end-cap"/>
+        </xul:hbox>
+        <xul:hbox class="status-container">
+          <xul:spacer flex="1"/>
+          <xul:label anonid="status" class="status"/>
+          <xul:spacer flex="1"/>
+          <xul:button anonid="cancel-btn" class="cancel"
+                      tooltiptext="&progress.cancel.tooltip;"
+                      oncommand="document.getBindingParent(this).cancel();"/>
+        </xul:hbox>
+      </xul:stack>
+    </content>
+
+    <implementation>
+      <constructor><![CDATA[
+        var progress = 0;
+        if (this.hasAttribute("progress"))
+          progress = parseInt(this.getAttribute("progress"));
+        this.progress = progress;
+      ]]></constructor>
+
+      <field name="_progress">
+        document.getAnonymousElementByAttribute(this, "anonid", "progress");
+      </field>
+      <field name="_cancel">
+        document.getAnonymousElementByAttribute(this, "anonid", "cancel-btn");
+      </field>
+      <field name="_status">
+        document.getAnonymousElementByAttribute(this, "anonid", "status");
+      </field>
+
+      <property name="progress">
+        <setter><![CDATA[
+          // This property is always updated after maxProgress.
+          if (this.getAttribute("mode") == "determined") {
+            this._progress.value = val;
+          }
+          if (val == this._progress.max)
+            this.setAttribute("complete", true);
+          else
+            this.removeAttribute("complete");
+        ]]></setter>
+      </property>
+
+      <property name="maxProgress">
+        <setter><![CDATA[
+          if (val == -1) {
+            this.setAttribute("mode", "undetermined");
+            this._progress.removeAttribute("value");
+          } else {
+            this.setAttribute("mode", "determined");
+            this._progress.setAttribute("max", val);
+          }
+        ]]></setter>
+      </property>
+
+      <property name="status">
+        <getter><![CDATA[
+          return this._status.value;
+        ]]></getter>
+        <setter><![CDATA[
+          this._status.value = val;
+        ]]></setter>
+      </property>
+
+      <method name="cancel">
+        <body><![CDATA[
+          this.mInstall.cancel();
+        ]]></body>
+      </method>
+    </implementation>
+  </binding>
+
   <!-- Category item - an item in the category list. -->
   <binding id="category"
            extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
     <content align="center">
       <xul:image anonid="icon" class="category-icon"/>
       <xul:label anonid="name" class="category-name" crop="end" flex="1" xbl:inherits="value=name"/>
       <xul:label anonid="badge" class="category-badge" xbl:inherits="value=count"/>
     </content>
@@ -44,9 +230,1496 @@
           this.setAttribute("count", val);
           var event = document.createEvent("Events");
           event.initEvent("CategoryBadgeUpdated", true, true);
           this.dispatchEvent(event);
         ]]></setter>
       </property>
     </implementation>
   </binding>
+
+
+  <!-- Creator link - Name of a user/developer, providing a link if relevant. -->
+  <binding id="creator-link">
+    <content>
+      <xul:label anonid="label" value="&addon.createdBy.label;"/>
+      <xul:label anonid="creator-link" class="creator-link" is="text-link"/>
+      <xul:label anonid="creator-name" class="creator-name"/>
+    </content>
+
+    <implementation>
+      <constructor><![CDATA[
+        if (this.hasAttribute("nameonly") &&
+            this.getAttribute("nameonly") == "true") {
+          this._label.hidden = true;
+        }
+      ]]></constructor>
+
+      <field name="_label">
+        document.getAnonymousElementByAttribute(this, "anonid", "label");
+      </field>
+      <field name="_creatorLink">
+        document.getAnonymousElementByAttribute(this, "anonid", "creator-link");
+      </field>
+      <field name="_creatorName">
+        document.getAnonymousElementByAttribute(this, "anonid", "creator-name");
+      </field>
+
+      <method name="setCreator">
+        <parameter name="aCreator"/>
+        <parameter name="aHomepageURL"/>
+        <body><![CDATA[
+          if (!aCreator) {
+            this.collapsed = true;
+            return;
+          }
+          this.collapsed = false;
+          var url = aCreator.url || aHomepageURL;
+          var showLink = !!url;
+          if (showLink) {
+            this._creatorLink.value = aCreator.name;
+            this._creatorLink.href = url;
+          } else {
+            this._creatorName.value = aCreator.name;
+          }
+          this._creatorLink.hidden = !showLink;
+          this._creatorName.hidden = showLink;
+        ]]></body>
+      </method>
+    </implementation>
+  </binding>
+
+
+  <!-- Install status - Displays the status of an install/upgrade. -->
+  <binding id="install-status">
+    <content>
+      <xul:label anonid="message"/>
+      <xul:box anonid="progress" class="download-progress"/>
+      <xul:button anonid="install-remote-btn" hidden="true"
+                  class="addon-control install" label="&addon.install.label;"
+                  tooltiptext="&addon.install.tooltip;"
+                  oncommand="document.getBindingParent(this).installRemote();"/>
+    </content>
+
+    <implementation>
+      <constructor><![CDATA[
+        if (this.mInstall)
+          this.initWithInstall(this.mInstall);
+        else if (this.mControl.mAddon.install)
+          this.initWithInstall(this.mControl.mAddon.install);
+        else
+          this.refreshState();
+      ]]></constructor>
+
+      <destructor><![CDATA[
+        if (this.mInstall)
+          this.mInstall.removeListener(this);
+      ]]></destructor>
+
+      <field name="_message">
+        document.getAnonymousElementByAttribute(this, "anonid", "message");
+      </field>
+      <field name="_progress">
+        document.getAnonymousElementByAttribute(this, "anonid", "progress");
+      </field>
+      <field name="_installRemote">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "install-remote-btn");
+      </field>
+      <field name="_undo">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "undo-btn");
+      </field>
+
+      <method name="initWithInstall">
+        <parameter name="aInstall"/>
+        <body><![CDATA[
+          if (this.mInstall) {
+            this.mInstall.removeListener(this);
+            this.mInstall = null;
+          }
+          this.mInstall = aInstall;
+          this._progress.mInstall = aInstall;
+          this.refreshState();
+          this.mInstall.addListener(this);
+        ]]></body>
+      </method>
+
+      <method name="refreshState">
+        <body><![CDATA[
+          var showInstallRemote = false;
+
+          if (this.mInstall) {
+            switch (this.mInstall.state) {
+              case AddonManager.STATE_AVAILABLE:
+                if (this.mControl.getAttribute("remote") != "true")
+                  break;
+
+                this._progress.hidden = true;
+                showInstallRemote = true;
+                break;
+              case AddonManager.STATE_DOWNLOADING:
+                this.showMessage("installDownloading");
+                break;
+              case AddonManager.STATE_CHECKING:
+                this.showMessage("installVerifying");
+                break;
+              case AddonManager.STATE_DOWNLOADED:
+                this.showMessage("installDownloaded");
+                break;
+              case AddonManager.STATE_DOWNLOAD_FAILED:
+                // XXXunf expose what error occured (bug 553487)
+                this.showMessage("installDownloadFailed", true);
+                break;
+              case AddonManager.STATE_INSTALLING:
+                this.showMessage("installInstalling");
+                break;
+              case AddonManager.STATE_INSTALL_FAILED:
+                // XXXunf expose what error occured (bug 553487)
+                this.showMessage("installFailed", true);
+                break;
+              case AddonManager.STATE_CANCELLED:
+                this.showMessage("installCancelled", true);
+                break;
+            }
+          }
+
+          this._installRemote.hidden = !showInstallRemote;
+
+          if ("refreshInfo" in this.mControl)
+            this.mControl.refreshInfo();
+        ]]></body>
+      </method>
+
+      <method name="showMessage">
+        <parameter name="aMsgId"/>
+        <parameter name="aHideProgress"/>
+        <body><![CDATA[
+          this._message.setAttribute("hidden", !aHideProgress);
+          this._progress.setAttribute("hidden", !!aHideProgress);
+
+          var msg = gStrings.ext.GetStringFromName(aMsgId);
+          if (aHideProgress)
+            this._message.value = msg;
+          else
+            this._progress.status = msg;
+        ]]></body>
+      </method>
+
+      <method name="installRemote">
+        <body><![CDATA[
+          if (this.mControl.getAttribute("remote") != "true")
+            return;
+
+          delete this.mControl.mAddon;
+          this.mControl.mInstall = this.mInstall;
+          this.mControl.setAttribute("status", "installing");
+          let prompt = Services.prefs.getBoolPref("extensions.webextPermissionPrompts", false);
+          if (prompt) {
+            this.mInstall.promptHandler = info => new Promise((resolve, reject) => {
+              // Skip prompts for non-webextensions
+              if (!info.addon.userPermissions) {
+                resolve();
+                return;
+              }
+              let subject = {
+                wrappedJSObject: {
+                  target: window.docShell.chromeEventHandler,
+                  info: {
+                    addon: info.addon,
+                    source: "AMO",
+                    icon: info.addon.iconURL,
+                    permissions: info.addon.userPermissions,
+                    resolve,
+                    reject,
+                  },
+                },
+              };
+              Services.obs.notifyObservers(subject, "webextension-permission-prompt");
+            });
+          }
+          this.mInstall.install();
+        ]]></body>
+      </method>
+
+      <method name="onDownloadStarted">
+        <body><![CDATA[
+          this.refreshState();
+        ]]></body>
+      </method>
+
+      <method name="onDownloadEnded">
+        <body><![CDATA[
+          this.refreshState();
+        ]]></body>
+      </method>
+
+      <method name="onDownloadFailed">
+        <body><![CDATA[
+          this.refreshState();
+        ]]></body>
+      </method>
+
+      <method name="onDownloadProgress">
+        <body><![CDATA[
+          this._progress.maxProgress = this.mInstall.maxProgress;
+          this._progress.progress = this.mInstall.progress;
+        ]]></body>
+      </method>
+
+      <method name="onInstallStarted">
+        <body><![CDATA[
+          this._progress.progress = 0;
+          this.refreshState();
+        ]]></body>
+      </method>
+
+      <method name="onInstallEnded">
+        <body><![CDATA[
+          this.refreshState();
+          if ("onInstallCompleted" in this.mControl)
+            this.mControl.onInstallCompleted();
+        ]]></body>
+      </method>
+
+      <method name="onInstallFailed">
+        <body><![CDATA[
+          this.refreshState();
+        ]]></body>
+      </method>
+    </implementation>
+  </binding>
+
+
+  <!-- Addon - base - parent binding of any item representing an addon. -->
+  <binding id="addon-base"
+           extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+    <implementation>
+      <property name="isLegacy" readonly="true">
+        <getter><![CDATA[
+          if (this.mAddon.install) {
+            return false;
+          }
+          return isLegacyExtension(this.mAddon);
+        ]]></getter>
+      </property>
+
+      <method name="hasPermission">
+        <parameter name="aPerm"/>
+        <body><![CDATA[
+          var perm = AddonManager["PERM_CAN_" + aPerm.toUpperCase()];
+          return !!(this.mAddon.permissions & perm);
+        ]]></body>
+      </method>
+
+      <method name="isPending">
+        <parameter name="aAction"/>
+        <body><![CDATA[
+          var action = AddonManager["PENDING_" + aAction.toUpperCase()];
+          return !!(this.mAddon.pendingOperations & action);
+        ]]></body>
+      </method>
+
+      <method name="typeHasFlag">
+        <parameter name="aFlag"/>
+        <body><![CDATA[
+          let flag = AddonManager["TYPE_" + aFlag];
+          let type = AddonManager.addonTypes[this.mAddon.type];
+
+          return !!(type.flags & flag);
+        ]]></body>
+      </method>
+
+      <method name="onUninstalled">
+        <body><![CDATA[
+          this.remove();
+        ]]></body>
+      </method>
+    </implementation>
+  </binding>
+
+
+  <!-- Addon - generic - A normal addon item, or an update to one -->
+  <binding id="addon-generic"
+           extends="chrome://mozapps/content/extensions/extensions.xml#addon-base">
+    <content tooltiptext="&addon.details.tooltip;">
+      <xul:hbox anonid="warning-container"
+                class="warning">
+        <xul:image class="warning-icon"/>
+        <xul:label anonid="warning" flex="1"/>
+        <xul:label anonid="warning-link" is="text-link"/>
+        <xul:button anonid="warning-btn" class="button-link" hidden="true"/>
+        <xul:spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
+      </xul:hbox>
+      <xul:hbox anonid="error-container"
+                class="error">
+        <xul:image class="error-icon"/>
+        <xul:label anonid="error" flex="1"/>
+        <xul:label anonid="error-link" hidden="true" is="text-link"/>
+        <xul:spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
+      </xul:hbox>
+      <xul:hbox anonid="pending-container"
+                class="pending">
+        <xul:image class="pending-icon"/>
+        <xul:label anonid="pending" flex="1"/>
+        <xul:button anonid="undo-btn" class="button-link"
+                    label="&addon.undoAction.label;"
+                    tooltipText="&addon.undoAction.tooltip;"
+                    oncommand="document.getBindingParent(this).undo();"/>
+        <xul:spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
+      </xul:hbox>
+
+      <xul:image class="card-heading-image" anonid="theme-screenshot" xbl:inherits="src=previewURL"/>
+
+      <xul:hbox class="content-container">
+        <xul:vbox class="icon-container">
+          <xul:image anonid="icon" class="icon"/>
+        </xul:vbox>
+        <xul:vbox class="content-inner-container" flex="1">
+          <xul:hbox class="basicinfo-container">
+              <xul:hbox class="name-container">
+                <xul:label anonid="name" class="name" crop="end" flex="1"
+                           tooltip="addonitem-tooltip" xbl:inherits="xbl:text=name"/>
+                <xul:label anonid="legacy" class="legacy-warning" value="&addon.legacy.label;" is="text-link"/>
+                <xul:label class="disabled-postfix" value="&addon.disabled.postfix;"/>
+                <xul:label class="update-postfix" value="&addon.update.postfix;"/>
+                <xul:spacer flex="5000"/> <!-- Necessary to make the name crop -->
+              </xul:hbox>
+            <xul:label anonid="date-updated" class="date-updated"
+                       unknown="&addon.unknownDate;"/>
+          </xul:hbox>
+
+          <xul:hbox class="advancedinfo-container" flex="1">
+            <xul:vbox class="description-outer-container" flex="1">
+              <xul:hbox class="description-container">
+                <xul:label anonid="description" class="description" crop="end" flex="1"/>
+                <xul:spacer flex="5000"/> <!-- Necessary to make the description crop -->
+              </xul:hbox>
+              <xul:hbox class="relnotes-toggle-container">
+                <xul:button anonid="relnotes-toggle-btn" class="relnotes-toggle"
+                            hidden="true" label="&cmd.showReleaseNotes.label;"
+                            tooltiptext="&cmd.showReleaseNotes.tooltip;"
+                            showlabel="&cmd.showReleaseNotes.label;"
+                            showtooltip="&cmd.showReleaseNotes.tooltip;"
+                            hidelabel="&cmd.hideReleaseNotes.label;"
+                            hidetooltip="&cmd.hideReleaseNotes.tooltip;"
+                            oncommand="document.getBindingParent(this).toggleReleaseNotes();"/>
+              </xul:hbox>
+              <xul:vbox anonid="relnotes-container" class="relnotes-container">
+                <xul:label class="relnotes-header" value="&addon.releaseNotes.label;"/>
+                <xul:label anonid="relnotes-loading" value="&addon.loadingReleaseNotes.label;"/>
+                <xul:label anonid="relnotes-error" hidden="true"
+                           value="&addon.errorLoadingReleaseNotes.label;"/>
+                <xul:vbox anonid="relnotes" class="relnotes"/>
+              </xul:vbox>
+            </xul:vbox>
+          </xul:hbox>
+        </xul:vbox>
+        <xul:vbox class="status-control-wrapper">
+          <xul:hbox class="status-container">
+            <xul:hbox anonid="checking-update" hidden="true">
+              <xul:image class="spinner"/>
+              <xul:label value="&addon.checkingForUpdates.label;"/>
+            </xul:hbox>
+            <xul:vbox anonid="update-available" class="update-available"
+                      hidden="true">
+              <xul:checkbox anonid="include-update" class="include-update"
+                            label="&addon.includeUpdate.label;" checked="true"
+                            oncommand="document.getBindingParent(this).onIncludeUpdateChanged();"/>
+              <xul:hbox class="update-info-container">
+                <xul:label class="update-available-notice"
+                           value="&addon.updateAvailable.label;"/>
+                <xul:button anonid="update-btn" class="addon-control update"
+                            label="&addon.updateNow.label;"
+                            tooltiptext="&addon.updateNow.tooltip;"
+                            oncommand="document.getBindingParent(this).upgrade();"/>
+              </xul:hbox>
+            </xul:vbox>
+            <xul:hbox anonid="install-status" class="install-status"
+                      hidden="true"/>
+          </xul:hbox>
+          <xul:hbox anonid="control-container" class="control-container" flex="1">
+            <xul:button anonid="preferences-btn"
+                        class="addon-control preferences"
+#ifdef XP_WIN
+                        label="&cmd.showPreferencesWin.label;"
+                        tooltiptext="&cmd.showPreferencesWin.tooltip;"
+#else
+                        label="&cmd.showPreferencesUnix.label;"
+                        tooltiptext="&cmd.showPreferencesUnix.tooltip;"
+#endif
+                        oncommand="document.getBindingParent(this).showPreferences();"/>
+            <xul:button anonid="enable-btn"  class="addon-control enable"
+                        label="&cmd.enableAddon.label;"
+                        oncommand="document.getBindingParent(this).userDisabled = false;"/>
+            <xul:button anonid="disable-btn" class="addon-control disable"
+                        label="&cmd.disableAddon.label;"
+                        oncommand="document.getBindingParent(this).userDisabled = true;"/>
+            <xul:button anonid="replacement-btn" class="addon-control replacement"
+                        label="&cmd.findReplacement.label;"
+                        oncommand="document.getBindingParent(this).findReplacement();"/>
+            <xul:button anonid="remove-btn" class="addon-control remove"
+                        label="&cmd.uninstallAddon.label;"
+                        oncommand="document.getBindingParent(this).uninstall();"/>
+            <xul:menulist anonid="state-menulist"
+                          class="addon-control state"
+                          flex="1"
+                          tooltiptext="&cmd.stateMenu.tooltip;">
+              <xul:menupopup>
+                <xul:menuitem anonid="ask-to-activate-menuitem"
+                              class="addon-control"
+                              label="&cmd.askToActivate.label;"
+                              tooltiptext="&cmd.askToActivate.tooltip;"
+                              oncommand="document.getBindingParent(this).userDisabled = AddonManager.STATE_ASK_TO_ACTIVATE;"/>
+                <xul:menuitem anonid="always-activate-menuitem"
+                              class="addon-control"
+                              label="&cmd.alwaysActivate.label;"
+                              tooltiptext="&cmd.alwaysActivate.tooltip;"
+                              oncommand="document.getBindingParent(this).userDisabled = false;"/>
+                <xul:menuitem anonid="never-activate-menuitem"
+                              class="addon-control"
+                              label="&cmd.neverActivate.label;"
+                              tooltiptext="&cmd.neverActivate.tooltip;"
+                              oncommand="document.getBindingParent(this).userDisabled = true;"/>
+              </xul:menupopup>
+            </xul:menulist>
+          </xul:hbox>
+        </xul:vbox>
+      </xul:hbox>
+      <xul:hbox class="description-container privateBrowsing-notice-container">
+        <xul:label anonid="privateBrowsing" class="description privateBrowsing-notice" value="&addon.privateBrowsing.label;"/>
+      </xul:hbox>
+    </content>
+
+    <implementation>
+      <constructor><![CDATA[
+        window.customElements.upgrade(this._stateMenulist);
+        window.customElements.upgrade(this._enableBtn);
+        window.customElements.upgrade(this._disableBtn);
+        window.customElements.upgrade(this._askToActivateMenuitem);
+        window.customElements.upgrade(this._alwaysActivateMenuitem);
+        window.customElements.upgrade(this._neverActivateMenuitem);
+
+        this._installStatus = document.getAnonymousElementByAttribute(this, "anonid", "install-status");
+        this._installStatus.mControl = this;
+
+        this.setAttribute("contextmenu", "addonitem-popup");
+
+        this._showStatus("none");
+
+        this._initWithAddon(this.mAddon);
+
+        gEventManager.registerAddonListener(this, this.mAddon.id);
+      ]]></constructor>
+
+      <destructor><![CDATA[
+        gEventManager.unregisterAddonListener(this, this.mAddon.id);
+      ]]></destructor>
+
+      <field name="_warningContainer">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "warning-container");
+      </field>
+      <field name="_warning">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "warning");
+      </field>
+      <field name="_warningLink">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "warning-link");
+      </field>
+      <field name="_warningBtn">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "warning-btn");
+      </field>
+      <field name="_errorContainer">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "error-container");
+      </field>
+      <field name="_error">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "error");
+      </field>
+      <field name="_errorLink">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "error-link");
+      </field>
+      <field name="_pendingContainer">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "pending-container");
+      </field>
+      <field name="_pending">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "pending");
+      </field>
+      <field name="_infoContainer">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "info-container");
+      </field>
+      <field name="_info">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "info");
+      </field>
+      <field name="_icon">
+        document.getAnonymousElementByAttribute(this, "anonid", "icon");
+      </field>
+      <field name="_dateUpdated">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "date-updated");
+      </field>
+      <field name="_description">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "description");
+      </field>
+      <field name="_stateMenulist">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "state-menulist");
+      </field>
+      <field name="_askToActivateMenuitem">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "ask-to-activate-menuitem");
+      </field>
+      <field name="_alwaysActivateMenuitem">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "always-activate-menuitem");
+      </field>
+      <field name="_neverActivateMenuitem">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "never-activate-menuitem");
+      </field>
+      <field name="_preferencesBtn">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "preferences-btn");
+      </field>
+      <field name="_enableBtn">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "enable-btn");
+      </field>
+      <field name="_disableBtn">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "disable-btn");
+      </field>
+      <field name="_removeBtn">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "remove-btn");
+      </field>
+      <field name="_updateBtn">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "update-btn");
+      </field>
+      <field name="_controlContainer">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "control-container");
+      </field>
+      <field name="_installStatus">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "install-status");
+      </field>
+      <field name="_checkingUpdate">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "checking-update");
+      </field>
+      <field name="_updateAvailable">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "update-available");
+      </field>
+      <field name="_includeUpdate">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "include-update");
+      </field>
+      <field name="_relNotesLoaded">false</field>
+      <field name="_relNotesToggle">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "relnotes-toggle-btn");
+      </field>
+      <field name="_relNotesLoading">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "relnotes-loading");
+      </field>
+      <field name="_relNotesError">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "relnotes-error");
+      </field>
+      <field name="_relNotesContainer">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "relnotes-container");
+      </field>
+      <field name="_relNotes">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "relnotes");
+      </field>
+
+      <property name="userDisabled">
+        <getter><![CDATA[
+          return this.mAddon.userDisabled;
+        ]]></getter>
+        <setter><![CDATA[
+          if (val === true) {
+            gViewController.commands.cmd_disableItem.doCommand(this.mAddon);
+          } else if (val === false) {
+            gViewController.commands.cmd_enableItem.doCommand(this.mAddon);
+          } else {
+            this.mAddon.userDisabled = val;
+          }
+        ]]></setter>
+      </property>
+
+      <property name="includeUpdate">
+        <getter><![CDATA[
+          return this._includeUpdate.checked && !!this.mManualUpdate;
+        ]]></getter>
+        <setter><![CDATA[
+          // XXXunf Eventually, we'll want to persist this for individual
+          //        updates - see bug 594619.
+          this._includeUpdate.checked = !!val;
+        ]]></setter>
+      </property>
+
+      <method name="_initWithAddon">
+        <parameter name="aAddon"/>
+        <body><![CDATA[
+          this.mAddon = aAddon;
+
+          this._installStatus.mAddon = this.mAddon;
+          this._updateDates();
+          this._updateState();
+
+          this.setAttribute("name", aAddon.name);
+
+          var iconURL = AddonManager.getPreferredIconURL(aAddon, 24, window);
+          if (iconURL)
+            this._icon.src = iconURL;
+          else
+            this._icon.src = "";
+
+          if (this.mAddon.description)
+            this._description.value = this.mAddon.description;
+          else
+            this._description.hidden = true;
+
+          // Set a previewURL for themes if one exists.
+          let previewURL = this.mAddon.type == "theme" &&
+            this.mAddon.screenshots &&
+            this.mAddon.screenshots[0] &&
+            this.mAddon.screenshots[0].url;
+          this.setAttribute("previewURL", previewURL ? previewURL : "");
+          this.setAttribute("hasPreview", previewURL ? "true" : "fase");
+
+          let legacyWarning = legacyExtensionsEnabled && !this.mAddon.install &&
+            isLegacyExtension(this.mAddon);
+          this.setAttribute("legacy", legacyWarning);
+          document.getAnonymousElementByAttribute(this, "anonid", "legacy").href = SUPPORT_URL + "webextensions";
+
+          if (!allowPrivateBrowsingByDefault && this.mAddon.type === "extension") {
+            ExtensionPermissions.get(this.mAddon.id).then((perms) => {
+              let allowed = perms.permissions.includes("internal:privateBrowsingAllowed");
+              this.setAttribute("privateBrowsing", allowed);
+              if (!allowed && PrivateBrowsingUtils.isContentWindowPrivate(window)) {
+                // Hide the preferences button if the current window is
+                // private and the addon is not allowed to access it.
+                this._preferencesBtn.hidden = true;
+              }
+            });
+          }
+
+          if (!("applyBackgroundUpdates" in this.mAddon) ||
+              (this.mAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DISABLE ||
+               (this.mAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DEFAULT &&
+                !AddonManager.autoUpdateDefault))) {
+            AddonManager.getAllInstalls().then(aInstallsList => {
+              // This can return after the binding has been destroyed,
+              // so try to detect that and return early
+              if (!("onNewInstall" in this))
+                return;
+              for (let install of aInstallsList) {
+                if (install.existingAddon &&
+                    install.existingAddon.id == this.mAddon.id &&
+                    install.state == AddonManager.STATE_AVAILABLE) {
+                  this.onNewInstall(install);
+                  this.onIncludeUpdateChanged();
+                }
+              }
+            });
+          }
+        ]]></body>
+      </method>
+
+      <method name="_showStatus">
+        <parameter name="aType"/>
+        <body><![CDATA[
+          this._controlContainer.hidden = aType != "none" &&
+                                          !(aType == "update-available" && !this.hasAttribute("upgrade"));
+
+          this._installStatus.hidden = aType != "progress";
+          if (aType == "progress")
+            this._installStatus.refreshState();
+          this._checkingUpdate.hidden = aType != "checking-update";
+          this._updateAvailable.hidden = aType != "update-available";
+          this._relNotesToggle.hidden = !(this.mManualUpdate ?
+                                          this.mManualUpdate.releaseNotesURI :
+                                          this.mAddon.releaseNotesURI);
+        ]]></body>
+      </method>
+
+      <method name="_updateDates">
+        <body><![CDATA[
+          function formatDate(aDate) {
+            const dtOptions = { year: "numeric", month: "long", day: "numeric" };
+            return aDate.toLocaleDateString(undefined, dtOptions);
+          }
+
+          if (this.mAddon.updateDate)
+            this._dateUpdated.value = formatDate(this.mAddon.updateDate);
+          else
+            this._dateUpdated.value = this._dateUpdated.getAttribute("unknown");
+        ]]></body>
+      </method>
+
+      <method name="_updateState">
+        <body><![CDATA[
+          if (this.parentNode.selectedItem == this)
+            gViewController.updateCommands();
+
+          var pending = this.mAddon.pendingOperations;
+          if (pending & AddonManager.PENDING_UNINSTALL) {
+            this.removeAttribute("notification");
+
+            // We don't care about pending operations other than uninstall.
+            // They're transient, and cannot be undone.
+            this.setAttribute("pending", "uninstall");
+            this._pending.textContent = gStrings.ext.formatStringFromName(
+              "notification.restartless-uninstall",
+              [this.mAddon.name]);
+          } else {
+            this.removeAttribute("pending");
+
+            var isUpgrade = this.hasAttribute("upgrade");
+            var install = this._installStatus.mInstall;
+
+            if (install && install.state == AddonManager.STATE_DOWNLOAD_FAILED) {
+              this.setAttribute("notification", "warning");
+              this._warning.textContent = gStrings.ext.formatStringFromName(
+                "notification.downloadError",
+                [this.mAddon.name]
+              );
+              this._warningBtn.label = gStrings.ext.GetStringFromName("notification.downloadError.retry");
+              this._warningBtn.tooltipText = gStrings.ext.GetStringFromName("notification.downloadError.retry.tooltip");
+              this._warningBtn.setAttribute("oncommand", "document.getBindingParent(this).retryInstall();");
+              this._warningBtn.hidden = false;
+              this._warningLink.hidden = true;
+            } else if (install && install.state == AddonManager.STATE_INSTALL_FAILED) {
+              this.setAttribute("notification", "warning");
+              this._warning.textContent = gStrings.ext.formatStringFromName(
+                "notification.installError",
+                [this.mAddon.name]
+              );
+              this._warningBtn.label = gStrings.ext.GetStringFromName("notification.installError.retry");
+              this._warningBtn.tooltipText = gStrings.ext.GetStringFromName("notification.downloadError.retry.tooltip");
+              this._warningBtn.setAttribute("oncommand", "document.getBindingParent(this).retryInstall();");
+              this._warningBtn.hidden = false;
+              this._warningLink.hidden = true;
+            } else if (!isUpgrade && this.mAddon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) {
+              this.setAttribute("notification", "error");
+              this._error.textContent = gStrings.ext.formatStringFromName(
+                "notification.blocked",
+                [this.mAddon.name]
+              );
+              this._errorLink.value = gStrings.ext.GetStringFromName("notification.blocked.link");
+              this.mAddon.getBlocklistURL().then(url => {
+                this._errorLink.href = url;
+                this._errorLink.hidden = false;
+              });
+            } else if (!isUpgrade && isDisabledUnsigned(this.mAddon)) {
+              this.setAttribute("notification", "error");
+              this._error.textContent = gStrings.ext.formatStringFromName(
+                "notification.unsignedAndDisabled", [this.mAddon.name, gStrings.brandShortName]
+              );
+              this._errorLink.value = gStrings.ext.GetStringFromName("notification.unsigned.link");
+              this._errorLink.href = SUPPORT_URL + "unsigned-addons";
+              this._errorLink.hidden = false;
+            } else if ((!isUpgrade && !this.mAddon.isCompatible) && (AddonManager.checkCompatibility
+            || (this.mAddon.blocklistState != Ci.nsIBlocklistService.STATE_SOFTBLOCKED))) {
+              this.setAttribute("notification", "warning");
+              this._warning.textContent = gStrings.ext.formatStringFromName(
+                "notification.incompatible",
+                [this.mAddon.name, gStrings.brandShortName, gStrings.appVersion]
+              );
+              this._warningLink.hidden = true;
+              this._warningBtn.hidden = true;
+            } else if (!isUpgrade && !isCorrectlySigned(this.mAddon)) {
+              this.setAttribute("notification", "warning");
+              this._warning.textContent = gStrings.ext.formatStringFromName(
+                "notification.unsigned", [this.mAddon.name, gStrings.brandShortName]
+              );
+              this._warningLink.value = gStrings.ext.GetStringFromName("notification.unsigned.link");
+              this._warningLink.href = SUPPORT_URL + "unsigned-addons";
+              this._warningLink.hidden = false;
+            } else if (!isUpgrade && this.mAddon.blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED) {
+              this.setAttribute("notification", "warning");
+              this._warning.textContent = gStrings.ext.formatStringFromName(
+                "notification.softblocked",
+                [this.mAddon.name]
+              );
+              this._warningLink.value = gStrings.ext.GetStringFromName("notification.softblocked.link");
+              this.mAddon.getBlocklistURL().then(url => {
+                this._warningLink.href = url;
+                this._warningLink.hidden = false;
+              });
+              this._warningBtn.hidden = true;
+            } else if (!isUpgrade && this.mAddon.blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED) {
+              this.setAttribute("notification", "warning");
+              this._warning.textContent = gStrings.ext.formatStringFromName(
+                "notification.outdated",
+                [this.mAddon.name]
+              );
+              this._warningLink.value = gStrings.ext.GetStringFromName("notification.outdated.link");
+              this.mAddon.getBlocklistURL().then(url => {
+                this._warningLink.href = url;
+                this._warningLink.hidden = false;
+              });
+              this._warningBtn.hidden = true;
+            } else if (!isUpgrade && this.mAddon.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE) {
+              this.setAttribute("notification", "error");
+              this._error.textContent = gStrings.ext.formatStringFromName(
+                "notification.vulnerableUpdatable",
+                [this.mAddon.name]
+              );
+              this._errorLink.value = gStrings.ext.GetStringFromName("notification.vulnerableUpdatable.link");
+              this.mAddon.getBlocklistURL().then(url => {
+                this._errorLink.href = url;
+                this._errorLink.hidden = false;
+              });
+            } else if (!isUpgrade && this.mAddon.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE) {
+              this.setAttribute("notification", "error");
+              this._error.textContent = gStrings.ext.formatStringFromName(
+                "notification.vulnerableNoUpdate",
+                [this.mAddon.name]
+              );
+              this._errorLink.value = gStrings.ext.GetStringFromName("notification.vulnerableNoUpdate.link");
+              this.mAddon.getBlocklistURL().then(url => {
+                this._errorLink.href = url;
+                this._errorLink.hidden = false;
+              });
+            } else if (this.mAddon.isGMPlugin && !this.mAddon.isInstalled &&
+                       this.mAddon.isActive) {
+              this.setAttribute("notification", "warning");
+              this._warning.textContent =
+                gStrings.ext.formatStringFromName("notification.gmpPending",
+                                                  [this.mAddon.name]);
+            } else {
+              this.removeAttribute("notification");
+            }
+          }
+
+          this._preferencesBtn.hidden = !this.mAddon.optionsType && this.mAddon.type != "plugin";
+
+          if (this.typeHasFlag("SUPPORTS_ASK_TO_ACTIVATE")) {
+            this._enableBtn.disabled = true;
+            this._disableBtn.disabled = true;
+            this._askToActivateMenuitem.disabled = !this.hasPermission("ask_to_activate");
+            let alwaysActivateProp = this.mAddon.isFlashPlugin ? "hidden" : "disabled";
+            this._alwaysActivateMenuitem[alwaysActivateProp] = !this.hasPermission("enable");
+            this._neverActivateMenuitem.disabled = !this.hasPermission("disable");
+            if (!this.mAddon.isActive) {
+              this._stateMenulist.selectedItem = this._neverActivateMenuitem;
+            } else if (this.mAddon.userDisabled == AddonManager.STATE_ASK_TO_ACTIVATE) {
+              this._stateMenulist.selectedItem = this._askToActivateMenuitem;
+            } else {
+              this._stateMenulist.selectedItem = this._alwaysActivateMenuitem;
+            }
+            let hasActivatePermission =
+              ["ask_to_activate", "enable", "disable"].some(perm => this.hasPermission(perm));
+            this._stateMenulist.disabled = !hasActivatePermission;
+            this._stateMenulist.hidden = false;
+            this._askToActivateMenuitem.classList.add("no-auto-hide");
+            this._alwaysActivateMenuitem.classList.add("no-auto-hide");
+            this._neverActivateMenuitem.classList.add("no-auto-hide");
+            this._stateMenulist.classList.add("no-auto-hide");
+          } else {
+            this._stateMenulist.hidden = true;
+
+            let enableTooltip = gViewController.commands.cmd_enableItem
+                                               .getTooltip(this.mAddon);
+            this._enableBtn.setAttribute("tooltiptext", enableTooltip);
+            if (this.hasPermission("enable")) {
+              this._enableBtn.hidden = false;
+            } else {
+              this._enableBtn.hidden = true;
+            }
+
+            let disableTooltip = gViewController.commands.cmd_disableItem
+                                                .getTooltip(this.mAddon);
+            this._disableBtn.setAttribute("tooltiptext", disableTooltip);
+            if (this.hasPermission("disable")) {
+              this._disableBtn.hidden = false;
+            } else {
+              this._disableBtn.hidden = true;
+            }
+          }
+
+          let uninstallTooltip = gViewController.commands.cmd_uninstallItem
+                                                .getTooltip(this.mAddon);
+          this._removeBtn.setAttribute("tooltiptext", uninstallTooltip);
+          if (this.hasPermission("uninstall")) {
+            this._removeBtn.hidden = false;
+          } else {
+            this._removeBtn.hidden = true;
+          }
+
+          this.setAttribute("active", this.mAddon.isActive);
+
+          var showProgress = (this.mAddon.install &&
+                              this.mAddon.install.state != AddonManager.STATE_INSTALLED);
+          this._showStatus(showProgress ? "progress" : "none");
+        ]]></body>
+      </method>
+
+      <method name="_fetchReleaseNotes">
+        <parameter name="aURI"/>
+        <body><![CDATA[
+          let sendToggleEvent = () => {
+            var event = document.createEvent("Events");
+            event.initEvent("RelNotesToggle", true, true);
+            this.dispatchEvent(event);
+          };
+
+          if (!aURI || this._relNotesLoaded) {
+            sendToggleEvent();
+            return;
+          }
+
+          this._relNotesLoaded = true;
+          this._relNotesLoading.hidden = false;
+          this._relNotesError.hidden = true;
+
+          loadReleaseNotes(aURI).then(fragment => {
+            this._relNotesLoading.hidden = true;
+            this._relNotes.appendChild(fragment);
+            if (this.hasAttribute("show-relnotes")) {
+              var container = this._relNotesContainer;
+              container.style.height = container.scrollHeight + "px";
+            }
+            sendToggleEvent();
+          }, () => {
+            this._relNotesLoading.hidden = true;
+            this._relNotesError.hidden = false;
+            this._relNotesLoaded = false; // allow loading to be re-tried
+            sendToggleEvent();
+          });
+        ]]></body>
+      </method>
+
+      <method name="toggleReleaseNotes">
+        <body><![CDATA[
+          if (this.hasAttribute("show-relnotes")) {
+            this._relNotesContainer.style.height = "0px";
+            this.removeAttribute("show-relnotes");
+            this._relNotesToggle.setAttribute(
+              "label",
+              this._relNotesToggle.getAttribute("showlabel")
+            );
+            this._relNotesToggle.setAttribute(
+              "tooltiptext",
+              this._relNotesToggle.getAttribute("showtooltip")
+            );
+            let event = document.createEvent("Events");
+            event.initEvent("RelNotesToggle", true, true);
+            this.dispatchEvent(event);
+          } else {
+            this._relNotesContainer.style.height = this._relNotesContainer.scrollHeight +
+                                                   "px";
+            this.setAttribute("show-relnotes", true);
+            this._relNotesToggle.setAttribute(
+              "label",
+              this._relNotesToggle.getAttribute("hidelabel")
+            );
+            this._relNotesToggle.setAttribute(
+              "tooltiptext",
+              this._relNotesToggle.getAttribute("hidetooltip")
+            );
+            var uri = this.mManualUpdate ?
+                      this.mManualUpdate.releaseNotesURI :
+                      this.mAddon.releaseNotesURI;
+            this._fetchReleaseNotes(uri);
+
+            // Dispatch an event so extensions.js can record telemetry.
+            let event = document.createEvent("Events");
+            event.initEvent("RelNotesShow", true, true);
+            this.dispatchEvent(event);
+          }
+        ]]></body>
+      </method>
+
+      <method name="undo">
+        <body><![CDATA[
+          gViewController.commands.cmd_cancelOperation.doCommand(this.mAddon);
+        ]]></body>
+      </method>
+
+      <method name="uninstall">
+        <body><![CDATA[
+          // If the type doesn't support undoing of restartless uninstalls,
+          // then we fake it by just disabling it it, and doing the real
+          // uninstall later.
+          if (this.typeHasFlag("SUPPORTS_UNDO_RESTARTLESS_UNINSTALL")) {
+            this.mAddon.uninstall(true);
+          } else {
+            this.setAttribute("wasDisabled", this.mAddon.userDisabled);
+
+            // We must set userDisabled to true first, this will call
+            // _updateState which will clear any pending attribute set.
+            this.mAddon.disable().then(() => {
+              // This won't update any other add-on manager views (bug 582002)
+              this.setAttribute("pending", "uninstall");
+            });
+          }
+
+          // Dispatch an event so extensions.js can track telemetry.
+          var event = document.createEvent("Events");
+          event.initEvent("Uninstall", true, true);
+          this.dispatchEvent(event);
+        ]]></body>
+      </method>
+
+      <method name="showPreferences">
+        <body><![CDATA[
+          gViewController.doCommand("cmd_showItemPreferences", this.mAddon);
+        ]]></body>
+      </method>
+
+      <method name="upgrade">
+        <body><![CDATA[
+          var install = this.mManualUpdate;
+          delete this.mManualUpdate;
+          install.install();
+        ]]></body>
+      </method>
+
+      <method name="retryInstall">
+        <body><![CDATA[
+          var install = this._installStatus.mInstall;
+          if (!install)
+            return;
+          if (install.state != AddonManager.STATE_DOWNLOAD_FAILED &&
+              install.state != AddonManager.STATE_INSTALL_FAILED)
+            return;
+          install.install();
+        ]]></body>
+      </method>
+
+      <method name="showInDetailView">
+        <body><![CDATA[
+          gViewController.loadView("addons://detail/" +
+                                   encodeURIComponent(this.mAddon.id));
+        ]]></body>
+      </method>
+
+      <method name="findReplacement">
+        <body><![CDATA[
+          let url = (this.mAddon.type == "theme") ?
+            SUPPORT_URL + "complete-themes" :
+            `https://addons.mozilla.org/find-replacement/?guid=${this.mAddon.id}`;
+            openURL(url);
+        ]]></body>
+      </method>
+
+      <method name="onIncludeUpdateChanged">
+        <body><![CDATA[
+          var event = document.createEvent("Events");
+          event.initEvent("IncludeUpdateChanged", true, true);
+          this.dispatchEvent(event);
+        ]]></body>
+      </method>
+
+      <method name="onEnabling">
+        <body><![CDATA[
+          this._updateState();
+        ]]></body>
+      </method>
+
+      <method name="onEnabled">
+        <body><![CDATA[
+          this._updateState();
+        ]]></body>
+      </method>
+
+      <method name="onDisabling">
+        <body><![CDATA[
+          this._updateState();
+        ]]></body>
+      </method>
+
+      <method name="onDisabled">
+        <body><![CDATA[
+          this._updateState();
+        ]]></body>
+      </method>
+
+      <method name="onUninstalling">
+        <body><![CDATA[
+          this._updateState();
+        ]]></body>
+      </method>
+
+      <method name="onOperationCancelled">
+        <body><![CDATA[
+          this._updateState();
+        ]]></body>
+      </method>
+
+      <method name="onPropertyChanged">
+        <parameter name="aProperties"/>
+        <body><![CDATA[
+          if (aProperties.includes("appDisabled") ||
+              aProperties.includes("signedState") ||
+              aProperties.includes("userDisabled"))
+            this._updateState();
+        ]]></body>
+      </method>
+
+      <method name="onUpdateAvailable">
+        <body><![CDATA[
+          this._showStatus("update-available");
+        ]]></body>
+      </method>
+
+      <method name="onNoUpdateAvailable">
+        <body><![CDATA[
+          this._showStatus("none");
+        ]]></body>
+      </method>
+
+      <method name="onCheckingUpdate">
+        <body><![CDATA[
+          this._showStatus("checking-update");
+        ]]></body>
+      </method>
+
+      <method name="onCompatibilityUpdateAvailable">
+        <body><![CDATA[
+          this._updateState();
+        ]]></body>
+      </method>
+
+      <method name="onExternalInstall">
+        <parameter name="aAddon"/>
+        <parameter name="aExistingAddon"/>
+        <body><![CDATA[
+          if (aExistingAddon.id != this.mAddon.id)
+            return;
+
+          this._initWithAddon(aAddon);
+        ]]></body>
+      </method>
+
+      <method name="onNewInstall">
+        <parameter name="aInstall"/>
+        <body><![CDATA[
+          if (this.mAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_ENABLE)
+            return;
+          if (this.mAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DEFAULT &&
+              AddonManager.autoUpdateDefault)
+            return;
+
+          this.mManualUpdate = aInstall;
+          this._showStatus("update-available");
+        ]]></body>
+      </method>
+
+      <method name="onDownloadStarted">
+        <parameter name="aInstall"/>
+        <body><![CDATA[
+          this._updateState();
+          this._showStatus("progress");
+          this._installStatus.initWithInstall(aInstall);
+        ]]></body>
+      </method>
+
+      <method name="onInstallStarted">
+        <parameter name="aInstall"/>
+        <body><![CDATA[
+          this._updateState();
+          this._showStatus("progress");
+          this._installStatus.initWithInstall(aInstall);
+        ]]></body>
+      </method>
+
+      <method name="onInstallEnded">
+        <parameter name="aInstall"/>
+        <parameter name="aAddon"/>
+        <body><![CDATA[
+          this._initWithAddon(aAddon);
+        ]]></body>
+      </method>
+
+      <method name="onDownloadFailed">
+        <body><![CDATA[
+            this._updateState();
+        ]]></body>
+      </method>
+
+      <method name="onInstallFailed">
+        <body><![CDATA[
+            this._updateState();
+        ]]></body>
+      </method>
+
+      <method name="onInstallCancelled">
+        <body><![CDATA[
+            this._updateState();
+        ]]></body>
+      </method>
+    </implementation>
+
+    <handlers>
+      <handler event="click" button="0"><![CDATA[
+        if (!["button", "checkbox", "menulist", "menuitem"].includes(event.originalTarget.localName) &&
+            !event.originalTarget.classList.contains("text-link") &&
+            // Treat the relnotes container as embedded text instead of a click target.
+            !event.originalTarget.closest(".relnotes-container")) {
+          this.showInDetailView();
+        } else if (event.originalTarget.localName == "a" &&
+                   event.originalTarget.closest(".relnotes-container") &&
+                   event.originalTarget.href) {
+          event.preventDefault();
+          event.stopPropagation();
+          openURL(event.originalTarget.href);
+        }
+      ]]></handler>
+    </handlers>
+  </binding>
+
+
+  <!-- Addon - uninstalled - An uninstalled addon that can be re-installed. -->
+  <binding id="addon-uninstalled"
+           extends="chrome://mozapps/content/extensions/extensions.xml#addon-base">
+    <content>
+      <xul:hbox class="pending">
+        <xul:image class="pending-icon"/>
+        <xul:label anonid="notice" flex="1"/>
+        <xul:button anonid="undo-btn" class="button-link"
+                    label="&addon.undoRemove.label;"
+                    tooltiptext="&addon.undoRemove.tooltip;"
+                    oncommand="document.getBindingParent(this).cancelUninstall();"/>
+        <xul:spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
+      </xul:hbox>
+    </content>
+
+    <implementation>
+      <constructor><![CDATA[
+        this._notice.textContent = gStrings.ext.formatStringFromName("uninstallNotice",
+                                                                     [this.mAddon.name]);
+
+        gEventManager.registerAddonListener(this, this.mAddon.id);
+      ]]></constructor>
+
+      <destructor><![CDATA[
+        gEventManager.unregisterAddonListener(this, this.mAddon.id);
+      ]]></destructor>
+
+      <field name="_notice" readonly="true">
+        document.getAnonymousElementByAttribute(this, "anonid", "notice");
+      </field>
+
+      <method name="cancelUninstall">
+        <body><![CDATA[
+          // This assumes that disabling does not require a restart when
+          // uninstalling doesn't. Things will still work if not, the add-on
+          // will just still be active until finally getting uninstalled.
+
+          if (this.isPending("uninstall"))
+            this.mAddon.cancelUninstall();
+          else if (this.getAttribute("wasDisabled") != "true")
+            this.mAddon.enable();
+
+          // Dispatch an event so extensions.js can record telemetry.
+          var event = document.createEvent("Events");
+          event.initEvent("Undo", true, true);
+          this.dispatchEvent(event);
+
+          this.removeAttribute("pending");
+        ]]></body>
+      </method>
+
+      <method name="onExternalInstall">
+        <parameter name="aAddon"/>
+        <parameter name="aExistingAddon"/>
+        <body><![CDATA[
+          if (aExistingAddon.id != this.mAddon.id)
+            return;
+
+          // Make sure any newly installed add-on has the correct disabled state
+          if (this.hasAttribute("wasDisabled")) {
+            if (this.getAttribute("wasDisabled") == "true")
+              aAddon.disable();
+            else
+              aAddon.enable();
+          }
+
+          this.mAddon = aAddon;
+
+          this.removeAttribute("pending");
+        ]]></body>
+      </method>
+
+      <method name="onInstallStarted">
+        <parameter name="aInstall"/>
+        <body><![CDATA[
+          // Make sure any newly installed add-on has the correct disabled state
+          if (this.hasAttribute("wasDisabled")) {
+            if (this.getAttribute("wasDisabled") == "true")
+              aInstall.addon.disable();
+            else
+              aInstall.addon.enable();
+          }
+        ]]></body>
+      </method>
+
+      <method name="onInstallEnded">
+        <parameter name="aInstall"/>
+        <parameter name="aAddon"/>
+        <body><![CDATA[
+          this.mAddon = aAddon;
+
+          this.removeAttribute("pending");
+        ]]></body>
+      </method>
+    </implementation>
+  </binding>
+
+
+  <!-- Addon - installing - an addon item that is currently being installed -->
+  <binding id="addon-installing"
+           extends="chrome://mozapps/content/extensions/extensions.xml#addon-base">
+    <content>
+      <xul:hbox anonid="warning-container" class="warning">
+        <xul:image class="warning-icon"/>
+        <xul:label anonid="warning" flex="1"/>
+        <xul:button anonid="warning-link" class="button-link"
+                   oncommand="document.getBindingParent(this).retryInstall();"/>
+        <xul:spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
+      </xul:hbox>
+      <xul:hbox class="content-container">
+        <xul:vbox class="icon-outer-container">
+          <xul:vbox class="icon-container">
+            <xul:image anonid="icon" class="icon"/>
+          </xul:vbox>
+        </xul:vbox>
+        <xul:vbox class="fade name-outer-container" flex="1">
+          <xul:hbox class="name-container">
+            <xul:label anonid="name" class="name" crop="end" tooltip="addonitem-tooltip"/>
+          </xul:hbox>
+        </xul:vbox>
+        <xul:vbox class="install-status-container">
+          <xul:hbox anonid="install-status" class="install-status"/>
+        </xul:vbox>
+      </xul:hbox>
+    </content>
+
+    <implementation>
+      <constructor><![CDATA[
+        this._installStatus.mControl = this;
+        this._installStatus.mInstall = this.mInstall;
+        this.refreshInfo();
+      ]]></constructor>
+
+      <field name="_icon">
+        document.getAnonymousElementByAttribute(this, "anonid", "icon");
+      </field>
+      <field name="_name">
+        document.getAnonymousElementByAttribute(this, "anonid", "name");
+      </field>
+      <field name="_warning">
+        document.getAnonymousElementByAttribute(this, "anonid", "warning");
+      </field>
+      <field name="_warningLink">
+        document.getAnonymousElementByAttribute(this, "anonid", "warning-link");
+      </field>
+      <field name="_installStatus">
+        document.getAnonymousElementByAttribute(this, "anonid",
+                                                "install-status");
+      </field>
+
+      <method name="onInstallCompleted">
+        <body><![CDATA[
+          this.mAddon = this.mInstall.addon;
+          this.setAttribute("name", this.mAddon.name);
+          this.setAttribute("value", this.mAddon.id);
+          this.setAttribute("status", "installed");
+        ]]></body>
+      </method>
+
+      <method name="refreshInfo">
+        <body><![CDATA[
+          this.mAddon = this.mAddon || this.mInstall.addon;
+          if (this.mAddon) {
+            this._icon.src = this.mAddon.iconURL ||
+                             (this.mInstall ? this.mInstall.iconURL : "");
+            this._name.value = this.mAddon.name;
+          } else {
+            this._icon.src = this.mInstall.iconURL;
+            // AddonInstall.name isn't always available - fallback to filename
+            if (this.mInstall.name) {
+              this._name.value = this.mInstall.name;
+            } else if (this.mInstall.sourceURI) {
+              var url = Cc["@mozilla.org/network/standard-url-mutator;1"]
+                          .createInstance(Ci.nsIStandardURLMutator)
+                          .init(Ci.nsIStandardURL.URLTYPE_STANDARD,
+                                80, this.mInstall.sourceURI.spec,
+                                null, null)
+                          .finalize()
+                          .QueryInterface(Ci.nsIURL);
+              this._name.value = url.fileName;
+            }
+          }
+
+          if (this.mInstall.state == AddonManager.STATE_DOWNLOAD_FAILED) {
+            this.setAttribute("notification", "warning");
+            this._warning.textContent = gStrings.ext.formatStringFromName(
+              "notification.downloadError",
+              [this._name.value]
+            );
+            this._warningLink.label = gStrings.ext.GetStringFromName("notification.downloadError.retry");
+            this._warningLink.tooltipText = gStrings.ext.GetStringFromName("notification.downloadError.retry.tooltip");
+          } else if (this.mInstall.state == AddonManager.STATE_INSTALL_FAILED) {
+            this.setAttribute("notification", "warning");
+            this._warning.textContent = gStrings.ext.formatStringFromName(
+              "notification.installError",
+              [this._name.value]
+            );
+            this._warningLink.label = gStrings.ext.GetStringFromName("notification.installError.retry");
+            this._warningLink.tooltipText = gStrings.ext.GetStringFromName("notification.downloadError.retry.tooltip");
+          } else {
+            this.removeAttribute("notification");
+          }
+        ]]></body>
+      </method>
+
+      <method name="retryInstall">
+        <body><![CDATA[
+          this.mInstall.install();
+        ]]></body>
+      </method>
+    </implementation>
+  </binding>
+
+  <binding id="detail-row">
+    <content>
+      <xul:label class="detail-row-label" xbl:inherits="value=label"/>
+      <xul:label class="detail-row-value" xbl:inherits="value"/>
+    </content>
+
+    <implementation>
+      <property name="value">
+        <getter><![CDATA[
+          return this.getAttribute("value");
+        ]]></getter>
+        <setter><![CDATA[
+          if (!val)
+            this.removeAttribute("value");
+          else
+            this.setAttribute("value", val);
+        ]]></setter>
+      </property>
+    </implementation>
+  </binding>
+
 </bindings>
--- a/toolkit/mozapps/extensions/content/extensions.xul
+++ b/toolkit/mozapps/extensions/content/extensions.xul
@@ -31,16 +31,42 @@
   </linkset>
   
   <script src="chrome://global/content/contentAreaUtils.js"/>
   <script src="chrome://mozapps/content/extensions/aboutaddonsCommon.js"/>
   <script src="chrome://mozapps/content/extensions/extensions.js"/>
   <script src="chrome://mozapps/content/extensions/abuse-report-frame.js"/>
 
   <popupset>
+    <!-- menu for an addon item -->
+    <menupopup id="addonitem-popup">
+      <menuitem id="menuitem_showDetails" command="cmd_showItemDetails"
+                default="true" data-l10n-id="cmd-show-details"/>
+      <menuitem id="menuitem_enableItem" command="cmd_enableItem"
+                label="&cmd.enableAddon.label;"
+                accesskey="&cmd.enableAddon.accesskey;"/>
+      <menuitem id="menuitem_disableItem" command="cmd_disableItem"
+                label="&cmd.disableAddon.label;"
+                accesskey="&cmd.disableAddon.accesskey;"/>
+      <menuitem id="menuitem_enableTheme" command="cmd_enableItem"
+                data-l10n-id="cmd-enable-theme"/>
+      <menuitem id="menuitem_disableTheme" command="cmd_disableItem"
+                data-l10n-id="cmd-disable-theme"/>
+      <menuitem id="menuitem_installItem" command="cmd_installItem"
+                data-l10n-id="cmd-install-addon"/>
+      <menuitem id="menuitem_uninstallItem" command="cmd_uninstallItem"
+                label="&cmd.uninstallAddon.label;"
+                accesskey="&cmd.uninstallAddon.accesskey;"/>
+      <menuseparator id="addonitem-menuseparator" />
+      <menuitem id="menuitem_preferences" command="cmd_showItemPreferences"
+                data-l10n-id="cmd-preferences"/>
+      <menuitem id="menuitem_findUpdates" command="cmd_findItemUpdates"
+                data-l10n-id="cmd-find-updates"/>
+    </menupopup>
+
     <menulist popuponly="true" id="ContentSelectDropdown" hidden="true">
       <menupopup rolluponmousewheel="true"
                  activateontab="true" position="after_start"
                  level="parent"
 #ifdef XP_WIN
                  consumeoutsideclicks="false" ignorekeys="shortcuts"
 #endif
         />
@@ -49,59 +75,85 @@
     <panel is="autocomplete-richlistbox-popup"
            type="autocomplete-richlistbox"
            id="PopupAutoComplete"
            noautofocus="true"
            hidden="true"
            norolluponanchor="true"
            nomaxresults="true" />
 
+    <tooltip id="addonitem-tooltip"/>
+
     <menupopup id="contentAreaContextMenu"
                onpopupshowing="Cu.reportError('This dummy menupopup is not supposed to be shown');
                                return false">
       <!-- a dummy element used to forward the context menu related to the extension's
            options page XUL browsers to the context menu defined in the parent chrome window -->
     </menupopup>
   </popupset>
 
   <!-- global commands - these act on all addons, or affect the addons manager
        in some other way -->
   <commandset id="globalCommandSet">
     <!-- XXXsw remove useless oncommand attribute once bug 371900 is fixed -->
     <command id="cmd_focusSearch" oncommand=";"/>
     <command id="cmd_findAllUpdates"/>
+    <command id="cmd_restartApp"/>
     <command id="cmd_goToDiscoverPane"/>
     <command id="cmd_goToRecentUpdates"/>
     <command id="cmd_goToAvailableUpdates"/>
     <command id="cmd_installFromFile"/>
     <command id="cmd_debugAddons"/>
     <command id="cmd_back"/>
     <command id="cmd_forward"/>
     <command id="cmd_enableCheckCompatibility"/>
     <command id="cmd_enableUpdateSecurity"/>
     <command id="cmd_toggleAutoUpdateDefault"/>
     <command id="cmd_resetAddonAutoUpdate"/>
+    <command id="cmd_showUnsignedExtensions"/>
+    <command id="cmd_showAllExtensions"/>
     <command id="cmd_showShortcuts"/>
   </commandset>
 
+  <!-- view commands - these act on the selected addon -->
+  <commandset id="viewCommandSet"
+              events="richlistbox-select" commandupdater="true">
+    <command id="cmd_showItemDetails"/>
+    <command id="cmd_findItemUpdates"/>
+    <command id="cmd_showItemPreferences"/>
+    <command id="cmd_enableItem"/>
+    <command id="cmd_disableItem"/>
+    <command id="cmd_installItem"/>
+    <command id="cmd_uninstallItem"/>
+    <command id="cmd_cancelUninstallItem"/>
+    <command id="cmd_cancelOperation"/>
+    <command id="cmd_contribute"/>
+    <command id="cmd_askToActivateItem"/>
+    <command id="cmd_alwaysActivateItem"/>
+    <command id="cmd_neverActivateItem"/>
+  </commandset>
+
   <keyset>
     <key id="focusSearch" data-l10n-id="search-header-shortcut"
          modifiers="accel" command="cmd_focusSearch"/>
   </keyset>
 
   <stack id="main-page-stack" flex="1">
   <hbox id="main-page-content" flex="1">
     <vbox id="category-box">
       <!-- category list -->
       <richlistbox id="categories" flex="1">
         <richlistitem id="category-discover" value="addons://discover/"
                       class="category"
                       data-l10n-id="extensions-view-discopane"
                       data-l10n-attrs="name"
                       priority="1000"/>
+        <richlistitem id="category-legacy" value="addons://legacy/"
+                      class="category" priority="20000"
+                      hidden="true"/>
         <richlistitem id="category-availableUpdates" value="addons://updates/available"
                       class="category"
                       data-l10n-id="extensions-view-available-updates"
                       data-l10n-attrs="name"
                       hidden="true"/>
         <richlistitem id="category-recentUpdates" value="addons://updates/recent"
                       class="category"
                       data-l10n-id="extensions-view-recent-updates"
@@ -164,17 +216,24 @@
           <browser id="discover-browser" type="content" flex="1"
                    disablehistory="true"/>
         </deck>
 
         <!-- container for views with the search/tools header -->
         <vbox id="headered-views" flex="1">
           <!-- main header -->
           <hbox id="header">
-            <hbox id="header-inner" align="center" pack="end">
+            <hbox id="header-inner" align="center">
+              <button id="show-all-extensions" hidden="true"
+                      data-l10n-id="show-all-extensions-button"
+                      command="cmd_showAllExtensions"/>
+              <spacer flex="1"/>
+              <button id="show-disabled-unsigned-extensions" hidden="true"
+                      class="warning" data-l10n-id="show-unsigned-extensions-button"
+                      command="cmd_showUnsignedExtensions"/>
               <label id="search-label" control="header-search"/>
               <textbox id="header-search" is="search-textbox" searchbutton="true"
                        data-l10n-id="search-header"
                        data-l10n-attrs="searchbuttonlabel" maxlength="100"/>
             </hbox>
           </hbox>
 
           <hbox id="heading">
@@ -194,16 +253,19 @@
                         data-l10n-id="extensions-updates-manual-updates-found"
                         command="cmd_goToAvailableUpdates"/>
                 <label id="updates-progress" hidden="true"
                        data-l10n-id="extensions-updates-updating"/>
                 <label id="updates-installed" hidden="true"
                        data-l10n-id="extensions-updates-installed"/>
                 <label id="updates-downloaded" hidden="true"
                        data-l10n-id="extensions-updates-downloaded"/>
+                <button id="updates-restart-btn" class="button-link" hidden="true"
+                        data-l10n-id="extensions-updates-restart"
+                        command="cmd_restartApp"/>
               </hbox>
 
               <button id="header-utils-btn" type="menu" data-l10n-id="tools-menu">
                 <menupopup id="utils-menu">
                   <menuitem id="utils-updateNow"
                             data-l10n-id="extensions-updates-check-for-updates"
                             command="cmd_findAllUpdates"/>
                   <menuitem id="utils-viewUpdates"
@@ -232,19 +294,389 @@
                             data-l10n-id="manage-extensions-shortcuts"
                             command="cmd_showShortcuts"/>
                 </menupopup>
               </button>
             </hbox>
           </hbox>
 
           <deck id="headered-views-content" flex="1" selectedIndex="0">
+            <!-- list view -->
+            <vbox id="list-view" flex="1" class="view-pane" align="stretch">
+              <!-- info UI for add-ons that have been disabled for being unsigned -->
+              <vbox id="disabled-unsigned-addons-info" class="alert-container" hidden="true">
+                <label id="disabled-unsigned-addons-heading" data-l10n-id="disabled-unsigned-heading"/>
+                <description data-l10n-id="disabled-unsigned-description">
+                  <label class="plain" id="find-alternative-addons" data-l10n-name="find-addons" is="text-link"/>
+                </description>
+                <hbox pack="start"><label id="signing-learn-more" data-l10n-id="disabled-unsigned-learn-more" is="text-link"></label></hbox>
+                <description id="signing-dev-info" data-l10n-id="disabled-unsigned-devinfo">
+                  <label class="plain" id="signing-dev-manual-link" data-l10n-name="learn-more" is="text-link"/>
+                </description>
+              </vbox>
+              <vbox id="legacy-extensions-notice" class="alert-container" hidden="true">
+                <vbox class="alert">
+                  <description id="legacy-extensions-description">
+                    <label class="plain" id="legacy-extensions-learnmore-link" data-l10n-id="legacy-warning-show-legacy" is="text-link"/>
+                  </description>
+                </vbox>
+              </vbox>
+              <vbox id="private-browsing-notice" class="alert-container" hidden="true" align="start">
+                <hbox class="message-bar" align="start">
+                  <image class="message-bar-icon"/>
+                  <vbox class="message-container">
+                    <description class="message-bar-description" data-l10n-id="private-browsing-description2">
+                      <label class="plain" id="private-browsing-learnmore-link" data-l10n-name="private-browsing-learn-more" is="text-link"/>
+                    </description>
+                  </vbox>
+                </hbox>
+              </vbox>
+              <vbox id="plugindeprecation-notice" class="list-view-notice" align="start">
+                <hbox class="message-bar">
+                  <image class="message-bar-icon"/>
+                  <description class="message-bar-description" data-l10n-id="plugin-deprecation-description">
+                    <label class="plain" id="plugindeprecation-learnmore-link" data-l10n-name="learn-more" is="text-link"></label>
+                  </description>
+                </hbox>
+              </vbox>
+              <hbox class="view-header global-warning-container">
+                <!-- global warnings -->
+                <hbox class="global-warning" flex="1">
+                  <hbox class="global-warning-safemode" flex="1" align="center"
+                        data-l10n-id="extensions-warning-safe-mode-container">
+                    <image class="warning-icon"/>
+                    <label class="global-warning-text" flex="1" crop="end"
+                           data-l10n-id="extensions-warning-safe-mode-label"/>
+                  </hbox>
+                  <hbox class="global-warning-checkcompatibility" flex="1" align="center"
+                        data-l10n-id="extensions-warning-check-compatibility-container">
+                    <image class="warning-icon"/>
+                    <label class="global-warning-text" flex="1" crop="end"
+                           data-l10n-id="extensions-warning-check-compatibility-label"/>
+                  </hbox>
+                  <button class="button-link global-warning-checkcompatibility"
+                          data-l10n-id="extensions-warning-check-compatibility-enable"
+                          command="cmd_enableCheckCompatibility"/>
+                  <hbox class="global-warning-updatesecurity" flex="1" align="center"
+                        data-l10n-id="extensions-warning-update-security-container">
+                    <image class="warning-icon"/>
+                    <label class="global-warning-text" flex="1" crop="end"
+                          data-l10n-id="extensions-warning-update-security-label"/>
+                  </hbox>
+                  <button class="button-link global-warning-updatesecurity"
+                          data-l10n-id="extensions-warning-update-security-enable"
+                          command="cmd_enableUpdateSecurity"/>
+                  <spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
+                </hbox>
+              </hbox>
+              <vbox id="addon-list-empty" class="alert-container"
+                    flex="1" hidden="true">
+                <spacer class="alert-spacer-before"/>
+                <vbox class="alert">
+                  <label data-l10n-id="list-empty-installed"/>
+                  <button class="discover-button"
+                          id="discover-button-install"
+                          data-l10n-id="list-empty-button"
+                          command="cmd_goToDiscoverPane"/>
+                </vbox>
+                <spacer class="alert-spacer-after"/>
+              </vbox>
+              <richlistbox id="addon-list" class="list" flex="1"/>
+            </vbox>
+
             <!-- extension shortcuts view -->
             <browser id="shortcuts-view" type="content" flex="1" disablehistory="true"/>
 
+            <!-- legacy extensions view -->
+            <vbox id="legacy-view" flex="1" class="view-pane" align="stretch">
+              <vbox id="legacy-extensions-info">
+                <label id="legacy-extensions-heading" data-l10n-id="legacy-extensions"/>
+                <description data-l10n-id="legacy-extensions-description">
+                  <label class="plain" id="legacy-learnmore" data-l10n-name="legacy-learn-more" is="text-link"></label>
+                </description>
+              </vbox>
+              <richlistbox id="legacy-list" class="list" flex="1"/>
+            </vbox>
+
+            <!-- updates view -->
+            <vbox id="updates-view" flex="1" class="view-pane">
+              <hbox class="view-header global-warning-container" align="center">
+                <!-- global warnings -->
+                <hbox class="global-warning" flex="1">
+                  <hbox class="global-warning-safemode" flex="1" align="center"
+                        data-l10n-id="extensions-warning-safe-mode-container">
+                    <image class="warning-icon"/>
+                    <label class="global-warning-text" flex="1" crop="end"
+                           data-l10n-id="extensions-warning-safe-mode-label"/>
+                  </hbox>
+                  <hbox class="global-warning-checkcompatibility" flex="1" align="center"
+                        data-l10n-id="extensions-warning-check-compatibility-label">
+                    <image class="warning-icon"/>
+                    <label class="global-warning-text" flex="1" crop="end"
+                           data-l10n-id="extensions-warning-check-compatibility-label"/>
+                  </hbox>
+                  <button class="button-link global-warning-checkcompatibility"
+                          data-l10n-id="extensions-warning-check-compatibility-enable"
+                          command="cmd_enableCheckCompatibility"/>
+                  <hbox class="global-warning-updatesecurity" flex="1" align="center"
+                        data-l10n-id="extensions-warning-update-security-label">
+                    <image class="warning-icon"/>
+                    <label class="global-warning-text" flex="1" crop="end"
+                           data-l10n-id="extensions-warning-update-security-label"/>
+                  </hbox>
+                  <button class="button-link global-warning-updatesecurity"
+                          data-l10n-id="extensions-warning-update-security-enable"
+                          command="cmd_enableUpdateSecurity"/>
+                  <spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
+                </hbox>
+              </hbox>
+              <vbox id="updates-list-empty" class="alert-container"
+                    flex="1" hidden="true">
+                <spacer class="alert-spacer-before"/>
+                <vbox class="alert">
+                  <label id="empty-availableUpdates-msg" data-l10n-id="list-empty-available-updates"/>
+                  <label id="empty-recentUpdates-msg" data-l10n-id="list-empty-recent-updates"/>
+                  <button data-l10n-id="list-empty-find-updates"
+                          command="cmd_findAllUpdates"/>
+                </vbox>
+                <spacer class="alert-spacer-after"/>
+              </vbox>
+              <hbox id="update-actions" pack="center">
+                <button id="update-selected-btn" hidden="true"
+                        data-l10n-id="extensions-updates-update-selected"/>
+              </hbox>
+              <richlistbox id="updates-list" class="list" flex="1"/>
+            </vbox>
+
+            <!-- detail view -->
+            <scrollbox id="detail-view" class="view-pane addon-view" orient="vertical" tabindex="0"
+                       role="document">
+              <!-- global warnings -->
+              <hbox class="global-warning-container global-warning">
+                <hbox class="global-warning-safemode" flex="1" align="center"
+                        data-l10n-id="extensions-warning-safe-mode-container">
+                  <image class="warning-icon"/>
+                  <label class="global-warning-text" flex="1" crop="end"
+                        data-l10n-id="extensions-warning-safe-mode-label"/>
+                </hbox>
+                <hbox class="global-warning-checkcompatibility" flex="1" align="center"
+                      data-l10n-id="extensions-warning-check-compatibility-container">
+                  <image class="warning-icon"/>
+                  <label class="global-warning-text" flex="1" crop="end"
+                         data-l10n-id="extensions-warning-check-compatibility-label"/>
+                </hbox>
+                <button class="button-link global-warning-checkcompatibility"
+                        data-l10n-id="extensions-warning-check-compatibility-enable"
+                        command="cmd_enableCheckCompatibility"/>
+                <hbox class="global-warning-updatesecurity" flex="1" align="center"
+                      data-l10n-id="extensions-warning-update-security-container">
+                  <image class="warning-icon"/>
+                  <label class="global-warning-text" flex="1" crop="end"
+                         data-l10n-id="extensions-warning-update-security-label"/>
+                </hbox>
+                <button class="button-link global-warning-updatesecurity"
+                        data-l10n-id="extensions-warning-update-security-label"
+                        command="cmd_enableUpdateSecurity"/>
+                <spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
+              </hbox>
+              <hbox class="detail-view-wrapper">
+                <!-- "loading" splash screen -->
+                <vbox class="alert-container">
+                  <spacer class="alert-spacer-before"/>
+                  <hbox class="alert loading">
+                    <image/>
+                    <label data-l10n-id="loading-label"/>
+                  </hbox>
+                  <spacer class="alert-spacer-after"/>
+                </vbox>
+                <!-- actual detail view -->
+                <vbox class="detail-view-container" contextmenu="addonitem-popup">
+                  <vbox id="detail-notifications">
+                    <hbox id="warning-container" align="center" class="warning">
+                      <image class="warning-icon"/>
+                      <label id="detail-warning" flex="1"/>
+                      <label id="detail-warning-link" is="text-link"/>
+                      <spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
+                    </hbox>
+                    <hbox id="error-container" align="center" class="error">
+                      <image class="error-icon"/>
+                      <label id="detail-error" flex="1"/>
+                      <label id="detail-error-link" is="text-link"/>
+                      <spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
+                    </hbox>
+                    <hbox id="pending-container" align="center" class="pending">
+                      <image class="pending-icon"/>
+                      <label id="detail-pending" flex="1"/>
+                      <button id="detail-restart-btn" class="button-link"
+                              data-l10n-id="addon-restart-now"
+                              command="cmd_restartApp"/>
+                      <button id="detail-undo-btn" class="button-link"
+                              label="&addon.undoAction.label;"
+                              tooltipText="&addon.undoAction.tooltip;"
+                              command="cmd_cancelOperation"/>
+                      <spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
+                    </hbox>
+                  </vbox>
+                  <hbox class="card addon-detail" align="start">
+                    <vbox flex="1">
+                      <image class="card-heading-image theme-screenshot"/>
+                      <hbox align="start">
+                        <vbox id="detail-icon-container" align="end">
+                          <image id="detail-icon" class="icon"/>
+                        </vbox>
+                        <vbox id="detail-summary">
+                          <hbox id="detail-name-container" class="name-container"
+                                align="start">
+                            <label id="detail-name" flex="1"/>
+                            <label id="detail-legacy-warning" class="legacy-warning" value="&addon.legacy.label;" is="text-link"/>
+                            <label class="disabled-postfix" value="&addon.disabled.postfix;"/>
+                            <label class="update-postfix" value="&addon.update.postfix;"/>
+                            <spacer flex="5000"/> <!-- Necessary to allow the name to wrap -->
+                          </hbox>
+                          <label id="detail-creator" class="creator"/>
+                        </vbox>
+                      </hbox>
+                      <vbox id="detail-desc-container" align="start" flex="1">
+                        <description id="detail-desc"/>
+                        <description id="detail-fulldesc"/>
+                      </vbox>
+                      <vbox id="detail-contributions">
+                        <description id="detail-contrib-description" data-l10n-id="detail-contributions-description"/>
+                        <hbox align="center">
+                          <spacer flex="1"/>
+                          <button id="detail-contrib-btn"
+                                  data-l10n-id="cmd-contribute"
+                                  command="cmd_contribute"/>
+                        </hbox>
+                      </vbox>
+                      <grid id="detail-grid">
+                        <columns>
+                           <column flex="1"/>
+                           <column flex="2"/>
+                        </columns>
+                        <rows id="detail-rows">
+                          <row class="detail-row-complex detail-privateBrowsing" id="detail-privateBrowsing-row">
+                            <label class="detail-row-label" data-l10n-id="detail-private-browsing-label"/>
+                            <hbox align="center">
+                              <radiogroup id="detail-privateBrowsing" orient="horizontal">
+                                <radio data-l10n-id="detail-private-browsing-on"
+                                       value="1"/>
+                                <radio data-l10n-id="detail-private-browsing-off"
+                                       value="0"/>
+                              </radiogroup>
+                            </hbox>
+                          </row>
+                          <hbox class="detail-row-footer detail-privateBrowsing" id="detail-privateBrowsing-row-footer">
+                            <description class="indent preferences-description" data-l10n-id="detail-private-browsing-description2">
+                              <label class="learnMore private-learnmore" data-l10n-name="detail-private-browsing-learn-more" is="text-link"/>
+                            </description>
+                          </hbox>
+                          <row class="detail-row-complex detail-privateBrowsing" id="detail-privateBrowsing-required">
+                            <label class="detail-row-label" data-l10n-id="detail-private-required-label"/>
+                          </row>
+                          <hbox class="detail-row-footer detail-privateBrowsing" id="detail-privateBrowsing-required-footer">
+                            <description class="indent preferences-description" data-l10n-id="detail-private-required-description">
+                              <label class="learnMore private-learnmore" data-l10n-name="detail-private-browsing-learn-more" is="text-link"/>
+                            </description>
+                          </hbox>
+                          <row class="detail-row-complex detail-privateBrowsing" id="detail-privateBrowsing-disallowed">
+                            <label class="detail-row-label" data-l10n-id="detail-private-disallowed-label"/>
+                          </row>
+                          <hbox class="detail-row-footer detail-privateBrowsing" id="detail-privateBrowsing-disallowed-footer">
+                            <description class="indent preferences-description" data-l10n-id="detail-private-disallowed-description">
+                              <label class="learnMore private-learnmore" data-l10n-name="detail-private-browsing-learn-more" is="text-link"/>
+                            </description>
+                          </hbox>
+                          <row class="detail-row-complex" id="detail-updates-row">
+                            <label class="detail-row-label" data-l10n-id="detail-update-type"/>
+                            <hbox align="center">
+                              <radiogroup id="detail-autoUpdate" orient="horizontal">
+                                <!-- The values here need to match the values of
+                                     AddonManager.AUTOUPDATE_* -->
+                                <radio data-l10n-id="detail-update-default"
+                                       value="1"/>
+                                <radio data-l10n-id="detail-update-automatic"
+                                       value="2"/>
+                                <radio data-l10n-id="detail-update-manual"
+                                       value="0"/>
+                              </radiogroup>
+                              <button id="detail-findUpdates-btn" class="button-link"
+                                      data-l10n-id="detail-check-for-updates"
+                                      command="cmd_findItemUpdates"/>
+                            </hbox>
+                          </row>
+                          <row class="detail-row" id="detail-version" data-l10n-id="detail-version"/>
+                          <row class="detail-row" id="detail-dateUpdated" data-l10n-id="detail-last-updated"/>
+                          <row class="detail-row-complex" id="detail-homepage-row" data-l10n-id="detail-home">
+                            <label class="detail-row-label" data-l10n-id="detail-home-value"/>
+                            <label id="detail-homepage" class="detail-row-value" crop="end" is="text-link"/>
+                          </row>
+                          <row class="detail-row-complex" id="detail-repository-row" data-l10n-id="detail-repository">
+                            <label class="detail-row-label" data-l10n-id="detail-repository-value"/>
+                            <label id="detail-repository" class="detail-row-value" is="text-link"/>
+                          </row>
+                          <row class="detail-row-complex" id="detail-rating-row">
+                            <label class="detail-row-label" data-l10n-id="detail-rating"/>
+                            <hbox>
+                              <label id="detail-rating" class="meta-value meta-rating"
+                                     showrating="average"/>
+                              <label id="detail-reviews" is="text-link"/>
+                            </hbox>
+                          </row>
+                        </rows>
+                      </grid>
+                      <hbox id="detail-controls">
+                        <button id="detail-prefs-btn" class="addon-control preferences"
+                                data-l10n-id="detail-show-preferences"
+                                command="cmd_showItemPreferences"/>
+                        <spacer flex="1"/>
+                        <button id="detail-enable-btn" class="addon-control enable"
+                                label="&cmd.enableAddon.label;"
+                                accesskey="&cmd.enableAddon.accesskey;"
+                                command="cmd_enableItem"/>
+                        <button id="detail-disable-btn" class="addon-control disable"
+                                label="&cmd.disableAddon.label;"
+                                accesskey="&cmd.disableAddon.accesskey;"
+                                command="cmd_disableItem"/>
+                        <button id="detail-uninstall-btn" class="addon-control remove"
+                                label="&cmd.uninstallAddon.label;"
+                                accesskey="&cmd.uninstallAddon.accesskey;"
+                                command="cmd_uninstallItem"/>
+                        <button id="detail-install-btn" class="addon-control install"
+                                data-l10n-id="cmd-install-addon"
+                                command="cmd_installItem"/>
+                        <menulist id="detail-state-menulist"
+                                  crop="none" sizetopopup="always"
+                                  tooltiptext="&cmd.stateMenu.tooltip;">
+                          <menupopup>
+                            <menuitem id="detail-ask-to-activate-menuitem"
+                                      class="addon-control"
+                                      label="&cmd.askToActivate.label;"
+                                      tooltiptext="&cmd.askToActivate.tooltip;"
+                                      command="cmd_askToActivateItem"/>
+                            <menuitem id="detail-always-activate-menuitem"
+                                      class="addon-control"
+                                      label="&cmd.alwaysActivate.label;"
+                                      tooltiptext="&cmd.alwaysActivate.tooltip;"
+                                      command="cmd_alwaysActivateItem"/>
+                            <menuitem id="detail-never-activate-menuitem"
+                                      class="addon-control"
+                                      label="&cmd.neverActivate.label;"
+                                      tooltiptext="&cmd.neverActivate.tooltip;"
+                                      command="cmd_neverActivateItem"/>
+                          </menupopup>
+                        </menulist>
+                      </hbox>
+                    </vbox>
+                  </hbox>
+                </vbox>
+                <spacer flex="1"/>
+              </hbox>
+            </scrollbox>
+
             <vbox id="html-view" flex="1">
               <vbox class="alert-container html-alert-container" align="start">
                 <hbox class="global-warning-safemode message-bar"
                       data-l10n-id="extensions-warning-safe-mode-container"
                       align="start">
                   <image class="message-bar-icon"/>
                   <vbox class="message-container">
                     <description class="message-bar-description"
--- a/toolkit/mozapps/extensions/jar.mn
+++ b/toolkit/mozapps/extensions/jar.mn
@@ -6,17 +6,17 @@ toolkit.jar:
 % content mozapps %content/mozapps/
   content/mozapps/extensions/shortcuts.html                     (content/shortcuts.html)
   content/mozapps/extensions/shortcuts.css                      (content/shortcuts.css)
   content/mozapps/extensions/shortcuts.js                       (content/shortcuts.js)
 #ifndef MOZ_FENNEC
 * content/mozapps/extensions/extensions.xul                     (content/extensions.xul)
   content/mozapps/extensions/extensions.css                     (content/extensions.css)
   content/mozapps/extensions/extensions.js                      (content/extensions.js)
-  content/mozapps/extensions/extensions.xml                     (content/extensions.xml)
+* content/mozapps/extensions/extensions.xml                     (content/extensions.xml)
   content/mozapps/extensions/blocklist.xul                      (content/blocklist.xul)
   content/mozapps/extensions/blocklist.js                       (content/blocklist.js)
   content/mozapps/extensions/pluginPrefs.xul                    (content/pluginPrefs.xul)
   content/mozapps/extensions/pluginPrefs.js                     (content/pluginPrefs.js)
   content/mozapps/extensions/OpenH264-license.txt               (content/OpenH264-license.txt)
   content/mozapps/extensions/aboutaddons.html                   (content/aboutaddons.html)
   content/mozapps/extensions/aboutaddons.js                     (content/aboutaddons.js)
   content/mozapps/extensions/aboutaddonsCommon.js               (content/aboutaddonsCommon.js)
--- a/toolkit/mozapps/extensions/test/browser/browser.ini
+++ b/toolkit/mozapps/extensions/test/browser/browser.ini
@@ -41,29 +41,40 @@ generated-files =
   addons/browser_installssl.xpi
   addons/browser_theme.xpi
   addons/options_signed.xpi
 
 [browser_CTP_plugins.js]
 tags = blocklist
 [browser_about_debugging_link.js]
 [browser_bug523784.js]
+[browser_bug562890.js]
+skip-if = os == 'win' && !debug # Disabled on Windows opt/PGO builds due to intermittent failures (bug 1135866)
+[browser_bug562899.js]
+[browser_bug562992.js]
 [browser_bug567127.js]
 skip-if = (!debug && os == 'win') #Bug 1489496
 [browser_bug567137.js]
 [browser_bug570760.js]
 skip-if = verify
 [browser_bug572561.js]
 [browser_bug577990.js]
+[browser_bug580298.js]
 [browser_bug586574.js]
+[browser_bug587970.js]
 [browser_bug591465.js]
 skip-if = os == "linux" && !debug # Bug 1395539 - fails on multi-core
+[browser_bug591663.js]
+[browser_bug618502.js]
 [browser_bug679604.js]
+[browser_bug590347.js]
 [browser_checkAddonCompatibility.js]
+[browser_details.js]
 [browser_discovery.js]
+[browser_discovery_clientid.js]
 [browser_dragdrop.js]
 [browser_extension_sideloading_permission.js]
 [browser_file_xpi_no_process_switch.js]
 skip-if = true # Bug 1449071 - Frequent failures
 [browser_globalwarnings.js]
 [browser_gmpProvider.js]
 skip-if = os == 'linux' && !debug # Bug 1398766
 [browser_history_navigation.js]
@@ -80,30 +91,50 @@ skip-if = os == 'win' # Bug 1562792
 [browser_html_options_ui.js]
 [browser_html_options_ui_in_tab.js]
 [browser_html_plugins.js]
 skip-if = (os == 'win' && processor == 'aarch64') # aarch64 has no plugin support, bug 1525174 and 1547495
 [browser_html_recent_updates.js]
 [browser_html_recommendations.js]
 [browser_html_updates.js]
 [browser_html_warning_messages.js]
+[browser_inlinesettings_browser.js]
+skip-if = os == 'mac' || os == 'linux' # Bug 1483347
 [browser_installssl.js]
 skip-if = verify
 [browser_interaction_telemetry.js]
+[browser_langpack_signing.js]
+[browser_legacy.js]
+[browser_legacy_pre57.js]
+[browser_list.js]
 [browser_manage_shortcuts.js]
 [browser_manage_shortcuts_hidden.js]
+[browser_manualupdates.js]
 [browser_pluginprefs.js]
+[browser_pluginprefs_is_not_disabled.js]
+[browser_plugin_enabled_state_locked.js]
 [browser_recentupdates.js]
 [browser_reinstall.js]
+[browser_sorting.js]
+[browser_sorting_plugins.js]
+[browser_tabsettings.js]
 [browser_task_next_test.js]
+[browser_theme_previews.js]
+[browser_types.js]
+[browser_uninstalling.js]
 [browser_updateid.js]
 [browser_updatessl.js]
 [browser_webapi.js]
 [browser_webapi_access.js]
 [browser_webapi_addon_listener.js]
 [browser_webapi_enable.js]
 [browser_webapi_install.js]
 [browser_webapi_install_disabled.js]
 [browser_webapi_theme.js]
 [browser_webapi_uninstall.js]
 [browser_webext_icon.js]
 [browser_webext_incognito.js]
 [browser_webext_incognito_doorhanger_telemetry.js]
+[browser_webext_options.js]
+tags = webextensions
+skip-if = os == 'linux' || (os == 'mac' && debug) # bug 1483347
+[browser_webext_options_addon_reload.js]
+tags = webextensions
--- a/toolkit/mozapps/extensions/test/browser/browser_CTP_plugins.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_CTP_plugins.js
@@ -51,60 +51,167 @@ function setAndUpdateBlocklist(aURL, aCa
   }
   updateBlocklist(aURL, aCallback);
 }
 
 function resetBlocklist() {
   Services.prefs.setCharPref("extensions.blocklist.url", _originalBlocklistURL);
 }
 
+function getXULPluginUI(plugin, anonid) {
+  if (
+    plugin.openOrClosedShadowRoot &&
+    plugin.openOrClosedShadowRoot.isUAWidget()
+  ) {
+    return plugin.openOrClosedShadowRoot.getElementById(anonid);
+  }
+  return plugin.ownerDocument.getAnonymousElementByAttribute(
+    plugin,
+    "anonid",
+    anonid
+  );
+}
+
+function assertPluginActiveState({
+  managerWindow,
+  pluginId,
+  expectedActivateState,
+}) {
+  let pluginEl = get_addon_element(managerWindow, pluginId);
+  ok(pluginEl, `Got the about:addon entry for "${pluginId}"`);
+
+  if (managerWindow.useHtmlViews) {
+    const pluginOptions = pluginEl.querySelector("plugin-options");
+    const pluginCheckedItem = pluginOptions.querySelector(
+      "panel-item[checked]"
+    );
+    is(
+      pluginCheckedItem.getAttribute("action"),
+      expectedActivateState,
+      `plugin should have ${expectedActivateState} state selected`
+    );
+  } else {
+    // Assertions for the XUL about:addons views.
+    pluginEl.parentNode.ensureElementIsVisible(pluginEl);
+    let enableButton = getXULPluginUI(pluginEl, "enable-btn");
+    let disableButton = getXULPluginUI(pluginEl, "disable-btn");
+    is_element_hidden(enableButton, "enable button should be hidden");
+    is_element_hidden(disableButton, "disable button should be hidden");
+    let menu = getXULPluginUI(pluginEl, "state-menulist");
+    is_element_visible(menu, "state menu should be visible");
+    let activateItem = getXULPluginUI(
+      pluginEl,
+      `${expectedActivateState}-menuitem`
+    );
+    ok(
+      activateItem,
+      `Got a menu item for the ${expectedActivateState} plugin activate state`
+    );
+    is(
+      menu.selectedItem,
+      activateItem,
+      `state menu should have '${expectedActivateState}' selected`
+    );
+  }
+}
+
 function setPluginActivateState({ managerWindow, pluginId, activateState }) {
   let pluginEl = get_addon_element(managerWindow, pluginId);
   ok(pluginEl, `Got the about:addon entry for "${pluginId}"`);
 
-  // Activate plugin on the HTML about:addons views.
-  let activateAction = pluginEl.querySelector(`[action="${activateState}"]`);
-  ok(activateAction, `Got element for ${activateState} plugin action`);
-  activateAction.click();
+  if (managerWindow.useHtmlViews) {
+    // Activate plugin on the HTML about:addons views.
+    let activateAction = pluginEl.querySelector(`[action="${activateState}"]`);
+    ok(activateAction, `Got element for ${activateState} plugin action`);
+    activateAction.click();
+  } else {
+    // Activate plugin on the XUL about:addons views.
+    let activateItem = getXULPluginUI(pluginEl, `${activateState}-menuitem`);
+    ok(
+      activateItem,
+      `Got a menu item for the ${activateState} plugin activate state`
+    );
+    let menu = getXULPluginUI(pluginEl, "state-menulist");
+    menu.selectedItem = activateItem;
+    activateItem.doCommand();
+  }
+}
+
+async function assertPluginAppDisabled({ managerWindow, pluginId }) {
+  const pluginEl = get_addon_element(managerWindow, pluginId);
+  ok(pluginEl, `Got the about:addon entry for "${pluginId}"`);
+
+  if (managerWindow.useHtmlViews) {
+    // Open the options menu (needed to check the disabled buttons).
+    const pluginOptions = pluginEl.querySelector("plugin-options");
+    pluginOptions.querySelector("panel-list").open = true;
+    // tests all buttons disabled (besides the checked one and the expand action)
+    // are expected to be disabled if locked is true.
+    for (const item of pluginOptions.querySelectorAll(
+      "panel-item:not([hidden])"
+    )) {
+      const actionName = item.getAttribute("action");
+      if (
+        !item.hasAttribute("checked") &&
+        actionName !== "expand" &&
+        actionName !== "preferences"
+      ) {
+        ok(
+          item.shadowRoot.querySelector("button").disabled,
+          `Plugin action "${actionName}" should be disabled`
+        );
+      }
+    }
+    pluginOptions.querySelector("panel-list").open = false;
+  } else {
+    // Assertions for the XUL about:addons views.
+    let menu = getXULPluginUI(pluginEl, "state-menulist");
+    pluginEl.parentNode.ensureElementIsVisible(pluginEl);
+    menu = getXULPluginUI(pluginEl, "state-menulist");
+    is(menu.disabled, true, "state menu should be disabled");
+
+    EventUtils.synthesizeMouseAtCenter(pluginEl, {}, managerWindow);
+    await BrowserTestUtils.waitForEvent(managerWindow.document, "ViewChanged");
+
+    menu = managerWindow.document.getElementById("detail-state-menulist");
+    is(menu.disabled, true, "detail state menu should be disabled");
+  }
 }
 
 async function getTestPluginAddon() {
   const plugins = await AddonManager.getAddonsByTypes(["plugin"]);
 
   for (const plugin of plugins) {
     if (plugin.name == "Test Plug-in") {
       return plugin;
     }
   }
 
   return undefined;
 }
 
-add_task(async function test_CTP_plugins() {
+async function test_CTP_plugins(aboutAddonsType) {
   let pluginTag = getTestPluginTag();
   pluginTag.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
   let managerWindow = await open_manager("addons://list/plugin");
 
   let testPluginAddon = await getTestPluginAddon();
   isnot(testPluginAddon, null, "part2: Test Plug-in should exist");
 
   let testPluginId = testPluginAddon.id;
 
   let pluginEl = get_addon_element(managerWindow, testPluginId);
   ok(pluginEl, `Got the about:addon entry for "${testPluginId}"`);
 
   info("part3: test plugin add-on actions status");
-
-  let pluginOptions = pluginEl.querySelector("plugin-options");
-  let pluginCheckedItem = pluginOptions.querySelector("panel-item[checked]");
-  is(
-    pluginCheckedItem.getAttribute("action"),
-    "ask-to-activate",
-    "plugin should have ask-to-activate state selected"
-  );
+  assertPluginActiveState({
+    managerWindow,
+    pluginId: testPluginId,
+    expectedActivateState: "ask-to-activate",
+  });
 
   let pluginTab = await BrowserTestUtils.openNewForegroundTab(
     gBrowser,
     gHttpTestRoot + "plugin_test.html"
   );
   let pluginBrowser = pluginTab.linkedBrowser;
 
   let condition = () =>
@@ -162,25 +269,21 @@ add_task(async function test_CTP_plugins
       Ci.nsIObjectLoadingContent
     );
     ok(!objLoadingContent.activated, "part7: plugin should not be activated");
   });
 
   BrowserTestUtils.removeTab(pluginTab);
 
   info("part8: test plugin state is never-activate");
-  pluginEl = get_addon_element(managerWindow, testPluginId);
-
-  pluginOptions = pluginEl.querySelector("plugin-options");
-  const pluginCheckItem = pluginOptions.querySelector("panel-item[checked]");
-  is(
-    pluginCheckItem.getAttribute("action"),
-    "never-activate",
-    "plugin should have never-activate state selected"
-  );
+  assertPluginActiveState({
+    managerWindow,
+    pluginId: testPluginId,
+    expectedActivateState: "never-activate",
+  });
 
   info("part9: set plugin state to always-activate");
   setPluginActivateState({
     managerWindow,
     pluginId: testPluginId,
     activateState: "always-activate",
   });
 
@@ -222,16 +325,33 @@ add_task(async function test_CTP_plugins
     PopupNotifications.getNotification("click-to-play-plugins", pluginBrowser);
   await BrowserTestUtils.waitForCondition(
     condition,
     "part11: should have a click-to-play notification"
   );
 
   BrowserTestUtils.removeTab(pluginTab);
   await close_manager(managerWindow);
+  await SpecialPowers.popPrefEnv();
+}
+
+add_task(async function test_CTP_plugins_XUL_aboutaddons() {
+  await SpecialPowers.pushPrefEnv({
+    set: [["extensions.htmlaboutaddons.enabled", false]],
+  });
+  await test_CTP_plugins("XUL");
+  await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function test_CTP_plugins_HTML_aboutaddons() {
+  await SpecialPowers.pushPrefEnv({
+    set: [["extensions.htmlaboutaddons.enabled", true]],
+  });
+  await test_CTP_plugins("HTML");
+  await SpecialPowers.popPrefEnv();
 });
 
 add_task(async function test_blocklisted_plugin_disabled() {
   async function ensurePluginEnabled() {
     let pluginTag = getTestPluginTag();
     pluginTag.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
     await new Promise(resolve => {
       setAndUpdateBlocklist(gHttpTestRoot + "blockNoPlugins.xml", resolve);
@@ -267,38 +387,26 @@ add_task(async function test_blocklisted
   await SpecialPowers.flushPrefEnv();
 });
 
 async function checkPlugins() {
   let testPluginAddon = await getTestPluginAddon();
   isnot(testPluginAddon, null, "Test Plug-in should exist");
   let testPluginId = testPluginAddon.id;
 
-  let managerWindow = await open_manager("addons://list/plugin");
-
-  let pluginEl = get_addon_element(managerWindow, testPluginId);
-  ok(pluginEl, `Got about:addons entry for ${testPluginId}`);
-
-  // Open the options menu (needed to check the disabled buttons).
-  const pluginOptions = pluginEl.querySelector("plugin-options");
-  pluginOptions.querySelector("panel-list").open = true;
+  let managerWindow;
 
-  // tests all buttons disabled (besides the checked one and the expand action)
-  // are expected to be disabled if locked is true.
-  for (const item of pluginOptions.querySelectorAll(
-    "panel-item:not([hidden])"
-  )) {
-    const actionName = item.getAttribute("action");
-    if (
-      !item.hasAttribute("checked") &&
-      actionName !== "expand" &&
-      actionName !== "preferences"
-    ) {
-      ok(
-        item.shadowRoot.querySelector("button").disabled,
-        `Plugin action "${actionName}" should be disabled`
-      );
-    }
-  }
-  pluginOptions.querySelector("panel-list").open = false;
+  info("Test blocklisted plugin actions disabled in XUL about:addons");
+  await SpecialPowers.pushPrefEnv({
+    set: [["extensions.htmlaboutaddons.enabled", false]],
+  });
+  managerWindow = await open_manager("addons://list/plugin");
+  await assertPluginAppDisabled({ managerWindow, pluginId: testPluginId });
+  await close_manager(managerWindow);
 
+  info("Test blocklisted plugin actions disabled in HTML about:addons");
+  await SpecialPowers.pushPrefEnv({
+    set: [["extensions.htmlaboutaddons.enabled", true]],
+  });
+  managerWindow = await open_manager("addons://list/plugin");
+  await assertPluginAppDisabled({ managerWindow, pluginId: testPluginId });
   await close_manager(managerWindow);
 }
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/browser/browser_bug562890.js
@@ -0,0 +1,78 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Tests the Preferences button for addons in list view
+ */
+
+// This test is testing XUL about:addons UI (the HTML about:addons has its
+// own test files for these test cases).
+SpecialPowers.pushPrefEnv({
+  set: [["extensions.htmlaboutaddons.enabled", false]],
+});
+
+async function test() {
+  requestLongerTimeout(2);
+
+  waitForExplicitFinish();
+
+  var addonPrefsURI = CHROMEROOT + "addon_prefs.xul";
+
+  var gProvider = new MockProvider();
+  gProvider.createAddons([
+    {
+      id: "test1@tests.mozilla.org",
+      name: "Test add-on 1",
+      description: "foo",
+    },
+    {
+      id: "test2@tests.mozilla.org",
+      name: "Test add-on 2",
+      description: "bar",
+      optionsURL: addonPrefsURI,
+    },
+  ]);
+
+  let aManager = await open_manager("addons://list/extension");
+  var addonList = aManager.document.getElementById("addon-list");
+  for (var addonItem of addonList.childNodes) {
+    if (
+      addonItem.hasAttribute("name") &&
+      addonItem.getAttribute("name") == "Test add-on 1"
+    ) {
+      break;
+    }
+  }
+  var prefsBtn = aManager.document.getAnonymousElementByAttribute(
+    addonItem,
+    "anonid",
+    "preferences-btn"
+  );
+  is(
+    prefsBtn.hidden,
+    true,
+    "Prefs button should be hidden for addon with no optionsURL set"
+  );
+
+  for (addonItem of addonList.childNodes) {
+    if (
+      addonItem.hasAttribute("name") &&
+      addonItem.getAttribute("name") == "Test add-on 2"
+    ) {
+      break;
+    }
+  }
+  prefsBtn = aManager.document.getAnonymousElementByAttribute(
+    addonItem,
+    "anonid",
+    "preferences-btn"
+  );
+  is(
+    prefsBtn.hidden,
+    true,
+    "Prefs button should not be shown for addon with just an optionsURL set"
+  );
+
+  close_manager(aManager, finish);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/browser/browser_bug562899.js
@@ -0,0 +1,80 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Simulates quickly switching between different list views to verify that only
+// the last selected is displayed
+
+const { PromiseTestUtils } = ChromeUtils.import(
+  "resource://testing-common/PromiseTestUtils.jsm"
+);
+
+PromiseTestUtils.whitelistRejectionsGlobally(/this\._errorLink/);
+
+var gManagerWindow;
+var gCategoryUtilities;
+
+// This test is testing XUL about:addons UI (the HTML about:addons has its
+// own test files and these test cases should be added in Bug 1552170).
+SpecialPowers.pushPrefEnv({
+  set: [["extensions.htmlaboutaddons.enabled", false]],
+});
+
+async function test() {
+  waitForExplicitFinish();
+
+  let aWindow = await open_manager(null);
+  gManagerWindow = aWindow;
+  gCategoryUtilities = new CategoryUtilities(gManagerWindow);
+  run_next_test();
+}
+
+async function end_test() {
+  await close_manager(gManagerWindow);
+  finish();
+}
+
+// Tests that loading a second view before the first has not finished loading
+// does not merge the results
+add_test(async function() {
+  var themeCount = null;
+  var pluginCount = null;
+  var themeItem = gCategoryUtilities.get("theme");
+  var pluginItem = gCategoryUtilities.get("plugin");
+  var list = gManagerWindow.document.getElementById("addon-list");
+
+  await gCategoryUtilities.open(themeItem);
+  themeCount = list.childNodes.length;
+  ok(themeCount > 0, "Test is useless if there are no themes");
+
+  await gCategoryUtilities.open(pluginItem);
+  pluginCount = list.childNodes.length;
+  ok(pluginCount > 0, "Test is useless if there are no plugins");
+
+  gCategoryUtilities.open(themeItem);
+
+  await gCategoryUtilities.open(pluginItem);
+  is(list.childNodes.length, pluginCount, "Should only see the plugins");
+
+  var item = list.firstChild;
+  while (item) {
+    is(item.getAttribute("type"), "plugin", "All items should be plugins");
+    item = item.nextSibling;
+  }
+
+  // Tests that switching to, from, to the same pane in quick succession
+  // still only shows the right number of results
+
+  gCategoryUtilities.open(themeItem);
+  gCategoryUtilities.open(pluginItem);
+  await gCategoryUtilities.open(themeItem);
+  is(list.childNodes.length, themeCount, "Should only see the theme");
+
+  item = list.firstChild;
+  while (item) {
+    is(item.getAttribute("type"), "theme", "All items should be theme");
+    item = item.nextSibling;
+  }
+
+  run_next_test();
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/browser/browser_bug562992.js
@@ -0,0 +1,74 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+/**
+ * This test ensures that when the extension manager UI is open and a
+ * restartless extension is installed from the web, its correct name appears
+ * when the download and installation complete.  See bug 562992.
+ */
+
+var gManagerWindow;
+var gProvider;
+var gInstall;
+
+const EXTENSION_NAME = "Wunderbar";
+
+// This test is testing XUL about:addons UI (the HTML about:addons has its
+// own test files for these test cases).
+SpecialPowers.pushPrefEnv({
+  set: [["extensions.htmlaboutaddons.enabled", false]],
+});
+
+async function test() {
+  waitForExplicitFinish();
+
+  gProvider = new MockProvider();
+
+  let aWindow = await open_manager("addons://list/extension");
+  gManagerWindow = aWindow;
+  run_next_test();
+}
+
+async function end_test() {
+  await close_manager(gManagerWindow);
+  finish();
+}
+
+// Create a MockInstall with a MockAddon payload and add it to the provider,
+// causing the onNewInstall event to fire, which in turn will cause a new
+// "installing" item to appear in the list of extensions.
+add_test(function() {
+  let addon = new MockAddon(undefined, EXTENSION_NAME, "extension", true);
+  gInstall = new MockInstall(undefined, undefined, addon);
+  gInstall.addTestListener({
+    onNewInstall: run_next_test,
+  });
+  gProvider.addInstall(gInstall);
+});
+
+// Finish the install, which will cause the "installing" item to be converted
+// to an "installed" item, which should have the correct add-on name.
+add_test(function() {
+  gInstall.addTestListener({
+    onInstallEnded() {
+      let list = gManagerWindow.document.getElementById("addon-list");
+
+      // To help prevent future breakage, don't assume the item is the only one
+      // in the list, or that it's first in the list.  Find it by name.
+      for (let i = 0; i < list.itemCount; i++) {
+        let item = list.getItemAtIndex(i);
+        if (item.getAttribute("name") === EXTENSION_NAME) {
+          ok(true, "Item with correct name found");
+          run_next_test();
+          return;
+        }
+      }
+      ok(false, "Item with correct name was not found");
+      run_next_test();
+    },
+  });
+  gInstall.install();
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/browser/browser_bug580298.js
@@ -0,0 +1,111 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Tests that certain types of addons do not have their version number
+// displayed. This currently only includes lightweight themes.
+
+var gManagerWindow;
+var gCategoryUtilities;
+var gProvider;
+
+// This test is testing XUL about:addons UI (and the HTML about:addons
+// actually shows the version also on themes).
+SpecialPowers.pushPrefEnv({
+  set: [["extensions.htmlaboutaddons.enabled", false]],
+});
+
+add_task(async function test() {
+  gProvider = new MockProvider();
+
+  gProvider.createAddons([
+    {
+      id: "extension@tests.mozilla.org",
+      name: "Extension 1",
+      type: "extension",
+      version: "123",
+    },
+    {
+      id: "theme@tests.mozilla.org",
+      name: "Theme 2",
+      type: "theme",
+      version: "456",
+    },
+    {
+      id: "lwtheme@personas.mozilla.org",
+      name: "Persona 3",
+      type: "theme",
+      version: "789",
+    },
+  ]);
+
+  gManagerWindow = await open_manager();
+  gCategoryUtilities = new CategoryUtilities(gManagerWindow);
+});
+
+function get(aId) {
+  return gManagerWindow.document.getElementById(aId);
+}
+
+function get_node(parent, anonid) {
+  return parent.ownerDocument.getAnonymousElementByAttribute(
+    parent,
+    "anonid",
+    anonid
+  );
+}
+
+function open_details(aList, aItem, aCallback) {
+  aList.ensureElementIsVisible(aItem);
+  EventUtils.synthesizeMouseAtCenter(aItem, {}, gManagerWindow);
+  return new Promise(resolve => wait_for_view_load(gManagerWindow, resolve));
+}
+
+var check_addon_has_version = async function(aList, aName, aVersion) {
+  for (let i = 0; i < aList.itemCount; i++) {
+    let item = aList.getItemAtIndex(i);
+    if (get_node(item, "name").textContent === aName) {
+      ok(true, "Item with correct name found");
+      let { version } = await get_tooltip_info(item);
+      is(version, aVersion, "Item has correct version");
+      return item;
+    }
+  }
+  ok(false, "Item with correct name was not found");
+  return null;
+};
+
+add_task(async function() {
+  await gCategoryUtilities.openType("extension");
+  info("Extension");
+  let list = gManagerWindow.document.getElementById("addon-list");
+  let item = await check_addon_has_version(list, "Extension 1", "123");
+  await open_details(list, item);
+  is_element_visible(get("detail-version"), "Details view has version visible");
+  is(get("detail-version").value, "123", "Details view has correct version");
+});
+
+add_task(async function() {
+  await gCategoryUtilities.openType("theme");
+  info("Normal theme");
+  let list = gManagerWindow.document.getElementById("addon-list");
+  let item = await check_addon_has_version(list, "Theme 2", "456");
+  await open_details(list, item);
+  is_element_visible(get("detail-version"), "Details view has version visible");
+  is(get("detail-version").value, "456", "Details view has correct version");
+});
+
+add_task(async function() {
+  await gCategoryUtilities.openType("theme");
+  info("Lightweight theme");
+  let list = gManagerWindow.document.getElementById("addon-list");
+  // See that the version isn't displayed
+  let item = await check_addon_has_version(list, "Persona 3", undefined);
+  await open_details(list, item);
+  is_element_hidden(get("detail-version"), "Details view has version hidden");
+  // If the version element is hidden then we don't care about its value
+});
+
+add_task(function end_test() {
+  close_manager(gManagerWindow, finish);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/browser/browser_bug587970.js
@@ -0,0 +1,294 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+/* eslint-disable mozilla/no-arbitrary-setTimeout */
+
+// Bug 587970 - Provide ability "Update all now" within 'Available Updates' screen
+
+var gManagerWindow;
+var gProvider;
+
+// This test is testing XUL about:addons UI (the HTML about:addons has its
+// own test files for these test cases, "Update all now" has been deprecated
+// and not supported in HTML about:addons).
+SpecialPowers.pushPrefEnv({
+  set: [["extensions.htmlaboutaddons.enabled", false]],
+});
+
+async function test() {
+  waitForExplicitFinish();
+
+  gProvider = new MockProvider();
+
+  gProvider.createAddons([
+    {
+      id: "addon1@tests.mozilla.org",
+      name: "addon 1",
+      version: "1.0",
+      applyBackgroundUpdates: AddonManager.AUTOUPDATE_DISABLE,
+    },
+    {
+      id: "addon2@tests.mozilla.org",
+      name: "addon 2",
+      version: "2.0",
+      applyBackgroundUpdates: AddonManager.AUTOUPDATE_DISABLE,
+    },
+    {
+      id: "addon3@tests.mozilla.org",
+      name: "addon 3",
+      version: "3.0",
+      applyBackgroundUpdates: AddonManager.AUTOUPDATE_DISABLE,
+    },
+  ]);
+
+  let aWindow = await open_manager("addons://updates/available");
+  gManagerWindow = aWindow;
+  run_next_test();
+}
+
+function end_test() {
+  close_manager(gManagerWindow, finish);
+}
+
+add_test(function() {
+  var list = gManagerWindow.document.getElementById("updates-list");
+  is(list.childNodes.length, 0, "Available updates list should be empty");
+
+  var emptyNotice = gManagerWindow.document.getElementById(
+    "empty-availableUpdates-msg"
+  );
+  is_element_visible(emptyNotice, "Empty notice should be visible");
+
+  var updateSelected = gManagerWindow.document.getElementById(
+    "update-selected-btn"
+  );
+  is_element_hidden(updateSelected, "Update Selected button should be hidden");
+
+  info("Adding updates");
+  gProvider.createInstalls([
+    {
+      name: "addon 1",
+      version: "1.1",
+      existingAddon: gProvider.addons[0],
+    },
+    {
+      name: "addon 2",
+      version: "2.1",
+      existingAddon: gProvider.addons[1],
+    },
+    {
+      name: "addon 3",
+      version: "3.1",
+      existingAddon: gProvider.addons[2],
+    },
+  ]);
+
+  function wait_for_refresh() {
+    if (
+      list.childNodes.length == 3 &&
+      list.childNodes[0].mManualUpdate &&
+      list.childNodes[1].mManualUpdate &&
+      list.childNodes[2].mManualUpdate
+    ) {
+      run_next_test();
+    } else {
+      info("Waiting for pane to refresh");
+      setTimeout(wait_for_refresh, 10);
+    }
+  }
+  info("Waiting for pane to refresh");
+  setTimeout(wait_for_refresh, 10);
+});
+
+add_test(function() {
+  var list = gManagerWindow.document.getElementById("updates-list");
+  is(list.childNodes.length, 3, "Available updates list should have 2 items");
+
+  var item1 = get_addon_element(gManagerWindow, "addon1@tests.mozilla.org");
+  isnot(item1, null, "Item for addon1@tests.mozilla.org should be in list");
+  var item2 = get_addon_element(gManagerWindow, "addon2@tests.mozilla.org");
+  isnot(item2, null, "Item for addon2@tests.mozilla.org should be in list");
+  var item3 = get_addon_element(gManagerWindow, "addon3@tests.mozilla.org");
+  isnot(item3, null, "Item for addon3@tests.mozilla.org should be in list");
+
+  var emptyNotice = gManagerWindow.document.getElementById(
+    "empty-availableUpdates-msg"
+  );
+  is_element_hidden(emptyNotice, "Empty notice should be hidden");
+
+  var updateSelected = gManagerWindow.document.getElementById(
+    "update-selected-btn"
+  );
+  is_element_visible(
+    updateSelected,
+    "Update Selected button should be visible"
+  );
+  is(
+    updateSelected.disabled,
+    false,
+    "Update Selected button should be enabled by default"
+  );
+
+  is(
+    item1._includeUpdate.checked,
+    true,
+    "Include Update checkbox should be checked by default for addon1"
+  );
+  is(
+    item2._includeUpdate.checked,
+    true,
+    "Include Update checkbox should be checked by default for addon2"
+  );
+  is(
+    item3._includeUpdate.checked,
+    true,
+    "Include Update checkbox should be checked by default for addon3"
+  );
+
+  info("Unchecking Include Update checkbox for addon1");
+  EventUtils.synthesizeMouse(item1._includeUpdate, 2, 2, {}, gManagerWindow);
+  is(
+    item1._includeUpdate.checked,
+    false,
+    "Include Update checkbox should now be be unchecked for addon1"
+  );
+  is(
+    updateSelected.disabled,
+    false,
+    "Update Selected button should still be enabled"
+  );
+
+  info("Unchecking Include Update checkbox for addon2");
+  EventUtils.synthesizeMouse(item2._includeUpdate, 2, 2, {}, gManagerWindow);
+  is(
+    item2._includeUpdate.checked,
+    false,
+    "Include Update checkbox should now be be unchecked for addon2"
+  );
+  is(
+    updateSelected.disabled,
+    false,
+    "Update Selected button should still be enabled"
+  );
+
+  info("Unchecking Include Update checkbox for addon3");
+  EventUtils.synthesizeMouse(item3._includeUpdate, 2, 2, {}, gManagerWindow);
+  is(
+    item3._includeUpdate.checked,
+    false,
+    "Include Update checkbox should now be be unchecked for addon3"
+  );
+  is(
+    updateSelected.disabled,
+    true,
+    "Update Selected button should now be disabled"
+  );
+
+  info("Checking Include Update checkbox for addon2");
+  EventUtils.synthesizeMouse(item2._includeUpdate, 2, 2, {}, gManagerWindow);
+  is(
+    item2._includeUpdate.checked,
+    true,
+    "Include Update checkbox should now be be checked for addon2"
+  );
+  is(
+    updateSelected.disabled,
+    false,
+    "Update Selected button should now be enabled"
+  );
+
+  info("Checking Include Update checkbox for addon3");
+  EventUtils.synthesizeMouse(item3._includeUpdate, 2, 2, {}, gManagerWindow);
+  is(
+    item3._includeUpdate.checked,
+    true,
+    "Include Update checkbox should now be be checked for addon3"
+  );
+  is(
+    updateSelected.disabled,
+    false,
+    "Update Selected button should now be enabled"
+  );
+
+  var installCount = 0;
+  var listener = {
+    onDownloadStarted(aInstall) {
+      isnot(
+        aInstall.existingAddon.id,
+        "addon1@tests.mozilla.org",
+        "Should not have seen a download start for addon1"
+      );
+    },
+
+    onInstallEnded(aInstall) {
+      if (++installCount < 2) {
+        return;
+      }
+
+      gProvider.installs[0].removeTestListener(listener);
+      gProvider.installs[1].removeTestListener(listener);
+      gProvider.installs[2].removeTestListener(listener);
+
+      // Installs are started synchronously so by the time an executeSoon is
+      // executed all installs that are going to start will have started
+      executeSoon(function() {
+        is(
+          gProvider.installs[0].state,
+          AddonManager.STATE_AVAILABLE,
+          "addon1 should not have been upgraded"
+        );
+        is(
+          gProvider.installs[1].state,
+          AddonManager.STATE_INSTALLED,
+          "addon2 should have been upgraded"
+        );
+        is(
+          gProvider.installs[2].state,
+          AddonManager.STATE_INSTALLED,
+          "addon3 should have been upgraded"
+        );
+
+        run_next_test();
+      });
+    },
+  };
+  gProvider.installs[0].addTestListener(listener);
+  gProvider.installs[1].addTestListener(listener);
+  gProvider.installs[2].addTestListener(listener);
+  info("Clicking Update Selected button");
+  EventUtils.synthesizeMouseAtCenter(updateSelected, {}, gManagerWindow);
+});
+
+add_test(function() {
+  var updateSelected = gManagerWindow.document.getElementById(
+    "update-selected-btn"
+  );
+  is(
+    updateSelected.disabled,
+    true,
+    "Update Selected button should be disabled"
+  );
+
+  var item1 = get_addon_element(gManagerWindow, "addon1@tests.mozilla.org");
+  isnot(item1, null, "Item for addon1@tests.mozilla.org should be in list");
+  is(
+    item1._includeUpdate.checked,
+    false,
+    "Include Update checkbox should not have changed"
+  );
+
+  info("Checking Include Update checkbox for addon1");
+  EventUtils.synthesizeMouse(item1._includeUpdate, 2, 2, {}, gManagerWindow);
+  is(
+    item1._includeUpdate.checked,
+    true,
+    "Include Update checkbox should now be be checked for addon1"
+  );
+  is(
+    updateSelected.disabled,
+    false,
+    "Update Selected button should now not be disabled"
+  );
+
+  run_next_test();
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/browser/browser_bug590347.js
@@ -0,0 +1,152 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Bug 590347
+// Tests if softblock notifications are exposed in preference to incompatible
+// notifications when compatibility checking is disabled
+
+var gProvider;
+var gManagerWindow;
+var gCategoryUtilities;
+
+var gApp = document.getElementById("bundle_brand").getString("brandShortName");
+var gVersion = Services.appinfo.version;
+
+// Tested in browser_html_warning_messages.js for HTML.
+SpecialPowers.pushPrefEnv({
+  set: [["extensions.htmlaboutaddons.enabled", false]],
+});
+
+// Opens the details view of an add-on
+async function open_details(aId, aType, aCallback) {
+  requestLongerTimeout(2);
+
+  await gCategoryUtilities.openType(aType);
+  var list = gManagerWindow.document.getElementById("addon-list");
+  var item = list.firstChild;
+  while (item) {
+    if ("mAddon" in item && item.mAddon.id == aId) {
+      list.ensureElementIsVisible(item);
+      EventUtils.synthesizeMouseAtCenter(
+        item,
+        { clickCount: 1 },
+        gManagerWindow
+      );
+      EventUtils.synthesizeMouseAtCenter(
+        item,
+        { clickCount: 2 },
+        gManagerWindow
+      );
+      wait_for_view_load(gManagerWindow, aCallback);
+      return;
+    }
+    item = item.nextSibling;
+  }
+  ok(false, "Should have found the add-on in the list");
+}
+
+function get_list_view_warning_node() {
+  let item = gManagerWindow.document.getElementById("addon-list").firstChild;
+  let found = false;
+  while (item) {
+    if (item.mAddon.name == "Test add-on") {
+      found = true;
+      break;
+    }
+    item = item.nextSibling;
+  }
+  ok(found, "Test add-on node should have been found.");
+  return item.ownerDocument.getAnonymousElementByAttribute(
+    item,
+    "anonid",
+    "warning"
+  );
+}
+
+function get_detail_view_warning_node(aManagerWindow) {
+  if (aManagerWindow) {
+    return aManagerWindow.document.getElementById("detail-warning");
+  }
+  return undefined;
+}
+
+async function test() {
+  waitForExplicitFinish();
+
+  gProvider = new MockProvider();
+
+  gProvider.createAddons([
+    {
+      id: "addon1@tests.mozilla.org",
+      name: "Test add-on",
+      description: "A test add-on",
+      isCompatible: false,
+      blocklistState: Ci.nsIBlocklistService.STATE_SOFTBLOCKED,
+    },
+  ]);
+
+  let aWindow = await open_manager(null);
+  gManagerWindow = aWindow;
+  gCategoryUtilities = new CategoryUtilities(gManagerWindow);
+  run_next_test();
+}
+
+async function end_test() {
+  await close_manager(gManagerWindow);
+  finish();
+}
+
+// Check with compatibility checking enabled
+add_test(async function() {
+  await gCategoryUtilities.openType("extension");
+  Services.prefs.setBoolPref(PREF_CHECK_COMPATIBILITY, true);
+  let warning_node = get_list_view_warning_node();
+  is_element_visible(warning_node, "Warning message should be visible");
+  is(
+    warning_node.textContent,
+    "Test add-on is incompatible with " + gApp + " " + gVersion + ".",
+    "Warning message should be correct"
+  );
+  run_next_test();
+});
+
+add_test(function() {
+  open_details("addon1@tests.mozilla.org", "extension", function() {
+    let warning_node = get_detail_view_warning_node(gManagerWindow);
+    is_element_visible(warning_node, "Warning message should be visible");
+    is(
+      warning_node.textContent,
+      "Test add-on is incompatible with " + gApp + " " + gVersion + ".",
+      "Warning message should be correct"
+    );
+    Services.prefs.setBoolPref(PREF_CHECK_COMPATIBILITY, false);
+    run_next_test();
+  });
+});
+
+// Check with compatibility checking disabled
+add_test(async function() {
+  await gCategoryUtilities.openType("extension");
+  let warning_node = get_list_view_warning_node();
+  is_element_visible(warning_node, "Warning message should be visible");
+  is(
+    warning_node.textContent,
+    "Test add-on is known to cause security or stability issues.",
+    "Warning message should be correct"
+  );
+  run_next_test();
+});
+
+add_test(function() {
+  open_details("addon1@tests.mozilla.org", "extension", function() {
+    let warning_node = get_detail_view_warning_node(gManagerWindow);
+    is_element_visible(warning_node, "Warning message should be visible");
+    is(
+      warning_node.textContent,
+      "Test add-on is known to cause security or stability issues.",
+      "Warning message should be correct"
+    );
+    run_next_test();
+  });
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/browser/browser_bug591663.js
@@ -0,0 +1,171 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test that the empty notice in the list view disappears as it should
+
+// Don't use a standard list view (e.g. "extension") to ensure that the list is
+// initially empty. Don't need to worry about the list of categories displayed
+// since only the list view itself is tested.
+var VIEW_ID = "addons://list/mock-addon";
+
+var LIST_ID = "addon-list";
+var EMPTY_ID = "addon-list-empty";
+
+var gManagerWindow;
+var gProvider;
+var gItem;
+
+var gInstallProperties = {
+  name: "Bug 591663 Mock Install",
+  type: "mock-addon",
+};
+var gAddonProperties = {
+  id: "test1@tests.mozilla.org",
+  name: "Bug 591663 Mock Add-on",
+  type: "mock-addon",
+};
+var gExtensionProperties = {
+  name: "Bug 591663 Extension Install",
+  type: "extension",
+};
+
+// Not implemented yet on HTML about:addons (Bug 1550911).
+SpecialPowers.pushPrefEnv({
+  set: [["extensions.htmlaboutaddons.enabled", false]],
+});
+
+async function test() {
+  waitForExplicitFinish();
+
+  gProvider = new MockProvider(true, [
+    {
+      id: "mock-addon",
+      name: "Mock Add-ons",
+      uiPriority: 4500,
+      flags: AddonManager.TYPE_UI_VIEW_LIST,
+    },
+  ]);
+
+  let aWindow = await open_manager(VIEW_ID);
+  gManagerWindow = aWindow;
+  run_next_test();
+}
+
+function end_test() {
+  close_manager(gManagerWindow, finish);
+}
+
+/**
+ * Check that the list view is as expected
+ *
+ * @param  aItem
+ *         The expected item in the list, or null if list should be empty
+ */
+function check_list(aItem) {
+  // Check state of the empty notice
+  let emptyNotice = gManagerWindow.document.getElementById(EMPTY_ID);
+  ok(emptyNotice != null, "Should have found the empty notice");
+  is(
+    !emptyNotice.hidden,
+    aItem == null,
+    "Empty notice should be showing if list empty"
+  );
+
+  // Check the children of the list
+  let list = gManagerWindow.document.getElementById(LIST_ID);
+  is(
+    list.childNodes.length,
+    aItem ? 1 : 0,
+    "Should get expected number of items in list"
+  );
+  if (aItem != null) {
+    let itemName = list.firstChild.getAttribute("name");
+    is(itemName, aItem.name, "List item should have correct name");
+  }
+}
+
+// Test that the empty notice is showing and no items are showing in list
+add_test(function() {
+  check_list(null);
+  run_next_test();
+});
+
+// Test that a new, non-active, install does not affect the list view
+add_test(function() {
+  gItem = gProvider.createInstalls([gInstallProperties])[0];
+  check_list(null);
+  run_next_test();
+});
+
+// Test that onInstallStarted properly hides empty notice and adds install to list
+add_test(function() {
+  gItem.addTestListener({
+    onDownloadStarted() {
+      // Install type unknown until download complete
+      check_list(null);
+    },
+    onInstallStarted() {
+      check_list(gItem);
+    },
+    onInstallEnded() {
+      check_list(gItem);
+      run_next_test();
+    },
+  });
+
+  gItem.install();
+});
+
+// Test that restarting the manager does not change list
+add_test(async function() {
+  let aManagerWindow = await restart_manager(gManagerWindow, VIEW_ID);
+  gManagerWindow = aManagerWindow;
+  check_list(gItem);
+  run_next_test();
+});
+
+// Test that onInstallCancelled removes install and shows empty notice
+add_test(function() {
+  gItem.cancel();
+  gItem = null;
+  check_list(null);
+  run_next_test();
+});
+
+// Test that add-ons of a different type do not show up in the list view
+add_test(function() {
+  let extension = gProvider.createInstalls([gExtensionProperties])[0];
+  check_list(null);
+
+  extension.addTestListener({
+    onDownloadStarted() {
+      check_list(null);
+    },
+    onInstallStarted() {
+      check_list(null);
+    },
+    onInstallEnded() {
+      check_list(null);
+      extension.cancel();
+      run_next_test();
+    },
+  });
+
+  extension.install();
+});
+
+// Test that onExternalInstall properly hides empty notice and adds install to list
+add_test(function() {
+  gItem = gProvider.createAddons([gAddonProperties])[0];
+  check_list(gItem);
+  run_next_test();
+});
+
+// Test that restarting the manager does not change list
+add_test(async function() {
+  let aManagerWindow = await restart_manager(gManagerWindow, VIEW_ID);
+  gManagerWindow = aManagerWindow;
+  check_list(gItem);
+  run_next_test();
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/browser/browser_bug618502.js
@@ -0,0 +1,58 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Bug 608316 - Test that opening the manager to an add-on that doesn't exist
+// just loads the default view
+
+var gCategoryUtilities;
+
+// Not implemented yet on HTML about:addons (Bug 1552184), once supported
+// this test case will be included in the HTML about:addons test files.
+SpecialPowers.pushPrefEnv({
+  set: [["extensions.htmlaboutaddons.enabled", false]],
+});
+
+function test() {
+  waitForExplicitFinish();
+
+  run_next_test();
+}
+
+function end_test() {
+  finish();
+}
+
+add_test(async function() {
+  let aManager = await open_manager("addons://detail/foo");
+  gCategoryUtilities = new CategoryUtilities(aManager);
+  is(
+    gCategoryUtilities.selectedCategory,
+    "discover",
+    "Should fall back to the discovery pane"
+  );
+
+  close_manager(aManager, run_next_test);
+});
+
+// Also test that opening directly to an add-on that does exist doesn't break
+// and selects the right category
+add_test(async function() {
+  new MockProvider().createAddons([
+    {
+      id: "addon1@tests.mozilla.org",
+      name: "addon 1",
+      version: "1.0",
+    },
+  ]);
+
+  let aManager = await open_manager("addons://detail/addon1@tests.mozilla.org");
+  gCategoryUtilities = new CategoryUtilities(aManager);
+  is(
+    gCategoryUtilities.selectedCategory,
+    "extension",
+    "Should have selected the right category"
+  );
+
+  close_manager(aManager, run_next_test);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/browser/browser_details.js
@@ -0,0 +1,1240 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Tests various aspects of the details view
+
+const { AppConstants } = ChromeUtils.import(
+  "resource://gre/modules/AppConstants.jsm"
+);
+
+const PREF_AUTOUPDATE_DEFAULT = "extensions.update.autoUpdateDefault";
+
+var gManagerWindow;
+var gCategoryUtilities;
+var gProvider;
+
+var gApp = document.getElementById("bundle_brand").getString("brandShortName");
+var gVersion = Services.appinfo.version;
+var gDate = new Date(2010, 7, 1);
+var infoURL =
+  Services.urlFormatter.formatURLPref("app.support.baseURL") +
+  "unsigned-addons";
+
+// This test is testing XUL about:addons UI (the HTML about:addons has its
+// own test files for these test cases).
+SpecialPowers.pushPrefEnv({
+  set: [["extensions.htmlaboutaddons.enabled", false]],
+});
+
+async function open_details(aId, aType, aCallback) {
+  requestLongerTimeout(2);
+
+  await gCategoryUtilities.openType(aType);
+  var list = gManagerWindow.document.getElementById("addon-list");
+  var item = list.firstChild;
+  while (item) {
+    if ("mAddon" in item && item.mAddon.id == aId) {
+      list.ensureElementIsVisible(item);
+      EventUtils.synthesizeMouseAtCenter(
+        item,
+        { clickCount: 1 },
+        gManagerWindow
+      );
+      EventUtils.synthesizeMouseAtCenter(
+        item,
+        { clickCount: 2 },
+        gManagerWindow
+      );
+      wait_for_view_load(gManagerWindow, aCallback);
+      return;
+    }
+    item = item.nextSibling;
+  }
+  ok(false, "Should have found the add-on in the list");
+}
+
+function get(aId) {
+  return gManagerWindow.document.getElementById(aId);
+}
+
+async function test() {
+  requestLongerTimeout(2);
+
+  waitForExplicitFinish();
+
+  gProvider = new MockProvider();
+
+  gProvider.createAddons([
+    {
+      id: "addon1@tests.mozilla.org",
+      name: "Test add-on 1",
+      version: "2.1",
+      description: "Short description",
+      fullDescription: "Longer description",
+      type: "extension",
+      iconURL: "chrome://foo/skin/icon.png",
+      contributionURL: "http://foo.com",
+      contributionAmount: "$0.99",
+      sourceURI: Services.io.newURI("http://example.com/foo"),
+      averageRating: 4,
+      reviewCount: 5,
+      reviewURL: "http://example.com/reviews",
+      homepageURL: "http://example.com/addon1",
+      applyBackgroundUpdates: AddonManager.AUTOUPDATE_ENABLE,
+    },
+    {
+      id: "addon2@tests.mozilla.org",
+      name: "Test add-on 2",
+      version: "2.2",
+      description: "Short description",
+      creator: { name: "Mozilla", url: null },
+      type: "extension",
+      iconURL: "chrome://foo/skin/icon.png",
+      contributionURL: "http://foo.com",
+      contributionAmount: null,
+      updateDate: gDate,
+      permissions: AddonManager.PERM_CAN_CHANGE_PRIVATEBROWSING_ACCESS,
+    },
+    {
+      id: "addon-theme@tests.mozilla.org",
+      name: "Test add-on theme",
+      version: "2.3",
+      description: "Short description",
+      creator: { name: "Mozilla", url: null },
+      type: "theme",
+      iconURL: "chrome://foo/skin/icon.png",
+      contributionURL: "http://foo.com",
+      contributionAmount: null,
+      updateDate: gDate,
+      permissions: 0,
+    },
+    {
+      id: "addon3@tests.mozilla.org",
+      name: "Test add-on 3",
+      description: "Short description",
+      creator: { name: "Mozilla", url: "http://www.mozilla.org" },
+      type: "extension",
+      sourceURI: Services.io.newURI("http://example.com/foo"),
+      updateDate: gDate,
+      reviewCount: 1,
+      reviewURL: "http://example.com/reviews",
+      applyBackgroundUpdates: AddonManager.AUTOUPDATE_DISABLE,
+      isActive: false,
+      isCompatible: false,
+      appDisabled: true,
+      permissions:
+        AddonManager.PERM_CAN_ENABLE |
+        AddonManager.PERM_CAN_DISABLE |
+        AddonManager.PERM_CAN_UPGRADE,
+    },
+    {
+      id: "addon5@tests.mozilla.org",
+      blocklistURL: "http://example.com/addon5@tests.mozilla.org",
+      name: "Test add-on 5",
+      isActive: false,
+      blocklistState: Ci.nsIBlocklistService.STATE_BLOCKED,
+      appDisabled: true,
+    },
+    {
+      id: "addon8@tests.mozilla.org",
+      blocklistURL: "http://example.com/addon8@tests.mozilla.org",
+      name: "Test add-on 8",
+      blocklistState: Ci.nsIBlocklistService.STATE_OUTDATED,
+    },
+    {
+      id: "addon9@tests.mozilla.org",
+      name: "Test add-on 9",
+      signedState: AddonManager.SIGNEDSTATE_MISSING,
+    },
+    {
+      id: "addon10@tests.mozilla.org",
+      name: "Test add-on 10",
+      signedState: AddonManager.SIGNEDSTATE_MISSING,
+      isActive: false,
+      appDisabled: true,
+      isCompatible: false,
+    },
+    {
+      id: "addon11@tests.mozilla.org",
+      name: "Test add-on 11",
+      signedState: AddonManager.SIGNEDSTATE_PRELIMINARY,
+      foreignInstall: true,
+      isActive: false,
+      appDisabled: true,
+      isCompatible: false,
+    },
+    {
+      id: "addon12@tests.mozilla.org",
+      name: "Test add-on 12",
+      signedState: AddonManager.SIGNEDSTATE_SIGNED,
+      foreignInstall: true,
+      isCorrectlySigned: true,
+      permissions:
+        AddonManager.PERM_CAN_UNINSTALL |
+        AddonManager.PERM_CAN_ENABLE |
+        AddonManager.PERM_CAN_DISABLE |
+        (AddonManager.PERM_CAN_UPGRADE &
+          ~AddonManager.PERM_CAN_CHANGE_PRIVATEBROWSING_ACCESS),
+    },
+  ]);
+
+  let aWindow = await open_manager(null);
+  gManagerWindow = aWindow;
+  gCategoryUtilities = new CategoryUtilities(gManagerWindow);
+
+  run_next_test();
+}
+
+async function end_test() {
+  await close_manager(gManagerWindow);
+  finish();
+}
+
+// Opens and tests the details view for add-on 2
+add_test(async function() {
+  open_details("addon2@tests.mozilla.org", "extension", async () => {
+    is(
+      get("detail-name").textContent,
+      "Test add-on 2",
+      "Name should be correct"
+    );
+    is_element_visible(get("detail-version"), "Version should not be hidden");
+    is(get("detail-version").value, "2.2", "Version should be correct");
+    is(
+      get("detail-icon").src,
+      "chrome://foo/skin/icon.png",
+      "Icon should be correct"
+    );
+
+    is_element_visible(get("detail-creator"), "Creator should not be hidden");
+    is_element_visible(
+      get("detail-creator")._creatorName,
+      "Creator name should not be hidden"
+    );
+    is(
+      get("detail-creator")._creatorName.value,
+      "Mozilla",
+      "Creator should be correct"
+    );
+    is_element_hidden(
+      get("detail-creator")._creatorLink,
+      "Creator link should be hidden"
+    );
+
+    is(
+      get("detail-desc").textContent,
+      "Short description",
+      "Description should be correct"
+    );
+    is_element_hidden(
+      get("detail-fulldesc"),
+      "Full description should be hidden"
+    );
+
+    is_element_visible(
+      get("detail-contributions"),
+      "Contributions section should be visible"
+    );
+
+    is_element_visible(
+      get("detail-dateUpdated"),
+      "Update date should not be hidden"
+    );
+    is(
+      get("detail-dateUpdated").value,
+      formatDate(gDate),
+      "Update date should be correct"
+    );
+
+    is_element_visible(
+      get("detail-privateBrowsing-row"),
+      "Private browsing should not be hidden"
+    );
+    is_element_visible(
+      get("detail-privateBrowsing-row-footer"),
+      "Private browsing footer should not be hidden"
+    );
+    is(
+      get("detail-privateBrowsing").value,
+      "0",
+      "Private browsing should be off"
+    );
+
+    is_element_hidden(get("detail-rating-row"), "Rating should be hidden");
+
+    is_element_hidden(
+      get("detail-homepage-row"),
+      "Homepage should not be visible"
+    );
+    is_element_hidden(
+      get("detail-repository-row"),
+      "Repository profile should not be visible"
+    );
+
+    is_element_hidden(get("detail-updates-row"), "Updates should be hidden");
+
+    is_element_hidden(
+      get("detail-prefs-btn"),
+      "Preferences button should be hidden"
+    );
+    is_element_hidden(
+      get("detail-enable-btn"),
+      "Enable button should be hidden"
+    );
+    is_element_hidden(
+      get("detail-disable-btn"),
+      "Disable button should be hidden"
+    );
+    is_element_hidden(
+      get("detail-uninstall-btn"),
+      "Remove button should be hidden"
+    );
+
+    is_element_hidden(
+      get("detail-warning"),
+      "Warning message should be hidden"
+    );
+    is_element_hidden(
+      get("detail-warning-link"),
+      "Warning link should be hidden"
+    );
+    is_element_hidden(get("detail-error"), "Error message should be hidden");
+    is_element_hidden(get("detail-error-link"), "Error link should be hidden");
+    is_element_hidden(
+      get("detail-pending"),
+      "Pending message should be hidden"
+    );
+
+    run_next_test();
+  });
+});
+
+// Opens and tests the details view for add-on theme
+add_test(async function() {
+  // This is a duplicate of addon-2, so we're only testing that private browsing is
+  // not visible.
+  open_details("addon-theme@tests.mozilla.org", "theme", async () => {
+    is(
+      get("detail-name").textContent,
+      "Test add-on theme",
+      "Name should be correct"
+    );
+    is_element_visible(get("detail-version"), "Version should not be hidden");
+    is(get("detail-version").value, "2.3", "Version should be correct");
+    is(
+      get("detail-icon").src,
+      "chrome://foo/skin/icon.png",
+      "Icon should be correct"
+    );
+
+    is_element_hidden(
+      get("detail-privateBrowsing-row"),
+      "Private browsing should be hidden"
+    );
+    is_element_hidden(
+      get("detail-privateBrowsing-row-footer"),
+      "Private browsing footer should be hidden"
+    );
+    is(
+      get("detail-privateBrowsing").value,
+      "0",
+      "Private browsing should be off"
+    );
+
+    run_next_test();
+  });
+});
+
+// Opens and tests the details view for add-on 3
+add_test(function() {
+  open_details("addon3@tests.mozilla.org", "extension", function() {
+    is(
+      get("detail-name").textContent,
+      "Test add-on 3",
+      "Name should be correct"
+    );
+    is_element_hidden(get("detail-version"), "Version should be hidden");
+    is(get("detail-icon").src, "", "Icon should be correct");
+
+    is_element_visible(get("detail-creator"), "Creator should not be hidden");
+    is_element_hidden(
+      get("detail-creator")._creatorName,
+      "Creator name should be hidden"
+    );
+    is_element_visible(
+      get("detail-creator")._creatorLink,
+      "Creator link should not be hidden"
+    );
+    is(
+      get("detail-creator")._creatorLink.value,
+      "Mozilla",
+      "Creator link should be correct"
+    );
+    is(
+      get("detail-creator")._creatorLink.href,
+      "http://www.mozilla.org",
+      "Creator link href should be correct"
+    );
+
+    is_element_hidden(
+      get("detail-contributions"),
+      "Contributions section should be hidden"
+    );
+
+    is_element_visible(
+      get("detail-updates-row"),
+      "Updates should not be hidden"
+    );
+    is_element_visible(
+      get("detail-dateUpdated"),
+      "Update date should not be hidden"
+    );
+    is(
+      get("detail-dateUpdated").value,
+      formatDate(gDate),
+      "Update date should be correct"
+    );
+
+    is_element_visible(
+      get("detail-rating-row"),
+      "Rating row should not be hidden"
+    );
+    is_element_hidden(get("detail-rating"), "Rating should be hidden");
+    is_element_visible(get("detail-reviews"), "Reviews should not be hidden");
+    is(
+      get("detail-reviews").href,
+      "http://example.com/reviews",
+      "Review URL should be correct"
+    );
+    is(
+      get("detail-reviews").value,
+      "1 review",
+      "Review text should be correct"
+    );
+
+    is_element_visible(
+      get("detail-autoUpdate"),
+      "Updates should not be hidden"
+    );
+    ok(get("detail-autoUpdate").lastChild.selected, "Updates should be manual");
+    is_element_visible(
+      get("detail-findUpdates-btn"),
+      "Check for updates should be visible"
+    );
+    EventUtils.synthesizeMouseAtCenter(
+      get("detail-autoUpdate").childNodes[1],
+      {},
+      gManagerWindow
+    );
+    ok(
+      get("detail-autoUpdate").childNodes[1].selected,
+      "Updates should be automatic"
+    );
+    is_element_hidden(
+      get("detail-findUpdates-btn"),
+      "Check for updates should be hidden"
+    );
+    EventUtils.synthesizeMouseAtCenter(
+      get("detail-autoUpdate").lastChild,
+      {},
+      gManagerWindow
+    );
+    ok(get("detail-autoUpdate").lastChild.selected, "Updates should be manual");
+    is_element_visible(
+      get("detail-findUpdates-btn"),
+      "Check for updates should be visible"
+    );
+
+    info("Setting " + PREF_AUTOUPDATE_DEFAULT + " to true");
+    Services.prefs.setBoolPref(PREF_AUTOUPDATE_DEFAULT, true);
+    EventUtils.synthesizeMouseAtCenter(
+      get("detail-autoUpdate").firstChild,
+      {},
+      gManagerWindow
+    );
+    ok(
+      get("detail-autoUpdate").firstChild.selected,
+      "Updates should be default"
+    );
+    is_element_hidden(
+      get("detail-findUpdates-btn"),
+      "Check for updates should be hidden"
+    );
+
+    info("Setting " + PREF_AUTOUPDATE_DEFAULT + " to false");
+    Services.prefs.setBoolPref(PREF_AUTOUPDATE_DEFAULT, false);
+    ok(
+      get("detail-autoUpdate").firstChild.selected,
+      "Updates should be default"
+    );
+    is_element_visible(
+      get("detail-findUpdates-btn"),
+      "Check for updates should be visible"
+    );
+    EventUtils.synthesizeMouseAtCenter(
+      get("detail-autoUpdate").childNodes[1],
+      {},
+      gManagerWindow
+    );
+    ok(
+      get("detail-autoUpdate").childNodes[1].selected,
+      "Updates should be automatic"
+    );
+    is_element_hidden(
+      get("detail-findUpdates-btn"),
+      "Check for updates should be hidden"
+    );
+    EventUtils.synthesizeMouseAtCenter(
+      get("detail-autoUpdate").firstChild,
+      {},
+      gManagerWindow
+    );
+    ok(
+      get("detail-autoUpdate").firstChild.selected,
+      "Updates should be default"
+    );
+    is_element_visible(
+      get("detail-findUpdates-btn"),
+      "Check for updates should be visible"
+    );
+    Services.prefs.clearUserPref(PREF_AUTOUPDATE_DEFAULT);
+
+    is_element_hidden(
+      get("detail-prefs-btn"),
+      "Preferences button should be hidden"
+    );
+    is_element_hidden(
+      get("detail-enable-btn"),
+      "Enable button should be hidden"
+    );
+    is_element_hidden(
+      get("detail-disable-btn"),
+      "Disable button should be hidden"
+    );
+    is_element_hidden(
+      get("detail-uninstall-btn"),
+      "Remove button should be hidden"
+    );
+
+    is_element_visible(
+      get("detail-warning"),
+      "Warning message should be visible"
+    );
+    is(
+      get("detail-warning").textContent,
+      "Test add-on 3 is incompatible with " + gApp + " " + gVersion + ".",
+      "Warning message should be correct"
+    );
+    is_element_hidden(
+      get("detail-warning-link"),
+      "Warning link should be hidden"
+    );
+    is_element_hidden(get("detail-error"), "Error message should be hidden");
+    is_element_hidden(get("detail-error-link"), "Error link should be hidden");
+    is_element_hidden(
+      get("detail-pending"),
+      "Pending message should be hidden"
+    );
+
+    run_next_test();
+  });
+});
+
+// Opens and tests the details view for add-on 5
+add_test(function() {
+  open_details("addon5@tests.mozilla.org", "extension", async function() {
+    await TestUtils.waitForCondition(
+      () => !BrowserTestUtils.is_hidden(get("detail-error-link"))
+    );
+    is(
+      get("detail-name").textContent,
+      "Test add-on 5",
+      "Name should be correct"
+    );
+
+    is_element_hidden(
+      get("detail-prefs-btn"),
+      "Preferences button should be hidden"
+    );
+    is_element_hidden(
+      get("detail-enable-btn"),
+      "Enable button should be hidden"
+    );
+    is_element_hidden(
+      get("detail-disable-btn"),
+      "Disable button should be hidden"
+    );
+    is_element_visible(
+      get("detail-uninstall-btn"),
+      "Remove button should be visible"
+    );
+
+    is_element_hidden(
+      get("detail-warning"),
+      "Warning message should be hidden"
+    );
+    is_element_hidden(
+      get("detail-warning-link"),
+      "Warning link should be hidden"
+    );
+    is_element_visible(get("detail-error"), "Error message should be visible");
+    is(
+      get("detail-error").textContent,
+      "Test add-on 5 has been disabled due to security or stability issues.",
+      "Error message should be correct"
+    );
+    is_element_visible(
+      get("detail-error-link"),
+      "Error link should be visible"
+    );
+    is(
+      get("detail-error-link").value,
+      "More Information",
+      "Error link text should be correct"
+    );
+    is(
+      get("detail-error-link").href,
+      "http://example.com/addon5@tests.mozilla.org",
+      "Error link should be correct"
+    );
+    is_element_hidden(
+      get("detail-pending"),
+      "Pending message should be hidden"
+    );
+
+    run_next_test();
+  });
+});
+
+// These tests are only appropriate when signing can be turned off
+if (!AppConstants.MOZ_REQUIRE_SIGNING) {
+  // Opens and tests the details view for add-on 9
+  add_test(function() {
+    open_details("addon9@tests.mozilla.org", "extension", function() {
+      is(
+        get("detail-name").textContent,
+        "Test add-on 9",
+        "Name should be correct"
+      );
+
+      is_element_hidden(
+        get("detail-prefs-btn"),
+        "Preferences button should be hidden"
+      );
+      is_element_hidden(
+        get("detail-enable-btn"),
+        "Enable button should be hidden"
+      );
+      is_element_visible(
+        get("detail-disable-btn"),
+        "Disable button should be visible"
+      );
+      is_element_visible(
+        get("detail-uninstall-btn"),
+        "Remove button should be visible"
+      );
+
+      is_element_hidden(get("detail-error"), "Error message should be hidden");
+      is_element_hidden(
+        get("detail-error-link"),
+        "Error link should be hidden"
+      );
+      is_element_visible(
+        get("detail-warning"),
+        "Error message should be visible"
+      );
+      is(
+        get("detail-warning").textContent,
+        "Test add-on 9 could not be verified for use in " +
+          gApp +
+          ". Proceed with caution.",
+        "Warning message should be correct"
+      );
+      is_element_visible(
+        get("detail-warning-link"),
+        "Warning link should be visible"
+      );
+      is(
+        get("detail-warning-link").value,
+        "More Information",
+        "Warning link text should be correct"
+      );
+      is(
+        get("detail-warning-link").href,
+        infoURL,
+        "Warning link should be correct"
+      );
+      is_element_hidden(
+        get("detail-pending"),
+        "Pending message should be hidden"
+      );
+
+      run_next_test();
+    });
+  });
+}
+
+// Opens and tests the details view for add-on 9 with signing required
+add_test(async function() {
+  await close_manager(gManagerWindow);
+  Services.prefs.setBoolPref("xpinstall.signatures.required", true);
+  let aWindow = await open_manager(null);
+  gManagerWindow = aWindow;
+  gCategoryUtilities = new CategoryUtilities(gManagerWindow);
+
+  open_details("addon9@tests.mozilla.org", "extension", async function() {
+    await TestUtils.waitForCondition(
+      () => !BrowserTestUtils.is_hidden(get("detail-error-link"))
+    );
+    is(
+      get("detail-name").textContent,
+      "Test add-on 9",
+      "Name should be correct"
+    );
+
+    is_element_hidden(
+      get("detail-prefs-btn"),
+      "Preferences button should be hidden"
+    );
+    is_element_hidden(
+      get("detail-enable-btn"),
+      "Enable button should be hidden"
+    );
+    is_element_visible(
+      get("detail-disable-btn"),
+      "Disable button should be visible"
+    );
+    is_element_visible(
+      get("detail-uninstall-btn"),
+      "Remove button should be visible"
+    );
+
+    is_element_hidden(
+      get("detail-warning"),
+      "Warning message should be hidden"
+    );
+    is_element_hidden(
+      get("detail-warning-link"),
+      "Warning link should be hidden"
+    );
+    is_element_visible(get("detail-error"), "Error message should be visible");
+    is(
+      get("detail-error").textContent,
+      "Test add-on 9 could not be verified for use in " +
+        gApp +
+        " and has been disabled.",
+      "Error message should be correct"
+    );
+    is_element_visible(
+      get("detail-error-link"),
+      "Error link should be visible"
+    );
+    is(
+      get("detail-error-link").value,
+      "More Information",
+      "Error link text should be correct"
+    );
+    is(get("detail-error-link").href, infoURL, "Error link should be correct");
+
+    await close_manager(gManagerWindow);
+    Services.prefs.setBoolPref("xpinstall.signatures.required", false);
+    aWindow = await open_manager(null);
+    gManagerWindow = aWindow;
+    gCategoryUtilities = new CategoryUtilities(gManagerWindow);
+
+    run_next_test();
+  });
+});
+
+// These tests are only appropriate when signing can be turned off
+if (!AppConstants.REQUIRE_SIGNING) {
+  // Opens and tests the details view for add-on 10
+  add_test(function() {
+    open_details("addon10@tests.mozilla.org", "extension", function() {
+      is(
+        get("detail-name").textContent,
+        "Test add-on 10",
+        "Name should be correct"
+      );
+
+      is_element_hidden(
+        get("detail-prefs-btn"),
+        "Preferences button should be hidden"
+      );
+      is_element_hidden(
+        get("detail-enable-btn"),
+        "Enable button should be hidden"
+      );
+      is_element_hidden(
+        get("detail-disable-btn"),
+        "Disable button should be hidden"
+      );
+      is_element_visible(
+        get("detail-uninstall-btn"),
+        "Remove button should be visible"
+      );
+
+      is_element_visible(
+        get("detail-warning"),
+        "Warning message should be visible"
+      );
+      is(
+        get("detail-warning").textContent,
+        "Test add-on 10 is incompatible with " + gApp + " " + gVersion + ".",
+        "Warning message should be correct"
+      );
+      is_element_hidden(
+        get("detail-warning-link"),
+        "Warning link should be hidden"
+      );
+      is_element_hidden(get("detail-error"), "Error message should be hidden");
+      is_element_hidden(
+        get("detail-error-link"),
+        "Error link should be hidden"
+      );
+      is_element_hidden(
+        get("detail-pending"),
+        "Pending message should be hidden"
+      );
+
+      run_next_test();
+    });
+  });
+}
+
+// Opens and tests the details view for add-on 10 with signing required
+add_test(async function() {
+  await close_manager(gManagerWindow);
+  Services.prefs.setBoolPref("xpinstall.signatures.required", true);
+  let aWindow = await open_manager(null);
+  gManagerWindow = aWindow;
+  gCategoryUtilities = new CategoryUtilities(gManagerWindow);
+
+  open_details("addon10@tests.mozilla.org", "extension", async function() {
+    await TestUtils.waitForCondition(
+      () => !BrowserTestUtils.is_hidden(get("detail-error-link"))
+    );
+    is(
+      get("detail-name").textContent,
+      "Test add-on 10",
+      "Name should be correct"
+    );
+
+    is_element_hidden(
+      get("detail-prefs-btn"),
+      "Preferences button should be hidden"
+    );
+    is_element_hidden(
+      get("detail-enable-btn"),
+      "Enable button should be hidden"
+    );
+    is_element_hidden(
+      get("detail-disable-btn"),
+      "Disable button should be hidden"
+    );
+    is_element_visible(
+      get("detail-uninstall-btn"),
+      "Remove button should be visible"
+    );
+
+    is_element_hidden(
+      get("detail-warning"),
+      "Warning message should be hidden"
+    );
+    is_element_hidden(
+      get("detail-warning-link"),
+      "Warning link should be hidden"
+    );
+    is_element_visible(get("detail-error"), "Error message should be visible");
+    is(
+      get("detail-error").textContent,
+      "Test add-on 10 could not be verified for use in " +
+        gApp +
+        " and has been disabled.",
+      "Error message should be correct"
+    );
+    is_element_visible(
+      get("detail-error-link"),
+      "Error link should be visible"
+    );
+    is(
+      get("detail-error-link").value,
+      "More Information",
+      "Error link text should be correct"
+    );
+    is(get("detail-error-link").href, infoURL, "Error link should be correct");
+
+    await close_manager(gManagerWindow);
+    Services.prefs.setBoolPref("xpinstall.signatures.required", false);
+    aWindow = await open_manager(null);
+    gManagerWindow = aWindow;
+    gCategoryUtilities = new CategoryUtilities(gManagerWindow);
+
+    run_next_test();
+  });
+});
+
+// Opens and tests the details view for add-on 11
+add_test(function() {
+  open_details("addon11@tests.mozilla.org", "extension", function() {
+    is(
+      get("detail-name").textContent,
+      "Test add-on 11",
+      "Name should be correct"
+    );
+
+    is_element_hidden(
+      get("detail-prefs-btn"),
+      "Preferences button should be hidden"
+    );
+    is_element_hidden(
+      get("detail-enable-btn"),
+      "Enable button should be hidden"
+    );
+    is_element_hidden(
+      get("detail-disable-btn"),
+      "Disable button should be hidden"
+    );
+    is_element_visible(
+      get("detail-uninstall-btn"),
+      "Remove button should be visible"
+    );
+
+    is_element_visible(
+      get("detail-warning"),
+      "Warning message should be visible"
+    );
+    is(
+      get("detail-warning").textContent,
+      "Test add-on 11 is incompatible with " + gApp + " " + gVersion + ".",
+      "Warning message should be correct"
+    );
+    is_element_hidden(
+      get("detail-warning-link"),
+      "Warning link should be hidden"
+    );
+    is_element_hidden(get("detail-error"), "Error message should be hidden");
+    is_element_hidden(get("detail-error-link"), "Error link should be hidden");
+    is_element_hidden(
+      get("detail-pending"),
+      "Pending message should be hidden"
+    );
+
+    run_next_test();
+  });
+});
+
+// Opens and tests the details view for add-on 11 with signing required
+add_test(async function() {
+  await close_manager(gManagerWindow);
+  Services.prefs.setBoolPref("xpinstall.signatures.required", true);
+  let aWindow = await open_manager(null);
+  gManagerWindow = aWindow;
+  gCategoryUtilities = new CategoryUtilities(gManagerWindow);
+
+  open_details("addon11@tests.mozilla.org", "extension", async function() {
+    is(
+      get("detail-name").textContent,
+      "Test add-on 11",
+      "Name should be correct"
+    );
+
+    is_element_hidden(
+      get("detail-prefs-btn"),
+      "Preferences button should be hidden"
+    );
+    is_element_hidden(
+      get("detail-enable-btn"),
+      "Enable button should be hidden"
+    );
+    is_element_hidden(
+      get("detail-disable-btn"),
+      "Disable button should be hidden"
+    );
+    is_element_visible(
+      get("detail-uninstall-btn"),
+      "Remove button should be visible"
+    );
+
+    is_element_visible(
+      get("detail-warning"),
+      "Warning message should be visible"
+    );
+    is(
+      get("detail-warning").textContent,
+      "Test add-on 11 is incompatible with " + gApp + " " + gVersion + ".",
+      "Warning message should be correct"
+    );
+    is_element_hidden(
+      get("detail-warning-link"),
+      "Warning link should be hidden"
+    );
+    is_element_hidden(get("detail-error"), "Error message should be hidden");
+    is_element_hidden(get("detail-error-link"), "Error link should be hidden");
+
+    await close_manager(gManagerWindow);
+    Services.prefs.setBoolPref("xpinstall.signatures.required", false);
+    aWindow = await open_manager(null);
+    gManagerWindow = aWindow;
+    gCategoryUtilities = new CategoryUtilities(gManagerWindow);
+
+    run_next_test();
+  });
+});
+
+// Opens and tests the details view for add-on 12
+add_test(function() {
+  open_details("addon12@tests.mozilla.org", "extension", function() {
+    is(
+      get("detail-name").textContent,
+      "Test add-on 12",
+      "Name should be correct"
+    );
+
+    is_element_hidden(
+      get("detail-prefs-btn"),
+      "Preferences button should be hidden"
+    );
+    is_element_hidden(
+      get("detail-enable-btn"),
+      "Enable button should be hidden"
+    );
+    is_element_visible(
+      get("detail-disable-btn"),
+      "Disable button should be visible"
+    );
+    is_element_visible(
+      get("detail-uninstall-btn"),
+      "Remove button should be visible"
+    );
+
+    is_element_hidden(
+      get("detail-warning"),
+      "Warning message should be hidden"
+    );
+    is_element_hidden(
+      get("detail-warning-link"),
+      "Warning link should be hidden"
+    );
+    is_element_hidden(get("detail-error"), "Error message should be hidden");
+    is_element_hidden(get("detail-error-link"), "Error link should be hidden");
+    is_element_hidden(
+      get("detail-pending"),
+      "Pending message should be hidden"
+    );
+
+    // Ensure that for a visible privileged addon (which is still going to be allowed
+    // on PB windows automatically on every extension startup) the private browsing
+    // row is hidden (See Bug 1533150 for a rationale).
+    is_element_hidden(
+      get("detail-privateBrowsing-row"),
+      "Private browsing should be hidden"
+    );
+    is_element_hidden(
+      get("detail-privateBrowsing-row-footer"),
+      "Private browsing footer should be hidden"
+    );
+
+    run_next_test();
+  });
+});
+
+// Opens and tests the details view for add-on 12 with signing required
+add_test(async function() {
+  await close_manager(gManagerWindow);
+  Services.prefs.setBoolPref("xpinstall.signatures.required", true);
+  let aWindow = await open_manager(null);
+  gManagerWindow = aWindow;
+  gCategoryUtilities = new CategoryUtilities(gManagerWindow);
+
+  open_details("addon12@tests.mozilla.org", "extension", async function() {
+    is(
+      get("detail-name").textContent,
+      "Test add-on 12",
+      "Name should be correct"
+    );
+
+    is_element_hidden(
+      get("detail-prefs-btn"),
+      "Preferences button should be hidden"
+    );
+    is_element_hidden(
+      get("detail-enable-btn"),
+      "Enable button should be hidden"
+    );
+    is_element_visible(
+      get("detail-disable-btn"),
+      "Disable button should be visible"
+    );
+    is_element_visible(
+      get("detail-uninstall-btn"),
+      "Remove button should be visible"
+    );
+
+    is_element_hidden(
+      get("detail-warning"),
+      "Warning message should be hidden"
+    );
+    is_element_hidden(
+      get("detail-warning-link"),
+      "Warning link should be hidden"
+    );
+    is_element_hidden(get("detail-error"), "Error message should be hidden");
+    is_element_hidden(get("detail-error-link"), "Error link should be hidden");
+
+    await close_manager(gManagerWindow);
+    Services.prefs.setBoolPref("xpinstall.signatures.required", false);
+    aWindow = await open_manager(null);
+    gManagerWindow = aWindow;
+    gCategoryUtilities = new CategoryUtilities(gManagerWindow);
+
+    run_next_test();
+  });
+});
+
+// Tests that upgrades with onExternalInstall apply immediately
+add_test(function() {
+  open_details("addon1@tests.mozilla.org", "extension", function() {
+    gProvider.createAddons([
+      {
+        id: "addon1@tests.mozilla.org",
+        name: "Test add-on replacement",
+        version: "2.5",
+        description: "Short description replacement",
+        fullDescription: "Longer description replacement",
+        type: "extension",
+        iconURL: "chrome://foo/skin/icon.png",
+        sourceURI: Services.io.newURI("http://example.com/foo"),
+        averageRating: 2,
+        optionsURL: "chrome://foo/content/options.xul",
+        applyBackgroundUpdates: AddonManager.AUTOUPDATE_ENABLE,
+        operationsRequiringRestart: AddonManager.OP_NEEDS_RESTART_NONE,
+      },
+    ]);
+
+    is(
+      get("detail-name").textContent,
+      "Test add-on replacement",
+      "Name should be correct"
+    );
+    is_element_visible(get("detail-version"), "Version should not be hidden");
+    is(get("detail-version").value, "2.5", "Version should be correct");
+    is(
+      get("detail-icon").src,
+      "chrome://foo/skin/icon.png",
+      "Icon should be correct"
+    );
+    is_element_hidden(get("detail-creator"), "Creator should be hidden");
+    is(
+      get("detail-desc").textContent,
+      "Short description replacement",
+      "Description should be correct"
+    );
+    is(
+      get("detail-fulldesc").textContent,
+      "Longer description replacement",
+      "Full description should be correct"
+    );
+
+    is_element_hidden(
+      get("detail-contributions"),
+      "Contributions section should be hidden"
+    );
+
+    is_element_hidden(
+      get("detail-dateUpdated"),
+      "Update date should be hidden"
+    );
+
+    is_element_visible(
+      get("detail-rating-row"),
+      "Rating row should not be hidden"
+    );
+    is_element_visible(get("detail-rating"), "Rating should not be hidden");
+    is(get("detail-rating").averageRating, 2, "Rating should be correct");
+    is_element_hidden(get("detail-reviews"), "Reviews should be hidden");
+
+    is_element_hidden(get("detail-homepage-row"), "Homepage should be hidden");
+
+    is_element_hidden(
+      get("detail-prefs-btn"),
+      "Preferences button should be hidden"
+    );
+    is_element_hidden(
+      get("detail-enable-btn"),
+      "Enable button should be hidden"
+    );
+    is_element_visible(
+      get("detail-disable-btn"),
+      "Disable button should be visible"
+    );
+    is_element_visible(
+      get("detail-uninstall-btn"),
+      "Remove button should be visible"
+    );
+
+    is_element_hidden(
+      get("detail-warning"),
+      "Warning message should be hidden"
+    );
+    is_element_hidden(
+      get("detail-warning-link"),
+      "Warning link should be hidden"
+    );
+    is_element_hidden(get("detail-error"), "Error message should be hidden");
+    is_element_hidden(
+      get("detail-pending"),
+      "Pending message should be hidden"
+    );
+
+    run_next_test();
+  });
+});
+
+// Check that onPropertyChanges for appDisabled updates the UI
+add_test(async function() {
+  info("Checking that onPropertyChanges for appDisabled updates the UI");
+
+  let aAddon = await AddonManager.getAddonByID("addon1@tests.mozilla.org");
+  await aAddon.disable();
+  aAddon.isCompatible = true;
+  aAddon.appDisabled = false;
+
+  open_details("addon1@tests.mozilla.org", "extension", function() {
+    is(
+      get("detail-view").getAttribute("active"),
+      "false",
+      "Addon should not be marked as active"
+    );
+    is_element_hidden(
+      get("detail-warning"),
+      "Warning message should not be visible"
+    );
+
+    info("Making addon incompatible and appDisabled");
+    aAddon.isCompatible = false;
+    aAddon.appDisabled = true;
+
+    is(
+      get("detail-view").getAttribute("active"),
+      "false",
+      "Addon should not be marked as active"
+    );
+    is_element_visible(
+      get("detail-warning"),
+      "Warning message should be visible"
+    );
+    is(
+      get("detail-warning").textContent,
+      "Test add-on replacement is incompatible with " +
+        gApp +
+        " " +
+        gVersion +
+        ".",
+      "Warning message should be correct"
+    );
+
+    run_next_test();
+  });
+});
--- a/toolkit/mozapps/extensions/test/browser/browser_discovery.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_discovery.js
@@ -7,18 +7,22 @@
 const MAIN_URL = "https://example.com/" + RELATIVE_DIR + "discovery.html";
 
 var gManagerWindow;
 var gCategoryUtilities;
 var gProvider;
 
 var gLoadCompleteCallback = null;
 
+// This test file is testing the old XUL disco pane.
 SpecialPowers.pushPrefEnv({
-  set: [["extensions.htmlaboutaddons.discover.enabled", false]],
+  set: [
+    ["extensions.htmlaboutaddons.enabled", false],
+    ["extensions.htmlaboutaddons.discover.enabled", false],
+  ],
 });
 
 var gProgressListener = {
   onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
     // Only care about the network stop status events
     if (
       !(aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) ||
       !(aStateFlags & Ci.nsIWebProgressListener.STATE_STOP)
@@ -644,16 +648,32 @@ async function bug_601442_test_elements(
       "Discover category should be visible"
     );
   } else {
     ok(
       !gCategoryUtilities.isTypeVisible("discover"),
       "Discover category should not be visible"
     );
   }
+
+  gManagerWindow.loadView("addons://list/dictionary");
+  let aManager = await wait_for_view_load(gManagerWindow);
+  var button = aManager.document.getElementById("discover-button-install");
+  if (visible) {
+    ok(
+      !BrowserTestUtils.is_hidden(button),
+      "Discover button should be visible!"
+    );
+  } else {
+    ok(
+      BrowserTestUtils.is_hidden(button),
+      "Discover button should not be visible!"
+    );
+  }
+
   close_manager(gManagerWindow, run_next_test);
 }
 
 add_test(function() {
   Services.prefs.setBoolPref(PREF_DISCOVER_ENABLED, false);
   Services.prefs.setBoolPref(PREF_XPI_ENABLED, true);
   bug_601442_test_elements(false);
 });
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/browser/browser_discovery_clientid.js
@@ -0,0 +1,77 @@
+"use strict";
+
+const { ClientID } = ChromeUtils.import("resource://gre/modules/ClientID.jsm");
+
+const MAIN_URL = "https://example.com/" + RELATIVE_DIR + "discovery.html";
+
+// This test is testing XUL about:addons UI (the HTML about:addons is tested in
+// browser_html_discover_view_clientid.js).
+SpecialPowers.pushPrefEnv({
+  set: [
+    ["extensions.htmlaboutaddons.discover.enabled", false],
+    ["extensions.htmlaboutaddons.enabled", false],
+  ],
+});
+
+function waitForHeader() {
+  return new Promise(resolve => {
+    let observer = (subject, topic, state) => {
+      let channel = subject.QueryInterface(Ci.nsIHttpChannel);
+      if (channel.URI.spec != MAIN_URL) {
+        return;
+      }
+      try {
+        resolve(channel.getRequestHeader("Moz-Client-Id"));
+      } catch (e) {
+        if (e.result == Cr.NS_ERROR_NOT_AVAILABLE) {
+          // The header was not set.
+          resolve(null);
+        }
+      } finally {
+        Services.obs.removeObserver(observer, "http-on-modify-request");
+      }
+    };
+    Services.obs.addObserver(observer, "http-on-modify-request");
+  });
+}
+
+add_task(async function setup() {
+  SpecialPowers.pushPrefEnv({
+    set: [
+      [PREF_DISCOVERURL, MAIN_URL],
+      ["datareporting.healthreport.uploadEnabled", true],
+      ["browser.discovery.enabled", true],
+    ],
+  });
+});
+
+add_task(async function test_no_private_clientid() {
+  let privateWindow = await BrowserTestUtils.openNewBrowserWindow({
+    private: true,
+  });
+  let [header, manager] = await Promise.all([
+    waitForHeader(),
+    open_manager(
+      "addons://discover/",
+      undefined,
+      undefined,
+      undefined,
+      privateWindow
+    ),
+  ]);
+  ok(PrivateBrowsingUtils.isContentWindowPrivate(manager), "window is private");
+  is(header, null, "header was not set");
+  await close_manager(manager);
+  await BrowserTestUtils.closeWindow(privateWindow);
+});
+
+add_task(async function test_clientid() {
+  let clientId = await ClientID.getClientIdHash();
+  ok(!!clientId, "clientId is avialable");
+  let [header, manager] = await Promise.all([
+    waitForHeader(),
+    open_manager("addons://discover/"),
+  ]);
+  is(header, clientId, "header was set");
+  await close_manager(manager);
+});
--- a/toolkit/mozapps/extensions/test/browser/browser_dragdrop.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_dragdrop.js
@@ -70,17 +70,20 @@ async function checkInstallConfirmation(
     notificationCount,
     names.length,
     `Saw ${names.length} addon-install-started notification`
   );
   Services.obs.removeObserver(observer, "addon-install-started");
 }
 
 function getViewContainer(gManagerWindow) {
-  return gManagerWindow.document.getElementById("category-box");
+  if (gManagerWindow.useHtmlViews) {
+    return gManagerWindow.document.getElementById("category-box");
+  }
+  return gManagerWindow.document.getElementById("view-port");
 }
 
 // Simulates dropping a URL onto the manager
 add_task(async function test_drop_url() {
   let url = TESTROOT + "addons/browser_dragdrop1.xpi";
   gManagerWindow = await open_manager("addons://list/extension");
   let promise = checkInstallConfirmation("Drag Drop test 1");
   let viewContainer = getViewContainer(gManagerWindow);
--- a/toolkit/mozapps/extensions/test/browser/browser_extension_sideloading_permission.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_extension_sideloading_permission.js
@@ -9,44 +9,84 @@ const { AddonTestUtils } = ChromeUtils.i
   "resource://testing-common/AddonTestUtils.jsm"
 );
 const ADDON_ID = "addon1@test.mozilla.org";
 
 AddonTestUtils.initMochitest(this);
 
 function assertDisabledSideloadedExtensionElement(managerWindow, addonElement) {
   const doc = addonElement.ownerDocument;
-  const toggleDisabled = addonElement.querySelector(
-    '[action="toggle-disabled"]'
-  );
-  is(
-    doc.l10n.getAttributes(toggleDisabled).id,
-    "enable-addon-button",
-    "Addon toggle-disabled action has the enable label"
-  );
+  if (managerWindow.useHtmlViews) {
+    const toggleDisabled = addonElement.querySelector(
+      '[action="toggle-disabled"]'
+    );
+    is(
+      doc.l10n.getAttributes(toggleDisabled).id,
+      "enable-addon-button",
+      "Addon toggle-disabled action has the enable label"
+    );
+  } else {
+    let el = doc.getAnonymousElementByAttribute(
+      addonElement,
+      "anonid",
+      "disable-btn"
+    );
+    is_element_hidden(el, "Disable button not visible.");
+    el = doc.getAnonymousElementByAttribute(
+      addonElement,
+      "anonid",
+      "enable-btn"
+    );
+    is_element_visible(el, "Enable button visible");
+  }
 }
 
 function assertEnabledSideloadedExtensionElement(managerWindow, addonElement) {
   const doc = addonElement.ownerDocument;
-  const toggleDisabled = addonElement.querySelector(
-    '[action="toggle-disabled"]'
-  );
-  is(
-    doc.l10n.getAttributes(toggleDisabled).id,
-    "enable-addon-button",
-    "Addon toggle-disabled action has the enable label"
-  );
+  if (managerWindow.useHtmlViews) {
+    const toggleDisabled = addonElement.querySelector(
+      '[action="toggle-disabled"]'
+    );
+    is(
+      doc.l10n.getAttributes(toggleDisabled).id,
+      "enable-addon-button",
+      "Addon toggle-disabled action has the enable label"
+    );
+  } else {
+    let el = doc.getAnonymousElementByAttribute(
+      addonElement,
+      "anonid",
+      "disable-btn"
+    );
+    is_element_hidden(el, "Disable button not visible.");
+    el = doc.getAnonymousElementByAttribute(
+      addonElement,
+      "anonid",
+      "enable-btn"
+    );
+    is_element_visible(el, "Enable button visible");
+  }
 }
 
 function clickEnableExtension(managerWindow, addonElement) {
-  addonElement.querySelector('[action="toggle-disabled"]').click();
+  if (managerWindow.useHtmlViews) {
+    addonElement.querySelector('[action="toggle-disabled"]').click();
+  } else {
+    const doc = addonElement.ownerDocument;
+    const el = doc.getAnonymousElementByAttribute(
+      addonElement,
+      "anonid",
+      "enable-btn"
+    );
+    EventUtils.synthesizeMouseAtCenter(el, { clickCount: 1 }, managerWindow);
+  }
 }
 
 // Loading extension by sideloading method
-add_task(async function test_sideloaded_extension_permissions_prompt() {
+async function test_sideloaded_extension_permissions_prompt() {
   await SpecialPowers.pushPrefEnv({
     set: [
       ["xpinstall.signatures.required", false],
       ["extensions.autoDisableScopes", 15],
       ["extensions.ui.ignoreUnsigned", true],
     ],
   });
 
@@ -119,9 +159,25 @@ add_task(async function test_sideloaded_
 
   addon = await AddonManager.getAddonByID(ADDON_ID);
   ok(addon.seen, "Seen flag should be true after permissions are accepted");
 
   ok(!PopupNotifications.isPanelOpen, "Permission popup should not be visible");
 
   await close_manager(manager);
   await addon.uninstall();
+}
+
+add_task(async function test_XUL_aboutaddons_sideloaded_permissions_prompt() {
+  await SpecialPowers.pushPrefEnv({
+    set: [["extensions.htmlaboutaddons.enabled", false]],
+  });
+  await test_sideloaded_extension_permissions_prompt();
+  await SpecialPowers.popPrefEnv();
 });
+
+add_task(async function test_HTML_aboutaddons_sideloaded_permissions_prompt() {
+  await SpecialPowers.pushPrefEnv({
+    set: [["extensions.htmlaboutaddons.enabled", true]],
+  });
+  await test_sideloaded_extension_permissions_prompt();
+  await SpecialPowers.popPrefEnv();
+});
--- a/toolkit/mozapps/extensions/test/browser/browser_globalwarnings.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_globalwarnings.js
@@ -2,44 +2,45 @@
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 // Bug 566194 - safe mode / security & compatibility check status are not exposed in new addon manager UI
 
 async function loadDetail(aWindow, id) {
   let loaded = wait_for_view_load(aWindow, undefined, true);
   // Check the detail view.
-  let browser = await aWindow.getHtmlBrowser();
-  let card = browser.contentDocument.querySelector(
-    `addon-card[addon-id="${id}"]`
-  );
-  EventUtils.synthesizeMouseAtCenter(card, {}, browser.contentWindow);
+  if (aWindow.useHtmlViews) {
+    let browser = await aWindow.getHtmlBrowser();
+    let card = browser.contentDocument.querySelector(
+      `addon-card[addon-id="${id}"]`
+    );
+    EventUtils.synthesizeMouseAtCenter(card, {}, browser.contentWindow);
+  } else {
+    let card = aWindow.document.querySelector(`.addon.card[value="${id}"]`);
+    EventUtils.synthesizeMouseAtCenter(card, {}, aWindow);
+  }
   await loaded;
 }
 
-add_task(async function checkCompatibility() {
+async function checkCompatibility(hboxSelector, buttonSelector) {
   info("Testing compatibility checking warning");
 
   info("Setting checkCompatibility to false");
   AddonManager.checkCompatibility = false;
 
   let id = "test@mochi.test";
   let extension = ExtensionTestUtils.loadExtension({
     manifest: { applications: { gecko: { id } } },
     useAddonManager: "temporary",
   });
   await extension.startup();
 
   let aWindow = await open_manager("addons://list/extension");
-  let hbox = aWindow.document.querySelector(
-    "#html-view .global-warning-checkcompatibility"
-  );
-  let button = aWindow.document.querySelector(
-    "#html-view .global-warning-checkcompatibility button"
-  );
+  let hbox = aWindow.document.querySelector(hboxSelector);
+  let button = aWindow.document.querySelector(buttonSelector);
 
   function checkMessage(visible) {
     if (visible) {
       is_element_visible(
         hbox,
         "Check Compatibility warning hbox should be visible"
       );
       is_element_visible(
@@ -80,39 +81,35 @@ add_task(async function checkCompatibili
     AddonManager.checkCompatibility,
     true,
     "Check Compatibility pref should be cleared"
   );
   checkMessage(false);
 
   await close_manager(aWindow);
   await extension.unload();
-});
+}
 
-add_task(async function checkSecurity() {
+async function checkSecurity(hboxSelector, buttonSelector) {
   info("Testing update security checking warning");
 
   var pref = "extensions.checkUpdateSecurity";
   info("Setting " + pref + " pref to false");
   Services.prefs.setBoolPref(pref, false);
 
   let id = "test-security@mochi.test";
   let extension = ExtensionTestUtils.loadExtension({
     manifest: { applications: { gecko: { id } } },
     useAddonManager: "temporary",
   });
   await extension.startup();
 
   let aWindow = await open_manager("addons://list/extension");
-  let hbox = aWindow.document.querySelector(
-    "#html-view .global-warning-updatesecurity"
-  );
-  let button = aWindow.document.querySelector(
-    "#html-view .global-warning-updatesecurity button"
-  );
+  let hbox = aWindow.document.querySelector(hboxSelector);
+  let button = aWindow.document.querySelector(buttonSelector);
 
   function checkMessage(visible) {