diff options
Diffstat (limited to 'toolkit/components/search/AppProvidedSearchEngine.sys.mjs')
-rw-r--r-- | toolkit/components/search/AppProvidedSearchEngine.sys.mjs | 291 |
1 files changed, 291 insertions, 0 deletions
diff --git a/toolkit/components/search/AppProvidedSearchEngine.sys.mjs b/toolkit/components/search/AppProvidedSearchEngine.sys.mjs new file mode 100644 index 0000000000..a8db801ac4 --- /dev/null +++ b/toolkit/components/search/AppProvidedSearchEngine.sys.mjs @@ -0,0 +1,291 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* eslint no-shadow: error, mozilla/no-aArgs: error */ + +import { + SearchEngine, + EngineURL, +} from "resource://gre/modules/SearchEngine.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + SearchUtils: "resource://gre/modules/SearchUtils.sys.mjs", +}); + +/** + * AppProvidedSearchEngine represents a search engine defined by the + * search configuration. + */ +export class AppProvidedSearchEngine extends SearchEngine { + static URL_TYPE_MAP = new Map([ + ["search", lazy.SearchUtils.URL_TYPE.SEARCH], + ["suggestions", lazy.SearchUtils.URL_TYPE.SUGGEST_JSON], + ["trending", lazy.SearchUtils.URL_TYPE.TRENDING_JSON], + ]); + + /** + * @param {object} options + * The options for this search engine. + * @param {object} options.config + * The engine config from Remote Settings. + * @param {object} [options.settings] + * The saved settings for the user. + */ + constructor({ config, settings }) { + // TODO Bug 1875912 - Remove the webextension.id and webextension.locale when + // we're ready to remove old search-config and use search-config-v2 for all + // clients. The id in appProvidedSearchEngine should be changed to + // engine.identifier. + let extensionId = config.webExtension.id; + let id = config.webExtension.id + config.webExtension.locale; + + super({ + loadPath: "[app]" + extensionId, + isAppProvided: true, + id, + }); + + this._extensionID = extensionId; + this._locale = config.webExtension.locale; + + this.#init(config); + + this._loadSettings(settings); + } + + /** + * Update this engine based on new config, used during + * config upgrades. + + * @param {object} options + * The options object. + * + * @param {object} options.locale + * The locale that is being used for the engine. + * @param {object} options.configuration + * The search engine configuration for application provided engines. + */ + update({ locale, configuration } = {}) { + this._urls = []; + this._iconMapObj = null; + this.#init(configuration); + lazy.SearchUtils.notifyAction(this, lazy.SearchUtils.MODIFIED_TYPE.CHANGED); + } + + /** + * This will update the application provided search engine if there is no + * name change. + * + * @param {object} options + * The options object. + * @param {object} [options.configuration] + * The search engine configuration for application provided engines. + * @param {string} [options.locale] + * The locale to use for getting details of the search engine. + * @returns {boolean} + * Returns true if the engine was updated, false otherwise. + */ + async updateIfNoNameChange({ configuration, locale }) { + let newName; + if (locale != "default") { + newName = configuration.webExtension.searchProvider[locale].name; + } else if ( + locale == "default" && + configuration.webExtension.default_locale + ) { + newName = + configuration.webExtension.searchProvider[ + configuration.webExtension.default_locale + ].name; + } else { + newName = configuration.webExtension.name; + } + + if (this.name != newName.trim()) { + return false; + } + this.update({ locale, configuration }); + return true; + } + + /** + * Whether or not this engine is provided by the application, e.g. it is + * in the list of configured search engines. Overrides the definition in + * `SearchEngine`. + * + * @returns {boolean} + */ + get isAppProvided() { + return true; + } + + /** + * Whether or not this engine is an in-memory only search engine. + * These engines are typically application provided or policy engines, + * where they are loaded every time on SearchService initialization + * using the policy JSON or the extension manifest. Minimal details of the + * in-memory engines are saved to disk, but they are never loaded + * from the user's saved settings file. + * + * @returns {boolean} + * Only returns true for application provided engines. + */ + get inMemory() { + return true; + } + + get isGeneralPurposeEngine() { + return !!( + this._extensionID && + lazy.SearchUtils.GENERAL_SEARCH_ENGINE_IDS.has(this._extensionID) + ); + } + + /** + * Creates a JavaScript object that represents this engine. + * + * @returns {object} + * An object suitable for serialization as JSON. + */ + toJSON() { + // For applicaiton provided engines we don't want to store all their data in + // the settings file so just store the relevant metadata. + return { + id: this.id, + _name: this.name, + _isAppProvided: true, + _metaData: this._metaData, + }; + } + + /** + * Initializes the engine. + * + * @param {object} [engineConfig] + * The search engine configuration for application provided engines. + */ + #init(engineConfig) { + this._orderHint = engineConfig.orderHint; + this._telemetryId = engineConfig.identifier; + + if (engineConfig.telemetrySuffix) { + this._telemetryId += `-${engineConfig.telemetrySuffix}`; + } + + // Set the main icon URL for the engine. + // let iconURL = searchProvider.favicon_url; + + // if (!iconURL) { + // iconURL = + // manifest.icons && + // extensionBaseURI.resolve( + // lazy.ExtensionParent.IconDetails.getPreferredIcon(manifest.icons).icon + // ); + // } + + // // Record other icons that the WebExtension has. + // if (manifest.icons) { + // let iconList = Object.entries(manifest.icons).map(icon => { + // return { + // width: icon[0], + // height: icon[0], + // url: extensionBaseURI.resolve(icon[1]), + // }; + // }); + // for (let icon of iconList) { + // this._addIconToMap(icon.size, icon.size, icon.url); + // } + // } + + // this._initWithDetails(config); + + // this._sendAttributionRequest = config.sendAttributionRequest ?? false; // TODO check if we need to this? + // if (details.iconURL) { + // this._setIcon(details.iconURL, true); + // } + + this._name = engineConfig.name.trim(); + this._definedAliases = + engineConfig.aliases?.map(alias => `@${alias}`) ?? []; + + for (const [type, urlData] of Object.entries(engineConfig.urls)) { + this.#setUrl(type, urlData, engineConfig.partnerCode); + } + } + + /** + * This sets the urls for the search engine based on the supplied parameters. + * + * @param {string} type + * The type of url. This could be a url for search, suggestions, or trending. + * @param {object} urlData + * The url data contains the template/base url and url params. + * @param {string} partnerCode + * The partner code associated with the search engine. + */ + #setUrl(type, urlData, partnerCode) { + let urlType = AppProvidedSearchEngine.URL_TYPE_MAP.get(type); + + if (!urlType) { + console.warn("unexpected engine url type.", type); + return; + } + + let engineURL = new EngineURL( + urlType, + urlData.method || "GET", + urlData.base + ); + + if (urlData.params) { + for (const param of urlData.params) { + switch (true) { + case "value" in param: + engineURL.addParam( + param.name, + param.value == "{partnerCode}" ? partnerCode : param.value + ); + break; + case "experimentConfig" in param: + engineURL._addMozParam({ + name: param.name, + pref: param.experimentConfig, + condition: "pref", + }); + break; + case "searchAccessPoint" in param: + for (const [key, value] of Object.entries( + param.searchAccessPoint + )) { + engineURL.addParam( + param.name, + value, + key == "addressbar" ? "keyword" : key + ); + } + break; + } + } + } + + if ( + !("searchTermParamName" in urlData) && + !urlData.base.includes("{searchTerms}") && + !urlType.includes("trending") + ) { + throw new Error("Search terms missing from engine URL."); + } + + if ("searchTermParamName" in urlData) { + // The search term parameter is always added last, which will add it to the + // end of the URL. This is because in the past we have seen users trying to + // modify their searches by altering the end of the URL. + engineURL.addParam(urlData.searchTermParamName, "{searchTerms}"); + } + + this._urls.push(engineURL); + } +} |