Skip to content
Open
Changes from 13 commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
e181de1
Stub out canvas.drawElement()
foolip Aug 21, 2025
8634ebe
Fix typ: y->h
foolip Aug 28, 2025
afc4a4d
Modernize the style somewhat
foolip Aug 28, 2025
f151c83
Replace handwavy check with real check and drop "update the rendering"
foolip Aug 28, 2025
74d67ae
Add the layoutsubtree attribute
foolip Aug 29, 2025
f7d320a
Rename to drawHTMLElement()
foolip Aug 29, 2025
3cad6d4
Fix the layoutsubtree UA style sheet
foolip Sep 2, 2025
4367aac
Rename to drawElementImage()
foolip Sep 16, 2025
84ec8e6
Define sensitive information
foolip Sep 17, 2025
f351c3d
Expose children to AT
foolip Sep 17, 2025
aed994e
grammar
foolip Sep 17, 2025
8c3ce46
Use containment to make children containing blocks
foolip Sep 17, 2025
ff066c9
Define setHitTestRegions()
foolip Sep 17, 2025
5e3c3d0
Merge remote-tracking branch 'origin/main' into foolip/html-in-canvas
foolip Oct 7, 2025
f8f119a
Wrap algorithms
foolip Oct 7, 2025
202adae
Fix typo and reserved word
foolip Oct 7, 2025
be22310
Add a note about not using CTM
foolip Oct 7, 2025
9866034
Fix reference to hit test regions
foolip Oct 7, 2025
4e559a9
Skip non-positive width/height regions
foolip Oct 7, 2025
4838669
Use CSS border box
foolip Oct 21, 2025
9fa39e9
Replace hit testing API with a simpler model
foolip Nov 11, 2025
f79ed95
Add a hit testing example
foolip Nov 11, 2025
0a243be
Add getElementTransform() and related algorithms
foolip Dec 2, 2025
1f71b9a
Update drawElementImage() to return matrix
foolip Dec 4, 2025
5aeb03b
Express the transformations in a more approachable way
foolip Dec 4, 2025
951bdee
Address progers feedback
foolip Dec 5, 2025
ba5c560
<data> -> <code>
foolip Dec 5, 2025
4d77e94
Use [Reflect] for layoutSubtree
foolip Dec 9, 2025
d033976
Link DOMMatrix
foolip Dec 9, 2025
b953591
Merge remote-tracking branch 'origin/main' into foolip/html-in-canvas
foolip Jan 27, 2026
a852071
Flesh out sensitive information a little bit
foolip Jan 27, 2026
5b5dc3d
Add canvas paint event
foolip Mar 11, 2026
49184b4
Define rendering with an internal shadow tree
foolip Mar 12, 2026
8d0edfc
Update and move example
foolip Mar 12, 2026
0480023
Merge remote-tracking branch 'origin/main' into foolip/html-in-canvas
foolip Mar 13, 2026
ffe1e1e
Replace example with pie chart
foolip Mar 13, 2026
bc57d89
Flesh out domintro and move algorithms
foolip Mar 13, 2026
00b5bf9
Add requestPaint()
foolip Mar 13, 2026
769de4c
better define timing using a snapshots concept
foolip Mar 13, 2026
ab2ee8f
Add OffscreenCanvas support
foolip Mar 13, 2026
b2cd7a3
a->an
foolip Mar 16, 2026
4096cbc
Move layoutsubtree check to when snapshots are created
foolip Mar 16, 2026
3ccf75f
Address @Kaiido feedback (thank you!)
foolip Mar 17, 2026
a27ade2
Move and polish the sensitive information dfn
foolip Mar 17, 2026
f3b7fe0
Overhaul OffscreenCanvas algorithms
foolip Mar 20, 2026
bd7a35c
Make ElementImage transferrable (no paint event in OffscreenCanvas)
foolip Mar 26, 2026
46273d9
Add missing return
foolip Mar 26, 2026
d9c4769
Add explicit steps and notes about painting
foolip Mar 26, 2026
e3965e6
Clarify that the canvas element layout cannot change
foolip Mar 26, 2026
12f02aa
Add source rect variants
foolip Mar 26, 2026
0a158e6
CanvasPaintEvent's changedElements can only be Element
foolip Mar 27, 2026
2df6477
Capture the scaling factor when creating snapshots
foolip Mar 27, 2026
c5e2815
Subpixel rendering is sensitive information
foolip Mar 27, 2026
2850d56
Say that selection and find-in-page highlight colors are sensitive
foolip Mar 27, 2026
f4f738b
Remove ElementImage's id
foolip Mar 31, 2026
09b811d
Add ElementImage close() method
foolip Mar 31, 2026
542723b
Throw on drawing a detached ElementImage
foolip Mar 31, 2026
7aee3ee
Make ElementImage width/height double
foolip Apr 1, 2026
2c95e37
Make non-default fonts sensitive information
foolip Apr 9, 2026
0a476ef
Don't expose platform-specific form appearance
foolip Apr 9, 2026
71347b5
Require snapshots to be scalable (not bitmaps)
foolip Apr 9, 2026
22b725e
Disallow same-origin nesting of canvas elements
foolip Apr 10, 2026
959ca6b
Replace shadow tree bits with simpler rendering rules
foolip Apr 13, 2026
3350248
Remove stray quotes
foolip Apr 13, 2026
cb6c69d
Merge remote-tracking branch 'origin/main' into foolip/html-in-canvas
foolip Apr 24, 2026
ae964a9
Add layout containment
foolip Apr 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
261 changes: 259 additions & 2 deletions source
Original file line number Diff line number Diff line change
Expand Up @@ -65673,6 +65673,7 @@ dictionary <dfn dictionary>AssignedNodesOptions</dfn> {
<dd><span>Global attributes</span></dd>
<dd><code data-x="attr-canvas-width">width</code></dd>
<dd><code data-x="attr-canvas-height">height</code></dd>
<dd><code data-x="attr-canvas-layoutsubtree">layoutsubtree</code></dd>
<dt><span
data-x="concept-element-accessibility-considerations">Accessibility considerations</span>:</dt>
<dd><a href="https://w3c.github.io/html-aria/#el-canvas">For authors</a>.</dd>
Expand Down Expand Up @@ -65775,6 +65776,12 @@ callback <dfn callback>BlobCallback</dfn> = undefined (<span>Blob</span>? blob);
<p>The user agent must use a square pixel density consisting of one pixel of image data per
coordinate space unit for the bitmaps of a <code>canvas</code> and its rendering contexts.</p>

<p>The <dfn element-attr for="canvas"><code
data-x="attr-canvas-layoutsubtree">layoutsubtree</code></dfn> attribute is a <span>boolean
attribute</span>. If present, <span data-x="concept-tree-child">children</span> of the
<data>canvas</data> element are layed out, so that they can be drawn using <code
data-x="dom-context-2d-drawElementImage">drawElementImage()</code>.</p>

<p class="note">A <code>canvas</code> element can be sized arbitrarily by a style sheet, its
bitmap is then subject to the <span>'object-fit'</span> CSS property.</p>

Expand Down Expand Up @@ -66274,6 +66281,8 @@ interface <dfn interface>CanvasRenderingContext2D</dfn> {
<span>CanvasRenderingContext2D</span> includes <span>CanvasDrawPath</span>;
<span>CanvasRenderingContext2D</span> includes <span>CanvasUserInterface</span>;
<span>CanvasRenderingContext2D</span> includes <span>CanvasText</span>;
<span>CanvasRenderingContext2D</span> includes <span>CanvasDrawElementImage</span>;
<span>CanvasRenderingContext2D</span> includes <span>CanvasHitTestRegions</span>;
<span>CanvasRenderingContext2D</span> includes <span>CanvasDrawImage</span>;
<span>CanvasRenderingContext2D</span> includes <span>CanvasImageData</span>;
<span>CanvasRenderingContext2D</span> includes <span>CanvasPathDrawingStyles</span>;
Expand Down Expand Up @@ -66393,6 +66402,29 @@ interface mixin <dfn interface>CanvasText</dfn> {
<span>TextMetrics</span> <span data-x="dom-context-2d-measureText">measureText</span>(DOMString text);
};

interface mixin <dfn interface>CanvasDrawElementImage</dfn> {
// drawing elements
undefined <span data-x="dom-context-2d-drawElementImage">drawElementImage</span>(<span>Element</span> element, unrestricted double x, unrestricted double y);
undefined <span data-x="dom-context-2d-drawElementImage">drawElementImage</span>(<span>Element</span> element, unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h);
};

dictionary <dfn dictionary>CanvasHitTestRect</dfn> {
double <dfn dict-member for="CanvasHitTestRect" data-x="dom-CanvasHitTestRect-x">x</dfn>;
double <dfn dict-member for="CanvasHitTestRect" data-x="dom-CanvasHitTestRect-y">y</dfn>;
double? <dfn dict-member for="CanvasHitTestRect" data-x="dom-CanvasHitTestRect-width">width</dfn>;
double? <dfn dict-member for="CanvasHitTestRect" data-x="dom-CanvasHitTestRect-height">height</dfn>;
};

dictionary <dfn dictionary>CanvasElementHitTestRegion</dfn> {
<span>Element</span> <dfn dict-member for="CanvasElementHitTestRegion" data-x="dom-CanvasElementHitTestRegion-element">element</dfn>;
<span>CanvasHitTestRect</span> <dfn dict-member for="CanvasElementHitTestRegion" data-x="dom-CanvasElementHitTestRegion-rect">rect</dfn>;
};

interface mixin <dfn interface>CanvasHitTestRegions</dfn> {
// hit testing
undefined <span data-x="dom-context-2d-setHitTestRegions">setHitTestRegions</span>(sequence&lt;CanvasElementHitTestRegion> regions);
};

interface mixin <dfn interface>CanvasDrawImage</dfn> {
// drawing images
undefined <span data-x="dom-context-2d-drawImage">drawImage</span>(<span>CanvasImageSource</span> image, unrestricted double dx, unrestricted double dy);
Expand Down Expand Up @@ -70681,6 +70713,215 @@ try {

</div>
Comment thread
foolip marked this conversation as resolved.
Outdated


<h6>Drawing elements</h6>

<dl class="domintro">
<dt><code data-x=""><var>context</var>.<span subdfn data-x="dom-context-2d-drawElementImage">drawElementImage</span>(<var>element</var>, <var>x</var>, <var>y</var>)</code></dt>
Comment thread
foolip marked this conversation as resolved.
Outdated
<dt><code data-x=""><var>context</var>.<span data-x="dom-context-2d-drawElementImage">drawElementImage</span>(<var>element</var>, <var>x</var>, <var>y</var>, <var>w</var>, <var>h</var>)</code></dt>

Comment thread
foolip marked this conversation as resolved.
Outdated
<dd>
<p>Draws the given element onto the canvas. Throws a <code>TypeError</code> if the element isn't
Comment thread
foolip marked this conversation as resolved.
Outdated
a descendant of the canvas.</p>
</dd>
</dl>

<div w-nodev>

<p>Objects that implement the <code>CanvasDrawElementImage</code> interface have the <dfn method
for="CanvasDrawElementImage"><code
data-x="dom-context-2d-drawElementImage">drawElementImage()</code></dfn> method to draw
Comment thread
foolip marked this conversation as resolved.
Outdated
elements.</p>

<p>The <code data-x="dom-context-2d-drawElementImage">drawElementImage(element, x, y)</code> method,
when invoked, must <span>draw an element</span> with <span>this</span>, <var>element</var>,
<var>x</var>, <var>y</var>.</p>

<p>The <code data-x="dom-context-2d-drawElementImage">drawElementImage(element, x, y, w, h)</code>
method, when invoked, must <span>draw an element</span> with <span>this</span>,
<var>element</var>, <var>x</var>, <var>y</var>, <var>w</var>, and <var>h</var>.</p>

<p>To <dfn>draw an element</dfn>, with a <code>CanvasRenderingContext2D</code> <var>context</var>,
an element <var>element</var>, numbers <var>x</var> and <var>y</var>, and optional numbers
<var>w</var> and <var>h</var>:</p>

<ol>
<li><p>If <var>x</var> or <var>y</var> are infinite or NaN, then return.</p></li>

<li><p>If <var>w</var> and <var>h</var> are given and either are infinite or NaN, then
return.</p></li>

<li><p>Let <var>canvas</var> be the <code>canvas</code> element to which <var>context</var> is
bound.</p></li>

<li><p>If <var>element</var>'s <span>parent</span> is not <var>canvas</var>, then throw a
<code>TypeError</code>.</p></li>

<li><p>If <var>canvas</var> does not have a <code
data-x="attr-canvas-layoutsubtree">layoutsubtree</code> attribute specified, then throw a
<code>TypeError</code>.</p></li>

<li><p>Let <var>layoutBox</var> be <var>element</var>'s CSS layout box.</p></li>
Comment thread
foolip marked this conversation as resolved.
Outdated

<li><p>If not given, <var>w</var> and <var>h</var> must default to the width and height of
<var>layoutBox</var>.</p></li>

<li><p>If either <var>w</var> or <var>h</var> are zero, then return.</p></li>

<li><p>Paint <var>element</var> to the specified rectangular area without using any
<span>sensitive information</span>. Instead, either paint nothing or use static information that
is the same for all users.</p></li>
</ol>

<p>When drawing elements to a <code>canvas</code>, no information should be used that isn't
otherwise observable to author code. Such <dfn export>sensitive information</dfn> includes
but isn't limited to:</p>

<ul>
<li><p><span>CORS-cross-origin</span> data</p></li>

<li><p>system colors, themes, or preferences</p></li>

<li><p>spelling and grammar markers</p></li>

<li><p>search text (find-in-page) and text-fragment (fragment url) markers</p></li>

<li><p>visited link information</p></li>

<li><p>form autofill information</p></li>
</ul>

</div>


<h6>Hit testing</h6>

<dl class="domintro">
<dt><code data-x=""><var>context</var>.<span subdfn data-x="dom-context-2d-setHitTestRegions">setHitTestRegions</span>(<var>regions</var>)</code></dt>

<dd>
<p>Sets hit test regions mapping given regions (rectangles) of the canvas to descendants of the
<code>canvas</code> element.</p>
</dd>
</dl>

<div w-nodev>

<p>Objects that implement the <code>CanvasHitTestRegions</code> interface have the <dfn method
for="CanvasHitTestRegions"><code
data-x="dom-context-2d-setHitTestRegions">setHitTestRegions()</code></dfn> method to set up
hit testing regions.</p>

<p>Each <code>canvas</code> element has a <dfn>hit test regions</dfn>
<span>list</span> of <span>hit test regions</span>s, initially empty.</p>
Comment thread
foolip marked this conversation as resolved.
Outdated

<p>A <dfn>hit test region</dfn> is a <span>struct</span> consisting of an
<dfn data-x="hit-test-region-element">element</dfn> (element),
<dfn data-x="hit-test-region-x">x</dfn> (number),
<dfn data-x="hit-test-region-y">y</dfn> (number),
<dfn data-x="hit-test-region-width">width</dfn> (number), and
<dfn data-x="hit-test-region-height">height</dfn> (number).

<p>The <code data-x="dom-context-2d-setHitTestRegions">setHitTestRegions(regions)</code> method,
when invoked, must <span>set hit test regions</span> with with <span>this</span> and
<var>regions</var>.</p>

<p>To <dfn>set hit test regions</dfn>, with a <code>CanvasRenderingContext2D</code>
<var>context</var> and a <span>list</span> of <code>CanvasElementHitTestRegion</code>
dictionaries <var>regions</var>:</p>

<ol>
<li><p>Let <var>newRegions</var> be an empty list.</p></li>

<li><p>Let <var>canvas</var> be the <code>canvas</code> element to which <var>this</var> is
bound.</p></li>

<li>
<p>For each <code>CanvasElementHitTestRegion</code> <var>region</var> in <var>regions</var>:</p>

<ol>
<li><p>If <var>region</var>["<code data-x="dom-CanvasElementHitTestRegion-element">element</code>"]
or <var>region</var>["<code data-x="dom-CanvasElementHitTestRegion-element">rect</code>"] do not
exist, then throw a <code>TypeError</code>.</p></li>
<!-- TODO: just make them required -->

<li><p>Let <var>element</var> be <var>region</var>["<code data-x="dom-CanvasElementHitTestRegion-element">element</code>"].</p></li>

<li><p>If <var>element</var>'s <span>parent</span> is not <var>canvas</var>, then throw a <code>TypeError</code>.</p></li>
<!-- TODO: what if the element moves later? -->

<li><p>Let <var>rect</var> be <var>region</var>["<code data-x="dom-CanvasElementHitTestRegion-rect">rect</code>"].</p></li>

<li><p>If <var>rect</var>["<code data-x="dom-CanvasHitTestRect-x">x</code>"] or
<var>rect</var>["<code data-x="dom-CanvasHitTestRect-y">y</code>"] do not exist, then throw a
<code>TypeError</code>.</p></li>
<!-- TODO: just make them required -->

<li><p>Let <var>x</var> be <var>rect</var>["<code data-x="dom-CanvasHitTestRect-x">x</code>"].</p></li>

<li><p>Let <var>y</var> be <var>rect</var>["<code data-x="dom-CanvasHitTestRect-y">y</code>"].</p></li>

<li><p>Let <var>layoutBox</var> be <var>element</var>'s CSS layout box.</p></li>

<li><p>Let <var>width</var> be <var>rect</var>["<code data-x="dom-CanvasHitTestRect-width">width</code>"]
if it exists, otherwise the width of <var>layoutBox</var>.</p></li>
Comment thread
foolip marked this conversation as resolved.
Outdated

<li><p>Let <var>height</var> be <var>rect</var>["<code data-x="dom-CanvasHitTestRect-height">height</code>"]
if it exists, otherwise the height of <var>layoutBox</var>.</p></li>
<!-- TODO: what if the layout changes later? -->

<li><p>Let <var>newRegion</var> be a <span>hit test region</span> whose
<span data-x="hit-test-region-element">element</span> is <var>element</var>,
<span data-x="hit-test-region-x">x</span> is <var>x</var>,
<span data-x="hit-test-region-y">y</span> is <var>y</var>,
<span data-x="hit-test-region-width">width</span> is <var>width</var>, and
<span data-x="hit-test-region-height">height</span> is <var>height</var>.</p></li>

<li><p>Append <var>newRegion</var> to <var>newRegions</var>.</p></li>
</ol>
</li>

<li><p>Set <var>canvas</var>'s <span>hit test regions</span> to <var>newRegions</var>.</p></li>
</ol>

<p>When performing hit testing for a <code>canvas</code> element <var>canvas</var> and coordinates
(<var>x</var>, <var>y</var>), run these steps:</p>

<ol>
<li>
<p>For each <span>hit test region</span> <var>region</var> in <var>canvas</var>'s <span>hit test regions</span>:</p>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely an edge case, and I suppose it's a bit of a gray area (see whatwg/infra#396) but how is this supposed to work if setHitTestRegions is called during this iteration? E.g.

// Add multiple regions
ctx.setHitTestRegions([
  { element, rect: { x, y } },
  { element: anotherElement, rect: { x: anotherX, y: anotherY } }
]);
element.onclick = e => ctx.setHitTestRegions([]); // that was a 'once' handler

Should the anotherElement still perform the hit-test?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This kind of problem is sometimes handled in the spec by making a frozen copy of the thing to iterate before starting iteration. Do you think that'd be OK here?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would certainly be clearer as to what's supposed to happen yes. Now, whether it's the best behavior or not, I don't know and don't have any strong opinion. Both possibilities might come surprising depending on the case. The fact that the timing of hit-testing w.r.t. events propagation isn't well defined doesn't help...

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@szager made a good point here which is that really when we start dispatching events hit testing is already done. So rather than making a copy of the list here, the spec here needs to make clear how the list is used in hit testing and that it all happens before event dispatch.


<ol>
<li><p>Let <var>element</var> be <var>region</var>'s <span data-x="hit-test-region-element">element</span>.</p></li>

<li><p>If <var>element</var>'s <span>parent</span> is not <var>canvas</var>, then <span>continue</span>.</p></li>

<li><p>If (<var>x</var>, <var>y</var>) is not within the rectangle defined by <var>region</var>'s
<span data-x="hit-test-region-x">x</span>, <span data-x="hit-test-region-y">y</span>,
Comment thread
foolip marked this conversation as resolved.
Outdated
<span data-x="hit-test-region-width">width</span>, and
<span data-x="hit-test-region-height">height</span>, then <span>continue</span>.</p></li>
<!-- TODO: spell out exact test and boundary conditions -->

<li><p>Let <var>elementX</var> and <var>elementY</var> be the corresponding coordinates for
<var>element</var>, translating and scaling the coordinates such that (x, y) is the top left
corner and (x + width, y + height) is the bottom right corner.</p></li>
<!-- TODO: spell out exact mapping -->

<li><p>👋 Act as if (<var>elementX</var>, <var>elementY</var>) of <var>element</var> was hit.</p></li>

<li><p><span>Break</span>.</p></li>
</ol>

<p class="note">Hit testing isn't defined anywhere so there is no spec to modify or monkey patch.
The intention of the above is that things should behave as if each element was positioned at the
given region, with all the observable behavior that implies, such as pointer events firing at the
element and propagating like they would for a regular element.</p>
</li>
</ol>

</div>


<h6>Drawing images</h6>

<p>Objects that implement the <code>CanvasDrawImage</code> interface have the <dfn method
Expand Down Expand Up @@ -139063,8 +139304,18 @@ legend[align=right i] {
<p>A <code>canvas</code> element that <span>represents</span> <span>embedded content</span> is
expected to be treated as a <span>replaced element</span>; the contents of such elements are the
element's bitmap, if any, or else a <span>transparent black</span> bitmap with the same
<span>natural dimensions</span> as the element. Other <code>canvas</code> elements are expected
to be treated as ordinary elements in the rendering model.</p>
<span>natural dimensions</span> as the element. A <code>canvas</code> element that
<span>represents</span> <span>embedded content</span> and has a <code
data-x="attr-canvas-layoutsubtree">layoutsubtree</code> attribute specified is additionally
expected to be treated as 👋replaced element with subtree layout👋, where children are laid out
but not rendered. Additionally, children are exposed to assistive technologies (ATs).</p>

<p class="XXX">This needs to be reconciled with the definition of <span
data-x="replaced element">replaced elements</span>, which says that the content of replaced
elements is not considered in the CSS formatting model.</p>

<p>Other <code>canvas</code> elements are expected to be treated as ordinary elements in the
rendering model.</p>

<p>An <code>object</code> element that <span>represents</span> an image, plugin, or its
<span>content navigable</span> is expected to be treated as a <span>replaced element</span>.
Expand Down Expand Up @@ -139111,6 +139362,7 @@ legend[align=right i] {

<pre><code class="css">@namespace "http://www.w3.org/1999/xhtml";

canvas[layoutsubtree] > * { isolation: isolate !important; contain: strict !important; }
iframe { border: 2px inset; }
<span id="video-object-fit">video { object-fit: contain; }</span></code></pre>

Expand Down Expand Up @@ -145298,6 +145550,11 @@ interface <dfn interface>External</dfn> {
<code data-x="attr-track-label">track</code>
<td> User-visible label
<td> <a href="#attribute-text">Text</a>
<tr>
<th> <code data-x="">layoutsubtree</code>
<td> <code data-x="attr-canvas-layoutsubtree">canvas</code>
<td> Whether to layout descendants
<td> <span>Boolean attribute</span>
<tr>
<th> <code data-x="">lang</code>
<td> <span data-x="attr-lang">HTML elements</span>
Expand Down
Loading