/* 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: "" } * { id, list: "setPref", value: "" } */ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { RemoteSettings: "resource://services-settings/remote-settings.sys.mjs", }); 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} */ 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; }, };