tor-browser

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

AddonSuggestions.sys.mjs (6620B)


      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 import { SuggestProvider } from "moz-src:///browser/components/urlbar/private/SuggestFeature.sys.mjs";
      6 
      7 const lazy = {};
      8 
      9 ChromeUtils.defineESModuleGetters(lazy, {
     10  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
     11  QuickSuggest: "moz-src:///browser/components/urlbar/QuickSuggest.sys.mjs",
     12  UrlbarPrefs: "moz-src:///browser/components/urlbar/UrlbarPrefs.sys.mjs",
     13  UrlbarResult: "moz-src:///browser/components/urlbar/UrlbarResult.sys.mjs",
     14  UrlbarUtils: "moz-src:///browser/components/urlbar/UrlbarUtils.sys.mjs",
     15 });
     16 
     17 const UTM_PARAMS = {
     18  utm_medium: "firefox-desktop",
     19  utm_source: "firefox-suggest",
     20 };
     21 
     22 const RESULT_MENU_COMMAND = {
     23  MANAGE: "manage",
     24  NOT_INTERESTED: "not_interested",
     25  NOT_RELEVANT: "not_relevant",
     26  SHOW_LESS_FREQUENTLY: "show_less_frequently",
     27 };
     28 
     29 /**
     30 * A feature that supports Addon suggestions.
     31 */
     32 export class AddonSuggestions extends SuggestProvider {
     33  get enablingPreferences() {
     34    return ["addonsFeatureGate", "suggest.addons", "suggest.quicksuggest.all"];
     35  }
     36 
     37  get primaryUserControlledPreferences() {
     38    return ["suggest.addons"];
     39  }
     40 
     41  get merinoProvider() {
     42    return "amo";
     43  }
     44 
     45  get rustSuggestionType() {
     46    return "Amo";
     47  }
     48 
     49  async makeResult(queryContext, suggestion, searchString) {
     50    if (!this.isEnabled) {
     51      // The feature is disabled on the client, but Merino may still return
     52      // addon suggestions anyway, and we filter them out here.
     53      return null;
     54    }
     55 
     56    // If the user hasn't clicked the "Show less frequently" command, the
     57    // suggestion can be shown. Otherwise, the suggestion can be shown if the
     58    // user typed more than one word with at least `showLessFrequentlyCount`
     59    // characters after the first word, including spaces.
     60    if (this.showLessFrequentlyCount) {
     61      let spaceIndex = searchString.search(/\s/);
     62      if (
     63        spaceIndex < 0 ||
     64        searchString.length - spaceIndex < this.showLessFrequentlyCount
     65      ) {
     66        return null;
     67      }
     68    }
     69 
     70    const { guid } =
     71      suggestion.source === "merino"
     72        ? suggestion.custom_details.amo
     73        : suggestion;
     74 
     75    const addon = await lazy.AddonManager.getAddonByID(guid);
     76    if (addon) {
     77      // Addon suggested is already installed.
     78      return null;
     79    }
     80 
     81    // Set UTM params unless they're already defined. This allows remote
     82    // settings or Merino to override them if need be.
     83    let url = new URL(suggestion.url);
     84    for (let [key, value] of Object.entries(UTM_PARAMS)) {
     85      if (!url.searchParams.has(key)) {
     86        url.searchParams.set(key, value);
     87      }
     88    }
     89 
     90    return new lazy.UrlbarResult({
     91      type: lazy.UrlbarUtils.RESULT_TYPE.URL,
     92      source: lazy.UrlbarUtils.RESULT_SOURCE.SEARCH,
     93      isBestMatch: true,
     94      suggestedIndex: 1,
     95      isRichSuggestion: true,
     96      richSuggestionIconSize: 24,
     97      showFeedbackMenu: true,
     98      payload: {
     99        url: url.href,
    100        originalUrl: suggestion.url,
    101        shouldShowUrl: true,
    102        // Rust uses `iconUrl` but Merino uses `icon`.
    103        icon: suggestion.iconUrl ?? suggestion.icon,
    104        title: suggestion.title,
    105        description: suggestion.description,
    106        bottomTextL10n: {
    107          id: "firefox-suggest-addons-recommended",
    108        },
    109        helpUrl: lazy.QuickSuggest.HELP_URL,
    110      },
    111    });
    112  }
    113 
    114  /**
    115   * Gets the list of commands that should be shown in the result menu for a
    116   * given result from the provider. All commands returned by this method should
    117   * be handled by implementing `onEngagement()` with the possible exception of
    118   * commands automatically handled by the urlbar, like "help".
    119   */
    120  getResultCommands() {
    121    /** @type {UrlbarResultCommand[]} */
    122    const commands = [];
    123 
    124    if (this.canShowLessFrequently) {
    125      commands.push({
    126        name: RESULT_MENU_COMMAND.SHOW_LESS_FREQUENTLY,
    127        l10n: {
    128          id: "urlbar-result-menu-show-less-frequently",
    129        },
    130      });
    131    }
    132 
    133    commands.push(
    134      {
    135        l10n: {
    136          id: "firefox-suggest-command-dont-show-this",
    137        },
    138        children: [
    139          {
    140            name: RESULT_MENU_COMMAND.NOT_RELEVANT,
    141            l10n: {
    142              id: "firefox-suggest-command-not-relevant",
    143            },
    144          },
    145          {
    146            name: RESULT_MENU_COMMAND.NOT_INTERESTED,
    147            l10n: {
    148              id: "firefox-suggest-command-not-interested",
    149            },
    150          },
    151        ],
    152      },
    153      { name: "separator" },
    154      {
    155        name: RESULT_MENU_COMMAND.MANAGE,
    156        l10n: {
    157          id: "urlbar-result-menu-manage-firefox-suggest",
    158        },
    159      }
    160    );
    161 
    162    return commands;
    163  }
    164 
    165  onEngagement(queryContext, controller, details, _searchString) {
    166    let { result } = details;
    167    switch (details.selType) {
    168      case RESULT_MENU_COMMAND.MANAGE:
    169        // "manage" is handled by UrlbarInput, no need to do anything here.
    170        break;
    171      // selType == "dismiss" when the user presses the dismiss key shortcut.
    172      case "dismiss":
    173      case RESULT_MENU_COMMAND.NOT_RELEVANT:
    174        lazy.QuickSuggest.dismissResult(result);
    175        result.acknowledgeDismissalL10n = {
    176          id: "firefox-suggest-dismissal-acknowledgment-one",
    177        };
    178        controller.removeResult(result);
    179        break;
    180      case RESULT_MENU_COMMAND.NOT_INTERESTED:
    181        lazy.UrlbarPrefs.set("suggest.addons", false);
    182        result.acknowledgeDismissalL10n = {
    183          id: "urlbar-result-dismissal-acknowledgment-all",
    184        };
    185        controller.removeResult(result);
    186        break;
    187      case RESULT_MENU_COMMAND.SHOW_LESS_FREQUENTLY:
    188        controller.view.acknowledgeFeedback(result);
    189        this.incrementShowLessFrequentlyCount();
    190        if (!this.canShowLessFrequently) {
    191          controller.view.invalidateResultMenuCommands();
    192        }
    193        break;
    194    }
    195  }
    196 
    197  incrementShowLessFrequentlyCount() {
    198    if (this.canShowLessFrequently) {
    199      lazy.UrlbarPrefs.set(
    200        "addons.showLessFrequentlyCount",
    201        this.showLessFrequentlyCount + 1
    202      );
    203    }
    204  }
    205 
    206  get showLessFrequentlyCount() {
    207    const count = lazy.UrlbarPrefs.get("addons.showLessFrequentlyCount") || 0;
    208    return Math.max(count, 0);
    209  }
    210 
    211  get canShowLessFrequently() {
    212    const cap =
    213      lazy.UrlbarPrefs.get("addonsShowLessFrequentlyCap") ||
    214      lazy.QuickSuggest.config.showLessFrequentlyCap ||
    215      0;
    216    return !cap || this.showLessFrequentlyCount < cap;
    217  }
    218 }