summaryrefslogtreecommitdiffstats
path: root/devtools/client/framework/test/head.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/framework/test/head.js')
-rw-r--r--devtools/client/framework/test/head.js490
1 files changed, 490 insertions, 0 deletions
diff --git a/devtools/client/framework/test/head.js b/devtools/client/framework/test/head.js
new file mode 100644
index 0000000000..2001d5e8c4
--- /dev/null
+++ b/devtools/client/framework/test/head.js
@@ -0,0 +1,490 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// shared-head.js handles imports, constants, and utility functions
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js",
+ this
+);
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/devtools/client/inspector/test/shared-head.js",
+ this
+);
+
+const EventEmitter = require("resource://devtools/shared/event-emitter.js");
+
+/**
+ * Retrieve all tool ids compatible with a target created for the provided tab.
+ *
+ * @param {XULTab} tab
+ * The tab for which we want to get the list of supported toolIds
+ * @return {Array<String>} array of tool ids
+ */
+async function getSupportedToolIds(tab) {
+ info("Getting the entire list of tools supported in this tab");
+
+ let shouldDestroyToolbox = false;
+
+ // Get the toolbox for this tab, or create one if needed.
+ let toolbox = gDevTools.getToolboxForTab(tab);
+ if (!toolbox) {
+ toolbox = await gDevTools.showToolboxForTab(tab);
+ shouldDestroyToolbox = true;
+ }
+
+ const toolIds = gDevTools
+ .getToolDefinitionArray()
+ .filter(def => def.isToolSupported(toolbox))
+ .map(def => def.id);
+
+ if (shouldDestroyToolbox) {
+ // Only close the toolbox if it was explicitly created here.
+ await toolbox.destroy();
+ }
+
+ return toolIds;
+}
+
+function toggleAllTools(state) {
+ for (const [, tool] of gDevTools._tools) {
+ if (!tool.visibilityswitch) {
+ continue;
+ }
+ if (state) {
+ Services.prefs.setBoolPref(tool.visibilityswitch, true);
+ } else {
+ Services.prefs.clearUserPref(tool.visibilityswitch);
+ }
+ }
+}
+
+async function getParentProcessActors(callback) {
+ const commands = await CommandsFactory.forMainProcess();
+ const mainProcessTargetFront = await commands.descriptorFront.getTarget();
+
+ callback(commands.client, mainProcessTargetFront);
+}
+
+function getSourceActor(aSources, aURL) {
+ const item = aSources.getItemForAttachment(a => a.source.url === aURL);
+ return item && item.value;
+}
+
+/**
+ * Synthesize a keypress from a <key> element, taking into account
+ * any modifiers.
+ * @param {Element} el the <key> element to synthesize
+ */
+function synthesizeKeyElement(el) {
+ const key = el.getAttribute("key") || el.getAttribute("keycode");
+ const mod = {};
+ el.getAttribute("modifiers")
+ .split(" ")
+ .forEach(m => (mod[m + "Key"] = true));
+ info(`Synthesizing: key=${key}, mod=${JSON.stringify(mod)}`);
+ EventUtils.synthesizeKey(key, mod, el.ownerDocument.defaultView);
+}
+
+/* Check the toolbox host type and prefs to make sure they match the
+ * expected values
+ * @param {Toolbox}
+ * @param {HostType} hostType
+ * One of {SIDE, BOTTOM, WINDOW} from Toolbox.HostType
+ * @param {HostType} Optional previousHostType
+ * The host that will be switched to when calling switchToPreviousHost
+ */
+function checkHostType(toolbox, hostType, previousHostType) {
+ is(toolbox.hostType, hostType, "host type is " + hostType);
+
+ const pref = Services.prefs.getCharPref("devtools.toolbox.host");
+ is(pref, hostType, "host pref is " + hostType);
+
+ if (previousHostType) {
+ is(
+ Services.prefs.getCharPref("devtools.toolbox.previousHost"),
+ previousHostType,
+ "The previous host is correct"
+ );
+ }
+}
+
+/**
+ * Create a new <script> referencing URL. Return a promise that
+ * resolves when this has happened
+ * @param {String} url
+ * the url
+ * @return {Promise} a promise that resolves when the element has been created
+ */
+function createScript(url) {
+ info(`Creating script: ${url}`);
+ // This is not ideal if called multiple times, as it loads the frame script
+ // separately each time. See bug 1443680.
+ return SpecialPowers.spawn(gBrowser.selectedBrowser, [url], urlChild => {
+ const script = content.document.createElement("script");
+ script.setAttribute("src", urlChild);
+ content.document.body.appendChild(script);
+ });
+}
+
+/**
+ * Wait for the toolbox to notice that a given source is loaded
+ * @param {Toolbox} toolbox
+ * @param {String} url
+ * the url to wait for
+ * @return {Promise} a promise that is resolved when the source is loaded
+ */
+function waitForSourceLoad(toolbox, url) {
+ info(`Waiting for source ${url} to be available...`);
+ return new Promise(resolve => {
+ const { resourceCommand } = toolbox;
+
+ function onAvailable(sources) {
+ for (const source of sources) {
+ if (source.url === url) {
+ resourceCommand.unwatchResources([resourceCommand.TYPES.SOURCE], {
+ onAvailable,
+ });
+ resolve();
+ }
+ }
+ }
+ resourceCommand.watchResources([resourceCommand.TYPES.SOURCE], {
+ onAvailable,
+ // Ignore the cached resources as we always listen *before*
+ // the action creating a source.
+ ignoreExistingResources: true,
+ });
+ });
+}
+
+/**
+ * When a Toolbox is started it creates a DevToolPanel for each of the tools
+ * by calling toolDefinition.build(). The returned object should
+ * at least implement these functions. They will be used by the ToolBox.
+ *
+ * There may be no benefit in doing this as an abstract type, but if nothing
+ * else gives us a place to write documentation.
+ */
+function DevToolPanel(iframeWindow, toolbox) {
+ EventEmitter.decorate(this);
+
+ this._toolbox = toolbox;
+ this._window = iframeWindow;
+}
+
+DevToolPanel.prototype = {
+ open() {
+ return new Promise(resolve => {
+ executeSoon(() => {
+ resolve(this);
+ });
+ });
+ },
+
+ get document() {
+ return this._window.document;
+ },
+
+ get target() {
+ return this._toolbox.target;
+ },
+
+ get toolbox() {
+ return this._toolbox;
+ },
+
+ destroy() {
+ return Promise.resolve(null);
+ },
+};
+
+/**
+ * Create a simple devtools test panel that implements the minimum API needed to be
+ * registered and opened in the toolbox.
+ */
+function createTestPanel(iframeWindow, toolbox) {
+ return new DevToolPanel(iframeWindow, toolbox);
+}
+
+async function openChevronMenu(toolbox) {
+ const chevronMenuButton = toolbox.doc.querySelector(".tools-chevron-menu");
+ EventUtils.synthesizeMouseAtCenter(chevronMenuButton, {}, toolbox.win);
+
+ const menuPopup = toolbox.doc.getElementById(
+ "tools-chevron-menu-button-panel"
+ );
+ ok(menuPopup, "tools-chevron-menupopup is available");
+
+ info("Waiting for the menu popup to be displayed");
+ await waitUntil(() => menuPopup.classList.contains("tooltip-visible"));
+}
+
+async function closeChevronMenu(toolbox) {
+ // In order to close the popup menu with escape key, set the focus to the chevron
+ // button at first.
+ const chevronMenuButton = toolbox.doc.querySelector(".tools-chevron-menu");
+ chevronMenuButton.focus();
+
+ EventUtils.sendKey("ESCAPE", toolbox.doc.defaultView);
+ const menuPopup = toolbox.doc.getElementById(
+ "tools-chevron-menu-button-panel"
+ );
+
+ info("Closing the chevron popup menu");
+ await waitUntil(() => !menuPopup.classList.contains("tooltip-visible"));
+}
+
+function prepareToolTabReorderTest(toolbox, startingOrder) {
+ Services.prefs.setCharPref(
+ "devtools.toolbox.tabsOrder",
+ startingOrder.join(",")
+ );
+ ok(
+ !toolbox.doc.getElementById("tools-chevron-menu-button"),
+ "The size of the screen being too small"
+ );
+
+ for (const id of startingOrder) {
+ ok(getElementByToolId(toolbox, id), `Tab element should exist for ${id}`);
+ }
+}
+
+async function dndToolTab(toolbox, dragTarget, dropTarget, passedTargets = []) {
+ info(`Drag ${dragTarget} to ${dropTarget}`);
+ const dragTargetEl = getElementByToolIdOrExtensionIdOrSelector(
+ toolbox,
+ dragTarget
+ );
+
+ const onReady = dragTargetEl.classList.contains("selected")
+ ? Promise.resolve()
+ : toolbox.once("select");
+ EventUtils.synthesizeMouseAtCenter(
+ dragTargetEl,
+ { type: "mousedown" },
+ dragTargetEl.ownerGlobal
+ );
+ await onReady;
+
+ for (const passedTarget of passedTargets) {
+ info(`Via ${passedTarget}`);
+ const passedTargetEl = getElementByToolIdOrExtensionIdOrSelector(
+ toolbox,
+ passedTarget
+ );
+ EventUtils.synthesizeMouseAtCenter(
+ passedTargetEl,
+ { type: "mousemove" },
+ passedTargetEl.ownerGlobal
+ );
+ }
+
+ if (dropTarget) {
+ const dropTargetEl = getElementByToolIdOrExtensionIdOrSelector(
+ toolbox,
+ dropTarget
+ );
+ EventUtils.synthesizeMouseAtCenter(
+ dropTargetEl,
+ { type: "mousemove" },
+ dropTargetEl.ownerGlobal
+ );
+ EventUtils.synthesizeMouseAtCenter(
+ dropTargetEl,
+ { type: "mouseup" },
+ dropTargetEl.ownerGlobal
+ );
+ } else {
+ const containerEl = toolbox.doc.getElementById("toolbox-container");
+ EventUtils.synthesizeMouse(
+ containerEl,
+ 0,
+ 0,
+ { type: "mouseout" },
+ containerEl.ownerGlobal
+ );
+ }
+
+ // Wait for updating the preference.
+ await new Promise(resolve => {
+ const onUpdated = () => {
+ Services.prefs.removeObserver("devtools.toolbox.tabsOrder", onUpdated);
+ resolve();
+ };
+
+ Services.prefs.addObserver("devtools.toolbox.tabsOrder", onUpdated);
+ });
+}
+
+function assertToolTabOrder(toolbox, expectedOrder) {
+ info("Check the order of the tabs on the toolbar");
+
+ const tabEls = toolbox.doc.querySelectorAll(".devtools-tab");
+
+ for (let i = 0; i < expectedOrder.length; i++) {
+ const isOrdered =
+ tabEls[i].dataset.id === expectedOrder[i] ||
+ tabEls[i].dataset.extensionId === expectedOrder[i];
+ ok(isOrdered, `The tab[${expectedOrder[i]}] should exist at [${i}]`);
+ }
+}
+
+function assertToolTabSelected(toolbox, dragTarget) {
+ info("Check whether the drag target was selected");
+ const dragTargetEl = getElementByToolIdOrExtensionIdOrSelector(
+ toolbox,
+ dragTarget
+ );
+ ok(
+ dragTargetEl.classList.contains("selected"),
+ "The dragged tool should be selected"
+ );
+}
+
+function assertToolTabPreferenceOrder(expectedOrder) {
+ info("Check the order in DevTools preference for tabs order");
+ is(
+ Services.prefs.getCharPref("devtools.toolbox.tabsOrder"),
+ expectedOrder.join(","),
+ "The preference should be correct"
+ );
+}
+
+function getElementByToolId(toolbox, id) {
+ for (const tabEl of toolbox.doc.querySelectorAll(".devtools-tab")) {
+ if (tabEl.dataset.id === id || tabEl.dataset.extensionId === id) {
+ return tabEl;
+ }
+ }
+
+ return null;
+}
+
+function getElementByToolIdOrExtensionIdOrSelector(toolbox, idOrSelector) {
+ const tabEl = getElementByToolId(toolbox, idOrSelector);
+ return tabEl ? tabEl : toolbox.doc.querySelector(idOrSelector);
+}
+
+/**
+ * Returns a toolbox tab element, even if it's overflowed
+ **/
+function getToolboxTab(doc, toolId) {
+ return (
+ doc.getElementById(`toolbox-tab-${toolId}`) ||
+ doc.getElementById(`tools-chevron-menupopup-${toolId}`)
+ );
+}
+
+function getWindow(toolbox) {
+ return toolbox.topWindow;
+}
+
+async function resizeWindow(toolbox, width, height) {
+ const hostWindow = toolbox.win.parent;
+ const originalWidth = hostWindow.outerWidth;
+ const originalHeight = hostWindow.outerHeight;
+ const toWidth = width || originalWidth;
+ const toHeight = height || originalHeight;
+
+ const onResize = once(hostWindow, "resize");
+ hostWindow.resizeTo(toWidth, toHeight);
+ await onResize;
+}
+
+function assertSelectedLocationInDebugger(debuggerPanel, line, column) {
+ const location = debuggerPanel._selectors.getSelectedLocation(
+ debuggerPanel._getState()
+ );
+ is(location.line, line);
+ is(location.column, column);
+}
+
+/**
+ * Open a new tab on about:devtools-toolbox with the provided params object used as
+ * queryString.
+ */
+async function openAboutToolbox(params) {
+ info("Open about:devtools-toolbox");
+ const querystring = new URLSearchParams();
+ Object.keys(params).forEach(x => querystring.append(x, params[x]));
+
+ const tab = await addTab(`about:devtools-toolbox?${querystring}`);
+ const browser = tab.linkedBrowser;
+
+ return {
+ tab,
+ document: browser.contentDocument,
+ };
+}
+
+/**
+ * Load FTL.
+ *
+ * @param {Toolbox} toolbox
+ * Toolbox instance.
+ * @param {String} path
+ * Path to the FTL file.
+ */
+function loadFTL(toolbox, path) {
+ const win = toolbox.doc.ownerGlobal;
+
+ if (win.MozXULElement) {
+ win.MozXULElement.insertFTLIfNeeded(path);
+ }
+}
+
+/**
+ * Emit a reload key shortcut from a given toolbox, and wait for the reload to
+ * be completed.
+ *
+ * @param {String} shortcut
+ * The key shortcut to send, as expected by the devtools shortcuts
+ * helpers (eg. "CmdOrCtrl+F5").
+ * @param {Toolbox} toolbox
+ * The toolbox through which the event should be emitted.
+ */
+async function sendToolboxReloadShortcut(shortcut, toolbox) {
+ const promises = [];
+
+ // If we have a jsdebugger panel, wait for it to complete its reload.
+ const jsdebugger = toolbox.getPanel("jsdebugger");
+ if (jsdebugger) {
+ promises.push(jsdebugger.once("reloaded"));
+ }
+
+ // If we have an inspector panel, wait for it to complete its reload.
+ const inspector = toolbox.getPanel("inspector");
+ if (inspector) {
+ promises.push(
+ inspector.once("reloaded"),
+ inspector.once("inspector-updated")
+ );
+ }
+
+ const loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ promises.push(loadPromise);
+
+ info("Focus the toolbox window and emit the reload shortcut: " + shortcut);
+ toolbox.win.focus();
+ synthesizeKeyShortcut(shortcut, toolbox.win);
+
+ info("Wait for page and toolbox reload promises");
+ await Promise.all(promises);
+}
+
+function getErrorIcon(toolbox) {
+ return toolbox.doc.querySelector(".toolbox-error");
+}
+
+function getErrorIconCount(toolbox) {
+ const textContent = getErrorIcon(toolbox)?.textContent;
+ try {
+ const int = parseInt(textContent, 10);
+ // 99+ parses to 99, so we check if the parsedInt does not match the textContent.
+ return int.toString() === textContent ? int : textContent;
+ } catch (e) {
+ // In case the parseInt threw, return the actual textContent so the test can display
+ // an easy to debug failure.
+ return textContent;
+ }
+}