Bug 1944627 - update sidebar button checked state for non-revamped sidebar cases...
[gecko.git] / browser / components / urlbar / UrlbarProviderRestrictKeywordsAutofill.sys.mjs
blobcb8f3d308fd36e9a79fc4e1182772259a0a28978
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 /**
6  * This module exports a provider that offers restrict keywords autofill for
7  * search mode.
8  */
10 import {
11   UrlbarProvider,
12   UrlbarUtils,
13 } from "resource:///modules/UrlbarUtils.sys.mjs";
15 const lazy = {};
17 ChromeUtils.defineESModuleGetters(lazy, {
18   UrlbarPrefs: "resource:///modules/UrlbarPrefs.sys.mjs",
19   UrlbarResult: "resource:///modules/UrlbarResult.sys.mjs",
20   UrlbarTokenizer: "resource:///modules/UrlbarTokenizer.sys.mjs",
21 });
23 const RESTRICT_KEYWORDS_FEATURE_GATE = "searchRestrictKeywords.featureGate";
25 /**
26  * Class used to create the provider.
27  */
28 class ProviderRestrictKeywordsAutofill extends UrlbarProvider {
29   #autofillData;
30   #lowerCaseTokenToKeywords;
32   constructor() {
33     super();
34   }
36   get name() {
37     return "RestrictKeywordsAutofill";
38   }
40   get type() {
41     return UrlbarUtils.PROVIDER_TYPE.HEURISTIC;
42   }
44   getPriority() {
45     return 1;
46   }
48   async #getLowerCaseTokenToKeywords() {
49     let tokenToKeywords = await lazy.UrlbarTokenizer.getL10nRestrictKeywords();
51     this.#lowerCaseTokenToKeywords = new Map(
52       [...tokenToKeywords].map(([token, keywords]) => [
53         token,
54         keywords.map(keyword => keyword.toLowerCase()),
55       ])
56     );
58     return this.#lowerCaseTokenToKeywords;
59   }
61   async #getKeywordAliases() {
62     return Array.from(await this.#lowerCaseTokenToKeywords.values())
63       .flat()
64       .map(keyword => "@" + keyword);
65   }
67   async isActive(queryContext) {
68     if (!lazy.UrlbarPrefs.getScotchBonnetPref(RESTRICT_KEYWORDS_FEATURE_GATE)) {
69       return false;
70     }
72     this.#autofillData = null;
74     if (
75       queryContext.searchMode ||
76       queryContext.tokens.length != 1 ||
77       queryContext.searchString.length == 1 ||
78       queryContext.restrictSource ||
79       !queryContext.searchString.startsWith("@")
80     ) {
81       return false;
82     }
84     // Returns partial autofill result when the user types
85     // @h, @hi, @hist, etc.
86     if (lazy.UrlbarPrefs.get("autoFill") && queryContext.allowAutofill) {
87       let instance = this.queryInstance;
88       let result = await this.#getAutofillResult(queryContext);
89       if (result && instance == this.queryInstance) {
90         this.#autofillData = { result, instance };
91         return true;
92       }
93     }
95     // Returns full autofill result when user types keyword with space to
96     // enter seach mode. Example, "@history ".
97     let keywordAliases = await this.#getKeywordAliases();
98     if (
99       keywordAliases.some(keyword =>
100         keyword.startsWith(queryContext.trimmedLowerCaseSearchString)
101       )
102     ) {
103       return true;
104     }
106     return false;
107   }
109   async startQuery(queryContext, addCallback) {
110     if (
111       this.#autofillData &&
112       this.#autofillData.instance == this.queryInstance
113     ) {
114       addCallback(this, this.#autofillData.result);
115       return;
116     }
118     let instance = this.queryInstance;
119     let typedKeyword = queryContext.lowerCaseSearchString;
120     let typedKeywordTrimmed =
121       queryContext.trimmedLowerCaseSearchString.substring(1);
122     let tokenToKeywords = await this.#getLowerCaseTokenToKeywords();
124     if (instance != this.queryInstance) {
125       return;
126     }
128     let restrictSymbol;
129     let aliasKeyword;
131     for (let [token, keywords] of tokenToKeywords) {
132       if (keywords.includes(typedKeywordTrimmed)) {
133         restrictSymbol = token;
134         aliasKeyword = "@" + typedKeywordTrimmed + " ";
135         break;
136       }
137     }
139     if (restrictSymbol && typedKeyword == aliasKeyword) {
140       let result = new lazy.UrlbarResult(
141         UrlbarUtils.RESULT_TYPE.RESTRICT,
142         UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
143         ...lazy.UrlbarResult.payloadAndSimpleHighlights(queryContext.tokens, {
144           keyword: restrictSymbol,
145           providesSearchMode: false,
146         })
147       );
148       result.heuristic = true;
149       addCallback(this, result);
150     }
152     this.#autofillData = null;
153   }
155   cancelQuery() {
156     if (this.#autofillData?.instance == this.queryInstance) {
157       this.#autofillData = null;
158     }
159   }
161   async #getAutofillResult(queryContext) {
162     let tokenToKeywords = await this.#getLowerCaseTokenToKeywords();
163     let { lowerCaseSearchString } = queryContext;
165     for (let [token, l10nRestrictKeywords] of tokenToKeywords.entries()) {
166       let keywords = [...l10nRestrictKeywords].map(keyword => `@${keyword}`);
167       let autofillKeyword = keywords.find(keyword =>
168         keyword.startsWith(lowerCaseSearchString)
169       );
171       // found the keyword
172       if (autofillKeyword) {
173         // Add an autofill result. Append a space so the user can hit enter
174         // or the right arrow key and immediately start typing their query.
175         let keywordPreservingUserCase =
176           queryContext.searchString +
177           autofillKeyword.substr(queryContext.searchString.length);
178         let value = keywordPreservingUserCase + " ";
179         let icon = UrlbarUtils.LOCAL_SEARCH_MODES.find(
180           mode => mode.restrict == token
181         )?.icon;
183         let result = new lazy.UrlbarResult(
184           UrlbarUtils.RESULT_TYPE.RESTRICT,
185           UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
186           ...lazy.UrlbarResult.payloadAndSimpleHighlights(queryContext.tokens, {
187             icon,
188             keyword: token,
189             l10nRestrictKeywords: [
190               l10nRestrictKeywords,
191               UrlbarUtils.HIGHLIGHT.TYPED,
192             ],
193             autofillKeyword: [
194               keywordPreservingUserCase,
195               UrlbarUtils.HIGHLIGHT.TYPED,
196             ],
197             providesSearchMode: true,
198           })
199         );
201         result.autofill = {
202           value,
203           selectionStart: queryContext.searchString.length,
204           selectionEnd: value.length,
205         };
207         return result;
208       }
209     }
211     return null;
212   }
215 export var UrlbarProviderRestrictKeywordsAutofill =
216   new ProviderRestrictKeywordsAutofill();