blitz-script: web compat needed by Flexbox Froggy (localStorage, createHTMLDocument, select options) - #757
blitz-script: web compat needed by Flexbox Froggy (localStorage, createHTMLDocument, select options)#757nicoburns wants to merge 30 commits into
Conversation
Signed-off-by: Nico Burns <nico@nicoburns.com>
Signed-off-by: Nico Burns <nico@nicoburns.com>
Signed-off-by: Nico Burns <nico@nicoburns.com>
Signed-off-by: Nico Burns <nico@nicoburns.com>
Errors are recorded in the runtime state (capped at 256 between drains) for collection via `ScriptDocument::take_js_errors`. Embedders decide how to surface them: the WPT runner logs them via `log::warn!` and records them as subtest failures; the browser logs them via `tracing::error!`.
`CSS.supports(property, value)` and `CSS.supports(conditionText)` are backed by stylo's declaration and @supports-condition parsers (via new `BaseDocument::css_supports_condition`), so support reporting exactly matches the engine's CSS support. `CSS.escape` implements the CSSOM serialize-an-identifier algorithm in the JS bootstrap. CSS.supports gates nearly every WPT css test helper (interpolation, computed-value and parsing testcommon), so this unblocks a large number of subtests (css/css-grid: 4332 -> 6929 passing).
…yles WPT's computed-value test helpers gate every test on `property in getComputedStyle(el)`. Add a `has` trap to the CSSStyleDeclaration Proxy backed by a new `blitz_dom::css_property_is_supported` check (stylo property parsing), so the `in` operator accurately reports engine support for both camelCase and kebab-case property names. css/css-grid: 6929 -> 7234 subtests passing.
…onBlock Replace the hand-rolled string-splitting implementation of the inline `style` CSSOM with new `BaseDocument::style_attr_*` helpers built on stylo's `PropertyDeclarationBlock`: - `getPropertyValue`/`cssText` return canonical stylo serializations (WPT parsing tests assert exact canonical serialization) - shorthands are expanded on set and re-serialized from their longhands on get (e.g. `place-items` -> `align-items`/`justify-items`) - invalid declarations are rejected exactly per the engine's CSS support - custom properties and the `!important` priority argument now work css/css-grid/parsing + css/css-align: 3073 -> 4629 subtests passing.
- Add `matches` (+`webkitMatchesSelector` alias) and `closest`, backed by blitz-dom's `matches_selector`/`closest` - Scope element-level `querySelector`/`querySelectorAll` with blitz-dom's `query_selector_in`/`query_selector_all_in` instead of filtering document-wide matches - Selector parse failures now throw a `SyntaxError` DOMException (per spec), constructed from the global DOMException interface so that testharness.js' `assert_throws_dom` checks (name, code, constructor) pass. The bootstrap DOMException gains `code` and the legacy code constants.
Backed by blitz-dom's hit testing (`BaseDocument::hit`), resolving layout-generated anonymous boxes and text nodes to their nearest element. Points outside the viewport return null; points over the background hit the root element. `elementsFromPoint` approximates the spec's paint-order list with the hit element's ancestor chain. Unblocks the WPT hit-test suites (e.g. css/CSS2/floats/hit-test-*).
Implement the ParentNode mixin (`childElementCount`, `firstElementChild`, `lastElementChild`) on both Element and Document, and the NonDocumentTypeChildNode mixin (`nextElementSibling`, `previousElementSibling`) on Element.
`resolved_style_value` previously returned an empty string for shorthand properties. Resolve each of the shorthand's longhands (via stylo's `computed_or_resolved_declaration`) and re-serialize them as the shorthand with `ShorthandId::longhands_to_css`, matching what `getComputedStyle()` returns for shorthands in browsers. css/css-grid: 7234 -> 7712 subtests passing (cumulative with the previous CSSOM commits).
Crashtests using script were skipped with a guard from before JS support existed. Run them through a ScriptDocument instead: execute scripts, run JS timers due within a short budget (crashes are often triggered by post-load setTimeout/requestAnimationFrame mutations), then resolve and render as before. css: 543 previously-skipped crashtests now run (all passing).
Implement `append`/`prepend`/`replaceChildren` (on Element and Document) and `before`/`after`/`replaceWith` (on all nodes). Arguments may be nodes or strings (converted to text nodes), already-parented nodes are moved, and the ChildNode methods implement the spec's "viable sibling" handling so that inserting a node relative to itself or its siblings lands in the right position. css: ~2,400 additional subtests passing (82,228 total).
Backed by blitz-dom's `node_client_rects`, which returns one border-box rect per box fragment: a single rect for nodes with their own layout box and one rect per line box for non-atomic inline elements. Boxless elements (`display: none`, detached) return an empty list per spec. Also re-export taffy from blitz-dom, since taffy types are part of its public API (`Node::style`, `Node::final_layout`).
Some checkLayout (attr) tests build their test DOM with an inline script (e.g. css-flexbox's position-absolute-013.html generates 432 flexboxes from nested loops) or run extra test()s of their own. The native attr runner executes no JavaScript, so it found zero/fewer matching elements and under-reported subtests. Detect attr tests whose inline scripts do more than call checkLayout() and, for those, execute the document's scripts (running the real check-layout-th.js/testharness.js) and use the harness-reported results, falling back to the native checks if the harness doesn't report. Static checkLayout tests keep the fast no-JS path. Also, since the JS path judges offsets with real CSSOM semantics: - back JS offsetLeft/offsetTop with blitz-dom's offset_top_left() (offsetParent-relative, matching the native checker) instead of the direct-parent approximation - implement window.innerWidth/innerHeight/outerWidth/outerHeight/ devicePixelRatio from the viewport (used by tests to compute expected sizes) css/css-flexbox: 4,598 -> 5,574 subtests enumerated (Servo: 5,739; the remainder needs template.content and a newer WPT checkout).
Parse a template element's children into a detached, inert "template contents" fragment node (implementing the html5ever get_template_contents hook and populating the previously-unused ElementData::template_contents), instead of parsing them as regular children. This matches the HTML spec: template contents are not part of the document, are never styled or rendered, and are not matched by document-wide selector queries. Fragments are represented as detached elements with the special name "#document-fragment" (nodeType 11). In blitz-script: - `template.content` returns the contents fragment (lazily created for script-created templates) - `document.createDocumentFragment()` creates an empty fragment - inserting a fragment (appendChild/insertBefore and the ParentNode/ ChildNode mixin helpers) moves its children, per the DOM spec This unblocks the clone-a-template-per-subtest pattern used by WPT's interpolation-testcommon.js (every css/*/animation directory) and by generative grid/cascade tests: e.g. flex-grow-interpolation.html goes from 1 to 152 subtests (114 passing, since the CSS Transitions negative- delay sampling works through stylo's transition engine), and the subgrid alignment-in-subgridded-axes ref tests now pass. css: 173,631 -> 185,152 subtests run; 82,812 -> 90,325 passing.
Blitz has no testdriver automation backend, so testdriver.js commands (test_driver.click() etc.) fall back to waiting for a *real user* to perform the action and such tests hang until the harness timeout. Serve a one-line testdriver-vendor.js setting in_automation = true: every command then rejects immediately with testdriver.js's own "...not implemented by testdriver-vendor.js" error, converting those timeouts into fast, clearly-attributed failures. (Tests which swallow the rejection without completing still run into the harness timeout; that is inherent to their structure.) No result changes on the css suite; 23 tests drop from ~1s to ~40ms.
Boa gates the Annex B (web compatibility) ECMAScript features behind the `annex-b` cargo feature: String.prototype.substr/trimLeft/trimRight and the legacy HTML string methods, escape/unescape, HTML comments in scripts, etc. All browsers implement Annex B and web content (including many WPT tests) relies on it: the entire css-text i18n line-break suite died on `String.prototype.substr` before declaring its subtests. css/css-text's subtest deficit vs Servo drops from 1,085 to 36 (the rest is checkout skew and the missing Range API); css overall: +1,068 subtests run, +607 passing (90,952).
Registers atob/btoa (Base64Extension), TextEncoder/TextDecoder (EncodingExtension), structuredClone (StructuredCloneExtension), a real queueMicrotask backed by the job queue (MicrotaskExtension, replacing the bootstrap's Promise-based stub) and the URL global (UrlExtension; note upstream does not implement URL.searchParams yet). Deliberately not registered: - TimeoutExtension: blitz-script has its own setTimeout/setInterval/ requestAnimationFrame implementation integrated with the document's event loop and timer thread - FetchExtension/AbortControllerExtension: fetch should route through the embedder's net provider, not an internal HTTP client - PostMessageExtension: needs a MessageSender; revisit with iframe/worker messaging
…ixes Per CSSOM, camelCase style attributes with a leading vendor prefix (`webkitTransform`, `msFlex`, `mozUserSelect`) correspond to dash-prefixed CSS properties (`-webkit-transform`, ...). The style proxy's camelCase->kebab conversion produced `webkit-transform` (no leading dash), which stylo rejected, so such writes were silently dropped. Uppercase-first spellings (`MozUserSelect`) already gained the dash from the uppercase conversion. Stylo then handles the prefixed names as legacy aliases and serializes canonically, matching browsers (style.webkitTransform = "scale(2)" yields style="transform: scale(2);"). css: +198 passing subtests (91,334).
Ports the API surface from the devarda/blitz react-dom-apis branch that our implementation was missing (reimplemented against the current code): - Event.getModifierState(key): React's synthetic keyboard/mouse events call it on the native event; reads back the event's own modifier fields - Element.hidden: reflected boolean attribute ([hidden] already hides via the presentation-attribute handling) - input.selectionStart/selectionEnd (get and set) and setSelectionRange: backed by the text input's real parley editor selection, in UTF-16 code units per spec; null on elements without a text editor. React snapshots and restores the caret around controlled-input re-renders - Interface-constructor global stubs (EventTarget, HTMLInputElement, KeyboardEvent, ...) so `instanceof` probes don't throw. All elements share one prototype, so tag-specific interfaces answer false Text inputs create their editor during layout construction, so the selection accessors resolve layout first. blitz-script's tests now enable blitz-dom's system-fonts feature: without fonts the editor's text never shapes and cursor placement collapses to the buffer end.
The React TodoMVC app (todomvc.com/examples/react/dist) failed to render:
it uses React Router 7's hash history, which reads `window.history.state`
during router creation (TypeError on our missing `history` global, killing
the initial render) and then `window.location.origin` when creating URLs.
- Add an in-memory `window.history` to the bootstrap: `state`,
`pushState`/`replaceState` (which resolve the given URL against
`location.href` and update the `location` fields) and no-op
`go`/`back`/`forward`. There is no real session history, so `popstate`
events are never fired; SPA routers drive their own listeners after
push, so in-app navigation works regardless.
- Add `origin` to the `location` object ("null" for about:blank, per
spec's opaque-origin serialization).
With these, TodoMVC renders fully and is interactive: typing into the
new-todo input and pressing Enter (through the real UI event pipeline)
runs React's controlled-input onChange/onKeyDown handlers and renders the
new todo item.
Replace local reimplementations with the methods now on main: - box metrics: Node::client_width/client_height/scroll_width/scroll_height/has_boxes - hit testing: BaseDocument::element_from_point/elements_from_point - lookups: BaseDocument::find_body_node/find_head_node - ParentNode/ChildNode mixins: DocumentMutator::prepend_nodes/replace_children/before_node/after_node/replace_with_nodes
Element scrollTop/scrollLeft, scroll/scrollTo/scrollBy (with WebIDL ScrollToOptions parsing) and scrollIntoView; window scrollX/scrollY/ pageXOffset/pageYOffset and scroll/scrollTo/scrollBy; and document.scrollingElement. Bound to blitz-dom's programmatic scrolling support. Takes css/cssom-view WPT subtests from 460 to 612 passing.
…Document, select options)
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
GUI test: Flexbox Froggy plays end-to-end in the Blitz browserTested this branch in the full browser GUI (release build, It should render the Flexbox Froggy game UI — ✅
Note: unrelated blank-window issue with plain cargo buildsWith |
2973ec3 to
e29f0fa
Compare
Summary
Makes https://flexboxfroggy.com/ work on top of #738. The site failed for three reasons, fixed in order of discovery:
document.implementation.createHTMLDocument("").body, which threw (implementationwas undefined), so$was never defined and every site script failed. Added a bootstrap-JSdocument.implementationshim whosecreateHTMLDocument(title)builds a detachedhtml/head/bodytree in the current document's arena and returns a document-like object delegating node creation to the main document (covers jQuery'sparseHTML-style template parsing; not a real separateDocument).localStorage is not defined:game.jsreadslocalStorage.colorblindat top level. Added in-memorylocalStorage/sessionStorageshims (Proxy-based, so named access likelocalStorage.levelfollows Storage's WebIDL named getter/setter semantics).game.start()threw:window.navigator.language.split("-")— addednavigator.language/navigator.platform..val(...)on<select>useselem.options,elem.selectedIndex, and option.selected. Added native accessors inelement.rs:Also extends
examples/screenshot_script.rs(offscreen JS+render harness) to load http(s) URLs with prefetched external scripts and to pump JS timers before rendering — jQuery fires its deferred-ready callback viasetTimeout, sogame.start()only runs once timers are pumped.After the fixes, the live site renders the full game (level selector "1 of 24", instructions, CSS editor, frog + lilypad on the pond):
Remaining JS errors on the page are from Google ads/analytics only (
adsbygoogle.jsinternalconsole.error, andXMLHttpRequest is not definedfrom analytics.js) and don't affect the game.Added
tests/dom.rscoverage forlocalStorage,createHTMLDocument, and the<select>accessors (24 tests pass).Link to Devin session: https://dioxus.staging.devinenterprise.com/sessions/446d0cb01a7b4cb3bced0e16920f5e28
Requested by: @nicoburns
WPT results
2825 newly passing, 208 newly failing (net +2617), 4406 other status changes.
Full diff (7439 changed tests)
Generated by the WPT workflow.