UrlbarProviderGlobalActions.sys.mjs (6718B)
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 returns all the available 7 * global actions for a query. 8 */ 9 10 import { 11 UrlbarProvider, 12 UrlbarUtils, 13 } from "moz-src:///browser/components/urlbar/UrlbarUtils.sys.mjs"; 14 15 const lazy = {}; 16 17 // Default icon shown for actions if no custom one is provided. 18 const DEFAULT_ICON = "chrome://global/skin/icons/settings.svg"; 19 const DYNAMIC_TYPE_NAME = "actions"; 20 21 // The suggestion index of the actions row within the urlbar results. 22 const SUGGESTED_INDEX = 1; 23 const SUGGESTED_INDEX_TABS_MODE = 0; 24 25 const SCOTCH_BONNET_PREF = "scotchBonnet.enableOverride"; 26 const ACTIONS_PREF = "secondaryActions.featureGate"; 27 const QUICK_ACTIONS_PREF = "suggest.quickactions"; 28 const MAX_ACTIONS_PREF = "secondaryActions.maxActionsShown"; 29 30 // Prefs relating to the onboarding label shown to new users. 31 const TIMES_TO_SHOW_PREF = "quickactions.timesToShowOnboardingLabel"; 32 const TIMES_SHOWN_PREF = "quickactions.timesShownOnboardingLabel"; 33 34 ChromeUtils.defineESModuleGetters(lazy, { 35 UrlbarPrefs: "moz-src:///browser/components/urlbar/UrlbarPrefs.sys.mjs", 36 UrlbarResult: "moz-src:///browser/components/urlbar/UrlbarResult.sys.mjs", 37 }); 38 39 import { ActionsProviderQuickActions } from "moz-src:///browser/components/urlbar/ActionsProviderQuickActions.sys.mjs"; 40 import { ActionsProviderContextualSearch } from "moz-src:///browser/components/urlbar/ActionsProviderContextualSearch.sys.mjs"; 41 import { ActionsProviderTabGroups } from "moz-src:///browser/components/urlbar/ActionsProviderTabGroups.sys.mjs"; 42 43 let globalActionsProviders = [ 44 ActionsProviderContextualSearch, 45 ActionsProviderQuickActions, 46 ActionsProviderTabGroups, 47 ]; 48 49 /** 50 * A provider that lets the user view all available global actions for a query. 51 */ 52 export class UrlbarProviderGlobalActions extends UrlbarProvider { 53 /** 54 * @returns {Values<typeof UrlbarUtils.PROVIDER_TYPE>} 55 */ 56 get type() { 57 return UrlbarUtils.PROVIDER_TYPE.PROFILE; 58 } 59 60 async isActive(_queryContext) { 61 return ( 62 (lazy.UrlbarPrefs.get(SCOTCH_BONNET_PREF) || 63 lazy.UrlbarPrefs.get(ACTIONS_PREF)) && 64 lazy.UrlbarPrefs.get(QUICK_ACTIONS_PREF) 65 ); 66 } 67 68 /** 69 * Starts querying. 70 * 71 * @param {UrlbarQueryContext} queryContext 72 * @param {(provider: UrlbarProvider, result: UrlbarResult) => void} addCallback 73 * Callback invoked by the provider to add a new result. 74 */ 75 async startQuery(queryContext, addCallback) { 76 let actionsResults = []; 77 78 for (let provider of globalActionsProviders) { 79 if (provider.isActive(queryContext)) { 80 for (let action of (await provider.queryActions(queryContext)) || []) { 81 action.providerName = provider.name; 82 actionsResults.push(action); 83 } 84 } 85 } 86 87 if (!actionsResults.length) { 88 return; 89 } 90 91 if (actionsResults.length > lazy.UrlbarPrefs.get(MAX_ACTIONS_PREF)) { 92 actionsResults.length = lazy.UrlbarPrefs.get(MAX_ACTIONS_PREF); 93 } 94 95 let showOnboardingLabel = 96 lazy.UrlbarPrefs.get(TIMES_TO_SHOW_PREF) > 97 lazy.UrlbarPrefs.get(TIMES_SHOWN_PREF); 98 99 let query = actionsResults.some(a => a.key == "matched-contextual-search") 100 ? "" 101 : queryContext.searchString; 102 103 let payload = { 104 actionsResults, 105 dynamicType: DYNAMIC_TYPE_NAME, 106 inputLength: queryContext.searchString.length, 107 input: query, 108 showOnboardingLabel, 109 query, 110 }; 111 112 let result = new lazy.UrlbarResult({ 113 type: UrlbarUtils.RESULT_TYPE.DYNAMIC, 114 source: UrlbarUtils.RESULT_SOURCE.ACTIONS, 115 suggestedIndex: 116 queryContext.restrictSource == UrlbarUtils.RESULT_SOURCE.TABS 117 ? SUGGESTED_INDEX_TABS_MODE 118 : SUGGESTED_INDEX, 119 payload, 120 }); 121 addCallback(this, result); 122 } 123 124 onSelection(result, element) { 125 let key = element.dataset.action; 126 let action = result.payload.actionsResults.find(a => a.key == key); 127 action.onSelection?.(result, element); 128 } 129 130 onEngagement(queryContext, controller, details) { 131 let key = details.element.dataset.action; 132 let action = details.result.payload.actionsResults.find(a => a.key == key); 133 let options = action.onPick(queryContext, controller); 134 if (options?.focusContent) { 135 details.element.ownerGlobal.gBrowser.selectedBrowser.focus(); 136 } 137 controller.view.close(); 138 } 139 140 onSearchSessionEnd(queryContext, controller, details) { 141 let showOnboardingLabel = queryContext.results?.find( 142 r => r.providerName == this.name 143 )?.payload.showOnboardingLabel; 144 if (showOnboardingLabel) { 145 lazy.UrlbarPrefs.set( 146 TIMES_SHOWN_PREF, 147 lazy.UrlbarPrefs.get(TIMES_SHOWN_PREF) + 1 148 ); 149 } 150 for (let provider of globalActionsProviders) { 151 provider.onSearchSessionEnd?.(queryContext, controller, details); 152 } 153 } 154 155 getViewTemplate(result) { 156 let children = result.payload.actionsResults.map((action, i) => { 157 let btn = { 158 name: `button-${i}`, 159 tag: "span", 160 classList: ["urlbarView-action-btn"], 161 attributes: { 162 inputLength: result.payload.inputLength, 163 "data-action": action.key, 164 role: "button", 165 }, 166 children: [ 167 { 168 tag: "img", 169 attributes: { 170 src: action.icon || DEFAULT_ICON, 171 }, 172 }, 173 { 174 name: `label-${i}`, 175 tag: "span", 176 classList: ["urlbarView-action-btn-label"], 177 }, 178 ], 179 }; 180 181 if (action.dataset?.style) { 182 let style = ""; 183 for (let [prop, val] of Object.entries(action.dataset.style)) { 184 style += `${prop}: ${val};`; 185 } 186 btn.attributes.style = style; 187 } 188 189 if (action.dataset?.providesSearchMode) { 190 btn.attributes["data-provides-searchmode"] = "true"; 191 btn.attributes["data-engine"] = action.engine; 192 } 193 194 return btn; 195 }); 196 197 if (result.payload.showOnboardingLabel) { 198 children.unshift({ 199 name: "press-tab-label", 200 tag: "span", 201 classList: ["urlbarView-press-tab-label"], 202 }); 203 } 204 205 return { children }; 206 } 207 208 getViewUpdate(result) { 209 let viewUpdate = {}; 210 if (result.payload.showOnboardingLabel) { 211 viewUpdate["press-tab-label"] = { 212 l10n: { id: "press-tab-label" }, 213 }; 214 } 215 result.payload.actionsResults.forEach((action, i) => { 216 viewUpdate[`label-${i}`] = { 217 l10n: { id: action.l10nId, args: action.l10nArgs }, 218 }; 219 }); 220 return viewUpdate; 221 } 222 }