diff options
Diffstat (limited to 'devtools/client/framework/test/browser_toolbox_options.js')
-rw-r--r-- | devtools/client/framework/test/browser_toolbox_options.js | 557 |
1 files changed, 557 insertions, 0 deletions
diff --git a/devtools/client/framework/test/browser_toolbox_options.js b/devtools/client/framework/test/browser_toolbox_options.js new file mode 100644 index 0000000000..7df58cbcfd --- /dev/null +++ b/devtools/client/framework/test/browser_toolbox_options.js @@ -0,0 +1,557 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests that changing preferences in the options panel updates the prefs +// and toggles appropriate things in the toolbox. + +var doc = null, + toolbox = null, + panelWin = null, + modifiedPrefs = []; +const { LocalizationHelper } = require("resource://devtools/shared/l10n.js"); +const L10N = new LocalizationHelper( + "devtools/client/locales/toolbox.properties" +); +const { PrefObserver } = require("resource://devtools/client/shared/prefs.js"); + +add_task(async function() { + const URL = + "data:text/html;charset=utf8,test for dynamically registering " + + "and unregistering tools"; + registerNewTool(); + const tab = await addTab(URL); + toolbox = await gDevTools.showToolboxForTab(tab); + + doc = toolbox.doc; + await registerNewPerToolboxTool(); + await testSelectTool(); + await testOptionsShortcut(); + await testOptions(); + await testToggleTools(); + + // Test that registered WebExtensions becomes entries in the + // options panel and toggling their checkbox toggle the related + // preference. + await registerNewWebExtensions(); + await testToggleWebExtensions(); + + await cleanup(); +}); + +function registerNewTool() { + const toolDefinition = { + id: "testTool", + isToolSupported: () => true, + visibilityswitch: "devtools.test-tool.enabled", + url: "about:blank", + label: "someLabel", + }; + + ok(gDevTools, "gDevTools exists"); + ok( + !gDevTools.getToolDefinitionMap().has("testTool"), + "The tool is not registered" + ); + + gDevTools.registerTool(toolDefinition); + ok( + gDevTools.getToolDefinitionMap().has("testTool"), + "The tool is registered" + ); +} + +// Register a fake WebExtension to check that it is +// listed in the toolbox options. +function registerNewWebExtensions() { + // Register some fake extensions and init the related preferences + // (similarly to ext-devtools.js). + for (let i = 0; i < 2; i++) { + const extPref = `devtools.webextensions.fakeExtId${i}.enabled`; + Services.prefs.setBoolPref(extPref, true); + + toolbox.registerWebExtension(`fakeUUID${i}`, { + name: `Fake WebExtension ${i}`, + pref: extPref, + }); + } +} + +function registerNewPerToolboxTool() { + const toolDefinition = { + id: "test-pertoolbox-tool", + isToolSupported: () => 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" + ); +} + +async function testSelectTool() { + info("Checking to make sure that the options panel can be selected."); + + const onceSelected = toolbox.once("options-selected"); + toolbox.selectTool("options"); + await onceSelected; + ok(true, "Toolbox selected via selectTool method"); +} + +async function testOptionsShortcut() { + info("Selecting another tool, then reselecting options panel with keyboard."); + + await toolbox.selectTool("webconsole"); + is(toolbox.currentToolId, "webconsole", "webconsole is selected"); + synthesizeKeyShortcut(L10N.getStr("toolbox.help.key")); + is(toolbox.currentToolId, "options", "Toolbox selected via shortcut key"); + synthesizeKeyShortcut(L10N.getStr("toolbox.help.key")); + is(toolbox.currentToolId, "webconsole", "webconsole is reselected"); + synthesizeKeyShortcut(L10N.getStr("toolbox.help.key")); + is(toolbox.currentToolId, "options", "Toolbox selected via shortcut key"); +} + +async function testOptions() { + const tool = toolbox.getPanel("options"); + panelWin = tool.panelWin; + const prefNodes = tool.panelDoc.querySelectorAll( + "input[type=checkbox][data-pref]" + ); + + // Store modified pref names so that they can be cleared on error. + for (const node of tool.panelDoc.querySelectorAll("[data-pref]")) { + const pref = node.getAttribute("data-pref"); + modifiedPrefs.push(pref); + } + + for (const node of prefNodes) { + const prefValue = GetPref(node.getAttribute("data-pref")); + + // Test clicking the checkbox for each options pref + await testMouseClick(node, prefValue); + + // Do again with opposite values to reset prefs + await testMouseClick(node, !prefValue); + } + + const prefSelects = tool.panelDoc.querySelectorAll("select[data-pref]"); + for (const node of prefSelects) { + await testSelect(node); + } +} + +async function testSelect(select) { + const pref = select.getAttribute("data-pref"); + const options = Array.from(select.options); + info("Checking select for: " + pref); + + is( + `${select.options[select.selectedIndex].value}`, + `${GetPref(pref)}`, + "select starts out selected" + ); + + for (const option of options) { + if (options.indexOf(option) === select.selectedIndex) { + continue; + } + + const observer = new PrefObserver("devtools."); + + let changeSeen = false; + const changeSeenPromise = new Promise(resolve => { + observer.once(pref, () => { + changeSeen = true; + is( + `${GetPref(pref)}`, + `${option.value}`, + "Preference been switched for " + pref + ); + resolve(); + }); + }); + + select.selectedIndex = options.indexOf(option); + const changeEvent = new Event("change"); + select.dispatchEvent(changeEvent); + + await changeSeenPromise; + + ok(changeSeen, "Correct pref was changed"); + observer.destroy(); + } +} + +async function testMouseClick(node, prefValue) { + const observer = new PrefObserver("devtools."); + + const pref = node.getAttribute("data-pref"); + let changeSeen = false; + const changeSeenPromise = new Promise(resolve => { + observer.once(pref, () => { + changeSeen = true; + is(GetPref(pref), !prefValue, "New value is correct for " + pref); + resolve(); + }); + }); + + node.scrollIntoView(); + + // We use executeSoon here to ensure that the element is in view and + // clickable. + executeSoon(function() { + info("Click event synthesized for pref " + pref); + EventUtils.synthesizeMouseAtCenter(node, {}, panelWin); + }); + + await changeSeenPromise; + + ok(changeSeen, "Correct pref was changed"); + observer.destroy(); +} + +async function testToggleWebExtensions() { + const disabledExtensions = new Set(); + const toggleableWebExtensions = toolbox.listWebExtensions(); + + function toggleWebExtension(node) { + node.scrollIntoView(); + EventUtils.synthesizeMouseAtCenter(node, {}, panelWin); + } + + function assertExpectedDisabledExtensions() { + for (const ext of toggleableWebExtensions) { + if (disabledExtensions.has(ext)) { + ok( + !toolbox.isWebExtensionEnabled(ext.uuid), + `The WebExtension "${ext.name}" should be disabled` + ); + } else { + ok( + toolbox.isWebExtensionEnabled(ext.uuid), + `The WebExtension "${ext.name}" should be enabled` + ); + } + } + } + + function assertAllExtensionsDisabled() { + const enabledUUIDs = toggleableWebExtensions + .filter(ext => toolbox.isWebExtensionEnabled(ext.uuid)) + .map(ext => ext.uuid); + + Assert.deepEqual( + enabledUUIDs, + [], + "All the registered WebExtensions should be disabled" + ); + } + + function assertAllExtensionsEnabled() { + const disabledUUIDs = toolbox + .listWebExtensions() + .filter(ext => !toolbox.isWebExtensionEnabled(ext.uuid)) + .map(ext => ext.uuid); + + Assert.deepEqual( + disabledUUIDs, + [], + "All the registered WebExtensions should be enabled" + ); + } + + function getWebExtensionNodes() { + const toolNodes = panelWin.document.querySelectorAll( + "#default-tools-box input[type=checkbox]:not([data-unsupported])," + + "#additional-tools-box input[type=checkbox]:not([data-unsupported])" + ); + + return [...toolNodes].filter(node => { + return toggleableWebExtensions.some( + ({ uuid }) => node.getAttribute("id") === `webext-${uuid}` + ); + }); + } + + let webExtensionNodes = getWebExtensionNodes(); + + is( + webExtensionNodes.length, + toggleableWebExtensions.length, + "There should be a toggle checkbox for every WebExtension registered" + ); + + for (const ext of toggleableWebExtensions) { + ok( + toolbox.isWebExtensionEnabled(ext.uuid), + `The WebExtension "${ext.name}" is initially enabled` + ); + } + + // Store modified pref names so that they can be cleared on error. + for (const ext of toggleableWebExtensions) { + modifiedPrefs.push(ext.pref); + } + + // Turn each registered WebExtension to disabled. + for (const node of webExtensionNodes) { + toggleWebExtension(node); + + const toggledExt = toggleableWebExtensions.find(ext => { + return node.id == `webext-${ext.uuid}`; + }); + ok(toggledExt, "Found a WebExtension for the checkbox element"); + disabledExtensions.add(toggledExt); + + assertExpectedDisabledExtensions(); + } + + assertAllExtensionsDisabled(); + + // Turn each registered WebExtension to enabled. + for (const node of webExtensionNodes) { + toggleWebExtension(node); + + const toggledExt = toggleableWebExtensions.find(ext => { + return node.id == `webext-${ext.uuid}`; + }); + ok(toggledExt, "Found a WebExtension for the checkbox element"); + disabledExtensions.delete(toggledExt); + + assertExpectedDisabledExtensions(); + } + + assertAllExtensionsEnabled(); + + // Unregister the WebExtensions one by one, and check that only the expected + // ones have been unregistered, and the remaining onea are still listed. + for (const ext of toggleableWebExtensions) { + ok( + !!toolbox.listWebExtensions().length, + "There should still be extensions registered" + ); + toolbox.unregisterWebExtension(ext.uuid); + + const registeredUUIDs = toolbox.listWebExtensions().map(item => item.uuid); + ok( + !registeredUUIDs.includes(ext.uuid), + `the WebExtension "${ext.name}" should have been unregistered` + ); + + webExtensionNodes = getWebExtensionNodes(); + + const checkboxEl = webExtensionNodes.find( + el => el.id === `webext-${ext.uuid}` + ); + is( + checkboxEl, + undefined, + "The unregistered WebExtension checkbox should have been removed" + ); + + is( + registeredUUIDs.length, + webExtensionNodes.length, + "There should be the expected number of WebExtensions checkboxes" + ); + } + + is( + toolbox.listWebExtensions().length, + 0, + "All WebExtensions have been unregistered" + ); + + webExtensionNodes = getWebExtensionNodes(); + + is( + webExtensionNodes.length, + 0, + "There should not be any checkbox for the unregistered WebExtensions" + ); +} + +function getToolNode(id) { + return panelWin.document.getElementById(id); +} + +async function testToggleTools() { + const toolNodes = panelWin.document.querySelectorAll( + "#default-tools-box input[type=checkbox]:not([data-unsupported])," + + "#additional-tools-box input[type=checkbox]:not([data-unsupported])" + ); + const toolNodeIds = [...toolNodes].map(node => node.id); + const enabledToolIds = [...toolNodes] + .filter(node => node.checked) + .map(node => node.id); + + const toggleableTools = gDevTools + .getDefaultTools() + .filter(tool => { + return tool.visibilityswitch; + }) + .concat(gDevTools.getAdditionalTools()) + .concat(toolbox.getAdditionalTools()); + + for (const node of toolNodes) { + const id = node.getAttribute("id"); + ok( + toggleableTools.some(tool => tool.id === id), + "There should be a toggle checkbox for: " + id + ); + } + + // Store modified pref names so that they can be cleared on error. + for (const tool of toggleableTools) { + const pref = tool.visibilityswitch; + modifiedPrefs.push(pref); + } + + // Toggle each tool + for (const id of toolNodeIds) { + await toggleTool(getToolNode(id)); + } + + // Toggle again to reset tool enablement state + for (const id of toolNodeIds) { + await toggleTool(getToolNode(id)); + } + + // Test that a tool can still be added when no tabs are present: + // Disable all tools + for (const id of enabledToolIds) { + await toggleTool(getToolNode(id)); + } + // Re-enable the tools which are enabled by default + for (const id of enabledToolIds) { + await toggleTool(getToolNode(id)); + } + + // Toggle first, middle, and last tools to ensure that toolbox tabs are + // inserted in order + const firstToolId = toolNodeIds[0]; + const middleToolId = toolNodeIds[(toolNodeIds.length / 2) | 0]; + const lastToolId = toolNodeIds[toolNodeIds.length - 1]; + + await toggleTool(getToolNode(firstToolId)); + await toggleTool(getToolNode(firstToolId)); + await toggleTool(getToolNode(middleToolId)); + await toggleTool(getToolNode(middleToolId)); + await toggleTool(getToolNode(lastToolId)); + await toggleTool(getToolNode(lastToolId)); +} + +/** + * Toggle tool node checkbox. Note: because toggling the checkbox will result in + * re-rendering of the tool list, we must re-query the checkboxes every time. + */ +async function toggleTool(node) { + const toolId = node.getAttribute("id"); + + const registeredPromise = new Promise(resolve => { + if (node.checked) { + gDevTools.once( + "tool-unregistered", + checkUnregistered.bind(null, toolId, resolve) + ); + } else { + gDevTools.once( + "tool-registered", + checkRegistered.bind(null, toolId, resolve) + ); + } + }); + node.scrollIntoView(); + EventUtils.synthesizeMouseAtCenter(node, {}, panelWin); + + await registeredPromise; +} + +function checkUnregistered(toolId, resolve, data) { + if (data == toolId) { + ok(true, "Correct tool removed"); + // checking tab on the toolbox + ok( + !doc.getElementById("toolbox-tab-" + toolId), + "Tab removed for " + toolId + ); + } else { + ok(false, "Something went wrong, " + toolId + " was not unregistered"); + } + resolve(); +} + +async function checkRegistered(toolId, resolve, data) { + if (data == toolId) { + ok(true, "Correct tool added back"); + // checking tab on the toolbox + const button = await lookupButtonForToolId(toolId); + ok(button, "Tab added back for " + toolId); + } else { + ok(false, "Something went wrong, " + toolId + " was not registered"); + } + resolve(); +} + +function GetPref(name) { + const type = Services.prefs.getPrefType(name); + switch (type) { + case Services.prefs.PREF_STRING: + return Services.prefs.getCharPref(name); + case Services.prefs.PREF_INT: + return Services.prefs.getIntPref(name); + case Services.prefs.PREF_BOOL: + return Services.prefs.getBoolPref(name); + default: + throw new Error("Unknown type"); + } +} + +/** + * Find the button from specified toolId. + * Generally, button which access to the tool panel is in toolbox or + * tools menu(in the Chevron menu). + */ +async function lookupButtonForToolId(toolId) { + let button = doc.getElementById("toolbox-tab-" + toolId); + if (!button) { + // search from the tools menu. + await openChevronMenu(toolbox); + button = doc.querySelector("#tools-chevron-menupopup-" + toolId); + + await closeChevronMenu(toolbox); + } + return button; +} + +async function cleanup() { + gDevTools.unregisterTool("testTool"); + await toolbox.destroy(); + gBrowser.removeCurrentTab(); + for (const pref of modifiedPrefs) { + Services.prefs.clearUserPref(pref); + } + toolbox = doc = panelWin = modifiedPrefs = null; +} |