tor-browser

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

commit c0f866931b7fab28afe0dc607db306aaff409500
parent 1bdba2f4267b00783092d2e612614eb0fe44d4d4
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 3 - devtool support, r=devtools-reviewers,dimi,devtools-backward-compat-reviewers,bomsy

Differential Revision: https://phabricator.services.mozilla.com/D263364

Diffstat:
Mdevtools/client/locales/en-US/netmonitor.properties | 5+++++
Mdevtools/client/netmonitor/src/components/request-list/RequestListColumnTransferredSize.js | 6+++---
Mdevtools/client/netmonitor/src/components/request-list/RequestListItem.js | 2+-
Mdevtools/client/netmonitor/src/constants.js | 3++-
Mdevtools/client/netmonitor/src/utils/l10n.js | 35+++++++++++++++++++++--------------
Mdevtools/client/netmonitor/test/browser_net_block-extensions.js | 2+-
Mdevtools/client/webconsole/components/Output/message-types/NetworkEventMessage.js | 4++--
Mdevtools/client/webconsole/test/browser/browser_webconsole_stubs_network_event.js | 2+-
Mdevtools/client/webconsole/test/node/fixtures/stubs/networkEvent.js | 6++++++
Mdevtools/server/actors/network-monitor/network-event-actor.js | 14+++++++++-----
Mdevtools/server/actors/resources/network-events.js | 2+-
Mdevtools/shared/network-observer/NetworkObserver.sys.mjs | 12+++++++-----
Mdevtools/shared/network-observer/NetworkResponseListener.sys.mjs | 11+++++------
Mdevtools/shared/network-observer/NetworkUtils.sys.mjs | 21+++++++++++++++++----
Mdevtools/shared/specs/network-event.js | 2+-
Mnetwerk/base/nsILoadInfo.idl | 1+
Mnetwerk/url-classifier/UrlClassifierCommon.cpp | 2+-
Mnetwerk/url-classifier/UrlClassifierFeatureHarmfulAddonProtection.cpp | 23+++++++++++++++++++++--
18 files changed, 105 insertions(+), 48 deletions(-)

diff --git a/devtools/client/locales/en-US/netmonitor.properties b/devtools/client/locales/en-US/netmonitor.properties @@ -262,6 +262,11 @@ networkMenu.blocked2=Blocked # %S is the extension name. networkMenu.blockedby=Blocked By %S +# LOCALIZATION NOTE (networkMenu.classifiedFor): This is a generic message for a +# URL that has been blocked by the URL-Classifier because loaded by an extension +# %S is the extension name. +networkMenu.addonBlocked=Harmful Add-on Blocked (%S) + # LOCALIZATION NOTE (networkMenu.blockedTooltip): This is a the text displayed # as a tooltip for the blocked icon in the request list networkMenu.blockedTooltip=Blocked diff --git a/devtools/client/netmonitor/src/components/request-list/RequestListColumnTransferredSize.js b/devtools/client/netmonitor/src/components/request-list/RequestListColumnTransferredSize.js @@ -30,7 +30,7 @@ const UPDATED_TRANSFERRED_PROPS = [ "isRacing", "fromServiceWorker", "blockedReason", - "blockingExtension", + "extension", ]; class RequestListColumnTransferredSize extends Component { @@ -51,7 +51,7 @@ class RequestListColumnTransferredSize extends Component { render() { const { blockedReason, - blockingExtension, + extension, fromCache, fromServiceWorker, status, @@ -61,7 +61,7 @@ class RequestListColumnTransferredSize extends Component { let text; if (blockedReason) { - text = getBlockedReasonString(blockedReason, blockingExtension); + text = getBlockedReasonString(blockedReason, extension); } else if (fromCache || status === "304") { text = SIZE_CACHED; } else if (fromServiceWorker) { diff --git a/devtools/client/netmonitor/src/components/request-list/RequestListItem.js b/devtools/client/netmonitor/src/components/request-list/RequestListItem.js @@ -174,7 +174,7 @@ const UPDATED_REQ_ITEM_PROPS = [ "isEventStream", "priority", "blockedReason", - "blockingExtension", + "extension", ]; const UPDATED_REQ_PROPS = [ diff --git a/devtools/client/netmonitor/src/constants.js b/devtools/client/netmonitor/src/constants.js @@ -238,7 +238,7 @@ const UPDATE_PROPS = [ "referrerPolicy", "priority", "blockedReason", - "blockingExtension", + "extension", "channelId", "waitingTime", "proxyHttpVersion", @@ -580,6 +580,7 @@ const BLOCKED_REASON_MESSAGES = { 2007: "Cryptomining", 2008: "Fingerprinting", 2009: "Socialtracking", + 2011: "Harmful Add-on Blocked", 3001: "Mixed Block", 4000: "CSP", 4001: "CSP No Data Protocol", diff --git a/devtools/client/netmonitor/src/utils/l10n.js b/devtools/client/netmonitor/src/utils/l10n.js @@ -16,27 +16,34 @@ const NET_STRINGS_URI = "devtools/client/locales/netmonitor.properties"; exports.L10N = new LocalizationHelper(NET_STRINGS_URI); -function getBlockedReasonString(blockedReason, blockingExtension) { - if (blockedReason && blockingExtension) { +function getBlockedReasonString(blockedReason, extension) { + if (!blockedReason) { + return null; + } + + if (extension?.blocking) { return exports.L10N.getFormatStr( - "networkMenu.blockedby", - blockingExtension + "networkMenu.addonBlocked", + extension.blocking ); } - if (blockedReason) { - // If we receive a platform error code, print it as-is - if (typeof blockedReason == "string" && blockedReason.startsWith("NS_")) { - return blockedReason; - } - - return ( - BLOCKED_REASON_MESSAGES[blockedReason] || - exports.L10N.getStr("networkMenu.blocked2") + if (extension?.blocked) { + return exports.L10N.getFormatStr( + "networkMenu.blockedby", + extension.blocked ); } - return null; + // If we receive a platform error code, print it as-is + if (typeof blockedReason == "string" && blockedReason.startsWith("NS_")) { + return blockedReason; + } + + return ( + BLOCKED_REASON_MESSAGES[blockedReason] || + exports.L10N.getStr("networkMenu.blocked2") + ); } exports.getBlockedReasonString = getBlockedReasonString; diff --git a/devtools/client/netmonitor/test/browser_net_block-extensions.js b/devtools/client/netmonitor/test/browser_net_block-extensions.js @@ -87,7 +87,7 @@ add_task(async function () { is( request.querySelector(".requests-list-transferred").innerText, - `Blocked By ${extensionName}`, + `Harmful Add-on Blocked (${extensionName})`, "The request shows the blocking extension name" ); diff --git a/devtools/client/webconsole/components/Output/message-types/NetworkEventMessage.js b/devtools/client/webconsole/components/Output/message-types/NetworkEventMessage.js @@ -89,7 +89,7 @@ function NetworkEventMessage({ isXHR, timeStamp, blockedReason, - blockingExtension, + extension, httpVersion, status, statusText, @@ -137,7 +137,7 @@ function NetworkEventMessage({ if (blockedReason) { statusInfo = dom.span( { className: "status-info" }, - getBlockedReasonString(blockedReason, blockingExtension) + getBlockedReasonString(blockedReason, extension) ); topLevelClasses.push("network-message-blocked"); } diff --git a/devtools/client/webconsole/test/browser/browser_webconsole_stubs_network_event.js b/devtools/client/webconsole/test/browser/browser_webconsole_stubs_network_event.js @@ -196,7 +196,7 @@ function getOrderedResource(resource) { isThirdPartyTrackingResource: resource.isThirdPartyTrackingResource, referrerPolicy: resource.referrerPolicy, blockedReason: resource.blockedReason, - blockingExtension: resource.blockingExtension, + extension: resource.extension, channelId: resource.channelId, totalTime: resource.totalTime, securityState: resource.securityState, diff --git a/devtools/client/webconsole/test/node/fixtures/stubs/networkEvent.js b/devtools/client/webconsole/test/node/fixtures/stubs/networkEvent.js @@ -45,6 +45,7 @@ rawPackets.set(`GET request`, { "isThirdPartyTrackingResource": false, "referrerPolicy": "strict-origin-when-cross-origin", "blockedReason": 0, + "extension": {}, "totalTime": 2, "securityState": "secure", "isRacing": false @@ -77,6 +78,7 @@ rawPackets.set(`GET request update`, { "isThirdPartyTrackingResource": false, "referrerPolicy": "strict-origin-when-cross-origin", "blockedReason": 0, + "extension": {}, "totalTime": 3, "securityState": "secure", "isRacing": false @@ -116,6 +118,7 @@ rawPackets.set(`XHR GET request`, { "isThirdPartyTrackingResource": false, "referrerPolicy": "strict-origin-when-cross-origin", "blockedReason": 0, + "extension": {}, "totalTime": 1, "securityState": "insecure", "isRacing": false @@ -154,6 +157,7 @@ rawPackets.set(`XHR GET request update`, { "isThirdPartyTrackingResource": false, "referrerPolicy": "strict-origin-when-cross-origin", "blockedReason": 0, + "extension": {}, "totalTime": 1, "securityState": "insecure", "isRacing": false @@ -193,6 +197,7 @@ rawPackets.set(`XHR POST request`, { "isThirdPartyTrackingResource": false, "referrerPolicy": "strict-origin-when-cross-origin", "blockedReason": 0, + "extension": {}, "totalTime": 1, "securityState": "insecure", "isRacing": false @@ -231,6 +236,7 @@ rawPackets.set(`XHR POST request update`, { "isThirdPartyTrackingResource": false, "referrerPolicy": "strict-origin-when-cross-origin", "blockedReason": 0, + "extension": {}, "totalTime": 2, "securityState": "insecure", "isRacing": false diff --git a/devtools/server/actors/network-monitor/network-event-actor.js b/devtools/server/actors/network-monitor/network-event-actor.js @@ -63,7 +63,7 @@ function isFileChannel(channel) { * Object describing the network event or the configuration of the * network observer, and which cannot be easily inferred from the raw * channel. - * - blockingExtension: optional string + * - extension: optional object with `blocking` or `blocked` extension IDs * id of the blocking webextension if any * - blockedReason: optional number or string * - discardRequestBody: boolean @@ -223,7 +223,7 @@ class NetworkEventActor extends Actor { resourceId: this._channelId, resourceType: NETWORK_EVENT, blockedReason, - blockingExtension: networkEventOptions.blockingExtension, + extension: networkEventOptions.extension, browsingContextID, cause, // This is used specifically in the browser toolbox console to distinguish privileged @@ -658,7 +658,7 @@ class NetworkEventActor extends Actor { * * @param object */ - addResponseContentComplete({ blockedReason, blockingExtension }) { + addResponseContentComplete({ blockedReason, extension }) { // Ignore calls when this actor is already destroyed if (this.isDestroyed()) { return; @@ -668,7 +668,7 @@ class NetworkEventActor extends Actor { lazy.NetworkUtils.NETWORK_EVENT_TYPES.RESPONSE_CONTENT_COMPLETE, { blockedReason, - blockingExtension, + extension, } ); } @@ -679,7 +679,9 @@ class NetworkEventActor extends Actor { * @param object content * The response content. */ - addResponseContent(content) { + addResponseContent(content, data) { + const { blockedReason, extension } = data || {}; + // Ignore calls when this actor is already destroyed if (this.isDestroyed()) { return; @@ -692,6 +694,8 @@ class NetworkEventActor extends Actor { mimeType: content.mimeType, contentSize: content.size, transferredSize: content.transferredSize, + blockedReason, + extension, } ); } diff --git a/devtools/server/actors/resources/network-events.js b/devtools/server/actors/resources/network-events.js @@ -385,7 +385,7 @@ class NetworkEventWatcher { resourceUpdates.mimeType = updateResource.mimeType; break; case NETWORK_EVENT_TYPES.RESPONSE_CONTENT_COMPLETE: - resourceUpdates.blockingExtension = updateResource.blockingExtension; + resourceUpdates.extension = updateResource.extension; resourceUpdates.blockedReason = updateResource.blockedReason; break; case NETWORK_EVENT_TYPES.EVENT_TIMINGS: diff --git a/devtools/shared/network-observer/NetworkObserver.sys.mjs b/devtools/shared/network-observer/NetworkObserver.sys.mjs @@ -443,11 +443,13 @@ export class NetworkObserver { }); } else { // Handles any early blockings e.g by Web Extensions or by CORS - const { blockingExtension, blockedReason } = - lazy.NetworkUtils.getBlockedReason(channel, httpActivity.fromCache); + const { extension, blockedReason } = lazy.NetworkUtils.getBlockedReason( + channel, + httpActivity.fromCache + ); this.#createNetworkEvent(httpActivity, { blockedReason, - blockingExtension, + extension, }); } } @@ -929,7 +931,7 @@ export class NetworkObserver { */ #createNetworkEvent( httpActivity, - { timestamp, blockedReason, blockingExtension, inProgressRequest } = {} + { timestamp, blockedReason, extension, inProgressRequest } = {} ) { if ( blockedReason === undefined && @@ -945,7 +947,7 @@ export class NetworkObserver { { timestamp, blockedReason, - blockingExtension, + extension, discardRequestBody: !this.#saveRequestAndResponseBodies, discardResponseBody: !this.#saveRequestAndResponseBodies, }, diff --git a/devtools/shared/network-observer/NetworkResponseListener.sys.mjs b/devtools/shared/network-observer/NetworkResponseListener.sys.mjs @@ -636,15 +636,14 @@ export class NetworkResponseListener { #getResponseContentComplete() { // Check any errors or blocking scenarios which happen late in the cycle // e.g If a host is not found (NS_ERROR_UNKNOWN_HOST) or CORS blocking. - const { blockingExtension, blockedReason } = - lazy.NetworkUtils.getBlockedReason( - this.#httpActivity.channel, - this.#httpActivity.fromCache - ); + const { extension, blockedReason } = lazy.NetworkUtils.getBlockedReason( + this.#httpActivity.channel, + this.#httpActivity.fromCache + ); this.#httpActivity.owner.addResponseContentComplete({ blockedReason, - blockingExtension, + extension, discardResponseBody: this.#httpActivity.discardResponseBody, truncated: this.#truncated, }); diff --git a/devtools/shared/network-observer/NetworkUtils.sys.mjs b/devtools/shared/network-observer/NetworkUtils.sys.mjs @@ -614,7 +614,8 @@ function matchRequest(channel, filters) { } function getBlockedReason(channel, fromCache = false) { - let blockingExtension, blockedReason; + let blockedReason; + const extension = {}; const { status } = channel; try { @@ -622,15 +623,27 @@ function getBlockedReason(channel, fromCache = false) { const properties = request.QueryInterface(Ci.nsIPropertyBag); blockedReason = request.loadInfo.requestBlockingReason; - blockingExtension = properties.getProperty("cancelledByExtension"); + extension.blocking = properties.getProperty("cancelledByExtension"); // WebExtensionPolicy is not available for workers if (typeof WebExtensionPolicy !== "undefined") { - blockingExtension = WebExtensionPolicy.getByID(blockingExtension).name; + extension.blocking = WebExtensionPolicy.getByID(extension.blocking).name; } } catch (err) { // "cancelledByExtension" doesn't have to be available. } + + if ( + blockedReason === Ci.nsILoadInfo.BLOCKING_REASON_CLASSIFY_HARMFULADDON_URI + ) { + try { + const properties = channel.QueryInterface(Ci.nsIPropertyBag); + extension.blocked = properties.getProperty("blockedExtension"); + } catch (err) { + // "blockedExtension" doesn't have to be available. + } + } + // These are platform errors which are not exposed to the users, // usually the requests (with these errors) might be displayed with various // other status codes. @@ -666,7 +679,7 @@ function getBlockedReason(channel, fromCache = false) { blockedReason = ChromeUtils.getXPCOMErrorName(status); } - return { blockingExtension, blockedReason }; + return { extension, blockedReason }; } function getCharset(channel) { diff --git a/devtools/shared/specs/network-event.js b/devtools/shared/specs/network-event.js @@ -152,7 +152,7 @@ const networkEventSpec = generateActorSpec({ encoding: Option(1, "string"), transferredSize: Option(1, "number"), blockedReason: Option(1, "number"), - blockingExtension: Option(1, "string"), + extension: Option(1, "json"), }, "network-event-update:event-timings": { diff --git a/netwerk/base/nsILoadInfo.idl b/netwerk/base/nsILoadInfo.idl @@ -1538,6 +1538,7 @@ interface nsILoadInfo : nsISupports const uint32_t BLOCKING_REASON_CLASSIFY_FINGERPRINTING_URI = 2008; const uint32_t BLOCKING_REASON_CLASSIFY_SOCIALTRACKING_URI = 2009; const uint32_t BLOCKING_REASON_CLASSIFY_EMAILTRACKING_URI = 2010; + const uint32_t BLOCKING_REASON_CLASSIFY_HARMFULADDON_URI = 2011; const uint32_t BLOCKING_REASON_MIXED_BLOCKED = 3001; // The general reason comes from nsCSPContext::permitsInternal(), // which is way too generic to distinguish an exact reason. diff --git a/netwerk/url-classifier/UrlClassifierCommon.cpp b/netwerk/url-classifier/UrlClassifierCommon.cpp @@ -142,7 +142,7 @@ nsresult UrlClassifierCommon::SetBlockedContent(nsIChannel* channel, switch (aErrorCode) { case NS_ERROR_HARMFULADDON_URI: NS_SetRequestBlockingReason( - channel, nsILoadInfo::BLOCKING_REASON_CLASSIFY_BLOCKED_URI); + channel, nsILoadInfo::BLOCKING_REASON_CLASSIFY_HARMFULADDON_URI); break; case NS_ERROR_MALWARE_URI: NS_SetRequestBlockingReason( diff --git a/netwerk/url-classifier/UrlClassifierFeatureHarmfulAddonProtection.cpp b/netwerk/url-classifier/UrlClassifierFeatureHarmfulAddonProtection.cpp @@ -14,10 +14,11 @@ #include "mozilla/StaticPrefs_privacy.h" #include "nsNetUtil.h" #include "mozilla/StaticPtr.h" +#include "nsIChannel.h" #include "nsIEffectiveTLDService.h" -#include "nsIWebProgressListener.h" #include "nsIHttpChannelInternal.h" -#include "nsIChannel.h" +#include "nsIWebProgressListener.h" +#include "nsIWritablePropertyBag2.h" namespace mozilla { namespace net { @@ -90,6 +91,16 @@ bool GetAddonId(nsIChannel* aChannel, nsACString& aAddonID) { return true; } +bool GetAddonName(nsIChannel* aChannel, nsACString& aAddonName) { + extensions::WebExtensionPolicy* policy = GetAddonPolicy(aChannel); + if (!policy) { + return false; + } + + CopyUTF16toUTF8(policy->Name(), aAddonName); + return true; +} + bool GetETLD(nsIChannel* aChannel, nsACString& aETLD) { nsCOMPtr<nsIURI> uri; nsresult rv = aChannel->GetURI(getter_AddRefs(uri)); @@ -261,6 +272,14 @@ UrlClassifierFeatureHarmfulAddonProtection::ProcessChannel( RecordGleanAddonBlocked(aChannel, list); + nsAutoCString addonName; + if (GetAddonName(aChannel, addonName)) { + nsCOMPtr<nsIWritablePropertyBag2> props(do_QueryInterface(aChannel)); + if (props) { + props->SetPropertyAsACString(u"blockedExtension"_ns, addonName); + } + } + UrlClassifierCommon::SetBlockedContent(aChannel, NS_ERROR_HARMFULADDON_URI, list, ""_ns, ""_ns);