Skip to content

Commit e3fbd73

Browse files
ursmclaude
andcommitted
feat(cssom): HTMLStyleElement.sheet + lowercase on-handler lookup
`<style>.sheet` now returns the element's `CSSStyleSheet` (CSSOM): created lazily once the `<style>` is connected, re-synced from its current text content (so `cssRules` tracks the text), null when disconnected. This lets an earlier-inserted script observe a later-inserted `<style>` already applied — clearing dom/nodes/insertion-removing-steps/Node-appendChild-script-and-style (the `<style>` cases) and Node-appendChild-text-and-script-in-style. (`<link>` sheets, which need the fetched resource, are not modelled — those subtests stay allowlisted.) The on-attribute handler lookup in dispatch now lower-cases the event type (`'on' + event.type.toLowerCase()`): IDL on-handler attribute names are lowercase, but a mixed-case type like `webkitAnimationEnd` mapped to the non-existent `onwebkitAnimationEnd`. No-op for the standard already-lowercase types. With this, the prefixed `onwebkitanimation*` / `onwebkittransitionend` handlers fire on the matching prefixed dispatch. Because `style.sheet` makes their setup succeed, the four dom/events/webkit-{animation-end,animation-iteration,animation-start, transition-end}-event.html files now run (they were vacuously green — setup threw on the missing `style.sheet`). Their prefixed on-handler/dispatch subtests (4/file = 16) now pass; the `triggerAnimation()` subtests (9/file = 36) need a real CSS animation/transition engine to fire a trusted animationend/ transitionend (rule 1 out-of-scope) and are allowlisted. Gate 660/0/15 (1441 fail subtests: +36 honest out-of-scope animation-engine exposure, −3 style cleared; +19 real subtests now pass); gem 1586/0/35; Redmine (inline onclick) green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent b80c116 commit e3fbd73

4 files changed

Lines changed: 112 additions & 10 deletions

File tree

lib/capybara/simulated/js/bridge.bundle.js

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3290,7 +3290,7 @@
32903290
event.target = event._csimRetargets.has(node) ? event._csimRetargets.get(node) : event._csimTopRetarget;
32913291
}
32923292
if (!capture && node._attrs && !event._immediatePropagationStopped) {
3293-
const attrName = "on" + event.type;
3293+
const attrName = "on" + event.type.toLowerCase();
32943294
const propHandler = typeof node[attrName] === "function" ? node[attrName] : null;
32953295
const attrVal = propHandler ? null : node._attrs[attrName];
32963296
let handler = propHandler;
@@ -8545,6 +8545,30 @@
85458545
}
85468546
Object.defineProperty(this, "content", { value: v, writable: true, configurable: true, enumerable: true });
85478547
}
8548+
// `HTMLStyleElement.sheet` — the CSSStyleSheet associated with a connected
8549+
// `<style>` (CSSOM). It exists once the element is in a document and reflects
8550+
// the element's current text content, so an earlier-inserted script can
8551+
// observe a later-inserted `<style>` already applied (cssRules track the text
8552+
// live). Disconnected → null; `<link>` sheets (which need the fetched
8553+
// resource) are not modelled here. Created lazily and cached for stable identity.
8554+
get sheet() {
8555+
if (this._tag !== "style") return void 0;
8556+
if (!this.isConnected) {
8557+
this._sheet = null;
8558+
return null;
8559+
}
8560+
const type = (this._attrs.type || "").toLowerCase();
8561+
if (type && type !== "text/css") {
8562+
this._sheet = null;
8563+
return null;
8564+
}
8565+
if (!this._sheet) {
8566+
this._sheet = new globalThis.CSSStyleSheet();
8567+
this._sheet.ownerNode = this;
8568+
}
8569+
this._sheet.replaceSync(this.textContent || "");
8570+
return this._sheet;
8571+
}
85488572
// `<dialog>` HTML interface — show() / showModal() / close() per
85498573
// HTMLDialogElement. Turbo's confirm flow uses this: opens
85508574
// `<dialog id="turbo-confirm">` via `showModal()`, waits for the

lib/capybara/simulated/js/src/dispatch.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,11 @@ function fireListeners(node, event, capture) {
336336
// this, Redmine's `onclick="showAndScrollTo(...); return false"`
337337
// never runs and the issue-notes form stays collapsed.
338338
if (!capture && node._attrs && !event._immediatePropagationStopped) {
339-
const attrName = 'on' + event.type;
339+
// IDL on-handler attribute names are lowercase; an event type can be mixed
340+
// case (e.g. `webkitAnimationEnd` → `onwebkitanimationend`), so lower-case
341+
// the type for the property/attribute lookup. No-op for the standard
342+
// already-lowercase event types.
343+
const attrName = 'on' + event.type.toLowerCase();
340344
// Property assignment (`el.onclick = fn`) takes precedence over
341345
// any `onclick="..."` attribute per HTML spec — the setter
342346
// *replaces* the inline handler. jstoolbar registers its Edit /

lib/capybara/simulated/js/src/dom-nodes.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2330,6 +2330,24 @@ class Element extends Node {
23302330
if (this._tag === 'meta') { this.setAttribute('content', v == null ? '' : String(v)); return; }
23312331
Object.defineProperty(this, 'content', {value: v, writable: true, configurable: true, enumerable: true});
23322332
}
2333+
// `HTMLStyleElement.sheet` — the CSSStyleSheet associated with a connected
2334+
// `<style>` (CSSOM). It exists once the element is in a document and reflects
2335+
// the element's current text content, so an earlier-inserted script can
2336+
// observe a later-inserted `<style>` already applied (cssRules track the text
2337+
// live). Disconnected → null; `<link>` sheets (which need the fetched
2338+
// resource) are not modelled here. Created lazily and cached for stable identity.
2339+
get sheet() {
2340+
if (this._tag !== 'style') return undefined;
2341+
if (!this.isConnected) { this._sheet = null; return null; }
2342+
const type = (this._attrs.type || '').toLowerCase();
2343+
if (type && type !== 'text/css') { this._sheet = null; return null; }
2344+
if (!this._sheet) {
2345+
this._sheet = new globalThis.CSSStyleSheet();
2346+
this._sheet.ownerNode = this;
2347+
}
2348+
this._sheet.replaceSync(this.textContent || ''); // re-sync rules from the current text
2349+
return this._sheet;
2350+
}
23332351
// `<dialog>` HTML interface — show() / showModal() / close() per
23342352
// HTMLDialogElement. Turbo's confirm flow uses this: opens
23352353
// `<dialog id="turbo-confirm">` via `showModal()`, waits for the

spec/support/wpt_expected_failures.yml

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,70 @@ dom/events/scrolling/scrollend-fires-to-text-input.html:
118118
}).'
119119
dom/events/scrolling/wheel-event-no-scroll-after-prevent-default.html:
120120
- When `preventDefault` is called on a WheelEvent, scrolling should be prevented.
121+
dom/events/webkit-animation-end-event.html:
122+
- event types for prefixed and unprefixed animationend event handlers should be named
123+
appropriately
124+
- event types for prefixed and unprefixed animationend event listeners should be named
125+
appropriately
126+
- onwebkitanimationend event handler should not trigger if an unprefixed event handler
127+
also exists
128+
- onwebkitanimationend event handler should not trigger if an unprefixed listener
129+
also exists
130+
- onwebkitanimationend event handler should trigger for an animation
131+
- webkitAnimationEnd event listener is case sensitive
132+
- webkitAnimationEnd event listener should not trigger if an unprefixed event handler
133+
also exists
134+
- webkitAnimationEnd event listener should not trigger if an unprefixed listener also
135+
exists
136+
- webkitAnimationEnd event listener should trigger for an animation
137+
dom/events/webkit-animation-iteration-event.html:
138+
- event types for prefixed and unprefixed animationiteration event handlers should
139+
be named appropriately
140+
- event types for prefixed and unprefixed animationiteration event listeners should
141+
be named appropriately
142+
- onwebkitanimationiteration event handler should not trigger if an unprefixed event
143+
handler also exists
144+
- onwebkitanimationiteration event handler should not trigger if an unprefixed listener
145+
also exists
146+
- onwebkitanimationiteration event handler should trigger for an animation
147+
- webkitAnimationIteration event listener is case sensitive
148+
- webkitAnimationIteration event listener should not trigger if an unprefixed event
149+
handler also exists
150+
- webkitAnimationIteration event listener should not trigger if an unprefixed listener
151+
also exists
152+
- webkitAnimationIteration event listener should trigger for an animation
153+
dom/events/webkit-animation-start-event.html:
154+
- event types for prefixed and unprefixed animationstart event handlers should be
155+
named appropriately
156+
- event types for prefixed and unprefixed animationstart event listeners should be
157+
named appropriately
158+
- onwebkitanimationstart event handler should not trigger if an unprefixed event handler
159+
also exists
160+
- onwebkitanimationstart event handler should not trigger if an unprefixed listener
161+
also exists
162+
- onwebkitanimationstart event handler should trigger for an animation
163+
- webkitAnimationStart event listener is case sensitive
164+
- webkitAnimationStart event listener should not trigger if an unprefixed event handler
165+
also exists
166+
- webkitAnimationStart event listener should not trigger if an unprefixed listener
167+
also exists
168+
- webkitAnimationStart event listener should trigger for an animation
169+
dom/events/webkit-transition-end-event.html:
170+
- event types for prefixed and unprefixed transitionend event handlers should be named
171+
appropriately
172+
- event types for prefixed and unprefixed transitionend event listeners should be
173+
named appropriately
174+
- onwebkittransitionend event handler should not trigger if an unprefixed event handler
175+
also exists
176+
- onwebkittransitionend event handler should not trigger if an unprefixed listener
177+
also exists
178+
- onwebkittransitionend event handler should trigger for an animation
179+
- webkitTransitionEnd event listener is case sensitive
180+
- webkitTransitionEnd event listener should not trigger if an unprefixed event handler
181+
also exists
182+
- webkitTransitionEnd event listener should not trigger if an unprefixed listener
183+
also exists
184+
- webkitTransitionEnd event listener should trigger for an animation
121185
dom/interface-objects.html: HARNESS_ERROR
122186
dom/lists/DOMTokenList-iteration.html:
123187
- classList inheritance from Array.prototype
@@ -195,10 +259,6 @@ dom/nodes/insertion-removing-steps/Node-appendChild-script-and-source-from-fragm
195259
- Empty <source> immediately sets media.networkState during DOM insertion, so that
196260
an earlier-running script can observe networkState
197261
dom/nodes/insertion-removing-steps/Node-appendChild-script-and-style.html:
198-
- An earlier-inserted <script> synchronously observes a later-inserted <style> (via
199-
a DocumentFragment) being applied
200-
- An earlier-inserted <script> synchronously observes a later-inserted <style> (via
201-
a div) being applied
202262
- Earlier-inserted <script> (via a DocumentFragment) synchronously observes a later-inserted
203263
<link rel=stylesheet>'s CSSStyleSheet creation
204264
- Earlier-inserted <script> (via a append()) synchronously observes a later-inserted
@@ -208,10 +268,6 @@ dom/nodes/insertion-removing-steps/Node-appendChild-script-and-style.html:
208268
dom/nodes/insertion-removing-steps/Node-appendChild-script-in-script.html:
209269
- An outer script whose preparation/execution gets triggered by the insertion of a
210270
'nested'/'inner' script, executes *before* the inner script executes
211-
dom/nodes/insertion-removing-steps/Node-appendChild-text-and-script-in-style.html:
212-
- All style rules appended to a <style> element are inserted and script-observable
213-
to scripts inserted in the `<style>` element, by the time scripts execute after
214-
DOM insertions.
215271
dom/nodes/moveBefore/child-style-preserve.html:
216272
- child-style-preserve
217273
dom/nodes/moveBefore/continue-css-animation-left.html:

0 commit comments

Comments
 (0)