Bug 1227135 - about:debugging : disable debug buttons if addons debugging disabled;r=ochameau

When a preference impacting about:debugging changes, the current tab will be rendered
again. Each "target" is responsible for checking if Debugging should be allowed.
If not, the debug button should be disabled. Currently only extensions/addons
can be disabled, depending on the value of the "devtools.chrome.enabled" preference

Adds a mochitest checking this scenario.
This commit is contained in:
Julian Descottes 2016-01-29 00:13:48 +01:00
parent 87341a135e
commit 41595106e9
10 changed files with 163 additions and 75 deletions

View File

@ -33,6 +33,9 @@ const Strings = Services.strings.createBundle(
var AboutDebugging = {
_prefListeners: [],
// Pointer to the current React component.
_component: null,
_categories: null,
get categories() {
// If needed, initialize the list of available categories.
@ -66,11 +69,11 @@ var AboutDebugging = {
location.hash = "#" + category;
if (category == "addons") {
React.render(React.createElement(AddonsComponent, { client: this.client }),
document.querySelector("#addons"));
this._component = React.render(React.createElement(AddonsComponent,
{client: this.client}), document.querySelector("#addons"));
} else if (category == "workers") {
React.render(React.createElement(WorkersComponent, { client: this.client }),
document.querySelector("#workers"));
this._component = React.render(React.createElement(WorkersComponent,
{client: this.client}), document.querySelector("#workers"));
}
},
@ -82,16 +85,22 @@ var AboutDebugging = {
let elements = document.querySelectorAll("input[type=checkbox][data-pref]");
Array.map(elements, element => {
let pref = element.dataset.pref;
let updatePref = () => {
Services.prefs.setBoolPref(pref, element.checked);
};
element.addEventListener("change", updatePref, false);
let updateCheckbox = () => {
let onPreferenceChanged = () => {
element.checked = Services.prefs.getBoolPref(pref);
this.update();
};
Services.prefs.addObserver(pref, updateCheckbox, false);
this._prefListeners.push([pref, updateCheckbox]);
updateCheckbox();
Services.prefs.addObserver(pref, onPreferenceChanged, false);
this._prefListeners.push([pref, onPreferenceChanged]);
// Initialize the current checkbox element.
element.checked = Services.prefs.getBoolPref(pref);
});
// Link buttons to their associated actions.
@ -112,6 +121,12 @@ var AboutDebugging = {
});
},
update() {
if (this._component) {
this._component.setState({});
}
},
loadAddonFromFile() {
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
fp.init(window,

View File

@ -41,8 +41,10 @@ exports.AddonsComponent = React.createClass({
let client = this.props.client;
let targets = this.state.extensions;
let name = Strings.GetStringFromName("extensions");
let debugDisabled = !Services.prefs.getBoolPref("devtools.chrome.enabled");
return React.createElement("div", null,
React.createElement(TargetListComponent, { name, targets, client })
React.createElement(TargetListComponent,
{ name, targets, client, debugDisabled })
);
},

View File

@ -23,8 +23,10 @@ exports.TargetListComponent = React.createClass({
render() {
let client = this.props.client;
let debugDisabled = this.props.debugDisabled;
let targets = this.props.targets.sort(LocaleCompare).map(target => {
return React.createElement(TargetComponent, { client, target });
return React.createElement(TargetComponent,
{ client, target, debugDisabled });
});
return (
React.createElement("div", { id: this.props.id, className: "targets" },

View File

@ -54,6 +54,8 @@ exports.TargetComponent = React.createClass({
render() {
let target = this.props.target;
let debugDisabled = this.props.debugDisabled;
return React.createElement("div", { className: "target" },
React.createElement("img", {
className: "target-icon",
@ -62,8 +64,11 @@ exports.TargetComponent = React.createClass({
React.createElement("div", { className: "target-name" }, target.name),
React.createElement("div", { className: "target-url" }, target.url)
),
React.createElement("button", { onClick: this.debug },
Strings.GetStringFromName("debug"))
React.createElement("button", {
className: "debug-button",
onClick: this.debug,
disabled: debugDisabled,
}, Strings.GetStringFromName("debug"))
);
},
});

View File

@ -9,5 +9,6 @@ support-files =
service-workers/empty-sw.js
[browser_addons_install.js]
[browser_addons_toggle_debug.js]
[browser_service_workers.js]
[browser_service_workers_timeout.js]

View File

@ -2,33 +2,13 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
var {AddonManager} = Cu.import("resource://gre/modules/AddonManager.jsm", {});
const ADDON_ID = "test-devtools@mozilla.org";
const ADDON_NAME = "test-devtools";
add_task(function *() {
let { tab, document } = yield openAboutDebugging("addons");
// Mock the file picker to select a test addon
let MockFilePicker = SpecialPowers.MockFilePicker;
MockFilePicker.init(null);
let file = get_supports_file("addons/unpacked/install.rdf");
MockFilePicker.returnFiles = [file.file];
// Wait for a message sent by the addon's bootstrap.js file
let promise = new Promise(done => {
Services.obs.addObserver(function listener() {
Services.obs.removeObserver(listener, "test-devtools", false);
ok(true, "Addon installed and running its bootstrap.js file");
done();
}, "test-devtools", false);
});
// Trigger the file picker by clicking on the button
document.getElementById("load-addon-from-file").click();
// Wait for the addon execution
yield promise;
yield installAddon(document, "addons/unpacked/install.rdf", "test-devtools");
// Check that the addon appears in the UI
let names = [...document.querySelectorAll("#addons .target-name")];
@ -36,21 +16,7 @@ add_task(function *() {
ok(names.includes(ADDON_NAME), "The addon name appears in the list of addons: " + names);
// Now uninstall this addon
yield new Promise(done => {
AddonManager.getAddonByID(ADDON_ID, addon => {
let listener = {
onUninstalled: function(aUninstalledAddon) {
if (aUninstalledAddon != addon) {
return;
}
AddonManager.removeAddonListener(listener);
done();
}
};
AddonManager.addAddonListener(listener);
addon.uninstall();
});
});
yield uninstallAddon(ADDON_ID);
// Ensure that the UI removes the addon from the list
names = [...document.querySelectorAll("#addons .target-name")];

View File

@ -0,0 +1,58 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that individual Debug buttons are disabled when "Addons debugging"
// is disabled.
// Test that the buttons are updated dynamically if the preference changes.
const ADDON_ID = "test-devtools@mozilla.org";
add_task(function* () {
info("Turn off addon debugging.");
yield new Promise(resolve => {
let options = {"set": [
["devtools.chrome.enabled", false],
]};
SpecialPowers.pushPrefEnv(options, resolve);
});
let { tab, document } = yield openAboutDebugging("addons");
info("Install a test addon.");
yield installAddon(document, "addons/unpacked/install.rdf", "test-devtools");
let addonDebugCheckbox = document.querySelector("#enable-addon-debugging");
ok(!addonDebugCheckbox.checked, "Addons debugging should be disabled.");
info("Check all debug buttons are disabled.");
let debugButtons = [...document.querySelectorAll("#addons .debug-button")];
ok(debugButtons.every(b => b.disabled), "Debug buttons should be disabled");
info("Click on 'Enable addons debugging' checkbox.");
let addonsContainer = document.getElementById("addons");
let onAddonsMutation = waitForMutation(addonsContainer,
{ subtree: true, attributes: true });
addonDebugCheckbox.click();
yield onAddonsMutation;
info("Check all debug buttons are enabled.");
ok(addonDebugCheckbox.checked, "Addons debugging should be enabled.");
debugButtons = [...document.querySelectorAll("#addons .debug-button")];
ok(debugButtons.every(b => !b.disabled), "Debug buttons should be enabled");
info("Click again on 'Enable addons debugging' checkbox.");
onAddonsMutation = waitForMutation(addonsContainer,
{ subtree: true, attributes: true });
addonDebugCheckbox.click();
yield onAddonsMutation;
info("Check all debug buttons are disabled again.");
debugButtons = [...document.querySelectorAll("#addons .debug-button")];
ok(debugButtons.every(b => b.disabled), "Debug buttons should be disabled");
info("Uninstall addon installed earlier.");
yield uninstallAddon(ADDON_ID);
yield closeAboutDebugging(tab);
});

View File

@ -9,17 +9,6 @@ const HTTP_ROOT = CHROME_ROOT.replace("chrome://mochitests/content/",
const SERVICE_WORKER = HTTP_ROOT + "service-workers/empty-sw.js";
const TAB_URL = HTTP_ROOT + "service-workers/empty-sw.html";
function waitForWorkersUpdate(document) {
return new Promise(done => {
var observer = new MutationObserver(function(mutations) {
observer.disconnect();
done();
});
var target = document.getElementById("service-workers");
observer.observe(target, { childList: true });
});
}
add_task(function *() {
yield new Promise(done => {
let options = {"set": [
@ -32,7 +21,8 @@ add_task(function *() {
let swTab = yield addTab(TAB_URL);
yield waitForWorkersUpdate(document);
let serviceWorkersElement = document.getElementById("service-workers");
yield waitForMutation(serviceWorkersElement, { childList: true });
// Check that the service worker appears in the UI
let names = [...document.querySelectorAll("#service-workers .target-name")];
@ -40,7 +30,8 @@ add_task(function *() {
ok(names.includes(SERVICE_WORKER), "The service worker url appears in the list: " + names);
// Finally, unregister the service worker itself
let aboutDebuggingUpdate = waitForWorkersUpdate(document);
let aboutDebuggingUpdate = waitForMutation(serviceWorkersElement,
{ childList: true });
// Use message manager to work with e10s
let frameScript = function () {

View File

@ -11,17 +11,6 @@ const TAB_URL = HTTP_ROOT + "service-workers/empty-sw.html";
const SW_TIMEOUT = 1000;
function waitForWorkersUpdate(document) {
return new Promise(done => {
var observer = new MutationObserver(function(mutations) {
observer.disconnect();
done();
});
var target = document.getElementById("service-workers");
observer.observe(target, { childList: true });
});
}
function assertHasWorker(expected, document, type, name) {
let names = [...document.querySelectorAll("#" + type + " .target-name")];
names = names.map(element => element.textContent);
@ -45,7 +34,8 @@ add_task(function *() {
let swTab = yield addTab(TAB_URL);
yield waitForWorkersUpdate(document);
let serviceWorkersElement = document.getElementById("service-workers");
yield waitForMutation(serviceWorkersElement, { childList: true });
assertHasWorker(true, document, "service-workers", SERVICE_WORKER);
@ -100,7 +90,7 @@ add_task(function *() {
// Now ensure that the worker is correctly destroyed
// after we destroy the toolbox.
// The list should update once it get destroyed.
yield waitForWorkersUpdate(document);
yield waitForMutation(serviceWorkersElement, { childList: true });
assertHasWorker(false, document, "service-workers", SERVICE_WORKER);

View File

@ -6,6 +6,7 @@
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", {});
const Services = require("Services");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
DevToolsUtils.testing = true;
@ -27,7 +28,6 @@ function openAboutDebugging(page) {
return {
tab,
document: browser.contentDocument,
window: browser.contentWindow
};
});
}
@ -80,3 +80,61 @@ function get_supports_file(path) {
let fileurl = cr.convertChromeURL(Services.io.newURI(CHROME_ROOT + path, null, null));
return fileurl.QueryInterface(Ci.nsIFileURL);
}
function installAddon(document, path, evt) {
// Mock the file picker to select a test addon
let MockFilePicker = SpecialPowers.MockFilePicker;
MockFilePicker.init(null);
let file = get_supports_file(path);
MockFilePicker.returnFiles = [file.file];
// Wait for a message sent by the addon's bootstrap.js file
let onAddonInstalled = new Promise(done => {
Services.obs.addObserver(function listener() {
Services.obs.removeObserver(listener, evt, false);
ok(true, "Addon installed and running its bootstrap.js file");
done();
}, evt, false);
});
// Trigger the file picker by clicking on the button
document.getElementById("load-addon-from-file").click();
// Wait for the addon execution
return onAddonInstalled;
}
function uninstallAddon(addonId) {
// Now uninstall this addon
return new Promise(done => {
AddonManager.getAddonByID(addonId, addon => {
let listener = {
onUninstalled: function(uninstalledAddon) {
if (uninstalledAddon != addon) {
return;
}
AddonManager.removeAddonListener(listener);
done();
}
};
AddonManager.addAddonListener(listener);
addon.uninstall();
});
});
}
/**
* 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 => {
let observer = new MutationObserver(() => {
observer.disconnect();
resolve();
});
observer.observe(target, mutationOptions);
});
}