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