commit d59db0f008c681c0c623a004a8347e94375c8f4c
parent 590be5def684a944bf3d30a97d74e0f2bf5dcb2b
Author: Pier Angelo Vendrame <pierov@torproject.org>
Date: Tue, 2 Sep 2025 10:04:03 +0200
BB 43525: Skip Remote Settings for search engine customization.
Also, add some bundled search engines.
Diffstat:
11 files changed, 198 insertions(+), 87 deletions(-)
diff --git a/toolkit/components/search/ConfigSearchEngine.sys.mjs b/toolkit/components/search/ConfigSearchEngine.sys.mjs
@@ -112,6 +112,10 @@ class IconHandler {
await this.#buildIconMap();
}
+ if (AppConstants.BASE_BROWSER_VERSION) {
+ return this.#iconMap.get(engineIdentifier);
+ }
+
let iconList = this.#iconMap.get(this.getKey(engineIdentifier)) || [];
return iconList.filter(r =>
this.#identifierMatches(engineIdentifier, r.engineIdentifiers)
@@ -128,29 +132,7 @@ class IconHandler {
* source object or null of there is no icon with the supplied width.
*/
async createIconURL(iconRecord) {
- let iconData;
- try {
- iconData = await this.#iconCollection.attachments.get(iconRecord);
- } catch (ex) {
- console.error(ex);
- }
- if (!iconData) {
- console.warn("Unable to find the attachment for", iconRecord.id);
- // Queue an update in case we haven't downloaded it yet.
- this.#pendingUpdatesMap.set(iconRecord.id, iconRecord);
- this.#maybeQueueIdle();
- return null;
- }
-
- if (iconData.record.last_modified != iconRecord.last_modified) {
- // The icon we have stored is out of date, queue an update so that we'll
- // download the new icon.
- this.#pendingUpdatesMap.set(iconRecord.id, iconRecord);
- this.#maybeQueueIdle();
- }
- return URL.createObjectURL(
- new Blob([iconData.buffer], { type: iconRecord.attachment.mimetype })
- );
+ return iconRecord.url;
}
QueryInterface = ChromeUtils.generateQI(["nsIObserver"]);
@@ -234,27 +216,23 @@ class IconHandler {
* Obtains the icon list from the remote settings collection.
*/
async #buildIconMap() {
- let iconList = [];
try {
- iconList = await this.#iconCollection.get();
+ this.#iconMap = new Map(
+ Object.entries(
+ await (
+ await fetch(
+ "chrome://global/content/search/base-browser-search-engine-icons.json"
+ )
+ ).json()
+ )
+ );
} catch (ex) {
console.error(ex);
+ this.#iconMap = null;
}
- if (!iconList.length) {
+ if (!this.#iconMap) {
console.error("Failed to obtain search engine icon list records");
}
-
- this.#iconMap = new Map();
- for (let record of iconList) {
- let keys = new Set(record.engineIdentifiers.map(this.getKey));
- for (let key of keys) {
- if (this.#iconMap.has(key)) {
- this.#iconMap.get(key).push(record);
- } else {
- this.#iconMap.set(key, [record]);
- }
- }
- }
}
/**
diff --git a/toolkit/components/search/SearchEngineSelector.sys.mjs b/toolkit/components/search/SearchEngineSelector.sys.mjs
@@ -9,6 +9,7 @@
* } from "../uniffi-bindgen-gecko-js/components/generated/RustSearch.sys.mjs";
*/
+import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
const lazy = XPCOMUtils.declareLazy({
@@ -91,42 +92,19 @@ export class SearchEngineSelector {
return this.#getConfigurationPromise;
}
- this.#getConfigurationPromise = Promise.all([
- this.#getConfiguration(),
- this.#getConfigurationOverrides(),
- ]);
- let remoteSettingsData = await this.#getConfigurationPromise;
- this.#configuration = remoteSettingsData[0];
- this.#getConfigurationPromise = null;
-
- if (!this.#configuration?.length) {
- throw Components.Exception(
- "Failed to get engine data from Remote Settings",
- Cr.NS_ERROR_UNEXPECTED
- );
- }
-
- /**
- * Records whether the listeners have been added or not.
- */
- if (!this.#listenerAdded) {
- this.#remoteConfig.on("sync", this.#boundOnConfigurationUpdated);
- this.#remoteConfigOverrides.on(
- "sync",
- this.#boundOnConfigurationOverridesUpdated
- );
- /**
- * Records whether the listeners have been added or not.
- */
- this.#listenerAdded = true;
- }
+ let { promise, resolve } = Promise.withResolvers();
+ this.#getConfigurationPromise = promise;
+ this.#configuration = await (
+ await fetch(
+ "chrome://global/content/search/base-browser-search-engines.json"
+ )
+ ).json();
+ resolve(this.#configuration);
this.#selector.setSearchConfig(
JSON.stringify({ data: this.#configuration })
);
- this.#selector.setConfigOverrides(
- JSON.stringify({ data: remoteSettingsData[1] })
- );
+ this.#selector.setConfigOverrides(JSON.stringify({ data: [] }));
return this.#configuration;
}
@@ -369,6 +347,12 @@ export class SearchEngineSelector {
* The new configuration object
*/
_onConfigurationUpdated({ data: { current } }) {
+ // tor-browser#43525: Even though RemoteSettings are a no-op for us, we do
+ // not want them to interfere in any way.
+ if (AppConstants.BASE_BROWSER_VERSION) {
+ return;
+ }
+
this.#configuration = current;
this.#selector.setSearchConfig(
@@ -396,6 +380,12 @@ export class SearchEngineSelector {
* The new configuration object
*/
_onConfigurationOverridesUpdated({ data: { current } }) {
+ // tor-browser#43525: Even though RemoteSettings are a no-op for us, we do
+ // not want them to interfere in any way.
+ if (AppConstants.BASE_BROWSER_VERSION) {
+ return;
+ }
+
this.#selector.setConfigOverrides(JSON.stringify({ data: current }));
lazy.logConsole.debug("Search configuration overrides updated remotely");
@@ -405,23 +395,6 @@ export class SearchEngineSelector {
}
/**
- * Obtains the configuration overrides from remote settings.
- *
- * @returns {Promise<object[]>}
- * An array of objects in the database, or an empty array if none
- * could be obtained.
- */
- async #getConfigurationOverrides() {
- let result = [];
- try {
- result = await this.#remoteConfigOverrides.get();
- } catch (ex) {
- // This data is remote only, so we just return an empty array if it fails.
- }
- return result;
- }
-
- /**
* @type {InstanceType<typeof lazy.SearchEngineSelector>?}
*/
#cachedSelector = null;
diff --git a/toolkit/components/search/content/base-browser-search-engine-icons.json b/toolkit/components/search/content/base-browser-search-engine-icons.json
@@ -0,0 +1,18 @@
+{
+ "ddg": [
+ { "url": "chrome://global/content/search/duckduckgo.ico", "imageSize": 32 }
+ ],
+ "ddg-noai": [
+ { "url": "chrome://global/content/search/duckduckgo.ico", "imageSize": 32 }
+ ],
+ "startpage": [
+ {
+ "url": "chrome://global/content/search/startpage-16.png",
+ "imageSize": 16
+ },
+ {
+ "url": "chrome://global/content/search/startpage-32.png",
+ "imageSize": 32
+ }
+ ]
+}
diff --git a/toolkit/components/search/content/base-browser-search-engines.json b/toolkit/components/search/content/base-browser-search-engines.json
@@ -0,0 +1,74 @@
+[
+ {
+ "base": {
+ "aliases": ["duckduckgo", "ddg"],
+ "classification": "general",
+ "name": "DuckDuckGo",
+ "urls": {
+ "search": {
+ "base": "https://duckduckgo.com/",
+ "params": [],
+ "searchTermParamName": "q"
+ }
+ }
+ },
+ "id": "04e99a38-13ee-47d8-8aa4-64482b3dea99",
+ "identifier": "ddg",
+ "recordType": "engine",
+ "variants": [{ "environment": { "allRegionsAndLocales": true } }]
+ },
+ {
+ "base": {
+ "aliases": ["ddgnoai"],
+ "classification": "general",
+ "name": "DuckDuckGo (no AI)",
+ "urls": {
+ "search": {
+ "base": "https://noai.duckduckgo.com/",
+ "params": [],
+ "searchTermParamName": "q"
+ }
+ }
+ },
+ "id": "91687f02-56dd-4fef-ba26-bf139dff3166",
+ "identifier": "ddg-noai",
+ "recordType": "engine",
+ "variants": [{ "environment": { "allRegionsAndLocales": true } }]
+ },
+ {
+ "base": {
+ "aliases": ["startpage", "sp"],
+ "classification": "general",
+ "name": "Startpage",
+ "urls": {
+ "search": {
+ "base": "https://www.startpage.com/sp/search",
+ "params": [],
+ "searchTermParamName": "q"
+ }
+ }
+ },
+ "id": "927bbd9f-b2f3-48b4-8974-1c1148028f4d",
+ "identifier": "startpage",
+ "recordType": "engine",
+ "variants": [{ "environment": { "allRegionsAndLocales": true } }]
+ },
+ {
+ "recordType": "defaultEngines",
+ "globalDefault": "ddg",
+ "globalDefaultPrivate": "ddg"
+ },
+ {
+ "recordType": "engineOrders",
+ "orders": [
+ {
+ "environment": { "allRegionsAndLocales": true },
+ "order": [
+ "ddg",
+ "ddg-noai",
+ "startpage"
+ ]
+ }
+ ]
+ }
+]
diff --git a/toolkit/components/search/content/duckduckgo.ico b/toolkit/components/search/content/duckduckgo.ico
Binary files differ.
diff --git a/toolkit/components/search/content/startpage-16.png b/toolkit/components/search/content/startpage-16.png
Binary files differ.
diff --git a/toolkit/components/search/content/startpage-32.png b/toolkit/components/search/content/startpage-32.png
Binary files differ.
diff --git a/toolkit/components/search/jar.mn b/toolkit/components/search/jar.mn
@@ -0,0 +1,6 @@
+# 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/.
+
+toolkit.jar:
+ content/global/search/ (content/*)
diff --git a/toolkit/components/search/moz.build b/toolkit/components/search/moz.build
@@ -46,5 +46,7 @@ TESTING_JS_MODULES += [
"tests/SearchTestUtils.sys.mjs",
]
+JAR_MANIFESTS += ["jar.mn"]
+
with Files("**"):
BUG_COMPONENT = ("Firefox", "Search")
diff --git a/toolkit/components/search/tests/xpcshell/test_base_browser.js b/toolkit/components/search/tests/xpcshell/test_base_browser.js
@@ -0,0 +1,58 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This tests the SearchService to check our override of the remote settings is
+ * working as expected.
+ *
+ * When adding new engines, it should be enough to change expectedURLs below.
+ */
+
+"use strict";
+
+const expectedURLs = {
+ ddg: "https://duckduckgo.com/?q=test",
+ "ddg-noai": "https://noai.duckduckgo.com/?q=test",
+ startpage: "https://www.startpage.com/sp/search?q=test",
+};
+const defaultEngine = "ddg";
+
+add_setup(async function setup() {
+ await Services.search.init();
+});
+
+add_task(async function test_listEngines() {
+ const { engines } =
+ await Services.search.wrappedJSObject._fetchEngineSelectorEngines();
+ const foundIdentifiers = engines.map(e => e.identifier);
+ Assert.deepEqual(foundIdentifiers, Object.keys(expectedURLs));
+});
+
+add_task(async function test_default() {
+ Assert.equal(
+ (await Services.search.getDefault()).id,
+ defaultEngine,
+ `${defaultEngine} is our default search engine in normal mode.`
+ );
+ Assert.equal(
+ (await Services.search.getDefaultPrivate()).id,
+ defaultEngine,
+ `${defaultEngine} is our default search engine in PBM.`
+ );
+});
+
+add_task(function test_checkSearchURLs() {
+ for (const [id, url] of Object.entries(expectedURLs)) {
+ const engine = Services.search.getEngineById(id);
+ const foundUrl = engine.getSubmission("test").uri.spec;
+ Assert.equal(foundUrl, url, `The URL of ${engine.name} is not altered.`);
+ }
+});
+
+add_task(async function test_iconsDoesNotFail() {
+ for (const id of Object.keys(expectedURLs)) {
+ const engine = Services.search.getEngineById(id);
+ // No need to assert anything, as in case of error this method should throw.
+ await engine.getIconURL();
+ }
+});
diff --git a/toolkit/components/search/tests/xpcshell/xpcshell.toml b/toolkit/components/search/tests/xpcshell/xpcshell.toml
@@ -37,6 +37,8 @@ tags = "remote-settings"
["test_async.js"]
+["test_base_browser.js"]
+
["test_configExpansion.js"]
support-files = [
"../../schema/search-config-v2-schema.json",