head.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  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. /* import-globals-from ../../framework/test/shared-head.js */
  5. "use strict";
  6. const FRAME_SCRIPT_UTILS_URL =
  7. "chrome://devtools/content/shared/frame-script-utils.js";
  8. // shared-head.js handles imports, constants, and utility functions
  9. Services.scriptloader.loadSubScript(
  10. "chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js", this);
  11. // DOM panel actions.
  12. const constants = require("devtools/client/dom/content/constants");
  13. // Uncomment this pref to dump all devtools emitted events to the console.
  14. // Services.prefs.setBoolPref("devtools.dom.enabled", true);
  15. // Enable the DOM panel
  16. Services.prefs.setBoolPref("devtools.dom.enabled", true);
  17. registerCleanupFunction(() => {
  18. info("finish() was called, cleaning up...");
  19. Services.prefs.clearUserPref("devtools.dump.emit");
  20. Services.prefs.clearUserPref("devtools.dom.enabled");
  21. });
  22. /**
  23. * Add a new test tab in the browser and load the given url.
  24. * @param {String} url
  25. * The url to be loaded in the new tab
  26. * @return a promise that resolves to the tab object when
  27. * the url is loaded
  28. */
  29. function addTestTab(url) {
  30. info("Adding a new test tab with URL: '" + url + "'");
  31. return new Promise(resolve => {
  32. addTab(url).then(tab => {
  33. // Load devtools/shared/frame-script-utils.js
  34. getFrameScript();
  35. // Select the DOM panel and wait till it's initialized.
  36. initDOMPanel(tab).then(panel => {
  37. waitForDispatch(panel, "FETCH_PROPERTIES").then(() => {
  38. resolve({
  39. tab: tab,
  40. browser: tab.linkedBrowser,
  41. panel: panel
  42. });
  43. });
  44. });
  45. });
  46. });
  47. }
  48. /**
  49. * Open the DOM panel for the given tab.
  50. *
  51. * @param {nsIDOMElement} tab
  52. * Optional tab element for which you want open the DOM panel.
  53. * The default tab is taken from the global variable |tab|.
  54. * @return a promise that is resolved once the web console is open.
  55. */
  56. function initDOMPanel(tab) {
  57. return new Promise(resolve => {
  58. let target = TargetFactory.forTab(tab || gBrowser.selectedTab);
  59. gDevTools.showToolbox(target, "dom").then(toolbox => {
  60. let panel = toolbox.getCurrentPanel();
  61. resolve(panel);
  62. });
  63. });
  64. }
  65. /**
  66. * Synthesize asynchronous click event (with clean stack trace).
  67. */
  68. function synthesizeMouseClickSoon(panel, element) {
  69. return new Promise(resolve => {
  70. executeSoon(() => {
  71. EventUtils.synthesizeMouse(element, 2, 2, {}, panel.panelWin);
  72. resolve();
  73. });
  74. });
  75. }
  76. /**
  77. * Returns tree row with specified label.
  78. */
  79. function getRowByLabel(panel, text) {
  80. let doc = panel.panelWin.document;
  81. let labels = [...doc.querySelectorAll(".treeLabel")];
  82. let label = labels.find(node => node.textContent == text);
  83. return label ? label.closest(".treeRow") : null;
  84. }
  85. /**
  86. * Returns the children (tree row text) of the specified object name as an
  87. * array.
  88. */
  89. function getAllRowsForLabel(panel, text) {
  90. let rootObjectLevel;
  91. let node;
  92. let result = [];
  93. let doc = panel.panelWin.document;
  94. let nodes = [...doc.querySelectorAll(".treeLabel")];
  95. // Find the label (object name) for which we want the children. We remove
  96. // nodes from the start of the array until we reach the property. The children
  97. // are then at the start of the array.
  98. while (true) {
  99. node = nodes.shift();
  100. if (!node || node.textContent === text) {
  101. rootObjectLevel = node.getAttribute("data-level");
  102. break;
  103. }
  104. }
  105. // Return an empty array if the node is not found.
  106. if (!node) {
  107. return result;
  108. }
  109. // Now get the children.
  110. for (node of nodes) {
  111. let level = node.getAttribute("data-level");
  112. if (level > rootObjectLevel) {
  113. result.push({
  114. name: normalizeTreeValue(node.textContent),
  115. value: normalizeTreeValue(node.parentNode.nextElementSibling.textContent)
  116. });
  117. } else {
  118. break;
  119. }
  120. }
  121. return result;
  122. }
  123. /**
  124. * Strings in the tree are in the form ""a"" and numbers in the form "1". We
  125. * normalize these values by converting ""a"" to "a" and "1" to 1.
  126. *
  127. * @param {String} value
  128. * The value to normalize.
  129. * @return {String|Number}
  130. * The normalized value.
  131. */
  132. function normalizeTreeValue(value) {
  133. if (value === `""`) {
  134. return "";
  135. }
  136. if (value.startsWith(`"`) && value.endsWith(`"`)) {
  137. return value.substr(1, value.length - 2);
  138. }
  139. if (isFinite(value) && parseInt(value, 10) == value) {
  140. return parseInt(value, 10);
  141. }
  142. return value;
  143. }
  144. /**
  145. * Expands elements with given label and waits till
  146. * children are received from the backend.
  147. */
  148. function expandRow(panel, labelText) {
  149. let row = getRowByLabel(panel, labelText);
  150. return synthesizeMouseClickSoon(panel, row).then(() => {
  151. // Wait till children (properties) are fetched
  152. // from the backend.
  153. return waitForDispatch(panel, "FETCH_PROPERTIES");
  154. });
  155. }
  156. function evaluateJSAsync(panel, expression) {
  157. return new Promise(resolve => {
  158. panel.target.activeConsole.evaluateJSAsync(expression, res => {
  159. resolve(res);
  160. });
  161. });
  162. }
  163. function refreshPanel(panel) {
  164. let doc = panel.panelWin.document;
  165. let button = doc.querySelector(".btn.refresh");
  166. return synthesizeMouseClickSoon(panel, button).then(() => {
  167. // Wait till children (properties) are fetched
  168. // from the backend.
  169. return waitForDispatch(panel, "FETCH_PROPERTIES");
  170. });
  171. }
  172. // Redux related API, use from shared location
  173. // as soon as bug 1261076 is fixed.
  174. // Wait until an action of `type` is dispatched. If it's part of an
  175. // async operation, wait until the `status` field is "done" or "error"
  176. function _afterDispatchDone(store, type) {
  177. return new Promise(resolve => {
  178. store.dispatch({
  179. // Normally we would use `services.WAIT_UNTIL`, but use the
  180. // internal name here so tests aren't forced to always pass it
  181. // in
  182. type: "@@service/waitUntil",
  183. predicate: action => {
  184. if (action.type === type) {
  185. return action.status ?
  186. (action.status === "end" || action.status === "error") :
  187. true;
  188. }
  189. return false;
  190. },
  191. run: (dispatch, getState, action) => {
  192. resolve(action);
  193. }
  194. });
  195. });
  196. }
  197. function waitForDispatch(panel, type, eventRepeat = 1) {
  198. const store = panel.panelWin.view.mainFrame.store;
  199. const actionType = constants[type];
  200. let count = 0;
  201. return Task.spawn(function* () {
  202. info("Waiting for " + type + " to dispatch " + eventRepeat + " time(s)");
  203. while (count < eventRepeat) {
  204. yield _afterDispatchDone(store, actionType);
  205. count++;
  206. info(type + " dispatched " + count + " time(s)");
  207. }
  208. });
  209. }