e that needs to be migrated to WebExtensions. lazy.logConsole.debug("Migrating existing engine"); shouldSetAsDefault = shouldSetAsDefault || this.defaultEngine == engine; await this.removeEngine( engine, Ci.nsISearchService.CHANGE_REASON_ADDON_INSTALL ); } } let newEngine = new lazy.AddonSearchEngine({ details: { extensionID: extension.id, }, }); await newEngine.init({ settings, extension, }); // If this extension is starting up, check to see if it previously overrode // an application provided engine that has now been removed from the user's // set-up. If the application provided engine has been removed and was // default, then we should set this engine back to default and copy // the settings across. if (extension.startupReason == "APP_STARTUP") { if (!settings) { settings = await this._settings.get(); } // We check the saved settings for the overridden flag, because if the engine // has been removed, we won't have that in _engines. let previouslyOverridden = settings.engines?.find( e => !!e._metaData.overriddenBy ); if (previouslyOverridden) { // Only allow override if we were previously overriding and the // engine is no longer installed, and the new engine still matches the // override allow list. if ( previouslyOverridden._metaData.overriddenBy == extension.id && !this._engines.get(previouslyOverridden.id) && (await lazy.defaultOverrideAllowlist.canEngineOverride( newEngine, previouslyOverridden.id )) ) { shouldSetAsDefault = true; // We assume that the app provided engine was removed due to a // configuration change, and therefore we have re-added the add-on // search engine. It is possible that it was actually due to a // locale/region change, but that is harder to detect here. changeReason = Ci.nsISearchService.CHANGE_REASON_CONFIG; newEngine.copyUserSettingsFrom(previouslyOverridden); } } } this.#addEngineToStore(newEngine); if (shouldSetAsDefault) { this.#setEngineDefault(false, newEngine, changeReason); } } /** * Called when we see an upgrade to an existing search extension. * * @param {object} extension * An Extension object containing data about the extension. */ async #upgradeExtensionEngine(extension) { let extensionEngines = this.#getEnginesByExtensionID(extension.id); for (let engine of extensionEngines) { let isDefault = engine == this.defaultEngine; let isDefaultPrivate = engine == this.defaultPrivateEngine; let originalName = engine.name; await engine.update({ extension, }); if (engine.name != originalName) { if (isDefault) { this._settings.setVerifiedMetaDataAttribute( "defaultEngineId", engine.id ); } if (isDefaultPrivate) { this._settings.setVerifiedMetaDataAttribute( "privateDefaultEngineId", engine.id ); } this._cachedSortedEngines = null; } } return extensionEngines; } /** * Removes a search engine from _sortedEngines and _engines. * * @param {SearchEngine} engine * The search engine to remove. */ #internalRemoveEngine(engine) { // Remove the engine from _sortedEngines if (this._cachedSortedEngines) { var index = this._cachedSortedEngines.indexOf(engine); if (index == -1) { throw Components.Exception( "Can't find engine to remove in _sortedEngines!", Cr.NS_ERROR_FAILURE ); } this._cachedSortedEngines.splice(index, 1); } // Remove the engine from the internal store this._engines.delete(engine.id); } /** * Helper function to find a new default engine and set it. This could * be used if there is not default set yet, or if the current default is * being removed. * * This function will not consider engines in #enginesPendingRemoval. * * The new default will be chosen from (in order): * * - Existing default from configuration, if it is not hidden. * - The first non-hidden engine that is a general search engine. * - If all other engines are hidden, unhide the default from the configuration. * - If the default from the configuration is the one being removed, unhide * the first general search engine, or first visible engine. * * @param {object} options * The options object. * @param {boolean} options.privateMode * If true, returns the default engine for private browsing mode, otherwise * the default engine for the normal mode. Note, this function does not * check the "separatePrivateDefault" preference - that is up to the caller. * @param {nsISearchService.DefaultEngineChangeReason} changeReason * The reason for the change of default engine. * @returns {?SearchEngine} * The appropriate search engine, or null if one could not be determined. */ #findAndSetNewDefaultEngine({ privateMode }, changeReason) { // First to the app default engine... let newDefault = privateMode ? this.appPrivateDefaultEngine : this.appDefaultEngine; if ( !newDefault || newDefault.hidden || this.#enginesPendingRemoval.has(newDefault) ) { let sortedEngines = this.#sortedVisibleEngines; let generalSearchEngines = sortedEngines.filter( e => e.isGeneralPurposeEngine ); // then to the first visible general search engine that isn't excluded... let firstVisible = generalSearchEngines.find( e => !this.#enginesPendingRemoval.has(e) ); if (firstVisible) { newDefault = firstVisible; } else if (newDefault) { // then to the app default if it is not the one that is excluded... if (!this.#enginesPendingRemoval.has(newDefault)) { newDefault.hidden = false; } else { newDefault = null; } } // and finally as a last resort we unhide the first engine // even if the name is the same as the excluded one (should never happen). if (!newDefault) { if (!firstVisible) { sortedEngines = this.#sortedEngines; firstVisible = sortedEngines.find(e => e.isGeneralPurposeEngine); if (!firstVisible) { firstVisible = sortedEngines[0]; } } if (firstVisible) { firstVisible.hidden = false; newDefault = firstVisible; } } } // We tried out best but something went very wrong. if (!newDefault) { lazy.logConsole.error("Could not find a replacement default engine."); return null; } // If the current engine wasn't set or was hidden, we used a fallback // to pick a new current engine. As soon as we return it, this new // current engine will become user-visible, so we should persist it. // by calling the setter. this.#setEngineDefault(privateMode, newDefault, changeReason); return privateMode ? this.#currentPrivateEngine : this.#currentEngine; } /** * Helper function to set the current default engine. * * @param {boolean} privateMode * If true, sets the default engine for private browsing mode, otherwise * sets the default engine for the normal mode. Note, this function does not * check the "separatePrivateDefault" preference - that is up to the caller. * @param {SearchEngine} newEngine * The search engine to select. * @param {nsISearchService.DefaultEngineChangeReason} changeReason * The reason for the default search engine change, one of * Ci.nsISearchService.CHANGE_REASON*. */ #setEngineDefault(privateMode, newEngine, changeReason) { // Sometimes we get wrapped nsISearchEngine objects (external XPCOM callers), // and sometimes we get raw Engine JS objects (callers in this file), so // handle both. if ( !(newEngine instanceof Ci.nsISearchEngine) && !(newEngine instanceof lazy.SearchEngine) ) { throw Components.Exception( "Invalid argument passed to defaultEngine setter", Cr.NS_ERROR_INVALID_ARG ); } const newCurrentEngine = this._engines.get(newEngine.id); if (!newCurrentEngine) { throw Components.Exception( "Can't find engine in store!", Cr.NS_ERROR_UNEXPECTED ); } if (!newCurrentEngine.isConfigEngine) { // If a non config engine is being set as the current engine, // ensure its loadPath has a verification hash. if (!newCurrentEngine._loadPath) { newCurrentEngine._loadPath = "[other]unknown"; } let loadPathHash = lazy.SearchUtils.getVerificationHash( newCurrentEngine._loadPath ); let currentHash = newCurrentEngine.getAttr("loadPathHash"); if (!currentHash || currentHash != loadPathHash) { newCurrentEngine.setAttr("loadPathHash", loadPathHash); lazy.SearchUtils.notifyAction( newCurrentEngine, lazy.SearchUtils.MODIFIED_TYPE.CHANGED ); } } let currentEngine = privateMode ? this.#currentPrivateEngine : this.#currentEngine; if (newCurrentEngine == currentEngine) { return; } // Ensure that we reset an engine override if it was previously overridden. currentEngine?.removeExtensionOverride(); if (privateMode) { this.#currentPrivateEngine = newCurrentEngine; } else { this.#currentEngine = newCurrentEngine; } // If we change the default engine in the future, that change should impact // users who have switched away from and then back to the build's // "app default" engine. So clear the user pref when the currentEngine is // set to the build's app default engine, so that the currentEngine getter // falls back to whatever the default is. // However, we do not do this whilst we are running an experiment - an // experiment must preseve the user's choice of default engine during it's // runtime and when it ends. Once the experiment ends, we will reset the // attribute elsewhere. let newId = newCurrentEngine.id; const appDefaultEngine = privateMode ? this.appPrivateDefaultEngine : this.appDefaultEngine; if ( newCurrentEngine == appDefaultEngine && !this.#lazyPrefs.experimentPrefValue ) { newId = ""; } this._settings.setVerifiedMetaDataAttribute( privateMode ? "privateDefaultEngineId" : "defaultEngineId", newId ); // Only do this if we're initialized though - this function can get called // during initalization. if (this.isInitialized) { this.#recordDefaultChangedEvent( privateMode, currentEngine, newCurrentEngine, changeReason ); this.#recordDefaultEngineTelemetryData(); } lazy.SearchUtils.notifyAction( newCurrentEngine, lazy.SearchUtils.MODIFIED_TYPE[ privateMode ? "DEFAULT_PRIVATE" : "DEFAULT" ] ); // If we've not got a separate private active, notify update of the // private so that the UI updates correctly. if (!privateMode && !this.#separatePrivateDefault) { lazy.SearchUtils.notifyAction( newCurrentEngine, lazy.SearchUtils.MODIFIED_TYPE.DEFAULT_PRIVATE ); } } #onSeparateDefaultPrefChanged(prefName, previousValue, currentValue) { // Clear out the sorted engines settings, so that we re-sort it if necessary. this._cachedSortedEngines = null; // We should notify if the normal default, and the currently saved private // default are different. Otherwise, save the energy. if (this.defaultEngine != this._getEngineDefault(true)) { lazy.SearchUtils.notifyAction( // Always notify with the new private engine, the function checks // the preference value for us. this.defaultPrivateEngine, lazy.SearchUtils.MODIFIED_TYPE.DEFAULT_PRIVATE ); } let eventReason = prefName.endsWith("separatePrivateDefault.ui.enabled") ? Ci.nsISearchService.CHANGE_REASON_USER_PRIVATE_PREF_ENABLED : Ci.nsISearchService.CHANGE_REASON_USER_PRIVATE_SPLIT; if (!previousValue && currentValue) { this.#recordDefaultChangedEvent( true, null, this._getEngineDefault(true), eventReason ); } else { this.#recordDefaultChangedEvent( true, this._getEngineDefault(true), null, eventReason ); } // Update the telemetry data. this.#recordDefaultEngineTelemetryData(); } /** * Gets summary information for an engine to report to telemetry. * * @param {SearchEngine} engine */ #getEngineInfo(engine) { if (!engine) { // The defaultEngine getter will throw if there's no engine at all, // which shouldn't happen unless an add-on or a test deleted all of them. // Our preferences UI doesn't let users do that. lazy.logConsole.error("getEngineInfo: No default engine"); return { providerId: "NONE", partnerCode: "NONE", overriddenByThirdParty: false, telemetryId: "NONE", loadPath: "NONE", name: "NONE", submissionURL: "NONE", }; } // When an engine is overridden by a third party, then we report the // override and skip reporting the partner code, since we don't have // a requirement to report the partner code in that case. let isOverridden = !!engine.overriddenById; let engineInfo = { providerId: engine.isConfigEngine ? engine.id : "other", partnerCode: isOverridden ? "" : engine.partnerCode, overriddenByThirdParty: isOverridden, telemetryId: engine.telemetryId, loadPath: engine.loadPath, name: engine.name ? engine.name : "", /** @type {string?} */ submissionURL: undefined, }; // For privacy, we only collect the submission URL for config engines... let sendSubmissionURL = engine.isConfigEngine; if (!sendSubmissionURL) { // ... or engines that are the same domain as a config engine. let engineHost = engine.searchUrlDomain; for (let innerEngine of this._engines.values()) { if (!innerEngine.isConfigEngine) { continue; } if (innerEngine.searchUrlDomain == engineHost) { sendSubmissionURL = true; break; } } if (!sendSubmissionURL) { // ... or well known search domains. // // Starts with: www.google., search.aol., yandex. // or // Ends with: search.yahoo.com, .ask.com, .bing.com, .startpage.com, baidu.com, duckduckgo.com const urlTest = /^(?:www\.google\.|search\.aol\.|yandex\.)|(?:search\.yahoo|\.ask|\.bing|\.startpage|\.baidu|duckduckgo)\.com$/; sendSubmissionURL = urlTest.test(engineHost); } } if (sendSubmissionURL) { let uri = engine.searchURLWithNoTerms; uri = uri .mutate() .setUserPass("") // Avoid reporting a username or password. .finalize(); engineInfo.submissionURL = uri.spec; } return engineInfo; } /** * Records an event for where the default engine is changed. This is * recorded to both Glean and Telemetry. * * The Glean GIFFT functionality is not used here because we use longer * names in the extra arguments to the event. * * @param {boolean} isPrivate * True if this is a event about a private engine. * @param {SearchEngine} [previousEngine] * The previously default search engine. * @param {SearchEngine} [newEngine] * The new default search engine. * @param {nsISearchService.DefaultEngineChangeReason} changeReason * The reason for the default search engine change, one of * Ci.nsISearchService.CHANGE_REASON*. */ #recordDefaultChangedEvent( isPrivate, previousEngine, newEngine, changeReason = Ci.nsISearchService.CHANGE_REASON_UNKNOWN ) { let engineInfo; // If we are toggling the separate private browsing settings, we might not // have an engine to record. if (newEngine) { engineInfo = this.#getEngineInfo(newEngine); } let submissionURL = engineInfo?.submissionURL ?? ""; let extraArgs = { // In docshell tests, the previous engine does not exist, so we allow // for the previousEngine to be undefined. previous_engine_id: previousEngine?.telemetryId ?? "", new_engine_id: engineInfo?.telemetryId ?? "", new_display_name: engineInfo?.name ?? "", new_load_path: engineInfo?.loadPath ?? "", // Glean has a limit of 100 characters. new_submission_url: submissionURL.slice(0, 100), change_reason: REASON_CHANGE_MAP.get(changeReason) ?? "unknown", }; if (isPrivate) { Glean.searchEnginePrivate.changed.record(extraArgs); } else { Glean.searchEngineDefault.changed.record(extraArgs); } } /** * Records the user's current default engine (normal and private) data to * telemetry. */ #recordDefaultEngineTelemetryData() { let engineInfo = this.#getEngineInfo(this.defaultEngine); Glean.searchEngineDefault.providerId.set(engineInfo.providerId); Glean.searchEngineDefault.partnerCode.set(engineInfo.partnerCode); Glean.searchEngineDefault.overriddenByThirdParty.set( engineInfo.overriddenByThirdParty ); Glean.searchEngineDefault.engineId.set(engineInfo.telemetryId); Glean.searchEngineDefault.displayName.set(engineInfo.name); Glean.searchEngineDefault.loadPath.set(engineInfo.loadPath); Glean.searchEngineDefault.submissionUrl.set( engineInfo.submissionURL ?? "blank:" ); if (this.#separatePrivateDefault) { let privateEngineInfo = this.#getEngineInfo(this.defaultPrivateEngine); Glean.searchEnginePrivate.providerId.set(privateEngineInfo.providerId); Glean.searchEnginePrivate.partnerCode.set(privateEngineInfo.partnerCode); Glean.searchEnginePrivate.overriddenByThirdParty.set( privateEngineInfo.overriddenByThirdParty ); Glean.searchEnginePrivate.engineId.set(privateEngineInfo.telemetryId); Glean.searchEnginePrivate.displayName.set(privateEngineInfo.name); Glean.searchEnginePrivate.loadPath.set(privateEngineInfo.loadPath); Glean.searchEnginePrivate.submissionUrl.set( privateEngineInfo.submissionURL ?? "blank:" ); } else { Glean.searchEnginePrivate.providerId.set(""); Glean.searchEnginePrivate.partnerCode.set(""); Glean.searchEnginePrivate.overriddenByThirdParty.set(false); Glean.searchEnginePrivate.engineId.set(""); Glean.searchEnginePrivate.displayName.set(""); Glean.searchEnginePrivate.loadPath.set(""); Glean.searchEnginePrivate.submissionUrl.set("blank:"); } } /** * This function is called at the beginning of search service init. * If the error type set in a test environment matches errorType * passed to this function, we throw an error. * * @param {string} errorType * The error that can occur during search service init. */ #maybeThrowErrorInTest(errorType) { if ( Services.env.exists("XPCSHELL_TEST_PROFILE_DIR") && this.errorToThrowInTest.type === errorType ) { throw new Error( this.errorToThrowInTest.message ?? `Fake ${errorType} error during search service initialization.` ); } } #buildParseSubmissionMap() { this.#parseSubmissionMap = new Map(); // Used only while building the map, indicates which entries do not refer to // the main domain of the engine but to an alternate domain, for example // "www.google.fr" for the "www.google.com" search engine. let keysOfAlternates = new Set(); for (let engine of this.#sortedEngines) { if (engine.hidden) { continue; } let urlParsingInfo = engine.getURLParsingInfo(); if (!urlParsingInfo) { continue; } // Store the same object on each matching map key, as an optimization. let mapValueForEngine = { engine, termsParameterName: urlParsingInfo.termsParameterName, }; let processDomain = (domain, isAlternate) => { let key = domain + urlParsingInfo.path; // Apply the logic for which main domains take priority over alternate // domains, even if they are found later in the ordered engine list. let existingEntry = this.#parseSubmissionMap.get(key); if (!existingEntry) { if (isAlternate) { keysOfAlternates.add(key); } } else if (!isAlternate && keysOfAlternates.has(key)) { keysOfAlternates.delete(key); } else { return; } this.#parseSubmissionMap.set(key, mapValueForEngine); }; processDomain(urlParsingInfo.mainDomain, false); lazy.SearchStaticData.getAlternateDomains( urlParsingInfo.mainDomain ).forEach(d => processDomain(d, true)); } } #addObservers() { if (this.#observersAdded) { // There might be a race between synchronous and asynchronous // initialization for which we try to register the observers twice. return; } this.#observersAdded = true; Services.obs.addObserver(this, lazy.SearchUtils.TOPIC_ENGINE_MODIFIED); Services.obs.addObserver(this, QUIT_APPLICATION_TOPIC); Services.obs.addObserver(this, TOPIC_LOCALES_CHANGE); this._settings.addObservers(); // The current stage of shutdown. Used to help analyze crash // signatures in case of shutdown timeout. let shutdownState = { step: "Not started", latestError: { message: undefined, stack: undefined, }, }; IOUtils.profileBeforeChange.addBlocker( "Search service: shutting down", () => (async () => { // If we are in initialization, then don't attempt to save the settings. // It is likely that shutdown will have caused the add-on manager to // stop, which can cause initialization to fail. // Hence at that stage, we could have broken settings which we don't // want to write. // The good news is, that if we don't write the settings here, we'll // detect the out-of-date settings on next state, and automatically // rebuild it. if (!this.isInitialized) { lazy.logConsole.warn( "not saving settings on shutdown due to initializing." ); return; } try { await this._settings.shutdown(shutdownState); } catch (ex) { // Ensure that error is reported and that it causes tests // to fail, otherwise ignore it. Promise.reject(ex); } })(), () => shutdownState ); } // This is prefixed with _ rather than # because it is // called in a test. _removeObservers() { if (this.ignoreListListener) { lazy.IgnoreLists.unsubscribe(this.ignoreListListener); delete this.ignoreListListener; } if (this.#queuedIdle) { lazy.idleService.removeIdleObserver(this, RECONFIG_IDLE_TIME_SEC); this.#queuedIdle = false; } this._settings.removeObservers(); Services.obs.removeObserver(this, lazy.Region.REGION_TOPIC); Services.obs.removeObserver(this, lazy.SearchUtils.TOPIC_ENGINE_MODIFIED); Services.obs.removeObserver(this, QUIT_APPLICATION_TOPIC); Services.obs.removeObserver(this, TOPIC_LOCALES_CHANGE); this.#observersAdded = false; this.#earlyObserversAdded = false; } QueryInterface = ChromeUtils.generateQI([ "nsISearchService", "nsIObserver", "nsITimerCallback", ]); // nsIObserver observe(engine, topic, verb) { switch (topic) { case lazy.SearchUtils.TOPIC_ENGINE_MODIFIED: switch (verb) { case lazy.SearchUtils.MODIFIED_TYPE.ADDED: this.#parseSubmissionMap = null; break; case lazy.SearchUtils.MODIFIED_TYPE.CHANGED: engine = engine.wrappedJSObject; if ( engine == this.defaultEngine || engine == this.defaultPrivateEngine ) { this.#recordDefaultChangedEvent( engine != this.defaultEngine, engine, engine, Ci.nsISearchService.CHANGE_REASON_ENGINE_UPDATE ); } this.#parseSubmissionMap = null; break; case lazy.SearchUtils.MODIFIED_TYPE.REMOVED: // Invalidate the map used to parse URLs to search engines. this.#parseSubmissionMap = null; break; } break; case "idle": { lazy.idleService.removeIdleObserver(this, RECONFIG_IDLE_TIME_SEC); this.#queuedIdle = false; lazy.logConsole.debug( "Reloading engines after idle due to configuration change" ); this._maybeReloadEngines( Ci.nsISearchService.CHANGE_REASON_CONFIG ).catch(console.error); break; } case QUIT_APPLICATION_TOPIC: this._removeObservers(); break; case TOPIC_LOCALES_CHANGE: // Locale changed. Re-init. We rely on observers, because we can't // return this promise to anyone. // At the time of writing, when the user does a "Apply and Restart" for // a new language the preferences code triggers the locales change and // restart straight after, so we delay the check, which means we should // be able to avoid the reload on shutdown, and we'll sort it out // on next startup. // This also helps to avoid issues with the add-on manager shutting // down at the same time (see _reInit for more info). Services.tm.dispatchToMainThread(() => { if (!Services.startup.shuttingDown) { this._maybeReloadEngines( Ci.nsISearchService.CHANGE_REASON_LOCALE ).catch(console.error); } }); break; case lazy.Region.REGION_TOPIC: lazy.logConsole.debug("Region updated:", lazy.Region.home); this._maybeReloadEngines( Ci.nsISearchService.CHANGE_REASON_REGION ).catch(console.error); break; } } /** * @param {object} metaData * The metadata object that defines the details of the engine. * @returns {boolean} * Returns true if metaData has different property values than * the cached _metaData. */ #didSettingsMetaDataUpdate(metaData) { let metaDataProperties = [ "locale", "region", "channel", "experiment", "distroID", ]; return metaDataProperties.some(p => { return metaData?.[p] !== this._settings.getMetaDataAttribute(p); }); } /** * Shows an infobar to notify the user their default search engine has been * removed and replaced by a new default search engine. * * This indirection exists to simplify tests. * * @param {string} prevCurrentEngineName * The name of the previous default engine that will be replaced. * @param {string} newCurrentEngineName * The name of the engine that will be the new default engine. */ _showRemovalOfSearchEngineNotificationBox( prevCurrentEngineName, newCurrentEngineName ) { lazy.BrowserUtils.callModulesFromCategory( { categoryName: "search-service-notification" }, "search-engine-removal", prevCurrentEngineName, newCurrentEngineName ); } /** * Infobar informing the user that the search settings had to be reset * and what their new default engine is. * * @param {string} newEngine * The name of the new default search engine. */ _showSearchSettingsResetNotificationBox(newEngine) { lazy.BrowserUtils.callModulesFromCategory( { categoryName: "search-service-notification" }, "search-settings-reset", newEngine ); } /** * Maybe starts the timer for OpenSearch engine updates. This will be set * only if updates are enabled and there are OpenSearch engines installed * which have updates. */ #maybeStartOpenSearchUpdateTimer() { if ( this.#openSearchUpdateTimerStarted || !Services.prefs.getBoolPref( lazy.SearchUtils.BROWSER_SEARCH_PREF + "update", true ) ) { return; } let engineWithUpdates = [...this._engines.values()].some( engine => engine instanceof lazy.OpenSearchEngine && engine.hasUpdates ); if (engineWithUpdates) { lazy.logConsole.debug("Engine with updates found, setting update timer"); lazy.timerManager.registerTimer( OPENSEARCH_UPDATE_TIMER_TOPIC, this, OPENSEARCH_UPDATE_TIMER_INTERVAL, true ); this.#openSearchUpdateTimerStarted = true; } } } // end SearchService class /** * Handles getting and checking extensions against the allow list. */ class SearchDefaultOverrideAllowlistHandler { constructor() { this._remoteConfig = lazy.RemoteSettings( lazy.SearchUtils.SETTINGS_ALLOWLIST_KEY ); } /** * Determines if a search engine extension can override a default one * according to the allow list. * * @param {object} extension * The extension object (from add-on manager) that will override the * app provided search engine. * @param {string} appProvidedEngineId * The id of the search engine that will be overriden. * @returns {Promise} * Returns true if the search engine extension may override the app provided * instance. */ async canOverride(extension, appProvidedEngineId) { const overrideTable = await this._getAllowlist(); let entry = overrideTable.find(e => e.thirdPartyId == extension.id); if (!entry) { return false; } if (appProvidedEngineId != entry.overridesAppIdv2) { return false; } let searchProvider = extension.manifest.chrome_settings_overrides.search_provider; return entry.urls.some( e => searchProvider.search_url == e.search_url && searchProvider.search_url_get_params == e.search_url_get_params && searchProvider.search_url_post_params == e.search_url_post_params ); } /** * Determines if an existing search engine is allowed to override a default one * according to the allow list. * * @param {SearchEngine} engine * The existing search engine. * @param {string} appProvidedEngineId * The id of the search engine that will be overriden. * @returns {Promise} * Returns true if the existing search engine is allowed to override the * app provided instance. */ async canEngineOverride(engine, appProvidedEngineId) { const overrideEntries = await this._getAllowlist(); let entry; if (engine instanceof lazy.AddonSearchEngine) { entry = overrideEntries.find(e => e.thirdPartyId == engine._extensionID); } else if (engine instanceof lazy.OpenSearchEngine) { entry = overrideEntries.find( e => e.thirdPartyId == "opensearch@search.mozilla.org" && e.engineName == engine.name ); } if (!entry) { return false; } if (appProvidedEngineId != entry.overridesAppIdv2) { return false; } return entry.urls.some(urlSet => engine.checkSearchUrlMatchesManifest(urlSet) ); } /** * Obtains the configuration from remote settings. This includes * verifying the signature of the record within the database. * * If the signature in the database is invalid, the database will be wiped * and the stored dump will be used, until the settings next update. * * Note that this may cause a network check of the certificate, but that * should generally be quick. * * @returns {Promise} * An array of objects in the database, or an empty array if none * could be obtained. */ async _getAllowlist() { let result = []; try { result = await this._remoteConfig.get(); } catch (ex) { // Don't throw an error just log it, just continue with no data, and hopefully // a sync will fix things later on. console.error(ex); } lazy.logConsole.debug("Allow list is:", result); return result; } } PK