head.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. /* eslint no-unused-vars: [2, {"vars": "local", "args": "none"}] */
  5. /* import-globals-from ../../framework/test/shared-head.js */
  6. "use strict";
  7. // shared-head.js handles imports, constants, and utility functions
  8. Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js", this);
  9. const {DOMHelpers} = Cu.import("resource://devtools/client/shared/DOMHelpers.jsm", {});
  10. const {Hosts} = require("devtools/client/framework/toolbox-hosts");
  11. const TEST_URI_ROOT = "http://example.com/browser/devtools/client/shared/test/";
  12. const OPTIONS_VIEW_URL = TEST_URI_ROOT + "doc_options-view.xul";
  13. function catchFail(func) {
  14. return function () {
  15. try {
  16. return func.apply(null, arguments);
  17. } catch (ex) {
  18. ok(false, ex);
  19. console.error(ex);
  20. finish();
  21. throw ex;
  22. }
  23. };
  24. }
  25. /**
  26. * Polls a given function waiting for the given value.
  27. *
  28. * @param object options
  29. * Options object with the following properties:
  30. * - validator
  31. * A validator function that should return the expected value. This is
  32. * called every few milliseconds to check if the result is the expected
  33. * one. When the returned result is the expected one, then the |success|
  34. * function is called and polling stops. If |validator| never returns
  35. * the expected value, then polling timeouts after several tries and
  36. * a failure is recorded - the given |failure| function is invoked.
  37. * - success
  38. * A function called when the validator function returns the expected
  39. * value.
  40. * - failure
  41. * A function called if the validator function timeouts - fails to return
  42. * the expected value in the given time.
  43. * - name
  44. * Name of test. This is used to generate the success and failure
  45. * messages.
  46. * - timeout
  47. * Timeout for validator function, in milliseconds. Default is 5000 ms.
  48. * - value
  49. * The expected value. If this option is omitted then the |validator|
  50. * function must return a trueish value.
  51. * Each of the provided callback functions will receive two arguments:
  52. * the |options| object and the last value returned by |validator|.
  53. */
  54. function waitForValue(options) {
  55. let start = Date.now();
  56. let timeout = options.timeout || 5000;
  57. let lastValue;
  58. function wait(validatorFn, successFn, failureFn) {
  59. if ((Date.now() - start) > timeout) {
  60. // Log the failure.
  61. ok(false, "Timed out while waiting for: " + options.name);
  62. let expected = "value" in options ?
  63. "'" + options.value + "'" :
  64. "a trueish value";
  65. info("timeout info :: got '" + lastValue + "', expected " + expected);
  66. failureFn(options, lastValue);
  67. return;
  68. }
  69. lastValue = validatorFn(options, lastValue);
  70. let successful = "value" in options ?
  71. lastValue == options.value :
  72. lastValue;
  73. if (successful) {
  74. ok(true, options.name);
  75. successFn(options, lastValue);
  76. } else {
  77. setTimeout(() => {
  78. wait(validatorFn, successFn, failureFn);
  79. }, 100);
  80. }
  81. }
  82. wait(options.validator, options.success, options.failure);
  83. }
  84. function oneTimeObserve(name, callback) {
  85. return new Promise((resolve) => {
  86. let func = function () {
  87. Services.obs.removeObserver(func, name);
  88. if (callback) {
  89. callback();
  90. }
  91. resolve();
  92. };
  93. Services.obs.addObserver(func, name, false);
  94. });
  95. }
  96. let createHost =
  97. Task.async(function* (type = "bottom", src = "data:text/html;charset=utf-8,") {
  98. let host = new Hosts[type](gBrowser.selectedTab);
  99. let iframe = yield host.create();
  100. yield new Promise(resolve => {
  101. let domHelper = new DOMHelpers(iframe.contentWindow);
  102. iframe.setAttribute("src", src);
  103. domHelper.onceDOMReady(resolve);
  104. });
  105. return [host, iframe.contentWindow, iframe.contentDocument];
  106. });
  107. /**
  108. * Check the correctness of the data recorded in Telemetry after
  109. * loadTelemetryAndRecordLogs was called.
  110. */
  111. function checkTelemetryResults(Telemetry) {
  112. let result = Telemetry.prototype.telemetryInfo;
  113. for (let histId in result) {
  114. let value = result[histId];
  115. if (histId.endsWith("OPENED_COUNT")) {
  116. ok(value.length > 1, histId + " has more than one entry");
  117. let okay = value.every(function (element) {
  118. return element === true;
  119. });
  120. ok(okay, "All " + histId + " entries are === true");
  121. } else if (histId.endsWith("TIME_ACTIVE_SECONDS")) {
  122. ok(value.length > 1, histId + " has more than one entry");
  123. let okay = value.every(function (element) {
  124. return element > 0;
  125. });
  126. ok(okay, "All " + histId + " entries have time > 0");
  127. }
  128. }
  129. }
  130. /**
  131. * Open and close the toolbox in the current browser tab, several times, waiting
  132. * some amount of time in between.
  133. * @param {Number} nbOfTimes
  134. * @param {Number} usageTime in milliseconds
  135. * @param {String} toolId
  136. */
  137. function* openAndCloseToolbox(nbOfTimes, usageTime, toolId) {
  138. for (let i = 0; i < nbOfTimes; i++) {
  139. info("Opening toolbox " + (i + 1));
  140. let target = TargetFactory.forTab(gBrowser.selectedTab);
  141. yield gDevTools.showToolbox(target, toolId);
  142. // We use a timeout to check the toolbox's active time
  143. yield new Promise(resolve => setTimeout(resolve, usageTime));
  144. info("Closing toolbox " + (i + 1));
  145. yield gDevTools.closeToolbox(target);
  146. }
  147. }
  148. /**
  149. * Synthesize a profile for testing.
  150. */
  151. function synthesizeProfileForTest(samples) {
  152. const RecordingUtils = require("devtools/shared/performance/recording-utils");
  153. samples.unshift({
  154. time: 0,
  155. frames: []
  156. });
  157. let uniqueStacks = new RecordingUtils.UniqueStacks();
  158. return RecordingUtils.deflateThread({
  159. samples: samples,
  160. markers: []
  161. }, uniqueStacks);
  162. }
  163. /**
  164. * Waits until a predicate returns true.
  165. *
  166. * @param function predicate
  167. * Invoked once in a while until it returns true.
  168. * @param number interval [optional]
  169. * How often the predicate is invoked, in milliseconds.
  170. */
  171. function waitUntil(predicate, interval = 10) {
  172. if (predicate()) {
  173. return Promise.resolve(true);
  174. }
  175. return new Promise(resolve => {
  176. setTimeout(function () {
  177. waitUntil(predicate).then(() => resolve(true));
  178. }, interval);
  179. });
  180. }
  181. /**
  182. * Show the presets list sidebar in the cssfilter widget popup
  183. * @param {CSSFilterWidget} widget
  184. * @return {Promise}
  185. */
  186. function showFilterPopupPresets(widget) {
  187. let onRender = widget.once("render");
  188. widget._togglePresets();
  189. return onRender;
  190. }
  191. /**
  192. * Show presets list and create a sample preset with the name and value provided
  193. * @param {CSSFilterWidget} widget
  194. * @param {string} name
  195. * @param {string} value
  196. * @return {Promise}
  197. */
  198. let showFilterPopupPresetsAndCreatePreset =
  199. Task.async(function* (widget, name, value) {
  200. yield showFilterPopupPresets(widget);
  201. let onRender = widget.once("render");
  202. widget.setCssValue(value);
  203. yield onRender;
  204. let footer = widget.el.querySelector(".presets-list .footer");
  205. footer.querySelector("input").value = name;
  206. onRender = widget.once("render");
  207. widget._savePreset({
  208. preventDefault: () => {}
  209. });
  210. yield onRender;
  211. });
  212. /**
  213. * Utility function for testing CSS code samples that have been
  214. * syntax-highlighted.
  215. *
  216. * The CSS syntax highlighter emits a collection of DOM nodes that have
  217. * CSS classes applied to them. This function checks that those nodes
  218. * are what we expect.
  219. *
  220. * @param {array} expectedNodes
  221. * A representation of the nodes we expect to see.
  222. * Each node is an object containing two properties:
  223. * - type: a string which can be one of:
  224. * - text, comment, property-name, property-value
  225. * - text: the textContent of the node
  226. *
  227. * For example, given a string like this:
  228. * "<comment> The part we want </comment>\n this: is-the-part-we-want;"
  229. *
  230. * we would represent the expected output like this:
  231. * [{type: "comment", text: "<comment> The part we want </comment>"},
  232. * {type: "text", text: "\n"},
  233. * {type: "property-name", text: "this"},
  234. * {type: "text", text: ":"},
  235. * {type: "text", text: " "},
  236. * {type: "property-value", text: "is-the-part-we-want"},
  237. * {type: "text", text: ";"}];
  238. *
  239. * @param {Node} parent
  240. * The DOM node whose children are the output of the syntax highlighter.
  241. */
  242. function checkCssSyntaxHighlighterOutput(expectedNodes, parent) {
  243. /**
  244. * The classes applied to the output nodes by the syntax highlighter.
  245. * These must be same as the definitions in MdnDocsWidget.js.
  246. */
  247. const PROPERTY_NAME_COLOR = "theme-fg-color5";
  248. const PROPERTY_VALUE_COLOR = "theme-fg-color1";
  249. const COMMENT_COLOR = "theme-comment";
  250. /**
  251. * Check the type and content of a single node.
  252. */
  253. function checkNode(expected, actual) {
  254. ok(actual.textContent == expected.text,
  255. "Check that node has the expected textContent");
  256. info("Expected text content: [" + expected.text + "]");
  257. info("Actual text content: [" + actual.textContent + "]");
  258. info("Check that node has the expected type");
  259. if (expected.type == "text") {
  260. ok(actual.nodeType == 3, "Check that node is a text node");
  261. } else {
  262. ok(actual.tagName.toUpperCase() == "SPAN", "Check that node is a SPAN");
  263. }
  264. info("Check that node has the expected className");
  265. let expectedClassName = null;
  266. let actualClassName = null;
  267. switch (expected.type) {
  268. case "property-name":
  269. expectedClassName = PROPERTY_NAME_COLOR;
  270. break;
  271. case "property-value":
  272. expectedClassName = PROPERTY_VALUE_COLOR;
  273. break;
  274. case "comment":
  275. expectedClassName = COMMENT_COLOR;
  276. break;
  277. default:
  278. ok(!actual.classList, "No className expected");
  279. return;
  280. }
  281. ok(actual.classList.length == 1, "One className expected");
  282. actualClassName = actual.classList[0];
  283. ok(expectedClassName == actualClassName, "Check className value");
  284. info("Expected className: " + expectedClassName);
  285. info("Actual className: " + actualClassName);
  286. }
  287. info("Logging the actual nodes we have:");
  288. for (let j = 0; j < parent.childNodes.length; j++) {
  289. let n = parent.childNodes[j];
  290. info(j + " / " +
  291. "nodeType: " + n.nodeType + " / " +
  292. "textContent: " + n.textContent);
  293. }
  294. ok(parent.childNodes.length == parent.childNodes.length,
  295. "Check we have the expected number of nodes");
  296. info("Expected node count " + expectedNodes.length);
  297. info("Actual node count " + expectedNodes.length);
  298. for (let i = 0; i < expectedNodes.length; i++) {
  299. info("Check node " + i);
  300. checkNode(expectedNodes[i], parent.childNodes[i]);
  301. }
  302. }