mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-10-08 20:32:00 -04:00
webapi: Legacy constuctor API
We previously supported the legacy constructors via new Image() and new Audio() and https://github.com/lightpanda-io/browser/pull/3620 adds support for new Option(). In most cases, that was good enough, but it wasn't quite up to spec, e.g. new HTMLImageElement() still worked and HtMLImageElement showed up in window. This PR cleans it up by giving each one a distinct constructor for their legacy function and NOT creating the default one bridge.constructor would otherwise create.
This commit is contained in:
4 files changed
+105
-15
No files matched your search
+45
-15
@@ -325,22 +325,18 @@ fn createSnapshotContext(
|
||||
const template_index = comptime bridge.JsApiLookup.getId(JsApi);
|
||||
const func = v8.v8__FunctionTemplate__GetFunction(templates[template_index], context);
|
||||
if (@hasDecl(JsApi.Meta, "name")) {
|
||||
const name = JsApi.Meta.name;
|
||||
const v8_class_name = v8.v8__String__NewFromUtf8(isolate, name.ptr, v8.kNormal, @intCast(name.len));
|
||||
var maybe_result: v8.MaybeBool = undefined;
|
||||
// Web IDL: interface objects on the global are non-enumerable.
|
||||
v8.v8__Object__DefineOwnProperty(global_obj, context, v8_class_name, func, v8.DontEnum, &maybe_result);
|
||||
|
||||
if (@hasDecl(JsApi.Meta, "constructor_alias")) {
|
||||
const alias = JsApi.Meta.constructor_alias;
|
||||
const v8_class_name = v8.v8__String__NewFromUtf8(isolate, alias.ptr, v8.kNormal, @intCast(alias.len));
|
||||
var maybe_result: v8.MaybeBool = undefined;
|
||||
v8.v8__Object__Set(global_obj, context, v8_class_name, func, &maybe_result);
|
||||
|
||||
const name = JsApi.Meta.name;
|
||||
const illegal_class_name = v8.v8__String__NewFromUtf8(isolate, name.ptr, v8.kNormal, @intCast(name.len));
|
||||
var maybe_result2: v8.MaybeBool = undefined;
|
||||
v8.v8__Object__DefineOwnProperty(global_obj, context, illegal_class_name, func, 0, &maybe_result2);
|
||||
} else {
|
||||
const name = JsApi.Meta.name;
|
||||
const v8_class_name = v8.v8__String__NewFromUtf8(isolate, name.ptr, v8.kNormal, @intCast(name.len));
|
||||
var maybe_result: v8.MaybeBool = undefined;
|
||||
// Web IDL: interface objects on the global are non-enumerable.
|
||||
v8.v8__Object__DefineOwnProperty(global_obj, context, v8_class_name, func, v8.DontEnum, &maybe_result);
|
||||
const alias_func = generateLegacyFactoryFunction(JsApi, isolate, templates[template_index], func.?, context.?);
|
||||
const v8_alias_name = v8.v8__String__NewFromUtf8(isolate, alias.ptr, v8.kNormal, @intCast(alias.len));
|
||||
var maybe_alias_result: v8.MaybeBool = undefined;
|
||||
v8.v8__Object__DefineOwnProperty(global_obj, context, v8_alias_name, @ptrCast(alias_func), v8.DontEnum, &maybe_alias_result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -720,7 +716,9 @@ fn protoIndexLookup(comptime JsApi: type) ?u16 {
|
||||
// Generate a constructor template for a JsApi type (public for reuse)
|
||||
fn generateConstructor(comptime JsApi: type, isolate: *v8.Isolate) *const v8.FunctionTemplate {
|
||||
const callback, const arity = comptime blk: {
|
||||
if (@hasDecl(JsApi, "constructor")) {
|
||||
// The constructor belongs to the legacy factory function (`Image`),
|
||||
// see generateLegacyFactoryFunction.
|
||||
if (@hasDecl(JsApi, "constructor") and !@hasDecl(JsApi.Meta, "constructor_alias")) {
|
||||
break :blk .{ JsApi.constructor.func, JsApi.constructor.arity };
|
||||
}
|
||||
if (inheritsFromHtmlElement(JsApi)) {
|
||||
@@ -748,6 +746,38 @@ fn generateConstructor(comptime JsApi: type, isolate: *v8.Isolate) *const v8.Fun
|
||||
return template;
|
||||
}
|
||||
|
||||
// https://webidl.spec.whatwg.org/#legacy-factory-functions
|
||||
// `Image`, `Audio`, `Option`: a function distinct from the interface object
|
||||
// (so `new HTMLImageElement()` stays illegal and `Image.name` is "Image"),
|
||||
// whose `prototype` is the interface's prototype object.
|
||||
fn generateLegacyFactoryFunction(comptime JsApi: type, isolate: *v8.Isolate, interface_template: *const v8.FunctionTemplate, interface_func: *const v8.Function, context: *const v8.Context) *const v8.Function {
|
||||
const alias = JsApi.Meta.constructor_alias;
|
||||
const template = v8.v8__FunctionTemplate__New__Config(isolate, &.{
|
||||
.length = JsApi.constructor.arity,
|
||||
.callback = JsApi.constructor.func,
|
||||
.behavior = v8.kConstructorBehavior_Allow,
|
||||
}).?;
|
||||
// Inherit so that the objects we construct pass the interface's
|
||||
// accessor and method signature checks.
|
||||
v8.v8__FunctionTemplate__Inherit(template, interface_template);
|
||||
{
|
||||
const internal_field_count = comptime countInternalFields(JsApi);
|
||||
if (internal_field_count > 0) {
|
||||
const instance_template = v8.v8__FunctionTemplate__InstanceTemplate(template);
|
||||
v8.v8__ObjectTemplate__SetInternalFieldCount(instance_template, internal_field_count);
|
||||
}
|
||||
}
|
||||
const class_name = v8.v8__String__NewFromUtf8(isolate, alias.ptr, v8.kNormal, @intCast(alias.len));
|
||||
v8.v8__FunctionTemplate__SetClassName(template, class_name);
|
||||
|
||||
const func = v8.v8__FunctionTemplate__GetFunction(template, context).?;
|
||||
const prototype_key = v8.v8__String__NewFromUtf8(isolate, "prototype", v8.kNormal, 9);
|
||||
const interface_prototype = v8.v8__Object__Get(@ptrCast(interface_func), context, prototype_key).?;
|
||||
var maybe_result: v8.MaybeBool = undefined;
|
||||
v8.v8__Object__DefineOwnProperty(@ptrCast(func), context, prototype_key, interface_prototype, v8.ReadOnly + v8.DontEnum + v8.DontDelete, &maybe_result);
|
||||
return func;
|
||||
}
|
||||
|
||||
// hard-coded special case for HtmlElement which can be extended but not
|
||||
// instantiated.
|
||||
fn inheritsFromHtmlElement(comptime JsApi: type) bool {
|
||||
|
||||
@@ -179,3 +179,23 @@
|
||||
img.src = 'data:image/gif;base64,R0lGODlhAQABAAAAACw=';
|
||||
testing.expectEqual(undefined, await img.decode());
|
||||
</script>
|
||||
|
||||
<script id="legacy_factory_function">
|
||||
// https://webidl.spec.whatwg.org/#legacy-factory-functions
|
||||
testing.expectEqual(true, Image !== HTMLImageElement)
|
||||
testing.expectEqual('Image', Image.name)
|
||||
testing.expectEqual(0, Image.length)
|
||||
testing.expectEqual(HTMLImageElement.prototype, Image.prototype)
|
||||
testing.expectEqual(HTMLImageElement, new Image().constructor)
|
||||
testing.expectEqual('img', new Image().localName)
|
||||
testing.expectError('TypeError', () => new HTMLImageElement())
|
||||
testing.expectError('TypeError', () => Image())
|
||||
|
||||
const desc = Object.getOwnPropertyDescriptor(Image, 'prototype')
|
||||
testing.expectEqual(false, desc.writable)
|
||||
testing.expectEqual(false, desc.enumerable)
|
||||
testing.expectEqual(false, desc.configurable)
|
||||
|
||||
testing.expectEqual(false, Object.keys(window).includes('Image'))
|
||||
testing.expectEqual(false, Object.keys(window).includes('HTMLImageElement'))
|
||||
</script>
|
||||
@@ -308,3 +308,23 @@
|
||||
testing.expectEqual(true, video.paused);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="legacy_factory_function">
|
||||
// https://webidl.spec.whatwg.org/#legacy-factory-functions
|
||||
testing.expectEqual(true, Audio !== HTMLAudioElement)
|
||||
testing.expectEqual('Audio', Audio.name)
|
||||
testing.expectEqual(0, Audio.length)
|
||||
testing.expectEqual(HTMLAudioElement.prototype, Audio.prototype)
|
||||
testing.expectEqual(HTMLAudioElement, new Audio().constructor)
|
||||
testing.expectEqual('audio', new Audio().localName)
|
||||
testing.expectError('TypeError', () => new HTMLAudioElement())
|
||||
testing.expectError('TypeError', () => Audio())
|
||||
|
||||
const desc = Object.getOwnPropertyDescriptor(Audio, 'prototype')
|
||||
testing.expectEqual(false, desc.writable)
|
||||
testing.expectEqual(false, desc.enumerable)
|
||||
testing.expectEqual(false, desc.configurable)
|
||||
|
||||
testing.expectEqual(false, Object.keys(window).includes('Audio'))
|
||||
testing.expectEqual(false, Object.keys(window).includes('HTMLAudioElement'))
|
||||
</script>
|
||||
@@ -257,3 +257,23 @@
|
||||
testing.expectEqual(1, select.selectedIndex)
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="legacy_factory_function">
|
||||
// https://webidl.spec.whatwg.org/#legacy-factory-functions
|
||||
testing.expectEqual(true, Option !== HTMLOptionElement)
|
||||
testing.expectEqual('Option', Option.name)
|
||||
testing.expectEqual(0, Option.length)
|
||||
testing.expectEqual(HTMLOptionElement.prototype, Option.prototype)
|
||||
testing.expectEqual(HTMLOptionElement, new Option().constructor)
|
||||
testing.expectEqual('option', new Option().localName)
|
||||
testing.expectError('TypeError', () => new HTMLOptionElement())
|
||||
testing.expectError('TypeError', () => Option())
|
||||
|
||||
const desc = Object.getOwnPropertyDescriptor(Option, 'prototype')
|
||||
testing.expectEqual(false, desc.writable)
|
||||
testing.expectEqual(false, desc.enumerable)
|
||||
testing.expectEqual(false, desc.configurable)
|
||||
|
||||
testing.expectEqual(false, Object.keys(window).includes('Option'))
|
||||
testing.expectEqual(false, Object.keys(window).includes('HTMLOptionElement'))
|
||||
</script>
|
||||
Reference in new issue
Block a user