Source code
Revision control
Copy as Markdown
Other Tools
(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
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
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 workerUtils = {exports: {}};
var hasRequiredWorkerUtils;
function requireWorkerUtils () {
if (hasRequiredWorkerUtils) return workerUtils.exports;
hasRequiredWorkerUtils = 1;
(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 +
(error.stack ? "\nStack in the worker:" + error.stack : ""),
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));
return workerUtils.exports;
}
var workerUtilsExports = requireWorkerUtils();
self.onmessage = workerUtilsExports.workerHandler({ getMatches, findSourceMatches });
}));