tor-browser

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

SuggestFeature.sys.mjs (15573B)


      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 /* eslint-disable no-unused-vars */
      6 
      7 const lazy = {};
      8 
      9 ChromeUtils.defineESModuleGetters(lazy, {
     10  QuickSuggest: "moz-src:///browser/components/urlbar/QuickSuggest.sys.mjs",
     11  UrlbarPrefs: "moz-src:///browser/components/urlbar/UrlbarPrefs.sys.mjs",
     12  UrlbarUtils: "moz-src:///browser/components/urlbar/UrlbarUtils.sys.mjs",
     13 });
     14 
     15 /**
     16 * Base class for Suggest features. It can be extended to implement a feature
     17 * that should be enabled only when Suggest is enabled. Most features should
     18 * extend one of the `SuggestFeature` subclasses, however.
     19 *
     20 * Subclasses should be registered with `QuickSuggest` by adding them to the
     21 * `FEATURES` const in `QuickSuggest.sys.mjs`.
     22 */
     23 export class SuggestFeature {
     24  // Methods designed for overriding below
     25 
     26  /**
     27   * @returns {Array}
     28   *   If the feature is conditioned on any prefs or Nimbus variables, the
     29   *   subclass should override this getter and return their names in this
     30   *   array. When one of these prefs or variables changes, Suggest will call
     31   *   `feature.update()`, which checks `feature.shouldEnable`. If the value of
     32   *   `shouldEnable` is different from the feature's current enabled status,
     33   *   then `feature.update()` calls `feature.enable()`.
     34   *
     35   *   Pref and variable names should be recognized by `UrlbarPrefs`. i.e., pref
     36   *   names should be relative to the `browser.urlbar.` branch. For Nimbus
     37   *   variables with fallback prefs, include only the variable name.
     38   */
     39  get enablingPreferences() {
     40    return [];
     41  }
     42 
     43  /**
     44   * @returns {Array}
     45   *   If there are any feature-specific prefs that are exposed to the user and
     46   *   allow the feature to be toggled on or off, the subclass should override
     47   *   this getter and return their names. They should also be included in
     48   *   `enablingPreferences`. The names should be recognized by `UrlbarPrefs`,
     49   *   i.e., they should be relative to the `browser.urlbar.` branch.
     50   *
     51   *   If the feature is a `SuggestProvider`, typically this should include the
     52   *   pref that's named `suggest.mySuggestionType` and set to `false` when the
     53   *   user dismisses the entire suggestion type, i.e., the relevant
     54   *   These prefs should be controlled by the user, so they should never
     55   *   include the feature's `featureGate` pref.
     56   *
     57   *   These prefs should control this feature specifically, so they should
     58   *   never include `suggest.quicksuggest.all` or
     59   *   `suggest.quicksuggest.sponsored`. If the feature has no such prefs,
     60   *   this getter should return an empty array.
     61   */
     62  get primaryUserControlledPreferences() {
     63    return [];
     64  }
     65 
     66  /**
     67   * @returns {boolean}
     68   *   Whether the feature should be enabled, assuming Suggest is enabled. This
     69   *   base implementation returns true if every pref in `enablingPreferences`
     70   *   is truthy. The subclass should override it if it needs different logic.
     71   *
     72   *   This getter will be called only when Suggest is enabled.
     73   */
     74  get shouldEnable() {
     75    return this.enablingPreferences.every(p => lazy.UrlbarPrefs.get(p));
     76  }
     77 
     78  /**
     79   * This method should initialize or uninitialize any state related to the
     80   * feature. It will only be called when the enabled status changes, i.e., when
     81   * it goes from false to true or true to false.
     82   *
     83   * @param {boolean} enabled
     84   *   Whether the feature should be enabled or not.
     85   */
     86  enable(enabled) {}
     87 
     88  // Methods not designed for overriding below
     89 
     90  /**
     91   * @returns {ConsoleInstance}
     92   *   The feature's logger.
     93   */
     94  get logger() {
     95    if (!this._logger) {
     96      this._logger = lazy.UrlbarUtils.getLogger({
     97        prefix: `QuickSuggest.${this.name}`,
     98      });
     99    }
    100    return this._logger;
    101  }
    102 
    103  /**
    104   * @returns {boolean}
    105   *   Whether the feature is enabled. The enabled status is automatically
    106   *   managed by `QuickSuggest` and subclasses should not override this.
    107   */
    108  get isEnabled() {
    109    return this.#isEnabled;
    110  }
    111 
    112  /**
    113   * @returns {string}
    114   *   The feature's name.
    115   */
    116  get name() {
    117    return this.constructor.name;
    118  }
    119 
    120  /**
    121   * Enables or disables the feature according to `shouldEnable` and whether
    122   * Suggest is enabled. If the feature's enabled status changes, `enable()` is
    123   * called with the new status; otherwise `enable()` is not called.
    124   */
    125  update() {
    126    let enable =
    127      lazy.UrlbarPrefs.get("quickSuggestEnabled") && this.shouldEnable;
    128    if (enable != this.isEnabled) {
    129      this.logger.info("Feature enabled status changed", {
    130        nowEnabled: enable,
    131      });
    132      this.#isEnabled = enable;
    133      this.enable(enable);
    134    }
    135  }
    136 
    137  #isEnabled = false;
    138 }
    139 
    140 /**
    141 * Base class for Suggest features that manage a suggestion type [1].
    142 *
    143 * The same suggestion type can be served by multiple backends, and a single
    144 * `SuggestProvider` subclass can manage the type regardless of backend by
    145 * overriding the appropriate methods and getters.
    146 *
    147 * Subclasses should be registered with `QuickSuggest` by adding them to the
    148 * `FEATURES` const in `QuickSuggest.sys.mjs`.
    149 *
    150 * [1] Typically a feature should manage only one type. In rare cases, it might
    151 * make sense to manage multiple types, for example when a single Merino
    152 * provider serves more than one type of suggestion.
    153 */
    154 export class SuggestProvider extends SuggestFeature {
    155  // Methods designed for overriding below
    156 
    157  /**
    158   * @returns {string}
    159   *   If the feature's suggestions are served by Merino, the subclass should
    160   *   override this getter and return the name of the Merino provider that
    161   *   serves them.
    162   */
    163  get merinoProvider() {
    164    return "";
    165  }
    166 
    167  /**
    168   * @returns {string}
    169   *   If the feature's suggestions are served by the Rust component, the
    170   *   subclass should override this getter and return their type name as
    171   *   defined by the `Suggestion` enum in the component. e.g., "Amp",
    172   *   "Wikipedia", "Mdn", etc.
    173   */
    174  get rustSuggestionType() {
    175    return "";
    176  }
    177 
    178  /**
    179   * @returns {Array}
    180   *   If the feature manages dynamic Rust suggestions, its `rustSuggestionType`
    181   *   getter should return "Dynamic", and it should override
    182   *   `dynamicRustSuggestionTypes` to return an array of the dynamic type
    183   *   names as defined by `suggestion_type` in the remote settings records.
    184   */
    185  get dynamicRustSuggestionTypes() {
    186    return [];
    187  }
    188 
    189  /**
    190   * @returns {object|null}
    191   *   If the feature manages suggestions served by the Rust component that
    192   *   require provider constraints, the subclass should override this getter
    193   *   and return a plain JS object that can be passed to
    194   *   `SuggestionProviderConstraints()`. This getter will only be called if the
    195   *   feature is enabled.
    196   */
    197  get rustProviderConstraints() {
    198    if (this.dynamicRustSuggestionTypes?.length) {
    199      return {
    200        dynamicSuggestionTypes: this.dynamicRustSuggestionTypes,
    201      };
    202    }
    203    return null;
    204  }
    205 
    206  /**
    207   * @returns {string}
    208   *   If the feature's suggestions are served by the ML backend, the subclass
    209   *   should override this getter and return the ML intent name as returned by
    210   *   `MLSuggest`. e.g., "yelp_intent"
    211   */
    212  get mlIntent() {
    213    return "";
    214  }
    215 
    216  /**
    217   * @returns {boolean}
    218   *   If the feature's suggestions are served by the ML backend, the subclass
    219   *   should override this getter and return true if the ML suggestions should
    220   *   be enabled and false otherwise.
    221   */
    222  get isMlIntentEnabled() {
    223    return false;
    224  }
    225 
    226  /**
    227   * Subclasses should typically override this method. It should return the
    228   * telemetry type for the given suggestion. A telemetry type uniquely
    229   * identifies a type of Suggest suggestion independent of the backend that
    230   * returned it. It's used to build the result type values that are recorded in
    231   * urlbar telemetry. The telemetry type does not include the suggestion's
    232   * source/backend. For example, "adm_sponsored" is the AMP suggestion
    233   * telemetry type, not "rust_adm_sponsored".
    234   *
    235   * @param {object} suggestion
    236   *   A suggestion returned by one of the Suggest backends.
    237   * @returns {string}
    238   *   The suggestion's telemetry type.
    239   */
    240  getSuggestionTelemetryType(suggestion) {
    241    return this.merinoProvider;
    242  }
    243 
    244  /**
    245   * Gets the list of commands that should be shown in the result menu for a
    246   * given result from the provider. All commands returned by this method should
    247   * be handled by implementing `onEngagement()` with the possible exception of
    248   * commands automatically handled by the urlbar, like "help".
    249   *
    250   * @returns {?UrlbarResultCommand[]}
    251   */
    252  getResultCommand() {
    253    return undefined;
    254  }
    255 
    256  /**
    257   * The subclass should override this method if it manages any sponsored
    258   * suggestion types. It should return true if the given suggestion should be
    259   * considered sponsored.
    260   *
    261   * @param {object} suggestion
    262   *   A suggestion returned by one of the Suggest backends.
    263   * @returns {boolean}
    264   *   Whether the suggestion should be considered sponsored.
    265   */
    266  isSuggestionSponsored(suggestion) {
    267    return false;
    268  }
    269 
    270  /**
    271   * The subclass may override this method as necessary. It will be called once
    272   * per query with all of the feature's suggestions that matched the query. It
    273   * should filter out suggestions that should not be shown. This is useful in
    274   * cases where a backend may return many of the feature's suggestions but only
    275   * some of them should be shown, and the criteria for determining which to
    276   * show are external to the backend.
    277   *
    278   * `makeResult()` can also be used to filter suggestions by returning null for
    279   * suggestions that should be discarded. Use `filterSuggestions()` when you
    280   * need to know all matching suggestions in order to decide which to show.
    281   *
    282   * @param {Array} suggestions
    283   *   All the feature's suggestions that matched a query.
    284   * @returns {Promise<Array>}
    285   *   The subset of `suggestions` that should be shown (typically all).
    286   */
    287  async filterSuggestions(suggestions) {
    288    return suggestions;
    289  }
    290 
    291  /**
    292   * The subclass should override this method. It should return either a new
    293   * `UrlbarResult` for the given suggestion or null if the suggestion should
    294   * not be shown.
    295   *
    296   * @param {UrlbarQueryContext} queryContext
    297   *   The query context.
    298   * @param {object} suggestion
    299   *   A suggestion returned by one of the Suggest backends.
    300   * @param {string} searchString
    301   *   The search string that was used to fetch the suggestion. It may be
    302   *   different from `queryContext.searchString` due to trimming, lower-casing,
    303   *   etc. This is included as a param in case it's useful.
    304   * @returns {Promise<UrlbarResult|null>}
    305   *   A new result for the suggestion or null if a result should not be shown.
    306   */
    307  async makeResult(queryContext, suggestion, searchString) {
    308    return null;
    309  }
    310 
    311  /**
    312   * The subclass may override this method as necessary. It's analogous to
    313   * `UrlbarProvider.onImpression()` and will be called when one or more of the
    314   * feature's results were visible at the end of a urlbar session.
    315   *
    316   * @param {string} state
    317   *   The user-interaction state. See `UrlbarProvider.onImpression()`.
    318   * @param {UrlbarQueryContext} queryContext
    319   *   The urlbar session's query context.
    320   * @param {UrlbarController} controller
    321   *   The controller.
    322   * @param {Array} featureResults
    323   *   The feature's results that were visible at the end of the session. This
    324   *   will always be non-empty and will only contain results from the feature.
    325   * @param {object|null} details
    326   *   Details about the engagement. See `UrlbarProvider.onImpression()`.
    327   */
    328  onImpression(state, queryContext, controller, featureResults, details) {}
    329 
    330  /**
    331   * The subclass may override this method as necessary. It's analogous to
    332   * `UrlbarProvider.onEngagement()` and will be called when the user engages
    333   * with a result from the feature.
    334   *
    335   * @param {UrlbarQueryContext} queryContext
    336   *   The urlbar session's query context.
    337   * @param {UrlbarController} controller
    338   *   The controller.
    339   * @param {object|null} details
    340   *   See `UrlbarProvider.onEngagement()`.
    341   * @param {string} searchString
    342   *   The actual search string used to fetch Suggest results. It might be
    343   *   slightly different from `queryContext.searchString`. e.g., it might be
    344   *   trimmed differently.
    345   */
    346  onEngagement(queryContext, controller, details, searchString) {}
    347 
    348  /**
    349   * Some features may create result URLs that are potentially unique per query.
    350   * Typically this is done by modifying an original suggestion URL at query
    351   * time, for example by adding timestamps or query-specific search params. In
    352   * that case, a single original suggestion URL will map to many result URLs.
    353   * If this is true for the subclass, it should override this method and return
    354   * whether the given URL and result URL both map back to the same original
    355   * suggestion URL.
    356   *
    357   * @param {string} url
    358   *   The URL to check, typically from the user's history.
    359   * @param {UrlbarResult} result
    360   *   The Suggest result.
    361   * @returns {boolean}
    362   *   Whether `url` is equivalent to the result's URL.
    363   */
    364  isUrlEquivalentToResultUrl(url, result) {
    365    return url == result.payload.url;
    366  }
    367 
    368  // Methods not designed for overriding below
    369 
    370  /**
    371   * Enables or disables the feature. If the feature manages any Rust suggestion
    372   * types that become enabled as a result, they will be ingested.
    373   */
    374  update() {
    375    super.update();
    376    lazy.QuickSuggest.rustBackend?.ingestEnabledSuggestions(this);
    377  }
    378 }
    379 
    380 /**
    381 * Base class for Suggest features that serve suggestions. None of the methods
    382 * will be called when the backend is disabled.
    383 *
    384 * Subclasses should be registered with `QuickSuggest` by adding them to the
    385 * `FEATURES` const in `QuickSuggest.sys.mjs`.
    386 */
    387 export class SuggestBackend extends SuggestFeature {
    388  // Methods designed for overriding below
    389 
    390  /**
    391   * The subclass should override this method. It should fetch and return
    392   * matching suggestions.
    393   *
    394   * @param {string} searchString
    395   *   The search string.
    396   * @param {object} [options]
    397   *   Options object.
    398   * @param {UrlbarQueryContext} [options.queryContext]
    399   *   The query context.
    400   * @param {?Array} [options.types]
    401   *   This is only intended to be used in special circumstances and normally
    402   *   should not be specified. Array of suggestion types to query. By default
    403   *   all enabled suggestion types are queried.
    404   * @returns {Promise<Array>}
    405   *   Array of matching suggestions. An empty array should be returned if no
    406   *   suggestions matched or suggestions can't be fetched for any reason.
    407   * @abstract
    408   */
    409  async query(searchString, { queryContext, types = null } = {}) {
    410    throw new Error("Trying to access the base class, must be overridden");
    411  }
    412 
    413  /**
    414   * The subclass should override this method if anything needs to be stopped or
    415   * cleaned up when a query is canceled.
    416   */
    417  cancelQuery() {}
    418 
    419  /**
    420   * The subclass should override this method as necessary. It's called on the
    421   * backend in response to `UrlbarProviderQuickSuggest.onSearchSessionEnd()`.
    422   *
    423   * @param {UrlbarQueryContext} queryContext
    424   *    The query context.
    425   * @param {UrlbarController} controller
    426   *    The controller.
    427   * @param {object} details
    428   *    Details object.
    429   */
    430  onSearchSessionEnd(queryContext, controller, details) {}
    431 }