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 }