Previously, the only error generateKey would throw as SyntaxError. This commit
adds validation to the input so that the correct error can be returned. This
helps a couple thousands of WPT tests to pass, e.g.
/WebCryptoAPI/generateKey/failures_AES-CBC.https.any.html
Goes from 210 / 775 to 775/775.
This does not add more crypto capabilities / algos, just validation of the
provided parameters.
Address review feedback on the legacy KeyboardEvent.keyCode and
KeyboardEvent.charCode getters:
* `getKeyCode` and `getCharCode` now early-return 0 when the event is
not trusted, matching Chrome's behavior. Synthetic events created via
`new KeyboardEvent(...)` from script have `isTrusted === false` and
therefore expose 0 for both legacy attributes; only events dispatched
by the user agent itself surface the legacy mapping.
* `getCharCode` now uses `_type_string.eql(comptime .wrap("keypress"))`
to match the idiom used elsewhere in the project.
* The charCode mapping is moved into a pure `Key.charCode()` helper that
mirrors `Key.keyCode()`, including a `.Enter => 13` arm so a trusted
`keypress` for Enter exposes `\r` (U+000D) per spec.
The JS test fixture is consolidated into a single block asserting the
Chrome-correct behavior for synthetic events. The full per-key mapping
table is now exercised via two pure-function Zig unit tests on
`Key.keyCode()` and `Key.charCode()`.
`StyleManager.hasDisplayNone` honored only the `[hidden]` UA-stylesheet
rule; the rest of HTML Rendering §15.3.1 ("Hidden elements") was
unimplemented. As a result `getComputedStyle(headEl).display === "block"`
and `el.checkVisibility()` returned `true` for `<head>`, `<script>`,
`<style>`, `<link>`, `<meta>`, `<title>`, `<noscript>`, `<template>`,
`<param>`, `<source>`, `<track>`, `<area>`, `<datalist>`,
`<input type="hidden">`, and the non-`<summary>` direct children of a
closed `<details>`.
Add a `matchesUaDisplayNoneRule` helper consulted at the top of
`isElementHidden` so both `getComputedStyle().display` (via
`hasDisplayNone`) and `el.checkVisibility()` (via `isHidden`'s ancestor
walker) honor the same UA-stylesheet truth. The helper covers the
`[hidden]` attribute, a new `Tag.isHiddenByUaStylesheet()` predicate
mapping the spec's unrendered tag list, the
`input[type="hidden" i]` rule, and the `details:not([open]) > *:not(summary)`
parent-relationship rule. Inline `display` overrides still win per CSS
cascade.
Closes#2293
Both getters were stubs returning 0. Per W3C UI Events § Annex C, keyCode
should report the legacy fixed virtual key code for the key being pressed
(e.g. 84 for 't'/'T', 16 for Shift, 13 for Enter), and charCode should
report the Unicode code point of the character produced on keypress events.
Adds a keyCode() method to the Key union that maps each named variant to
its spec-defined value and computes the uppercase-ASCII fallback for
.standard printable characters. getKeyCode delegates to it. getCharCode
checks the event type and returns the first byte of _key.standard for
keypress, 0 elsewhere.
For shift-modified symbol keys (e.g. shift+1='!'), keyCode falls back to
the modified char's ASCII rather than the unmodified key's value, since
KeyboardEvent doesn't currently store the unmodified key. Spec-strict
behavior would need plumbing unmodifiedText through KeyboardEventOptions
— left as a follow-up.
Closes#2291
Both CDP methods returned -31998 UnknownMethod even though the underlying
browsing-context history is fully tracked in webapi/navigation/Navigation.zig.
Add the two methods to the Page dispatch enum + switch and wire them to the
existing Navigation._entries / navigateInner traverse path.
getNavigationHistory walks _entries and reports {currentIndex, entries:
[{id, url, userTypedURL, title, transitionType}]}. navigateToHistoryEntry
looks up the entry by id and calls frame.navigate(url, .{ .reason = .history,
.kind = .{ .traverse = idx } }), reusing commitNavigation's traverse arm
which only updates _index without pushing a new entry. URL is duplicated
into the new frame's arena because replacePage releases the prior arena.
Closes#2288
- Drop redundant `step_attr.len == 3` guard in `snapToStep`. Zig's
`std.ascii.eqlIgnoreCase` already short-circuits on length mismatch
(`ascii.zig:329`), so the outer length check is wasted work.
- Implement the `value` content attribute fallback for step base.
Per https://html.spec.whatwg.org/multipage/input.html#concept-input-min
the chain is `min` content attr -> `value` content attr -> 0. The
fallback was previously skipped (step base went straight from `min`
to 0), which meant `<input type=range value="3.5" max=10>` then
`el.value = '5.3'` snapped to `5` instead of the spec-required `5.5`.
Threads `value_attr` through `sanitizeRange` so the fallback engages
whenever `min` is absent or unparseable but a parseable `value`
content attr exists.
Three new test cases cover the fallback path (s10), the unparseable
`value` attr that falls through to 0 (s11), and `min` taking precedence
over `value` attr (s12).
Adds the ValidityState interface plus validity / validationMessage /
willValidate / checkValidity / reportValidity / setCustomValidity on
HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement,
HTMLButtonElement, and HTMLFormElement (form-level checkValidity /
reportValidity iterate the form's listed elements). checkValidity
fires a cancelable `invalid` event on each element whose
ValidityState.valid is false, matching the HTML Living Standard
constraint validation algorithm.
Validity flags implemented per element type follow the spec's
per-element constraint set: Input covers valueMissing / typeMismatch
(email + url) / tooLong / tooShort / rangeUnderflow / rangeOverflow /
customError; Select covers valueMissing / customError; TextArea covers
valueMissing / tooLong / tooShort / customError; Button covers
customError (only when type=submit). patternMismatch and stepMismatch
are placeholders that return false (pattern needs JS RegExp from Zig;
step matching for <input type=range> is being landed in PR #2280);
badInput is always false because headless never receives raw user
keystrokes.
Closes#2284
Anchor click, form submit, and `location.href = ...` assignments queue a
navigation through `Frame.scheduleNavigation`, which then tears down the
originating page and rebuilds the frame in `Session.processRootQueuedNavigation`
before `Frame.navigate` issues the HTTP request. The originator's URL was
discarded with the old arena, so the request went out without a Referer
header — even though the HTML "navigate" algorithm and Fetch §4.5 require
one. `Frame.headersForRequest` (#1449) handled subresource fetches but was
never called from the navigation path.
Capture the originating frame's URL into a new `referer` field on
`NavigateOpts` at scheduling time, dup'd into the `QueuedNavigation` arena
so it survives the page tear-down. `Frame.navigate` adds it as a
`Referer:` header alongside the existing per-request headers. Iframe
initial navigation (`Frame.zig:1282`) also sets `referer = parent.url`
since the parent frame outlives that direct `navigate` call. CDP
`Page.navigate` (`.reason = .address_bar`) and `Page.reload` continue to
omit Referer — matches Chrome.
Closes#2281
Per WHATWG HTML "Suffering from a step mismatch", value sanitization for
<input type=range> rounds the value to the nearest valid `step base + step * n`
after the existing min/max clamp. Ties round toward positive infinity; if the
rounded-up neighbor exceeds max, the rounded-down neighbor is used instead.
`step` defaults to 1; `step="any"` (case-insensitive) disables step matching.
When the post-snap value matches the parsed input value exactly, the original
string is returned unchanged so assignments like `el.value = "1.0"` round-trip
without canonicalization.
Closes#2277
When a form is submitted via a click on a submit button (or
form.requestSubmit(button)), the HTML form-submission algorithm requires
the submitter's formaction / formmethod / formenctype attributes to
override the form's corresponding attributes when present. Frame.submitForm
was reading action / method / enctype only from the form element, so the
button-side overrides were silently ignored. The symmetrical lookup for
formtarget already exists upstream — this commit applies the same pattern
to the other three attributes.
Closes#2278
If a POST navigation gets redirected (302/303), the page that actually
loads is fetched with GET — but Frame._navigated_options still carries
the original POST method, body, and Content-Type. Page.reload would
then re-POST the form data to the redirect target, which is both
incorrect and dangerous (re-submission of credentials, charges, etc.).
Reset method/body/header on _navigated_options inside
frameHeaderDoneCallback whenever response.redirectCount() > 0. The full
spec-correct version would distinguish 307/308 (preserve method) from
301/302/303 (convert POST→GET), but resubmitting form data is the more
dangerous failure mode — conservative reset matches Chrome's practical
behavior on reload.
Also collapse the prev_body/prev_header extraction in doReload to a
single tuple-destructured blk: block (no behavior change).
Tests: new cdp.frame: reload after POST→redirect drops the POST drives
POST /redirect_to_echo → 302 → /echo_method, then Page.reload, asserts
the second request is GET. /redirect_to_echo route added to
testing.zig. The existing reload-replays-POST test still passes (no
redirect, POST is still replayed).
Per review: collectForm walked the children once to look for a
non-disabled <option>, then Select.getValue walked them again to
emit the value. Extract the selectedness-candidate algorithm into
Select.effectiveOption() ?*Option so both call sites share it and
each runs a single iteration.
- Select.getValue is now a one-liner over effectiveOption.
- collectForm replaces the candidate-check + getValue pair with
`select.effectiveOption() orelse continue; break :blk opt.getValue(frame);`.
Behavior is unchanged; the existing form_data.html fixtures
(selectWithoutOptions, selectMultipleWithoutOptions,
selectAllOptionsDisabled) still pass.
- Use std.fmt.allocPrint in formatFloat (per-review suggestion).
- Drop the `r3.value = '1' -> '1'` fractional-bounds assertion, which
conflicted with WHATWG step matching (default step=1, base=min=0.5,
nearest valid is '1.5'). Step matching remains out of scope here.
Per review feedback. attributeValue now guarantees an arena-owned
return: the quoted path already allocates into the arena via the
ArrayList, so we just dupe in the unquoted path and drop the redundant
arena.dupe at the call site. Pre-size the ArrayList to input.len since
escapes only shrink, skipping grow-from-zero reallocs.