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/SourceLocation.h"
14 #include "mozilla/dom/BrowserChild.h"
15 #include "mozilla/dom/BrowsingContext.h"
16 #include "mozilla/dom/Document.h"
17 #include "mozilla/dom/ContentParent.h"
18 #include "mozilla/dom/WindowGlobalParent.h"
19 #include "nsIClassifiedChannel.h"
20 #include "nsIRunnable.h"
21 #include "nsIScriptError.h"
23 #include "nsIOService.h"
24 #include "nsGlobalWindowOuter.h"
25 #include "mozIThirdPartyUtil.h"
27 using namespace mozilla
;
28 using namespace mozilla::dom
;
29 using mozilla::dom::BrowsingContext
;
30 using mozilla::dom::Document
;
32 static const uint32_t kMaxConsoleOutputDelayMs
= 100;
36 void RunConsoleReportingRunnable(already_AddRefed
<nsIRunnable
>&& aRunnable
) {
37 if (StaticPrefs::privacy_restrict3rdpartystorage_console_lazy()) {
38 nsresult rv
= NS_DispatchToCurrentThreadQueue(std::move(aRunnable
),
39 kMaxConsoleOutputDelayMs
,
40 EventQueuePriority::Idle
);
41 if (NS_WARN_IF(NS_FAILED(rv
))) {
45 nsCOMPtr
<nsIRunnable
> runnable(std::move(aRunnable
));
46 nsresult rv
= runnable
->Run();
47 if (NS_WARN_IF(NS_FAILED(rv
))) {
53 void ReportUnblockingToConsole(
54 uint64_t aWindowID
, nsIPrincipal
* aPrincipal
,
55 const nsAString
& aTrackingOrigin
,
56 ContentBlockingNotifier::StorageAccessPermissionGrantedReason aReason
) {
57 MOZ_ASSERT(aWindowID
);
58 MOZ_ASSERT(aPrincipal
);
60 // Grab the calling location now since the runnable will run without a JS
61 // context on the stack.
62 auto location
= JSCallingLocation::Get();
63 nsCOMPtr
<nsIPrincipal
> principal(aPrincipal
);
64 nsAutoString
trackingOrigin(aTrackingOrigin
);
66 RefPtr
<Runnable
> runnable
= NS_NewRunnableFunction(
67 "ReportUnblockingToConsoleDelayed",
68 [aWindowID
, loc
= std::move(location
), principal
, trackingOrigin
,
70 const char* messageWithSameOrigin
= nullptr;
73 case ContentBlockingNotifier::eStorageAccessAPI
:
74 case ContentBlockingNotifier::ePrivilegeStorageAccessForOriginAPI
:
75 messageWithSameOrigin
= "CookieAllowedForOriginByStorageAccessAPI";
78 case ContentBlockingNotifier::eOpenerAfterUserInteraction
:
80 case ContentBlockingNotifier::eOpener
:
81 messageWithSameOrigin
= "CookieAllowedForOriginByHeuristic";
86 nsresult rv
= principal
->GetOriginNoSuffix(origin
);
87 if (NS_WARN_IF(NS_FAILED(rv
))) {
91 // Not adding grantedOrigin yet because we may not want it later.
92 AutoTArray
<nsString
, 2> params
= {NS_ConvertUTF8toUTF16(origin
),
95 nsAutoString errorText
;
96 rv
= nsContentUtils::FormatLocalizedString(
97 nsContentUtils::eNECKO_PROPERTIES
, messageWithSameOrigin
, params
,
99 NS_ENSURE_SUCCESS_VOID(rv
);
101 nsContentUtils::ReportToConsoleByWindowID(
102 errorText
, nsIScriptError::warningFlag
,
103 ANTITRACKING_CONSOLE_CATEGORY
, aWindowID
, loc
);
106 RunConsoleReportingRunnable(runnable
.forget());
109 void ReportBlockingToConsole(uint64_t aWindowID
, nsIURI
* aURI
,
110 uint32_t aRejectedReason
) {
111 MOZ_ASSERT(aWindowID
);
114 aRejectedReason
== 0 ||
116 static_cast<uint32_t>(
117 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION
) ||
119 static_cast<uint32_t>(
120 nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER
) ||
122 static_cast<uint32_t>(
123 nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER
) ||
125 static_cast<uint32_t>(
126 nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN
) ||
128 static_cast<uint32_t>(
129 nsIWebProgressListener::STATE_COOKIES_PARTITIONED_TRACKER
) ||
131 static_cast<uint32_t>(
132 nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL
) ||
134 static_cast<uint32_t>(
135 nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN
));
137 if (aURI
->SchemeIs("chrome") || aURI
->SchemeIs("about")) {
141 nsresult rv
= NS_URIChainHasFlags(
142 aURI
, nsIProtocolHandler::URI_FORBIDS_COOKIE_ACCESS
, &hasFlags
);
143 if (NS_FAILED(rv
) || hasFlags
) {
144 // If the protocol doesn't support cookies, no need to report them blocked.
148 auto location
= JSCallingLocation::Get();
150 nsCOMPtr
<nsIURI
> uri(aURI
);
152 RefPtr
<Runnable
> runnable
= NS_NewRunnableFunction(
153 "ReportBlockingToConsoleDelayed",
154 [aWindowID
, loc
= std::move(location
), uri
, aRejectedReason
]() {
155 const char* message
= nullptr;
156 nsAutoCString category
;
157 // When changing this list, please make sure to update the corresponding
158 // code in antitracking_head.js (inside _createTask).
159 // XXX: The nsIWebProgressListener constants below are interpreted as
160 // signed integers on Windows and the compiler complains that they can't
161 // be narrowed to uint32_t. To prevent this, we cast them to uint32_t.
162 switch (aRejectedReason
) {
164 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION
):
165 message
= "CookieBlockedByPermission";
166 category
= "cookieBlockedPermission"_ns
;
169 case uint32_t(nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER
):
170 message
= "CookieBlockedTracker";
171 category
= "cookieBlockedTracker"_ns
;
174 case uint32_t(nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL
):
175 message
= "CookieBlockedAll";
176 category
= "cookieBlockedAll"_ns
;
179 case uint32_t(nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN
):
180 message
= "CookieBlockedForeign";
181 category
= "cookieBlockedForeign"_ns
;
185 nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN
):
187 nsIWebProgressListener::STATE_COOKIES_PARTITIONED_TRACKER
):
188 message
= "CookiePartitionedForeign2";
189 category
= "cookiePartitionedForeign"_ns
;
198 // Strip the URL of any possible username/password and make it ready
199 // to be presented in the UI.
200 nsCOMPtr
<nsIURI
> exposableURI
=
201 net::nsIOService::CreateExposableURI(uri
);
202 AutoTArray
<nsString
, 1> params
;
203 CopyUTF8toUTF16(exposableURI
->GetSpecOrDefault(),
204 *params
.AppendElement());
206 nsAutoString errorText
;
207 nsresult rv
= nsContentUtils::FormatLocalizedString(
208 nsContentUtils::eNECKO_PROPERTIES
, message
, params
, errorText
);
209 NS_ENSURE_SUCCESS_VOID(rv
);
211 nsContentUtils::ReportToConsoleByWindowID(
212 errorText
, nsIScriptError::warningFlag
, category
, aWindowID
, loc
);
215 RunConsoleReportingRunnable(runnable
.forget());
218 void ReportBlockingToConsole(nsIChannel
* aChannel
, nsIURI
* aURI
,
219 uint32_t aRejectedReason
) {
220 MOZ_ASSERT(aChannel
&& aURI
);
221 uint64_t windowID
= nsContentUtils::GetInnerWindowID(aChannel
);
223 // Get the window ID from the target BrowsingContext
224 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
226 RefPtr
<dom::BrowsingContext
> targetBrowsingContext
;
227 loadInfo
->GetTargetBrowsingContext(getter_AddRefs(targetBrowsingContext
));
229 if (!targetBrowsingContext
) {
233 WindowContext
* windowContext
=
234 targetBrowsingContext
->GetCurrentWindowContext();
235 if (!windowContext
) {
239 windowID
= windowContext
->InnerWindowId();
241 ReportBlockingToConsole(windowID
, aURI
, aRejectedReason
);
244 void NotifyBlockingDecision(nsIChannel
* aTrackingChannel
,
245 ContentBlockingNotifier::BlockingDecision aDecision
,
246 uint32_t aRejectedReason
, nsIURI
* aURI
) {
247 MOZ_ASSERT(aTrackingChannel
);
249 // This can be called in either the parent process or the child processes.
250 // When this is called in the child processes, we must have a window.
251 if (XRE_IsContentProcess()) {
252 nsCOMPtr
<nsILoadContext
> loadContext
;
253 NS_QueryNotificationCallbacks(aTrackingChannel
, loadContext
);
258 nsCOMPtr
<mozIDOMWindowProxy
> window
;
259 loadContext
->GetAssociatedWindow(getter_AddRefs(window
));
264 nsCOMPtr
<nsPIDOMWindowOuter
> outer
= nsPIDOMWindowOuter::From(window
);
269 // When this is called in the child processes with system privileges,
270 // the decision should always be ALLOW. We can stop here because both
271 // UI and content blocking log don't care this event.
272 if (nsGlobalWindowOuter::Cast(outer
)->GetPrincipal() ==
273 nsContentUtils::GetSystemPrincipal()) {
274 MOZ_DIAGNOSTIC_ASSERT(aDecision
==
275 ContentBlockingNotifier::BlockingDecision::eAllow
);
280 nsAutoCString trackingOrigin
;
282 // Using an empty OriginAttributes is OK here, as we'll only be accessing
284 nsCOMPtr
<nsIPrincipal
> principal
=
285 BasePrincipal::CreateContentPrincipal(aURI
, OriginAttributes
{});
286 principal
->GetOriginNoSuffix(trackingOrigin
);
289 if (aDecision
== ContentBlockingNotifier::BlockingDecision::eBlock
) {
290 ContentBlockingNotifier::OnEvent(aTrackingChannel
, true, aRejectedReason
,
293 ReportBlockingToConsole(aTrackingChannel
, aURI
, aRejectedReason
);
296 // Now send the generic "cookies loaded" notifications, from the most generic
297 // to the most specific.
298 ContentBlockingNotifier::OnEvent(aTrackingChannel
, false,
299 nsIWebProgressListener::STATE_COOKIES_LOADED
,
302 nsCOMPtr
<nsIClassifiedChannel
> classifiedChannel
=
303 do_QueryInterface(aTrackingChannel
);
304 if (!classifiedChannel
) {
308 if (aRejectedReason
==
309 nsIWebProgressListener::STATE_COOKIES_PARTITIONED_TRACKER
) {
310 ContentBlockingNotifier::OnEvent(
311 aTrackingChannel
, true,
312 nsIWebProgressListener::STATE_COOKIES_PARTITIONED_TRACKER
,
314 // Stop notifying the tracker cookie loaded events if they are partitioned.
318 uint32_t classificationFlags
=
319 classifiedChannel
->GetThirdPartyClassificationFlags();
320 if (classificationFlags
&
321 nsIClassifiedChannel::ClassificationFlags::CLASSIFIED_TRACKING
) {
322 ContentBlockingNotifier::OnEvent(
323 aTrackingChannel
, false,
324 nsIWebProgressListener::STATE_COOKIES_LOADED_TRACKER
, trackingOrigin
);
327 if (classificationFlags
&
328 nsIClassifiedChannel::ClassificationFlags::CLASSIFIED_SOCIALTRACKING
) {
329 ContentBlockingNotifier::OnEvent(
330 aTrackingChannel
, false,
331 nsIWebProgressListener::STATE_COOKIES_LOADED_SOCIALTRACKER
,
336 // Send a message to notify OnContentBlockingEvent in the parent, which will
337 // update the ContentBlockingLog in the parent.
338 void NotifyEventInChild(
339 nsIChannel
* aTrackingChannel
, bool aBlocked
, uint32_t aRejectedReason
,
340 const nsACString
& aTrackingOrigin
,
341 const Maybe
<ContentBlockingNotifier::StorageAccessPermissionGrantedReason
>&
343 const Maybe
<ContentBlockingNotifier::CanvasFingerprinter
>
344 aCanvasFingerprinter
,
345 const Maybe
<bool> aCanvasFingerprinterKnownText
) {
346 MOZ_ASSERT(XRE_IsContentProcess());
348 // We don't need to find the top-level window here because the
349 // parent will do that for us.
350 nsCOMPtr
<nsILoadContext
> loadContext
;
351 NS_QueryNotificationCallbacks(aTrackingChannel
, loadContext
);
356 nsCOMPtr
<mozIDOMWindowProxy
> window
;
357 loadContext
->GetAssociatedWindow(getter_AddRefs(window
));
362 RefPtr
<dom::BrowserChild
> browserChild
= dom::BrowserChild::GetFrom(window
);
363 NS_ENSURE_TRUE_VOID(browserChild
);
365 nsTArray
<nsCString
> trackingFullHashes
;
366 nsCOMPtr
<nsIClassifiedChannel
> classifiedChannel
=
367 do_QueryInterface(aTrackingChannel
);
369 if (classifiedChannel
) {
370 Unused
<< classifiedChannel
->GetMatchedTrackingFullHashes(
374 browserChild
->NotifyContentBlockingEvent(
375 aRejectedReason
, aTrackingChannel
, aBlocked
, aTrackingOrigin
,
376 trackingFullHashes
, aReason
, aCanvasFingerprinter
,
377 aCanvasFingerprinterKnownText
);
380 // Update the ContentBlockingLog of the top-level WindowGlobalParent of
381 // the tracking channel.
382 void NotifyEventInParent(
383 nsIChannel
* aTrackingChannel
, bool aBlocked
, uint32_t aRejectedReason
,
384 const nsACString
& aTrackingOrigin
,
385 const Maybe
<ContentBlockingNotifier::StorageAccessPermissionGrantedReason
>&
387 const Maybe
<ContentBlockingNotifier::CanvasFingerprinter
>
388 aCanvasFingerprinter
,
389 const Maybe
<bool> aCanvasFingerprinterKnownText
) {
390 MOZ_ASSERT(XRE_IsParentProcess());
392 nsCOMPtr
<nsILoadInfo
> loadInfo
= aTrackingChannel
->LoadInfo();
393 RefPtr
<dom::BrowsingContext
> bc
;
394 loadInfo
->GetBrowsingContext(getter_AddRefs(bc
));
396 if (!bc
|| bc
->IsDiscarded()) {
401 RefPtr
<dom::WindowGlobalParent
> wgp
=
402 bc
->Canonical()->GetCurrentWindowGlobal();
403 NS_ENSURE_TRUE_VOID(wgp
);
405 nsTArray
<nsCString
> trackingFullHashes
;
406 nsCOMPtr
<nsIClassifiedChannel
> classifiedChannel
=
407 do_QueryInterface(aTrackingChannel
);
409 if (classifiedChannel
) {
410 Unused
<< classifiedChannel
->GetMatchedTrackingFullHashes(
414 wgp
->NotifyContentBlockingEvent(aRejectedReason
, aTrackingChannel
, aBlocked
,
415 aTrackingOrigin
, trackingFullHashes
, aReason
,
416 aCanvasFingerprinter
,
417 aCanvasFingerprinterKnownText
);
423 void ContentBlockingNotifier::ReportUnblockingToConsole(
424 BrowsingContext
* aBrowsingContext
, const nsAString
& aTrackingOrigin
,
425 ContentBlockingNotifier::StorageAccessPermissionGrantedReason aReason
) {
426 MOZ_ASSERT(aBrowsingContext
);
427 MOZ_ASSERT_IF(XRE_IsContentProcess(), aBrowsingContext
->Top()->IsInProcess());
429 uint64_t windowID
= aBrowsingContext
->GetCurrentInnerWindowId();
431 // The storage permission is granted under the top-level origin.
432 nsCOMPtr
<nsIPrincipal
> principal
=
433 AntiTrackingUtils::GetPrincipal(aBrowsingContext
->Top());
434 if (NS_WARN_IF(!principal
)) {
438 ::ReportUnblockingToConsole(windowID
, principal
, aTrackingOrigin
, aReason
);
442 void ContentBlockingNotifier::OnDecision(nsIChannel
* aChannel
,
443 BlockingDecision aDecision
,
444 uint32_t aRejectedReason
) {
446 aRejectedReason
== 0 ||
448 static_cast<uint32_t>(
449 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION
) ||
451 static_cast<uint32_t>(
452 nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER
) ||
454 static_cast<uint32_t>(
455 nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER
) ||
457 static_cast<uint32_t>(
458 nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN
) ||
460 static_cast<uint32_t>(
461 nsIWebProgressListener::STATE_COOKIES_PARTITIONED_TRACKER
) ||
463 static_cast<uint32_t>(
464 nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL
) ||
466 static_cast<uint32_t>(
467 nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN
));
468 MOZ_ASSERT(aDecision
== BlockingDecision::eBlock
||
469 aDecision
== BlockingDecision::eAllow
);
475 nsCOMPtr
<nsIURI
> uri
;
476 aChannel
->GetURI(getter_AddRefs(uri
));
478 // Can be called in EITHER the parent or child process.
479 NotifyBlockingDecision(aChannel
, aDecision
, aRejectedReason
, uri
);
483 void ContentBlockingNotifier::OnDecision(nsPIDOMWindowInner
* aWindow
,
484 BlockingDecision aDecision
,
485 uint32_t aRejectedReason
) {
488 aRejectedReason
== 0 ||
490 static_cast<uint32_t>(
491 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION
) ||
493 static_cast<uint32_t>(
494 nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER
) ||
496 static_cast<uint32_t>(
497 nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER
) ||
499 static_cast<uint32_t>(
500 nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN
) ||
502 static_cast<uint32_t>(
503 nsIWebProgressListener::STATE_COOKIES_PARTITIONED_TRACKER
) ||
505 static_cast<uint32_t>(
506 nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL
) ||
508 static_cast<uint32_t>(
509 nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN
));
510 MOZ_ASSERT(aDecision
== BlockingDecision::eBlock
||
511 aDecision
== BlockingDecision::eAllow
);
513 Document
* document
= aWindow
->GetExtantDoc();
518 nsIChannel
* channel
= document
->GetChannel();
523 nsIURI
* uri
= document
->GetDocumentURI();
525 NotifyBlockingDecision(channel
, aDecision
, aRejectedReason
, uri
);
529 void ContentBlockingNotifier::OnDecision(BrowsingContext
* aBrowsingContext
,
530 BlockingDecision aDecision
,
531 uint32_t aRejectedReason
) {
532 MOZ_ASSERT(aBrowsingContext
);
533 MOZ_ASSERT_IF(XRE_IsContentProcess(), aBrowsingContext
->IsInProcess());
535 if (aBrowsingContext
->IsInProcess()) {
536 nsCOMPtr
<nsPIDOMWindowOuter
> outer
= aBrowsingContext
->GetDOMWindow();
537 if (NS_WARN_IF(!outer
)) {
541 nsCOMPtr
<nsPIDOMWindowInner
> inner
= outer
->GetCurrentInnerWindow();
542 if (NS_WARN_IF(!inner
)) {
546 ContentBlockingNotifier::OnDecision(inner
, aDecision
, aRejectedReason
);
548 // we send an IPC to the content process when we don't have an in-process
549 // browsing context. This is not smart because this should be able to be
550 // done directly in the parent. The reason we are doing this is because we
551 // need the channel, which is not accessible in the parent when you only
552 // have a browsing context.
553 MOZ_ASSERT(XRE_IsParentProcess());
555 ContentParent
* cp
= aBrowsingContext
->Canonical()->GetContentParent();
556 Unused
<< cp
->SendOnContentBlockingDecision(aBrowsingContext
, aDecision
,
562 void ContentBlockingNotifier::OnEvent(nsIChannel
* aTrackingChannel
,
563 uint32_t aRejectedReason
, bool aBlocked
) {
564 MOZ_ASSERT(XRE_IsParentProcess() && aTrackingChannel
);
566 nsCOMPtr
<nsIURI
> uri
;
567 aTrackingChannel
->GetURI(getter_AddRefs(uri
));
569 nsAutoCString trackingOrigin
;
571 // Using empty OriginAttributes is OK here, as we only want to access
573 nsCOMPtr
<nsIPrincipal
> trackingPrincipal
=
574 BasePrincipal::CreateContentPrincipal(uri
, OriginAttributes
{});
575 trackingPrincipal
->GetOriginNoSuffix(trackingOrigin
);
578 return ContentBlockingNotifier::OnEvent(aTrackingChannel
, aBlocked
,
579 aRejectedReason
, trackingOrigin
);
583 void ContentBlockingNotifier::OnEvent(
584 nsIChannel
* aTrackingChannel
, bool aBlocked
, uint32_t aRejectedReason
,
585 const nsACString
& aTrackingOrigin
,
586 const Maybe
<StorageAccessPermissionGrantedReason
>& aReason
,
587 const Maybe
<CanvasFingerprinter
>& aCanvasFingerprinter
,
588 const Maybe
<bool> aCanvasFingerprinterKnownText
) {
589 if (XRE_IsParentProcess()) {
590 NotifyEventInParent(aTrackingChannel
, aBlocked
, aRejectedReason
,
591 aTrackingOrigin
, aReason
, aCanvasFingerprinter
,
592 aCanvasFingerprinterKnownText
);
594 NotifyEventInChild(aTrackingChannel
, aBlocked
, aRejectedReason
,
595 aTrackingOrigin
, aReason
, aCanvasFingerprinter
,
596 aCanvasFingerprinterKnownText
);