diff options
Diffstat (limited to 'devtools/client/debugger/dist/search-worker.js')
-rw-r--r-- | devtools/client/debugger/dist/search-worker.js | 397 |
1 files changed, 397 insertions, 0 deletions
diff --git a/devtools/client/debugger/dist/search-worker.js b/devtools/client/debugger/dist/search-worker.js new file mode 100644 index 0000000000..d37027fd6c --- /dev/null +++ b/devtools/client/debugger/dist/search-worker.js @@ -0,0 +1,397 @@ +(function (factory) { + typeof define === 'function' && define.amd ? define(factory) : + factory(); +})((function () { 'use strict'; + + (function() { + const env = {"NODE_ENV":"production"}; + try { + if (process) { + process.env = Object.assign({}, process.env); + Object.assign(process.env, env); + return; + } + } catch (e) {} // avoid ReferenceError: process is not defined + globalThis.process = { env:env }; + })(); + + /* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ + + function isNode() { + try { + return process.release.name == "node"; + } catch (e) { + return false; + } + } + + function isNodeTest() { + return isNode() && process.env.NODE_ENV != "production"; + } + + let assert; + // TODO: try to enable these assertions on mochitest by also enabling it on: + // import flags from "devtools/shared/flags"; + // if (flags.testing) + // Unfortunately it throws a lot on mochitests... + + if (isNodeTest()) { + assert = function (condition, message) { + if (!condition) { + throw new Error(`Assertion failure: ${message}`); + } + }; + } else { + assert = function () {}; + } + var assert$1 = assert; + + /* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ + + function escapeRegExp(str) { + const reRegExpChar = /[\\^$.*+?()[\]{}|]/g; + return str.replace(reRegExpChar, "\\$&"); + } + + /** + * Ignore doing outline matches for less than 3 whitespaces + * + * @memberof utils/source-search + * @static + */ + function ignoreWhiteSpace(str) { + return /^\s{0,2}$/.test(str) ? "(?!\\s*.*)" : str; + } + + function wholeMatch(query, wholeWord) { + if (query === "" || !wholeWord) { + return query; + } + + return `\\b${query}\\b`; + } + + function buildFlags(caseSensitive, isGlobal) { + if (caseSensitive && isGlobal) { + return "g"; + } + + if (!caseSensitive && isGlobal) { + return "gi"; + } + + if (!caseSensitive && !isGlobal) { + return "i"; + } + + return null; + } + + function buildQuery( + originalQuery, + modifiers, + { isGlobal = false, ignoreSpaces = false } + ) { + const { caseSensitive, regexMatch, wholeWord } = modifiers; + + if (originalQuery === "") { + return new RegExp(originalQuery); + } + + // Remove the backslashes at the end of the query as it + // breaks the RegExp + let query = originalQuery.replace(/\\$/, ""); + + // If we don't want to do a regexMatch, we need to escape all regex related characters + // so they would actually match. + if (!regexMatch) { + query = escapeRegExp(query); + } + + // ignoreWhiteSpace might return a negative lookbehind, and in such case, we want it + // to be consumed as a RegExp part by the callsite, so this needs to be called after + // the regexp is escaped. + if (ignoreSpaces) { + query = ignoreWhiteSpace(query); + } + + query = wholeMatch(query, wholeWord); + const flags = buildFlags(caseSensitive, isGlobal); + + if (flags) { + return new RegExp(query, flags); + } + + return new RegExp(query); + } + + function getMatches(query, text, options) { + if (!query || !text || !options) { + return []; + } + const regexQuery = buildQuery(query, options, { + isGlobal: true, + }); + const matchedLocations = []; + const lines = text.split("\n"); + for (let i = 0; i < lines.length; i++) { + let singleMatch; + const line = lines[i]; + while ((singleMatch = regexQuery.exec(line)) !== null) { + // Flow doesn't understand the test above. + if (!singleMatch) { + throw new Error("no singleMatch"); + } + + matchedLocations.push({ + line: i, + ch: singleMatch.index, + match: singleMatch[0], + }); + + // When the match is an empty string the regexQuery.lastIndex will not + // change resulting in an infinite loop so we need to check for this and + // increment it manually in that case. See issue #7023 + if (singleMatch[0] === "") { + assert$1( + !regexQuery.unicode, + "lastIndex++ can cause issues in unicode mode" + ); + regexQuery.lastIndex++; + } + } + } + return matchedLocations; + } + + function findSourceMatches(content, queryText, options) { + if (queryText == "") { + return []; + } + + const text = content.value; + const lines = text.split("\n"); + + return getMatches(queryText, text, options).map(({ line, ch, match }) => { + const { value, matchIndex } = truncateLine(lines[line], ch); + return { + line: line + 1, + column: ch, + + matchIndex, + match, + value, + }; + }); + } + + // This is used to find start of a word, so that cropped string look nice + const startRegex = /([ !@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?])/g; + // Similarly, find + const endRegex = new RegExp( + [ + "([ !@#$%^&*()_+-=[]{};':\"\\|,.<>/?])", + '[^ !@#$%^&*()_+-=[]{};\':"\\|,.<>/?]*$"/', + ].join("") + ); + // For texts over 100 characters this truncates the text (for display) + // around the context of the matched text. + function truncateLine(text, column) { + if (text.length < 100) { + return { + matchIndex: column, + value: text, + }; + } + + // Initially take 40 chars left to the match + const offset = Math.max(column - 40, 0); + // 400 characters should be enough to figure out the context of the match + const truncStr = text.slice(offset, column + 400); + let start = truncStr.search(startRegex); + let end = truncStr.search(endRegex); + + if (start > column) { + // No word separator found before the match, so we take all characters + // before the match + start = -1; + } + if (end < column) { + end = truncStr.length; + } + const value = truncStr.slice(start + 1, end); + + return { + matchIndex: column - start - offset - 1, + value, + }; + } + + var workerUtilsExports = {}; + var workerUtils = { + get exports(){ return workerUtilsExports; }, + set exports(v){ workerUtilsExports = v; }, + }; + + (function (module) { + + class WorkerDispatcher { + #msgId = 1; + #worker = null; + // Map of message ids -> promise resolution functions, for dispatching worker responses + #pendingCalls = new Map(); + #url = ""; + + constructor(url) { + this.#url = url; + } + + start() { + // When running in debugger jest test, we don't have access to ChromeWorker + if (typeof ChromeWorker == "function") { + this.#worker = new ChromeWorker(this.#url); + } else { + this.#worker = new Worker(this.#url); + } + this.#worker.onerror = err => { + console.error(`Error in worker ${this.#url}`, err.message); + }; + this.#worker.addEventListener("message", this.#onMessage); + } + + stop() { + if (!this.#worker) { + return; + } + + this.#worker.removeEventListener("message", this.#onMessage); + this.#worker.terminate(); + this.#worker = null; + this.#pendingCalls.clear(); + } + + task(method, { queue = false } = {}) { + const calls = []; + const push = args => { + return new Promise((resolve, reject) => { + if (queue && calls.length === 0) { + Promise.resolve().then(flush); + } + + calls.push({ args, resolve, reject }); + + if (!queue) { + flush(); + } + }); + }; + + const flush = () => { + const items = calls.slice(); + calls.length = 0; + + if (!this.#worker) { + this.start(); + } + + const id = this.#msgId++; + this.#worker.postMessage({ + id, + method, + calls: items.map(item => item.args), + }); + + this.#pendingCalls.set(id, items); + }; + + return (...args) => push(args); + } + + invoke(method, ...args) { + return this.task(method)(...args); + } + + #onMessage = ({ data: result }) => { + const items = this.#pendingCalls.get(result.id); + this.#pendingCalls.delete(result.id); + if (!items) { + return; + } + + if (!this.#worker) { + return; + } + + result.results.forEach((resultData, i) => { + const { resolve, reject } = items[i]; + + if (resultData.error) { + const err = new Error(resultData.message); + err.metadata = resultData.metadata; + reject(err); + } else { + resolve(resultData.response); + } + }); + }; + } + + function workerHandler(publicInterface) { + return function (msg) { + const { id, method, calls } = msg.data; + + Promise.all( + calls.map(args => { + try { + const response = publicInterface[method].apply(undefined, args); + if (response instanceof Promise) { + return response.then( + val => ({ response: val }), + err => asErrorMessage(err) + ); + } + return { response }; + } catch (error) { + return asErrorMessage(error); + } + }) + ).then(results => { + globalThis.postMessage({ id, results }); + }); + }; + } + + function asErrorMessage(error) { + if (typeof error === "object" && error && "message" in error) { + // Error can't be sent via postMessage, so be sure to convert to + // string. + return { + error: true, + message: error.message, + metadata: error.metadata, + }; + } + + return { + error: true, + message: error == null ? error : error.toString(), + metadata: undefined, + }; + } + + // Might be loaded within a worker thread where `module` isn't available. + { + module.exports = { + WorkerDispatcher, + workerHandler, + }; + } + } (workerUtils)); + + self.onmessage = workerUtilsExports.workerHandler({ getMatches, findSourceMatches }); + +})); |