DynamicSuggestions.sys.mjs (5767B)
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 QuickSuggest: "moz-src:///browser/components/urlbar/QuickSuggest.sys.mjs", 11 UrlbarPrefs: "moz-src:///browser/components/urlbar/UrlbarPrefs.sys.mjs", 12 UrlbarResult: "moz-src:///browser/components/urlbar/UrlbarResult.sys.mjs", 13 UrlbarUtils: "moz-src:///browser/components/urlbar/UrlbarUtils.sys.mjs", 14 }); 15 16 /** 17 * A feature that manages the dynamic Rust suggestion types defined in the 18 * `quickSuggestDynamicSuggestionTypes` Nimbus variable or its related pref 19 * `quicksuggest.dynamicSuggestionTypes`. Dynamic Rust suggestions are not 20 * statically typed except for a few core properties, so they can be used to 21 * serve many different types of suggestions without any Rust changes. They are 22 * also used for hidden-exposure suggestions (potential exposures). 23 * 24 * This feature only manages the types defined in the variable or pref. Other 25 * features can manage dynamic suggestion types that are *not* defined in the 26 * variable or pref by extending `SuggestProvider` as usual and overriding the 27 * `dynamicRustSuggestionTypes` getter. That makes it possible for a feature to 28 * use dynamic suggestions as an implementation detail. 29 */ 30 export class DynamicSuggestions extends SuggestProvider { 31 get enablingPreferences() { 32 return ["quicksuggest.dynamicSuggestionTypes"]; 33 } 34 35 get shouldEnable() { 36 return !!this.dynamicRustSuggestionTypes.length; 37 } 38 39 get rustSuggestionType() { 40 return "Dynamic"; 41 } 42 43 get dynamicRustSuggestionTypes() { 44 // UrlbarPrefs converts this pref to a `Set` of type strings. 45 return [...lazy.UrlbarPrefs.get("quicksuggest.dynamicSuggestionTypes")]; 46 } 47 48 isSuggestionSponsored(suggestion) { 49 return !!suggestion.data?.result?.payload?.isSponsored; 50 } 51 52 getSuggestionTelemetryType(suggestion) { 53 if (suggestion.data?.result?.payload?.hasOwnProperty("telemetryType")) { 54 return suggestion.data.result.payload.telemetryType; 55 } 56 if (suggestion.data?.result?.isHiddenExposure) { 57 return "exposure"; 58 } 59 return suggestion.suggestionType; 60 } 61 62 makeResult(queryContext, suggestion, _searchString) { 63 let { data } = suggestion; 64 if (!data || typeof data != "object") { 65 this.logger.warn( 66 "suggestion.data is falsey or not an object, ignoring suggestion" 67 ); 68 return null; 69 } 70 71 let { result } = data; 72 if (!result || typeof result != "object") { 73 this.logger.warn( 74 "suggestion.data.result is falsey or not an object, ignoring suggestion" 75 ); 76 return null; 77 } 78 79 let payload = {}; 80 if (result.hasOwnProperty("payload")) { 81 if (typeof result.payload != "object") { 82 this.logger.warn( 83 "suggestion.data.result.payload is not an object, ignoring suggestion" 84 ); 85 return null; 86 } 87 payload = result.payload; 88 } 89 90 if (result.isHiddenExposure) { 91 return this.#makeExposureResult(suggestion, payload); 92 } 93 94 // Dynamic results can set `result.bypassSuggestAll` to be shown even when 95 // `suggest.quicksuggest.all` is false. Typically results should not do this 96 // unless they aren't considered part of the Suggest brand. 97 if ( 98 !result.bypassSuggestAll && 99 (!lazy.UrlbarPrefs.get("suggest.quicksuggest.all") || 100 (payload.isSponsored && 101 !lazy.UrlbarPrefs.get("suggest.quicksuggest.sponsored"))) 102 ) { 103 return null; 104 } 105 106 payload.isManageable = true; 107 payload.helpUrl = lazy.QuickSuggest.HELP_URL; 108 109 if (!payload.title && payload.url) { 110 try { 111 // If there's no title, show the domain as the title. Not all valid URLs 112 // have a domain. 113 payload.title = new URL(payload.url).URI.displayHostPort; 114 } catch (e) {} 115 } 116 117 let resultProperties = { ...result }; 118 delete resultProperties.payload; 119 return new lazy.UrlbarResult({ 120 type: lazy.UrlbarUtils.RESULT_TYPE.URL, 121 source: lazy.UrlbarUtils.RESULT_SOURCE.SEARCH, 122 ...resultProperties, 123 payload, 124 }); 125 } 126 127 onEngagement(_queryContext, controller, details, _searchString) { 128 switch (details.selType) { 129 case "manage": 130 // "manage" is handled by UrlbarInput, no need to do anything here. 131 break; 132 case "dismiss": { 133 let { result } = details; 134 lazy.QuickSuggest.dismissResult(result); 135 result.acknowledgeDismissalL10n = { 136 id: "firefox-suggest-dismissal-acknowledgment-one", 137 }; 138 controller.removeResult(result); 139 break; 140 } 141 } 142 } 143 144 #makeExposureResult(suggestion, payload) { 145 // It doesn't really matter what kind of result we return since it won't be 146 // shown. Use a dynamic result since that kind of makes sense and there are 147 // no requirements for its payload other than `dynamicType`. 148 return new lazy.UrlbarResult({ 149 type: lazy.UrlbarUtils.RESULT_TYPE.DYNAMIC, 150 source: lazy.UrlbarUtils.RESULT_SOURCE.SEARCH, 151 // Exposure suggestions should always be hidden, and it's assumed that 152 // exposure telemetry should be recorded for them, so as a convenience 153 // set `exposureTelemetry` here. Otherwise experiments would need to set 154 // the corresponding Nimbus variables properly. (They can still do that, 155 // it's just not required.) 156 exposureTelemetry: lazy.UrlbarUtils.EXPOSURE_TELEMETRY.HIDDEN, 157 payload: { 158 ...payload, 159 dynamicType: "exposure", 160 }, 161 }); 162 } 163 }