diff options
Diffstat (limited to 'toolkit/components/antitracking/PartitioningExceptionListService.sys.mjs')
-rw-r--r-- | toolkit/components/antitracking/PartitioningExceptionListService.sys.mjs | 142 |
1 files changed, 142 insertions, 0 deletions
diff --git a/toolkit/components/antitracking/PartitioningExceptionListService.sys.mjs b/toolkit/components/antitracking/PartitioningExceptionListService.sys.mjs new file mode 100644 index 0000000000..8df2bc651d --- /dev/null +++ b/toolkit/components/antitracking/PartitioningExceptionListService.sys.mjs @@ -0,0 +1,142 @@ +/* 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); + }, +}; |