mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-08-02 18:59:36 -04:00
js: support [LegacyUnforgeable] accessors; make Event.isTrusted one
Fixes the failing test in WPT /dom/events/Event-isTrusted.any.html (and its .worker variant): Object.getOwnPropertyDescriptor(event, "isTrusted") returned undefined because the accessor lived on Event.prototype. Per Web IDL, isTrusted is [LegacyUnforgeable]: an own, non-configurable accessor property of every Event instance, with the same getter function shared between instances. - bridge.accessor gains an `unforgeable` option. Such accessors are installed on the class's v8 instance template instead of the prototype template, so every instance gets an own accessor backed by one getter function per context. - Since v8's Inherit only chains prototype templates, attachClass now walks the ancestor chain (via the _proto fields) and re-installs ancestors' unforgeable accessors on the subclass's instance template, so MouseEvent instances & co. get their own isTrusted as well. The accessor installation code is extracted into attachAccessorProperty for reuse. - Event.isTrusted is declared unforgeable and non-deletable. Coverage: /dom/events/Event-isTrusted.any.html 0/1 -> 1/1, /dom/events/Event-isTrusted.any.worker.html 0/1 -> 1/1. No regressions across /dom/events. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
committed by
Karl Seguin
parent
2623be4493
commit
0b2cfc35f4
@@ -719,6 +719,10 @@ pub const Function = struct {
|
||||
exposed: Exposed = .both,
|
||||
ce_reactions: bool = false,
|
||||
js_name: ?[:0]const u8 = null,
|
||||
// Web IDL [LegacyUnforgeable]: the accessor is an own property of
|
||||
// every instance (including subclass instances) instead of living on
|
||||
// the interface prototype object.
|
||||
unforgeable: bool = false,
|
||||
|
||||
pub const Exposed = enum { both, window, worker };
|
||||
|
||||
|
||||
@@ -757,6 +757,52 @@ fn inheritsFromHtmlElement(comptime JsApi: type) bool {
|
||||
// and define_on != null. This is the "flattening" pass, and it defines all of
|
||||
// the functions/accessors on directly on the global instance. Thus, globals have
|
||||
// it defined on both their prototype (first pass) and their own instance (2nd pass).
|
||||
fn attachAccessorProperty(comptime name: [:0]const u8, value: bridge.Accessor, isolate: *v8.Isolate, template: *const v8.FunctionTemplate, signature: anytype, target: anytype) void {
|
||||
const js_name = v8.v8__String__NewFromUtf8(isolate, name.ptr, v8.kNormal, @intCast(name.len));
|
||||
const getter_signature = if (value.static) null else signature;
|
||||
const getter_callback = v8.v8__FunctionTemplate__New__Config(isolate, &.{
|
||||
.callback = value.getter,
|
||||
.signature = getter_signature,
|
||||
}).?;
|
||||
// WebIDL: getter function's .name should be "get X"
|
||||
const getter_name_str = "get " ++ name;
|
||||
const getter_name_v8 = v8.v8__String__NewFromUtf8(isolate, getter_name_str.ptr, v8.kNormal, @intCast(getter_name_str.len));
|
||||
v8.v8__FunctionTemplate__SetClassName(getter_callback, getter_name_v8);
|
||||
|
||||
const setter_callback = if (value.setter) |setter| blk: {
|
||||
const cb = v8.v8__FunctionTemplate__New__Config(isolate, &.{
|
||||
.callback = setter,
|
||||
.signature = getter_signature,
|
||||
.length = 1,
|
||||
}).?;
|
||||
const setter_name_str = "set " ++ name;
|
||||
const setter_name_v8 = v8.v8__String__NewFromUtf8(isolate, setter_name_str.ptr, v8.kNormal, @intCast(setter_name_str.len));
|
||||
v8.v8__FunctionTemplate__SetClassName(cb, setter_name_v8);
|
||||
break :blk cb;
|
||||
} else null;
|
||||
|
||||
var attribute: v8.PropertyAttribute = 0;
|
||||
if (value.setter == null) {
|
||||
attribute |= v8.ReadOnly;
|
||||
}
|
||||
if (value.deletable == false) {
|
||||
attribute |= v8.DontDelete;
|
||||
}
|
||||
|
||||
if (value.static) {
|
||||
v8.v8__Template__SetAccessorProperty(@ptrCast(template), js_name, getter_callback, setter_callback, attribute);
|
||||
} else {
|
||||
// Web IDL: attributes on the interface prototype object
|
||||
// (and mirrored onto [Global] instances) are enumerable.
|
||||
v8.v8__ObjectTemplate__SetAccessorProperty__Config(target, &.{
|
||||
.key = js_name,
|
||||
.getter = getter_callback,
|
||||
.setter = setter_callback,
|
||||
.attribute = attribute,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn attachClass(comptime JsApi: type, comptime flatten: bool, isolate: *v8.Isolate, template: *const v8.FunctionTemplate, define_on: ?*const v8.ObjectTemplate) void {
|
||||
const instance = v8.v8__FunctionTemplate__InstanceTemplate(template);
|
||||
const prototype = v8.v8__FunctionTemplate__PrototypeTemplate(template);
|
||||
@@ -792,49 +838,10 @@ fn attachClass(comptime JsApi: type, comptime flatten: bool, isolate: *v8.Isolat
|
||||
continue;
|
||||
}
|
||||
|
||||
const js_name = v8.v8__String__NewFromUtf8(isolate, name.ptr, v8.kNormal, @intCast(name.len));
|
||||
const getter_signature = if (value.static) null else signature;
|
||||
const getter_callback = v8.v8__FunctionTemplate__New__Config(isolate, &.{
|
||||
.callback = value.getter,
|
||||
.signature = getter_signature,
|
||||
}).?;
|
||||
// WebIDL: getter function's .name should be "get X"
|
||||
const getter_name_str = "get " ++ name;
|
||||
const getter_name_v8 = v8.v8__String__NewFromUtf8(isolate, getter_name_str.ptr, v8.kNormal, @intCast(getter_name_str.len));
|
||||
v8.v8__FunctionTemplate__SetClassName(getter_callback, getter_name_v8);
|
||||
|
||||
const setter_callback = if (value.setter) |setter| blk: {
|
||||
const cb = v8.v8__FunctionTemplate__New__Config(isolate, &.{
|
||||
.callback = setter,
|
||||
.signature = getter_signature,
|
||||
.length = 1,
|
||||
}).?;
|
||||
const setter_name_str = "set " ++ name;
|
||||
const setter_name_v8 = v8.v8__String__NewFromUtf8(isolate, setter_name_str.ptr, v8.kNormal, @intCast(setter_name_str.len));
|
||||
v8.v8__FunctionTemplate__SetClassName(cb, setter_name_v8);
|
||||
break :blk cb;
|
||||
} else null;
|
||||
|
||||
var attribute: v8.PropertyAttribute = 0;
|
||||
if (value.setter == null) {
|
||||
attribute |= v8.ReadOnly;
|
||||
}
|
||||
if (value.deletable == false) {
|
||||
attribute |= v8.DontDelete;
|
||||
}
|
||||
|
||||
if (value.static) {
|
||||
v8.v8__Template__SetAccessorProperty(@ptrCast(template), js_name, getter_callback, setter_callback, attribute);
|
||||
} else {
|
||||
// Web IDL: attributes on the interface prototype object
|
||||
// (and mirrored onto [Global] instances) are enumerable.
|
||||
v8.v8__ObjectTemplate__SetAccessorProperty__Config(define_on orelse prototype, &.{
|
||||
.key = js_name,
|
||||
.getter = getter_callback,
|
||||
.setter = setter_callback,
|
||||
.attribute = attribute,
|
||||
});
|
||||
}
|
||||
// [LegacyUnforgeable] accessors live on the instances, not on
|
||||
// the interface prototype object.
|
||||
const target = if (value.unforgeable) instance else (define_on orelse prototype);
|
||||
attachAccessorProperty(name, value, isolate, template, signature, target);
|
||||
},
|
||||
bridge.Function => {
|
||||
if (value.wpt_only and wpt_extensions_enabled == false) {
|
||||
@@ -920,6 +927,26 @@ fn attachClass(comptime JsApi: type, comptime flatten: bool, isolate: *v8.Isolat
|
||||
}
|
||||
}
|
||||
|
||||
// Web IDL [LegacyUnforgeable]: unforgeable accessors of ancestor
|
||||
// interfaces are own properties of every subclass instance too. v8's
|
||||
// Inherit only chains prototype templates, not instance templates, so
|
||||
// re-install them on this class's instance template.
|
||||
if (comptime !flatten) {
|
||||
comptime var T = JsApi.bridge.type;
|
||||
inline while (comptime @hasField(T, "_proto")) {
|
||||
const P = @typeInfo(std.meta.fieldInfo(T, ._proto).type).pointer.child;
|
||||
inline for (@typeInfo(P.JsApi).@"struct".decls) |pd| {
|
||||
const pvalue = @field(P.JsApi, pd.name);
|
||||
if (comptime @TypeOf(pvalue) == bridge.Accessor) {
|
||||
if (comptime (pvalue.unforgeable and !pvalue.static)) {
|
||||
attachAccessorProperty(pd.name, pvalue, isolate, template, signature, instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
T = P;
|
||||
}
|
||||
}
|
||||
|
||||
// The remaining per-class setup targets the class's own instance template;
|
||||
// in [Global] flattening mode the global already has these (or doesn't need
|
||||
// them), so skip it.
|
||||
|
||||
@@ -230,6 +230,7 @@ pub const Accessor = struct {
|
||||
static: bool = false,
|
||||
deletable: bool = true,
|
||||
wpt_only: bool = false,
|
||||
unforgeable: bool = false,
|
||||
exposed: Caller.Function.Opts.Exposed = .both,
|
||||
cache: ?Caller.Function.Opts.Caching = null,
|
||||
getter: ?*const fn (?*const v8.FunctionCallbackInfo) callconv(.c) void = null,
|
||||
@@ -241,6 +242,7 @@ pub const Accessor = struct {
|
||||
.static = opts.static,
|
||||
.wpt_only = opts.wpt_only,
|
||||
.deletable = opts.deletable,
|
||||
.unforgeable = opts.unforgeable,
|
||||
.exposed = opts.exposed,
|
||||
};
|
||||
|
||||
|
||||
@@ -481,7 +481,9 @@ pub const JsApi = struct {
|
||||
pub const eventPhase = bridge.accessor(Event.getEventPhase, null, .{});
|
||||
pub const defaultPrevented = bridge.accessor(Event.getDefaultPrevented, null, .{});
|
||||
pub const timeStamp = bridge.accessor(Event.getTimeStamp, null, .{});
|
||||
pub const isTrusted = bridge.accessor(Event.getIsTrusted, null, .{});
|
||||
// [LegacyUnforgeable]: an own, non-configurable accessor of every
|
||||
// Event (and subclass) instance.
|
||||
pub const isTrusted = bridge.accessor(Event.getIsTrusted, null, .{ .unforgeable = true, .deletable = false });
|
||||
pub const preventDefault = bridge.function(Event.preventDefault, .{});
|
||||
pub const stopPropagation = bridge.function(Event.stopPropagation, .{});
|
||||
pub const stopImmediatePropagation = bridge.function(Event.stopImmediatePropagation, .{});
|
||||
|
||||
Reference in New Issue
Block a user