diff options
Diffstat (limited to 'browser/extensions/webcompat/experiment-apis/trackingProtection.js')
-rw-r--r-- | browser/extensions/webcompat/experiment-apis/trackingProtection.js | 168 |
1 files changed, 168 insertions, 0 deletions
diff --git a/browser/extensions/webcompat/experiment-apis/trackingProtection.js b/browser/extensions/webcompat/experiment-apis/trackingProtection.js new file mode 100644 index 0000000000..557090e974 --- /dev/null +++ b/browser/extensions/webcompat/experiment-apis/trackingProtection.js @@ -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/. */ + +"use strict"; + +/* global ExtensionAPI, ExtensionCommon, ExtensionParent, Services, XPCOMUtils */ + +XPCOMUtils.defineLazyModuleGetters(this, { + Services: "resource://gre/modules/Services.jsm", +}); + +XPCOMUtils.defineLazyGlobalGetters(this, ["URL", "ChannelWrapper"]); + +class Manager { + constructor() { + this._allowLists = new Map(); + } + + _ensureStarted() { + if (this._classifierObserver) { + return; + } + + this._unblockedChannelIds = new Set(); + this._channelClassifier = Cc[ + "@mozilla.org/url-classifier/channel-classifier-service;1" + ].getService(Ci.nsIChannelClassifierService); + this._classifierObserver = {}; + this._classifierObserver.observe = (subject, topic, data) => { + switch (topic) { + case "http-on-stop-request": { + const { channelId } = subject.QueryInterface(Ci.nsIIdentChannel); + this._unblockedChannelIds.delete(channelId); + break; + } + case "urlclassifier-before-block-channel": { + const channel = subject.QueryInterface( + Ci.nsIUrlClassifierBlockedChannel + ); + const { channelId, url } = channel; + let topHost; + try { + topHost = new URL(channel.topLevelUrl).hostname; + } catch (_) { + return; + } + for (const allowList of this._allowLists.values()) { + for (const entry of allowList.values()) { + const { matcher, hosts, notHosts } = entry; + if (matcher.matches(url)) { + if ( + !notHosts?.has(topHost) && + (hosts === true || hosts.has(topHost)) + ) { + this._unblockedChannelIds.add(channelId); + channel.unblock(); + return; + } + } + } + } + break; + } + } + }; + Services.obs.addObserver(this._classifierObserver, "http-on-stop-request"); + this._channelClassifier.addListener(this._classifierObserver); + } + + stop() { + if (!this._classifierObserver) { + return; + } + + Services.obs.removeObserver( + this._classifierObserver, + "http-on-stop-request" + ); + this._channelClassifier.removeListener(this._classifierObserver); + delete this._channelClassifier; + delete this._classifierObserver; + } + + wasChannelIdUnblocked(channelId) { + return this._unblockedChannelIds.has(channelId); + } + + allow(allowListId, patterns, { hosts, notHosts }) { + this._ensureStarted(); + + if (!this._allowLists.has(allowListId)) { + this._allowLists.set(allowListId, new Map()); + } + const allowList = this._allowLists.get(allowListId); + for (const pattern of patterns) { + if (!allowList.has(pattern)) { + allowList.set(pattern, { + matcher: new MatchPattern(pattern), + }); + } + const allowListPattern = allowList.get(pattern); + if (!hosts) { + allowListPattern.hosts = true; + } else { + if (!allowListPattern.hosts) { + allowListPattern.hosts = new Set(); + } + for (const host of hosts) { + allowListPattern.hosts.add(host); + } + } + if (notHosts) { + if (!allowListPattern.notHosts) { + allowListPattern.notHosts = new Set(); + } + for (const notHost of notHosts) { + allowListPattern.notHosts.add(notHost); + } + } + } + } + + revoke(allowListId) { + this._allowLists.delete(allowListId); + } +} +var manager = new Manager(); + +function getChannelId(context, requestId) { + const wrapper = ChannelWrapper.getRegisteredChannel( + requestId, + context.extension.policy, + context.xulBrowser.frameLoader.remoteTab + ); + return wrapper?.channel?.QueryInterface(Ci.nsIIdentChannel)?.channelId; +} + +this.trackingProtection = class extends ExtensionAPI { + onShutdown(isAppShutdown) { + if (manager) { + manager.stop(); + } + } + + getAPI(context) { + return { + trackingProtection: { + async allow(allowListId, patterns, options) { + manager.allow(allowListId, patterns, options); + }, + async revoke(allowListId) { + manager.revoke(allowListId); + }, + async wasRequestUnblocked(requestId) { + if (!manager) { + return false; + } + const channelId = getChannelId(context, requestId); + if (!channelId) { + return false; + } + return manager.wasChannelIdUnblocked(channelId); + }, + }, + }; + } +}; |