Bug 1308912 - Add support for addon tools registered to a specific DevTools toolbox. r=ochameau

MozReview-Commit-ID: 7DzyXLGOs5w

--HG--
rename : devtools/client/framework/test/browser_toolbox_dynamic_registration.js => devtools/client/framework/test/browser_toolbox_tools_per_toolbox_registration.js
extra : rebase_source : fb3b29d99151f3996624d8ba8cafaf8307affd8e
extra : source : 714d261424158deabda8dc3cbf20c371775b3306
This commit is contained in:
Luca Greco 2016-11-03 18:41:26 +01:00
Родитель e86fa50b4e
Коммит ef2eba34ab
6 изменённых файлов: 332 добавлений и 26 удалений

Просмотреть файл

@ -726,7 +726,11 @@ gDevTools.getToolDefinitionArray()
// and the new ones.
gDevTools.on("tool-registered", function (ev, toolId) {
let toolDefinition = gDevTools._tools.get(toolId);
// If the tool has been registered globally, add to all the
// available windows.
if (toolDefinition) {
gDevToolsBrowser._addToolToWindows(toolDefinition);
}
});
gDevTools.on("tool-unregistered", function (ev, toolId) {

Просмотреть файл

@ -78,6 +78,7 @@ skip-if = e10s # Bug 1069044 - destroyInspector may hang during shutdown
[browser_toolbox_toggle.js]
[browser_toolbox_tool_ready.js]
[browser_toolbox_tool_remote_reopen.js]
[browser_toolbox_tools_per_toolbox_registration.js]
[browser_toolbox_transport_events.js]
[browser_toolbox_view_source_01.js]
[browser_toolbox_view_source_02.js]

Просмотреть файл

@ -21,6 +21,7 @@ add_task(function* () {
let target = TargetFactory.forTab(tab);
toolbox = yield gDevTools.showToolbox(target);
doc = toolbox.doc;
yield registerNewPerToolboxTool();
yield testSelectTool();
yield testOptionsShortcut();
yield testOptions();
@ -46,6 +47,31 @@ function registerNewTool() {
"The tool is registered");
}
function registerNewPerToolboxTool() {
let toolDefinition = {
id: "test-pertoolbox-tool",
isTargetSupported: () => true,
visibilityswitch: "devtools.test-pertoolbox-tool.enabled",
url: "about:blank",
label: "perToolboxSomeLabel"
};
ok(gDevTools, "gDevTools exists");
ok(!gDevTools.getToolDefinitionMap().has("test-pertoolbox-tool"),
"The per-toolbox tool is not registered globally");
ok(toolbox, "toolbox exists");
ok(!toolbox.hasAdditionalTool("test-pertoolbox-tool"),
"The per-toolbox tool is not yet registered to the toolbox");
toolbox.addAdditionalTool(toolDefinition);
ok(!gDevTools.getToolDefinitionMap().has("test-pertoolbox-tool"),
"The per-toolbox tool is not registered globally");
ok(toolbox.hasAdditionalTool("test-pertoolbox-tool"),
"The per-toolbox tool has been registered to the toolbox");
}
function* testSelectTool() {
info("Checking to make sure that the options panel can be selected.");
@ -168,9 +194,13 @@ function* testToggleTools() {
"#additional-tools-box input[type=checkbox]:not([data-unsupported])");
let enabledTools = [...toolNodes].filter(node => node.checked);
let toggleableTools = gDevTools.getDefaultTools().filter(tool => {
let toggleableTools = gDevTools.getDefaultTools()
.filter(tool => {
return tool.visibilityswitch;
}).concat(gDevTools.getAdditionalTools());
})
.concat(gDevTools.getAdditionalTools())
.concat(toolbox.getAdditionalTools());
for (let node of toolNodes) {
let id = node.getAttribute("id");

Просмотреть файл

@ -0,0 +1,139 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
const TEST_URL = `data:text/html,<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
test for registering and unregistering tools to a specific toolbox
</body>
</html>`;
const TOOL_ID = "test-toolbox-tool";
var toolbox;
var target;
function test() {
addTab(TEST_URL).then(tab => {
target = TargetFactory.forTab(tab);
gDevTools.showToolbox(target)
.then(toolboxRegister)
.then(testToolRegistered);
});
}
var resolveToolInstanceBuild;
var waitForToolInstanceBuild = new Promise((resolve) => {
resolveToolInstanceBuild = resolve;
});
var resolveToolInstanceDestroyed;
var waitForToolInstanceDestroyed = new Promise((resolve) => {
resolveToolInstanceDestroyed = resolve;
});
function toolboxRegister(aToolbox) {
toolbox = aToolbox;
var resolveToolInstanceBuild;
waitForToolInstanceBuild = new Promise((resolve) => {
resolveToolInstanceBuild = resolve;
});
info("add per-toolbox tool in the opened toolbox.");
toolbox.addAdditionalTool({
id: TOOL_ID,
label: "per-toolbox Test Tool",
inMenu: true,
isTargetSupported: () => true,
build: function () {
info("per-toolbox tool has been built.");
resolveToolInstanceBuild();
return {
destroy: () => {
info("per-toolbox tool has been destroyed.");
resolveToolInstanceDestroyed();
},
};
},
key: "t"
});
}
function testToolRegistered() {
ok(!gDevTools.getToolDefinitionMap().has(TOOL_ID), "per-toolbox tool is not registered globally");
ok(toolbox.hasAdditionalTool(TOOL_ID),
"per-toolbox tool registered to the specific toolbox");
// Test that the tool appeared in the UI.
let doc = toolbox.doc;
let tab = doc.getElementById("toolbox-tab-" + TOOL_ID);
ok(tab, "new tool's tab exists in toolbox UI");
let panel = doc.getElementById("toolbox-panel-" + TOOL_ID);
ok(panel, "new tool's panel exists in toolbox UI");
for (let win of getAllBrowserWindows()) {
let key = win.document.getElementById("key_" + TOOL_ID);
if (win.document == doc) {
continue;
}
ok(!key, "key for new tool should not exists in the other browser windows");
let menuitem = win.document.getElementById("menuitem_" + TOOL_ID);
ok(!menuitem, "menu item should not exists in the other browser window");
}
// Test that the tool is built once selected and then test its unregistering.
info("select per-toolbox tool in the opened toolbox.");
gDevTools.showToolbox(target, TOOL_ID)
.then(waitForToolInstanceBuild)
.then(testUnregister);
}
function getAllBrowserWindows() {
let wins = [];
let enumerator = Services.wm.getEnumerator("navigator:browser");
while (enumerator.hasMoreElements()) {
wins.push(enumerator.getNext());
}
return wins;
}
function testUnregister() {
info("remove per-toolbox tool in the opened toolbox.");
toolbox.removeAdditionalTool(TOOL_ID);
Promise.all([
waitForToolInstanceDestroyed
]).then(toolboxToolUnregistered);
}
function toolboxToolUnregistered() {
ok(!toolbox.hasAdditionalTool(TOOL_ID),
"per-toolbox tool unregistered from the specific toolbox");
// test that it disappeared from the UI
let doc = toolbox.doc;
let tab = doc.getElementById("toolbox-tab-" + TOOL_ID);
ok(!tab, "tool's tab was removed from the toolbox UI");
let panel = doc.getElementById("toolbox-panel-" + TOOL_ID);
ok(!panel, "tool's panel was removed from toolbox UI");
cleanup();
}
function cleanup() {
toolbox.destroy();
toolbox = null;
gBrowser.removeCurrentTab();
finish();
}

Просмотреть файл

@ -186,8 +186,12 @@ OptionsPanel.prototype = {
"tools-not-supported-label");
let atleastOneToolNotSupported = false;
const toolbox = this.toolbox;
// Signal tool registering/unregistering globally (for the tools registered
// globally) and per toolbox (for the tools registered to a single toolbox).
let onCheckboxClick = function (id) {
let toolDefinition = gDevTools._tools.get(id);
let toolDefinition = gDevTools._tools.get(id) || toolbox.getToolDefinition(id);
// Set the kill switch pref boolean to true
Services.prefs.setBoolPref(toolDefinition.visibilityswitch, this.checked);
gDevTools.emit(this.checked ? "tool-registered" : "tool-unregistered", id);
@ -239,6 +243,13 @@ OptionsPanel.prototype = {
additionalToolsBox.appendChild(createToolCheckbox(tool));
}
// Populating the additional toolbox-specific tools list that came
// from WebExtension add-ons.
for (let tool of this.toolbox.getAdditionalTools()) {
atleastOneAddon = true;
additionalToolsBox.appendChild(createToolCheckbox(tool));
}
if (!atleastOneAddon) {
additionalToolsBox.style.display = "none";
}

Просмотреть файл

@ -895,6 +895,7 @@ Toolbox.prototype = {
* Add tabs to the toolbox UI for registered tools
*/
_buildTabs: function () {
// Build tabs for global registered tools.
for (let definition of gDevTools.getToolDefinitionArray()) {
this._buildTabForTool(definition);
}
@ -1252,6 +1253,81 @@ Toolbox.prototype = {
this._addKeysToWindow();
},
/**
* Lazily created map of the additional tools registered to this toolbox.
*
* @returns {Map<string, object>}
* a map of the tools definitions registered to this
* particular toolbox (the key is the toolId string, the value
* is the tool definition plain javascript object).
*/
get additionalToolDefinitions() {
if (!this._additionalToolDefinitions) {
this._additionalToolDefinitions = new Map();
}
return this._additionalToolDefinitions;
},
/**
* Retrieve the array of the additional tools registered to this toolbox.
*
* @return {Array<object>}
* the array of additional tool definitions registered on this toolbox.
*/
getAdditionalTools() {
return Array.from(this.additionalToolDefinitions.values());
},
/**
* Test the existence of a additional tools registered to this toolbox by tool id.
*
* @param {string} toolId
* the id of the tool to test for existence.
*
* @return {boolean}
*
*/
hasAdditionalTool(toolId) {
return this.additionalToolDefinitions.has(toolId);
},
/**
* Register and load an additional tool on this particular toolbox.
*
* @param {object} definition
* the additional tool definition to register and add to this toolbox.
*/
addAdditionalTool(definition) {
if (!definition.id) {
throw new Error("Tool definition id is missing");
}
if (this.isToolRegistered(definition.id)) {
throw new Error("Tool definition already registered: " +
definition.id);
}
this.additionalToolDefinitions.set(definition.id, definition);
this._buildTabForTool(definition);
},
/**
* Unregister and unload an additional tool from this particular toolbox.
*
* @param {string} toolId
* the id of the additional tool to unregister and remove.
*/
removeAdditionalTool(toolId) {
if (!this.hasAdditionalTool(toolId)) {
throw new Error("Tool definition not registered to this toolbox: " +
toolId);
}
this.unloadTool(toolId);
this.additionalToolDefinitions.delete(toolId);
},
/**
* Ensure the tool with the given id is loaded.
*
@ -1280,7 +1356,9 @@ Toolbox.prototype = {
return deferred.promise;
}
let definition = gDevTools.getToolDefinition(id);
// Retrieve the tool definition (from the global or the per-toolbox tool maps)
let definition = this.getToolDefinition(id);
if (!definition) {
deferred.reject(new Error("no such tool id " + id));
return deferred.promise;
@ -1907,38 +1985,49 @@ Toolbox.prototype = {
},
/**
* Return if the tool is available as a tab (i.e. if it's checked
* in the options panel). This is different from Toolbox.getPanel -
* a tool could be registered but not yet opened in which case
* isToolRegistered would return true but getPanel would return false.
* Test the availability of a tool (both globally registered tools and
* additional tools registered to this toolbox) by tool id.
*
* @param {string} toolId
* Id of the tool definition to search in the per-toolbox or globally
* registered tools.
*
* @returns {bool}
* Returns true if the tool is registered globally or on this toolbox.
*/
isToolRegistered: function (toolId) {
return gDevTools.getToolDefinitionMap().has(toolId);
return !!this.getToolDefinition(toolId);
},
/**
* Handler for the tool-registered event.
* @param {string} event
* Name of the event ("tool-registered")
* Return the tool definition registered globally or additional tools registered
* to this toolbox.
*
* @param {string} toolId
* Id of the tool that was registered
* Id of the tool definition to retrieve for the per-toolbox and globally
* registered tools.
*
* @returns {object}
* The plain javascript object that represents the requested tool definition.
*/
_toolRegistered: function (event, toolId) {
let tool = gDevTools.getToolDefinition(toolId);
this._buildTabForTool(tool);
// Emit the event so tools can listen to it from the toolbox level
// instead of gDevTools
this.emit("tool-registered", toolId);
getToolDefinition: function (toolId) {
return gDevTools.getToolDefinition(toolId) ||
this.additionalToolDefinitions.get(toolId);
},
/**
* Handler for the tool-unregistered event.
* @param {string} event
* Name of the event ("tool-unregistered")
* Internal helper that removes a loaded tool from the toolbox,
* it removes a loaded tool panel and tab from the toolbox without removing
* its definition, so that it can still be listed in options and re-added later.
*
* @param {string} toolId
* id of the tool that was unregistered.
* Id of the tool to be removed.
*/
_toolUnregistered: function (event, toolId) {
unloadTool: function (toolId) {
if (typeof toolId != "string") {
throw new Error("Unexpected non-string toolId received.");
}
if (this._toolPanels.has(toolId)) {
let instance = this._toolPanels.get(toolId);
instance.destroy();
@ -1975,6 +2064,38 @@ Toolbox.prototype = {
key.parentNode.removeChild(key);
}
}
},
/**
* Handler for the tool-registered event.
* @param {string} event
* Name of the event ("tool-registered")
* @param {string} toolId
* Id of the tool that was registered
*/
_toolRegistered: function (event, toolId) {
let tool = this.getToolDefinition(toolId);
if (!tool) {
// Ignore if the tool is not found, when a per-toolbox tool
// has been toggle in the toolbox options view, every toolbox will receive
// the toolbox-register and toolbox-unregister events.
return;
}
this._buildTabForTool(tool);
// Emit the event so tools can listen to it from the toolbox level
// instead of gDevTools.
this.emit("tool-registered", toolId);
},
/**
* Handler for the tool-unregistered event.
* @param {string} event
* Name of the event ("tool-unregistered")
* @param {string} toolId
* id of the tool that was unregistered
*/
_toolUnregistered: function (event, toolId) {
this.unloadTool(toolId);
// Emit the event so tools can listen to it from the toolbox level
// instead of gDevTools
this.emit("tool-unregistered", toolId);