/* 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/. */ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { RemoteSettings: "resource://services-settings/remote-settings.sys.mjs", }); const COLLECTION_NAME = "partitioning-exempt-urls"; const PREF_NAME = "privacy.restrict3rdpartystorage.skip_list"; class Feature { constructor() { this.prefName = PREF_NAME; this.observers = new Set(); this.prefValue = []; this.remoteEntries = []; if (this.prefName) { let prefValue = Services.prefs.getStringPref(this.prefName, null); this.prefValue = prefValue ? prefValue.split(";") : []; Services.prefs.addObserver(this.prefName, this); } } async addAndRunObserver(observer) { this.observers.add(observer); this.notifyObservers(observer); } removeObserver(observer) { this.observers.delete(observer); } observe(subject, topic, data) { if (topic != "nsPref:changed" || data != this.prefName) { console.error(`Unexpected event ${topic} with ${data}`); return; } let prefValue = Services.prefs.getStringPref(this.prefName, null); this.prefValue = prefValue ? prefValue.split(";") : []; this.notifyObservers(); } onRemoteSettingsUpdate(entries) { this.remoteEntries = []; for (let entry of entries) { this.remoteEntries.push( `${entry.firstPartyOrigin},${entry.thirdPartyOrigin}` ); } } notifyObservers(observer = null) { let entries = this.prefValue.concat(this.remoteEntries); let entriesAsString = entries.join(";").toLowerCase(); if (observer) { observer.onExceptionListUpdate(entriesAsString); } else { for (let obs of this.observers) { obs.onExceptionListUpdate(entriesAsString); } } } } export function PartitioningExceptionListService() {} PartitioningExceptionListService.prototype = { classID: Components.ID("{ab94809d-33f0-4f28-af38-01efbd3baf22}"), QueryInterface: ChromeUtils.generateQI([ "nsIPartitioningExceptionListService", ]), _initialized: false, async lazyInit() { if (this._initialized) { return; } this.feature = new Feature(); let rs = lazy.RemoteSettings(COLLECTION_NAME); rs.on("sync", event => { let { data: { current }, } = event; this.onUpdateEntries(current); }); this._initialized = true; let entries; // If the remote settings list hasn't been populated yet we have to make sure // to do it before firing the first notification. // This has to be run after _initialized is set because we'll be // blocked while getting entries from RemoteSetting, and we don't want // LazyInit is executed again. try { // The data will be initially available from the local DB (via a // resource:// URI). entries = await rs.get(); } catch (e) {} // RemoteSettings.get() could return null, ensure passing a list to // onUpdateEntries. this.onUpdateEntries(entries || []); }, onUpdateEntries(entries) { if (!this.feature) { return; } this.feature.onRemoteSettingsUpdate(entries); this.feature.notifyObservers(); }, registerAndRunExceptionListObserver(observer) { // We don't await this; the caller is C++ and won't await this function, // and because we prevent re-entering into this method, once it's been // called once any subsequent calls will early-return anyway - so // awaiting that would be meaningless. Instead, `Feature` implementations // make sure not to call into observers until they have data, and we // make sure to let feature instances know whether we have data // immediately. this.lazyInit(); this.feature.addAndRunObserver(observer); }, unregisterExceptionListObserver(observer) { if (!this.feature) { return; } this.feature.removeObserver(observer); }, };