tor-browser

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

commit 36a0e6a17e58a21485c531bd5173e8cba23f85d2
parent 61617ef6966cba6f24070b026f38866f27c49706
Author: Alexandru Marc <amarc@mozilla.com>
Date:   Mon, 29 Dec 2025 19:16:17 +0200

Revert "Bug 2007208 - Adding v2 service support for remote-settings client r=leplatrem" for causing multiple failures

This reverts commit 2c518d40a8da389566b749fb397430f43b54eb58.

Diffstat:
Mservices/settings/RemoteSettingsClient.sys.mjs | 2+-
Mservices/settings/SyncHistory.sys.mjs | 15++++++++-------
Mservices/settings/Utils.sys.mjs | 25++++++++++++-------------
Mservices/settings/remote-settings.sys.mjs | 21+++++++++++++--------
Mservices/settings/test/unit/test_remote_settings.js | 16++++++++--------
Mservices/settings/test/unit/test_remote_settings_older_than_local.js | 2+-
Mservices/settings/test/unit/test_remote_settings_poll.js | 14+++++++-------
Mservices/settings/test/unit/test_remote_settings_release_prefs.js | 37++++++-------------------------------
Mservices/settings/test/unit/test_remote_settings_signatures.js | 14+++++++-------
Mservices/settings/test/unit/test_remote_settings_sync_history.js | 20++++++++++----------
Mtoolkit/modules/AppConstants.sys.mjs | 6+++---
Mtools/@types/subs/AppConstants.sys.d.mts | 2+-
12 files changed, 77 insertions(+), 97 deletions(-)

diff --git a/services/settings/RemoteSettingsClient.sys.mjs b/services/settings/RemoteSettingsClient.sys.mjs @@ -1153,7 +1153,7 @@ export class RemoteSettingsClient extends EventEmitter { const hasLocalData = localTimestamp !== null; const { retry = false } = options; // On retry, we fully re-fetch the collection (no `?_since`). - const since = retry || !hasLocalData ? undefined : localTimestamp; + const since = retry || !hasLocalData ? undefined : `"${localTimestamp}"`; // Define an executor that will verify the signature of the local data. const verifySignatureLocalData = (resolve, reject) => { diff --git a/services/settings/SyncHistory.sys.mjs b/services/settings/SyncHistory.sys.mjs @@ -30,19 +30,20 @@ export class SyncHistory { } /** - * Store the synchronization status. The timestamp is converted and stored as + * Store the synchronization status. The ETag is converted and stored as * a millisecond epoch timestamp. * The entries with the oldest timestamps will be deleted to maintain the * history size under the configured maximum. * - * @param {int} timestamp the timestamp value from the server (eg. 1647961052593) + * @param {string} etag the ETag value from the server (eg. `"1647961052593"`) * @param {string} status the synchronization status (eg. `"success"`) * @param {object} infos optional additional information to keep track of */ - async store(timestamp, status, infos = {}) { + async store(etag, status, infos = {}) { const rkv = await this.#init(); - if (!Number.isInteger(timestamp)) { - throw new Error(`Invalid timestamp value ${timestamp}`); + const timestamp = parseInt(etag.replace('"', ""), 10); + if (Number.isNaN(timestamp)) { + throw new Error(`Invalid ETag value ${etag}`); } const key = `v1-${this.source}\t${timestamp}`; const value = { timestamp, status, infos }; @@ -50,8 +51,8 @@ export class SyncHistory { // Trim old entries. const allEntries = await this.list(); for (let i = this.size; i < allEntries.length; i++) { - let { timestamp: entryTimestamp } = allEntries[i]; - await rkv.delete(`v1-${this.source}\t${entryTimestamp}`); + let { timestamp } = allEntries[i]; + await rkv.delete(`v1-${this.source}\t${timestamp}`); } } diff --git a/services/settings/Utils.sys.mjs b/services/settings/Utils.sys.mjs @@ -50,7 +50,7 @@ ChromeUtils.defineLazyGetter(lazy, "isRunningTests", () => { // Overriding the server URL is normally disabled on Beta and Release channels, // except under some conditions. -ChromeUtils.defineLazyGetter(lazy, "allowServerURL", () => { +ChromeUtils.defineLazyGetter(lazy, "allowServerURLOverride", () => { if (!AppConstants.RELEASE_OR_BETA) { // Always allow to override the server URL on Nightly/DevEdition. return true; @@ -65,14 +65,13 @@ ChromeUtils.defineLazyGetter(lazy, "allowServerURL", () => { return true; } - if (AppConstants.REMOTE_SETTINGS_SERVER_URLS.includes(lazy.gServerURL)) { - return true; + if (lazy.gServerURL != AppConstants.REMOTE_SETTINGS_SERVER_URL) { + log.warn("Ignoring preference override of remote settings server"); + log.warn( + "Allow by setting MOZ_REMOTE_SETTINGS_DEVTOOLS=1 in the environment" + ); } - log.warn("Ignoring preference override of remote settings server"); - log.warn( - "Allow by setting MOZ_REMOTE_SETTINGS_DEVTOOLS=1 in the environment" - ); return false; }); @@ -80,7 +79,7 @@ XPCOMUtils.defineLazyPreferenceGetter( lazy, "gServerURL", "services.settings.server", - AppConstants.REMOTE_SETTINGS_SERVER_URLS + AppConstants.REMOTE_SETTINGS_SERVER_URL ); XPCOMUtils.defineLazyPreferenceGetter( @@ -98,9 +97,9 @@ const _cdnURLs = {}; export var Utils = { get SERVER_URL() { - return lazy.allowServerURL + return lazy.allowServerURLOverride ? lazy.gServerURL - : AppConstants.REMOTE_SETTINGS_SERVER_URLS[0]; + : AppConstants.REMOTE_SETTINGS_SERVER_URL; }, CHANGES_PATH: "/buckets/monitor/collections/changes/changeset", @@ -140,7 +139,7 @@ export var Utils = { get LOAD_DUMPS() { // Load dumps only if pulling data from the production server, or in tests. return ( - AppConstants.REMOTE_SETTINGS_SERVER_URLS.includes(this.SERVER_URL) || + this.SERVER_URL == AppConstants.REMOTE_SETTINGS_SERVER_URL || lazy.isRunningTests ); }, @@ -148,7 +147,7 @@ export var Utils = { get PREVIEW_MODE() { // We want to offer the ability to set preview mode via a preference // for consumers who want to pull from the preview bucket on startup. - if (_isUndefined(this._previewModeEnabled) && lazy.allowServerURL) { + if (_isUndefined(this._previewModeEnabled) && lazy.allowServerURLOverride) { return lazy.gPreviewEnabled; } return !!this._previewModeEnabled; @@ -491,7 +490,7 @@ export var Utils = { return { changes, - timestamp, + currentEtag: `"${timestamp}"`, serverTimeMillis, backoffSeconds, ageSeconds, diff --git a/services/settings/remote-settings.sys.mjs b/services/settings/remote-settings.sys.mjs @@ -482,8 +482,13 @@ function remoteSettingsFunction() { throw new Error(`Polling for changes failed: ${e.message}.`); } - const { serverTimeMillis, changes, timestamp, backoffSeconds, ageSeconds } = - pollResult; + const { + serverTimeMillis, + changes, + currentEtag, + backoffSeconds, + ageSeconds, + } = pollResult; // Report age of server data in Telemetry. pollTelemetryArgs = { age: ageSeconds, ...pollTelemetryArgs }; @@ -560,7 +565,7 @@ function remoteSettingsFunction() { const syncTelemetryArgs = { source: TELEMETRY_SOURCE_SYNC, duration: durationMilliseconds, - timestamp, + timestamp: `${currentEtag}`, trigger, }; @@ -574,7 +579,7 @@ function remoteSettingsFunction() { ); // Keep track of sync failure in history. await lazy.gSyncHistory - .store(timestamp, status, { + .store(currentEtag, status, { expectedTimestamp, errorName: firstError.name, }) @@ -606,7 +611,7 @@ function remoteSettingsFunction() { } // Save current Etag for next poll. - lazy.gPrefs.setStringPref(PREF_SETTINGS_LAST_ETAG, timestamp); + lazy.gPrefs.setStringPref(PREF_SETTINGS_LAST_ETAG, currentEtag); // Report the global synchronization success. const status = lazy.UptakeTelemetry.STATUS.SUCCESS; @@ -617,7 +622,7 @@ function remoteSettingsFunction() { ); // Keep track of sync success in history. await lazy.gSyncHistory - .store(timestamp, status) + .store(currentEtag, status) .catch(error => console.error(error)); lazy.console.info( @@ -657,7 +662,7 @@ function remoteSettingsFunction() { if (!localOnly) { // Make sure we fetch the latest server info, use a random cache bust value. const randomCacheBust = 99990000 + Math.floor(Math.random() * 9999); - ({ changes, timestamp: serverTimestamp } = + ({ changes, currentEtag: serverTimestamp } = await lazy.Utils.fetchLatestChanges(lazy.Utils.SERVER_URL, { expected: randomCacheBust, })); @@ -790,7 +795,7 @@ export var remoteSettingsBroadcastHandler = { ); return RemoteSettings.pollChanges({ - expectedTimestamp: version.replaceAll('"', ""), + expectedTimestamp: version.replace('"', ""), trigger: isStartup ? "startup" : "broadcast", }); }, diff --git a/services/settings/test/unit/test_remote_settings.js b/services/settings/test/unit/test_remote_settings.js @@ -810,7 +810,7 @@ add_task(async function test_inspect_method() { equal(mainBucket, "main"); equal(serverURL, `http://localhost:${server.identity.primaryPort}/v1`); equal(defaultSigner, rsSigner); - equal(serverTimestamp, "5000"); + equal(serverTimestamp, '"5000"'); // A collection is listed in .inspect() if it has local data or if there // is a JSON dump for it. @@ -1527,7 +1527,7 @@ wNuvFqc= ], }, }, - "GET:/v1/buckets/main/collections/password-fields/changeset?_expected=3001&_since=3000": + "GET:/v1/buckets/main/collections/password-fields/changeset?_expected=3001&_since=%223000%22": { sampleHeaders: [ "Access-Control-Allow-Origin: *", @@ -1558,7 +1558,7 @@ wNuvFqc= ], }, }, - "GET:/v1/buckets/main/collections/password-fields/changeset?_expected=4001&_since=4000": + "GET:/v1/buckets/main/collections/password-fields/changeset?_expected=4001&_since=%224000%22": { sampleHeaders: [ "Access-Control-Allow-Origin: *", @@ -1581,7 +1581,7 @@ wNuvFqc= ], }, }, - "GET:/v1/buckets/main/collections/password-fields/changeset?_expected=10000&_since=9999": + "GET:/v1/buckets/main/collections/password-fields/changeset?_expected=10000&_since=%229999%22": { sampleHeaders: [ "Access-Control-Allow-Origin: *", @@ -1596,7 +1596,7 @@ wNuvFqc= error: "Service Unavailable", }, }, - "GET:/v1/buckets/main/collections/password-fields/changeset?_expected=10001&_since=10000": + "GET:/v1/buckets/main/collections/password-fields/changeset?_expected=10001&_since=%2210000%22": { sampleHeaders: [ "Access-Control-Allow-Origin: *", @@ -1608,7 +1608,7 @@ wNuvFqc= status: { status: 200, statusText: "OK" }, responseBody: "<invalid json", }, - "GET:/v1/buckets/main/collections/password-fields/changeset?_expected=11001&_since=11000": + "GET:/v1/buckets/main/collections/password-fields/changeset?_expected=11001&_since=%2211000%22": { sampleHeaders: [ "Access-Control-Allow-Origin: *", @@ -1695,7 +1695,7 @@ wNuvFqc= ], }, }, - "GET:/v1/buckets/main/collections/password-fields/changeset?_expected=1337&_since=3000": + "GET:/v1/buckets/main/collections/password-fields/changeset?_expected=1337&_since=%223000%22": { sampleHeaders: [ "Access-Control-Allow-Origin: *", @@ -1795,7 +1795,7 @@ wNuvFqc= ], }, }, - "GET:/v1/buckets/main/collections/with-local-fields/changeset?_expected=3000&_since=2000": + "GET:/v1/buckets/main/collections/with-local-fields/changeset?_expected=3000&_since=%222000%22": { sampleHeaders: [ "Access-Control-Allow-Origin: *", diff --git a/services/settings/test/unit/test_remote_settings_older_than_local.js b/services/settings/test/unit/test_remote_settings_older_than_local.js @@ -41,7 +41,7 @@ add_setup(() => { ], }, // Client fetches with _expected=222&_since=333 - "/v1/buckets/main/collections/some-cid/changeset?_expected=222&_since=333": + "/v1/buckets/main/collections/some-cid/changeset?_expected=222&_since=%22333%22": { timestamp: 222, metadata: { diff --git a/services/settings/test/unit/test_remote_settings_poll.js b/services/settings/test/unit/test_remote_settings_poll.js @@ -73,7 +73,7 @@ add_task(async function test_an_event_is_sent_on_start() { server.registerPathHandler(CHANGES_PATH, (request, response) => { response.write(JSON.stringify({ timestamp: 42, changes: [] })); response.setHeader("Content-Type", "application/json; charset=UTF-8"); - response.setHeader("ETag", "42"); + response.setHeader("ETag", '"42"'); response.setHeader("Date", new Date().toUTCString()); response.setStatusLine(null, 200, "OK"); }); @@ -173,7 +173,7 @@ add_task(async function test_check_success() { Assert.ok(maybeSyncCalled, "maybeSync was called"); Assert.ok(notificationObserved, "a notification should have been observed"); // Last timestamp was saved. An ETag header value is a quoted string. - Assert.equal(Services.prefs.getStringPref(PREF_LAST_ETAG), "1100"); + Assert.equal(Services.prefs.getStringPref(PREF_LAST_ETAG), '"1100"'); // check the last_update is updated Assert.equal(Services.prefs.getIntPref(PREF_LAST_UPDATE), serverTime / 1000); @@ -238,7 +238,7 @@ add_task(async function test_update_timer_interface() { }); // Everything went fine. - Assert.equal(Services.prefs.getStringPref(PREF_LAST_ETAG), "42"); + Assert.equal(Services.prefs.getStringPref(PREF_LAST_ETAG), '"42"'); Assert.equal(Services.prefs.getIntPref(PREF_LAST_UPDATE), serverTime / 1000); }); add_task(clear_state); @@ -253,7 +253,7 @@ add_task(async function test_check_up_to_date() { const serverTime = 4000; server.registerPathHandler(CHANGES_PATH, serveChangesEntries(serverTime, [])); - Services.prefs.setStringPref(PREF_LAST_ETAG, "1100"); + Services.prefs.setStringPref(PREF_LAST_ETAG, '"1100"'); // Ensure that the remote-settings:changes-poll-end notification is sent. let notificationObserved = false; @@ -414,7 +414,7 @@ add_task(async function test_age_of_data_is_reported_in_uptake_status() { source: TELEMETRY_SOURCE_SYNC, duration: () => true, trigger: "manual", - timestamp: `${recordsTimestamp}`, + timestamp: `"${recordsTimestamp}"`, }, ], ], @@ -493,7 +493,7 @@ add_task(async function test_success_with_partial_list() { collection: "poll-test-collection", }, ]; - if (request.queryString.includes(`_since=42`)) { + if (request.queryString.includes(`_since=${encodeURIComponent('"42"')}`)) { response.write( JSON.stringify({ timestamp: 43, @@ -1308,7 +1308,7 @@ add_task( timestamp: 42, }) ); - response.setHeader("ETag", "42"); + response.setHeader("ETag", '"42"'); response.setStatusLine(null, 200, "OK"); response.setHeader("Content-Type", "application/json; charset=UTF-8"); response.setHeader("Date", new Date().toUTCString()); diff --git a/services/settings/test/unit/test_remote_settings_release_prefs.js b/services/settings/test/unit/test_remote_settings_release_prefs.js @@ -57,7 +57,7 @@ add_task( Assert.equal( Utils.SERVER_URL, - AppConstants.REMOTE_SETTINGS_SERVER_URLS[0], + AppConstants.REMOTE_SETTINGS_SERVER_URL, "Server url pref was not read in release" ); } @@ -65,31 +65,6 @@ add_task( add_task( { - skip_if: () => !AppConstants.RELEASE_OR_BETA, - }, - async function test_server_url_can_be_changed_to_another_valid_option() { - Services.prefs.setStringPref( - "services.settings.server", - AppConstants.REMOTE_SETTINGS_SERVER_URLS[1] - ); - - const Utils = getNewUtils(); - - Assert.equal( - Utils.SERVER_URL, - AppConstants.REMOTE_SETTINGS_SERVER_URLS[1], - "Server url pref was read as second option in release" - ); - - Services.prefs.setStringPref( - "services.settings.server", - AppConstants.REMOTE_SETTINGS_SERVER_URLS[0] - ); - } -); - -add_task( - { skip_if: () => AppConstants.RELEASE_OR_BETA, }, async function test_server_url_cannot_be_toggled_in_dev_nightly() { @@ -102,7 +77,7 @@ add_task( Assert.notEqual( Utils.SERVER_URL, - AppConstants.REMOTE_SETTINGS_SERVER_URLS[0], + AppConstants.REMOTE_SETTINGS_SERVER_URL, "Server url pref was read in nightly/dev" ); } @@ -151,7 +126,7 @@ add_task( Assert.equal( Utils.SERVER_URL, - AppConstants.REMOTE_SETTINGS_SERVER_URLS[0], + AppConstants.REMOTE_SETTINGS_SERVER_URL, "Server url pref was not read" ); Assert.ok(Utils.LOAD_DUMPS, "Dumps will always be loaded"); @@ -172,7 +147,7 @@ add_task( Assert.notEqual( Utils.SERVER_URL, - AppConstants.REMOTE_SETTINGS_SERVER_URLS[0], + AppConstants.REMOTE_SETTINGS_SERVER_URL, "Server url pref was read" ); Assert.ok(!Utils.LOAD_DUMPS, "Dumps are not loaded if server is not prod"); @@ -192,7 +167,7 @@ add_task( Assert.notEqual( Utils.SERVER_URL, - AppConstants.REMOTE_SETTINGS_SERVER_URLS[0], + AppConstants.REMOTE_SETTINGS_SERVER_URL, "Server url pref was read" ); } @@ -266,7 +241,7 @@ add_task( Services.env.set("MOZ_REMOTE_SETTINGS_DEVTOOLS", "1"); Services.prefs.setStringPref( "services.settings.server", - AppConstants.REMOTE_SETTINGS_SERVER_URLS[0] + AppConstants.REMOTE_SETTINGS_SERVER_URL ); const Utils = getNewUtils(); diff --git a/services/settings/test/unit/test_remote_settings_signatures.js b/services/settings/test/unit/test_remote_settings_signatures.js @@ -441,7 +441,7 @@ add_task(async function test_check_synchronization_with_signatures() { }; const twoItemsResponses = { - "GET:/v1/buckets/main/collections/signed/changeset?_expected=3000&_since=1000": + "GET:/v1/buckets/main/collections/signed/changeset?_expected=3000&_since=%221000%22": [RESPONSE_TWO_ADDED], }; registerHandlers(twoItemsResponses); @@ -482,7 +482,7 @@ add_task(async function test_check_synchronization_with_signatures() { }; const oneAddedOneRemovedResponses = { - "GET:/v1/buckets/main/collections/signed/changeset?_expected=4000&_since=3000": + "GET:/v1/buckets/main/collections/signed/changeset?_expected=4000&_since=%223000%22": [RESPONSE_ONE_ADDED_ONE_REMOVED], }; registerHandlers(oneAddedOneRemovedResponses); @@ -520,7 +520,7 @@ add_task(async function test_check_synchronization_with_signatures() { }; const noOpResponses = { - "GET:/v1/buckets/main/collections/signed/changeset?_expected=4100&_since=4000": + "GET:/v1/buckets/main/collections/signed/changeset?_expected=4100&_since=%224000%22": [RESPONSE_EMPTY_NO_UPDATE], }; registerHandlers(noOpResponses); @@ -581,7 +581,7 @@ add_task(async function test_check_synchronization_with_signatures() { // The first collection state is the three item collection (since // there was sync with no updates before) - but, since the signature is wrong, // another request will be made... - "GET:/v1/buckets/main/collections/signed/changeset?_expected=5000&_since=4000": + "GET:/v1/buckets/main/collections/signed/changeset?_expected=5000&_since=%224000%22": [RESPONSE_EMPTY_NO_UPDATE_BAD_SIG], // Subsequent signature returned is a valid one for the three item // collection. @@ -639,7 +639,7 @@ add_task(async function test_check_synchronization_with_signatures() { const badSigGoodOldResponses = { // The first collection state is the current state (since there's no update // - but, since the signature is wrong, another request will be made) - "GET:/v1/buckets/main/collections/signed/changeset?_expected=5000&_since=4000": + "GET:/v1/buckets/main/collections/signed/changeset?_expected=5000&_since=%224000%22": [RESPONSE_EMPTY_NO_UPDATE_BAD_SIG], // The next request is for the full collection. This will be // checked against the valid signature and last_modified times will be @@ -691,7 +691,7 @@ add_task(async function test_check_synchronization_with_signatures() { }; const badLocalContentGoodSigResponses = { - "GET:/v1/buckets/main/collections/signed/changeset?_expected=5000&_since=3900": + "GET:/v1/buckets/main/collections/signed/changeset?_expected=5000&_since=%223900%22": [RESPONSE_COMPLETE_BAD_SIG], "GET:/v1/buckets/main/collections/signed/changeset?_expected=5000": [ RESPONSE_COMPLETE_INITIAL, @@ -824,7 +824,7 @@ add_task(async function test_check_synchronization_with_signatures() { }), }; const allBadSigResponses = { - "GET:/v1/buckets/main/collections/signed/changeset?_expected=6000&_since=4000": + "GET:/v1/buckets/main/collections/signed/changeset?_expected=6000&_since=%224000%22": [RESPONSE_EMPTY_NO_UPDATE_BAD_SIG_6000], "GET:/v1/buckets/main/collections/signed/changeset?_expected=6000": [ RESPONSE_ONLY_RECORD4_BAD_SIG, diff --git a/services/settings/test/unit/test_remote_settings_sync_history.js b/services/settings/test/unit/test_remote_settings_sync_history.js @@ -7,9 +7,9 @@ add_task(clear_state); add_task(async function test_entries_are_stored_by_source() { const history = new SyncHistory(); - await history.store(42, "success", { pi: "3.14" }); + await history.store("42", "success", { pi: "3.14" }); // Check that history is isolated by source. - await new SyncHistory("main/cfr").store(88, "error"); + await new SyncHistory("main/cfr").store("88", "error"); const l = await history.list(); @@ -29,15 +29,15 @@ add_task( const history = new SyncHistory("settings-sync", { size: 3 }); const anotherHistory = await new SyncHistory("main/cfr"); - await history.store(42, "success"); - await history.store(41, "sync_error"); - await history.store(43, "up_to_date"); + await history.store("42", "success"); + await history.store("41", "sync_error"); + await history.store("43", "up_to_date"); let l = await history.list(); Assert.equal(l.length, 3); - await history.store(44, "success"); - await anotherHistory.store(44, "success"); + await history.store("44", "success"); + await anotherHistory.store("44", "success"); l = await history.list(); Assert.equal(l.length, 3); @@ -51,9 +51,9 @@ add_task(clear_state); add_task(async function test_entries_are_sorted_by_timestamp_desc() { const history = new SyncHistory("settings-sync"); - await history.store(42, "success"); - await history.store(41, "sync_error"); - await history.store(44, "up_to_date"); + await history.store("42", "success"); + await history.store("41", "sync_error"); + await history.store("44", "up_to_date"); const l = await history.list(); diff --git a/toolkit/modules/AppConstants.sys.mjs b/toolkit/modules/AppConstants.sys.mjs @@ -207,11 +207,11 @@ export var AppConstants = Object.freeze({ ENABLE_WEBDRIVER: @ENABLE_WEBDRIVER_BOOL@, - REMOTE_SETTINGS_SERVER_URLS: + REMOTE_SETTINGS_SERVER_URL: #ifdef MOZ_THUNDERBIRD - [ "https://thunderbird-settings.thunderbird.net/v1" ], + "https://thunderbird-settings.thunderbird.net/v1", #else - [ "https://firefox.settings.services.mozilla.com/v1", "https://firefox.settings.services.mozilla.com/v2" ], + "https://firefox.settings.services.mozilla.com/v1", #endif REMOTE_SETTINGS_VERIFY_SIGNATURE: diff --git a/tools/@types/subs/AppConstants.sys.d.mts b/tools/@types/subs/AppConstants.sys.d.mts @@ -154,7 +154,7 @@ export const AppConstants: Readonly<{ ENABLE_WEBDRIVER: boolean; // #ifdef !MOZ_THUNDERBIRD - REMOTE_SETTINGS_SERVER_URLS: [ "https://firefox.settings.services.mozilla.com/v1" ]; + REMOTE_SETTINGS_SERVER_URL: "https://firefox.settings.services.mozilla.com/v1"; // #ifdef !MOZ_THUNDERBIRD REMOTE_SETTINGS_VERIFY_SIGNATURE: boolean;