Bug 1913453 - Remove the execution right on a pdf test file r=marco
[gecko.git] / toolkit / components / antitracking / ContentBlockingNotifier.cpp
blob0faf20582adff4fd6b7bfe0fccf5a468c8fad41e
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"
22 #include "nsIURI.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;
34 namespace {
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))) {
42 return;
44 } else {
45 nsCOMPtr<nsIRunnable> runnable(std::move(aRunnable));
46 nsresult rv = runnable->Run();
47 if (NS_WARN_IF(NS_FAILED(rv))) {
48 return;
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,
69 aReason]() {
70 const char* messageWithSameOrigin = nullptr;
72 switch (aReason) {
73 case ContentBlockingNotifier::eStorageAccessAPI:
74 case ContentBlockingNotifier::ePrivilegeStorageAccessForOriginAPI:
75 messageWithSameOrigin = "CookieAllowedForOriginByStorageAccessAPI";
76 break;
78 case ContentBlockingNotifier::eOpenerAfterUserInteraction:
79 [[fallthrough]];
80 case ContentBlockingNotifier::eOpener:
81 messageWithSameOrigin = "CookieAllowedForOriginByHeuristic";
82 break;
85 nsAutoCString origin;
86 nsresult rv = principal->GetOriginNoSuffix(origin);
87 if (NS_WARN_IF(NS_FAILED(rv))) {
88 return;
91 // Not adding grantedOrigin yet because we may not want it later.
92 AutoTArray<nsString, 2> params = {NS_ConvertUTF8toUTF16(origin),
93 trackingOrigin};
95 nsAutoString errorText;
96 rv = nsContentUtils::FormatLocalizedString(
97 nsContentUtils::eNECKO_PROPERTIES, messageWithSameOrigin, params,
98 errorText);
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);
112 MOZ_ASSERT(aURI);
113 MOZ_ASSERT(
114 aRejectedReason == 0 ||
115 aRejectedReason ==
116 static_cast<uint32_t>(
117 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION) ||
118 aRejectedReason ==
119 static_cast<uint32_t>(
120 nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER) ||
121 aRejectedReason ==
122 static_cast<uint32_t>(
123 nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER) ||
124 aRejectedReason ==
125 static_cast<uint32_t>(
126 nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN) ||
127 aRejectedReason ==
128 static_cast<uint32_t>(
129 nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL) ||
130 aRejectedReason ==
131 static_cast<uint32_t>(
132 nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN));
134 if (aURI->SchemeIs("chrome") || aURI->SchemeIs("about")) {
135 return;
137 bool hasFlags;
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.
142 return;
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) {
160 case uint32_t(
161 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION):
162 message = "CookieBlockedByPermission";
163 category = "cookieBlockedPermission"_ns;
164 break;
166 case uint32_t(nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER):
167 message = "CookieBlockedTracker";
168 category = "cookieBlockedTracker"_ns;
169 break;
171 case uint32_t(nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL):
172 message = "CookieBlockedAll";
173 category = "cookieBlockedAll"_ns;
174 break;
176 case uint32_t(nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN):
177 message = "CookieBlockedForeign";
178 category = "cookieBlockedForeign"_ns;
179 break;
181 case uint32_t(
182 nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN):
183 message = "CookiePartitionedForeign2";
184 category = "cookiePartitionedForeign"_ns;
185 break;
187 default:
188 return;
191 MOZ_ASSERT(message);
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);
217 if (!windowID) {
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) {
225 return;
228 WindowContext* windowContext =
229 targetBrowsingContext->GetCurrentWindowContext();
230 if (!windowContext) {
231 return;
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);
249 if (!loadContext) {
250 return;
253 nsCOMPtr<mozIDOMWindowProxy> window;
254 loadContext->GetAssociatedWindow(getter_AddRefs(window));
255 if (!window) {
256 return;
259 nsCOMPtr<nsPIDOMWindowOuter> outer = nsPIDOMWindowOuter::From(window);
260 if (!outer) {
261 return;
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);
271 return;
275 nsAutoCString trackingOrigin;
276 if (aURI) {
277 // Using an empty OriginAttributes is OK here, as we'll only be accessing
278 // OriginNoSuffix.
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,
286 trackingOrigin);
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,
295 trackingOrigin);
297 nsCOMPtr<nsIClassifiedChannel> classifiedChannel =
298 do_QueryInterface(aTrackingChannel);
299 if (!classifiedChannel) {
300 return;
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,
317 trackingOrigin);
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>&
327 aReason,
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);
337 if (!loadContext) {
338 return;
341 nsCOMPtr<mozIDOMWindowProxy> window;
342 loadContext->GetAssociatedWindow(getter_AddRefs(window));
343 if (!window) {
344 return;
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(
356 trackingFullHashes);
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>&
371 aReason,
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()) {
382 return;
385 bc = bc->Top();
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(
396 trackingFullHashes);
399 wgp->NotifyContentBlockingEvent(aRejectedReason, aTrackingChannel, aBlocked,
400 aTrackingOrigin, trackingFullHashes, aReason,
401 aCanvasFingerprinter,
402 aCanvasFingerprinterKnownText);
405 } // namespace
407 /* static */
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)) {
420 return;
423 ::ReportUnblockingToConsole(windowID, principal, aTrackingOrigin, aReason);
426 /* static */
427 void ContentBlockingNotifier::OnDecision(nsIChannel* aChannel,
428 BlockingDecision aDecision,
429 uint32_t aRejectedReason) {
430 MOZ_ASSERT(
431 aRejectedReason == 0 ||
432 aRejectedReason ==
433 static_cast<uint32_t>(
434 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION) ||
435 aRejectedReason ==
436 static_cast<uint32_t>(
437 nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER) ||
438 aRejectedReason ==
439 static_cast<uint32_t>(
440 nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER) ||
441 aRejectedReason ==
442 static_cast<uint32_t>(
443 nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN) ||
444 aRejectedReason ==
445 static_cast<uint32_t>(
446 nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL) ||
447 aRejectedReason ==
448 static_cast<uint32_t>(
449 nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN));
450 MOZ_ASSERT(aDecision == BlockingDecision::eBlock ||
451 aDecision == BlockingDecision::eAllow);
453 if (!aChannel) {
454 return;
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);
464 /* static */
465 void ContentBlockingNotifier::OnDecision(nsPIDOMWindowInner* aWindow,
466 BlockingDecision aDecision,
467 uint32_t aRejectedReason) {
468 MOZ_ASSERT(aWindow);
469 MOZ_ASSERT(
470 aRejectedReason == 0 ||
471 aRejectedReason ==
472 static_cast<uint32_t>(
473 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION) ||
474 aRejectedReason ==
475 static_cast<uint32_t>(
476 nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER) ||
477 aRejectedReason ==
478 static_cast<uint32_t>(
479 nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER) ||
480 aRejectedReason ==
481 static_cast<uint32_t>(
482 nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN) ||
483 aRejectedReason ==
484 static_cast<uint32_t>(
485 nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL) ||
486 aRejectedReason ==
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();
493 if (!document) {
494 return;
497 nsIChannel* channel = document->GetChannel();
498 if (!channel) {
499 return;
502 nsIURI* uri = document->GetDocumentURI();
504 NotifyBlockingDecision(channel, aDecision, aRejectedReason, uri);
507 /* static */
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)) {
517 return;
520 nsCOMPtr<nsPIDOMWindowInner> inner = outer->GetCurrentInnerWindow();
521 if (NS_WARN_IF(!inner)) {
522 return;
525 ContentBlockingNotifier::OnDecision(inner, aDecision, aRejectedReason);
526 } else {
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,
536 aRejectedReason);
540 /* static */
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;
549 if (uri) {
550 // Using empty OriginAttributes is OK here, as we only want to access
551 // OriginNoSuffix.
552 nsCOMPtr<nsIPrincipal> trackingPrincipal =
553 BasePrincipal::CreateContentPrincipal(uri, OriginAttributes{});
554 trackingPrincipal->GetOriginNoSuffix(trackingOrigin);
557 return ContentBlockingNotifier::OnEvent(aTrackingChannel, aBlocked,
558 aRejectedReason, trackingOrigin);
561 /* static */
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);
572 } else {
573 NotifyEventInChild(aTrackingChannel, aBlocked, aRejectedReason,
574 aTrackingOrigin, aReason, aCanvasFingerprinter,
575 aCanvasFingerprinterKnownText);