tor-browser

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

UrlbarProviderSearchSuggestions.sys.mjs (21739B)


      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 search engine suggestions.
      7 */
      8 
      9 import {
     10  SkippableTimer,
     11  UrlbarProvider,
     12  UrlbarUtils,
     13 } from "moz-src:///browser/components/urlbar/UrlbarUtils.sys.mjs";
     14 
     15 const lazy = {};
     16 
     17 ChromeUtils.defineESModuleGetters(lazy, {
     18  DEFAULT_FORM_HISTORY_PARAM:
     19    "moz-src:///toolkit/components/search/SearchSuggestionController.sys.mjs",
     20  FormHistory: "resource://gre/modules/FormHistory.sys.mjs",
     21  SearchSuggestionController:
     22    "moz-src:///toolkit/components/search/SearchSuggestionController.sys.mjs",
     23  UrlbarPrefs: "moz-src:///browser/components/urlbar/UrlbarPrefs.sys.mjs",
     24  UrlbarProviderTopSites:
     25    "moz-src:///browser/components/urlbar/UrlbarProviderTopSites.sys.mjs",
     26  UrlbarResult: "moz-src:///browser/components/urlbar/UrlbarResult.sys.mjs",
     27  UrlbarSearchUtils:
     28    "moz-src:///browser/components/urlbar/UrlbarSearchUtils.sys.mjs",
     29  UrlbarTokenizer:
     30    "moz-src:///browser/components/urlbar/UrlbarTokenizer.sys.mjs",
     31  UrlUtils: "resource://gre/modules/UrlUtils.sys.mjs",
     32 });
     33 
     34 /**
     35 * @import {SearchSuggestionController} from "moz-src:///toolkit/components/search/SearchSuggestionController.sys.mjs"
     36 */
     37 
     38 const RESULT_MENU_COMMANDS = {
     39  TRENDING_BLOCK: "trendingblock",
     40  TRENDING_HELP: "help",
     41 };
     42 
     43 const TRENDING_HELP_URL =
     44  Services.urlFormatter.formatURLPref("app.support.baseURL") +
     45  "google-trending-searches-on-awesomebar";
     46 
     47 /**
     48 * Returns whether the passed in string looks like a url.
     49 *
     50 * @param {string} str
     51 *   The string to check.
     52 * @param {boolean} [ignoreAlphanumericHosts]
     53 *   If true, don't consider a string with an alphanumeric host to be a URL.
     54 * @returns {boolean}
     55 *   True if the query looks like a URL.
     56 */
     57 function looksLikeUrl(str, ignoreAlphanumericHosts = false) {
     58  // Single word including special chars.
     59  return (
     60    !lazy.UrlUtils.REGEXP_SPACES.test(str) &&
     61    (["/", "@", ":", "["].some(c => str.includes(c)) ||
     62      (ignoreAlphanumericHosts
     63        ? /^([\[\]A-Z0-9-]+\.){3,}[^.]+$/i.test(str)
     64        : str.includes(".")))
     65  );
     66 }
     67 
     68 /**
     69 * Class used to create the provider.
     70 */
     71 export class UrlbarProviderSearchSuggestions extends UrlbarProvider {
     72  constructor() {
     73    super();
     74  }
     75 
     76  /**
     77   * @returns {Values<typeof UrlbarUtils.PROVIDER_TYPE>}
     78   */
     79  get type() {
     80    return UrlbarUtils.PROVIDER_TYPE.NETWORK;
     81  }
     82 
     83  /**
     84   * Whether this provider should be invoked for the given context.
     85   * If this method returns false, the providers manager won't start a query
     86   * with this provider, to save on resources.
     87   *
     88   * @param {UrlbarQueryContext} queryContext The query context object.
     89   */
     90  async isActive(queryContext) {
     91    // If the sources don't include search or the user used a restriction
     92    // character other than search, don't allow any suggestions.
     93    if (
     94      !queryContext.sources.includes(UrlbarUtils.RESULT_SOURCE.SEARCH) ||
     95      (queryContext.restrictSource &&
     96        queryContext.restrictSource != UrlbarUtils.RESULT_SOURCE.SEARCH)
     97    ) {
     98      return false;
     99    }
    100 
    101    // No suggestions for empty search strings, unless we are restricting to
    102    // search or showing trending suggestions.
    103    if (
    104      !queryContext.trimmedSearchString &&
    105      !this._isTokenOrRestrictionPresent(queryContext) &&
    106      !this.#shouldFetchTrending(queryContext)
    107    ) {
    108      return false;
    109    }
    110 
    111    if (!this._allowSuggestions(queryContext)) {
    112      return false;
    113    }
    114 
    115    let wantsLocalSuggestions =
    116      lazy.UrlbarPrefs.get("maxHistoricalSearchSuggestions") &&
    117      queryContext.trimmedSearchString;
    118 
    119    return (
    120      !!wantsLocalSuggestions || this._allowRemoteSuggestions(queryContext)
    121    );
    122  }
    123 
    124  /**
    125   * Returns whether the user typed a token alias or restriction token, or is in
    126   * search mode. We use this value to override the pref to disable search
    127   * suggestions in the Urlbar.
    128   *
    129   * @param {UrlbarQueryContext} queryContext  The query context object.
    130   * @returns {boolean} True if the user typed a token alias or search
    131   *   restriction token.
    132   */
    133  _isTokenOrRestrictionPresent(queryContext) {
    134    return (
    135      queryContext.searchString.startsWith("@") ||
    136      (queryContext.restrictSource &&
    137        queryContext.restrictSource == UrlbarUtils.RESULT_SOURCE.SEARCH) ||
    138      queryContext.tokens.some(
    139        t => t.type == lazy.UrlbarTokenizer.TYPE.RESTRICT_SEARCH
    140      ) ||
    141      (queryContext.searchMode &&
    142        queryContext.sources.includes(UrlbarUtils.RESULT_SOURCE.SEARCH))
    143    );
    144  }
    145 
    146  /**
    147   * Returns whether suggestions in general are allowed for a given query
    148   * context.  If this returns false, then we shouldn't fetch either form
    149   * history or remote suggestions.
    150   *
    151   * @param {UrlbarQueryContext} queryContext The query context object.
    152   * @returns {boolean} True if suggestions in general are allowed and false if
    153   *   not.
    154   */
    155  _allowSuggestions(queryContext) {
    156    if (
    157      // If the user typed a restriction token or token alias, we ignore the
    158      // pref to disable suggestions in the Urlbar.
    159      (queryContext.sapName == "urlbar" &&
    160        !lazy.UrlbarPrefs.get("suggest.searches") &&
    161        !this._isTokenOrRestrictionPresent(queryContext)) ||
    162      !lazy.UrlbarPrefs.get("browser.search.suggest.enabled") ||
    163      (queryContext.isPrivate &&
    164        !lazy.UrlbarPrefs.get("browser.search.suggest.enabled.private"))
    165    ) {
    166      return false;
    167    }
    168    return true;
    169  }
    170 
    171  /**
    172   * Returns whether remote suggestions are allowed for a given query context.
    173   *
    174   * @param {object} queryContext The query context object
    175   * @param {string} [searchString] The effective search string without
    176   *        restriction tokens or aliases. Defaults to the context searchString.
    177   * @returns {boolean} True if remote suggestions are allowed and false if not.
    178   */
    179  _allowRemoteSuggestions(
    180    queryContext,
    181    searchString = queryContext.searchString
    182  ) {
    183    // This is checked by `queryContext.allowRemoteResults` below, but we can
    184    // short-circuit that call with the `_isTokenOrRestrictionPresent` block
    185    // before that. Make sure we don't allow remote suggestions if this is set.
    186    if (queryContext.prohibitRemoteResults) {
    187      return false;
    188    }
    189 
    190    // Allow remote suggestions if trending suggestions are enabled.
    191    if (this.#shouldFetchTrending(queryContext)) {
    192      return true;
    193    }
    194 
    195    if (!searchString.trim()) {
    196      return false;
    197    }
    198 
    199    // Skip all remaining checks and allow remote suggestions at this point if
    200    // the user used a token alias or restriction token. We want "@engine query"
    201    // to return suggestions from the engine. We'll return early from startQuery
    202    // if the query doesn't match an alias.
    203    if (this._isTokenOrRestrictionPresent(queryContext)) {
    204      return true;
    205    }
    206 
    207    // If the user is just adding on to a query that previously didn't return
    208    // many remote suggestions, we are unlikely to get any more results.
    209    if (
    210      !!this._lastLowResultsSearchSuggestion &&
    211      searchString.length > this._lastLowResultsSearchSuggestion.length &&
    212      searchString.startsWith(this._lastLowResultsSearchSuggestion)
    213    ) {
    214      return false;
    215    }
    216 
    217    return queryContext.allowRemoteResults(
    218      searchString,
    219      lazy.UrlbarPrefs.get("trending.featureGate")
    220    );
    221  }
    222 
    223  /**
    224   * Starts querying.
    225   *
    226   * @param {UrlbarQueryContext} queryContext
    227   * @param {(provider: UrlbarProvider, result: UrlbarResult) => void} addCallback
    228   *   Callback invoked by the provider to add a new result.
    229   */
    230  async startQuery(queryContext, addCallback) {
    231    let instance = this.queryInstance;
    232 
    233    let aliasEngine = await this._maybeGetAlias(queryContext);
    234    if (!aliasEngine) {
    235      // Autofill matches queries starting with "@" to token alias engines.
    236      // If the string starts with "@", but an alias engine is not yet
    237      // matched, then autofill might still be filtering token alias
    238      // engine results. We don't want to mix search suggestions with those
    239      // engine results, so we return early. See bug 1551049 comment 1 for
    240      // discussion on how to improve this behavior.
    241      if (queryContext.searchString.startsWith("@")) {
    242        return;
    243      }
    244    }
    245 
    246    let query = aliasEngine
    247      ? aliasEngine.query
    248      : UrlbarUtils.substringAt(
    249          queryContext.searchString,
    250          queryContext.tokens[0]?.value || ""
    251        ).trim();
    252 
    253    let leadingRestrictionToken = null;
    254    if (
    255      lazy.UrlbarTokenizer.isRestrictionToken(queryContext.tokens[0]) &&
    256      (queryContext.tokens.length > 1 ||
    257        queryContext.tokens[0].type ==
    258          lazy.UrlbarTokenizer.TYPE.RESTRICT_SEARCH)
    259    ) {
    260      leadingRestrictionToken = queryContext.tokens[0].value;
    261    }
    262 
    263    // Strip a leading search restriction char, because we prepend it to text
    264    // when the search shortcut is used and it's not user typed. Don't strip
    265    // other restriction chars, so that it's possible to search for things
    266    // including one of those (e.g. "c#").
    267    if (leadingRestrictionToken === lazy.UrlbarTokenizer.RESTRICT.SEARCH) {
    268      query = UrlbarUtils.substringAfter(query, leadingRestrictionToken).trim();
    269    }
    270 
    271    // Find our search engine. It may have already been set with an alias.
    272    let engine;
    273    if (aliasEngine) {
    274      engine = aliasEngine.engine;
    275    } else if (queryContext.searchMode?.engineName) {
    276      engine = lazy.UrlbarSearchUtils.getEngineByName(
    277        queryContext.searchMode.engineName
    278      );
    279    } else {
    280      engine = lazy.UrlbarSearchUtils.getDefaultEngine(queryContext.isPrivate);
    281    }
    282 
    283    if (!engine) {
    284      return;
    285    }
    286 
    287    let alias = (aliasEngine && aliasEngine.alias) || "";
    288    let results = await this.#fetchSearchSuggestions(
    289      queryContext,
    290      engine,
    291      query,
    292      alias
    293    );
    294 
    295    if (!results || instance != this.queryInstance) {
    296      return;
    297    }
    298 
    299    for (let result of results) {
    300      addCallback(this, result);
    301    }
    302  }
    303 
    304  /**
    305   * Gets the provider's priority.
    306   *
    307   * @param {UrlbarQueryContext} queryContext The query context object
    308   * @returns {number} The provider's priority for the given query.
    309   */
    310  getPriority(queryContext) {
    311    if (this.#shouldFetchTrending(queryContext)) {
    312      return lazy.UrlbarProviderTopSites.PRIORITY;
    313    }
    314    return 0;
    315  }
    316 
    317  /**
    318   * Cancels a running query.
    319   */
    320  cancelQuery() {
    321    if (this.#suggestionsController) {
    322      this.#suggestionsController.stop();
    323    }
    324  }
    325 
    326  /**
    327   * Returns the menu commands to be shown for trending results.
    328   *
    329   * @param {UrlbarResult} result
    330   *   The result to get menu comands for.
    331   */
    332  getResultCommands(result) {
    333    if (result.payload.trending) {
    334      return /** @type {UrlbarResultCommand[]} */ ([
    335        {
    336          name: RESULT_MENU_COMMANDS.TRENDING_BLOCK,
    337          l10n: { id: "urlbar-result-menu-trending-dont-show" },
    338        },
    339        {
    340          name: "separator",
    341        },
    342        {
    343          name: RESULT_MENU_COMMANDS.TRENDING_HELP,
    344          l10n: { id: "urlbar-result-menu-trending-why" },
    345        },
    346      ]);
    347    }
    348    return undefined;
    349  }
    350 
    351  onEngagement(queryContext, controller, details) {
    352    let { result } = details;
    353 
    354    if (details.selType == "dismiss") {
    355      lazy.FormHistory.update({
    356        op: "remove",
    357        fieldname: lazy.DEFAULT_FORM_HISTORY_PARAM,
    358        value: result.payload.suggestion,
    359      }).catch(error =>
    360        console.error(`Removing form history failed: ${error}`)
    361      );
    362      controller.removeResult(result);
    363      return;
    364    }
    365 
    366    switch (details.selType) {
    367      case RESULT_MENU_COMMANDS.TRENDING_HELP:
    368        // Handled by UrlbarInput
    369        break;
    370      case RESULT_MENU_COMMANDS.TRENDING_BLOCK:
    371        lazy.UrlbarPrefs.set("suggest.trending", false);
    372        this.#recordTrendingBlockedTelemetry();
    373        this.#replaceTrendingResultWithAcknowledgement(controller);
    374        break;
    375    }
    376  }
    377 
    378  /**
    379   * @type {?SearchSuggestionController}
    380   */
    381  #suggestionsController;
    382 
    383  async #fetchSearchSuggestions(queryContext, engine, searchString, alias) {
    384    if (!engine) {
    385      return null;
    386    }
    387 
    388    if (!this.#suggestionsController) {
    389      this.#suggestionsController = new lazy.SearchSuggestionController();
    390    }
    391 
    392    // If there's a form history entry that equals the search string, the search
    393    // suggestions controller will include it, and we'll make a result for it.
    394    // If the heuristic result ends up being a search result, the muxer will
    395    // discard the form history result since it dupes the heuristic, and the
    396    // final list of results would be left with `count` - 1 form history results
    397    // instead of `count`.  Therefore we request `count` + 1 entries.  The muxer
    398    // will dedupe and limit the final form history count as appropriate.
    399    let maxLocalResults = queryContext.maxResults + 1;
    400 
    401    // Request maxResults + 1 remote suggestions for the same reason we request
    402    // maxResults + 1 form history entries.
    403    let allowRemote = this._allowRemoteSuggestions(queryContext, searchString);
    404    let maxRemoteResults = allowRemote ? queryContext.maxResults + 1 : 0;
    405 
    406    if (allowRemote && this.#shouldFetchTrending(queryContext)) {
    407      if (
    408        queryContext.searchMode &&
    409        lazy.UrlbarPrefs.get("trending.maxResultsSearchMode") != -1
    410      ) {
    411        maxRemoteResults = lazy.UrlbarPrefs.get(
    412          "trending.maxResultsSearchMode"
    413        );
    414      } else if (
    415        !queryContext.searchMode &&
    416        lazy.UrlbarPrefs.get("trending.maxResultsNoSearchMode") != -1
    417      ) {
    418        maxRemoteResults = lazy.UrlbarPrefs.get(
    419          "trending.maxResultsNoSearchMode"
    420        );
    421      }
    422    }
    423 
    424    // See `SearchSuggestionsController.fetch` documentation for a description
    425    // of `fetchData`.
    426    let fetchData = await this.#suggestionsController.fetch({
    427      searchString,
    428      inPrivateBrowsing: queryContext.isPrivate,
    429      engine,
    430      userContextId: queryContext.userContextId,
    431      restrictToEngine: this._isTokenOrRestrictionPresent(queryContext),
    432      dedupeRemoteAndLocal: false,
    433      fetchTrending: this.#shouldFetchTrending(queryContext),
    434      maxLocalResults,
    435      maxRemoteResults,
    436    });
    437 
    438    // The fetch was canceled.
    439    if (!fetchData) {
    440      return null;
    441    }
    442 
    443    let results = [];
    444 
    445    // maxHistoricalSearchSuggestions used to determine the initial number of
    446    // form history results, with the special case where zero means to never
    447    // show form history at all.  With the introduction of flexed result
    448    // groups, we now use it only as a boolean: Zero means don't show form
    449    // history at all (as before), non-zero means show it.
    450    if (lazy.UrlbarPrefs.get("maxHistoricalSearchSuggestions")) {
    451      for (let entry of fetchData.local) {
    452        results.push(makeFormHistoryResult(queryContext, engine, entry));
    453      }
    454    }
    455 
    456    // If we don't return many results, then keep track of the query. If the
    457    // user just adds on to the query, we won't fetch more suggestions if the
    458    // query is very long since we are unlikely to get any.
    459    if (
    460      allowRemote &&
    461      !fetchData.remote.length &&
    462      searchString.length > lazy.UrlbarPrefs.get("maxCharsForSearchSuggestions")
    463    ) {
    464      this._lastLowResultsSearchSuggestion = searchString;
    465    }
    466 
    467    // If we have only tail suggestions, we only show them if we have no other
    468    // results. We need to wait for other results to arrive to avoid flickering.
    469    // We will wait for this timer unless we have suggestions that don't have a
    470    // tail.
    471    let tailTimer = new SkippableTimer({
    472      name: "ProviderSearchSuggestions",
    473      time: 100,
    474      logger: this.logger,
    475    });
    476 
    477    for (let entry of fetchData.remote) {
    478      if (looksLikeUrl(entry.value)) {
    479        continue;
    480      }
    481 
    482      let tail = entry.tail;
    483      let tailPrefix = entry.matchPrefix;
    484 
    485      // Skip tail suggestions if the pref is disabled.
    486      if (tail && !lazy.UrlbarPrefs.get("richSuggestions.tail")) {
    487        continue;
    488      }
    489 
    490      if (!tail) {
    491        await tailTimer.fire().catch(ex => this.logger.error(ex));
    492      }
    493 
    494      try {
    495        let query = searchString.trim();
    496        let suggestion = entry.value;
    497        let title;
    498        if (tail && entry.tailOffsetIndex >= 0) {
    499          title = tail;
    500        } else if (suggestion) {
    501          title = suggestion;
    502        } else {
    503          title = query;
    504        }
    505 
    506        results.push(
    507          new lazy.UrlbarResult({
    508            type: UrlbarUtils.RESULT_TYPE.SEARCH,
    509            source: UrlbarUtils.RESULT_SOURCE.SEARCH,
    510            isRichSuggestion: !!entry.icon,
    511            payload: {
    512              title,
    513              engine: engine.name,
    514              suggestion,
    515              lowerCaseSuggestion: entry.value.toLocaleLowerCase(),
    516              tailPrefix,
    517              tail,
    518              tailOffsetIndex: tail ? entry.tailOffsetIndex : undefined,
    519              keyword: alias || undefined,
    520              trending: entry.trending,
    521              description: entry.description || undefined,
    522              query,
    523              icon: !entry.value ? await engine.getIconURL() : entry.icon,
    524              helpUrl: entry.trending ? TRENDING_HELP_URL : undefined,
    525            },
    526            highlights: {
    527              engine: UrlbarUtils.HIGHLIGHT.TYPED,
    528              suggestion: UrlbarUtils.HIGHLIGHT.SUGGESTED,
    529              tail: UrlbarUtils.HIGHLIGHT.SUGGESTED,
    530              keyword: UrlbarUtils.HIGHLIGHT.TYPED,
    531            },
    532          })
    533        );
    534      } catch (err) {
    535        this.logger.error(err);
    536        continue;
    537      }
    538    }
    539 
    540    await tailTimer.promise;
    541    return results;
    542  }
    543 
    544  /**
    545   * @typedef {object} EngineAlias
    546   *
    547   * @property {nsISearchEngine} engine
    548   *   The search engine
    549   * @property {string} alias
    550   *   The search engine's alias
    551   * @property {string} query
    552   *   The remainder of the search engine string after the alias
    553   */
    554 
    555  /**
    556   * Searches for an engine alias given the queryContext.
    557   *
    558   * @param {UrlbarQueryContext} queryContext
    559   *   The query context object.
    560   * @returns {Promise<EngineAlias?>} aliasEngine
    561   *   A representation of the aliased engine. Null if there's no match.
    562   */
    563  async _maybeGetAlias(queryContext) {
    564    if (queryContext.searchMode) {
    565      // If we're in search mode, don't try to parse an alias at all.
    566      return null;
    567    }
    568 
    569    let possibleAlias = queryContext.tokens[0]?.value;
    570    // "@" on its own is handled by UrlbarProviderTokenAliasEngines and returns
    571    // a list of every available token alias.
    572    if (!possibleAlias || possibleAlias == "@") {
    573      return null;
    574    }
    575 
    576    let query = UrlbarUtils.substringAfter(
    577      queryContext.searchString,
    578      possibleAlias
    579    );
    580 
    581    // Match an alias only when it has a space after it.  If there's no trailing
    582    // space, then continue to treat it as part of the search string.
    583    if (!lazy.UrlUtils.REGEXP_SPACES_START.test(query)) {
    584      return null;
    585    }
    586 
    587    // Check if the user entered an engine alias directly.
    588    let engineMatch =
    589      await lazy.UrlbarSearchUtils.engineForAlias(possibleAlias);
    590    if (engineMatch) {
    591      return {
    592        engine: engineMatch,
    593        alias: possibleAlias,
    594        query: query.trim(),
    595      };
    596    }
    597 
    598    return null;
    599  }
    600 
    601  /**
    602   * Whether we should show trending suggestions. These are shown when the
    603   * user enters a specific engines searchMode when enabled, the
    604   * seperate `requireSearchMode` pref controls whether they are visible
    605   * when the urlbar is first opened without any search mode.
    606   *
    607   * @param {UrlbarQueryContext} queryContext
    608   *   The query context object.
    609   * @returns {boolean}
    610   *   Whether we should fetch trending results.
    611   */
    612  #shouldFetchTrending(queryContext) {
    613    return !!(
    614      queryContext.searchString == "" &&
    615      queryContext.sapName != "searchbar" &&
    616      lazy.UrlbarPrefs.get("trending.featureGate") &&
    617      lazy.UrlbarPrefs.get("suggest.trending") &&
    618      (queryContext.searchMode ||
    619        !lazy.UrlbarPrefs.get("trending.requireSearchMode"))
    620    );
    621  }
    622 
    623  /*
    624   * Send telemetry to indicating trending results have been hidden.
    625   */
    626  #recordTrendingBlockedTelemetry() {
    627    Glean.urlbarTrending.block.add(1);
    628  }
    629 
    630  /*
    631   * Remove all the trending results and show an acknowledgement that the
    632   * trending suggestions have been turned off.
    633   */
    634  #replaceTrendingResultWithAcknowledgement(controller) {
    635    let resultsToRemove = controller.view.visibleResults.filter(
    636      result => result.payload.trending
    637    );
    638    if (resultsToRemove.length) {
    639      // Show an acknowledgement tip for the first result.
    640      resultsToRemove[0].acknowledgeDismissalL10n = {
    641        id: "urlbar-trending-dismissal-acknowledgment",
    642      };
    643    }
    644    // Remove results in reverse order so the acknowledgment tip isn't removed.
    645    resultsToRemove.reverse();
    646    resultsToRemove.forEach(result => controller.removeResult(result));
    647  }
    648 }
    649 
    650 function makeFormHistoryResult(queryContext, engine, entry) {
    651  return new lazy.UrlbarResult({
    652    type: UrlbarUtils.RESULT_TYPE.SEARCH,
    653    source: UrlbarUtils.RESULT_SOURCE.HISTORY,
    654    payload: {
    655      engine: engine.name,
    656      suggestion: entry.value,
    657      title: entry.value,
    658      lowerCaseSuggestion: entry.value.toLocaleLowerCase(),
    659      isBlockable: true,
    660      blockL10n: { id: "urlbar-result-menu-remove-from-history" },
    661      helpUrl:
    662        Services.urlFormatter.formatURLPref("app.support.baseURL") +
    663        "awesome-bar-result-menu",
    664    },
    665    highlights: {
    666      suggestion: UrlbarUtils.HIGHLIGHT.SUGGESTED,
    667    },
    668  });
    669 }