UrlbarProviderOmnibox.sys.mjs (5422B)
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 class that is used for providers created by 7 * extensions using the `omnibox` API. 8 */ 9 10 import { 11 SkippableTimer, 12 UrlbarProvider, 13 UrlbarUtils, 14 } from "moz-src:///browser/components/urlbar/UrlbarUtils.sys.mjs"; 15 16 const lazy = {}; 17 18 ChromeUtils.defineESModuleGetters(lazy, { 19 ExtensionSearchHandler: 20 "resource://gre/modules/ExtensionSearchHandler.sys.mjs", 21 22 UrlbarPrefs: "moz-src:///browser/components/urlbar/UrlbarPrefs.sys.mjs", 23 UrlbarResult: "moz-src:///browser/components/urlbar/UrlbarResult.sys.mjs", 24 }); 25 26 /** 27 * This provider handles results returned by extensions using the WebExtensions 28 * Omnibox API. If the user types a registered keyword, we send subsequent 29 * keystrokes to the extension. 30 */ 31 export class UrlbarProviderOmnibox extends UrlbarProvider { 32 constructor() { 33 super(); 34 } 35 36 /** 37 * @returns {Values<typeof UrlbarUtils.PROVIDER_TYPE>} 38 */ 39 get type() { 40 return UrlbarUtils.PROVIDER_TYPE.HEURISTIC; 41 } 42 43 /** 44 * Whether the provider should be invoked for the given context. If this 45 * method returns false, the providers manager won't start a query with this 46 * provider, to save on resources. 47 * 48 * @param {UrlbarQueryContext} queryContext 49 * The query context object. 50 */ 51 async isActive(queryContext) { 52 if ( 53 queryContext.tokens[0] && 54 queryContext.tokens[0].value.length && 55 lazy.ExtensionSearchHandler.isKeywordRegistered( 56 queryContext.tokens[0].value 57 ) && 58 UrlbarUtils.substringAfter( 59 queryContext.searchString, 60 queryContext.tokens[0].value 61 ) && 62 !queryContext.searchMode 63 ) { 64 return true; 65 } 66 67 // We need to handle cancellation here since isActive is called once per 68 // query but cancelQuery can be called multiple times per query. 69 // The frequent cancels can cause the extension's state to drift from the 70 // provider's state. 71 if (lazy.ExtensionSearchHandler.hasActiveInputSession()) { 72 lazy.ExtensionSearchHandler.handleInputCancelled(); 73 } 74 75 return false; 76 } 77 78 /** 79 * Gets the provider's priority. 80 * 81 * @returns {number} 82 * The provider's priority for the given query. 83 */ 84 getPriority() { 85 return 0; 86 } 87 88 /** 89 * Starts querying. 90 * 91 * @param {UrlbarQueryContext} queryContext 92 * @param {(provider: UrlbarProvider, result: UrlbarResult) => void} addCallback 93 * Callback invoked by the provider to add a new result. 94 */ 95 async startQuery(queryContext, addCallback) { 96 let instance = this.queryInstance; 97 98 // Fetch heuristic result. 99 let keyword = queryContext.tokens[0].value; 100 let description = lazy.ExtensionSearchHandler.getDescription(keyword); 101 let heuristicResult = new lazy.UrlbarResult({ 102 type: UrlbarUtils.RESULT_TYPE.OMNIBOX, 103 source: UrlbarUtils.RESULT_SOURCE.ADDON, 104 heuristic: true, 105 payload: { 106 title: description, 107 content: queryContext.searchString, 108 keyword: queryContext.tokens[0].value, 109 icon: UrlbarUtils.ICON.EXTENSION, 110 }, 111 highlights: { 112 title: UrlbarUtils.HIGHLIGHT.TYPED, 113 content: UrlbarUtils.HIGHLIGHT.TYPED, 114 keyword: UrlbarUtils.HIGHLIGHT.TYPED, 115 }, 116 }); 117 addCallback(this, heuristicResult); 118 119 // Fetch non-heuristic results. 120 let data = { 121 keyword, 122 text: queryContext.searchString, 123 inPrivateWindow: queryContext.isPrivate, 124 }; 125 let resultsPromise = lazy.ExtensionSearchHandler.handleSearch( 126 data, 127 suggestions => { 128 if (instance != this.queryInstance) { 129 return; 130 } 131 for (let suggestion of suggestions) { 132 let content = `${queryContext.tokens[0].value} ${suggestion.content}`; 133 if (content == heuristicResult.payload.content) { 134 continue; 135 } 136 let result = new lazy.UrlbarResult({ 137 type: UrlbarUtils.RESULT_TYPE.OMNIBOX, 138 source: UrlbarUtils.RESULT_SOURCE.ADDON, 139 payload: { 140 title: suggestion.description, 141 content, 142 keyword: queryContext.tokens[0].value, 143 isBlockable: suggestion.deletable, 144 icon: UrlbarUtils.ICON.EXTENSION, 145 }, 146 highlights: { 147 title: UrlbarUtils.HIGHLIGHT.TYPED, 148 content: UrlbarUtils.HIGHLIGHT.TYPED, 149 keyword: UrlbarUtils.HIGHLIGHT.TYPED, 150 }, 151 }); 152 addCallback(this, result); 153 } 154 } 155 ); 156 157 // Since the extension has no way to signal when it's done pushing results, 158 // we add a timer racing with the addition. 159 let timeoutPromise = new SkippableTimer({ 160 name: "ProviderOmnibox", 161 time: lazy.UrlbarPrefs.get("extension.omnibox.timeout"), 162 logger: this.logger, 163 }).promise; 164 await Promise.race([timeoutPromise, resultsPromise]).catch(ex => 165 this.logger.error(ex) 166 ); 167 } 168 169 onEngagement(queryContext, controller, details) { 170 let { result } = details; 171 if (details.selType == "dismiss" && result.payload.isBlockable) { 172 lazy.ExtensionSearchHandler.handleInputDeleted(result.payload.title); 173 controller.removeResult(result); 174 } 175 } 176 }