diff options
Diffstat (limited to 'devtools/client/dom/test/head.js')
-rw-r--r-- | devtools/client/dom/test/head.js | 239 |
1 files changed, 239 insertions, 0 deletions
diff --git a/devtools/client/dom/test/head.js b/devtools/client/dom/test/head.js new file mode 100644 index 000000000..f9382786b --- /dev/null +++ b/devtools/client/dom/test/head.js @@ -0,0 +1,239 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +/* eslint no-unused-vars: [2, {"vars": "local", "args": "none"}] */ +/* import-globals-from ../../framework/test/shared-head.js */ + +"use strict"; + +const FRAME_SCRIPT_UTILS_URL = + "chrome://devtools/content/shared/frame-script-utils.js"; + +// shared-head.js handles imports, constants, and utility functions +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js", this); + +// DOM panel actions. +const constants = require("devtools/client/dom/content/constants"); + +// Uncomment this pref to dump all devtools emitted events to the console. +// Services.prefs.setBoolPref("devtools.dom.enabled", true); + +// Enable the DOM panel +Services.prefs.setBoolPref("devtools.dom.enabled", true); + +registerCleanupFunction(() => { + info("finish() was called, cleaning up..."); + Services.prefs.clearUserPref("devtools.dump.emit"); + Services.prefs.clearUserPref("devtools.dom.enabled"); +}); + +/** + * Add a new test tab in the browser and load the given url. + * @param {String} url + * The url to be loaded in the new tab + * @return a promise that resolves to the tab object when + * the url is loaded + */ +function addTestTab(url) { + info("Adding a new test tab with URL: '" + url + "'"); + + return new Promise(resolve => { + addTab(url).then(tab => { + // Load devtools/shared/frame-script-utils.js + getFrameScript(); + + // Select the DOM panel and wait till it's initialized. + initDOMPanel(tab).then(panel => { + waitForDispatch(panel, "FETCH_PROPERTIES").then(() => { + resolve({ + tab: tab, + browser: tab.linkedBrowser, + panel: panel + }); + }); + }); + }); + }); +} + +/** + * Open the DOM panel for the given tab. + * + * @param {nsIDOMElement} tab + * Optional tab element for which you want open the DOM panel. + * The default tab is taken from the global variable |tab|. + * @return a promise that is resolved once the web console is open. + */ +function initDOMPanel(tab) { + return new Promise(resolve => { + let target = TargetFactory.forTab(tab || gBrowser.selectedTab); + gDevTools.showToolbox(target, "dom").then(toolbox => { + let panel = toolbox.getCurrentPanel(); + resolve(panel); + }); + }); +} + +/** + * Synthesize asynchronous click event (with clean stack trace). + */ +function synthesizeMouseClickSoon(panel, element) { + return new Promise(resolve => { + executeSoon(() => { + EventUtils.synthesizeMouse(element, 2, 2, {}, panel.panelWin); + resolve(); + }); + }); +} + +/** + * Returns tree row with specified label. + */ +function getRowByLabel(panel, text) { + let doc = panel.panelWin.document; + let labels = [...doc.querySelectorAll(".treeLabel")]; + let label = labels.find(node => node.textContent == text); + return label ? label.closest(".treeRow") : null; +} + +/** + * Returns the children (tree row text) of the specified object name as an + * array. + */ +function getAllRowsForLabel(panel, text) { + let rootObjectLevel; + let node; + let result = []; + let doc = panel.panelWin.document; + let nodes = [...doc.querySelectorAll(".treeLabel")]; + + // Find the label (object name) for which we want the children. We remove + // nodes from the start of the array until we reach the property. The children + // are then at the start of the array. + while (true) { + node = nodes.shift(); + + if (!node || node.textContent === text) { + rootObjectLevel = node.getAttribute("data-level"); + break; + } + } + + // Return an empty array if the node is not found. + if (!node) { + return result; + } + + // Now get the children. + for (node of nodes) { + let level = node.getAttribute("data-level"); + + if (level > rootObjectLevel) { + result.push({ + name: normalizeTreeValue(node.textContent), + value: normalizeTreeValue(node.parentNode.nextElementSibling.textContent) + }); + } else { + break; + } + } + + return result; +} + +/** + * Strings in the tree are in the form ""a"" and numbers in the form "1". We + * normalize these values by converting ""a"" to "a" and "1" to 1. + * + * @param {String} value + * The value to normalize. + * @return {String|Number} + * The normalized value. + */ +function normalizeTreeValue(value) { + if (value === `""`) { + return ""; + } + if (value.startsWith(`"`) && value.endsWith(`"`)) { + return value.substr(1, value.length - 2); + } + if (isFinite(value) && parseInt(value, 10) == value) { + return parseInt(value, 10); + } + + return value; +} + +/** + * Expands elements with given label and waits till + * children are received from the backend. + */ +function expandRow(panel, labelText) { + let row = getRowByLabel(panel, labelText); + return synthesizeMouseClickSoon(panel, row).then(() => { + // Wait till children (properties) are fetched + // from the backend. + return waitForDispatch(panel, "FETCH_PROPERTIES"); + }); +} + +function evaluateJSAsync(panel, expression) { + return new Promise(resolve => { + panel.target.activeConsole.evaluateJSAsync(expression, res => { + resolve(res); + }); + }); +} + +function refreshPanel(panel) { + let doc = panel.panelWin.document; + let button = doc.querySelector(".btn.refresh"); + return synthesizeMouseClickSoon(panel, button).then(() => { + // Wait till children (properties) are fetched + // from the backend. + return waitForDispatch(panel, "FETCH_PROPERTIES"); + }); +} + +// Redux related API, use from shared location +// as soon as bug 1261076 is fixed. + +// Wait until an action of `type` is dispatched. If it's part of an +// async operation, wait until the `status` field is "done" or "error" +function _afterDispatchDone(store, type) { + return new Promise(resolve => { + store.dispatch({ + // Normally we would use `services.WAIT_UNTIL`, but use the + // internal name here so tests aren't forced to always pass it + // in + type: "@@service/waitUntil", + predicate: action => { + if (action.type === type) { + return action.status ? + (action.status === "end" || action.status === "error") : + true; + } + return false; + }, + run: (dispatch, getState, action) => { + resolve(action); + } + }); + }); +} + +function waitForDispatch(panel, type, eventRepeat = 1) { + const store = panel.panelWin.view.mainFrame.store; + const actionType = constants[type]; + let count = 0; + + return Task.spawn(function* () { + info("Waiting for " + type + " to dispatch " + eventRepeat + " time(s)"); + while (count < eventRepeat) { + yield _afterDispatchDone(store, actionType); + count++; + info(type + " dispatched " + count + " time(s)"); + } + }); +} |