diff options
Diffstat (limited to '')
10 files changed, 588 insertions, 0 deletions
diff --git a/browser/extensions/webcompat/experiment-apis/aboutConfigPrefs.js b/browser/extensions/webcompat/experiment-apis/aboutConfigPrefs.js new file mode 100644 index 0000000000..21ad297dc1 --- /dev/null +++ b/browser/extensions/webcompat/experiment-apis/aboutConfigPrefs.js @@ -0,0 +1,53 @@ +/* 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, Services, XPCOMUtils */ + +this.aboutConfigPrefs = class extends ExtensionAPI { + getAPI(context) { + const EventManager = ExtensionCommon.EventManager; + const extensionIDBase = context.extension.id.split("@")[0]; + const extensionPrefNameBase = `extensions.${extensionIDBase}.`; + + return { + aboutConfigPrefs: { + onPrefChange: new EventManager({ + context, + name: "aboutConfigPrefs.onUAOverridesPrefChange", + register: (fire, name) => { + const prefName = `${extensionPrefNameBase}${name}`; + const callback = () => { + fire.async(name).catch(() => {}); // ignore Message Manager disconnects + }; + Services.prefs.addObserver(prefName, callback); + return () => { + Services.prefs.removeObserver(prefName, callback); + }; + }, + }).api(), + async getBranch(branchName) { + const branch = `${extensionPrefNameBase}${branchName}.`; + return Services.prefs.getChildList(branch).map(pref => { + const name = pref.replace(branch, ""); + return { name, value: Services.prefs.getBoolPref(pref) }; + }); + }, + async getPref(name) { + try { + return Services.prefs.getBoolPref( + `${extensionPrefNameBase}${name}` + ); + } catch (_) { + return undefined; + } + }, + async setPref(name, value) { + Services.prefs.setBoolPref(`${extensionPrefNameBase}${name}`, value); + }, + }, + }; + } +}; diff --git a/browser/extensions/webcompat/experiment-apis/aboutConfigPrefs.json b/browser/extensions/webcompat/experiment-apis/aboutConfigPrefs.json new file mode 100644 index 0000000000..44284f199c --- /dev/null +++ b/browser/extensions/webcompat/experiment-apis/aboutConfigPrefs.json @@ -0,0 +1,72 @@ +[ + { + "namespace": "aboutConfigPrefs", + "description": "experimental API extension to allow access to about:config preferences", + "events": [ + { + "name": "onPrefChange", + "type": "function", + "parameters": [ + { + "name": "name", + "type": "string", + "description": "The preference which changed" + } + ], + "extraParameters": [ + { + "name": "name", + "type": "string", + "description": "The preference to monitor" + } + ] + } + ], + "functions": [ + { + "name": "getBranch", + "type": "function", + "description": "Get all child prefs for a branch", + "parameters": [ + { + "name": "branchName", + "type": "string", + "description": "The branch name" + } + ], + "async": true + }, + { + "name": "getPref", + "type": "function", + "description": "Get a preference's value", + "parameters": [ + { + "name": "name", + "type": "string", + "description": "The preference name" + } + ], + "async": true + }, + { + "name": "setPref", + "type": "function", + "description": "Set a preference's value", + "parameters": [ + { + "name": "name", + "type": "string", + "description": "The preference name" + }, + { + "name": "value", + "type": "boolean", + "description": "The new value" + } + ], + "async": true + } + ] + } +] diff --git a/browser/extensions/webcompat/experiment-apis/appConstants.js b/browser/extensions/webcompat/experiment-apis/appConstants.js new file mode 100644 index 0000000000..2869f299a4 --- /dev/null +++ b/browser/extensions/webcompat/experiment-apis/appConstants.js @@ -0,0 +1,28 @@ +/* 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 AppConstants, ExtensionAPI, XPCOMUtils */ + +this.appConstants = class extends ExtensionAPI { + getAPI(context) { + return { + appConstants: { + getReleaseBranch: () => { + if (AppConstants.NIGHTLY_BUILD) { + return "nightly"; + } else if (AppConstants.MOZ_DEV_EDITION) { + return "dev_edition"; + } else if (AppConstants.EARLY_BETA_OR_EARLIER) { + return "early_beta_or_earlier"; + } else if (AppConstants.RELEASE_OR_BETA) { + return "release_or_beta"; + } + return "unknown"; + }, + }, + }; + } +}; diff --git a/browser/extensions/webcompat/experiment-apis/appConstants.json b/browser/extensions/webcompat/experiment-apis/appConstants.json new file mode 100644 index 0000000000..cf04915eca --- /dev/null +++ b/browser/extensions/webcompat/experiment-apis/appConstants.json @@ -0,0 +1,15 @@ +[ + { + "namespace": "appConstants", + "description": "experimental API to expose some app constants", + "functions": [ + { + "name": "getReleaseBranch", + "type": "function", + "description": "", + "async": true, + "parameters": [] + } + ] + } +] diff --git a/browser/extensions/webcompat/experiment-apis/matchPatterns.js b/browser/extensions/webcompat/experiment-apis/matchPatterns.js new file mode 100644 index 0000000000..422cba5fc4 --- /dev/null +++ b/browser/extensions/webcompat/experiment-apis/matchPatterns.js @@ -0,0 +1,30 @@ +/* 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 */ + +this.matchPatterns = class extends ExtensionAPI { + getAPI(context) { + return { + matchPatterns: { + getMatcher(patterns) { + const set = new MatchPatternSet(patterns); + return Cu.cloneInto( + { + matches: url => { + return set.matches(url); + }, + }, + context.cloneScope, + { + cloneFunctions: true, + } + ); + }, + }, + }; + } +}; diff --git a/browser/extensions/webcompat/experiment-apis/matchPatterns.json b/browser/extensions/webcompat/experiment-apis/matchPatterns.json new file mode 100644 index 0000000000..6fb4dc10fc --- /dev/null +++ b/browser/extensions/webcompat/experiment-apis/matchPatterns.json @@ -0,0 +1,29 @@ +[ + { + "namespace": "matchPatterns", + "description": "experimental API extension to expose MatchPattern functionality", + "functions": [ + { + "name": "getMatcher", + "type": "function", + "description": "get a MatchPatternSet", + "parameters": [ + { + "name": "patterns", + "description": "Array of string URL patterns to match", + "type": "array", + "items": { + "type": "string" + } + } + ], + "returns": { + "type": "object", + "properties": { + "matches": { "type": "function" } + } + } + } + ] + } +] diff --git a/browser/extensions/webcompat/experiment-apis/systemManufacturer.js b/browser/extensions/webcompat/experiment-apis/systemManufacturer.js new file mode 100644 index 0000000000..b7dc68415c --- /dev/null +++ b/browser/extensions/webcompat/experiment-apis/systemManufacturer.js @@ -0,0 +1,23 @@ +/* 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, Services, XPCOMUtils */ + +this.systemManufacturer = class extends ExtensionAPI { + getAPI(context) { + return { + systemManufacturer: { + getManufacturer() { + try { + return Services.sysinfo.getProperty("manufacturer"); + } catch (_) { + return undefined; + } + }, + }, + }; + } +}; diff --git a/browser/extensions/webcompat/experiment-apis/systemManufacturer.json b/browser/extensions/webcompat/experiment-apis/systemManufacturer.json new file mode 100644 index 0000000000..c64fccc46d --- /dev/null +++ b/browser/extensions/webcompat/experiment-apis/systemManufacturer.json @@ -0,0 +1,20 @@ +[ + { + "namespace": "systemManufacturer", + "description": "experimental API extension to allow reading the device's manufacturer", + "functions": [ + { + "name": "getManufacturer", + "type": "function", + "description": "Get the device's manufacturer", + "parameters": [], + "returns": { + "type": "string", + "properties": {}, + "additionalProperties": { "type": "any" }, + "description": "The manufacturer's name." + } + } + ] + } +] diff --git a/browser/extensions/webcompat/experiment-apis/trackingProtection.js b/browser/extensions/webcompat/experiment-apis/trackingProtection.js new file mode 100644 index 0000000000..0f5d9a4233 --- /dev/null +++ b/browser/extensions/webcompat/experiment-apis/trackingProtection.js @@ -0,0 +1,216 @@ +/* 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 */ + +// eslint-disable-next-line mozilla/reject-importGlobalProperties +XPCOMUtils.defineLazyGlobalGetters(this, ["URL", "ChannelWrapper"]); + +class AllowList { + constructor(id) { + this._id = id; + } + + setShims(patterns, notHosts) { + this._shimPatterns = patterns; + this._shimMatcher = new MatchPatternSet(patterns || []); + this._shimNotHosts = notHosts || []; + return this; + } + + setAllows(patterns, hosts) { + this._allowPatterns = patterns; + this._allowMatcher = new MatchPatternSet(patterns || []); + this._allowHosts = hosts || []; + return this; + } + + shims(url, topHost) { + return ( + this._shimMatcher?.matches(url) && !this._shimNotHosts?.includes(topHost) + ); + } + + allows(url, topHost) { + return ( + this._allowMatcher?.matches(url) && this._allowHosts?.includes(topHost) + ); + } +} + +class Manager { + constructor() { + this._allowLists = new Map(); + } + + _getAllowList(id) { + if (!this._allowLists.has(id)) { + this._allowLists.set(id, new AllowList(id)); + } + return this._allowLists.get(id); + } + + _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; + } + // If anti-tracking webcompat is disabled, we only permit replacing + // channels, not fully unblocking them. + if (Manager.ENABLE_WEBCOMPAT) { + // if any allowlist unblocks the request entirely, we allow it + for (const allowList of this._allowLists.values()) { + if (allowList.allows(url, topHost)) { + this._unblockedChannelIds.add(channelId); + channel.allow(); + return; + } + } + } + // otherwise, if any allowlist shims the request we say it's replaced + for (const allowList of this._allowLists.values()) { + if (allowList.shims(url, topHost)) { + this._unblockedChannelIds.add(channelId); + channel.replace(); + 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) { + this._ensureStarted(); + this._getAllowList(allowListId).setAllows(patterns, hosts); + } + + shim(allowListId, patterns, notHosts) { + this._ensureStarted(); + this._getAllowList(allowListId).setShims(patterns, notHosts); + } + + 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; +} + +var dFPIPrefName = "network.cookie.cookieBehavior"; +var dFPIPbPrefName = "network.cookie.cookieBehavior.pbmode"; +var dFPIStatus; +function updateDFPIStatus() { + dFPIStatus = { + nonPbMode: 5 == Services.prefs.getIntPref(dFPIPrefName), + pbMode: 5 == Services.prefs.getIntPref(dFPIPbPrefName), + }; +} + +this.trackingProtection = class extends ExtensionAPI { + onShutdown(isAppShutdown) { + if (manager) { + manager.stop(); + } + Services.prefs.removeObserver(dFPIPrefName, updateDFPIStatus); + Services.prefs.removeObserver(dFPIPbPrefName, updateDFPIStatus); + } + + getAPI(context) { + Services.prefs.addObserver(dFPIPrefName, updateDFPIStatus); + Services.prefs.addObserver(dFPIPbPrefName, updateDFPIStatus); + updateDFPIStatus(); + + return { + trackingProtection: { + async shim(allowListId, patterns, notHosts) { + manager.shim(allowListId, patterns, notHosts); + }, + async allow(allowListId, patterns, hosts) { + manager.allow(allowListId, patterns, hosts); + }, + 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); + }, + async isDFPIActive(isPrivate) { + if (isPrivate) { + return dFPIStatus.pbMode; + } + return dFPIStatus.nonPbMode; + }, + }, + }; + } +}; + +XPCOMUtils.defineLazyPreferenceGetter( + Manager, + "ENABLE_WEBCOMPAT", + "privacy.antitracking.enableWebcompat", + false +); diff --git a/browser/extensions/webcompat/experiment-apis/trackingProtection.json b/browser/extensions/webcompat/experiment-apis/trackingProtection.json new file mode 100644 index 0000000000..c495f39add --- /dev/null +++ b/browser/extensions/webcompat/experiment-apis/trackingProtection.json @@ -0,0 +1,102 @@ +[ + { + "namespace": "trackingProtection", + "description": "experimental API allow requests through ETP", + "functions": [ + { + "name": "isDFPIActive", + "type": "function", + "description": "Returns whether dFPI is active for private/non-private browsing tabs", + "parameters": [ + { + "type": "boolean", + "name": "isPrivate" + } + ], + "async": true + }, + { + "name": "shim", + "type": "function", + "description": "Set specified URL patterns as intended to be shimmed", + "parameters": [ + { + "name": "allowlistId", + "description": "Identfier for the allow-list, so it may be added-to or revoked", + "type": "string" + }, + { + "name": "patterns", + "description": "Array of match patterns", + "type": "array", + "items": { + "type": "string" + } + }, + { + "name": "notHosts", + "description": "Hosts on which to not shim these patterns", + "type": "array", + "optional": true, + "items": { + "type": "string" + } + } + ] + }, + { + "name": "allow", + "type": "function", + "description": "Set specified URL patterns as intended to be allowed through the content blocker for the specified top hosts", + "parameters": [ + { + "name": "allowlistId", + "description": "Identfier for the allow-list, so it may be added-to or revoked", + "type": "string" + }, + { + "name": "patterns", + "description": "Array of match patterns", + "type": "array", + "items": { + "type": "string" + } + }, + { + "name": "hosts", + "description": "Hosts to allow the patterns on", + "type": "array", + "items": { + "type": "string" + } + } + ], + "async": true + }, + { + "name": "revoke", + "type": "function", + "description": "Revokes the given allow-list entirely (both shims and allows)", + "parameters": [ + { + "name": "allowListId", + "type": "string" + } + ], + "async": true + }, + { + "name": "wasRequestUnblocked", + "type": "function", + "description": "Whether the given requestId was unblocked by any allowList", + "parameters": [ + { + "name": "requestId", + "type": "string" + } + ], + "async": true + } + ] + } +] |