summaryrefslogtreecommitdiffstats
path: root/netwerk/url-classifier/UrlClassifierExceptionListService.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/url-classifier/UrlClassifierExceptionListService.sys.mjs')
-rw-r--r--netwerk/url-classifier/UrlClassifierExceptionListService.sys.mjs168
1 files changed, 168 insertions, 0 deletions
diff --git a/netwerk/url-classifier/UrlClassifierExceptionListService.sys.mjs b/netwerk/url-classifier/UrlClassifierExceptionListService.sys.mjs
new file mode 100644
index 0000000000..8c2bc8a3aa
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierExceptionListService.sys.mjs
@@ -0,0 +1,168 @@
+/* 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/. */
+
+export function UrlClassifierExceptionListService() {}
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
+});
+
+const COLLECTION_NAME = "url-classifier-skip-urls";
+
+class Feature {
+ constructor(name, prefName) {
+ this.name = name;
+ this.prefName = prefName;
+ this.observers = new Set();
+ this.prefValue = null;
+ this.remoteEntries = null;
+
+ if (prefName) {
+ this.prefValue = Services.prefs.getStringPref(this.prefName, null);
+ Services.prefs.addObserver(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;
+ }
+
+ this.prefValue = Services.prefs.getStringPref(this.prefName, null);
+ this.notifyObservers();
+ }
+
+ onRemoteSettingsUpdate(entries) {
+ this.remoteEntries = [];
+
+ for (let entry of entries) {
+ if (entry.feature == this.name) {
+ this.remoteEntries.push(entry.pattern.toLowerCase());
+ }
+ }
+ }
+
+ notifyObservers(observer = null) {
+ let entries = [];
+ if (this.prefValue) {
+ entries = this.prefValue.split(",");
+ }
+
+ if (this.remoteEntries) {
+ for (let entry of this.remoteEntries) {
+ entries.push(entry);
+ }
+ }
+
+ let entriesAsString = entries.join(",").toLowerCase();
+ if (observer) {
+ observer.onExceptionListUpdate(entriesAsString);
+ } else {
+ for (let obs of this.observers) {
+ obs.onExceptionListUpdate(entriesAsString);
+ }
+ }
+ }
+}
+
+UrlClassifierExceptionListService.prototype = {
+ classID: Components.ID("{b9f4fd03-9d87-4bfd-9958-85a821750ddc}"),
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIUrlClassifierExceptionListService",
+ ]),
+
+ features: {},
+ _initialized: false,
+
+ async lazyInit() {
+ if (this._initialized) {
+ return;
+ }
+
+ let rs = lazy.RemoteSettings(COLLECTION_NAME);
+ rs.on("sync", event => {
+ let {
+ data: { current },
+ } = event;
+ this.entries = current || [];
+ this.onUpdateEntries(current);
+ });
+
+ this._initialized = true;
+
+ // 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).
+ this.entries = await rs.get();
+ } catch (e) {}
+
+ // RemoteSettings.get() could return null, ensure passing a list to
+ // onUpdateEntries.
+ if (!this.entries) {
+ this.entries = [];
+ }
+
+ this.onUpdateEntries(this.entries);
+ },
+
+ onUpdateEntries(entries) {
+ for (let key of Object.keys(this.features)) {
+ let feature = this.features[key];
+ feature.onRemoteSettingsUpdate(entries);
+ feature.notifyObservers();
+ }
+ },
+
+ registerAndRunExceptionListObserver(feature, prefName, 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();
+
+ if (!this.features[feature]) {
+ let featureObj = new Feature(feature, prefName);
+ this.features[feature] = featureObj;
+ // If we've previously initialized, we need to pass the entries
+ // we already have to the new feature.
+ if (this.entries) {
+ featureObj.onRemoteSettingsUpdate(this.entries);
+ }
+ }
+ this.features[feature].addAndRunObserver(observer);
+ },
+
+ unregisterExceptionListObserver(feature, observer) {
+ if (!this.features[feature]) {
+ return;
+ }
+ this.features[feature].removeObserver(observer);
+ },
+
+ clear() {
+ this.features = {};
+ this._initialized = false;
+ this.entries = null;
+ },
+};