search-worker.js (11372B)
1 (function (factory) { 2 typeof define === 'function' && define.amd ? define(factory) : 3 factory(); 4 })((function () { 'use strict'; 5 6 (function() { 7 const env = {"NODE_ENV":"production"}; 8 try { 9 if (process) { 10 process.env = Object.assign({}, process.env); 11 Object.assign(process.env, env); 12 return; 13 } 14 } catch (e) {} // avoid ReferenceError: process is not defined 15 globalThis.process = { env:env }; 16 })(); 17 18 /* This Source Code Form is subject to the terms of the Mozilla Public 19 * License, v. 2.0. If a copy of the MPL was not distributed with this 20 * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ 21 22 function isNode() { 23 try { 24 return process.release.name == "node"; 25 } catch (e) { 26 return false; 27 } 28 } 29 30 function isNodeTest() { 31 return isNode() && process.env.NODE_ENV != "production"; 32 } 33 34 let assert; 35 // TODO: try to enable these assertions on mochitest by also enabling it on: 36 // import flags from "devtools/shared/flags"; 37 // if (flags.testing) 38 // Unfortunately it throws a lot on mochitests... 39 40 if (isNodeTest()) { 41 assert = function (condition, message) { 42 if (!condition) { 43 throw new Error(`Assertion failure: ${message}`); 44 } 45 }; 46 } else { 47 assert = function () {}; 48 } 49 var assert$1 = assert; 50 51 /* This Source Code Form is subject to the terms of the Mozilla Public 52 * License, v. 2.0. If a copy of the MPL was not distributed with this 53 * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ 54 55 function escapeRegExp(str) { 56 const reRegExpChar = /[\\^$.*+?()[\]{}|]/g; 57 return str.replace(reRegExpChar, "\\$&"); 58 } 59 60 /** 61 * Ignore doing outline matches for less than 3 whitespaces 62 * 63 * @memberof utils/source-search 64 * @static 65 */ 66 function ignoreWhiteSpace(str) { 67 return /^\s{0,2}$/.test(str) ? "(?!\\s*.*)" : str; 68 } 69 70 function wholeMatch(query, wholeWord) { 71 if (query === "" || !wholeWord) { 72 return query; 73 } 74 75 return `\\b${query}\\b`; 76 } 77 78 function buildFlags(caseSensitive, isGlobal) { 79 if (caseSensitive && isGlobal) { 80 return "g"; 81 } 82 83 if (!caseSensitive && isGlobal) { 84 return "gi"; 85 } 86 87 if (!caseSensitive && !isGlobal) { 88 return "i"; 89 } 90 91 return null; 92 } 93 94 function buildQuery( 95 originalQuery, 96 modifiers, 97 { isGlobal = false, ignoreSpaces = false } 98 ) { 99 const { caseSensitive, regexMatch, wholeWord } = modifiers; 100 101 if (originalQuery === "") { 102 return new RegExp(originalQuery); 103 } 104 105 // Remove the backslashes at the end of the query as it 106 // breaks the RegExp 107 let query = originalQuery.replace(/\\$/, ""); 108 109 // If we don't want to do a regexMatch, we need to escape all regex related characters 110 // so they would actually match. 111 if (!regexMatch) { 112 query = escapeRegExp(query); 113 } 114 115 // ignoreWhiteSpace might return a negative lookbehind, and in such case, we want it 116 // to be consumed as a RegExp part by the callsite, so this needs to be called after 117 // the regexp is escaped. 118 if (ignoreSpaces) { 119 query = ignoreWhiteSpace(query); 120 } 121 122 query = wholeMatch(query, wholeWord); 123 const flags = buildFlags(caseSensitive, isGlobal); 124 125 if (flags) { 126 return new RegExp(query, flags); 127 } 128 129 return new RegExp(query); 130 } 131 132 function getMatches(query, text, options) { 133 if (!query || !text || !options) { 134 return []; 135 } 136 const regexQuery = buildQuery(query, options, { 137 isGlobal: true, 138 }); 139 const matchedLocations = []; 140 const lines = text.split("\n"); 141 for (let i = 0; i < lines.length; i++) { 142 let singleMatch; 143 const line = lines[i]; 144 while ((singleMatch = regexQuery.exec(line)) !== null) { 145 // Flow doesn't understand the test above. 146 if (!singleMatch) { 147 throw new Error("no singleMatch"); 148 } 149 150 matchedLocations.push({ 151 line: i, 152 ch: singleMatch.index, 153 match: singleMatch[0], 154 }); 155 156 // When the match is an empty string the regexQuery.lastIndex will not 157 // change resulting in an infinite loop so we need to check for this and 158 // increment it manually in that case. See issue #7023 159 if (singleMatch[0] === "") { 160 assert$1( 161 !regexQuery.unicode, 162 "lastIndex++ can cause issues in unicode mode" 163 ); 164 regexQuery.lastIndex++; 165 } 166 } 167 } 168 return matchedLocations; 169 } 170 171 function findSourceMatches(content, queryText, options) { 172 if (queryText == "") { 173 return []; 174 } 175 176 const text = content.value; 177 const lines = text.split("\n"); 178 179 return getMatches(queryText, text, options).map(({ line, ch, match }) => { 180 const { value, matchIndex } = truncateLine(lines[line], ch); 181 return { 182 line: line + 1, 183 column: ch, 184 185 matchIndex, 186 match, 187 value, 188 }; 189 }); 190 } 191 192 // This is used to find start of a word, so that cropped string look nice 193 const startRegex = /([ !@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?])/g; 194 // Similarly, find 195 const endRegex = new RegExp( 196 [ 197 "([ !@#$%^&*()_+-=[]{};':\"\\|,.<>/?])", 198 '[^ !@#$%^&*()_+-=[]{};\':"\\|,.<>/?]*$"/', 199 ].join("") 200 ); 201 // For texts over 100 characters this truncates the text (for display) 202 // around the context of the matched text. 203 function truncateLine(text, column) { 204 if (text.length < 100) { 205 return { 206 matchIndex: column, 207 value: text, 208 }; 209 } 210 211 // Initially take 40 chars left to the match 212 const offset = Math.max(column - 40, 0); 213 // 400 characters should be enough to figure out the context of the match 214 const truncStr = text.slice(offset, column + 400); 215 let start = truncStr.search(startRegex); 216 let end = truncStr.search(endRegex); 217 218 if (start > column) { 219 // No word separator found before the match, so we take all characters 220 // before the match 221 start = -1; 222 } 223 if (end < column) { 224 end = truncStr.length; 225 } 226 const value = truncStr.slice(start + 1, end); 227 228 return { 229 matchIndex: column - start - offset - 1, 230 value, 231 }; 232 } 233 234 var workerUtils = {exports: {}}; 235 236 var hasRequiredWorkerUtils; 237 238 function requireWorkerUtils () { 239 if (hasRequiredWorkerUtils) return workerUtils.exports; 240 hasRequiredWorkerUtils = 1; 241 (function (module) { 242 243 class WorkerDispatcher { 244 #msgId = 1; 245 #worker = null; 246 // Map of message ids -> promise resolution functions, for dispatching worker responses 247 #pendingCalls = new Map(); 248 #url = ""; 249 250 constructor(url) { 251 this.#url = url; 252 } 253 254 start() { 255 // When running in debugger jest test, we don't have access to ChromeWorker 256 if (typeof ChromeWorker == "function") { 257 this.#worker = new ChromeWorker(this.#url); 258 } else { 259 this.#worker = new Worker(this.#url); 260 } 261 this.#worker.onerror = err => { 262 console.error(`Error in worker ${this.#url}`, err.message); 263 }; 264 this.#worker.addEventListener("message", this.#onMessage); 265 } 266 267 stop() { 268 if (!this.#worker) { 269 return; 270 } 271 272 this.#worker.removeEventListener("message", this.#onMessage); 273 this.#worker.terminate(); 274 this.#worker = null; 275 this.#pendingCalls.clear(); 276 } 277 278 task(method, { queue = false } = {}) { 279 const calls = []; 280 const push = args => { 281 return new Promise((resolve, reject) => { 282 if (queue && calls.length === 0) { 283 Promise.resolve().then(flush); 284 } 285 286 calls.push({ args, resolve, reject }); 287 288 if (!queue) { 289 flush(); 290 } 291 }); 292 }; 293 294 const flush = () => { 295 const items = calls.slice(); 296 calls.length = 0; 297 298 if (!this.#worker) { 299 this.start(); 300 } 301 302 const id = this.#msgId++; 303 this.#worker.postMessage({ 304 id, 305 method, 306 calls: items.map(item => item.args), 307 }); 308 309 this.#pendingCalls.set(id, items); 310 }; 311 312 return (...args) => push(args); 313 } 314 315 invoke(method, ...args) { 316 return this.task(method)(...args); 317 } 318 319 #onMessage = ({ data: result }) => { 320 const items = this.#pendingCalls.get(result.id); 321 this.#pendingCalls.delete(result.id); 322 if (!items) { 323 return; 324 } 325 326 if (!this.#worker) { 327 return; 328 } 329 330 result.results.forEach((resultData, i) => { 331 const { resolve, reject } = items[i]; 332 333 if (resultData.error) { 334 const err = new Error(resultData.message); 335 err.metadata = resultData.metadata; 336 reject(err); 337 } else { 338 resolve(resultData.response); 339 } 340 }); 341 }; 342 } 343 344 function workerHandler(publicInterface) { 345 return function (msg) { 346 const { id, method, calls } = msg.data; 347 348 Promise.all( 349 calls.map(args => { 350 try { 351 const response = publicInterface[method].apply(undefined, args); 352 if (response instanceof Promise) { 353 return response.then( 354 val => ({ response: val }), 355 err => asErrorMessage(err) 356 ); 357 } 358 return { response }; 359 } catch (error) { 360 return asErrorMessage(error); 361 } 362 }) 363 ).then(results => { 364 globalThis.postMessage({ id, results }); 365 }); 366 }; 367 } 368 369 function asErrorMessage(error) { 370 if (typeof error === "object" && error && "message" in error) { 371 // Error can't be sent via postMessage, so be sure to convert to 372 // string. 373 return { 374 error: true, 375 message: 376 error.message + 377 (error.stack ? "\nStack in the worker:" + error.stack : ""), 378 metadata: error.metadata, 379 }; 380 } 381 382 return { 383 error: true, 384 message: error == null ? error : error.toString(), 385 metadata: undefined, 386 }; 387 } 388 389 // Might be loaded within a worker thread where `module` isn't available. 390 { 391 module.exports = { 392 WorkerDispatcher, 393 workerHandler, 394 }; 395 } 396 } (workerUtils)); 397 return workerUtils.exports; 398 } 399 400 var workerUtilsExports = requireWorkerUtils(); 401 402 self.onmessage = workerUtilsExports.workerHandler({ getMatches, findSourceMatches }); 403 404 }));