head.js (5476B)
1 /* Any copyright is dedicated to the Public Domain. 2 * http://creativecommons.org/publicdomain/zero/1.0/ */ 3 /* eslint no-unused-vars: [2, {"vars": "local", "args": "none"}] */ 4 5 "use strict"; 6 7 // shared-head.js handles imports, constants, and utility functions 8 Services.scriptloader.loadSubScript( 9 "chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js", 10 this 11 ); 12 13 // DOM panel actions. 14 const constants = require("resource://devtools/client/dom/content/constants.js"); 15 16 // Uncomment this pref to dump all devtools emitted events to the console. 17 // Services.prefs.setBoolPref("devtools.dump.emit", true); 18 19 /** 20 * Add a new test tab in the browser and load the given url. 21 * 22 * @param {string} url 23 * The url to be loaded in the new tab 24 * @return a promise that resolves to the tab object when 25 * the url is loaded 26 */ 27 async function addTestTab(url) { 28 info("Adding a new test tab with URL: '" + url + "'"); 29 30 const tab = await addTab(url); 31 32 // Select the DOM panel and wait till it's initialized. 33 const panel = await initDOMPanel(tab); 34 35 // FETCH_PROPERTIES should be fired during the call to initDOMPanel 36 // But note that this behavior changed during a change in webconsole 37 // initialization. So this might be racy. 38 const doc = panel.panelWin.document; 39 const nodes = [...doc.querySelectorAll(".treeLabel")]; 40 ok(!!nodes.length, "The DOM panel is already populated"); 41 42 return { 43 tab, 44 browser: tab.linkedBrowser, 45 panel, 46 }; 47 } 48 49 /** 50 * Open the DOM panel for the given tab. 51 * 52 * @param {Element} tab 53 * Optional tab element for which you want open the DOM panel. 54 * The default tab is taken from the global variable |tab|. 55 * @return a promise that is resolved once the web console is open. 56 */ 57 async function initDOMPanel(tab) { 58 tab = tab || gBrowser.selectedTab; 59 const toolbox = await gDevTools.showToolboxForTab(tab, { toolId: "dom" }); 60 const panel = toolbox.getCurrentPanel(); 61 return panel; 62 } 63 64 /** 65 * Synthesize asynchronous click event (with clean stack trace). 66 */ 67 function synthesizeMouseClickSoon(panel, element) { 68 return new Promise(resolve => { 69 executeSoon(() => { 70 EventUtils.synthesizeMouse(element, 2, 2, {}, panel.panelWin); 71 resolve(); 72 }); 73 }); 74 } 75 76 /** 77 * Returns tree row with specified label. 78 */ 79 function getRowByLabel(panel, text) { 80 const doc = panel.panelWin.document; 81 const labels = [...doc.querySelectorAll(".treeLabel")]; 82 const label = labels.find(node => node.textContent == text); 83 return label ? label.closest(".treeRow") : null; 84 } 85 86 /** 87 * Returns tree row with specified index. 88 */ 89 function getRowByIndex(panel, id) { 90 const doc = panel.panelWin.document; 91 const labels = [...doc.querySelectorAll(".treeLabel")]; 92 const label = labels.find((node, i) => i == id); 93 return label ? label.closest(".treeRow") : null; 94 } 95 96 /** 97 * Returns the children (tree row text) of the specified object name as an 98 * array. 99 */ 100 function getAllRowsForLabel(panel, text) { 101 let rootObjectLevel; 102 let node; 103 const result = []; 104 const doc = panel.panelWin.document; 105 const nodes = [...doc.querySelectorAll(".treeLabel")]; 106 107 // Find the label (object name) for which we want the children. We remove 108 // nodes from the start of the array until we reach the property. The children 109 // are then at the start of the array. 110 while (true) { 111 node = nodes.shift(); 112 113 if (!node || node.textContent === text) { 114 rootObjectLevel = node.getAttribute("data-level"); 115 break; 116 } 117 } 118 119 // Return an empty array if the node is not found. 120 if (!node) { 121 return result; 122 } 123 124 // Now get the children. 125 for (node of nodes) { 126 const level = node.getAttribute("data-level"); 127 128 if (level > rootObjectLevel) { 129 result.push({ 130 name: normalizeTreeValue(node.textContent), 131 value: normalizeTreeValue( 132 node.parentNode.nextElementSibling.textContent 133 ), 134 }); 135 } else { 136 break; 137 } 138 } 139 140 return result; 141 } 142 143 /** 144 * Strings in the tree are in the form ""a"" and numbers in the form "1". We 145 * normalize these values by converting ""a"" to "a" and "1" to 1. 146 * 147 * @param {string} value 148 * The value to normalize. 149 * @return {string | number} 150 * The normalized value. 151 */ 152 function normalizeTreeValue(value) { 153 if (value === `""`) { 154 return ""; 155 } 156 if (value.startsWith(`"`) && value.endsWith(`"`)) { 157 return value.substr(1, value.length - 2); 158 } 159 if (isFinite(value) && parseInt(value, 10) == value) { 160 return parseInt(value, 10); 161 } 162 163 return value; 164 } 165 166 /** 167 * Expands elements with given label and waits till 168 * children are received from the backend. 169 */ 170 function expandRow(panel, labelText) { 171 const row = getRowByLabel(panel, labelText); 172 return synthesizeMouseClickSoon(panel, row).then(() => { 173 // Wait till children (properties) are fetched 174 // from the backend. 175 const store = getReduxStoreFromPanel(panel); 176 return waitForDispatch(store, "FETCH_PROPERTIES"); 177 }); 178 } 179 180 function refreshPanel(panel) { 181 const doc = panel.panelWin.document; 182 const button = doc.querySelector("#dom-refresh-button"); 183 return synthesizeMouseClickSoon(panel, button).then(() => { 184 // Wait till children (properties) are fetched 185 // from the backend. 186 const store = getReduxStoreFromPanel(panel); 187 return waitForDispatch(store, "FETCH_PROPERTIES"); 188 }); 189 } 190 191 function getReduxStoreFromPanel(panel) { 192 return panel.panelWin.view.mainFrame.store; 193 }