tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 1bdba2f4267b00783092d2e612614eb0fe44d4d4
parent f1a3613f4487c3fd4dc5b1e2add94c5a560af8eb
Author: Andrea Marchesini <amarchesini@mozilla.com>
Date:   Wed,  3 Dec 2025 15:45:12 +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:
Mnetwerk/metrics.yaml | 24++++++++++++++++++++++++
Mnetwerk/url-classifier/UrlClassifierFeatureHarmfulAddonProtection.cpp | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtoolkit/components/extensions/test/xpcshell/test_ext_urlclassifier.js | 29++++++++++++++++++++++++++---
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(); }