CVE-2026-78908
Overview
Background
- HTML-in-Canvas (`[HiC]`)
- An experimental Blink feature in which a
<canvas layoutsubtree>lays out its DOM children and lets script rasterize them into the canvas withdrawElementImage(). The canvas pixels can then be read back withgetImageData(), as the new.tentativeWPTs do. - `IsInCanvasSubtree()`
- A per-
Elementflag set for content under such a canvas. Paint uses it to apply privacy protections: in the new tests, cross-origin images and:visitedlink colors are not drawn for flagged content. - Pseudo elements
PseudoElementobjects (::before,::after,::marker,::first-letter,::details-content, etc.) are stored in the originating element’sNodeRareData(GetPseudoElements()), not as ordinary DOM children, soElementTraversal::ChildrenOf()does not visit them.- Paint invalidation
- When the flag changes,
DidChangeIsInCanvasSubtree()must make sure cached display items are repainted, or stale unprotected paint output may be reused.
Root Cause Analysis
The flag-propagation routine in element.cc (the hunk shows the value == IsInCanvasSubtree() early-out and the recursive child.SetIsInCanvasSubtree(value), i.e. Element::SetIsInCanvasSubtree) walked shadow roots, slot-assigned nodes, frame-owner documents and DOM children, but never the element’s pseudo elements.
As a result, ::before/::after/::marker and similar pseudos of an element inside a layoutsubtree canvas kept IsInCanvasSubtree() == false. They were painted without the canvas privacy filtering, so cross-origin content: url(...) images, :visited-dependent currentColor, and similar private content were rasterized by drawElementImage() and became readable through getImageData().
The fix adds a loop over RareData()->GetPseudoElements() that calls SetIsInCanvasSubtree(value) on each pseudo. It also changes DidChangeIsInCanvasSubtree() to use SlowSetPaintingLayerNeedsRepaintAndInvalidateDisplayItemClient(), so the enclosing painting layer is repainted even when the object has no layer of its own, which covers the “moved” tests that use moveBefore(). The DCHECK-only verifier was converted to a member and extended to check pseudos too.
IsInCanvasSubtree to every PseudoElement, and forcing a repaint of the painting layer when it changes, closes the gap.Attack Path
- Canvas with layout subtree
The attacker page places an element under
<canvas layoutsubtree>. This requires the HTML-in-Canvas feature to be enabled; its tests are.tentative, so it is likely not enabled by default for ordinary web content (inferred). - Private content via pseudo elements
The element’s
::before/::after/::marker/::first-lineand similar pseudos display a cross-origin image (content: url(...), background images, or content in a cross-origin iframe) or a:visited-dependentcurrentColor. The element can be placed there directly or moved in withmoveBefore(). - Unprotected paint
Before the fix, those pseudos lacked
IsInCanvasSubtree, so paint drew the real cross-origin pixels and the visited color into the canvas. - Read back
Script calls
drawElementImage()and thengetImageData()to read the pixels. This leaks cross-origin image content or browsing-history (visited-link) state, as the new tests’ assertions encode.
Impact Assessment
:visited privacy protections, as the advisory says (“Information leak in Canvas”). No memory corruption is involved, and the patch shows no escalation beyond disclosure. Reachability depends on the HTML-in-Canvas feature being enabled. The vendor advisory rates this high severity and does not report in-the-wild exploitation; NVD scores it CVSS 4.3.Changed Functions
| Function | Change | Notes |
|---|---|---|
promise_testthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-after-images-ignored.tentative.https.sub.html |
modified | |
promise_testthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-after-images-moved-ignored.tentative.https.sub.html |
modified | |
promise_testthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-after-visited-color-ignored.tentative.html |
modified | |
promise_testthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-background-image-iframe-ignored.tentative.https.sub.html |
modified |
Files Changed
third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/details-content-visited-link-ignored.tentative.htmlthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-background-image-iframe-moved-ignored.tentative.https.sub.htmlthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/visited-link/visited-link-svg-use-outside-subtree-ignored.tentative.htmlthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/first-line-visited-link-color-moved-ignored.tentative.htmlthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/first-line-visited-link-currentcolor-ignored.tentative.htmlthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-after-images-moved-ignored.tentative.https.sub.htmlthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/first-letter-background-images-ignored.tentative.https.sub.htmlthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/first-line-visited-link-color-ignored.tentative.htmlthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-after-images-ignored.tentative.https.sub.htmlthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/details-content-after-image-ignored.tentative.https.sub.htmlthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-images-iframe-ignored.tentative.https.sub.htmlthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/visited-link/visited-link-slotted-color-ignored.tentative.htmlthird_party/blink/renderer/core/paint/object_paint_invalidator.ccthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/support/subframe-cross-origin-before-content-on-body.https.sub.htmlthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/marker-images-ignored.tentative.https.sub.htmlthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-images-iframe-moved-ignored.tentative.https.sub.htmlthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/first-line-background-images-ignored.tentative.https.sub.htmlthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/first-line-background-images-moved-ignored.tentative.https.sub.htmlthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-after-visited-color-ignored.tentative.htmlthird_party/blink/renderer/core/dom/element.hthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/support/subframe-cross-origin-before-background-on-body-modified.https.sub.htmlthird_party/blink/renderer/core/dom/element.ccthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/first-letter-visited-link-currentcolor-ignored.tentative.htmlthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/details-content-background-image-ignored.tentative.https.sub.htmlthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-background-image-iframe-ignored.tentative.https.sub.htmlthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/marker-visited-link-currentcolor-ignored.tentative.htmlthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/support/subframe-cross-origin-before-background-on-body.https.sub.htmlthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/details-content-visited-link-currentcolor-ignored.tentative.htmlthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/first-letter-visited-link-color-ignored.tentative.htmlthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-background-image-iframe-modified-ignored.tentative.https.sub.html
Audit Directions
- Tree walks that skip pseudo elementsAudit other flag-propagation and invalidation walks (inertness,
content-visibility, privacy and taint flags) that useElementTraversal::ChildrenOf()and may missPseudoElements, including nested pseudos like::details-content::after. - Remaining pseudo types and rendering pathsThe commit says more pseudos still need testing. Check
::first-linestyle inheritance,::marker,::backdrop,::selection,::placeholder, view-transition pseudos, and SVG<use>shadow content underdrawElementImage(). - Flag changes without full repaintLook for privacy-relevant paint flags whose change handlers only invalidate the object’s own layer. Moving content in or out of a protected subtree (
moveBefore(), slotting, iframe changes) can leave cached unprotected display items behind.
Patch
From 584ed97cc8158833992d052aa92b0cd0b9eff8f5 Mon Sep 17 00:00:00 2001 From: Stephen Chenney <[email protected]> Date: Tue, 18 Aug 2026 16:21:41 -0700 Subject: [PATCH] [HiC] Privacy protection for pseudo elements Protect private content in pseudo elements by setting IsInCanvasSubtree for all pseudo elements. Add tests to cover the following: ::first-line ::first-letter ::before and ::after ::marker ::details-content ::details-content::after (to test nested pseudos) There are still more to test but land this first and then add more, to get the protection in sooner. Testing everything will require several more days. Fixed: 532904047 Change-Id: I859b78904b7ac8980c548db593274c009c16fdb2 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8253249 Reviewed-by: Stefan Zager <[email protected]> Commit-Queue: Stephen Chenney <[email protected]> Cr-Commit-Position: refs/heads/main@{#1681817} --- diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc index bf3a13f..5f721f1 100644 --- a/third_party/blink/renderer/core/dom/element.cc +++ b/third_party/blink/renderer/core/dom/element.cc @@ -4354,39 +4354,45 @@ } #if DCHECK_IS_ON() -void VerifySubtreeIsInCanvas(const Element& element, bool value) { - DCHECK(element.IsInCanvasSubtree() == value); - if (IsA<HTMLCanvasElement>(element)) { - DCHECK(element.IsCanvasOrInCanvasSubtree()); +void Element::VerifySubtreeIsInCanvas(bool value) { + DCHECK(IsInCanvasSubtree() == value); + if (IsA<HTMLCanvasElement>(this)) { + DCHECK(IsCanvasOrInCanvasSubtree()); // When the verifier starts with an element outside the tree that should // have value false, but then reaches a canvas within the subtree (e.g. // in an iframe or nested), we should set the expected value back to true. value = true; } - if (ShadowRoot* shadow_root = element.GetShadowRoot()) { + if (ShadowRoot* shadow_root = GetShadowRoot()) { for (Element& child : ElementTraversal::ChildrenOf(*shadow_root)) { - VerifySubtreeIsInCanvas(child, value); + child.VerifySubtreeIsInCanvas(value); } } - if (auto* slot = ToHTMLSlotElementIfSupportsAssignmentOrNull(element)) { + if (auto* slot = ToHTMLSlotElementIfSupportsAssignmentOrNull(*this)) { for (Node* node : slot->AssignedNodesNoRecalc()) { if (auto* child = DynamicTo<Element>(node)) { - VerifySubtreeIsInCanvas(*child, value); + child->VerifySubtreeIsInCanvas(value); } } } - if (const auto* frame_owner = DynamicTo<HTMLFrameOwnerElement>(element)) { + if (const auto* frame_owner = DynamicTo<HTMLFrameOwnerElement>(this)) { if (Document* inner_document = frame_owner->contentDocument()) { if (Element* root = inner_document->documentElement()) { - VerifySubtreeIsInCanvas(*root, value); + root->VerifySubtreeIsInCanvas(value); } } } - for (Element& child : ElementTraversal::ChildrenOf(element)) { + if (const NodeRareData* rare_data = RareData()) { + for (PseudoElement* pseudo_element : rare_data->GetPseudoElements()) { + pseudo_element->VerifySubtreeIsInCanvas(value); + } + } + + for (Element& child : ElementTraversal::ChildrenOf(*this)) { if (child.AssignedSlotWithoutRecalc()) { continue; } - VerifySubtreeIsInCanvas(child, value); + child.VerifySubtreeIsInCanvas(value); } } #endif @@ -4395,7 +4401,7 @@ if (value == IsInCanvasSubtree()) { #if DCHECK_IS_ON() if (!GetDocument().IsSlotAssignmentRecalcForbidden()) { - VerifySubtreeIsInCanvas(*this, value); + VerifySubtreeIsInCanvas(value); } #endif return; @@ -4427,6 +4433,11 @@ } child.SetIsInCanvasSubtree(value); } + if (const NodeRareData* rare_data = RareData()) { + for (PseudoElement* pseudo_element : rare_data->GetPseudoElements()) { + pseudo_element->SetIsInCanvasSubtree(value); + } + } } bool Element::ComputeIsInCanvasSubtree() const { @@ -4469,12 +4480,9 @@ void Element::DidChangeIsInCanvasSubtree() { if (auto* layout_object = GetLayoutObject()) { layout_object->SetNeedsPaintPropertyUpdate(); - if (layout_object->HasLayer()) { - To<LayoutBoxModelObject>(layout_object)->Layer()->SetNeedsRepaint(); - } ObjectPaintInvalidator(*layout_object) - .InvalidateDisplayItemClient(*layout_object, - PaintInvalidationReason::kUncacheable); + .SlowSetPaintingLayerNeedsRepaintAndInvalidateDisplayItemClient( + *layout_object, PaintInvalidationReason::kUncacheable); } } diff --git a/third_party/blink/renderer/core/dom/element.h b/third_party/blink/renderer/core/dom/element.h index f7353a1..ec6c85e3 100644 --- a/third_party/blink/renderer/core/dom/element.h +++ b/third_party/blink/renderer/core/dom/element.h @@ -1174,6 +1174,10 @@ bool IsCanvasOrInCanvasSubtree() const; // Called when `IsInCanvasSubtree()` changes. virtual void DidChangeIsInCanvasSubtree(); +#if DCHECK_IS_ON() + void VerifySubtreeIsInCanvas(bool value); +#endif + HTMLCanvasElement* CanvasForDrawing() const; DOMMatrix* getCanvasTransform(); diff --git a/third_party/blink/renderer/core/paint/object_paint_invalidator.cc b/third_party/blink/renderer/core/paint/object_paint_invalidator.cc index 57cbac0..f3d25357 100644 --- a/third_party/blink/renderer/core/paint/object_paint_invalidator.cc +++ b/third_party/blink/renderer/core/paint/object_paint_invalidator.cc @@ -34,7 +34,7 @@ // It's caller's responsibility to ensure PaintingLayer's NeedsRepaint is // set. Don't set the flag here because getting PaintLayer has cost and the // caller can use various ways (e.g. - // PaintInvalidatinContext::painting_layer) to reduce the cost. + // PaintInvalidationContext::painting_layer) to reduce the cost. CheckPaintLayerNeedsRepaint(); #endif TRACE_EVENT_INSTANT2(TRACE_DISABLED_BY_DEFAULT("blink.invalidation"), diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-after-images-ignored.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-after-images-ignored.tentative.https.sub.html new file mode 100644 index 0000000..89b2191 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-after-images-ignored.tentative.https.sub.html @@ -0,0 +1,56 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>drawElementImage does not draw cross-origin images for before/after</title> + <script src='/resources/testharness.js'></script> + <script src='/resources/testharnessreport.js'></script> + <script src='/html/canvas/resources/canvas-tests.js'></script> + <script src="/html/canvas/resources/wait-for-canvas-paint.js"></script> + <style> + .wrapper { + width: 400px; + height: 120px; + } + .base::before { + content: url("https://{{hosts[alt][www]}}:{{ports[https][0]}}/images/red-100x100.png"); + width: 100px; + height: 100px; + background-color: green; + display: inline-block; + } + .base::after { + content: url("https://{{hosts[alt][www]}}:{{ports[https][0]}}/images/red-100x100.png"); + width: 100px; + height: 100px; + background-color: green; + display: inline-block; + } + </style> +</head> +<body> + <canvas id=canvas width="100" height="200" layoutsubtree> + <div id=child class=wrapper> + <p class=base>Before/After</p> + </div> + </canvas> + + <script> + window.onload = () => { + promise_test(async function(t) { + await waitForCanvasPaint(canvas); + const context = canvas.getContext("2d"); + context.drawElementImage(child, 0, 0); + + // Fetch all pixel data once to avoid multiple slow readbacks. + const imgData = context.getImageData(0, 0, canvas.width, canvas.height); + + pixel = _getPixelFromImageData(imgData, 50, 50); + assert_array_equals(pixel, [0, 128, 0, 255], "Cross origin ::before should not draw"); + + pixel = _getPixelFromImageData(imgData, 250, 50); + assert_array_equals(pixel, [0, 128, 0, 255], "Cross origin ::after should not draw"); + }); + }; + </script> +</body> +</html> diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-after-images-moved-ignored.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-after-images-moved-ignored.tentative.https.sub.html new file mode 100644 index 0000000..0eb6f3b --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-after-images-moved-ignored.tentative.https.sub.html @@ -0,0 +1,60 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>drawElementImage does not draw cross-origin images for before/after</title> + <script src='/resources/testharness.js'></script> + <script src='/resources/testharnessreport.js'></script> + <script src='/html/canvas/resources/canvas-tests.js'></script> + <script src="/html/canvas/resources/wait-for-canvas-paint.js"></script> + <style> + .wrapper { + width: 400px; + height: 120px; + } + .base::before { + content: url("https://{{hosts[alt][www]}}:{{ports[https][0]}}/images/red-100x100.png"); + width: 100px; + height: 100px; + background-color: green; + display: inline-block; + } + .base::after { + content: url("https://{{hosts[alt][www]}}:{{ports[https][0]}}/images/red-100x100.png"); + width: 100px; + height: 100px; + background-color: green; + display: inline-block; + } + </style> +</head> +<body> + <canvas id=canvas width="100" height="200" layoutsubtree> + <div id=child class=wrapper> + </div> + </canvas> + <p id="text" class=base>Before/After</p> + + <script> + window.onload = () => { + promise_test(async function(t) { + await waitForCanvasPaint(canvas); + + child.moveBefore(text, null); + + await waitForCanvasPaint(canvas); + const context = canvas.getContext("2d"); + context.drawElementImage(child, 0, 0); + + // Fetch all pixel data once to avoid multiple slow readbacks. + const imgData = context.getImageData(0, 0, canvas.width, canvas.height); + + pixel = _getPixelFromImageData(imgData, 50, 50); + assert_array_equals(pixel, [0, 128, 0, 255], "Cross origin ::before should not draw"); + + pixel = _getPixelFromImageData(imgData, 250, 50); + assert_array_equals(pixel, [0, 128, 0, 255], "Cross origin ::after should not draw"); + }); + }; + </script> +</body> +</html> diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-after-visited-color-ignored.tentative.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-after-visited-color-ignored.tentative.html new file mode 100644 index 0000000..977142ce --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-after-visited-color-ignored.tentative.html @@ -0,0 +1,64 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>drawElementImage does not draw visited colors for before/after</title> + <script src='/resources/testharness.js'></script> + <script src='/resources/testharnessreport.js'></script> + <script src='/html/canvas/resources/canvas-tests.js'></script> + <script src="/html/canvas/resources/wait-for-canvas-paint.js"></script> + <style> + a { + color: green; + } + a:visited { + color: red; + } + .wrapper { + width: 400px; + height: 120px; + } + .base::before { + content: ""; + width: 100px; + height: 100px; + background-color: currentColor; + display: inline-block; + } + .base::after { + content: ""; + width: 100px; + height: 100px; + background-color: currentColor; + display: inline-block; + } + </style> +</head> +<body> + <a href=""> + <canvas id=canvas width="100" height="200" layoutsubtree> + <div id=child class=wrapper> + <p class=base>Before/After</p> + </div> + </canvas> + </a> + + <script> + window.onload = () => { + promise_test(async function(t) { + await waitForCanvasPaint(canvas); + const context = canvas.getContext("2d"); + context.drawElementImage(child, 0, 0); + + // Fetch all pixel data once to avoid multiple slow readbacks. + const imgData = context.getImageData(0, 0, canvas.width, canvas.height); + + pixel = _getPixelFromImageData(imgData, 50, 50); + assert_array_equals(pixel, [0, 128, 0, 255], "Visited color in ::before should not draw"); + + pixel = _getPixelFromImageData(imgData, 250, 50); + assert_array_equals(pixel, [0, 128, 0, 255], "Visited color in ::after should not draw"); + }); + }; + </script> +</body> +</html> diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-background-image-iframe-ignored.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-background-image-iframe-ignored.tentative.https.sub.html new file mode 100644 index 0000000..2f236942 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-background-image-iframe-ignored.tentative.https.sub.html @@ -0,0 +1,39 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>drawElementImage does not draw cross-origin background images for before/after</title> + <script src='/resources/testharness.js'></script> + <script src='/resources/testharnessreport.js'></script> + <script src='/html/canvas/resources/canvas-tests.js'></script> + <script src="/html/canvas/resources/wait-for-canvas-paint.js"></script> + <style> + iframe { + width: 100px; + height: 200px; + background-color: blue; + } + </style> +</head> +<body> + <canvas id=canvas width="100" height="200" layoutsubtree> + <div id=child> + <iframe id=iframe src="../support/subframe-cross-origin-before-background-on-body.https.sub.html"></iframe> + </div> + </canvas> + + <script> + iframe.onload = () => { + promise_test(async function(t) { + await waitForCanvasPaint(canvas); + const context = canvas.getContext("2d"); + context.drawElementImage(child, 0, 0); + + let pixel = context.getImageData(20, 20, 1, 1).data; + assert_false(pixel[0] > 0, "The canvas should not paint cross origin content."); + assert_true(pixel[1] > 0, "The canvas should not paint cross origin content."); + assert_false(pixel[2] > 0, "The canvas should not paint cross origin content."); + }); + }; + </script> +</body> +</html> diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-background-image-iframe-modified-ignored.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-background-image-iframe-modified-ignored.tentative.https.sub.html new file mode 100644 index 0000000..4f70984 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-background-image-iframe-modified-ignored.tentative.https.sub.html @@ -0,0 +1,50 @@ ... (file diff truncated)
Regression Test / PoC
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-after-images-ignored.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-after-images-ignored.tentative.https.sub.html
new file mode 100644
index 0000000..89b2191
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-after-images-ignored.tentative.https.sub.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>drawElementImage does not draw cross-origin images for before/after</title>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ <script src='/html/canvas/resources/canvas-tests.js'></script>
+ <script src="/html/canvas/resources/wait-for-canvas-paint.js"></script>
+ <style>
+ .wrapper {
+ width: 400px;
+ height: 120px;
+ }
+ .base::before {
+ content: url("https://{{hosts[alt][www]}}:{{ports[https][0]}}/images/red-100x100.png");
+ width: 100px;
+ height: 100px;
+ background-color: green;
+ display: inline-block;
+ }
+ .base::after {
+ content: url("https://{{hosts[alt][www]}}:{{ports[https][0]}}/images/red-100x100.png");
+ width: 100px;
+ height: 100px;
+ background-color: green;
+ display: inline-block;
+ }
+ </style>
+</head>
+<body>
+ <canvas id=canvas width="100" height="200" layoutsubtree>
+ <div id=child class=wrapper>
+ <p class=base>Before/After</p>
+ </div>
+ </canvas>
+
+ <script>
+ window.onload = () => {
+ promise_test(async function(t) {
+ await waitForCanvasPaint(canvas);
+ const context = canvas.getContext("2d");
+ context.drawElementImage(child, 0, 0);
+
+ // Fetch all pixel data once to avoid multiple slow readbacks.
+ const imgData = context.getImageData(0, 0, canvas.width, canvas.height);
+
+ pixel = _getPixelFromImageData(imgData, 50, 50);
+ assert_array_equals(pixel, [0, 128, 0, 255], "Cross origin ::before should not draw");
+
+ pixel = _getPixelFromImageData(imgData, 250, 50);
+ assert_array_equals(pixel, [0, 128, 0, 255], "Cross origin ::after should not draw");
+ });
+ };
+ </script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-after-images-moved-ignored.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-after-images-moved-ignored.tentative.https.sub.html
new file mode 100644
index 0000000..0eb6f3b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-after-images-moved-ignored.tentative.https.sub.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>drawElementImage does not draw cross-origin images for before/after</title>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ <script src='/html/canvas/resources/canvas-tests.js'></script>
+ <script src="/html/canvas/resources/wait-for-canvas-paint.js"></script>
+ <style>
+ .wrapper {
+ width: 400px;
+ height: 120px;
+ }
+ .base::before {
+ content: url("https://{{hosts[alt][www]}}:{{ports[https][0]}}/images/red-100x100.png");
+ width: 100px;
+ height: 100px;
+ background-color: green;
+ display: inline-block;
+ }
+ .base::after {
+ content: url("https://{{hosts[alt][www]}}:{{ports[https][0]}}/images/red-100x100.png");
+ width: 100px;
+ height: 100px;
+ background-color: green;
+ display: inline-block;
+ }
+ </style>
+</head>
+<body>
+ <canvas id=canvas width="100" height="200" layoutsubtree>
+ <div id=child class=wrapper>
+ </div>
+ </canvas>
+ <p id="text" class=base>Before/After</p>
+
+ <script>
+ window.onload = () => {
+ promise_test(async function(t) {
+ await waitForCanvasPaint(canvas);
+
+ child.moveBefore(text, null);
+
+ await waitForCanvasPaint(canvas);
+ const context = canvas.getContext("2d");
+ context.drawElementImage(child, 0, 0);
+
+ // Fetch all pixel data once to avoid multiple slow readbacks.
+ const imgData = context.getImageData(0, 0, canvas.width, canvas.height);
+
+ pixel = _getPixelFromImageData(imgData, 50, 50);
+ assert_array_equals(pixel, [0, 128, 0, 255], "Cross origin ::before should not draw");
+
+ pixel = _getPixelFromImageData(imgData, 250, 50);
+ assert_array_equals(pixel, [0, 128, 0, 255], "Cross origin ::after should not draw");
+ });
+ };
+ </script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-after-visited-color-ignored.tentative.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-after-visited-color-ignored.tentative.html
new file mode 100644
index 0000000..977142ce
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-after-visited-color-ignored.tentative.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>drawElementImage does not draw visited colors for before/after</title>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ <script src='/html/canvas/resources/canvas-tests.js'></script>
+ <script src="/html/canvas/resources/wait-for-canvas-paint.js"></script>
+ <style>
+ a {
+ color: green;
+ }
+ a:visited {
+ color: red;
+ }
+ .wrapper {
+ width: 400px;
+ height: 120px;
+ }
+ .base::before {
+ content: "";
+ width: 100px;
+ height: 100px;
+ background-color: currentColor;
+ display: inline-block;
+ }
+ .base::after {
+ content: "";
+ width: 100px;
+ height: 100px;
+ background-color: currentColor;
+ display: inline-block;
+ }
+ </style>
+</head>
+<body>
+ <a href="">
+ <canvas id=canvas width="100" height="200" layoutsubtree>
+ <div id=child class=wrapper>
+ <p class=base>Before/After</p>
+ </div>
+ </canvas>
+ </a>
+
+ <script>
+ window.onload = () => {
+ promise_test(async function(t) {
+ await waitForCanvasPaint(canvas);
+ const context = canvas.getContext("2d");
+ context.drawElementImage(child, 0, 0);
+
+ // Fetch all pixel data once to avoid multiple slow readbacks.
+ const imgData = context.getImageData(0, 0, canvas.width, canvas.height);
+
+ pixel = _getPixelFromImageData(imgData, 50, 50);
+ assert_array_equals(pixel, [0, 128, 0, 255], "Visited color in ::before should not draw");
+
+ pixel = _getPixelFromImageData(imgData, 250, 50);
+ assert_array_equals(pixel, [0, 128, 0, 255], "Visited color in ::after should not draw");
+ });
+ };
+ </script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-background-image-iframe-ignored.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-background-image-iframe-ignored.tentative.https.sub.html
new file mode 100644
index 0000000..2f236942
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-background-image-iframe-ignored.tentative.https.sub.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>drawElementImage does not draw cross-origin background images for before/after</title>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ <script src='/html/canvas/resources/canvas-tests.js'></script>
+ <script src="/html/canvas/resources/wait-for-canvas-paint.js"></script>
+ <style>
+ iframe {
+ width: 100px;
+ height: 200px;
+ background-color: blue;
+ }
+ </style>
+</head>
+<body>
+ <canvas id=canvas width="100" height="200" layoutsubtree>
+ <div id=child>
+ <iframe id=iframe src="../support/subframe-cross-origin-before-background-on-body.https.sub.html"></iframe>
+ </div>
+ </canvas>
+
+ <script>
+ iframe.onload = () => {
+ promise_test(async function(t) {
+ await waitForCanvasPaint(canvas);
+ const context = canvas.getContext("2d");
+ context.drawElementImage(child, 0, 0);
+
+ let pixel = context.getImageData(20, 20, 1, 1).data;
+ assert_false(pixel[0] > 0, "The canvas should not paint cross origin content.");
+ assert_true(pixel[1] > 0, "The canvas should not paint cross origin content.");
+ assert_false(pixel[2] > 0, "The canvas should not paint cross origin content.");
+ });
+ };
+ </script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-background-image-iframe-modified-ignored.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-background-image-iframe-modified-ignored.tentative.https.sub.html
new file mode 100644
index 0000000..4f70984
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-background-image-iframe-modified-ignored.tentative.https.sub.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>drawElementImage does not draw cross-origin background images for before/after when modified</title>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ <script src='/html/canvas/resources/canvas-tests.js'></script>
+ <script src="/html/canvas/resources/wait-for-canvas-paint.js"></script>
+ <style>
+ iframe {
+ width: 100px;
+ height: 200px;
+ background-color: blue;
+ }
+ </style>
+</head>
+<body>
+ <canvas id=canvas width="100" height="200" layoutsubtree>
+ <div id=child>
+ <iframe id=iframe src="../support/subframe-cross-origin-before-background-on-body-modified.https.sub.html"></iframe>
+ </div>
+ </canvas>
+
+ <script>
+ iframe.onload = () => {
+ promise_test(async function(t) {
+ await waitForCanvasPaint(canvas);
+ const context = canvas.getContext("2d");
+ context.drawElementImage(child, 0, 0);
+
+ let pixel = context.getImageData(20, 20, 1, 1).data;
+ assert_false(pixel[0] > 0, "Initial draw: Red should not paint.");
+ assert_false(pixel[1] > 0, "Initial draw: Green should not paint before class is added.");
+ assert_true(pixel[2] > 0, "Initial draw: Blue iframe background should paint.");
+
+ // Add class to iframe body causing it to match the ::before rule.
+ iframe.contentDocument.body.classList.add('has-before');
+
+ await waitForCanvasPaint(canvas);
+ context.drawElementImage(child, 0, 0);
+
+ pixel = context.getImageData(20, 20, 1, 1).data;
+ assert_false(pixel[0] > 0, "The canvas should not paint cross origin content.");
+ assert_true(pixel[1] > 0, "The canvas should paint the ::before background color.");
+ assert_false(pixel[2] > 0, "The canvas should paint the ::before element over the iframe background.");
+ });
+ };
+ </script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-background-image-iframe-moved-ignored.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/pseudos/before-background-image-iframe-moved-ignored.tentative.https.sub.html
... (truncated)