tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 3673fd430a31be72e32aa2ad966e4755a4a5e810
parent e611e2d43ed722928fbcbfc944857227765acbda
Author: Mark Banner <standard8@mozilla.com>
Date:   Sat, 15 Nov 2025 14:10:48 +0000

Bug 2000003 - Add and improve TypeScript definitions on the SearchService. r=search-reviewers,scunnane

Most of these are copied, or based on, the descriptions in the current nsISearchService.idl file.

Differential Revision: https://phabricator.services.mozilla.com/D272620

Diffstat:
Mtoolkit/components/search/OpenSearchLoader.sys.mjs | 2+-
Mtoolkit/components/search/SearchService.sys.mjs | 231++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Mtoolkit/components/search/SearchUtils.sys.mjs | 7++++---
Mtoolkit/components/search/nsISearchService.idl | 4+++-
Mtools/@types/generated/lib.gecko.dom.d.ts | 3++-
Mtools/@types/generated/lib.gecko.glean.d.ts | 15++++++++++++---
Mtools/@types/generated/lib.gecko.xpcom.d.ts | 4+---
7 files changed, 228 insertions(+), 38 deletions(-)

diff --git a/toolkit/components/search/OpenSearchLoader.sys.mjs b/toolkit/components/search/OpenSearchLoader.sys.mjs @@ -95,7 +95,7 @@ const MOZSEARCH_LOCALNAME = "SearchPlugin"; * The uri from which to load the OpenSearch engine data. * @param {string} [lastModified] * The UTC date when the engine was last updated, if any. - * @param {object} [originAttributes] + * @param {OriginAttributesDictionary} [originAttributes] * The origin attributes of the site loading the manifest. If none are * specified, the origin attributes will be formed of the first party domain * based on the domain of the manifest. diff --git a/toolkit/components/search/SearchService.sys.mjs b/toolkit/components/search/SearchService.sys.mjs @@ -54,6 +54,7 @@ const lazy = XPCOMUtils.declareLazy({ }); /** + * @import {AppProvidedConfigEngine} from "ConfigSearchEngine.sys.mjs" * @import {AddonSearchEngine} from "AddonSearchEngine.sys.mjs" * @import {OpenSearchEngine} from "OpenSearchEngine.sys.mjs" * @import {SearchEngine} from "SearchEngine.sys.mjs" @@ -149,46 +150,43 @@ const REASON_CHANGE_MAP = new Map([ * @implements {nsISearchParseSubmissionResult} */ class ParseSubmissionResult { + /** + * @param {?nsISearchEngine} engine + * @param {string} terms + * @param {string} termsParameterName + */ constructor(engine, terms, termsParameterName) { this.#engine = engine; this.#terms = terms; this.#termsParameterName = termsParameterName; } - get engine() { - return this.#engine; - } - - get terms() { - return this.#terms; - } - - get termsParameterName() { - return this.#termsParameterName; - } - /** * The search engine associated with the URL passed in to * nsISearchEngine::parseSubmissionURL, or null if the URL does not represent * a search submission. - * - * @type {nsISearchEngine|null} */ - #engine; + get engine() { + return this.#engine; + } /** * String containing the sought terms. This can be an empty string in case no * terms were specified or the URL does not represent a search submission. - * - * @type {string} */ - #terms; + get terms() { + return this.#terms; + } /** * The name of the query parameter used by `engine` for queries. E.g. "q". - * - * @type {string} */ + get termsParameterName() { + return this.#termsParameterName; + } + + #engine; + #terms; #termsParameterName; QueryInterface = ChromeUtils.generateQI(["nsISearchParseSubmissionResult"]); @@ -211,31 +209,75 @@ export class SearchService { classID = Components.ID("{7319788a-fe93-4db3-9f39-818cf08f4256}"); + /** + * The currently active search engine. + * Unless the application doesn't ship any search engine, this should never + * be null. If the currently active engine is removed, this attribute will + * fallback first to the application default engine if it's not hidden, then to + * the first visible engine, and as a last resort it will unhide the app + * default engine. + */ get defaultEngine() { this.#ensureInitialized(); return this._getEngineDefault(false); } + /** + * The currently active search engine for private browsing mode. + * + * @see defaultEngine + */ get defaultPrivateEngine() { this.#ensureInitialized(); return this._getEngineDefault(this.#separatePrivateDefault); } + /** + * The currently active search engine. + * Unless the application doesn't ship any search engine, this should never + * be null. If the currently active engine is removed, this attribute will + * fallback first to the application default engine if it's not hidden, then to + * the first visible engine, and as a last resort it will unhide the app + * default engine. + */ async getDefault() { await this.init(); return this.defaultEngine; } + /** + * Sets the currently active search engine. + * + * @param {SearchEngine} engine + * The engine to set the default to. + * @param {nsISearchService.DefaultEngineChangeReason} changeReason + * The reason the default engine is being changed, used for recording to + * telemetry. + */ async setDefault(engine, changeReason) { await this.init(); this.#setEngineDefault(false, engine, changeReason); } + /** + * The currently active search engine for private browsing mode. + * + * @see defaultPrivateEngine + */ async getDefaultPrivate() { await this.init(); return this.defaultPrivateEngine; } + /** + * Sets the currently active default private search engine. + * + * @param {SearchEngine} engine + * The engine to set the default to. + * @param {nsISearchService.DefaultEngineChangeReason} changeReason + * The reason the default engine is being changed, used for recording to + * telemetry. + */ async setDefaultPrivate(engine, changeReason) { await this.init(); if (!this.#lazyPrefs.separatePrivateDefaultPrefValue) { @@ -307,7 +349,7 @@ export class SearchService { * A promise that is resolved when initialization has finished. This does not * trigger initialization to begin. * - * @returns {Promise} + * @returns {Promise<void>} * Resolved when initalization has successfully finished, and rejected if it * has failed. */ @@ -315,8 +357,39 @@ export class SearchService { return this.#initDeferredPromise.promise; } + /** + * Gets a representation of the default engine in an anonymized JSON + * string suitable for recording in the Telemetry environment. + * + * @typedef {object} SearchEngineTelemetryInfo + * @property {string} name + * The user given name of the search engine. + * @property {string} loadPath + * The load path for the search engine. + * @property {string} [submissionURL] + * The submission URL for the search engine, only reported for a select + * list of domains. See `#getEngineInfo()` for more info. + * + * @typedef {object} EnginesTelemetryInfo + * Contains anonymized info about the default engine(s). + * @property {string} defaultSearchEngine + * The telemetry id of the default engine. + * @property {SearchEngineTelemetryInfo} defaultSearchEngineData + * Information about the default engine. + * @property {string} [defaultPrivateSearchEngine] + * Only returned if the preference for having a separate engine in private + * mode is turned on. + * The telemetry id of the default engine for private browsing mode. + * @property {SearchEngineTelemetryInfo} [defaultPrivateSearchEngineData] + * Only returned if the preference for having a separate engine in private + * mode is turned on. + * Information about the default engine for private browsing mode. + * + * @returns {EnginesTelemetryInfo} + */ getDefaultEngineInfo() { let engineInfo = this.#getEngineInfo(this.defaultEngine); + /** @type {EnginesTelemetryInfo} */ const result = { defaultSearchEngine: engineInfo.telemetryId, defaultSearchEngineData: { @@ -414,28 +487,52 @@ export class SearchService { return null; } + /** + * Returns an array of all installed search engines. + * The array is sorted either to the user requirements or the default order. + * + * @returns {Promise<SearchEngine[]>} + */ async getEngines() { await this.init(); lazy.logConsole.debug("getEngines: getting all engines"); return this.#sortedEngines; } + /** + * Returns an array of all installed search engines whose hidden attribute is + * false. + * The array is sorted either to the user requirements or the default order. + * + * @returns {Promise<SearchEngine[]>} + */ async getVisibleEngines() { await this.init(); lazy.logConsole.debug("getVisibleEngines: getting all visible engines"); return this.#sortedVisibleEngines; } + /** + * Returns the current list of application provided engines. + */ async getAppProvidedEngines() { await this.init(); return lazy.SearchUtils.sortEnginesByDefaults({ - engines: this.#sortedEngines.filter(e => e.isAppProvided), + engines: this.#sortedEngines.filter( + e => e instanceof lazy.AppProvidedConfigEngine + ), appDefaultEngine: this.appDefaultEngine, appPrivateDefaultEngine: this.appPrivateDefaultEngine, }); } + /** + * Returns an engine definition if it's search url matches the host provided. + * + * @param {string} host + * The host to search for. + */ async findContextualSearchEngineByHost(host) { await this.init(); let settings = await this._settings.get(); @@ -447,6 +544,17 @@ export class SearchService { return null; } + /** + * Returns whether the user should be given a prompt to install the + * engine they are currently using. A prompt is shown after the + * second time a user picks a contextual engine to search with. After + * the second time the prompt should not be shown again. + * + * @param {SearchEngine} engine + * The engine to check. + * @returns {Promise<boolean>} + * Whether or not to show the prompt. + */ async shouldShowInstallPrompt(engine) { let identifer = engine._loadPath; let seenEngines = @@ -474,10 +582,10 @@ export class SearchService { } /** - * This function calls #init to start initialization when it has not been - * started yet. Otherwise, it returns the pending promise. + * Starts initialisation if necessary, otherwise returns a promise which indicates + * the state of initialisation. * - * @returns {Promise} + * @returns {Promise<void>} * Returns the pending Promise when #init has started but not yet finished. * | Resolved | when initialization has successfully finished. * | Rejected | when initialization has failed. @@ -559,6 +667,9 @@ export class SearchService { ); } + /** + * Resets the default engine to its app default engine value. + */ resetToAppDefaultEngine() { let appDefaultEngine = this.appDefaultEngine; appDefaultEngine.hidden = false; @@ -694,6 +805,12 @@ export class SearchService { return newEngine; } + /** + * Installs an engine into the user's engine list. + * + * @param {SearchEngine} engine + * An engine configuration definition. + */ async addSearchEngine(engine) { await this.init(); this.#addEngineToStore(engine); @@ -746,6 +863,20 @@ export class SearchService { }); } + /** + * Adds a new Open Search engine from the xml file at the supplied URI. + * + * @param {string} engineURL + * The URL to the search engine's description file. + * @param {string} iconURL + * A URL string to an icon file to be used as the search engine's icon. This + * value may be overridden by an icon specified in the engine description + * file. + * @param {OriginAttributesDictionary} [originAttributes] + * The origin attributes to use to load this manifest. + * @throws {Cr.NS_ERROR_FAILURE} + * If the description file cannot be successfully loaded. + */ async addOpenSearchEngine(engineURL, iconURL, originAttributes) { lazy.logConsole.debug("addOpenSearchEngine: Adding", engineURL); await this.init(); @@ -772,6 +903,13 @@ export class SearchService { return engine; } + /** + * This should be called when an extension is removed. It will remove any + * search engines that are associated with the extension. + * + * @param {string} id + * The id of the extension. + */ async removeWebExtensionEngine(id) { if (!this.isInitialized) { lazy.logConsole.debug( @@ -791,6 +929,17 @@ export class SearchService { } } + /** + * Removes the search engine. If the search engine is installed in a global + * location, this will just hide the engine. If the engine is in the user's + * profile directory, it will be removed from disk. + * + * @param {SearchEngine} engine + * The engine to remove. + * @param {nsISearchService.DefaultEngineChangeReason} changeReason + * The reason for the engine being removed, used for telemetry if the engine + * is currently a default engine. + */ async removeEngine(engine, changeReason) { await this.init(); if (!engine) { @@ -882,6 +1031,19 @@ export class SearchService { ); } + /** + * Moves a visible search engine. + * + * @param {SearchEngine} engine + * The engine to move. + * @param {number} newIndex + * The engine's new index in the set of visible engines. + * + * @throws {Cr.NS_ERROR_INVALID_ARG} + * If newIndex is out of bounds. + * @throws {Cr.NS_ERROR_FAILURE} + * If the engine is hidden. + */ async moveEngine(engine, newIndex) { await this.init(); if (newIndex > this.#sortedEngines.length || newIndex < 0) { @@ -961,6 +1123,9 @@ export class SearchService { this.#saveSortedEngineList(); } + /** + * Un-hides all application provided engines. + */ restoreDefaultEngines() { this.#ensureInitialized(); for (let e of this._engines.values()) { @@ -971,6 +1136,20 @@ export class SearchService { } } + /** + * Determines if the provided URL represents results from a search engine, and + * provides details about the match. + * + * The lookup mechanism checks whether the domain name and path of the + * provided HTTP or HTTPS URL matches one of the known values for the visible + * search engines. The match does not depend on which of the schemes is used. + * The expected URI parameter for the search terms must exist in the query + * string, but other parameters are ignored. + * + * @param {string} url + * String containing the URL to parse, for example + * `https://www.google.com/search?q=terms`. + */ parseSubmissionURL(url) { if (!this.hasSuccessfullyInitialized) { // If search is not initialized or failed initializing, do nothing. @@ -1083,7 +1262,7 @@ export class SearchService { * Resolved when initalization has successfully finished, and rejected if it * has failed. * - * @type {PromiseWithResolvers} + * @type {PromiseWithResolvers<void>} */ #initDeferredPromise = Promise.withResolvers(); diff --git a/toolkit/components/search/SearchUtils.sys.mjs b/toolkit/components/search/SearchUtils.sys.mjs @@ -407,9 +407,10 @@ export var SearchUtils = { * This is implemented here as it is used in searchengine-devtools as well as * the search service. * + * @template {SearchEngine} T * @param {object} options * The options for this function. - * @param {SearchEngine[]} options.engines + * @param {T[]} options.engines * An array of engine objects to sort. These should have the `name` and * `orderHint` fields as top-level properties. * @param {SearchEngine} options.appDefaultEngine @@ -418,7 +419,7 @@ export var SearchUtils = { * The application private default engine, if any. * @param {string} [options.locale] * The current application locale, or the locale to use for the sorting. - * @returns {SearchEngine[]} + * @returns {T[]} * The sorted array of engine objects. */ sortEnginesByDefaults({ @@ -427,7 +428,7 @@ export var SearchUtils = { appPrivateDefaultEngine, locale = Services.locale.appLocaleAsBCP47, }) { - /** @type {SearchEngine[]} */ + /** @type {T[]} */ const sortedEngines = []; /** @type {Set<string>} */ const addedEngines = new Set(); diff --git a/toolkit/components/search/nsISearchService.idl b/toolkit/components/search/nsISearchService.idl @@ -490,7 +490,9 @@ interface nsISearchService : nsISupports * @param engine * The engine to remove. */ - Promise removeEngine(in nsISearchEngine engine); + Promise removeEngine( + in nsISearchEngine engine, + [optional] in unsigned short changeReason); /** * Notify nsSearchService that an extension has been removed. Removes any diff --git a/tools/@types/generated/lib.gecko.dom.d.ts b/tools/@types/generated/lib.gecko.dom.d.ts @@ -3663,6 +3663,7 @@ interface RedirectBlockedEventInit extends EventInit { interface RegistrationOptions { scope?: string; + type?: WorkerType; updateViaCache?: ServiceWorkerUpdateViaCache; } @@ -19542,7 +19543,7 @@ declare var SVGSwitchElement: { isInstance: IsInstance<SVGSwitchElement>; }; -interface SVGSymbolElement extends SVGElement, SVGFitToViewBox, SVGTests { +interface SVGSymbolElement extends SVGGraphicsElement, SVGFitToViewBox, SVGTests { addEventListener<K extends keyof SVGElementEventMap>(type: K, listener: (this: SVGSymbolElement, ev: SVGElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; removeEventListener<K extends keyof SVGElementEventMap>(type: K, listener: (this: SVGSymbolElement, ev: SVGElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void; diff --git a/tools/@types/generated/lib.gecko.glean.d.ts b/tools/@types/generated/lib.gecko.glean.d.ts @@ -337,6 +337,11 @@ interface GleanImpl { tabAssignedContainer: GleanEventWithExtras<{ from_container_id?: string, to_container_id?: string }>; } + browserCustomkeys: { + actions: Record<"change"|"clear"|"reset"|"reset_all", GleanCounter>; + opened: GleanCounter; + } + downloads: { panelShown: GleanCounter; addedFileExtension: GleanEventWithExtras<{ value?: string }>; @@ -5884,13 +5889,17 @@ interface GleanImpl { } firefoxAiRuntime: { - engineCreationFailure: GleanEventWithExtras<{ engineId?: string, error?: string, featureId?: string, modelId?: string, taskName?: string }>; + engineCreationFailure: GleanEventWithExtras<{ engineId?: string, error?: string, featureId?: string, flow_id?: string, modelId?: string, taskName?: string }>; engineCreationSuccess: Record<"about-inference"|"autofill-ml"|"default-engine"|"ml-suggest-intent"|"ml-suggest-ner"|"pdfjs"|"smart-intent"|"smart-tab-embedding-engine"|"smart-tab-topic-engine"|"webextension"|"wllamapreview", GleanTimingDistribution>; - engineRun: GleanEventWithExtras<{ backend?: string, cores?: string, cpu_milliseconds?: string, cpu_utilization?: string, engine_id?: string, feature_id?: string, memory_bytes?: string, model_id?: string, wall_milliseconds?: string }>; + engineCreationSuccessFlow: GleanEventWithExtras<{ duration?: string, engineId?: string, flow_id?: string }>; + engineRun: GleanEventWithExtras<{ backend?: string, cores?: string, cpu_milliseconds?: string, cpu_utilization?: string, engine_id?: string, feature_id?: string, flow_id?: string, memory_bytes?: string, model_id?: string, wall_milliseconds?: string }>; modelDeletion: GleanEventWithExtras<{ deletedBy?: string, error?: string, modelId?: string, modelRevision?: string }>; modelDownload: GleanEventWithExtras<{ duration?: string, engineId?: string, error?: string, featureId?: string, modelDownloadId?: string, modelId?: string, modelRevision?: string, step?: string, when?: string }>; - runInferenceFailure: GleanEventWithExtras<{ engineId?: string, featureId?: string, modelId?: string }>; + runInferenceFailure: GleanEventWithExtras<{ engineId?: string, featureId?: string, flow_id?: string, modelId?: string }>; runInferenceSuccess: Record<"about-inference"|"autofill-ml"|"default-engine"|"ml-suggest-intent"|"ml-suggest-ner"|"pdfjs"|"smart-intent"|"smart-tab-embedding-engine"|"smart-tab-topic-engine"|"webextension"|"wllamapreview", GleanTimingDistribution>; + runInferenceSuccessFlow: GleanEventWithExtras<{ flow_id?: string, inference_time?: string, tokenizing_time?: string }>; + sessionEnd: GleanEventWithExtras<{ duration?: string, feature_id?: string, flow_id?: string, status?: string }>; + sessionStart: GleanEventWithExtras<{ feature_id?: string, flow_id?: string, interaction?: string }>; } modelManagement: { diff --git a/tools/@types/generated/lib.gecko.xpcom.d.ts b/tools/@types/generated/lib.gecko.xpcom.d.ts @@ -7861,7 +7861,6 @@ interface nsICookieManager extends nsISupports { getCookieBehavior(aIsPrivate: boolean): u32; remove(aHost: string, aName: string, aPath: string, aOriginAttributes: any): void; add(aHost: string, aPath: string, aName: string, aValue: string, aIsSecure: boolean, aIsHttpOnly: boolean, aIsSession: boolean, aExpiry: i64, aOriginAttributes: any, aSameSite: i32, aSchemeMap: nsICookie.schemeType, aIsPartitioned?: boolean): nsICookieValidation; - addForAddOn(aHost: string, aPath: string, aName: string, aValue: string, aIsSecure: boolean, aIsHttpOnly: boolean, aIsSession: boolean, aExpiry: i64, aOriginAttributes: any, aSameSite: i32, aSchemeMap: nsICookie.schemeType, aIsPartitioned?: boolean): nsICookieValidation; cookieExists(aHost: string, aPath: string, aName: string, aOriginAttributes: any): boolean; countCookiesFromHost(aHost: string): u32; getCookiesFromHost(aHost: string, aOriginAttributes: any, aSorted?: boolean): nsICookie[]; @@ -11693,7 +11692,7 @@ interface nsISearchService extends nsISupports, Enums<typeof nsISearchService_Op shouldShowInstallPrompt(engine: any): Promise<any>; addSearchEngine(engine: any): Promise<any>; moveEngine(engine: nsISearchEngine, newIndex: i32): Promise<any>; - removeEngine(engine: nsISearchEngine): Promise<any>; + removeEngine(engine: nsISearchEngine, changeReason?: u16): Promise<any>; removeWebExtensionEngine(id: string): Promise<any>; readonly appDefaultEngine: nsISearchEngine; readonly appPrivateDefaultEngine: nsISearchEngine; @@ -12870,7 +12869,6 @@ interface nsIBaseWindow extends nsISupports { getSize(cx: OutParam<i32>, cy: OutParam<i32>): void; setPositionAndSize(x: i32, y: i32, cx: i32, cy: i32, flags: u32): void; getPositionAndSize(x: OutParam<i32>, y: OutParam<i32>, cx: OutParam<i32>, cy: OutParam<i32>): void; - repaint(force: boolean): void; readonly nativeHandle: string; visibility: boolean; enabled: boolean;