Bug 1246030 - Allow reloading an add-on in about:debugging. r=ochameau
☠☠ backed out by 5a88d4e4b4e0 ☠ ☠
authorKumar McMillan <kumar.mcmillan@gmail.com>
Thu, 31 Mar 2016 12:01:02 -0500
changeset 331293 159d0cb3c2c360ec38ff965a50a67fc2b5ab736b
parent 331292 55f774304456fba311e048824adf6df207c5d760
child 331294 1a22155b5544b1690270b7a3f9e93e495ed4cdd8
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersochameau
bugs1246030
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1246030 - Allow reloading an add-on in about:debugging. r=ochameau MozReview-Commit-ID: Lh2iwPgmlhU
devtools/client/aboutdebugging/components/addon-target.js
devtools/client/aboutdebugging/components/addons-tab.js
devtools/client/aboutdebugging/test/browser.ini
devtools/client/aboutdebugging/test/browser_addons_debug_bootstrapped.js
devtools/client/aboutdebugging/test/browser_addons_debugging_initial_state.js
devtools/client/aboutdebugging/test/browser_addons_install.js
devtools/client/aboutdebugging/test/browser_addons_reload.js
devtools/client/aboutdebugging/test/browser_addons_toggle_debug.js
devtools/client/aboutdebugging/test/head.js
devtools/client/locales/en-US/aboutdebugging.properties
devtools/server/actors/addon.js
--- a/devtools/client/aboutdebugging/components/addon-target.js
+++ b/devtools/client/aboutdebugging/components/addon-target.js
@@ -20,28 +20,45 @@ const Strings = Services.strings.createB
 module.exports = createClass({
   displayName: "AddonTarget",
 
   debug() {
     let { target } = this.props;
     BrowserToolboxProcess.init({ addonID: target.addonID });
   },
 
+  reload() {
+    let { client, target } = this.props;
+    // This function sometimes returns a partial promise that only
+    // implements then().
+    client.request({
+      to: target.addonActor,
+      type: "reload"
+    }).then(() => {}, error => {
+      throw new Error(
+        "Error reloading addon " + target.addonID + ": " + error);
+    });
+  },
+
   render() {
     let { target, debugDisabled } = this.props;
 
     return dom.div({ className: "target-container" },
       dom.img({
         className: "target-icon",
         role: "presentation",
         src: target.icon
       }),
       dom.div({ className: "target" },
         dom.div({ className: "target-name" }, target.name)
       ),
       dom.button({
         className: "debug-button",
         onClick: this.debug,
         disabled: debugDisabled,
-      }, Strings.GetStringFromName("debug"))
+      }, Strings.GetStringFromName("debug")),
+      dom.button({
+        className: "reload-button",
+        onClick: this.reload
+      }, Strings.GetStringFromName("reload"))
     );
   }
 });
--- a/devtools/client/aboutdebugging/components/addons-tab.js
+++ b/devtools/client/aboutdebugging/components/addons-tab.js
@@ -55,27 +55,31 @@ module.exports = createClass({
     let debugDisabled =
       !Services.prefs.getBoolPref(CHROME_ENABLED_PREF) ||
       !Services.prefs.getBoolPref(REMOTE_ENABLED_PREF);
 
     this.setState({ debugDisabled });
   },
 
   updateAddonsList() {
-    AddonManager.getAllAddons(addons => {
-      let extensions = addons.filter(addon => addon.isDebuggable).map(addon => {
-        return {
-          name: addon.name,
-          icon: addon.iconURL || ExtensionIcon,
-          addonID: addon.id
-        };
+    this.props.client.listAddons()
+      .then(({addons}) => {
+        let extensions = addons.filter(addon => addon.debuggable).map(addon => {
+          return {
+            name: addon.name,
+            icon: addon.iconURL || ExtensionIcon,
+            addonID: addon.id,
+            addonActor: addon.actor
+          };
+        });
+
+        this.setState({ extensions });
+      }, error => {
+        throw new Error("Client error while listing addons: " + error);
       });
-
-      this.setState({ extensions });
-    });
   },
 
   /**
    * Mandatory callback as AddonManager listener.
    */
   onInstalled() {
     this.updateAddonsList();
   },
--- a/devtools/client/aboutdebugging/test/browser.ini
+++ b/devtools/client/aboutdebugging/test/browser.ini
@@ -9,15 +9,16 @@ support-files =
   service-workers/empty-sw.html
   service-workers/empty-sw.js
   service-workers/push-sw.html
   service-workers/push-sw.js
 
 [browser_addons_debug_bootstrapped.js]
 [browser_addons_debugging_initial_state.js]
 [browser_addons_install.js]
+[browser_addons_reload.js]
 [browser_addons_toggle_debug.js]
 [browser_service_workers.js]
 [browser_service_workers_push.js]
 [browser_service_workers_start.js]
 [browser_service_workers_timeout.js]
 skip-if = true # Bug 1232931
 [browser_service_workers_unregister.js]
--- a/devtools/client/aboutdebugging/test/browser_addons_debug_bootstrapped.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_bootstrapped.js
@@ -17,16 +17,17 @@ add_task(function* () {
       ["devtools.debugger.prompt-connection", false],
       // Enable Browser toolbox test script execution via env variable
       ["devtools.browser-toolbox.allow-unsafe-script", true],
     ]};
     SpecialPowers.pushPrefEnv(options, resolve);
   });
 
   let { tab, document } = yield openAboutDebugging("addons");
+  yield waitForInitialAddonList(document);
   yield installAddon(document, "addons/unpacked/install.rdf", ADDON_NAME,
                      "test-devtools");
 
   // Retrieve the DEBUG button for the addon
   let names = [...document.querySelectorAll("#addons .target-name")];
   let name = names.filter(element => element.textContent === ADDON_NAME)[0];
   ok(name, "Found the addon in the list");
   let targetElement = name.parentNode.parentNode;
--- a/devtools/client/aboutdebugging/test/browser_addons_debugging_initial_state.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_debugging_initial_state.js
@@ -42,16 +42,17 @@ function* testCheckboxState(testData) {
     let options = {"set": [
       ["devtools.chrome.enabled", testData.chromeEnabled],
       ["devtools.debugger.remote-enabled", testData.debuggerRemoteEnable],
     ]};
     SpecialPowers.pushPrefEnv(options, resolve);
   });
 
   let { tab, document } = yield openAboutDebugging("addons");
+  yield waitForInitialAddonList(document);
 
   info("Install a test addon.");
   yield installAddon(document, "addons/unpacked/install.rdf", ADDON_NAME,
                      "test-devtools");
 
   info("Test checkbox checked state.");
   let addonDebugCheckbox = document.querySelector("#enable-addon-debugging");
   is(addonDebugCheckbox.checked, testData.expected,
--- a/devtools/client/aboutdebugging/test/browser_addons_install.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_install.js
@@ -2,29 +2,31 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
 const ADDON_ID = "test-devtools@mozilla.org";
 const ADDON_NAME = "test-devtools";
 
 add_task(function* () {
   let { tab, document } = yield openAboutDebugging("addons");
+  yield waitForInitialAddonList(document);
 
   // Install this add-on, and verify that it appears in the about:debugging UI
   yield installAddon(document, "addons/unpacked/install.rdf", ADDON_NAME,
                      "test-devtools");
 
   // Install the add-on, and verify that it disappears in the about:debugging UI
   yield uninstallAddon(document, ADDON_ID, ADDON_NAME);
 
   yield closeAboutDebugging(tab);
 });
 
 add_task(function* () {
   let { tab, document } = yield openAboutDebugging("addons");
+  yield waitForInitialAddonList(document);
 
   // Start an observer that looks for the install error before
   // actually doing the install
   let top = document.querySelector(".addons-top");
   let promise = waitForMutation(top, { childList: true });
 
   // Mock the file picker to select a test addon
   let MockFilePicker = SpecialPowers.MockFilePicker;
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/browser_addons_reload.js
@@ -0,0 +1,65 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const ADDON_ID = "test-devtools@mozilla.org";
+const ADDON_NAME = "test-devtools";
+
+/**
+ * Returns a promise that resolves when the given add-on event is fired. The
+ * resolved value is an array of arguments passed for the event.
+ */
+function promiseAddonEvent(event) {
+  return new Promise(resolve => {
+    let listener = {
+      [event]: function(...args) {
+        AddonManager.removeAddonListener(listener);
+        resolve(args);
+      }
+    };
+
+    AddonManager.addAddonListener(listener);
+  });
+}
+
+add_task(function* () {
+  const { tab, document } = yield openAboutDebugging("addons");
+  yield waitForInitialAddonList(document);
+  yield installAddon(document, "addons/unpacked/install.rdf",
+                     ADDON_NAME, ADDON_NAME);
+
+  // Retrieve the Reload button.
+  const names = [...document.querySelectorAll("#addons .target-name")];
+  const name = names.filter(element => element.textContent === ADDON_NAME)[0];
+  ok(name, "Found " + ADDON_NAME + " add-on in the list");
+  const targetElement = name.parentNode.parentNode;
+  const reloadButton = targetElement.querySelector(".reload-button");
+  ok(reloadButton, "Found its reload button");
+
+  const onDisabled = promiseAddonEvent("onDisabled");
+  const onEnabled = promiseAddonEvent("onEnabled");
+
+  const onBootstrapInstallCalled = new Promise(done => {
+    Services.obs.addObserver(function listener() {
+      Services.obs.removeObserver(listener, ADDON_NAME, false);
+      ok(true, "Add-on was installed: " + ADDON_NAME);
+      done();
+    }, ADDON_NAME, false);
+  });
+
+  reloadButton.click();
+
+  const [disabledAddon] = yield onDisabled;
+  ok(disabledAddon.name === ADDON_NAME,
+     "Add-on was disabled: " + disabledAddon.name);
+
+  const [enabledAddon] = yield onEnabled;
+  ok(enabledAddon.name === ADDON_NAME,
+     "Add-on was re-enabled: " + enabledAddon.name);
+
+  yield onBootstrapInstallCalled;
+
+  info("Uninstall addon installed earlier.");
+  yield uninstallAddon(document, ADDON_ID, ADDON_NAME);
+  yield closeAboutDebugging(tab);
+});
--- a/devtools/client/aboutdebugging/test/browser_addons_toggle_debug.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_toggle_debug.js
@@ -15,16 +15,17 @@ add_task(function* () {
     let options = {"set": [
       ["devtools.chrome.enabled", false],
       ["devtools.debugger.remote-enabled", false],
     ]};
     SpecialPowers.pushPrefEnv(options, resolve);
   });
 
   let { tab, document } = yield openAboutDebugging("addons");
+  yield waitForInitialAddonList(document);
 
   info("Install a test addon.");
   yield installAddon(document, "addons/unpacked/install.rdf", ADDON_NAME,
                      "test-devtools");
 
   let addonDebugCheckbox = document.querySelector("#enable-addon-debugging");
   ok(!addonDebugCheckbox.checked, "Addons debugging should be disabled.");
 
--- a/devtools/client/aboutdebugging/test/head.js
+++ b/devtools/client/aboutdebugging/test/head.js
@@ -1,16 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /* eslint-env browser */
 /* eslint-disable mozilla/no-cpows-in-tests */
 /* exported openAboutDebugging, closeAboutDebugging, installAddon,
    uninstallAddon, waitForMutation, assertHasTarget,
-   waitForServiceWorkerRegistered, unregisterServiceWorker */
+   waitForInitialAddonList, waitForServiceWorkerRegistered,
+   unregisterServiceWorker */
 /* global sendAsyncMessage */
 
 "use strict";
 
 var { utils: Cu, classes: Cc, interfaces: Ci } = Components;
 
 const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
 const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});
@@ -151,16 +152,28 @@ function* uninstallAddon(document, addon
   let names = [...addonList.querySelectorAll(".target-name")];
   names = names.map(element => element.textContent);
   ok(!names.includes(addonName),
     "After uninstall, the addon name disappears from the list of addons: "
     + names);
 }
 
 /**
+ * Returns a promise that will resolve when the add-on list has been updated.
+ *
+ * @param {Node} document
+ * @return {Promise}
+ */
+function waitForInitialAddonList(document) {
+  // Wait for actor to load initial list of add-ons.
+  return waitForMutation(document.querySelector("#addons .targets"),
+                         { childList: true });
+}
+
+/**
  * Returns a promise that will resolve after receiving a mutation matching the
  * provided mutation options on the provided target.
  * @param {Node} target
  * @param {Object} mutationOptions
  * @return {Promise}
  */
 function waitForMutation(target, mutationOptions) {
   return new Promise(resolve => {
--- a/devtools/client/locales/en-US/aboutdebugging.properties
+++ b/devtools/client/locales/en-US/aboutdebugging.properties
@@ -11,16 +11,17 @@ unregister = unregister
 
 addons = Add-ons
 addonDebugging.label = Enable add-on debugging
 addonDebugging.tooltip = Turning this on will allow you to debug add-ons and various other parts of the browser chrome
 addonDebugging.moreInfo = more info
 loadTemporaryAddon = Load Temporary Add-on
 extensions = Extensions
 selectAddonFromFile2 = Select Manifest File or Package (.xpi)
+reload = Reload
 
 workers = Workers
 serviceWorkers = Service Workers
 sharedWorkers = Shared Workers
 otherWorkers = Other Workers
 
 nothing = Nothing yet.
 
--- a/devtools/server/actors/addon.js
+++ b/devtools/server/actors/addon.js
@@ -78,16 +78,17 @@ BrowserAddonActor.prototype = {
       this._contextPool.addActor(this._consoleActor);
     }
 
     return {
       actor: this.actorID,
       id: this.id,
       name: this._addon.name,
       url: this.url,
+      iconURL: this._addon.iconURL,
       debuggable: this._addon.isDebuggable,
       consoleActor: this._consoleActor.actorID,
 
       traits: {
         highlightable: false,
         networkMonitor: false,
       },
     };
@@ -150,16 +151,23 @@ BrowserAddonActor.prototype = {
     this._contextPool.removeActor(this.threadActor);
 
     this.threadActor = null;
     this._sources = null;
 
     return { type: "detached" };
   },
 
+  onReload: function BAA_onReload() {
+    return this._addon.reload()
+      .then(() => {
+        return {}; // send an empty response
+      });
+  },
+
   preNest: function() {
     let e = Services.wm.getEnumerator(null);
     while (e.hasMoreElements()) {
       let win = e.getNext();
       let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
                            .getInterface(Ci.nsIDOMWindowUtils);
       windowUtils.suppressEventHandling(true);
       windowUtils.suspendTimeouts();
@@ -244,17 +252,18 @@ BrowserAddonActor.prototype = {
    */
   _findDebuggees: function (dbg) {
     return dbg.findAllGlobals().filter(this._shouldAddNewGlobalAsDebuggee);
   }
 };
 
 BrowserAddonActor.prototype.requestTypes = {
   "attach": BrowserAddonActor.prototype.onAttach,
-  "detach": BrowserAddonActor.prototype.onDetach
+  "detach": BrowserAddonActor.prototype.onDetach,
+  "reload": BrowserAddonActor.prototype.onReload
 };
 
 /**
  * The AddonConsoleActor implements capabilities needed for the add-on web
  * console feature.
  *
  * @constructor
  * @param object aAddon