/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ /* eslint no-unused-vars: [2, {"vars": "local", "args": "none"}] */ "use strict"; // shared-head.js handles imports, constants, and utility functions Services.scriptloader.loadSubScript( "chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js", this ); // DOM panel actions. const constants = require("resource://devtools/client/dom/content/constants.js"); // 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 */ async function addTestTab(url) { info("Adding a new test tab with URL: '" + url + "'"); const tab = await addTab(url); // Select the DOM panel and wait till it's initialized. const panel = await initDOMPanel(tab); // FETCH_PROPERTIES should be fired during the call to initDOMPanel // But note that this behavior changed during a change in webconsole // initialization. So this might be racy. const doc = panel.panelWin.document; const nodes = [...doc.querySelectorAll(".treeLabel")]; ok(!!nodes.length, "The DOM panel is already populated"); return { tab, browser: tab.linkedBrowser, panel, }; } /** * Open the DOM panel for the given tab. * * @param {Element} 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. */ async function initDOMPanel(tab) { tab = tab || gBrowser.selectedTab; const toolbox = await gDevTools.showToolboxForTab(tab, { toolId: "dom" }); const panel = toolbox.getCurrentPanel(); return 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) { const doc = panel.panelWin.document; const labels = [...doc.querySelectorAll(".treeLabel")]; const label = labels.find(node => node.textContent == text); return label ? label.closest(".treeRow") : null; } /** * Returns tree row with specified index. */ function getRowByIndex(panel, id) { const doc = panel.panelWin.document; const labels = [...doc.querySelectorAll(".treeLabel")]; const label = labels.find((node, i) => i == id); 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; const result = []; const doc = panel.panelWin.document; const 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) { const 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) { const row = getRowByLabel(panel, labelText); return synthesizeMouseClickSoon(panel, row).then(() => { // Wait till children (properties) are fetched // from the backend. const store = getReduxStoreFromPanel(panel); return waitForDispatch(store, "FETCH_PROPERTIES"); }); } function refreshPanel(panel) { const doc = panel.panelWin.document; const button = doc.querySelector("#dom-refresh-button"); return synthesizeMouseClickSoon(panel, button).then(() => { // Wait till children (properties) are fetched // from the backend. const store = getReduxStoreFromPanel(panel); return waitForDispatch(store, "FETCH_PROPERTIES"); }); } function getReduxStoreFromPanel(panel) { return panel.panelWin.view.mainFrame.store; }