Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.

Commit baa6dfa

Browse files
committed
Bug 1394750 - Keep track of the active and enabled devtools webextensions. r=jdescottes
MozReview-Commit-ID: 4KUQCls8CPe --HG-- extra : rebase_source : 643303ee53078bb4f5ae43b045a85d09ec65aa58
1 parent 0b019c5 commit baa6dfa

5 files changed

Lines changed: 292 additions & 17 deletions

File tree

devtools/client/framework/devtools.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,16 @@ DevTools.prototype = {
696696
// Is taken care of by the gDevToolsBrowser.forgetBrowserWindow
697697
},
698698

699+
/**
700+
* Returns the array of the existing toolboxes.
701+
*
702+
* @return {Array<Toolbox>}
703+
* An array of toolboxes.
704+
*/
705+
getToolboxes() {
706+
return Array.from(this._toolboxes.values());
707+
},
708+
699709
/**
700710
* Iterator that yields each of the toolboxes.
701711
*/

devtools/client/framework/test/browser_toolbox_options.js

Lines changed: 152 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ add_task(async function() {
2626
await testOptionsShortcut();
2727
await testOptions();
2828
await testToggleTools();
29+
30+
// Test that registered WebExtensions becomes entries in the
31+
// options panel and toggling their checkbox toggle the related
32+
// preference.
33+
await registerNewWebExtensions();
34+
await testToggleWebExtensions();
35+
2936
await cleanup();
3037
});
3138

@@ -47,6 +54,22 @@ function registerNewTool() {
4754
"The tool is registered");
4855
}
4956

57+
// Register a fake WebExtension to check that it is
58+
// listed in the toolbox options.
59+
function registerNewWebExtensions() {
60+
// Register some fake extensions and init the related preferences
61+
// (similarly to ext-devtools.js).
62+
for (let i = 0; i < 2; i++) {
63+
const extPref = `devtools.webextensions.fakeExtId${i}.enabled`;
64+
Services.prefs.setBoolPref(extPref, true);
65+
66+
toolbox.registerWebExtension(`fakeUUID${i}`, {
67+
name: `Fake WebExtension ${i}`,
68+
pref: extPref,
69+
});
70+
}
71+
}
72+
5073
function registerNewPerToolboxTool() {
5174
let toolDefinition = {
5275
id: "test-pertoolbox-tool",
@@ -104,8 +127,7 @@ async function testOptionsShortcut() {
104127
async function testOptions() {
105128
let tool = toolbox.getPanel("options");
106129
panelWin = tool.panelWin;
107-
let prefNodes = tool.panelDoc.querySelectorAll(
108-
"input[type=checkbox][data-pref]");
130+
let prefNodes = tool.panelDoc.querySelectorAll("input[type=checkbox][data-pref]");
109131

110132
// Store modified pref names so that they can be cleared on error.
111133
for (let node of tool.panelDoc.querySelectorAll("[data-pref]")) {
@@ -191,6 +213,132 @@ async function testMouseClick(node, prefValue) {
191213
observer.destroy();
192214
}
193215

216+
async function testToggleWebExtensions() {
217+
const disabledExtensions = new Set();
218+
let toggleableWebExtensions = toolbox.listWebExtensions();
219+
220+
function toggleWebExtension(node) {
221+
node.scrollIntoView();
222+
EventUtils.synthesizeMouseAtCenter(node, {}, panelWin);
223+
}
224+
225+
function assertExpectedDisabledExtensions() {
226+
for (let ext of toggleableWebExtensions) {
227+
if (disabledExtensions.has(ext)) {
228+
ok(!toolbox.isWebExtensionEnabled(ext.uuid),
229+
`The WebExtension "${ext.name}" should be disabled`);
230+
} else {
231+
ok(toolbox.isWebExtensionEnabled(ext.uuid),
232+
`The WebExtension "${ext.name}" should be enabled`);
233+
}
234+
}
235+
}
236+
237+
function assertAllExtensionsDisabled() {
238+
const enabledUUIDs = toggleableWebExtensions
239+
.filter(ext => toolbox.isWebExtensionEnabled(ext.uuid))
240+
.map(ext => ext.uuid);
241+
242+
Assert.deepEqual(enabledUUIDs, [],
243+
"All the registered WebExtensions should be disabled");
244+
}
245+
246+
function assertAllExtensionsEnabled() {
247+
const disabledUUIDs = toolbox.listWebExtensions()
248+
.filter(ext => !toolbox.isWebExtensionEnabled(ext.uuid))
249+
.map(ext => ext.uuid);
250+
251+
Assert.deepEqual(disabledUUIDs, [],
252+
"All the registered WebExtensions should be enabled");
253+
}
254+
255+
function getWebExtensionNodes() {
256+
let toolNodes = panelWin.document.querySelectorAll(
257+
"#default-tools-box input[type=checkbox]:not([data-unsupported])," +
258+
"#additional-tools-box input[type=checkbox]:not([data-unsupported])");
259+
260+
return [...toolNodes].filter(node => {
261+
return toggleableWebExtensions.some(
262+
({uuid}) => node.getAttribute("id") === `webext-${uuid}`
263+
);
264+
});
265+
}
266+
267+
let webExtensionNodes = getWebExtensionNodes();
268+
269+
is(webExtensionNodes.length, toggleableWebExtensions.length,
270+
"There should be a toggle checkbox for every WebExtension registered");
271+
272+
for (let ext of toggleableWebExtensions) {
273+
ok(toolbox.isWebExtensionEnabled(ext.uuid),
274+
`The WebExtension "${ext.name}" is initially enabled`);
275+
}
276+
277+
// Store modified pref names so that they can be cleared on error.
278+
for (let ext of toggleableWebExtensions) {
279+
modifiedPrefs.push(ext.pref);
280+
}
281+
282+
// Turn each registered WebExtension to disabled.
283+
for (let node of webExtensionNodes) {
284+
toggleWebExtension(node);
285+
286+
const toggledExt = toggleableWebExtensions.find(ext => {
287+
return node.id == `webext-${ext.uuid}`;
288+
});
289+
ok(toggledExt, "Found a WebExtension for the checkbox element");
290+
disabledExtensions.add(toggledExt);
291+
292+
assertExpectedDisabledExtensions();
293+
}
294+
295+
assertAllExtensionsDisabled();
296+
297+
// Turn each registered WebExtension to enabled.
298+
for (let node of webExtensionNodes) {
299+
toggleWebExtension(node);
300+
301+
const toggledExt = toggleableWebExtensions.find(ext => {
302+
return node.id == `webext-${ext.uuid}`;
303+
});
304+
ok(toggledExt, "Found a WebExtension for the checkbox element");
305+
disabledExtensions.delete(toggledExt);
306+
307+
assertExpectedDisabledExtensions();
308+
}
309+
310+
assertAllExtensionsEnabled();
311+
312+
// Unregister the WebExtensions one by one, and check that only the expected
313+
// ones have been unregistered, and the remaining onea are still listed.
314+
for (let ext of toggleableWebExtensions) {
315+
ok(toolbox.listWebExtensions().length > 0,
316+
"There should still be extensions registered");
317+
toolbox.unregisterWebExtension(ext.uuid);
318+
319+
const registeredUUIDs = toolbox.listWebExtensions().map(item => item.uuid);
320+
ok(!registeredUUIDs.includes(ext.uuid),
321+
`the WebExtension "${ext.name}" should have been unregistered`);
322+
323+
webExtensionNodes = getWebExtensionNodes();
324+
325+
const checkboxEl = webExtensionNodes.find(el => el.id === `webext-${ext.uuid}`);
326+
is(checkboxEl, undefined,
327+
"The unregistered WebExtension checkbox should have been removed");
328+
329+
is(registeredUUIDs.length, webExtensionNodes.length,
330+
"There should be the expected number of WebExtensions checkboxes");
331+
}
332+
333+
is(toolbox.listWebExtensions().length, 0,
334+
"All WebExtensions have been unregistered");
335+
336+
webExtensionNodes = getWebExtensionNodes();
337+
338+
is(webExtensionNodes.length, 0,
339+
"There should not be any checkbox for the unregistered WebExtensions");
340+
}
341+
194342
async function testToggleTools() {
195343
let toolNodes = panelWin.document.querySelectorAll(
196344
"#default-tools-box input[type=checkbox]:not([data-unsupported])," +
@@ -207,7 +355,7 @@ async function testToggleTools() {
207355
for (let node of toolNodes) {
208356
let id = node.getAttribute("id");
209357
ok(toggleableTools.some(tool => tool.id === id),
210-
"There should be a toggle checkbox for: " + id);
358+
"There should be a toggle checkbox for: " + id);
211359
}
212360

213361
// Store modified pref names so that they can be cleared on error.
@@ -220,6 +368,7 @@ async function testToggleTools() {
220368
for (let node of toolNodes) {
221369
await toggleTool(node);
222370
}
371+
223372
// Toggle again to reset tool enablement state
224373
for (let node of toolNodes) {
225374
await toggleTool(node);

devtools/client/framework/toolbox-options.js

Lines changed: 66 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ function OptionsPanel(iframeWindow, toolbox) {
6464
this._prefChanged = this._prefChanged.bind(this);
6565
this._themeRegistered = this._themeRegistered.bind(this);
6666
this._themeUnregistered = this._themeUnregistered.bind(this);
67+
this._webExtensionRegistered = this._webExtensionRegistered.bind(this);
68+
this._webExtensionUnregistered = this._webExtensionUnregistered.bind(this);
6769
this._disableJSClicked = this._disableJSClicked.bind(this);
6870

6971
this.disableJSNode = this.panelDoc.getElementById("devtools-disable-javascript");
@@ -103,13 +105,20 @@ OptionsPanel.prototype = {
103105
this._prefChanged);
104106
gDevTools.on("theme-registered", this._themeRegistered);
105107
gDevTools.on("theme-unregistered", this._themeUnregistered);
108+
109+
this.toolbox.on("webextension-registered", this._webExtensionRegistered);
110+
this.toolbox.on("webextension-unregistered", this._webExtensionUnregistered);
106111
},
107112

108113
_removeListeners: function() {
109114
Services.prefs.removeObserver("devtools.cache.disabled", this._prefChanged);
110115
Services.prefs.removeObserver("devtools.theme", this._prefChanged);
111116
Services.prefs.removeObserver("devtools.source-map.client-service.enabled",
112117
this._prefChanged);
118+
119+
this.toolbox.off("webextension-registered", this._webExtensionRegistered);
120+
this.toolbox.off("webextension-unregistered", this._webExtensionUnregistered);
121+
113122
gDevTools.off("theme-registered", this._themeRegistered);
114123
gDevTools.off("theme-unregistered", this._themeUnregistered);
115124
},
@@ -139,6 +148,18 @@ OptionsPanel.prototype = {
139148
}
140149
},
141150

151+
_webExtensionRegistered: function(extensionUUID) {
152+
// Refresh the tools list when a new webextension has been registered
153+
// to the toolbox.
154+
this.setupToolsList();
155+
},
156+
157+
_webExtensionUnregistered: function(extensionUUID) {
158+
// Refresh the tools list when a new webextension has been unregistered
159+
// from the toolbox.
160+
this.setupToolsList();
161+
},
162+
142163
async setupToolbarButtonsList() {
143164
// Ensure the toolbox is open, and the buttons are all set up.
144165
await this.toolbox.isOpen;
@@ -200,14 +221,17 @@ OptionsPanel.prototype = {
200221

201222
// Signal tool registering/unregistering globally (for the tools registered
202223
// globally) and per toolbox (for the tools registered to a single toolbox).
203-
let onCheckboxClick = function(id) {
204-
let toolDefinition = gDevTools._tools.get(id) || toolbox.getToolDefinition(id);
224+
// This event handler expect this to be binded to the related checkbox element.
225+
let onCheckboxClick = function(tool) {
205226
// Set the kill switch pref boolean to true
206-
Services.prefs.setBoolPref(toolDefinition.visibilityswitch, this.checked);
207-
gDevTools.emit(this.checked ? "tool-registered" : "tool-unregistered", id);
227+
Services.prefs.setBoolPref(tool.visibilityswitch, this.checked);
228+
229+
if (!tool.isWebExtension) {
230+
gDevTools.emit(this.checked ? "tool-registered" : "tool-unregistered", tool.id);
231+
}
208232
};
209233

210-
let createToolCheckbox = tool => {
234+
let createToolCheckbox = (tool) => {
211235
let checkboxLabel = this.panelDoc.createElement("label");
212236
let checkboxInput = this.panelDoc.createElement("input");
213237
checkboxInput.setAttribute("type", "checkbox");
@@ -229,14 +253,18 @@ OptionsPanel.prototype = {
229253
checkboxInput.setAttribute("checked", "true");
230254
}
231255

232-
checkboxInput.addEventListener("change",
233-
onCheckboxClick.bind(checkboxInput, tool.id));
256+
checkboxInput.addEventListener("change", onCheckboxClick.bind(checkboxInput, tool));
234257

235258
checkboxLabel.appendChild(checkboxInput);
236259
checkboxLabel.appendChild(checkboxSpanLabel);
237260
return checkboxLabel;
238261
};
239262

263+
// Clean up any existent default tools content.
264+
for (let label of defaultToolsBox.querySelectorAll("label")) {
265+
label.remove();
266+
}
267+
240268
// Populating the default tools lists
241269
let toggleableTools = gDevTools.getDefaultTools().filter(tool => {
242270
return tool.visibilityswitch && !tool.hiddenInOptions;
@@ -246,26 +274,52 @@ OptionsPanel.prototype = {
246274
defaultToolsBox.appendChild(createToolCheckbox(tool));
247275
}
248276

249-
// Populating the additional tools list that came from add-ons.
277+
// Clean up any existent additional tools content.
278+
for (let label of additionalToolsBox.querySelectorAll("label")) {
279+
label.remove();
280+
}
281+
282+
// Populating the additional tools list.
250283
let atleastOneAddon = false;
251284
for (let tool of gDevTools.getAdditionalTools()) {
252285
atleastOneAddon = true;
253286
additionalToolsBox.appendChild(createToolCheckbox(tool));
254287
}
255288

256-
// Populating the additional toolbox-specific tools list that came
257-
// from WebExtension add-ons.
258-
for (let tool of this.toolbox.getAdditionalTools()) {
289+
// Populating the additional tools that came from the installed WebExtension add-ons.
290+
for (let {uuid, name, pref} of toolbox.listWebExtensions()) {
259291
atleastOneAddon = true;
260-
additionalToolsBox.appendChild(createToolCheckbox(tool));
292+
293+
additionalToolsBox.appendChild(createToolCheckbox({
294+
isWebExtension: true,
295+
296+
// Use the preference as the unified webextensions tool id.
297+
id: `webext-${uuid}`,
298+
tooltip: name,
299+
label: name,
300+
// Disable the devtools extension using the given pref name:
301+
// the toolbox options for the WebExtensions are not related to a single
302+
// tool (e.g. a devtools panel created from the extension devtools_page)
303+
// but to the entire devtools part of a webextension which is enabled
304+
// by the Addon Manager (but it may be disabled by its related
305+
// devtools about:config preference), and so the following
306+
visibilityswitch: pref,
307+
308+
// Only local tabs are currently supported as targets.
309+
isTargetSupported: target => target.isLocalTab,
310+
}));
261311
}
262312

263313
if (!atleastOneAddon) {
264314
additionalToolsBox.style.display = "none";
315+
} else {
316+
additionalToolsBox.style.display = "";
265317
}
266318

267319
if (!atleastOneToolNotSupported) {
268320
toolsNotSupportedLabel.style.display = "none";
321+
} else {
322+
toolsNotSupportedLabel.style.display = "";
269323
}
270324

271325
this.panelWin.focus();

0 commit comments

Comments
 (0)