commit 2a13eb6ec3e803078f99cf8646a8df6ee0305704
parent 1ef88130d9c3dc97cfee299c3069966e7d37f776
Author: Andrea Marchesini <amarchesini@mozilla.com>
Date: Wed, 3 Dec 2025 09:45:35 +0000
Bug 1986320 - Implement an add-on block URL-Classifier feature - part 2 - telemetry, r=dimi,extension-reviewers,robwu
Differential Revision: https://phabricator.services.mozilla.com/D263292
Diffstat:
3 files changed, 142 insertions(+), 3 deletions(-)
diff --git a/netwerk/metrics.yaml b/netwerk/metrics.yaml
@@ -3105,3 +3105,27 @@ network:
- kershaw@mozilla.com
expires: never
telemetry_mirror: h#REL_PRELOAD_MISS_RATIO
+
+ urlclassifier_addon_block:
+ type: event
+ description: >
+ A classified network request initiated by a WebExtension was blocked by the
+ addon-protection feature.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1986320
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1986320
+ notification_emails:
+ - amarchesini@mozilla.com
+ - addons-dev-internal@mozilla.com
+ expires: never
+ extra_keys:
+ addon_id:
+ description: The add-on ID
+ type: string
+ etld:
+ description: The eTLD+1 of the request URL
+ type: string
+ table:
+ description: Classifier table name(s) that matched
+ type: string
diff --git a/netwerk/url-classifier/UrlClassifierFeatureHarmfulAddonProtection.cpp b/netwerk/url-classifier/UrlClassifierFeatureHarmfulAddonProtection.cpp
@@ -7,11 +7,14 @@
#include "UrlClassifierFeatureHarmfulAddonProtection.h"
#include "mozilla/AntiTrackingUtils.h"
+#include "mozilla/extensions/WebExtensionPolicy.h"
+#include "mozilla/glean/NetwerkMetrics.h"
#include "mozilla/net/UrlClassifierCommon.h"
#include "ChannelClassifierService.h"
#include "mozilla/StaticPrefs_privacy.h"
#include "nsNetUtil.h"
#include "mozilla/StaticPtr.h"
+#include "nsIEffectiveTLDService.h"
#include "nsIWebProgressListener.h"
#include "nsIHttpChannelInternal.h"
#include "nsIChannel.h"
@@ -39,6 +42,93 @@ namespace {
StaticRefPtr<UrlClassifierFeatureHarmfulAddonProtection>
gFeatureHarmfulAddonProtection;
+extensions::WebExtensionPolicy* GetAddonPolicy(nsIChannel* aChannel) {
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal;
+ if (NS_FAILED(loadInfo->GetTriggeringPrincipal(
+ getter_AddRefs(triggeringPrincipal)))) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIPrincipal> loadingPrincipal;
+ if (NS_FAILED(
+ loadInfo->GetLoadingPrincipal(getter_AddRefs(loadingPrincipal)))) {
+ return nullptr;
+ }
+
+ extensions::WebExtensionPolicy* policy = nullptr;
+
+ if (triggeringPrincipal) {
+ policy = BasePrincipal::Cast(triggeringPrincipal)->AddonPolicy();
+
+ if (!policy) {
+ policy =
+ BasePrincipal::Cast(triggeringPrincipal)->ContentScriptAddonPolicy();
+ }
+ }
+
+ if (!policy && loadingPrincipal) {
+ policy = BasePrincipal::Cast(loadingPrincipal)->AddonPolicy();
+
+ if (!policy) {
+ policy =
+ BasePrincipal::Cast(loadingPrincipal)->ContentScriptAddonPolicy();
+ }
+ }
+
+ return policy;
+}
+
+bool GetAddonId(nsIChannel* aChannel, nsACString& aAddonID) {
+ extensions::WebExtensionPolicy* policy = GetAddonPolicy(aChannel);
+ if (!policy) {
+ return false;
+ }
+
+ CopyUTF16toUTF8(nsDependentAtomString(policy->Id()), aAddonID);
+ return true;
+}
+
+bool GetETLD(nsIChannel* aChannel, nsACString& aETLD) {
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = aChannel->GetURI(getter_AddRefs(uri));
+ if (NS_WARN_IF(NS_FAILED(rv)) || !uri) {
+ return false;
+ }
+
+ nsCOMPtr<nsIEffectiveTLDService> etld(
+ do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID));
+ if (NS_WARN_IF(!etld)) {
+ return false;
+ }
+
+ rv = etld->GetBaseDomain(uri, 0, aETLD);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ return !aETLD.IsEmpty();
+}
+
+void RecordGleanAddonBlocked(nsIChannel* aChannel,
+ const nsACString& aTableStr) {
+ nsAutoCString etld;
+ if (!GetETLD(aChannel, etld)) {
+ return;
+ }
+
+ nsAutoCString addonId;
+ if (!GetAddonId(aChannel, addonId)) {
+ return;
+ }
+
+ glean::network::urlclassifier_addon_block.Record(
+ Some(glean::network::UrlclassifierAddonBlockExtra{
+ mozilla::Some(addonId), mozilla::Some(etld),
+ mozilla::Some(nsCString(aTableStr))}));
+}
+
} // namespace
UrlClassifierFeatureHarmfulAddonProtection::
@@ -169,6 +259,8 @@ UrlClassifierFeatureHarmfulAddonProtection::ProcessChannel(
return NS_OK;
}
+ RecordGleanAddonBlocked(aChannel, list);
+
UrlClassifierCommon::SetBlockedContent(aChannel, NS_ERROR_HARMFULADDON_URI,
list, ""_ns, ""_ns);
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_urlclassifier.js b/toolkit/components/extensions/test/xpcshell/test_ext_urlclassifier.js
@@ -5,6 +5,9 @@
"use strict";
add_setup(async () => {
+ do_get_profile();
+ Services.fog.initializeFOG();
+
Services.prefs.setCharPref(
"urlclassifier.features.harmfuladdon.blocklistHosts",
"example.org"
@@ -56,18 +59,20 @@ server.registerPathHandler("/backgroundScript", (request, response) => {
add_task(
{ pref_set: [["privacy.trackingprotection.harmfuladdon.enabled", true]] },
async function test_addon_blocked_by_url_classifier() {
- await runTest("backgroundScript_failed", "contentScript_failed");
+ await runTest("backgroundScript_failed", "contentScript_failed", true);
}
);
add_task(
{ pref_set: [["privacy.trackingprotection.harmfuladdon.enabled", false]] },
async function test_addon_blocked_by_url_classifier() {
- await runTest("backgroundScript_loaded", "contentScript_loaded");
+ await runTest("backgroundScript_loaded", "contentScript_loaded", false);
}
);
-async function runTest(message1, message2) {
+async function runTest(message1, message2, expectGleanEvent) {
+ Services.fog.testResetFOG();
+
const extension = ExtensionTestUtils.loadExtension({
manifest: {
host_permissions: ["http://example.org/"],
@@ -111,6 +116,24 @@ async function runTest(message1, message2) {
await extension.awaitMessage(message1);
await extension.awaitMessage(message2);
+ if (expectGleanEvent) {
+ const events = Glean.network.urlclassifierAddonBlock.testGetValue();
+ Assert.greater(events.length, 1, "We have received glean events");
+
+ let glean = events[0];
+ Assert.greater(glean.extra.addon_id.length, 0);
+ Assert.equal(glean.extra.table, "harmfuladdon-blocklist-pref");
+ Assert.equal(glean.extra.etld, "example.org");
+
+ glean = events[1];
+ Assert.greater(glean.extra.addon_id.length, 0);
+ Assert.equal(glean.extra.table, "harmfuladdon-blocklist-pref");
+ Assert.equal(glean.extra.etld, "example.org");
+ } else {
+ const events = Glean.network.urlclassifierAddonBlock.testGetValue();
+ Assert.equal(events, undefined, "We haven't received glean events");
+ }
+
await contentPage.close();
await extension.unload();
}