Bug 1944416: Restore individual tabs from closed groups in closed windows r=dao,sessi...
[gecko.git] / browser / components / urlbar / ActionsProviderQuickActions.sys.mjs
blobffaf450660a0cc8ad7882f6dd3feedd90b1bf5b9
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/. */
5 import {
6   ActionsProvider,
7   ActionsResult,
8 } from "resource:///modules/ActionsProvider.sys.mjs";
10 const lazy = {};
11 ChromeUtils.defineESModuleGetters(lazy, {
12   QuickActionsLoaderDefault:
13     "resource:///modules/QuickActionsLoaderDefault.sys.mjs",
14   UrlbarPrefs: "resource:///modules/UrlbarPrefs.sys.mjs",
15 });
17 // These prefs are relative to the `browser.urlbar` branch.
18 const ENABLED_PREF = "suggest.quickactions";
19 const MATCH_IN_PHRASE_PREF = "quickactions.matchInPhrase";
20 const MIN_SEARCH_PREF = "quickactions.minimumSearchString";
22 /**
23  * A provider that matches the urlbar input to built in actions.
24  */
25 class ProviderQuickActions extends ActionsProvider {
26   get name() {
27     return "ActionsProviderQuickActions";
28   }
30   isActive(queryContext) {
31     return (
32       lazy.UrlbarPrefs.get(ENABLED_PREF) &&
33       !queryContext.searchMode &&
34       queryContext.trimmedSearchString.length < 50 &&
35       queryContext.trimmedSearchString.length >=
36         lazy.UrlbarPrefs.get(MIN_SEARCH_PREF)
37     );
38   }
40   async queryActions(queryContext) {
41     let input = queryContext.trimmedLowerCaseSearchString;
42     let results = await this.getActions(input);
44     if (lazy.UrlbarPrefs.get(MATCH_IN_PHRASE_PREF)) {
45       for (let [keyword, key] of this.#keywords) {
46         if (input.includes(keyword)) {
47           results.push(key);
48         }
49       }
50     }
52     // Remove invisible actions.
53     results = results.filter(key => {
54       const action = this.#actions.get(key);
55       return action.isVisible?.() ?? true;
56     });
58     if (!results.length) {
59       return null;
60     }
62     return results.map(key => {
63       let action = this.#actions.get(key);
64       return new ActionsResult({
65         key,
66         l10nId: action.label,
67         icon: action.icon,
68         dataset: {
69           action: key,
70           inputLength: queryContext.trimmedSearchString.length,
71         },
72         onPick: action.onPick,
73       });
74     });
75   }
77   async getActions(prefix) {
78     await lazy.QuickActionsLoaderDefault.ensureLoaded();
79     return [...(this.#prefixes.get(prefix) ?? [])];
80   }
82   getAction(key) {
83     return this.#actions.get(key);
84   }
86   pickAction(_queryContext, _controller, element) {
87     let action = element.dataset.action;
88     let inputLength = Math.min(element.dataset.inputLength, 10);
89     Glean.urlbarQuickaction.picked[`${action}-${inputLength}`].add(1);
90     let options = this.#actions.get(action).onPick();
91     if (options?.focusContent) {
92       element.ownerGlobal.gBrowser.selectedBrowser.focus();
93     }
94   }
96   /**
97    * Adds a new QuickAction.
98    *
99    * @param {string} key A key to identify this action.
100    * @param {string} definition An object that describes the action.
101    */
102   addAction(key, definition) {
103     this.#actions.set(key, definition);
104     definition.commands.forEach(cmd => this.#keywords.set(cmd, key));
105     this.#loopOverPrefixes(definition.commands, prefix => {
106       let result = this.#prefixes.get(prefix);
107       if (result) {
108         if (!result.includes(key)) {
109           result.push(key);
110         }
111       } else {
112         result = [key];
113       }
114       this.#prefixes.set(prefix, result);
115     });
116   }
118   /**
119    * Removes an action.
120    *
121    * @param {string} key A key to identify this action.
122    */
123   removeAction(key) {
124     let definition = this.#actions.get(key);
125     this.#actions.delete(key);
126     definition.commands.forEach(cmd => this.#keywords.delete(cmd));
127     this.#loopOverPrefixes(definition.commands, prefix => {
128       let result = this.#prefixes.get(prefix);
129       if (result) {
130         result = result.filter(val => val != key);
131       }
132       this.#prefixes.set(prefix, result);
133     });
134   }
136   // A map from keywords to an action.
137   #keywords = new Map();
139   // A map of all prefixes to an array of actions.
140   #prefixes = new Map();
142   // The actions that have been added.
143   #actions = new Map();
145   #loopOverPrefixes(commands, fun) {
146     for (const command of commands) {
147       // Loop over all the prefixes of the word, ie
148       // "", "w", "wo", "wor", stopping just before the full
149       // word itself which will be matched by the whole
150       // phrase matching.
151       for (let i = 1; i <= command.length; i++) {
152         let prefix = command.substring(0, command.length - i);
153         fun(prefix);
154       }
155     }
156   }
159 export var ActionsProviderQuickActions = new ProviderQuickActions();