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:
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;