1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "AntiTrackingLog.h"
8 #include "ContentBlockingNotifier.h"
9 #include "AntiTrackingUtils.h"
11 #include "mozilla/EventQueue.h"
12 #include "mozilla/StaticPrefs_privacy.h"
13 #include "mozilla/dom/BrowserChild.h"
14 #include "mozilla/dom/BrowsingContext.h"
15 #include "mozilla/dom/Document.h"
16 #include "mozilla/dom/ContentParent.h"
17 #include "mozilla/dom/WindowGlobalParent.h"
18 #include "nsIClassifiedChannel.h"
19 #include "nsIRunnable.h"
20 #include "nsIScriptError.h"
22 #include "nsIOService.h"
23 #include "nsGlobalWindowOuter.h"
24 #include "nsJSUtils.h"
25 #include "mozIThirdPartyUtil.h"
27 using namespace mozilla
;
28 using namespace mozilla::dom
;
29 using mozilla::dom::BrowsingContext
;
30 using mozilla::dom::ContentChild
;
31 using mozilla::dom::Document
;
33 static const uint32_t kMaxConsoleOutputDelayMs
= 100;
37 void RunConsoleReportingRunnable(already_AddRefed
<nsIRunnable
>&& aRunnable
) {
38 if (StaticPrefs::privacy_restrict3rdpartystorage_console_lazy()) {
39 nsresult rv
= NS_DispatchToCurrentThreadQueue(std::move(aRunnable
),
40 kMaxConsoleOutputDelayMs
,
41 EventQueuePriority::Idle
);
42 if (NS_WARN_IF(NS_FAILED(rv
))) {
46 nsCOMPtr
<nsIRunnable
> runnable(std::move(aRunnable
));
47 nsresult rv
= runnable
->Run();
48 if (NS_WARN_IF(NS_FAILED(rv
))) {
54 void ReportUnblockingToConsole(
55 uint64_t aWindowID
, nsIPrincipal
* aPrincipal
,
56 const nsAString
& aTrackingOrigin
,
57 ContentBlockingNotifier::StorageAccessPermissionGrantedReason aReason
) {
58 MOZ_ASSERT(aWindowID
);
59 MOZ_ASSERT(aPrincipal
);
61 nsAutoString sourceLine
;
62 uint32_t lineNumber
= 0, columnNumber
= 1;
63 JSContext
* cx
= nsContentUtils::GetCurrentJSContext();
65 nsJSUtils::GetCallingLocation(cx
, sourceLine
, &lineNumber
, &columnNumber
);
68 nsCOMPtr
<nsIPrincipal
> principal(aPrincipal
);
69 nsAutoString
trackingOrigin(aTrackingOrigin
);
71 RefPtr
<Runnable
> runnable
= NS_NewRunnableFunction(
72 "ReportUnblockingToConsoleDelayed",
73 [aWindowID
, sourceLine
, lineNumber
, columnNumber
, principal
,
74 trackingOrigin
, aReason
]() {
75 const char* messageWithSameOrigin
= nullptr;
78 case ContentBlockingNotifier::eStorageAccessAPI
:
79 case ContentBlockingNotifier::ePrivilegeStorageAccessForOriginAPI
:
80 messageWithSameOrigin
= "CookieAllowedForOriginByStorageAccessAPI";
83 case ContentBlockingNotifier::eOpenerAfterUserInteraction
:
85 case ContentBlockingNotifier::eOpener
:
86 messageWithSameOrigin
= "CookieAllowedForOriginByHeuristic";
91 nsresult rv
= principal
->GetOriginNoSuffix(origin
);
92 if (NS_WARN_IF(NS_FAILED(rv
))) {
96 // Not adding grantedOrigin yet because we may not want it later.
97 AutoTArray
<nsString
, 2> params
= {NS_ConvertUTF8toUTF16(origin
),
100 nsAutoString errorText
;
101 rv
= nsContentUtils::FormatLocalizedString(
102 nsContentUtils::eNECKO_PROPERTIES
, messageWithSameOrigin
, params
,
104 NS_ENSURE_SUCCESS_VOID(rv
);
106 nsContentUtils::ReportToConsoleByWindowID(
107 errorText
, nsIScriptError::warningFlag
,
108 ANTITRACKING_CONSOLE_CATEGORY
, aWindowID
, nullptr, sourceLine
,
109 lineNumber
, columnNumber
);
112 RunConsoleReportingRunnable(runnable
.forget());
115 void ReportBlockingToConsole(uint64_t aWindowID
, nsIURI
* aURI
,
116 uint32_t aRejectedReason
) {
117 MOZ_ASSERT(aWindowID
);
120 aRejectedReason
== 0 ||
122 static_cast<uint32_t>(
123 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION
) ||
125 static_cast<uint32_t>(
126 nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER
) ||
128 static_cast<uint32_t>(
129 nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER
) ||
131 static_cast<uint32_t>(
132 nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN
) ||
134 static_cast<uint32_t>(
135 nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL
) ||
137 static_cast<uint32_t>(
138 nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN
));
140 if (aURI
->SchemeIs("chrome") || aURI
->SchemeIs("about")) {
144 nsresult rv
= NS_URIChainHasFlags(
145 aURI
, nsIProtocolHandler::URI_FORBIDS_COOKIE_ACCESS
, &hasFlags
);
146 if (NS_FAILED(rv
) || hasFlags
) {
147 // If the protocol doesn't support cookies, no need to report them blocked.
151 nsAutoString sourceLine
;
152 uint32_t lineNumber
= 0, columnNumber
= 1;
153 JSContext
* cx
= nsContentUtils::GetCurrentJSContext();
155 nsJSUtils::GetCallingLocation(cx
, sourceLine
, &lineNumber
, &columnNumber
);
158 nsCOMPtr
<nsIURI
> uri(aURI
);
160 RefPtr
<Runnable
> runnable
= NS_NewRunnableFunction(
161 "ReportBlockingToConsoleDelayed", [aWindowID
, sourceLine
, lineNumber
,
162 columnNumber
, uri
, aRejectedReason
]() {
163 const char* message
= nullptr;
164 nsAutoCString category
;
165 // When changing this list, please make sure to update the corresponding
166 // code in antitracking_head.js (inside _createTask).
167 // XXX: The nsIWebProgressListener constants below are interpreted as
168 // signed integers on Windows and the compiler complains that they can't
169 // be narrowed to uint32_t. To prevent this, we cast them to uint32_t.
170 switch (aRejectedReason
) {
172 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION
):
173 message
= "CookieBlockedByPermission";
174 category
= "cookieBlockedPermission"_ns
;
177 case uint32_t(nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER
):
178 message
= "CookieBlockedTracker";
179 category
= "cookieBlockedTracker"_ns
;
182 case uint32_t(nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL
):
183 message
= "CookieBlockedAll";
184 category
= "cookieBlockedAll"_ns
;
187 case uint32_t(nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN
):
188 message
= "CookieBlockedForeign";
189 category
= "cookieBlockedForeign"_ns
;
193 nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN
):
194 message
= "CookiePartitionedForeign2";
195 category
= "cookiePartitionedForeign"_ns
;
204 // Strip the URL of any possible username/password and make it ready
205 // to be presented in the UI.
206 nsCOMPtr
<nsIURI
> exposableURI
=
207 net::nsIOService::CreateExposableURI(uri
);
208 AutoTArray
<nsString
, 1> params
;
209 CopyUTF8toUTF16(exposableURI
->GetSpecOrDefault(),
210 *params
.AppendElement());
212 nsAutoString errorText
;
213 nsresult rv
= nsContentUtils::FormatLocalizedString(
214 nsContentUtils::eNECKO_PROPERTIES
, message
, params
, errorText
);
215 NS_ENSURE_SUCCESS_VOID(rv
);
217 nsContentUtils::ReportToConsoleByWindowID(
218 errorText
, nsIScriptError::warningFlag
, category
, aWindowID
,
219 nullptr, sourceLine
, lineNumber
, columnNumber
);
222 RunConsoleReportingRunnable(runnable
.forget());
225 void ReportBlockingToConsole(nsIChannel
* aChannel
, nsIURI
* aURI
,
226 uint32_t aRejectedReason
) {
227 MOZ_ASSERT(aChannel
&& aURI
);
228 uint64_t windowID
= nsContentUtils::GetInnerWindowID(aChannel
);
230 // Get the window ID from the target BrowsingContext
231 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
233 RefPtr
<dom::BrowsingContext
> targetBrowsingContext
;
234 loadInfo
->GetTargetBrowsingContext(getter_AddRefs(targetBrowsingContext
));
236 if (!targetBrowsingContext
) {
240 WindowContext
* windowContext
=
241 targetBrowsingContext
->GetCurrentWindowContext();
242 if (!windowContext
) {
246 windowID
= windowContext
->InnerWindowId();
248 ReportBlockingToConsole(windowID
, aURI
, aRejectedReason
);
251 void NotifyBlockingDecision(nsIChannel
* aTrackingChannel
,
252 ContentBlockingNotifier::BlockingDecision aDecision
,
253 uint32_t aRejectedReason
, nsIURI
* aURI
) {
254 MOZ_ASSERT(aTrackingChannel
);
256 // This can be called in either the parent process or the child processes.
257 // When this is called in the child processes, we must have a window.
258 if (XRE_IsContentProcess()) {
259 nsCOMPtr
<nsILoadContext
> loadContext
;
260 NS_QueryNotificationCallbacks(aTrackingChannel
, loadContext
);
265 nsCOMPtr
<mozIDOMWindowProxy
> window
;
266 loadContext
->GetAssociatedWindow(getter_AddRefs(window
));
271 nsCOMPtr
<nsPIDOMWindowOuter
> outer
= nsPIDOMWindowOuter::From(window
);
276 // When this is called in the child processes with system privileges,
277 // the decision should always be ALLOW. We can stop here because both
278 // UI and content blocking log don't care this event.
279 if (nsGlobalWindowOuter::Cast(outer
)->GetPrincipal() ==
280 nsContentUtils::GetSystemPrincipal()) {
281 MOZ_DIAGNOSTIC_ASSERT(aDecision
==
282 ContentBlockingNotifier::BlockingDecision::eAllow
);
287 nsAutoCString trackingOrigin
;
289 // Using an empty OriginAttributes is OK here, as we'll only be accessing
291 nsCOMPtr
<nsIPrincipal
> principal
=
292 BasePrincipal::CreateContentPrincipal(aURI
, OriginAttributes
{});
293 principal
->GetOriginNoSuffix(trackingOrigin
);
296 if (aDecision
== ContentBlockingNotifier::BlockingDecision::eBlock
) {
297 ContentBlockingNotifier::OnEvent(aTrackingChannel
, true, aRejectedReason
,
300 ReportBlockingToConsole(aTrackingChannel
, aURI
, aRejectedReason
);
303 // Now send the generic "cookies loaded" notifications, from the most generic
304 // to the most specific.
305 ContentBlockingNotifier::OnEvent(aTrackingChannel
, false,
306 nsIWebProgressListener::STATE_COOKIES_LOADED
,
309 nsCOMPtr
<nsIClassifiedChannel
> classifiedChannel
=
310 do_QueryInterface(aTrackingChannel
);
311 if (!classifiedChannel
) {
315 uint32_t classificationFlags
=
316 classifiedChannel
->GetThirdPartyClassificationFlags();
317 if (classificationFlags
&
318 nsIClassifiedChannel::ClassificationFlags::CLASSIFIED_TRACKING
) {
319 ContentBlockingNotifier::OnEvent(
320 aTrackingChannel
, false,
321 nsIWebProgressListener::STATE_COOKIES_LOADED_TRACKER
, trackingOrigin
);
324 if (classificationFlags
&
325 nsIClassifiedChannel::ClassificationFlags::CLASSIFIED_SOCIALTRACKING
) {
326 ContentBlockingNotifier::OnEvent(
327 aTrackingChannel
, false,
328 nsIWebProgressListener::STATE_COOKIES_LOADED_SOCIALTRACKER
,
333 // Send a message to notify OnContentBlockingEvent in the parent, which will
334 // update the ContentBlockingLog in the parent.
335 void NotifyEventInChild(
336 nsIChannel
* aTrackingChannel
, bool aBlocked
, uint32_t aRejectedReason
,
337 const nsACString
& aTrackingOrigin
,
338 const Maybe
<ContentBlockingNotifier::StorageAccessPermissionGrantedReason
>&
340 const Maybe
<ContentBlockingNotifier::CanvasFingerprinter
>
341 aCanvasFingerprinter
,
342 const Maybe
<bool> aCanvasFingerprinterKnownText
) {
343 MOZ_ASSERT(XRE_IsContentProcess());
345 // We don't need to find the top-level window here because the
346 // parent will do that for us.
347 nsCOMPtr
<nsILoadContext
> loadContext
;
348 NS_QueryNotificationCallbacks(aTrackingChannel
, loadContext
);
353 nsCOMPtr
<mozIDOMWindowProxy
> window
;
354 loadContext
->GetAssociatedWindow(getter_AddRefs(window
));
359 RefPtr
<dom::BrowserChild
> browserChild
= dom::BrowserChild::GetFrom(window
);
360 NS_ENSURE_TRUE_VOID(browserChild
);
362 nsTArray
<nsCString
> trackingFullHashes
;
363 nsCOMPtr
<nsIClassifiedChannel
> classifiedChannel
=
364 do_QueryInterface(aTrackingChannel
);
366 if (classifiedChannel
) {
367 Unused
<< classifiedChannel
->GetMatchedTrackingFullHashes(
371 browserChild
->NotifyContentBlockingEvent(
372 aRejectedReason
, aTrackingChannel
, aBlocked
, aTrackingOrigin
,
373 trackingFullHashes
, aReason
, aCanvasFingerprinter
,
374 aCanvasFingerprinterKnownText
);
377 // Update the ContentBlockingLog of the top-level WindowGlobalParent of
378 // the tracking channel.
379 void NotifyEventInParent(
380 nsIChannel
* aTrackingChannel
, bool aBlocked
, uint32_t aRejectedReason
,
381 const nsACString
& aTrackingOrigin
,
382 const Maybe
<ContentBlockingNotifier::StorageAccessPermissionGrantedReason
>&
384 const Maybe
<ContentBlockingNotifier::CanvasFingerprinter
>
385 aCanvasFingerprinter
,
386 const Maybe
<bool> aCanvasFingerprinterKnownText
) {
387 MOZ_ASSERT(XRE_IsParentProcess());
389 nsCOMPtr
<nsILoadInfo
> loadInfo
= aTrackingChannel
->LoadInfo();
390 RefPtr
<dom::BrowsingContext
> bc
;
391 loadInfo
->GetBrowsingContext(getter_AddRefs(bc
));
393 if (!bc
|| bc
->IsDiscarded()) {
398 RefPtr
<dom::WindowGlobalParent
> wgp
=
399 bc
->Canonical()->GetCurrentWindowGlobal();
400 NS_ENSURE_TRUE_VOID(wgp
);
402 nsTArray
<nsCString
> trackingFullHashes
;
403 nsCOMPtr
<nsIClassifiedChannel
> classifiedChannel
=
404 do_QueryInterface(aTrackingChannel
);
406 if (classifiedChannel
) {
407 Unused
<< classifiedChannel
->GetMatchedTrackingFullHashes(
411 wgp
->NotifyContentBlockingEvent(aRejectedReason
, aTrackingChannel
, aBlocked
,
412 aTrackingOrigin
, trackingFullHashes
, aReason
,
413 aCanvasFingerprinter
,
414 aCanvasFingerprinterKnownText
);
420 void ContentBlockingNotifier::ReportUnblockingToConsole(
421 BrowsingContext
* aBrowsingContext
, const nsAString
& aTrackingOrigin
,
422 ContentBlockingNotifier::StorageAccessPermissionGrantedReason aReason
) {
423 MOZ_ASSERT(aBrowsingContext
);
424 MOZ_ASSERT_IF(XRE_IsContentProcess(), aBrowsingContext
->Top()->IsInProcess());
426 uint64_t windowID
= aBrowsingContext
->GetCurrentInnerWindowId();
428 // The storage permission is granted under the top-level origin.
429 nsCOMPtr
<nsIPrincipal
> principal
=
430 AntiTrackingUtils::GetPrincipal(aBrowsingContext
->Top());
431 if (NS_WARN_IF(!principal
)) {
435 ::ReportUnblockingToConsole(windowID
, principal
, aTrackingOrigin
, aReason
);
439 void ContentBlockingNotifier::OnDecision(nsIChannel
* aChannel
,
440 BlockingDecision aDecision
,
441 uint32_t aRejectedReason
) {
443 aRejectedReason
== 0 ||
445 static_cast<uint32_t>(
446 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION
) ||
448 static_cast<uint32_t>(
449 nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER
) ||
451 static_cast<uint32_t>(
452 nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER
) ||
454 static_cast<uint32_t>(
455 nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN
) ||
457 static_cast<uint32_t>(
458 nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL
) ||
460 static_cast<uint32_t>(
461 nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN
));
462 MOZ_ASSERT(aDecision
== BlockingDecision::eBlock
||
463 aDecision
== BlockingDecision::eAllow
);
469 nsCOMPtr
<nsIURI
> uri
;
470 aChannel
->GetURI(getter_AddRefs(uri
));
472 // Can be called in EITHER the parent or child process.
473 NotifyBlockingDecision(aChannel
, aDecision
, aRejectedReason
, uri
);
477 void ContentBlockingNotifier::OnDecision(nsPIDOMWindowInner
* aWindow
,
478 BlockingDecision aDecision
,
479 uint32_t aRejectedReason
) {
482 aRejectedReason
== 0 ||
484 static_cast<uint32_t>(
485 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION
) ||
487 static_cast<uint32_t>(
488 nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER
) ||
490 static_cast<uint32_t>(
491 nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER
) ||
493 static_cast<uint32_t>(
494 nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN
) ||
496 static_cast<uint32_t>(
497 nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL
) ||
499 static_cast<uint32_t>(
500 nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN
));
501 MOZ_ASSERT(aDecision
== BlockingDecision::eBlock
||
502 aDecision
== BlockingDecision::eAllow
);
504 Document
* document
= aWindow
->GetExtantDoc();
509 nsIChannel
* channel
= document
->GetChannel();
514 nsIURI
* uri
= document
->GetDocumentURI();
516 NotifyBlockingDecision(channel
, aDecision
, aRejectedReason
, uri
);
520 void ContentBlockingNotifier::OnDecision(BrowsingContext
* aBrowsingContext
,
521 BlockingDecision aDecision
,
522 uint32_t aRejectedReason
) {
523 MOZ_ASSERT(aBrowsingContext
);
524 MOZ_ASSERT_IF(XRE_IsContentProcess(), aBrowsingContext
->IsInProcess());
526 if (aBrowsingContext
->IsInProcess()) {
527 nsCOMPtr
<nsPIDOMWindowOuter
> outer
= aBrowsingContext
->GetDOMWindow();
528 if (NS_WARN_IF(!outer
)) {
532 nsCOMPtr
<nsPIDOMWindowInner
> inner
= outer
->GetCurrentInnerWindow();
533 if (NS_WARN_IF(!inner
)) {
537 ContentBlockingNotifier::OnDecision(inner
, aDecision
, aRejectedReason
);
539 // we send an IPC to the content process when we don't have an in-process
540 // browsing context. This is not smart because this should be able to be
541 // done directly in the parent. The reason we are doing this is because we
542 // need the channel, which is not accessible in the parent when you only
543 // have a browsing context.
544 MOZ_ASSERT(XRE_IsParentProcess());
546 ContentParent
* cp
= aBrowsingContext
->Canonical()->GetContentParent();
547 Unused
<< cp
->SendOnContentBlockingDecision(aBrowsingContext
, aDecision
,
553 void ContentBlockingNotifier::OnEvent(nsIChannel
* aTrackingChannel
,
554 uint32_t aRejectedReason
, bool aBlocked
) {
555 MOZ_ASSERT(XRE_IsParentProcess() && aTrackingChannel
);
557 nsCOMPtr
<nsIURI
> uri
;
558 aTrackingChannel
->GetURI(getter_AddRefs(uri
));
560 nsAutoCString trackingOrigin
;
562 // Using empty OriginAttributes is OK here, as we only want to access
564 nsCOMPtr
<nsIPrincipal
> trackingPrincipal
=
565 BasePrincipal::CreateContentPrincipal(uri
, OriginAttributes
{});
566 trackingPrincipal
->GetOriginNoSuffix(trackingOrigin
);
569 return ContentBlockingNotifier::OnEvent(aTrackingChannel
, aBlocked
,
570 aRejectedReason
, trackingOrigin
);
574 void ContentBlockingNotifier::OnEvent(
575 nsIChannel
* aTrackingChannel
, bool aBlocked
, uint32_t aRejectedReason
,
576 const nsACString
& aTrackingOrigin
,
577 const Maybe
<StorageAccessPermissionGrantedReason
>& aReason
,
578 const Maybe
<CanvasFingerprinter
>& aCanvasFingerprinter
,
579 const Maybe
<bool> aCanvasFingerprinterKnownText
) {
580 if (XRE_IsParentProcess()) {
581 NotifyEventInParent(aTrackingChannel
, aBlocked
, aRejectedReason
,
582 aTrackingOrigin
, aReason
, aCanvasFingerprinter
,
583 aCanvasFingerprinterKnownText
);
585 NotifyEventInChild(aTrackingChannel
, aBlocked
, aRejectedReason
,
586 aTrackingOrigin
, aReason
, aCanvasFingerprinter
,
587 aCanvasFingerprinterKnownText
);