tor-browser

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

UrlbarProviderRestrictKeywordsAutofill.sys.mjs (6200B)


      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 /**
      6 * This module exports a provider that offers restrict keywords autofill for
      7 * search mode.
      8 */
      9 
     10 import {
     11  UrlbarProvider,
     12  UrlbarUtils,
     13 } from "moz-src:///browser/components/urlbar/UrlbarUtils.sys.mjs";
     14 
     15 const lazy = {};
     16 
     17 ChromeUtils.defineESModuleGetters(lazy, {
     18  UrlbarPrefs: "moz-src:///browser/components/urlbar/UrlbarPrefs.sys.mjs",
     19  UrlbarResult: "moz-src:///browser/components/urlbar/UrlbarResult.sys.mjs",
     20  UrlbarTokenizer:
     21    "moz-src:///browser/components/urlbar/UrlbarTokenizer.sys.mjs",
     22 });
     23 
     24 const RESTRICT_KEYWORDS_FEATURE_GATE = "searchRestrictKeywords.featureGate";
     25 
     26 /**
     27 * Class used to create the provider.
     28 */
     29 export class UrlbarProviderRestrictKeywordsAutofill extends UrlbarProvider {
     30  #autofillData;
     31  #lowerCaseTokenToKeywords;
     32 
     33  constructor() {
     34    super();
     35  }
     36 
     37  /**
     38   * @returns {Values<typeof UrlbarUtils.PROVIDER_TYPE>}
     39   */
     40  get type() {
     41    return UrlbarUtils.PROVIDER_TYPE.HEURISTIC;
     42  }
     43 
     44  getPriority() {
     45    return 1;
     46  }
     47 
     48  async #getLowerCaseTokenToKeywords() {
     49    let tokenToKeywords = await lazy.UrlbarTokenizer.getL10nRestrictKeywords();
     50 
     51    this.#lowerCaseTokenToKeywords = new Map(
     52      [...tokenToKeywords].map(([token, keywords]) => [
     53        token,
     54        keywords.map(keyword => keyword.toLowerCase()),
     55      ])
     56    );
     57 
     58    return this.#lowerCaseTokenToKeywords;
     59  }
     60 
     61  async #getKeywordAliases() {
     62    return Array.from(await this.#lowerCaseTokenToKeywords.values())
     63      .flat()
     64      .map(keyword => "@" + keyword);
     65  }
     66 
     67  async isActive(queryContext) {
     68    if (!lazy.UrlbarPrefs.getScotchBonnetPref(RESTRICT_KEYWORDS_FEATURE_GATE)) {
     69      return false;
     70    }
     71 
     72    this.#autofillData = null;
     73 
     74    if (
     75      queryContext.searchMode ||
     76      queryContext.tokens.length != 1 ||
     77      queryContext.searchString.length == 1 ||
     78      queryContext.restrictSource ||
     79      !queryContext.searchString.startsWith("@")
     80    ) {
     81      return false;
     82    }
     83 
     84    // Returns partial autofill result when the user types
     85    // @h, @hi, @hist, etc.
     86    if (lazy.UrlbarPrefs.get("autoFill") && queryContext.allowAutofill) {
     87      let instance = this.queryInstance;
     88      let result = await this.#getAutofillResult(queryContext);
     89      if (result && instance == this.queryInstance) {
     90        this.#autofillData = { result, instance };
     91        return true;
     92      }
     93    }
     94 
     95    // Returns full autofill result when user types keyword with space to
     96    // enter seach mode. Example, "@history ".
     97    let keywordAliases = await this.#getKeywordAliases();
     98    if (
     99      keywordAliases.some(keyword =>
    100        keyword.startsWith(queryContext.trimmedLowerCaseSearchString)
    101      )
    102    ) {
    103      return true;
    104    }
    105 
    106    return false;
    107  }
    108 
    109  /**
    110   * Starts querying.
    111   *
    112   * @param {UrlbarQueryContext} queryContext
    113   * @param {(provider: UrlbarProvider, result: UrlbarResult) => void} addCallback
    114   *   Callback invoked by the provider to add a new result.
    115   */
    116  async startQuery(queryContext, addCallback) {
    117    if (
    118      this.#autofillData &&
    119      this.#autofillData.instance == this.queryInstance
    120    ) {
    121      addCallback(this, this.#autofillData.result);
    122      return;
    123    }
    124 
    125    let instance = this.queryInstance;
    126    let typedKeyword = queryContext.lowerCaseSearchString;
    127    let typedKeywordTrimmed =
    128      queryContext.trimmedLowerCaseSearchString.substring(1);
    129    let tokenToKeywords = await this.#getLowerCaseTokenToKeywords();
    130 
    131    if (instance != this.queryInstance) {
    132      return;
    133    }
    134 
    135    let restrictSymbol;
    136    let aliasKeyword;
    137 
    138    for (let [token, keywords] of tokenToKeywords) {
    139      if (keywords.includes(typedKeywordTrimmed)) {
    140        restrictSymbol = token;
    141        aliasKeyword = "@" + typedKeywordTrimmed + " ";
    142        break;
    143      }
    144    }
    145 
    146    if (restrictSymbol && typedKeyword == aliasKeyword) {
    147      let result = new lazy.UrlbarResult({
    148        type: UrlbarUtils.RESULT_TYPE.RESTRICT,
    149        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
    150        heuristic: true,
    151        hideRowLabel: true,
    152        payload: {
    153          keyword: restrictSymbol,
    154          providesSearchMode: false,
    155        },
    156      });
    157      addCallback(this, result);
    158    }
    159 
    160    this.#autofillData = null;
    161  }
    162 
    163  cancelQuery() {
    164    if (this.#autofillData?.instance == this.queryInstance) {
    165      this.#autofillData = null;
    166    }
    167  }
    168 
    169  async #getAutofillResult(queryContext) {
    170    let tokenToKeywords = await this.#getLowerCaseTokenToKeywords();
    171    let { lowerCaseSearchString } = queryContext;
    172 
    173    for (let [token, l10nRestrictKeywords] of tokenToKeywords.entries()) {
    174      let keywords = [...l10nRestrictKeywords].map(keyword => `@${keyword}`);
    175      let autofillKeyword = keywords.find(keyword =>
    176        keyword.startsWith(lowerCaseSearchString)
    177      );
    178 
    179      // found the keyword
    180      if (autofillKeyword) {
    181        // Add an autofill result. Append a space so the user can hit enter
    182        // or the right arrow key and immediately start typing their query.
    183        let keywordPreservingUserCase =
    184          queryContext.searchString +
    185          autofillKeyword.substr(queryContext.searchString.length);
    186        let value = keywordPreservingUserCase + " ";
    187        let icon = UrlbarUtils.LOCAL_SEARCH_MODES.find(
    188          mode => mode.restrict == token
    189        )?.icon;
    190 
    191        return new lazy.UrlbarResult({
    192          type: UrlbarUtils.RESULT_TYPE.RESTRICT,
    193          source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
    194          hideRowLabel: true,
    195          autofill: {
    196            value,
    197            selectionStart: queryContext.searchString.length,
    198            selectionEnd: value.length,
    199          },
    200          payload: {
    201            icon,
    202            keyword: token,
    203            l10nRestrictKeywords,
    204            autofillKeyword: keywordPreservingUserCase,
    205            providesSearchMode: true,
    206          },
    207          highlights: {
    208            l10nRestrictKeywords: UrlbarUtils.HIGHLIGHT.TYPED,
    209            autofillKeyword: UrlbarUtils.HIGHLIGHT.TYPED,
    210          },
    211        });
    212      }
    213    }
    214 
    215    return null;
    216  }
    217 }