LibWeb/Bindings: Share more interface constructors

Split the generator decision for shared prototype and constructor
objects. Interfaces with custom prototype behavior can still use the
shared InterfaceConstructor when the constructor object itself has no
custom GC cell state.

Teach Intrinsics how to register a shared constructor for an existing
custom prototype object, and keep LegacyFactoryFunction aliases wired up
when the primary interface objects are shared.
This commit is contained in:
Andreas Kling 2026-05-09 14:08:15 +02:00 committed by Andreas Kling
parent 8a259c1cde
commit 0dd6b32a1b
4 changed files with 123 additions and 28 deletions

View file

@ -56,15 +56,20 @@ JS::Object& Intrinsics::existing_web_prototype(FlyString const& class_name)
void Intrinsics::create_web_prototype_and_constructor(JS::Realm& realm, InterfaceObjectMetadata const& metadata)
{
auto& vm = realm.vm();
auto prototype = realm.create<InterfacePrototypeObject>(realm, metadata);
m_prototypes.set(FlyString::from_utf8_without_validation(metadata.namespaced_name.bytes()), prototype);
create_web_constructor(realm, metadata, prototype);
}
void Intrinsics::create_web_constructor(JS::Realm& realm, InterfaceObjectMetadata const& metadata, JS::Object& prototype)
{
auto& vm = realm.vm();
auto constructor = realm.create<InterfaceConstructor>(realm, metadata);
m_constructors.set(FlyString::from_utf8_without_validation(metadata.namespaced_name.bytes()), constructor);
prototype->define_direct_property(vm.names.constructor, constructor.ptr(), JS::Attribute::Writable | JS::Attribute::Configurable);
prototype.define_direct_property(vm.names.constructor, constructor.ptr(), JS::Attribute::Writable | JS::Attribute::Configurable);
}
}

View file

@ -98,6 +98,7 @@ private:
template<typename PrototypeType>
void create_web_prototype_and_constructor(JS::Realm& realm);
void create_web_prototype_and_constructor(JS::Realm& realm, InterfaceObjectMetadata const&);
void create_web_constructor(JS::Realm& realm, InterfaceObjectMetadata const&, JS::Object& prototype);
HashMap<FlyString, GC::Ref<JS::Object>> m_namespaces;
HashMap<FlyString, GC::Ref<JS::Object>> m_prototypes;

View file

@ -153,12 +153,15 @@ def should_have_interface_object(interface: Interface) -> bool:
return True
def can_use_shared_interface_objects(interface: Interface) -> bool:
def can_use_shared_interface_constructor(interface: Interface) -> bool:
return not interface.is_namespace and not interface.is_callback_interface
def can_use_shared_interface_prototype(interface: Interface) -> bool:
return (
not interface.is_namespace
and not interface.is_callback_interface
and "Global" not in interface.extended_attributes
and lookup_legacy_constructor(interface) is None
and not interface.has_special_member
and interface.named_property_getter is None
and interface.indexed_property_getter is None
@ -397,7 +400,7 @@ void Intrinsics::create_web_namespace<{interface.namespace_class}>(JS::Realm& re
def write_interface_creation(out: TextIO, interface: Interface) -> None:
if can_use_shared_interface_objects(interface):
if can_use_shared_interface_prototype(interface):
out.write(
f"""template<>
WEB_API void Intrinsics::create_web_prototype_and_constructor<{interface.prototype_class}>(JS::Realm& realm)
@ -424,7 +427,77 @@ WEB_API void Intrinsics::create_web_prototype_and_constructor<{interface.prototy
.has_immutable_prototype = {str(interface_prototype_has_immutable_prototype(interface)).lower()},
}};
create_web_prototype_and_constructor(realm, metadata);
}}
"""
)
legacy_constructor = lookup_legacy_constructor(interface)
if legacy_constructor is not None:
out.write(
f""" auto legacy_constructor = realm.create<{legacy_constructor.constructor_class}>(realm);
m_constructors.set("{legacy_constructor.name}"_fly_string, legacy_constructor.ptr());
"""
)
out.write(
"""}
"""
)
return
if can_use_shared_interface_constructor(interface):
out.write(
f"""template<>
WEB_API void Intrinsics::create_web_prototype_and_constructor<{interface.prototype_class}>(JS::Realm& realm)
{{
"""
)
ensure_parent_constructor = "nullptr"
if interface.parent_name:
ensure_parent_constructor = f"""[](JS::Realm& realm) -> JS::NativeFunction& {{ return Web::Bindings::ensure_web_constructor<{interface.parent_name}Prototype>(realm, "{interface.parent_name}"_fly_string); }}"""
out.write(
f""" static constexpr InterfaceObjectMetadata metadata {{
.name = "{interface.name}"sv,
.namespaced_name = "{interface.namespaced_name}"sv,
.ensure_parent_constructor = {ensure_parent_constructor},
.initialize_constructor = &{interface.constructor_class}::initialize,
.construct = &{interface.constructor_class}::construct,
}};
"""
)
named_properties_class = ""
if "Global" in interface.extended_attributes and interface.supports_named_properties():
named_properties_class = f"{interface.name}Properties"
if named_properties_class:
out.write(
f""" auto named_properties_object = realm.create<{named_properties_class}>(realm);
m_prototypes.set("{named_properties_class}"_fly_string, named_properties_object);
"""
)
out.write(
f""" auto prototype = realm.create<{interface.prototype_class}>(realm);
m_prototypes.set("{interface.namespaced_name}"_fly_string, prototype);
create_web_constructor(realm, metadata, prototype);
"""
)
legacy_constructor = lookup_legacy_constructor(interface)
if legacy_constructor is not None:
out.write(
f""" auto legacy_constructor = realm.create<{legacy_constructor.constructor_class}>(realm);
m_constructors.set("{legacy_constructor.name}"_fly_string, legacy_constructor.ptr());
"""
)
out.write(
"""}
"""
)
return

View file

@ -4485,12 +4485,17 @@ static bool interface_prototype_has_immutable_prototype(IDL::Interface const& in
|| interface.name == "EventTarget"sv;
}
static bool can_use_shared_interface_objects(IDL::Interface const& interface)
static bool can_use_shared_interface_constructor(IDL::Interface const& interface)
{
return !interface.is_namespace
&& !interface.is_callback_interface;
}
static bool can_use_shared_interface_prototype(IDL::Interface const& interface)
{
return !interface.is_namespace
&& !interface.is_callback_interface
&& !interface.extended_attributes.contains("Global")
&& !interface.extended_attributes.contains("LegacyFactoryFunction")
&& !interface.value_iterator_type.has_value()
&& !interface.pair_iterator_types.has_value()
&& !interface.async_value_iterator_type.has_value()
@ -7169,10 +7174,12 @@ namespace Web::Bindings {
)~~~"sv);
if (can_use_shared_interface_objects(interface)) {
auto can_use_shared_constructor = can_use_shared_interface_constructor(interface);
auto can_use_shared_prototype = can_use_shared_interface_prototype(interface);
if (can_use_shared_constructor) {
SourceGenerator generator { builder };
generator.set("constructor_class", interface.constructor_class);
generator.set("prototype_class", interface.prototype_class);
generator.append(R"~~~(
struct @constructor_class@ {
public:
@ -7184,7 +7191,13 @@ private:
generate_constructor_declarations(interface, builder);
generator.append(R"~~~(
};
)~~~");
}
if (can_use_shared_prototype) {
SourceGenerator generator { builder };
generator.set("prototype_class", interface.prototype_class);
generator.append(R"~~~(
struct @prototype_class@ {
public:
static void initialize(JS::Realm&, JS::Object&);
@ -7202,8 +7215,10 @@ private:
if (interface.is_namespace) {
generate_namespace_header(interface, builder);
} else {
generate_constructor_header(interface, builder);
generate_prototype_header(interface, builder);
if (!can_use_shared_constructor)
generate_constructor_header(interface, builder);
if (!can_use_shared_prototype)
generate_prototype_header(interface, builder);
}
if (interface.pair_iterator_types.has_value())
@ -7262,27 +7277,28 @@ namespace Web::Bindings {
static void generate_implementation_for_interface(IDL::Interface const& interface, StringBuilder& builder)
{
if (can_use_shared_interface_objects(interface)) {
generate_implementation_prologue(interface, builder);
generate_constructor_initialization_for_existing_object(interface, builder);
generate_constructor_static_member_definitions(interface, builder);
generate_prototype_or_global_mixin_initialization(interface, builder, GenerateUnforgeables::No, InitializeExistingObject::Yes);
generate_prototype_or_global_mixin_initialization(interface, builder, GenerateUnforgeables::Yes);
generate_prototype_or_global_mixin_definitions(interface, builder);
generate_idl_value_conversion_implementations(module_for_path(interface.context, interface.module_own_path), builder);
builder.append(R"~~~(
} // namespace Web::Bindings
)~~~"sv);
return;
}
auto can_use_shared_constructor = can_use_shared_interface_constructor(interface);
auto can_use_shared_prototype = can_use_shared_interface_prototype(interface);
generate_implementation_prologue(interface, builder);
if (interface.is_namespace) {
generate_namespace_implementation(interface, builder);
} else {
generate_constructor_implementation(interface, builder);
generate_prototype_implementation(interface, builder);
if (can_use_shared_constructor) {
generate_constructor_initialization_for_existing_object(interface, builder);
generate_constructor_static_member_definitions(interface, builder);
} else {
generate_constructor_implementation(interface, builder);
}
if (can_use_shared_prototype) {
generate_prototype_or_global_mixin_initialization(interface, builder, GenerateUnforgeables::No, InitializeExistingObject::Yes);
generate_prototype_or_global_mixin_initialization(interface, builder, GenerateUnforgeables::Yes);
generate_prototype_or_global_mixin_definitions(interface, builder);
} else {
generate_prototype_implementation(interface, builder);
}
}
if (interface.pair_iterator_types.has_value())