tor-browser

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

UrlbarProviderTokenAliasEngines.sys.mjs (7016B)


      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 token alias engines.
      7 */
      8 
      9 import {
     10  UrlbarProvider,
     11  UrlbarUtils,
     12 } from "moz-src:///browser/components/urlbar/UrlbarUtils.sys.mjs";
     13 
     14 const lazy = {};
     15 
     16 ChromeUtils.defineESModuleGetters(lazy, {
     17  UrlbarPrefs: "moz-src:///browser/components/urlbar/UrlbarPrefs.sys.mjs",
     18  UrlbarResult: "moz-src:///browser/components/urlbar/UrlbarResult.sys.mjs",
     19  UrlbarSearchUtils:
     20    "moz-src:///browser/components/urlbar/UrlbarSearchUtils.sys.mjs",
     21  UrlUtils: "resource://gre/modules/UrlUtils.sys.mjs",
     22 });
     23 
     24 /**
     25 * Class used to create the provider.
     26 */
     27 export class UrlbarProviderTokenAliasEngines extends UrlbarProvider {
     28  constructor() {
     29    super();
     30    this._engines = [];
     31  }
     32 
     33  /**
     34   * @returns {Values<typeof UrlbarUtils.PROVIDER_TYPE>}
     35   */
     36  get type() {
     37    return UrlbarUtils.PROVIDER_TYPE.HEURISTIC;
     38  }
     39 
     40  static get PRIORITY() {
     41    // Beats UrlbarProviderSearchSuggestions and UrlbarProviderPlaces.
     42    return 1;
     43  }
     44 
     45  /**
     46   * Whether this provider should be invoked for the given context.
     47   * If this method returns false, the providers manager won't start a query
     48   * with this provider, to save on resources.
     49   *
     50   * @param {UrlbarQueryContext} queryContext The query context object
     51   */
     52  async isActive(queryContext) {
     53    let instance = this.queryInstance;
     54 
     55    // This is usually reset on canceling or completing the query, but since we
     56    // query in isActive, it may not have been canceled by the previous call.
     57    // It is an object with values { result: UrlbarResult, instance: Query }.
     58    this._autofillData = null;
     59 
     60    // Once the user starts typing a search string after the token, we hand off
     61    // suggestions to UrlbarProviderSearchSuggestions.
     62    if (
     63      !queryContext.searchString.startsWith("@") ||
     64      queryContext.tokens.length != 1
     65    ) {
     66      return false;
     67    }
     68 
     69    // Do not show token alias results in search mode.
     70    if (queryContext.searchMode) {
     71      return false;
     72    }
     73 
     74    this._engines = await lazy.UrlbarSearchUtils.tokenAliasEngines();
     75    if (!this._engines.length) {
     76      return false;
     77    }
     78 
     79    // Check the query was not canceled while this executed.
     80    if (instance != this.queryInstance) {
     81      return false;
     82    }
     83 
     84    if (queryContext.trimmedSearchString == "@") {
     85      return true;
     86    }
     87 
     88    // If the user is typing a potential engine name, autofill it.
     89    if (lazy.UrlbarPrefs.get("autoFill") && queryContext.allowAutofill) {
     90      let result = await this._getAutofillResult(queryContext);
     91      if (result && instance == this.queryInstance) {
     92        this._autofillData = { result, instance };
     93        return true;
     94      }
     95    }
     96 
     97    return false;
     98  }
     99 
    100  /**
    101   * Starts querying.
    102   *
    103   * @param {UrlbarQueryContext} queryContext
    104   * @param {(provider: UrlbarProvider, result: UrlbarResult) => void} addCallback
    105   *   Callback invoked by the provider to add a new result.
    106   */
    107  async startQuery(queryContext, addCallback) {
    108    if (!this._engines || !this._engines.length) {
    109      return;
    110    }
    111 
    112    if (
    113      this._autofillData &&
    114      this._autofillData.instance == this.queryInstance
    115    ) {
    116      addCallback(this, this._autofillData.result);
    117    }
    118 
    119    let instance = this.queryInstance;
    120    for (let { engine, tokenAliases } of this._engines) {
    121      if (
    122        tokenAliases[0].startsWith(queryContext.trimmedSearchString) &&
    123        engine.name != this._autofillData?.result.payload.engine
    124      ) {
    125        let result = new lazy.UrlbarResult({
    126          type: UrlbarUtils.RESULT_TYPE.SEARCH,
    127          source: UrlbarUtils.RESULT_SOURCE.SEARCH,
    128          hideRowLabel: true,
    129          payload: {
    130            engine: engine.name,
    131            keyword: tokenAliases[0],
    132            keywords: tokenAliases.join(", "),
    133            query: "",
    134            icon: await engine.getIconURL(),
    135            providesSearchMode: true,
    136          },
    137          highlights: {
    138            engine: UrlbarUtils.HIGHLIGHT.TYPED,
    139            keyword: UrlbarUtils.HIGHLIGHT.TYPED,
    140          },
    141        });
    142        if (instance != this.queryInstance) {
    143          break;
    144        }
    145        addCallback(this, result);
    146      }
    147    }
    148 
    149    this._autofillData = null;
    150  }
    151 
    152  /**
    153   * Gets the provider's priority.
    154   *
    155   * @returns {number} The provider's priority for the given query.
    156   */
    157  getPriority() {
    158    return UrlbarProviderTokenAliasEngines.PRIORITY;
    159  }
    160 
    161  /**
    162   * Cancels a running query.
    163   */
    164  cancelQuery() {
    165    if (this._autofillData?.instance == this.queryInstance) {
    166      this._autofillData = null;
    167    }
    168  }
    169 
    170  async _getAutofillResult(queryContext) {
    171    let { lowerCaseSearchString } = queryContext;
    172 
    173    // The user is typing a specific engine. We should show a heuristic result.
    174    for (let { engine, tokenAliases } of this._engines) {
    175      for (let alias of tokenAliases) {
    176        if (alias.startsWith(lowerCaseSearchString)) {
    177          // We found the engine.
    178 
    179          // Stop adding an autofill result once the user has typed the full
    180          // alias followed by a space. We enter search mode at that point.
    181          if (
    182            lowerCaseSearchString.startsWith(alias) &&
    183            lazy.UrlUtils.REGEXP_SPACES_START.test(
    184              lowerCaseSearchString.substring(alias.length)
    185            )
    186          ) {
    187            return null;
    188          }
    189 
    190          // Add an autofill result.  Append a space so the user can hit enter
    191          // or the right arrow key and immediately start typing their query.
    192          let aliasPreservingUserCase =
    193            queryContext.searchString +
    194            alias.substr(queryContext.searchString.length);
    195          let value = aliasPreservingUserCase + " ";
    196          return new lazy.UrlbarResult({
    197            type: UrlbarUtils.RESULT_TYPE.SEARCH,
    198            source: UrlbarUtils.RESULT_SOURCE.SEARCH,
    199            // We set suggestedIndex = 0 instead of the heuristic because we
    200            // don't want this result to be automatically selected. That way,
    201            // users can press Tab to select the result, building on their
    202            // muscle memory from tab-to-search.
    203            suggestedIndex: 0,
    204            autofill: {
    205              value,
    206              selectionStart: queryContext.searchString.length,
    207              selectionEnd: value.length,
    208            },
    209            hideRowLabel: true,
    210            payload: {
    211              engine: engine.name,
    212              keyword: aliasPreservingUserCase,
    213              keywords: tokenAliases.join(", "),
    214              query: "",
    215              icon: await engine.getIconURL(),
    216              providesSearchMode: true,
    217            },
    218            highlights: {
    219              engine: UrlbarUtils.HIGHLIGHT.TYPED,
    220              keyword: UrlbarUtils.HIGHLIGHT.TYPED,
    221            },
    222          });
    223        }
    224      }
    225    }
    226    return null;
    227  }
    228 }