Merge remote-tracking branch 'origin/main' into fix-2277-input-range-step-matching

This commit is contained in:
Navid EMAD
2026-04-28 04:08:58 +02:00
8 changed files with 147 additions and 25 deletions

View File

@@ -13,7 +13,7 @@ inputs:
zig-v8:
description: 'zig v8 version to install'
required: false
default: 'v0.4.1'
default: 'v0.4.2'
v8:
description: 'v8 version to install'
required: false

View File

@@ -3,7 +3,7 @@ FROM debian:stable-slim
ARG MINISIG=0.12
ARG ZIG_MINISIG=RWSGOq2NVecA2UPNdBUZykf1CCb147pkmdtYxgb3Ti+JO/wCYvhbAb/U
ARG V8=14.0.365.4
ARG ZIG_V8=v0.4.1
ARG ZIG_V8=v0.4.2
ARG TARGETPLATFORM
RUN apt-get update -yq && \

View File

@@ -5,8 +5,8 @@
.minimum_zig_version = "0.15.2",
.dependencies = .{
.v8 = .{
.url = "https://github.com/lightpanda-io/zig-v8-fork/archive/refs/tags/v0.4.1.tar.gz",
.hash = "v8-0.0.0-xddH672HBAA1hQIa2Uv4mzs_qHC9-Py-M5ssqSSVhWtK",
.url = "https://github.com/lightpanda-io/zig-v8-fork/archive/refs/tags/v0.4.2.tar.gz",
.hash = "v8-0.0.0-xddH672HBABNrbtyNk9o4QXxQJTlpjiCscmdEQuMvKnR",
},
// .v8 = .{ .path = "../zig-v8-fork" },
.brotli = .{

View File

@@ -3753,7 +3753,16 @@ pub fn submitForm(self: *Frame, submitter_: ?*Element, form_: ?*Element.Html.For
const arena = try self._session.getArena(.medium, "submitForm");
errdefer self._session.releaseArena(arena);
const enctype = form_element.getAttributeSafe(comptime .wrap("enctype"));
// Per HTML spec form-submission algorithm, when the submitter is a submit
// button, its formaction/formmethod/formenctype attributes override the
// form's corresponding attributes (matching how formtarget is honored above).
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-form-submit
const enctype = blk: {
if (submitter_) |s| {
if (s.getAttributeSafe(comptime .wrap("formenctype"))) |fe| break :blk fe;
}
break :blk form_element.getAttributeSafe(comptime .wrap("enctype"));
};
// Get charset from accept-charset attribute or fall back to document charset
const charset: []const u8 = blk: {
@@ -3770,8 +3779,18 @@ pub fn submitForm(self: *Frame, submitter_: ?*Element, form_: ?*Element.Html.For
var buf = std.Io.Writer.Allocating.init(arena);
try form_data.write(.{ .enctype = enctype, .charset = charset, .allocator = arena }, &buf.writer);
const method = form_element.getAttributeSafe(comptime .wrap("method")) orelse "";
var action = form_element.getAttributeSafe(comptime .wrap("action")) orelse self.url;
const method = blk: {
if (submitter_) |s| {
if (s.getAttributeSafe(comptime .wrap("formmethod"))) |fm| break :blk fm;
}
break :blk form_element.getAttributeSafe(comptime .wrap("method")) orelse "";
};
var action = blk: {
if (submitter_) |s| {
if (s.getAttributeSafe(comptime .wrap("formaction"))) |fa| break :blk fa;
}
break :blk form_element.getAttributeSafe(comptime .wrap("action")) orelse self.url;
};
var opts = NavigateOpts{
.reason = .form,

View File

@@ -569,7 +569,12 @@ pub fn generateConstructor(comptime JsApi: type, isolate: *v8.Isolate) *const v8
break :blk illegalConstructorCallback;
};
const template = v8.v8__FunctionTemplate__New__DEFAULT2(isolate, callback).?;
const arity: c_int = if (@hasDecl(JsApi, "constructor")) JsApi.constructor.arity else 0;
const template = v8.v8__FunctionTemplate__New__Config(isolate, &.{
.length = arity,
.callback = callback,
.behavior = v8.kConstructorBehavior_Allow,
}).?;
{
const internal_field_count = comptime countInternalFields(JsApi);
if (internal_field_count > 0) {

View File

@@ -107,6 +107,7 @@ pub fn Builder(comptime T: type) type {
}
pub const Constructor = struct {
arity: c_int,
func: *const fn (?*const v8.FunctionCallbackInfo) callconv(.c) void,
const Opts = struct {
@@ -118,21 +119,24 @@ pub const Constructor = struct {
};
fn init(comptime T: type, comptime func: anytype, comptime opts: Opts) Constructor {
return .{ .func = struct {
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
const v8_isolate = v8.v8__FunctionCallbackInfo__GetIsolate(handle).?;
var caller: Caller = undefined;
if (!caller.init(v8_isolate)) {
return;
}
defer caller.deinit();
return .{
.arity = comptime Function.getArity(@TypeOf(func), if (opts.new_target) 1 else 0),
.func = struct {
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
const v8_isolate = v8.v8__FunctionCallbackInfo__GetIsolate(handle).?;
var caller: Caller = undefined;
if (!caller.init(v8_isolate)) {
return;
}
defer caller.deinit();
caller.constructor(T, func, handle.?, .{
.dom_exception = opts.dom_exception,
.new_target = opts.new_target,
});
}
}.wrap };
caller.constructor(T, func, handle.?, .{
.dom_exception = opts.dom_exception,
.new_target = opts.new_target,
});
}
}.wrap,
};
}
};
@@ -149,7 +153,7 @@ pub const Function = struct {
.cache = opts.cache,
.static = opts.static,
.wpt_only = opts.wpt_only,
.arity = getArity(@TypeOf(func)),
.arity = getArity(@TypeOf(func), 1),
.func = if (opts.noop) noopFunction else struct {
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
Caller.Function.call(T, handle.?, func, opts);
@@ -160,14 +164,32 @@ pub const Function = struct {
pub fn noopFunction(_: ?*const v8.FunctionCallbackInfo) callconv(.c) void {}
fn getArity(comptime T: type) usize {
fn getArity(comptime T: type, comptime start: usize) usize {
const Execution = js.Execution;
const Page = @import("../Page.zig");
const Session = @import("../Session.zig");
var count: usize = 0;
var params = @typeInfo(T).@"fn".params;
for (params[1..]) |p| { // start at 1, skip self
for (params[start..]) |p| { // start at 1, skip self
const PT = p.type.?;
if (PT == *Frame or PT == *const Frame) {
break;
}
if (PT == *Page or PT == *const Page) {
break;
}
if (PT == *Execution or PT == *const Execution) {
break;
}
if (PT == *Session or PT == *const Session) {
break;
}
if (@typeInfo(PT) == .optional) {
break;
}

View File

@@ -35,5 +35,11 @@
const append = Object.getOwnPropertyDescriptor(Element.prototype, 'append');
testing.expectEqual('append', append.value.name);
// Constructor.length should equal the number of required arguments (1 for
// events: the type). Sentinel for the New__Config wiring in Snapshot.zig.
testing.expectEqual(1, MouseEvent.length);
testing.expectEqual(1, KeyboardEvent.length);
testing.expectEqual(1, Event.length);
}
</script>

View File

@@ -56,3 +56,73 @@
});
}
</script>
<!-- Per HTML spec, formaction on the submit button overrides the form's action.
https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-form-submit -->
<iframe name=frame4 id=f4></iframe>
<form target="frame4" action="support/should-not-load.html">
<input type=submit id=submit_formaction formaction="support/page.html">
</form>
<script id=formaction type=module>
{
const state = await testing.async();
$('#submit_formaction').click();
$('#f4').onload = () => {
state.resolve();
};
await state.done(() => {
testing.expectEqual('a-page\n', $('#f4').contentDocument.body.textContent);
});
}
</script>
<!-- Per HTML spec, formmethod on the submit button overrides the form's method.
A form with method=GET would append the FormData as ?x=probe to the action;
formmethod=POST should suppress that and send the values in the request body.
We verify the override by asserting the loaded iframe URL has no query string. -->
<iframe name=frame5 id=f5></iframe>
<form target="frame5" method="GET" action="support/page.html">
<input name="x" value="probe">
<input type=submit id=submit_formmethod formmethod="POST">
</form>
<script id=formmethod type=module>
{
const state = await testing.async();
$('#submit_formmethod').click();
$('#f5').onload = () => {
state.resolve();
};
await state.done(() => {
// formmethod=POST overrode form's GET, so the FormData entry was NOT
// appended to the action URL as a query string.
testing.expectEqual(false, $('#f5').contentDocument.URL.includes('?'));
testing.expectEqual('a-page\n', $('#f5').contentDocument.body.textContent);
});
}
</script>
<!-- Combined: formaction + formmethod on the same submit button. -->
<iframe name=frame6 id=f6></iframe>
<form target="frame6" method="GET" action="support/should-not-load.html">
<input name="x" value="probe">
<input type=submit id=submit_form_both formaction="support/page.html" formmethod="POST">
</form>
<script id=formaction_and_formmethod type=module>
{
const state = await testing.async();
$('#submit_form_both').click();
$('#f6').onload = () => {
state.resolve();
};
await state.done(() => {
testing.expectEqual(false, $('#f6').contentDocument.URL.includes('?'));
testing.expectEqual('a-page\n', $('#f6').contentDocument.body.textContent);
});
}
</script>