Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / netwerk / url-classifier / UrlClassifierExceptionListService.sys.mjs
blob8c2bc8a3aac1818a2fa01ff52f0466a5d11db973
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 export function UrlClassifierExceptionListService() {}
7 const lazy = {};
9 ChromeUtils.defineESModuleGetters(lazy, {
10   RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
11 });
13 const COLLECTION_NAME = "url-classifier-skip-urls";
15 class Feature {
16   constructor(name, prefName) {
17     this.name = name;
18     this.prefName = prefName;
19     this.observers = new Set();
20     this.prefValue = null;
21     this.remoteEntries = null;
23     if (prefName) {
24       this.prefValue = Services.prefs.getStringPref(this.prefName, null);
25       Services.prefs.addObserver(prefName, this);
26     }
27   }
29   async addAndRunObserver(observer) {
30     this.observers.add(observer);
31     this.notifyObservers(observer);
32   }
34   removeObserver(observer) {
35     this.observers.delete(observer);
36   }
38   observe(subject, topic, data) {
39     if (topic != "nsPref:changed" || data != this.prefName) {
40       console.error(`Unexpected event ${topic} with ${data}`);
41       return;
42     }
44     this.prefValue = Services.prefs.getStringPref(this.prefName, null);
45     this.notifyObservers();
46   }
48   onRemoteSettingsUpdate(entries) {
49     this.remoteEntries = [];
51     for (let entry of entries) {
52       if (entry.feature == this.name) {
53         this.remoteEntries.push(entry.pattern.toLowerCase());
54       }
55     }
56   }
58   notifyObservers(observer = null) {
59     let entries = [];
60     if (this.prefValue) {
61       entries = this.prefValue.split(",");
62     }
64     if (this.remoteEntries) {
65       for (let entry of this.remoteEntries) {
66         entries.push(entry);
67       }
68     }
70     let entriesAsString = entries.join(",").toLowerCase();
71     if (observer) {
72       observer.onExceptionListUpdate(entriesAsString);
73     } else {
74       for (let obs of this.observers) {
75         obs.onExceptionListUpdate(entriesAsString);
76       }
77     }
78   }
81 UrlClassifierExceptionListService.prototype = {
82   classID: Components.ID("{b9f4fd03-9d87-4bfd-9958-85a821750ddc}"),
83   QueryInterface: ChromeUtils.generateQI([
84     "nsIUrlClassifierExceptionListService",
85   ]),
87   features: {},
88   _initialized: false,
90   async lazyInit() {
91     if (this._initialized) {
92       return;
93     }
95     let rs = lazy.RemoteSettings(COLLECTION_NAME);
96     rs.on("sync", event => {
97       let {
98         data: { current },
99       } = event;
100       this.entries = current || [];
101       this.onUpdateEntries(current);
102     });
104     this._initialized = true;
106     // If the remote settings list hasn't been populated yet we have to make sure
107     // to do it before firing the first notification.
108     // This has to be run after _initialized is set because we'll be
109     // blocked while getting entries from RemoteSetting, and we don't want
110     // LazyInit is executed again.
111     try {
112       // The data will be initially available from the local DB (via a
113       // resource:// URI).
114       this.entries = await rs.get();
115     } catch (e) {}
117     // RemoteSettings.get() could return null, ensure passing a list to
118     // onUpdateEntries.
119     if (!this.entries) {
120       this.entries = [];
121     }
123     this.onUpdateEntries(this.entries);
124   },
126   onUpdateEntries(entries) {
127     for (let key of Object.keys(this.features)) {
128       let feature = this.features[key];
129       feature.onRemoteSettingsUpdate(entries);
130       feature.notifyObservers();
131     }
132   },
134   registerAndRunExceptionListObserver(feature, prefName, observer) {
135     // We don't await this; the caller is C++ and won't await this function,
136     // and because we prevent re-entering into this method, once it's been
137     // called once any subsequent calls will early-return anyway - so
138     // awaiting that would be meaningless. Instead, `Feature` implementations
139     // make sure not to call into observers until they have data, and we
140     // make sure to let feature instances know whether we have data
141     // immediately.
142     this.lazyInit();
144     if (!this.features[feature]) {
145       let featureObj = new Feature(feature, prefName);
146       this.features[feature] = featureObj;
147       // If we've previously initialized, we need to pass the entries
148       // we already have to the new feature.
149       if (this.entries) {
150         featureObj.onRemoteSettingsUpdate(this.entries);
151       }
152     }
153     this.features[feature].addAndRunObserver(observer);
154   },
156   unregisterExceptionListObserver(feature, observer) {
157     if (!this.features[feature]) {
158       return;
159     }
160     this.features[feature].removeObserver(observer);
161   },
163   clear() {
164     this.features = {};
165     this._initialized = false;
166     this.entries = null;
167   },