Source code

Revision control

Copy as Markdown

Other Tools

/* 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/. */
/**
* Remote Settings backed cache that allows for extending the in-tree
* messaging-system allowlist off-train. Consumers union their hardcoded
* baseline with the getters here, so an empty or unavailable collection means
* only items in the baseline list will be allowed.
*
* Records in the `ms-action-allowlists` collection are discriminated by a
* `list` field:
* { id, list: "actionOnly", value: "<SpecialMessageAction type>" }
* { id, list: "setPref", value: "<pref name>" }
*/
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
});
const COLLECTION_ID = "ms-action-allowlists";
// Caches of Remote Settings supplied entries, keyed by the record's `list`
// discriminator. Empty until the first successful load.
let gActionOnly = new Set();
let gSetPref = new Set();
let gClient = null;
let gInitPromise = null;
function update(records) {
const actionOnly = new Set();
const setPref = new Set();
for (const record of records ?? []) {
if (typeof record?.value !== "string") {
continue;
}
if (record.list === "actionOnly") {
actionOnly.add(record.value);
} else if (record.list === "setPref") {
setPref.add(record.value);
}
}
gActionOnly = actionOnly;
gSetPref = setPref;
}
function onSync({ data: { current } }) {
update(current);
}
export const MessagingSystemAllowlists = {
// Wrapper around lazy.RemoteSettings for stubbing in tests.
RemoteSettings(...args) {
return lazy.RemoteSettings(...args);
},
/**
* Idempotently create the Remote Settings client, subscribe to updates, and
* load the collection into the cache. Safe to call repeatedly and from
* multiple callers concurrently. Never throws.
*
* @returns {Promise<void>}
*/
ensureInit() {
if (!gInitPromise) {
gInitPromise = (async () => {
try {
gClient = this.RemoteSettings(COLLECTION_ID);
gClient.on("sync", onSync);
update(await gClient.get());
} catch (error) {
console.error(`Failed to load ${COLLECTION_ID}:`, error);
if (gClient) {
gClient.off("sync", onSync);
gClient = null;
}
gInitPromise = null;
}
})();
}
return gInitPromise;
},
/**
* Remote Settings supplied action types allowed for action_only messages.
*
* @returns {string[]}
*/
getActionOnlyActions() {
return [...gActionOnly];
},
/**
* Remote Settings supplied pref names allowed for the SET_PREF special
* message action.
*
* @returns {string[]}
*/
getAllowedPrefs() {
return [...gSetPref];
},
// TEST-ONLY: seed the cache directly without Remote Settings.
_setForTest({ actionOnly = [], setPref = [] } = {}) {
gActionOnly = new Set(actionOnly);
gSetPref = new Set(setPref);
},
// TEST-ONLY: reset all cached state and the Remote Settings client.
_resetForTest() {
if (gClient) {
gClient.off("sync", onSync);
}
gActionOnly = new Set();
gSetPref = new Set();
gClient = null;
gInitPromise = null;
},
};