worker.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  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. "use strict";
  5. /* global ChromeWorker */
  6. (function (factory) {
  7. if (this.module && module.id.indexOf("worker") >= 0) {
  8. // require
  9. const { Cc, Ci, Cu, ChromeWorker } = require("chrome");
  10. const dumpn = require("devtools/shared/DevToolsUtils").dumpn;
  11. factory.call(this, require, exports, module, { Cc, Ci, Cu }, ChromeWorker, dumpn);
  12. } else {
  13. // Cu.import
  14. const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
  15. const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
  16. this.isWorker = false;
  17. this.Promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
  18. this.console = Cu.import("resource://gre/modules/Console.jsm", {}).console;
  19. factory.call(
  20. this, require, this, { exports: this },
  21. { Cc, Ci, Cu }, ChromeWorker, null
  22. );
  23. this.EXPORTED_SYMBOLS = ["DevToolsWorker"];
  24. }
  25. }).call(this, function (require, exports, module, { Ci, Cc }, ChromeWorker, dumpn) {
  26. let MESSAGE_COUNTER = 0;
  27. /**
  28. * Creates a wrapper around a ChromeWorker, providing easy
  29. * communication to offload demanding tasks. The corresponding URL
  30. * must implement the interface provided by `devtools/shared/worker/helper`.
  31. *
  32. * @see `./devtools/client/shared/widgets/GraphsWorker.js`
  33. *
  34. * @param {string} url
  35. * The URL of the worker.
  36. * @param Object opts
  37. * An option with the following optional fields:
  38. * - name: a name that will be printed with logs
  39. * - verbose: log incoming and outgoing messages
  40. */
  41. function DevToolsWorker(url, opts) {
  42. opts = opts || {};
  43. this._worker = new ChromeWorker(url);
  44. this._verbose = opts.verbose;
  45. this._name = opts.name;
  46. this._worker.addEventListener("error", this.onError, false);
  47. }
  48. exports.DevToolsWorker = DevToolsWorker;
  49. /**
  50. * Performs the given task in a chrome worker, passing in data.
  51. * Returns a promise that resolves when the task is completed, resulting in
  52. * the return value of the task.
  53. *
  54. * @param {string} task
  55. * The name of the task to execute in the worker.
  56. * @param {any} data
  57. * Data to be passed into the task implemented by the worker.
  58. * @return {Promise}
  59. */
  60. DevToolsWorker.prototype.performTask = function (task, data) {
  61. if (this._destroyed) {
  62. return Promise.reject("Cannot call performTask on a destroyed DevToolsWorker");
  63. }
  64. let worker = this._worker;
  65. let id = ++MESSAGE_COUNTER;
  66. let payload = { task, id, data };
  67. if (this._verbose && dumpn) {
  68. dumpn("Sending message to worker" +
  69. (this._name ? (" (" + this._name + ")") : "") +
  70. ": " +
  71. JSON.stringify(payload, null, 2));
  72. }
  73. worker.postMessage(payload);
  74. return new Promise((resolve, reject) => {
  75. let listener = ({ data: result }) => {
  76. if (this._verbose && dumpn) {
  77. dumpn("Received message from worker" +
  78. (this._name ? (" (" + this._name + ")") : "") +
  79. ": " +
  80. JSON.stringify(result, null, 2));
  81. }
  82. if (result.id !== id) {
  83. return;
  84. }
  85. worker.removeEventListener("message", listener);
  86. if (result.error) {
  87. reject(result.error);
  88. } else {
  89. resolve(result.response);
  90. }
  91. };
  92. worker.addEventListener("message", listener);
  93. });
  94. };
  95. /**
  96. * Terminates the underlying worker. Use when no longer needing the worker.
  97. */
  98. DevToolsWorker.prototype.destroy = function () {
  99. this._worker.terminate();
  100. this._worker = null;
  101. this._destroyed = true;
  102. };
  103. DevToolsWorker.prototype.onError = function ({ message, filename, lineno }) {
  104. dump(new Error(message + " @ " + filename + ":" + lineno) + "\n");
  105. };
  106. /**
  107. * Takes a function and returns a Worker-wrapped version of the same function.
  108. * Returns a promise upon resolution.
  109. * @see `./devtools/shared/shared/tests/browser/browser_devtools-worker-03.js
  110. *
  111. * ⚠ This should only be used for tests or A/B testing performance ⚠
  112. *
  113. * The original function must:
  114. *
  115. * Be a pure function, that is, not use any variables not declared within the
  116. * function, or its arguments.
  117. *
  118. * Return a value or a promise.
  119. *
  120. * Note any state change in the worker will not affect the callee's context.
  121. *
  122. * @param {function} fn
  123. * @return {function}
  124. */
  125. function workerify(fn) {
  126. console.warn("`workerify` should only be used in tests or measuring performance. " +
  127. "This creates an object URL on the browser window, and should not be " +
  128. "used in production.");
  129. // Fetch via window/utils here as we don't want to include
  130. // this module normally.
  131. let { getMostRecentBrowserWindow } = require("sdk/window/utils");
  132. let { URL, Blob } = getMostRecentBrowserWindow();
  133. let stringifiedFn = createWorkerString(fn);
  134. let blob = new Blob([stringifiedFn]);
  135. let url = URL.createObjectURL(blob);
  136. let worker = new DevToolsWorker(url);
  137. let wrapperFn = data => worker.performTask("workerifiedTask", data);
  138. wrapperFn.destroy = function () {
  139. URL.revokeObjectURL(url);
  140. worker.destroy();
  141. };
  142. return wrapperFn;
  143. }
  144. exports.workerify = workerify;
  145. /**
  146. * Takes a function, and stringifies it, attaching the worker-helper.js
  147. * boilerplate hooks.
  148. */
  149. function createWorkerString(fn) {
  150. return `importScripts("resource://gre/modules/workers/require.js");
  151. const { createTask } = require("resource://devtools/shared/worker/helper.js");
  152. createTask(self, "workerifiedTask", ${fn.toString()});`;
  153. }
  154. });