Bug 1246030 - Allow reloading an add-on in about:debugging. r=ochameau
MozReview-Commit-ID: Lh2iwPgmlhU
--- 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,40 @@ 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) {
+ const addonListContainer = document.querySelector("#addons .targets");
+ let addonCount = addonListContainer.querySelectorAll(".target");
+ addonCount = addonCount ? [...addonCount].length : -1;
+ info("Waiting for add-ons to load. Current add-on count: " + addonCount);
+
+ // This relies on the network speed of the actor responding to the
+ // listAddons() request and also the speed of openAboutDebugging().
+ let result;
+ if (addonCount > 0) {
+ info("Actually, the add-ons have already loaded");
+ result = Promise.resolve();
+ } else {
+ result = waitForMutation(addonListContainer, { childList: true });
+ }
+ return result;
+}
+
+/**
* 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