Bug 1871991 - Required arguments after optional are not supported r=jesup
[gecko.git] / toolkit / components / antitracking / ContentBlockingNotifier.cpp
blob84f58020ef57a84cf4a4a147012957e3f69141e9
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"
21 #include "nsIURI.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;
35 namespace {
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))) {
43 return;
45 } else {
46 nsCOMPtr<nsIRunnable> runnable(std::move(aRunnable));
47 nsresult rv = runnable->Run();
48 if (NS_WARN_IF(NS_FAILED(rv))) {
49 return;
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();
64 if (cx) {
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;
77 switch (aReason) {
78 case ContentBlockingNotifier::eStorageAccessAPI:
79 case ContentBlockingNotifier::ePrivilegeStorageAccessForOriginAPI:
80 messageWithSameOrigin = "CookieAllowedForOriginByStorageAccessAPI";
81 break;
83 case ContentBlockingNotifier::eOpenerAfterUserInteraction:
84 [[fallthrough]];
85 case ContentBlockingNotifier::eOpener:
86 messageWithSameOrigin = "CookieAllowedForOriginByHeuristic";
87 break;
90 nsAutoCString origin;
91 nsresult rv = principal->GetOriginNoSuffix(origin);
92 if (NS_WARN_IF(NS_FAILED(rv))) {
93 return;
96 // Not adding grantedOrigin yet because we may not want it later.
97 AutoTArray<nsString, 2> params = {NS_ConvertUTF8toUTF16(origin),
98 trackingOrigin};
100 nsAutoString errorText;
101 rv = nsContentUtils::FormatLocalizedString(
102 nsContentUtils::eNECKO_PROPERTIES, messageWithSameOrigin, params,
103 errorText);
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);
118 MOZ_ASSERT(aURI);
119 MOZ_ASSERT(
120 aRejectedReason == 0 ||
121 aRejectedReason ==
122 static_cast<uint32_t>(
123 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION) ||
124 aRejectedReason ==
125 static_cast<uint32_t>(
126 nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER) ||
127 aRejectedReason ==
128 static_cast<uint32_t>(
129 nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER) ||
130 aRejectedReason ==
131 static_cast<uint32_t>(
132 nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN) ||
133 aRejectedReason ==
134 static_cast<uint32_t>(
135 nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL) ||
136 aRejectedReason ==
137 static_cast<uint32_t>(
138 nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN));
140 if (aURI->SchemeIs("chrome") || aURI->SchemeIs("about")) {
141 return;
143 bool hasFlags;
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.
148 return;
151 nsAutoString sourceLine;
152 uint32_t lineNumber = 0, columnNumber = 1;
153 JSContext* cx = nsContentUtils::GetCurrentJSContext();
154 if (cx) {
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) {
171 case uint32_t(
172 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION):
173 message = "CookieBlockedByPermission";
174 category = "cookieBlockedPermission"_ns;
175 break;
177 case uint32_t(nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER):
178 message = "CookieBlockedTracker";
179 category = "cookieBlockedTracker"_ns;
180 break;
182 case uint32_t(nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL):
183 message = "CookieBlockedAll";
184 category = "cookieBlockedAll"_ns;
185 break;
187 case uint32_t(nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN):
188 message = "CookieBlockedForeign";
189 category = "cookieBlockedForeign"_ns;
190 break;
192 case uint32_t(
193 nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN):
194 message = "CookiePartitionedForeign2";
195 category = "cookiePartitionedForeign"_ns;
196 break;
198 default:
199 return;
202 MOZ_ASSERT(message);
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);
229 if (!windowID) {
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) {
237 return;
240 WindowContext* windowContext =
241 targetBrowsingContext->GetCurrentWindowContext();
242 if (!windowContext) {
243 return;
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);
261 if (!loadContext) {
262 return;
265 nsCOMPtr<mozIDOMWindowProxy> window;
266 loadContext->GetAssociatedWindow(getter_AddRefs(window));
267 if (!window) {
268 return;
271 nsCOMPtr<nsPIDOMWindowOuter> outer = nsPIDOMWindowOuter::From(window);
272 if (!outer) {
273 return;
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);
283 return;
287 nsAutoCString trackingOrigin;
288 if (aURI) {
289 // Using an empty OriginAttributes is OK here, as we'll only be accessing
290 // OriginNoSuffix.
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,
298 trackingOrigin);
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,
307 trackingOrigin);
309 nsCOMPtr<nsIClassifiedChannel> classifiedChannel =
310 do_QueryInterface(aTrackingChannel);
311 if (!classifiedChannel) {
312 return;
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,
329 trackingOrigin);
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>&
339 aReason,
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);
349 if (!loadContext) {
350 return;
353 nsCOMPtr<mozIDOMWindowProxy> window;
354 loadContext->GetAssociatedWindow(getter_AddRefs(window));
355 if (!window) {
356 return;
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(
368 trackingFullHashes);
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>&
383 aReason,
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()) {
394 return;
397 bc = bc->Top();
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(
408 trackingFullHashes);
411 wgp->NotifyContentBlockingEvent(aRejectedReason, aTrackingChannel, aBlocked,
412 aTrackingOrigin, trackingFullHashes, aReason,
413 aCanvasFingerprinter,
414 aCanvasFingerprinterKnownText);
417 } // namespace
419 /* static */
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)) {
432 return;
435 ::ReportUnblockingToConsole(windowID, principal, aTrackingOrigin, aReason);
438 /* static */
439 void ContentBlockingNotifier::OnDecision(nsIChannel* aChannel,
440 BlockingDecision aDecision,
441 uint32_t aRejectedReason) {
442 MOZ_ASSERT(
443 aRejectedReason == 0 ||
444 aRejectedReason ==
445 static_cast<uint32_t>(
446 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION) ||
447 aRejectedReason ==
448 static_cast<uint32_t>(
449 nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER) ||
450 aRejectedReason ==
451 static_cast<uint32_t>(
452 nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER) ||
453 aRejectedReason ==
454 static_cast<uint32_t>(
455 nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN) ||
456 aRejectedReason ==
457 static_cast<uint32_t>(
458 nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL) ||
459 aRejectedReason ==
460 static_cast<uint32_t>(
461 nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN));
462 MOZ_ASSERT(aDecision == BlockingDecision::eBlock ||
463 aDecision == BlockingDecision::eAllow);
465 if (!aChannel) {
466 return;
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);
476 /* static */
477 void ContentBlockingNotifier::OnDecision(nsPIDOMWindowInner* aWindow,
478 BlockingDecision aDecision,
479 uint32_t aRejectedReason) {
480 MOZ_ASSERT(aWindow);
481 MOZ_ASSERT(
482 aRejectedReason == 0 ||
483 aRejectedReason ==
484 static_cast<uint32_t>(
485 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION) ||
486 aRejectedReason ==
487 static_cast<uint32_t>(
488 nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER) ||
489 aRejectedReason ==
490 static_cast<uint32_t>(
491 nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER) ||
492 aRejectedReason ==
493 static_cast<uint32_t>(
494 nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN) ||
495 aRejectedReason ==
496 static_cast<uint32_t>(
497 nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL) ||
498 aRejectedReason ==
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();
505 if (!document) {
506 return;
509 nsIChannel* channel = document->GetChannel();
510 if (!channel) {
511 return;
514 nsIURI* uri = document->GetDocumentURI();
516 NotifyBlockingDecision(channel, aDecision, aRejectedReason, uri);
519 /* static */
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)) {
529 return;
532 nsCOMPtr<nsPIDOMWindowInner> inner = outer->GetCurrentInnerWindow();
533 if (NS_WARN_IF(!inner)) {
534 return;
537 ContentBlockingNotifier::OnDecision(inner, aDecision, aRejectedReason);
538 } else {
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,
548 aRejectedReason);
552 /* static */
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;
561 if (uri) {
562 // Using empty OriginAttributes is OK here, as we only want to access
563 // OriginNoSuffix.
564 nsCOMPtr<nsIPrincipal> trackingPrincipal =
565 BasePrincipal::CreateContentPrincipal(uri, OriginAttributes{});
566 trackingPrincipal->GetOriginNoSuffix(trackingOrigin);
569 return ContentBlockingNotifier::OnEvent(aTrackingChannel, aBlocked,
570 aRejectedReason, trackingOrigin);
573 /* static */
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);
584 } else {
585 NotifyEventInChild(aTrackingChannel, aBlocked, aRejectedReason,
586 aTrackingOrigin, aReason, aCanvasFingerprinter,
587 aCanvasFingerprinterKnownText);