tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }));