|
14 | 14 | #include "mozilla/dom/OffscreenCanvas.h"
|
15 | 15 | #include "mozilla/dom/UserActivation.h"
|
16 | 16 | #include "mozilla/dom/WorkerCommon.h"
|
| 17 | +#include "mozilla/dom/WindowGlobalParent.h" |
17 | 18 | #include "mozilla/dom/WorkerPrivate.h"
|
18 | 19 | #include "mozilla/dom/WorkerRunnable.h"
|
19 | 20 | #include "mozilla/gfx/gfxVars.h"
|
@@ -101,12 +102,12 @@ uint32_t GetCanvasExtractDataPermission(nsIPrincipal& aPrincipal) {
|
101 | 102 | nsresult rv;
|
102 | 103 | nsCOMPtr<nsIPermissionManager> permissionManager =
|
103 | 104 | do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
|
104 |
| - NS_ENSURE_SUCCESS(rv, false); |
| 105 | + NS_ENSURE_SUCCESS(rv, nsIPermissionManager::UNKNOWN_ACTION); |
105 | 106 |
|
106 | 107 | uint32_t permission;
|
107 | 108 | rv = permissionManager->TestPermissionFromPrincipal(
|
108 | 109 | &aPrincipal, PERMISSION_CANVAS_EXTRACT_DATA, &permission);
|
109 |
| - NS_ENSURE_SUCCESS(rv, false); |
| 110 | + NS_ENSURE_SUCCESS(rv, nsIPermissionManager::UNKNOWN_ACTION); |
110 | 111 |
|
111 | 112 | return permission;
|
112 | 113 | }
|
@@ -331,20 +332,216 @@ ImageExtraction ImageExtractionResult(dom::HTMLCanvasElement* aCanvasElement,
|
331 | 332 | return ImageExtraction::Unrestricted;
|
332 | 333 | }
|
333 | 334 |
|
| 335 | +/* |
| 336 | +┌──────────────────────────────────────────────────────────────────────────┐ |
| 337 | +│IsImageExtractionAllowed(dom::OffscreenCanvas*, JSContext*, nsIPrincipal&)│ |
| 338 | +└────────────────────────────────────┬─────────────────────────────────────┘ |
| 339 | + │ |
| 340 | + ┌─────────────────▼────────────────────┐ |
| 341 | + ┌─────No──────────│Any prompt RFP target enabled? See [1]│ |
| 342 | + ▼ └─────────────────┬────────────────────┘ |
| 343 | + │ │Yes |
| 344 | + │ ┌─────────────────▼────────┐ |
| 345 | + ├─────Yes─────────┤Is unrestricted principal?│ |
| 346 | + ▼ └─────────────────┬────────┘ |
| 347 | + │ │No |
| 348 | + │ ┌─────────────────▼────────┐ |
| 349 | + │ ┌──No──┤Are third parties blocked?│ |
| 350 | + │ │ └─────────────────┬────────┘ |
| 351 | + │ │ │Yes |
| 352 | + │ │ ┌─────────────────▼─────────────┐ |
| 353 | + │ │ │Are we in a third-party window?├───────Yes──────────┐ |
| 354 | + │ │ └─────────────────┬─────────────┘ ▼ |
| 355 | + │ │ │No │ |
| 356 | + │ │ ┌─────────────────▼──┐ │ |
| 357 | + │ └──────►Do we show a prompt?├────────────Yes─┐ │ |
| 358 | + │ └─────────────────┬──┘ ▼ │ |
| 359 | + │ │No │ │ |
| 360 | + │ ┌─────────────────▼─────────────┐ │ │ |
| 361 | + │ │Do we allow reading canvas data│ │ │ |
| 362 | + │ │in response to user input? ├─No──┤ │ |
| 363 | + │ └─────────────────┬─────────────┘ ▼ │ |
| 364 | + │ │Yes │ │ |
| 365 | + │ ┌─────────────────▼─────────┐ │ │ |
| 366 | + ├─────Yes─────────┼Are we handling user input?│ │ │ |
| 367 | + ▼ └─────────────────┬─────────┘ │ │ |
| 368 | + │ │No │ │ |
| 369 | + │ ┌─────────────────▼─────────────┐ │ │ |
| 370 | +┌▼─────┐ │Show Permission Prompt (either ◄─────┘ ┌───▼──┐ |
| 371 | +│return│ │w/ doorhanger, or w/o depending│ │return│ |
| 372 | +│true │ │on User Input) ├────────────────►false │ |
| 373 | +└──────┘ └───────────────────────────────┘ └──────┘ |
| 374 | +[1]: CanvasImageExtractionPrompt, CanvasExtractionBeforeUserInputIsBlocked, |
| 375 | + CanvasExtractionFromThirdPartiesIsBlocked are the RFP targets mentioned. |
| 376 | + */ |
| 377 | +bool IsImageExtractionAllowed(dom::OffscreenCanvas* aOffscreenCanvas, |
| 378 | + JSContext* aCx, nsIPrincipal& aPrincipal) { |
| 379 | + if (!aOffscreenCanvas) { |
| 380 | + return false; |
| 381 | + } |
| 382 | + |
| 383 | + bool canvasImageExtractionPrompt = |
| 384 | + aOffscreenCanvas->ShouldResistFingerprinting( |
| 385 | + RFPTarget::CanvasImageExtractionPrompt); |
| 386 | + bool canvasExtractionBeforeUserInputIsBlocked = |
| 387 | + aOffscreenCanvas->ShouldResistFingerprinting( |
| 388 | + RFPTarget::CanvasExtractionBeforeUserInputIsBlocked); |
| 389 | + bool canvasExtractionFromThirdPartiesIsBlocked = |
| 390 | + aOffscreenCanvas->ShouldResistFingerprinting( |
| 391 | + RFPTarget::CanvasExtractionFromThirdPartiesIsBlocked); |
| 392 | + |
| 393 | + if (!canvasImageExtractionPrompt && |
| 394 | + !canvasExtractionBeforeUserInputIsBlocked && |
| 395 | + !canvasExtractionFromThirdPartiesIsBlocked) { |
| 396 | + return true; |
| 397 | + } |
| 398 | + |
| 399 | + // Don't proceed if we don't have a document or JavaScript context. |
| 400 | + if (!aCx) { |
| 401 | + return false; |
| 402 | + } |
| 403 | + |
| 404 | + if (IsUnrestrictedPrincipal(aPrincipal)) { |
| 405 | + return true; |
| 406 | + } |
| 407 | + |
| 408 | + // Define some lambdas to help with the logic |
| 409 | + |
| 410 | + Maybe<uint64_t> winId = Nothing(); |
| 411 | + RefPtr<dom::WindowContext> win = nullptr; |
| 412 | + // Tries to get window Id and window. We need window |
| 413 | + // multiple times or zero times depending on the conditions. |
| 414 | + // So, we "cache" the window Id and window. |
| 415 | + auto tryWin = [&]() { |
| 416 | + if (win) { |
| 417 | + return win; |
| 418 | + } |
| 419 | + |
| 420 | + if (winId.isNothing()) { |
| 421 | + winId = aOffscreenCanvas->GetWindowID(); |
| 422 | + if (winId.isNothing()) { |
| 423 | + // Do not try again if we failed to get the window ID |
| 424 | + winId = Some(UINT64_MAX); |
| 425 | + } |
| 426 | + } else { |
| 427 | + // Workers created outside of a window will return UINT64_MAX |
| 428 | + if (*winId == UINT64_MAX) { |
| 429 | + return win; |
| 430 | + } |
| 431 | + } |
| 432 | + |
| 433 | + win = dom::WindowGlobalParent::GetById(*winId); |
| 434 | + return win; |
| 435 | + }; |
| 436 | + |
| 437 | + Maybe<nsAutoCString> origin = Nothing(); |
| 438 | + auto tryOrigin = [&]() { |
| 439 | + if (origin.isSome()) { |
| 440 | + return !origin->IsEmpty(); |
| 441 | + } |
| 442 | + |
| 443 | + nsAutoCString originResult; |
| 444 | + nsresult rv = aPrincipal.GetOrigin(originResult); |
| 445 | + origin = NS_FAILED(rv) ? Some(""_ns) : Some(originResult); |
| 446 | + return NS_SUCCEEDED(rv); |
| 447 | + }; |
| 448 | + |
| 449 | + auto reportToConsole = [&](nsLiteralCString reason) { |
| 450 | + if (!tryWin()) { |
| 451 | + return; |
| 452 | + } |
| 453 | + |
| 454 | + nsAutoString message; |
| 455 | + message.Append(u"Blocked "); |
| 456 | + if (tryOrigin()) { |
| 457 | + message.AppendPrintf("%s's ", origin->get()); |
| 458 | + } |
| 459 | + message.AppendPrintf("offscreen canvas from extracting canvas data %s.", |
| 460 | + reason.get()); |
| 461 | + nsContentUtils::ReportToConsoleByWindowID( |
| 462 | + message, nsIScriptError::warningFlag, "Security"_ns, *winId); |
| 463 | + }; |
| 464 | + |
| 465 | + // Logic continues from here |
| 466 | + |
| 467 | + if (canvasExtractionFromThirdPartiesIsBlocked) { |
| 468 | + if (!tryWin() || win->GetIsThirdPartyWindow()) { |
| 469 | + reportToConsole( |
| 470 | + "because third party window tried to extract canvas data"_ns); |
| 471 | + return false; |
| 472 | + } |
| 473 | + } |
| 474 | + |
| 475 | + if (!canvasImageExtractionPrompt && |
| 476 | + !canvasExtractionBeforeUserInputIsBlocked) { |
| 477 | + return true; |
| 478 | + } |
| 479 | + |
| 480 | + // ------------------------------------------------------------------- |
| 481 | + // At this point, there's only one way to return true: if we are always |
| 482 | + // allowing canvas in response to user input, and not prompting |
| 483 | + bool hidePermissionDoorhanger = false; |
| 484 | + if (!canvasImageExtractionPrompt && |
| 485 | + canvasExtractionBeforeUserInputIsBlocked) { |
| 486 | + // If so, see if this is in response to user input. |
| 487 | + if (dom::UserActivation::IsHandlingUserInput()) { |
| 488 | + return true; |
| 489 | + } |
| 490 | + |
| 491 | + hidePermissionDoorhanger = true; |
| 492 | + } |
| 493 | + |
| 494 | + // ------------------------------------------------------------------- |
| 495 | + // Now we know we're going to block it, and log something to the console, |
| 496 | + // and show some sort of prompt maybe with the doorhanger, maybe not |
| 497 | + |
| 498 | + hidePermissionDoorhanger |= canvasExtractionBeforeUserInputIsBlocked && |
| 499 | + !dom::UserActivation::IsHandlingUserInput(); |
| 500 | + |
| 501 | + reportToConsole(hidePermissionDoorhanger |
| 502 | + ? "because no user input was detected"_ns |
| 503 | + : "but prompting the user."_ns); |
| 504 | + |
| 505 | + // Show the prompt to the user (asynchronous) - maybe with the doorhanger, |
| 506 | + // maybe not |
| 507 | + if (!tryOrigin() || !tryWin()) { |
| 508 | + return false; |
| 509 | + } |
| 510 | + |
| 511 | + NS_DispatchToMainThread( |
| 512 | + NS_NewRunnableFunction("IsImageExtractionAllowedOffscreen", [=]() { |
| 513 | + if (XRE_IsContentProcess()) { |
| 514 | + dom::BrowserChild* browserChild = |
| 515 | + dom::BrowserChild::GetFrom(win->GetExtantDoc()->GetWindow()); |
| 516 | + |
| 517 | + if (browserChild) { |
| 518 | + browserChild->SendShowCanvasPermissionPrompt( |
| 519 | + *origin, hidePermissionDoorhanger); |
| 520 | + } |
| 521 | + } else { |
| 522 | + nsCOMPtr<nsIObserverService> obs = |
| 523 | + mozilla::services::GetObserverService(); |
| 524 | + if (obs) { |
| 525 | + obs->NotifyObservers( |
| 526 | + win, |
| 527 | + hidePermissionDoorhanger |
| 528 | + ? TOPIC_CANVAS_PERMISSIONS_PROMPT_HIDE_DOORHANGER |
| 529 | + : TOPIC_CANVAS_PERMISSIONS_PROMPT, |
| 530 | + NS_ConvertUTF8toUTF16(*origin).get()); |
| 531 | + } |
| 532 | + } |
| 533 | + })); |
| 534 | + return false; |
| 535 | +} |
| 536 | + |
334 | 537 | ImageExtraction ImageExtractionResult(dom::OffscreenCanvas* aOffscreenCanvas,
|
335 | 538 | JSContext* aCx,
|
336 | 539 | nsIPrincipal& aPrincipal) {
|
337 | 540 | if (IsUnrestrictedPrincipal(aPrincipal)) {
|
338 | 541 | return ImageExtraction::Unrestricted;
|
339 | 542 | }
|
340 | 543 |
|
341 |
| - if (aOffscreenCanvas->ShouldResistFingerprinting( |
342 |
| - RFPTarget::CanvasImageExtractionPrompt)) { |
343 |
| - if (GetCanvasExtractDataPermission(aPrincipal) == |
344 |
| - nsIPermissionManager::ALLOW_ACTION) { |
345 |
| - return ImageExtraction::Unrestricted; |
346 |
| - } |
347 |
| - |
| 544 | + if (!IsImageExtractionAllowed(aOffscreenCanvas, aCx, aPrincipal)) { |
348 | 545 | return ImageExtraction::Placeholder;
|
349 | 546 | }
|
350 | 547 |
|
|
0 commit comments