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_BLOCKED_ALL
) ||
131 static_cast<uint32_t>(
132 nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN
));
134 if (aURI
->SchemeIs("chrome") || aURI
->SchemeIs("about")) {
138 nsresult rv
= NS_URIChainHasFlags(
139 aURI
, nsIProtocolHandler::URI_FORBIDS_COOKIE_ACCESS
, &hasFlags
);
140 if (NS_FAILED(rv
) || hasFlags
) {
141 // If the protocol doesn't support cookies, no need to report them blocked.
145 auto location
= JSCallingLocation::Get();
147 nsCOMPtr
<nsIURI
> uri(aURI
);
149 RefPtr
<Runnable
> runnable
= NS_NewRunnableFunction(
150 "ReportBlockingToConsoleDelayed",
151 [aWindowID
, loc
= std::move(location
), uri
, aRejectedReason
]() {
152 const char* message
= nullptr;
153 nsAutoCString category
;
154 // When changing this list, please make sure to update the corresponding
155 // code in antitracking_head.js (inside _createTask).
156 // XXX: The nsIWebProgressListener constants below are interpreted as
157 // signed integers on Windows and the compiler complains that they can't
158 // be narrowed to uint32_t. To prevent this, we cast them to uint32_t.
159 switch (aRejectedReason
) {
161 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION
):
162 message
= "CookieBlockedByPermission";
163 category
= "cookieBlockedPermission"_ns
;
166 case uint32_t(nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER
):
167 message
= "CookieBlockedTracker";
168 category
= "cookieBlockedTracker"_ns
;
171 case uint32_t(nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL
):
172 message
= "CookieBlockedAll";
173 category
= "cookieBlockedAll"_ns
;
176 case uint32_t(nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN
):
177 message
= "CookieBlockedForeign";
178 category
= "cookieBlockedForeign"_ns
;
182 nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN
):
183 message
= "CookiePartitionedForeign2";
184 category
= "cookiePartitionedForeign"_ns
;
193 // Strip the URL of any possible username/password and make it ready
194 // to be presented in the UI.
195 nsCOMPtr
<nsIURI
> exposableURI
=
196 net::nsIOService::CreateExposableURI(uri
);
197 AutoTArray
<nsString
, 1> params
;
198 CopyUTF8toUTF16(exposableURI
->GetSpecOrDefault(),
199 *params
.AppendElement());
201 nsAutoString errorText
;
202 nsresult rv
= nsContentUtils::FormatLocalizedString(
203 nsContentUtils::eNECKO_PROPERTIES
, message
, params
, errorText
);
204 NS_ENSURE_SUCCESS_VOID(rv
);
206 nsContentUtils::ReportToConsoleByWindowID(
207 errorText
, nsIScriptError::warningFlag
, category
, aWindowID
, loc
);
210 RunConsoleReportingRunnable(runnable
.forget());
213 void ReportBlockingToConsole(nsIChannel
* aChannel
, nsIURI
* aURI
,
214 uint32_t aRejectedReason
) {
215 MOZ_ASSERT(aChannel
&& aURI
);
216 uint64_t windowID
= nsContentUtils::GetInnerWindowID(aChannel
);
218 // Get the window ID from the target BrowsingContext
219 nsCOMPtr
<nsILoadInfo
> loadInfo
= aChannel
->LoadInfo();
221 RefPtr
<dom::BrowsingContext
> targetBrowsingContext
;
222 loadInfo
->GetTargetBrowsingContext(getter_AddRefs(targetBrowsingContext
));
224 if (!targetBrowsingContext
) {
228 WindowContext
* windowContext
=
229 targetBrowsingContext
->GetCurrentWindowContext();
230 if (!windowContext
) {
234 windowID
= windowContext
->InnerWindowId();
236 ReportBlockingToConsole(windowID
, aURI
, aRejectedReason
);
239 void NotifyBlockingDecision(nsIChannel
* aTrackingChannel
,
240 ContentBlockingNotifier::BlockingDecision aDecision
,
241 uint32_t aRejectedReason
, nsIURI
* aURI
) {
242 MOZ_ASSERT(aTrackingChannel
);
244 // This can be called in either the parent process or the child processes.
245 // When this is called in the child processes, we must have a window.
246 if (XRE_IsContentProcess()) {
247 nsCOMPtr
<nsILoadContext
> loadContext
;
248 NS_QueryNotificationCallbacks(aTrackingChannel
, loadContext
);
253 nsCOMPtr
<mozIDOMWindowProxy
> window
;
254 loadContext
->GetAssociatedWindow(getter_AddRefs(window
));
259 nsCOMPtr
<nsPIDOMWindowOuter
> outer
= nsPIDOMWindowOuter::From(window
);
264 // When this is called in the child processes with system privileges,
265 // the decision should always be ALLOW. We can stop here because both
266 // UI and content blocking log don't care this event.
267 if (nsGlobalWindowOuter::Cast(outer
)->GetPrincipal() ==
268 nsContentUtils::GetSystemPrincipal()) {
269 MOZ_DIAGNOSTIC_ASSERT(aDecision
==
270 ContentBlockingNotifier::BlockingDecision::eAllow
);
275 nsAutoCString trackingOrigin
;
277 // Using an empty OriginAttributes is OK here, as we'll only be accessing
279 nsCOMPtr
<nsIPrincipal
> principal
=
280 BasePrincipal::CreateContentPrincipal(aURI
, OriginAttributes
{});
281 principal
->GetOriginNoSuffix(trackingOrigin
);
284 if (aDecision
== ContentBlockingNotifier::BlockingDecision::eBlock
) {
285 ContentBlockingNotifier::OnEvent(aTrackingChannel
, true, aRejectedReason
,
288 ReportBlockingToConsole(aTrackingChannel
, aURI
, aRejectedReason
);
291 // Now send the generic "cookies loaded" notifications, from the most generic
292 // to the most specific.
293 ContentBlockingNotifier::OnEvent(aTrackingChannel
, false,
294 nsIWebProgressListener::STATE_COOKIES_LOADED
,
297 nsCOMPtr
<nsIClassifiedChannel
> classifiedChannel
=
298 do_QueryInterface(aTrackingChannel
);
299 if (!classifiedChannel
) {
303 uint32_t classificationFlags
=
304 classifiedChannel
->GetThirdPartyClassificationFlags();
305 if (classificationFlags
&
306 nsIClassifiedChannel::ClassificationFlags::CLASSIFIED_TRACKING
) {
307 ContentBlockingNotifier::OnEvent(
308 aTrackingChannel
, false,
309 nsIWebProgressListener::STATE_COOKIES_LOADED_TRACKER
, trackingOrigin
);
312 if (classificationFlags
&
313 nsIClassifiedChannel::ClassificationFlags::CLASSIFIED_SOCIALTRACKING
) {
314 ContentBlockingNotifier::OnEvent(
315 aTrackingChannel
, false,
316 nsIWebProgressListener::STATE_COOKIES_LOADED_SOCIALTRACKER
,
321 // Send a message to notify OnContentBlockingEvent in the parent, which will
322 // update the ContentBlockingLog in the parent.
323 void NotifyEventInChild(
324 nsIChannel
* aTrackingChannel
, bool aBlocked
, uint32_t aRejectedReason
,
325 const nsACString
& aTrackingOrigin
,
326 const Maybe
<ContentBlockingNotifier::StorageAccessPermissionGrantedReason
>&
328 const Maybe
<ContentBlockingNotifier::CanvasFingerprinter
>
329 aCanvasFingerprinter
,
330 const Maybe
<bool> aCanvasFingerprinterKnownText
) {
331 MOZ_ASSERT(XRE_IsContentProcess());
333 // We don't need to find the top-level window here because the
334 // parent will do that for us.
335 nsCOMPtr
<nsILoadContext
> loadContext
;
336 NS_QueryNotificationCallbacks(aTrackingChannel
, loadContext
);
341 nsCOMPtr
<mozIDOMWindowProxy
> window
;
342 loadContext
->GetAssociatedWindow(getter_AddRefs(window
));
347 RefPtr
<dom::BrowserChild
> browserChild
= dom::BrowserChild::GetFrom(window
);
348 NS_ENSURE_TRUE_VOID(browserChild
);
350 nsTArray
<nsCString
> trackingFullHashes
;
351 nsCOMPtr
<nsIClassifiedChannel
> classifiedChannel
=
352 do_QueryInterface(aTrackingChannel
);
354 if (classifiedChannel
) {
355 Unused
<< classifiedChannel
->GetMatchedTrackingFullHashes(
359 browserChild
->NotifyContentBlockingEvent(
360 aRejectedReason
, aTrackingChannel
, aBlocked
, aTrackingOrigin
,
361 trackingFullHashes
, aReason
, aCanvasFingerprinter
,
362 aCanvasFingerprinterKnownText
);
365 // Update the ContentBlockingLog of the top-level WindowGlobalParent of
366 // the tracking channel.
367 void NotifyEventInParent(
368 nsIChannel
* aTrackingChannel
, bool aBlocked
, uint32_t aRejectedReason
,
369 const nsACString
& aTrackingOrigin
,
370 const Maybe
<ContentBlockingNotifier::StorageAccessPermissionGrantedReason
>&
372 const Maybe
<ContentBlockingNotifier::CanvasFingerprinter
>
373 aCanvasFingerprinter
,
374 const Maybe
<bool> aCanvasFingerprinterKnownText
) {
375 MOZ_ASSERT(XRE_IsParentProcess());
377 nsCOMPtr
<nsILoadInfo
> loadInfo
= aTrackingChannel
->LoadInfo();
378 RefPtr
<dom::BrowsingContext
> bc
;
379 loadInfo
->GetBrowsingContext(getter_AddRefs(bc
));
381 if (!bc
|| bc
->IsDiscarded()) {
386 RefPtr
<dom::WindowGlobalParent
> wgp
=
387 bc
->Canonical()->GetCurrentWindowGlobal();
388 NS_ENSURE_TRUE_VOID(wgp
);
390 nsTArray
<nsCString
> trackingFullHashes
;
391 nsCOMPtr
<nsIClassifiedChannel
> classifiedChannel
=
392 do_QueryInterface(aTrackingChannel
);
394 if (classifiedChannel
) {
395 Unused
<< classifiedChannel
->GetMatchedTrackingFullHashes(
399 wgp
->NotifyContentBlockingEvent(aRejectedReason
, aTrackingChannel
, aBlocked
,
400 aTrackingOrigin
, trackingFullHashes
, aReason
,
401 aCanvasFingerprinter
,
402 aCanvasFingerprinterKnownText
);
408 void ContentBlockingNotifier::ReportUnblockingToConsole(
409 BrowsingContext
* aBrowsingContext
, const nsAString
& aTrackingOrigin
,
410 ContentBlockingNotifier::StorageAccessPermissionGrantedReason aReason
) {
411 MOZ_ASSERT(aBrowsingContext
);
412 MOZ_ASSERT_IF(XRE_IsContentProcess(), aBrowsingContext
->Top()->IsInProcess());
414 uint64_t windowID
= aBrowsingContext
->GetCurrentInnerWindowId();
416 // The storage permission is granted under the top-level origin.
417 nsCOMPtr
<nsIPrincipal
> principal
=
418 AntiTrackingUtils::GetPrincipal(aBrowsingContext
->Top());
419 if (NS_WARN_IF(!principal
)) {
423 ::ReportUnblockingToConsole(windowID
, principal
, aTrackingOrigin
, aReason
);
427 void ContentBlockingNotifier::OnDecision(nsIChannel
* aChannel
,
428 BlockingDecision aDecision
,
429 uint32_t aRejectedReason
) {
431 aRejectedReason
== 0 ||
433 static_cast<uint32_t>(
434 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION
) ||
436 static_cast<uint32_t>(
437 nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER
) ||
439 static_cast<uint32_t>(
440 nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER
) ||
442 static_cast<uint32_t>(
443 nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN
) ||
445 static_cast<uint32_t>(
446 nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL
) ||
448 static_cast<uint32_t>(
449 nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN
));
450 MOZ_ASSERT(aDecision
== BlockingDecision::eBlock
||
451 aDecision
== BlockingDecision::eAllow
);
457 nsCOMPtr
<nsIURI
> uri
;
458 aChannel
->GetURI(getter_AddRefs(uri
));
460 // Can be called in EITHER the parent or child process.
461 NotifyBlockingDecision(aChannel
, aDecision
, aRejectedReason
, uri
);
465 void ContentBlockingNotifier::OnDecision(nsPIDOMWindowInner
* aWindow
,
466 BlockingDecision aDecision
,
467 uint32_t aRejectedReason
) {
470 aRejectedReason
== 0 ||
472 static_cast<uint32_t>(
473 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION
) ||
475 static_cast<uint32_t>(
476 nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER
) ||
478 static_cast<uint32_t>(
479 nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER
) ||
481 static_cast<uint32_t>(
482 nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN
) ||
484 static_cast<uint32_t>(
485 nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL
) ||
487 static_cast<uint32_t>(
488 nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN
));
489 MOZ_ASSERT(aDecision
== BlockingDecision::eBlock
||
490 aDecision
== BlockingDecision::eAllow
);
492 Document
* document
= aWindow
->GetExtantDoc();
497 nsIChannel
* channel
= document
->GetChannel();
502 nsIURI
* uri
= document
->GetDocumentURI();
504 NotifyBlockingDecision(channel
, aDecision
, aRejectedReason
, uri
);
508 void ContentBlockingNotifier::OnDecision(BrowsingContext
* aBrowsingContext
,
509 BlockingDecision aDecision
,
510 uint32_t aRejectedReason
) {
511 MOZ_ASSERT(aBrowsingContext
);
512 MOZ_ASSERT_IF(XRE_IsContentProcess(), aBrowsingContext
->IsInProcess());
514 if (aBrowsingContext
->IsInProcess()) {
515 nsCOMPtr
<nsPIDOMWindowOuter
> outer
= aBrowsingContext
->GetDOMWindow();
516 if (NS_WARN_IF(!outer
)) {
520 nsCOMPtr
<nsPIDOMWindowInner
> inner
= outer
->GetCurrentInnerWindow();
521 if (NS_WARN_IF(!inner
)) {
525 ContentBlockingNotifier::OnDecision(inner
, aDecision
, aRejectedReason
);
527 // we send an IPC to the content process when we don't have an in-process
528 // browsing context. This is not smart because this should be able to be
529 // done directly in the parent. The reason we are doing this is because we
530 // need the channel, which is not accessible in the parent when you only
531 // have a browsing context.
532 MOZ_ASSERT(XRE_IsParentProcess());
534 ContentParent
* cp
= aBrowsingContext
->Canonical()->GetContentParent();
535 Unused
<< cp
->SendOnContentBlockingDecision(aBrowsingContext
, aDecision
,
541 void ContentBlockingNotifier::OnEvent(nsIChannel
* aTrackingChannel
,
542 uint32_t aRejectedReason
, bool aBlocked
) {
543 MOZ_ASSERT(XRE_IsParentProcess() && aTrackingChannel
);
545 nsCOMPtr
<nsIURI
> uri
;
546 aTrackingChannel
->GetURI(getter_AddRefs(uri
));
548 nsAutoCString trackingOrigin
;
550 // Using empty OriginAttributes is OK here, as we only want to access
552 nsCOMPtr
<nsIPrincipal
> trackingPrincipal
=
553 BasePrincipal::CreateContentPrincipal(uri
, OriginAttributes
{});
554 trackingPrincipal
->GetOriginNoSuffix(trackingOrigin
);
557 return ContentBlockingNotifier::OnEvent(aTrackingChannel
, aBlocked
,
558 aRejectedReason
, trackingOrigin
);
562 void ContentBlockingNotifier::OnEvent(
563 nsIChannel
* aTrackingChannel
, bool aBlocked
, uint32_t aRejectedReason
,
564 const nsACString
& aTrackingOrigin
,
565 const Maybe
<StorageAccessPermissionGrantedReason
>& aReason
,
566 const Maybe
<CanvasFingerprinter
>& aCanvasFingerprinter
,
567 const Maybe
<bool> aCanvasFingerprinterKnownText
) {
568 if (XRE_IsParentProcess()) {
569 NotifyEventInParent(aTrackingChannel
, aBlocked
, aRejectedReason
,
570 aTrackingOrigin
, aReason
, aCanvasFingerprinter
,
571 aCanvasFingerprinterKnownText
);
573 NotifyEventInChild(aTrackingChannel
, aBlocked
, aRejectedReason
,
574 aTrackingOrigin
, aReason
, aCanvasFingerprinter
,
575 aCanvasFingerprinterKnownText
);