LibWeb/Bindings: Port IDL bindings generator to Python

Replace the Lagom C++ bindings generator invocation with the new Python
generator under Meta/Generators/libweb_bindings.

Fold the exposed-interface generation into the same generator entry
point, and keep generated overload metadata using the existing LibIDL
types for now.
This commit is contained in:
Shannon Booth 2026-06-06 20:13:54 +02:00 committed by Andreas Kling
parent 5c169091de
commit dba62aefc0
25 changed files with 8119 additions and 230 deletions

View file

@ -243,6 +243,7 @@ endfunction()
function (generate_js_bindings target)
set(LIBWEB_INPUT_FOLDER "${CMAKE_CURRENT_SOURCE_DIR}")
find_package(Python3 REQUIRED COMPONENTS Interpreter)
set(generated_idl_targets ${LIBWEB_ALL_GENERATED_IDL})
list(TRANSFORM generated_idl_targets PREPEND "generate_")
set(LIBWEB_ALL_BINDINGS_SOURCES)
@ -283,61 +284,61 @@ function (generate_js_bindings target)
set(LIBWEB_ALL_PARSED_IDL_FILES ${LIBWEB_ALL_PARSED_IDL_FILES} PARENT_SCOPE)
endfunction()
function(generate_exposed_interface_files)
find_package(Python3 REQUIRED COMPONENTS Interpreter)
set(window_or_worker_generator "${LADYBIRD_SOURCE_DIR}/Meta/Generators/generate_window_or_worker_interfaces.py")
set(window_or_worker_generator_dependencies
"${window_or_worker_generator}"
"${LADYBIRD_SOURCE_DIR}/Meta/Utils/lexer.py"
"${LADYBIRD_SOURCE_DIR}/Meta/Utils/webidl_parser.py")
set(bindings_generator "${LADYBIRD_SOURCE_DIR}/Meta/Generators/generate_libweb_bindings.py")
set(bindings_generator_dependencies
"${bindings_generator}"
"${LADYBIRD_SOURCE_DIR}/Meta/Generators/libweb_bindings/__init__.py"
"${LADYBIRD_SOURCE_DIR}/Meta/Generators/libweb_bindings/arguments.py"
"${LADYBIRD_SOURCE_DIR}/Meta/Generators/libweb_bindings/attributes.py"
"${LADYBIRD_SOURCE_DIR}/Meta/Generators/libweb_bindings/callback_interfaces.py"
"${LADYBIRD_SOURCE_DIR}/Meta/Generators/libweb_bindings/constants.py"
"${LADYBIRD_SOURCE_DIR}/Meta/Generators/libweb_bindings/constructors.py"
"${LADYBIRD_SOURCE_DIR}/Meta/Generators/libweb_bindings/context.py"
"${LADYBIRD_SOURCE_DIR}/Meta/Generators/libweb_bindings/cpp_types.py"
"${LADYBIRD_SOURCE_DIR}/Meta/Generators/libweb_bindings/default_values.py"
"${LADYBIRD_SOURCE_DIR}/Meta/Generators/libweb_bindings/extended_attributes.py"
"${LADYBIRD_SOURCE_DIR}/Meta/Generators/libweb_bindings/global_mixins.py"
"${LADYBIRD_SOURCE_DIR}/Meta/Generators/libweb_bindings/includes.py"
"${LADYBIRD_SOURCE_DIR}/Meta/Generators/libweb_bindings/interface_declaration.py"
"${LADYBIRD_SOURCE_DIR}/Meta/Generators/libweb_bindings/interfaces.py"
"${LADYBIRD_SOURCE_DIR}/Meta/Generators/libweb_bindings/intrinsics.py"
"${LADYBIRD_SOURCE_DIR}/Meta/Generators/libweb_bindings/iterables.py"
"${LADYBIRD_SOURCE_DIR}/Meta/Generators/libweb_bindings/named_and_indexed_properties.py"
"${LADYBIRD_SOURCE_DIR}/Meta/Generators/libweb_bindings/namespaces.py"
"${LADYBIRD_SOURCE_DIR}/Meta/Generators/libweb_bindings/operations.py"
"${LADYBIRD_SOURCE_DIR}/Meta/Generators/libweb_bindings/overload_resolution.py"
"${LADYBIRD_SOURCE_DIR}/Meta/Generators/libweb_bindings/to_idl_value.py"
"${LADYBIRD_SOURCE_DIR}/Meta/Generators/libweb_bindings/to_js_value.py"
"${LADYBIRD_SOURCE_DIR}/Meta/Utils/lexer.py"
"${LADYBIRD_SOURCE_DIR}/Meta/Utils/utils.py"
"${LADYBIRD_SOURCE_DIR}/Meta/Utils/webidl_parser.py")
set(exposed_interface_sources
Forward.h
IntrinsicDefinitions.cpp IntrinsicDefinitions.h
DedicatedWorkerExposedInterfaces.cpp DedicatedWorkerExposedInterfaces.h
SharedWorkerExposedInterfaces.cpp SharedWorkerExposedInterfaces.h
WindowExposedInterfaces.cpp WindowExposedInterfaces.h)
list(TRANSFORM exposed_interface_sources PREPEND "Bindings/")
add_custom_command(
OUTPUT ${exposed_interface_sources}
COMMAND "${CMAKE_COMMAND}" -E make_directory "tmp"
COMMAND "${Python3_EXECUTABLE}" "${window_or_worker_generator}" -o "${CMAKE_CURRENT_BINARY_DIR}/tmp" ${LIBWEB_ALL_IDL_FILES_ARGUMENT}
COMMAND "${CMAKE_COMMAND}" -E copy_if_different tmp/Forward.h "Bindings/Forward.h"
COMMAND "${CMAKE_COMMAND}" -E copy_if_different tmp/IntrinsicDefinitions.h "Bindings/IntrinsicDefinitions.h"
COMMAND "${CMAKE_COMMAND}" -E copy_if_different tmp/IntrinsicDefinitions.cpp "Bindings/IntrinsicDefinitions.cpp"
COMMAND "${CMAKE_COMMAND}" -E copy_if_different tmp/DedicatedWorkerExposedInterfaces.h "Bindings/DedicatedWorkerExposedInterfaces.h"
COMMAND "${CMAKE_COMMAND}" -E copy_if_different tmp/DedicatedWorkerExposedInterfaces.cpp "Bindings/DedicatedWorkerExposedInterfaces.cpp"
COMMAND "${CMAKE_COMMAND}" -E copy_if_different tmp/SharedWorkerExposedInterfaces.h "Bindings/SharedWorkerExposedInterfaces.h"
COMMAND "${CMAKE_COMMAND}" -E copy_if_different tmp/SharedWorkerExposedInterfaces.cpp "Bindings/SharedWorkerExposedInterfaces.cpp"
COMMAND "${CMAKE_COMMAND}" -E copy_if_different tmp/WindowExposedInterfaces.h "Bindings/WindowExposedInterfaces.h"
COMMAND "${CMAKE_COMMAND}" -E copy_if_different tmp/WindowExposedInterfaces.cpp "Bindings/WindowExposedInterfaces.cpp"
COMMAND "${CMAKE_COMMAND}" -E remove_directory "${CMAKE_CURRENT_BINARY_DIR}/tmp"
VERBATIM
DEPENDS ${window_or_worker_generator_dependencies} ${LIBWEB_ALL_IDL_FILES}
)
target_sources(${target} PRIVATE ${exposed_interface_sources})
add_custom_target(generate_exposed_interfaces DEPENDS ${exposed_interface_sources})
add_dependencies(ladybird_codegen_accumulator generate_exposed_interfaces)
add_dependencies(${target} generate_exposed_interfaces)
add_dependencies(generate_exposed_interfaces ${generated_idl_targets})
set(exposed_interface_sources
Forward.h
IntrinsicDefinitions.cpp IntrinsicDefinitions.h
DedicatedWorkerExposedInterfaces.cpp DedicatedWorkerExposedInterfaces.h
SharedWorkerExposedInterfaces.cpp SharedWorkerExposedInterfaces.h
WindowExposedInterfaces.cpp WindowExposedInterfaces.h)
list(TRANSFORM exposed_interface_sources PREPEND "Bindings/")
target_sources(${target} PRIVATE ${exposed_interface_sources})
list(TRANSFORM exposed_interface_sources PREPEND "${CMAKE_CURRENT_BINARY_DIR}/")
set(exposed_interface_headers ${exposed_interface_sources})
list(FILTER exposed_interface_headers INCLUDE REGEX "\.h$")
set(exposed_interface_headers ${exposed_interface_sources})
list(FILTER exposed_interface_headers INCLUDE REGEX "\.h$")
list(TRANSFORM exposed_interface_headers PREPEND "${CMAKE_CURRENT_BINARY_DIR}/")
if (ENABLE_INSTALL_HEADERS)
install(FILES ${exposed_interface_headers} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/LibWeb/Bindings")
endif()
if (ENABLE_INSTALL_HEADERS)
install(FILES ${exposed_interface_headers} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/LibWeb/Bindings")
endif()
list(APPEND LIBWEB_ALL_GENERATED_HEADERS ${exposed_interface_headers})
set(LIBWEB_ALL_GENERATED_HEADERS ${LIBWEB_ALL_GENERATED_HEADERS} PARENT_SCOPE)
endfunction()
list(APPEND LIBWEB_ALL_GENERATED_HEADERS ${exposed_interface_headers})
set(LIBWEB_ALL_GENERATED_HEADERS ${LIBWEB_ALL_GENERATED_HEADERS} PARENT_SCOPE)
include("idl_files.cmake")
list(REMOVE_DUPLICATES LIBWEB_ALL_PARSED_IDL_FILES)
set(LIBWEB_ALL_IDL_FILES_ARGUMENT ${LIBWEB_ALL_IDL_FILES})
set(LIBWEB_ALL_PARSED_IDL_FILES_ARGUMENT ${LIBWEB_ALL_PARSED_IDL_FILES})
set(LIBWEB_BINDINGS_DEPFILE "${CMAKE_CURRENT_BINARY_DIR}/Bindings/LibWebBindings.d")
if (WIN32)
list(JOIN LIBWEB_ALL_IDL_FILES "\n" idl_file_list)
file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/all_idl_files.txt" CONTENT "${idl_file_list}" NEWLINE_STYLE UNIX)
@ -349,16 +350,15 @@ function (generate_js_bindings target)
endif()
add_custom_command(
OUTPUT ${LIBWEB_ALL_BINDINGS_SOURCES}
OUTPUT ${LIBWEB_ALL_BINDINGS_SOURCES} ${exposed_interface_sources}
COMMAND "${CMAKE_COMMAND}" -E make_directory "Bindings"
COMMAND "$<TARGET_FILE:Lagom::BindingsGenerator>" -o "Bindings" --depfile "Bindings/all_bindings.d"
--header-include-path "${CMAKE_CURRENT_SOURCE_DIR}/.."
--header-include-path "${CMAKE_CURRENT_BINARY_DIR}/.."
COMMAND "${Python3_EXECUTABLE}" "${bindings_generator}" -o "Bindings"
--depfile "${LIBWEB_BINDINGS_DEPFILE}"
${LIBWEB_ALL_PARSED_IDL_FILES_ARGUMENT}
VERBATIM
COMMENT "Generating LibWeb bindings"
DEPENDS Lagom::BindingsGenerator ${LIBWEB_ALL_IDL_FILES} ${LIBWEB_ALL_PARSED_IDL_FILES}
DEPFILE ${CMAKE_CURRENT_BINARY_DIR}/Bindings/all_bindings.d
DEPFILE "${LIBWEB_BINDINGS_DEPFILE}"
DEPENDS ${bindings_generator_dependencies} ${LIBWEB_ALL_IDL_FILES} ${LIBWEB_ALL_PARSED_IDL_FILES}
)
add_custom_target(generate_bindings DEPENDS ${LIBWEB_ALL_BINDINGS_SOURCES})
@ -366,7 +366,10 @@ function (generate_js_bindings target)
add_dependencies(${target} generate_bindings)
add_dependencies(generate_bindings ${generated_idl_targets})
generate_exposed_interface_files()
add_custom_target(generate_exposed_interfaces DEPENDS ${exposed_interface_sources})
add_dependencies(ladybird_codegen_accumulator generate_exposed_interfaces)
add_dependencies(${target} generate_exposed_interfaces)
add_dependencies(generate_exposed_interfaces ${generated_idl_targets})
set(LIBWEB_ALL_GENERATED_HEADERS ${LIBWEB_ALL_GENERATED_HEADERS} PARENT_SCOPE)
endfunction()

View file

@ -0,0 +1,270 @@
#!/usr/bin/env python3
# Copyright (c) 2026-present, the Ladybird developers.
#
# SPDX-License-Identifier: BSD-2-Clause
import argparse
import sys
from io import StringIO
from pathlib import Path
from typing import Dict
from typing import List
from typing import Set
from typing import TextIO
sys.path.append(str(Path(__file__).resolve().parent.parent))
from Generators.libweb_bindings import interfaces
from Generators.libweb_bindings.context import GenerationContext
from Generators.libweb_bindings.includes import GeneratedIncludes
from Generators.libweb_bindings.intrinsics import collect_interface_sets
from Generators.libweb_bindings.intrinsics import write_exposed_interface_header
from Generators.libweb_bindings.intrinsics import write_exposed_interface_implementation
from Generators.libweb_bindings.intrinsics import write_intrinsic_definitions_header
from Generators.libweb_bindings.intrinsics import write_intrinsic_definitions_implementation
from Generators.libweb_bindings.to_idl_value import dictionaries_in_dependency_order
from Generators.libweb_bindings.to_idl_value import write_dictionary_conversion
from Generators.libweb_bindings.to_idl_value import write_dictionary_declaration
from Generators.libweb_bindings.to_idl_value import write_enumeration_conversion
from Generators.libweb_bindings.to_idl_value import write_enumeration_declaration
from Generators.libweb_bindings.to_js_value import write_dictionary_to_javascript_value_conversion
from Generators.libweb_bindings.to_js_value import write_dictionary_to_javascript_value_declaration
from Generators.libweb_bindings.to_js_value import write_enumeration_to_javascript_value_conversion
from Generators.libweb_bindings.to_js_value import write_enumeration_to_javascript_value_declaration
from Utils.webidl_parser import Module
from Utils.webidl_parser import parse_module
def parse_arguments() -> argparse.Namespace:
argument_parser = argparse.ArgumentParser()
argument_parser.add_argument(
"-o",
"--output-path",
required=True,
type=Path,
help="Path to output generated files into",
)
argument_parser.add_argument(
"-d",
"--depfile",
type=Path,
help="Path to write dependency file to",
)
argument_parser.add_argument("paths", nargs="+", type=Path, help="Paths of every IDL file that could be Exposed")
return argument_parser.parse_args()
def read_input_paths(paths: List[Path]) -> List[Path]:
if len(paths) == 1 and str(paths[0]).startswith("@"):
response_file_path = Path(str(paths[0])[1:])
return [Path(path) for path in response_file_path.read_text().splitlines() if path]
return paths
def cpp_namespace_for_module_path(path: Path) -> str:
"""A path of Libraries/LibWeb/<namespace>/... should have a namespace of Web::<namespace>."""
parts = path.parts
return parts[parts.index("LibWeb") + 1]
def local_type_names(module: Module) -> set[str]:
local_types = {enumeration.name for enumeration in module.enumerations}
local_types.update(dictionary.name for dictionary in module.dictionaries)
if module.interface is not None:
local_types.add(module.interface.name)
return local_types
def write_idl_header(out: TextIO, module: Module, context: GenerationContext) -> None:
includes = GeneratedIncludes(local_type_names(module))
body = StringIO()
interfaces.write_declaration(body, includes, context, module.interface)
for enumeration in module.enumerations:
write_enumeration_declaration(body, enumeration, includes)
write_enumeration_to_javascript_value_declaration(body, enumeration, includes)
for dictionary in dictionaries_in_dependency_order(module.dictionaries, context):
write_dictionary_declaration(body, dictionary, includes, context)
write_dictionary_to_javascript_value_declaration(body, dictionary)
out.write("#pragma once\n\n")
includes.write(out)
out.write("namespace Web::Bindings {\n\n")
out.write(body.getvalue())
out.write("} // namespace Web::Bindings\n")
def write_idl_implementation(out: TextIO, module: Module, context: GenerationContext) -> None:
includes = GeneratedIncludes(local_type_names(module))
includes.add_binding(module.path.stem)
body = StringIO()
interfaces.write_implementation(body, includes, context, module.interface)
for enumeration in module.enumerations:
write_enumeration_conversion(body, enumeration, includes)
write_enumeration_to_javascript_value_conversion(body, enumeration)
for dictionary in module.dictionaries:
write_dictionary_conversion(body, dictionary, includes, context)
write_dictionary_to_javascript_value_conversion(body, dictionary, includes, context)
includes.write(out)
out.write("namespace Web::Bindings {\n\n")
out.write(body.getvalue())
out.write("} // namespace Web::Bindings\n")
def write_forward_header(out: TextIO, modules: List[Module]) -> None:
out.write(
"""#pragma once
"""
)
interface_names_by_namespace: Dict[str, Set[str]] = {}
for module in modules:
interface = module.interface
if interface is None or interface.is_namespace:
continue
namespace_name = cpp_namespace_for_module_path(interface.path)
if not namespace_name:
continue
interface_names_by_namespace.setdefault(namespace_name, set()).add(interface.implemented_name)
for namespace_name in sorted(interface_names_by_namespace):
out.write(f"namespace Web::{namespace_name} {{\n\n")
for class_name in sorted(interface_names_by_namespace[namespace_name]):
out.write(f"class {class_name};\n")
out.write(
"""
}
"""
)
dictionary_names = {dictionary.name for module in modules for dictionary in module.dictionaries}
out.write(
"""namespace Web::Bindings {
"""
)
for dictionary_name in sorted(dictionary_names):
out.write(f"struct {dictionary_name};\n")
out.write(
"""
}
"""
)
def write_generated_file(path: Path, writer, *args) -> None:
output_file = StringIO()
writer(output_file, *args)
generated_contents = output_file.getvalue()
if path.exists() and path.read_text(encoding="utf-8") == generated_contents:
return
with path.open("w", encoding="utf-8", newline="\n") as output_file:
output_file.write(generated_contents)
def generate_depfile(depfile_path: Path, dependency_paths: List[Path], output_files: List[Path]) -> None:
depfile_path.parent.mkdir(parents=True, exist_ok=True)
depfile_contents = " ".join(str(output_file) for output_file in output_files)
depfile_contents += ":"
for dependency_path in dependency_paths:
depfile_contents += f" \\\n {dependency_path}"
depfile_contents += "\n"
depfile_path.write_text(depfile_contents, encoding="utf-8")
def main() -> int:
arguments = parse_arguments()
output_directory = arguments.output_path
output_directory.mkdir(parents=True, exist_ok=True)
dependency_paths: List[Path] = []
modules: List[Module] = []
for path in read_input_paths(arguments.paths):
module = parse_module(path, path.read_text(encoding="utf-8"))
modules.append(module)
dependency_paths.append(path)
context = GenerationContext(modules)
modules = context.modules
output_files: List[Path] = []
for module in modules:
path = module.path
header_path = output_directory / f"{path.stem}.h"
implementation_path = output_directory / f"{path.stem}.cpp"
write_generated_file(header_path, write_idl_header, module, context)
write_generated_file(implementation_path, write_idl_implementation, module, context)
output_files.append(header_path)
output_files.append(implementation_path)
interface_sets = collect_interface_sets(modules)
intrinsic_definitions_header_path = output_directory / "IntrinsicDefinitions.h"
intrinsic_definitions_implementation_path = output_directory / "IntrinsicDefinitions.cpp"
write_generated_file(intrinsic_definitions_header_path, write_intrinsic_definitions_header, interface_sets)
write_generated_file(
intrinsic_definitions_implementation_path, write_intrinsic_definitions_implementation, interface_sets
)
output_files.extend([intrinsic_definitions_header_path, intrinsic_definitions_implementation_path])
for class_name in ("Window", "DedicatedWorker", "SharedWorker"):
exposed_interface_header_path = output_directory / f"{class_name}ExposedInterfaces.h"
write_generated_file(exposed_interface_header_path, write_exposed_interface_header, class_name)
output_files.append(exposed_interface_header_path)
exposed_interface_implementations = [
("Window", interface_sets.window_exposed),
("DedicatedWorker", interface_sets.dedicated_worker_exposed),
("SharedWorker", interface_sets.shared_worker_exposed),
]
for class_name, exposed_interfaces in exposed_interface_implementations:
exposed_interface_implementation_path = output_directory / f"{class_name}ExposedInterfaces.cpp"
write_generated_file(
exposed_interface_implementation_path,
write_exposed_interface_implementation,
class_name,
exposed_interfaces,
)
output_files.append(exposed_interface_implementation_path)
forward_header_path = output_directory / "Forward.h"
write_generated_file(forward_header_path, write_forward_header, modules)
output_files.append(forward_header_path)
if arguments.depfile is not None:
generate_depfile(arguments.depfile, dependency_paths, output_files)
return 0
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -0,0 +1,3 @@
# Copyright (c) 2026-present, the Ladybird developers.
#
# SPDX-License-Identifier: BSD-2-Clause

View file

@ -0,0 +1,114 @@
# Copyright (c) 2026-present, the Ladybird developers.
#
# SPDX-License-Identifier: BSD-2-Clause
from typing import TextIO
from Generators.libweb_bindings.context import GenerationContext
from Generators.libweb_bindings.cpp_types import TypeOptionality
from Generators.libweb_bindings.cpp_types import add_include_for_contained_storage_type
from Generators.libweb_bindings.cpp_types import cpp_type_for_idl_type
from Generators.libweb_bindings.cpp_types import cpp_type_for_idl_type_details
from Generators.libweb_bindings.cpp_types import cpp_value_type
from Generators.libweb_bindings.cpp_types import idl_identifier_cpp_name
from Generators.libweb_bindings.default_values import cpp_default_value_conversion
from Generators.libweb_bindings.includes import GeneratedIncludes
from Generators.libweb_bindings.to_idl_value import to_idl_value
from Utils.webidl_parser import OperationParameter
def operation_parameter_cpp_type(parameter: OperationParameter, context: GenerationContext) -> str:
if parameter.variadic:
cpp_type = cpp_type_for_idl_type_details(
parameter.type,
context,
extended_attributes=parameter.extended_attributes,
)
storage_type_name = cpp_type.contained_storage_type.value
return f"{storage_type_name}<{cpp_type.name}>"
if parameter.optional:
return cpp_type_for_idl_type(
parameter.type,
context,
optionality=TypeOptionality.OptionalArgument,
extended_attributes=parameter.extended_attributes,
)
return cpp_value_type(parameter, context)
def write_operation_parameter_conversions(
out: TextIO,
parameters: list[OperationParameter],
includes: GeneratedIncludes,
context: GenerationContext,
) -> None:
includes.add("LibWeb/Bindings/ExceptionOrUtils.h")
for index, parameter in enumerate(parameters):
if parameter.variadic:
write_variadic_operation_parameter_conversion(out, parameter, index, includes, context)
continue
argument_value_name = f"arg{index}"
parameter_name = idl_identifier_cpp_name(parameter)
out.write(f" auto {argument_value_name} = vm.argument({index});\n")
if parameter.optional and parameter.default_value is None:
out.write(
f""" {operation_parameter_cpp_type(parameter, context)} {parameter_name} {{}};
if (!{argument_value_name}.is_undefined())
{parameter_name} = TRY(throw_dom_exception_if_needed(vm, [&] {{ return {to_idl_value(parameter, argument_value_name, includes, context)}; }}));
"""
)
continue
if parameter.optional:
out.write(
f""" {cpp_value_type(parameter, context)} {parameter_name} = {cpp_default_value_conversion(parameter, context)};
if (!{argument_value_name}.is_undefined())
{parameter_name} = TRY(throw_dom_exception_if_needed(vm, [&] {{ return {to_idl_value(parameter, argument_value_name, includes, context)}; }}));
"""
)
continue
out.write(
f""" auto {parameter_name} = TRY(throw_dom_exception_if_needed(vm, [&] {{ return {to_idl_value(parameter, argument_value_name, includes, context)}; }}));
"""
)
def write_variadic_operation_parameter_conversion(
out: TextIO,
parameter: OperationParameter,
index: int,
includes: GeneratedIncludes,
context: GenerationContext,
) -> None:
cpp_type = cpp_type_for_idl_type_details(
parameter.type,
context,
extended_attributes=parameter.extended_attributes,
)
add_include_for_contained_storage_type(cpp_type.contained_storage_type, includes)
parameter_name = idl_identifier_cpp_name(parameter)
parameter_cpp_type = operation_parameter_cpp_type(parameter, context)
out.write(
f""" {parameter_cpp_type} {parameter_name};
if (vm.argument_count() > {index}) {{
{parameter_name}.ensure_capacity(vm.argument_count() - {index});
for (size_t i = {index}; i < vm.argument_count(); ++i) {{
auto argument = TRY(throw_dom_exception_if_needed(vm, [&] {{ return {to_idl_value(parameter, "vm.argument(i)", includes, context)}; }}));
{parameter_name}.unchecked_append(move(argument));
}}
}}
"""
)

View file

@ -0,0 +1,712 @@
# Copyright (c) 2026-present, the Ladybird developers.
#
# SPDX-License-Identifier: BSD-2-Clause
from io import StringIO
from typing import Optional
from typing import TextIO
from Generators.libweb_bindings.context import GenerationContext
from Generators.libweb_bindings.cpp_types import fully_qualified_name_for_interface
from Generators.libweb_bindings.cpp_types import idl_identifier_cpp_name
from Generators.libweb_bindings.cpp_types import idl_implementation_cpp_name
from Generators.libweb_bindings.extended_attributes import wrap_with_ce_reactions
from Generators.libweb_bindings.extended_attributes import wrap_with_extended_attribute_exposure_checks
from Generators.libweb_bindings.includes import GeneratedIncludes
from Generators.libweb_bindings.to_idl_value import to_idl_value
from Generators.libweb_bindings.to_js_value import to_javascript_value
from Utils.webidl_parser import Attribute
from Utils.webidl_parser import IDLParameterizedType
from Utils.webidl_parser import IDLType
from Utils.webidl_parser import Interface
def attribute_has_setter(attribute: Attribute, include_replaceable: bool = False) -> bool:
return (
not attribute.readonly
or "LegacyLenientSetter" in attribute.extended_attributes
or "PutForwards" in attribute.extended_attributes
or (include_replaceable and "Replaceable" in attribute.extended_attributes)
)
def attribute_is_nullable_reflected_frozen_array_of_element(attribute: Attribute) -> bool:
return (
"Reflect" in attribute.extended_attributes
and attribute.type.nullable
and isinstance(attribute.type, IDLParameterizedType)
and attribute.type.name == "FrozenArray"
and len(attribute.type.parameters) == 1
and attribute.type.parameters[0].name == "Element"
)
def attribute_is_nullable_reflected_element(attribute: Attribute) -> bool:
return "Reflect" in attribute.extended_attributes and attribute.type.nullable and attribute.type.name == "Element"
def attribute_uses_cached_js_value(attribute: Attribute) -> bool:
return (
"CachedAttribute" in attribute.extended_attributes
or attribute_is_nullable_reflected_frozen_array_of_element(attribute)
)
def reflected_attribute_name(attribute: Attribute) -> str:
return attribute.extended_attributes.get("Reflect") or attribute.name.lower()
def attribute_callback_cpp_name(attribute: Attribute) -> str:
return attribute.extended_attributes.get("AttributeCallbackName", idl_identifier_cpp_name(attribute))
def attribute_getter_callback_name(attribute: Attribute) -> str:
return f"{attribute_callback_cpp_name(attribute)}_getter"
def attribute_setter_callback_name(attribute: Attribute) -> str:
return f"{attribute_callback_cpp_name(attribute)}_setter"
def define_the_regular_attributes(
out: TextIO,
includes: GeneratedIncludes,
interface: Interface,
include_replaceable_setters: bool = False,
) -> None:
# 1. Let attributes be the list of regular attributes that are members of definition.
attributes = [
attribute for attribute in interface.regular_attributes if "FIXME" not in attribute.extended_attributes
]
# 2. Remove from attributes all the attributes that are unforgeable.
attributes = [attribute for attribute in attributes if "LegacyUnforgeable" not in attribute.extended_attributes]
# 3. Define the attributes attributes of definition on target given realm.
define_the_attributes(out, includes, attributes, interface, include_replaceable_setters)
def define_the_unforgeable_attributes(
out: TextIO,
includes: GeneratedIncludes,
interface: Interface,
include_replaceable_setters: bool = False,
) -> None:
attributes = [
attribute
for attribute in interface.regular_attributes
if "FIXME" not in attribute.extended_attributes and "LegacyUnforgeable" in attribute.extended_attributes
]
define_the_attributes(out, includes, attributes, interface, include_replaceable_setters)
def define_the_attributes(
out: TextIO,
includes: GeneratedIncludes,
attributes: list[Attribute],
interface: Interface,
include_replaceable_setters: bool = False,
) -> None:
if not attributes:
return
out.write("\n")
# 1. For each attribute attr of attributes:
for attribute in attributes:
getter_name = attribute_getter_callback_name(attribute)
setter_name = attribute_setter_callback_name(attribute)
cpp_name = attribute_callback_cpp_name(attribute)
native_getter_name = f"native_{getter_name}"
native_setter_name = f"native_{setter_name}"
definition = StringIO()
# 1. If attr is not exposed in realm, then continue.
# NB: This is done at the end of this function.
# 2. Let getter be the result of creating an attribute getter given attr, definition, and realm.
if "LegacyUnforgeable" in attribute.extended_attributes:
includes.add("LibWeb/Bindings/Intrinsics.h")
definition.write(
f' auto {native_getter_name} = host_defined_intrinsics(realm).ensure_web_unforgeable_function("{interface.namespaced_name}"_utf16_fly_string, "{attribute.name}"_utf16_fly_string, {getter_name}, UnforgeableKey::Type::Getter);\n'
)
else:
definition.write(
f' auto {native_getter_name} = JS::NativeFunction::create(realm, {getter_name}, 0, "{attribute.name}"_utf16_fly_string, &realm, "get"sv);\n'
)
# 3. Let setter be the result of creating an attribute setter given attr, definition, and realm.
if not attribute_has_setter(attribute, include_replaceable=include_replaceable_setters):
# NB: the algorithm to create an attribute setter returns undefined if attr is read only.
definition.write(f" GC::Ptr<JS::NativeFunction> {native_setter_name};\n")
else:
if "LegacyUnforgeable" in attribute.extended_attributes:
includes.add("LibWeb/Bindings/Intrinsics.h")
definition.write(
f' auto {native_setter_name} = host_defined_intrinsics(realm).ensure_web_unforgeable_function("{interface.namespaced_name}"_utf16_fly_string, "{attribute.name}"_utf16_fly_string, {setter_name}, UnforgeableKey::Type::Setter);\n'
)
else:
definition.write(
f' auto {native_setter_name} = JS::NativeFunction::create(realm, {setter_name}, 1, "{attribute.name}"_utf16_fly_string, &realm, "set"sv);\n'
)
definition.write(
f"""
// 4. Let configurable be false if attr is unforgeable and true otherwise.
auto {cpp_name}_attributes = default_attributes;
// 5. Let desc be the PropertyDescriptor{{[[Get]]: getter, [[Set]]: setter, [[Enumerable]]: true, [[Configurable]]: configurable}}.
// 6. Let id be attrs identifier.
auto {cpp_name}_id = "{attribute.name}"_utf16_fly_string;
// 7. Perform ! DefinePropertyOrThrow(target, id, desc).
object.define_direct_accessor({cpp_name}_id, {native_getter_name}, {native_setter_name}, {cpp_name}_attributes);
// 8. FIXME: If attrs type is an observable array type with type argument T, then set targets backing observable array exotic object for attr to the result of creating an observable array exotic object in realm, given T, attrs set an indexed value algorithm, and attrs delete an indexed value algorithm.
"""
)
out.write(
wrap_with_extended_attribute_exposure_checks(
includes,
attribute.extended_attributes,
definition.getvalue(),
)
)
def define_the_static_attributes(out: TextIO, includes: GeneratedIncludes, interface: Interface) -> None:
for attribute in interface.static_attributes:
if "FIXME" in attribute.extended_attributes:
continue
definition = f' object.define_native_accessor(realm, "{attribute.name}"_utf16_fly_string, {attribute_getter_callback_name(attribute)}, nullptr, default_attributes);\n'
out.write(wrap_with_extended_attribute_exposure_checks(includes, attribute.extended_attributes, definition))
def write_attribute_getters(
out: TextIO, context: GenerationContext, includes: GeneratedIncludes, interface: Interface
) -> None:
for attribute in interface.regular_attributes:
if "FIXME" in attribute.extended_attributes:
continue
write_attribute_getter(out, context, includes, interface, attribute)
def write_attribute_getter(
out: TextIO,
context: GenerationContext,
includes: GeneratedIncludes,
interface: Interface,
attribute: Attribute,
receiver_class: Optional[str] = None,
) -> None:
if receiver_class is None:
receiver_class = interface.prototype_class
attribute_type_is_promise = attribute.type.name == "Promise"
if attribute_type_is_promise:
includes.add("LibWeb/WebIDL/Promise.h")
getter_prelude = ""
getter_cache_check = ""
getter_steps = f"auto R = TRY(throw_dom_exception_if_needed(vm, [&] {{ return idl_object->{idl_implementation_cpp_name(attribute)}(); }}));"
is_reflected = "Reflect" in attribute.extended_attributes
is_non_nullable_reflected = is_reflected and not attribute.type.nullable
is_non_nullable_reflected_string = is_non_nullable_reflected and attribute.type.name == "DOMString"
is_reflected_usv_string = is_non_nullable_reflected and attribute.type.name == "USVString"
if is_reflected and attribute.type.name == "boolean":
getter_steps = f"""// If a reflected IDL attribute has the type boolean:
// 1. Let contentAttributeValue be the result of running this's get the content attribute.
// 2. If contentAttributeValue is null, then return false.
auto R = idl_object->has_attribute("{reflected_attribute_name(attribute)}"_fly_string);"""
elif is_non_nullable_reflected and attribute.type.name == "long":
includes.add("LibWeb/HTML/Numbers.h")
getter_steps = f"""// If a reflected IDL attribute has the type long:
// 1. Let contentAttributeValue be the result of running this's get the content attribute.
// 2. If contentAttributeValue is not null:
// 1. Let parsedValue be the result of integer parsing contentAttributeValue.
// 2. If parsedValue is not an error and is within the long range, then return parsedValue.
i32 R = 0;
auto content_attribute_value = idl_object->get_attribute("{reflected_attribute_name(attribute)}"_fly_string);
if (content_attribute_value.has_value()) {{
auto maybe_parsed_value = Web::HTML::parse_integer(*content_attribute_value);
if (maybe_parsed_value.has_value())
R = *maybe_parsed_value;
}}"""
elif is_non_nullable_reflected and attribute.type.name == "unsigned long":
includes.add("LibWeb/HTML/Numbers.h")
getter_steps = f"""// If a reflected IDL attribute has the type unsigned long:
// 1. Let contentAttributeValue be the result of running this's get the content attribute.
// 2. Let minimum be 0.
// FIXME: 3. If the reflected IDL attribute is limited to only positive numbers or limited to only positive numbers with fallback, then set minimum to 1.
// FIXME: 4. If the reflected IDL attribute is clamped to the range, then set minimum to clampedMin.
// 5. Let maximum be 2147483647 if the reflected IDL attribute is not clamped to the range; otherwise clampedMax.
// 6. If contentAttributeValue is not null:
// 1. Let parsedValue be the result of non-negative integer parsing contentAttributeValue.
// 2. If parsedValue is not an error and is in the range minimum to maximum, inclusive, then return parsedValue.
u32 R = 0;
auto content_attribute_value = idl_object->get_attribute("{reflected_attribute_name(attribute)}"_fly_string);
u32 minimum = 0;
u32 maximum = 2147483647;
if (content_attribute_value.has_value()) {{
auto parsed_value = Web::HTML::parse_non_negative_integer(*content_attribute_value);
if (parsed_value.has_value()) {{
if (*parsed_value >= minimum && *parsed_value <= maximum)
R = *parsed_value;
}}
}}"""
elif is_reflected_usv_string:
includes.add("LibWeb/Infra/Strings.h")
getter_steps = f"""// If a reflected IDL attribute has the type USVString:
// 1. Let element be the result of running this's get the element.
// 2. Let contentAttributeValue be the result of running this's get the content attribute.
auto content_attribute_value = idl_object->attribute("{reflected_attribute_name(attribute)}"_fly_string);
// 3. Let attributeDefinition be the attribute definition of element's content attribute whose namespace is null and local name is the reflected content attribute name.
// 5. Return contentAttributeValue, converted to a scalar value string.
String R;
if (content_attribute_value.has_value())
R = MUST(Infra::convert_to_scalar_value_string(*content_attribute_value));"""
if "URL" in attribute.extended_attributes:
includes.add("LibWeb/DOM/Document.h")
getter_steps = f"""// If a reflected IDL attribute has the type USVString:
// 1. Let element be the result of running this's get the element.
// 2. Let contentAttributeValue be the result of running this's get the content attribute.
auto content_attribute_value = idl_object->attribute("{reflected_attribute_name(attribute)}"_fly_string);
// 3. Let attributeDefinition be the attribute definition of element's content attribute whose namespace is null and local name is the reflected content attribute name.
// 4. If attributeDefinition indicates it contains a URL:
String R;
if (content_attribute_value.has_value()) {{
// 2. Let urlString be the result of encoding-parsing-and-serializing a URL given contentAttributeValue, relative to element's node document.
auto url_string = idl_object->document().encoding_parse_and_serialize_url(*content_attribute_value);
// 3. If urlString is not failure, then return urlString.
if (url_string.has_value())
R = url_string.release_value();
else
R = MUST(Infra::convert_to_scalar_value_string(*content_attribute_value));
}}"""
elif is_reflected and attribute.type.name == "DOMString" and "Enumerated" in attribute.extended_attributes:
includes.add("AK/Array.h")
enumeration = context.enumeration(IDLType(attribute.extended_attributes["Enumerated"]))
if enumeration is None:
raise RuntimeError(
f"Unknown reflected enumerated attribute type '{attribute.extended_attributes['Enumerated']}'"
)
valid_values = ", ".join(f'"{value}"_string' for value in enumeration.values)
missing_value_default = enumeration.extended_attributes.get("MissingValueDefault", "")
invalid_value_default = enumeration.extended_attributes.get("InvalidValueDefault", missing_value_default)
if attribute.type.nullable:
getter_steps = f"""// If a reflected IDL attribute is an enumerated attribute:
// 1. Let contentAttributeValue be the result of running this's get the content attribute.
auto R = idl_object->attribute("{reflected_attribute_name(attribute)}"_fly_string);
// 3. If contentAttributeValue is an ASCII case-insensitive match for one of the keywords, then return that keyword's canonical keyword.
Array valid_values {{ {valid_values} }};
if (R.has_value()) {{
auto has_keyword = false;
for (auto const& value : valid_values) {{
if (value.equals_ignoring_ascii_case(*R)) {{
has_keyword = true;
R = value;
break;
}}
}}
// 4. If contentAttributeValue is not a keyword, return the invalid value default.
if (!has_keyword)
R = "{invalid_value_default}"_string;
}}"""
else:
getter_steps = f"""// If a reflected IDL attribute is an enumerated attribute:
// 1. Let contentAttributeValue be the result of running this's get the content attribute.
auto content_attribute_value = idl_object->attribute("{reflected_attribute_name(attribute)}"_fly_string);
// 2. If contentAttributeValue is null, then set contentAttributeValue to the missing value default.
auto R = content_attribute_value.value_or("{missing_value_default}"_string);
auto did_set_to_missing_value = false;
if (!content_attribute_value.has_value())
did_set_to_missing_value = true;
// 3. If contentAttributeValue is an ASCII case-insensitive match for one of the keywords, then return that keyword's canonical keyword.
Array valid_values {{ {valid_values} }};
auto has_keyword = false;
for (auto const& value : valid_values) {{
if (value.equals_ignoring_ascii_case(R)) {{
has_keyword = true;
R = value;
break;
}}
}}
// 4. If contentAttributeValue is not a keyword and was not set to the missing value default, return the invalid value default.
if (!has_keyword && !did_set_to_missing_value)
R = "{invalid_value_default}"_string;"""
elif is_non_nullable_reflected_string:
getter_steps = f"""// If a reflected IDL attribute has the type DOMString:
// 1. Let element be the result of running this's get the element.
// 2. Let contentAttributeValue be the result of running this's get the content attribute.
// 5. If contentAttributeValue is null, then return the empty string.
// 6. Return contentAttributeValue.
auto R = idl_object->get_attribute_value("{reflected_attribute_name(attribute)}"_fly_string);"""
elif attribute_is_nullable_reflected_element(attribute):
getter_steps = f"""static auto const& content_attribute = *new FlyString("{reflected_attribute_name(attribute)}"_fly_string);
auto R = idl_object->get_the_attribute_associated_element(content_attribute, TRY(throw_dom_exception_if_needed(vm, [&] {{ return idl_object->{idl_implementation_cpp_name(attribute)}(); }})));"""
elif attribute_is_nullable_reflected_frozen_array_of_element(attribute):
getter_steps = f"""static auto const& content_attribute = *new FlyString("{reflected_attribute_name(attribute)}"_fly_string);
auto R = idl_object->get_the_attribute_associated_elements(content_attribute, TRY(throw_dom_exception_if_needed(vm, [&] {{ return idl_object->{idl_implementation_cpp_name(attribute)}(); }})));"""
if "CachedAttribute" in attribute.extended_attributes:
getter_prelude = f""" if (auto cached_value = idl_object->cached_{idl_implementation_cpp_name(attribute)}())
return JS::Value(cached_value.ptr());
"""
if attribute_is_nullable_reflected_frozen_array_of_element(attribute):
includes.add("LibWeb/WebIDL/AbstractOperations.h")
getter_cache_check = f""" if (auto cached_value = idl_object->cached_{idl_implementation_cpp_name(attribute)}(); WebIDL::lists_contain_same_elements(cached_value, R))
return JS::Value(cached_value.ptr());
"""
cached_return_value = None
if attribute_uses_cached_js_value(attribute):
cached_value = "&js_value.as_object()"
if attribute_is_nullable_reflected_frozen_array_of_element(attribute):
includes.add("LibJS/Runtime/Array.h")
cached_value = "&as<JS::Array>(js_value.as_object())"
cached_return_value = f"""[&]() -> JS::Value {{
JS::Value js_value = {to_javascript_value(attribute.type, "R", includes, context)};
if (js_value.is_object())
idl_object->set_cached_{idl_implementation_cpp_name(attribute)}({cached_value});
return js_value;
}}()"""
if attribute_type_is_promise:
if getter_prelude or getter_cache_check:
raise RuntimeError(f"Unsupported cached promise attribute '{attribute.name}' on '{interface.name}'")
out.write(
f"""JS_DEFINE_NATIVE_FUNCTION({receiver_class}::{attribute_getter_callback_name(attribute)})
{{
WebIDL::log_trace(vm, "{receiver_class}::{attribute_getter_callback_name(attribute)}");
[[maybe_unused]] auto& realm = *vm.current_realm();
auto steps = [&]() -> JS::ThrowCompletionOr<GC::Ptr<WebIDL::Promise>> {{
// 1. Let idlObject be null.
[[maybe_unused]] auto* idl_object = TRY(impl_from(vm));
{getter_steps}
return R;
}};
auto maybe_R = steps();
// 2. And then, if an exception E was thrown:
// 1. If attributes type is a promise type, then return ! Call(%Promise.reject%, %Promise%, «E»).
if (maybe_R.is_throw_completion())
return WebIDL::create_rejected_promise(realm, maybe_R.error_value())->promise();
// 2. Otherwise, end these steps and allow the exception to propagate.
auto R = maybe_R.release_value();
// 4. Return the result of converting R to a JavaScript value of the type attribute is declared as.
return {to_javascript_value(attribute.type, "R", includes, context)};
}}
"""
)
return
out.write(
f"""JS_DEFINE_NATIVE_FUNCTION({receiver_class}::{attribute_getter_callback_name(attribute)})
{{
WebIDL::log_trace(vm, "{receiver_class}::{attribute_getter_callback_name(attribute)}");
[[maybe_unused]] auto& realm = *vm.current_realm();
auto* idl_object = TRY(impl_from(vm));
{getter_prelude}
{getter_steps}
{getter_cache_check}
return {cached_return_value or to_javascript_value(attribute.type, "R", includes, context)};
}}
"""
)
def write_attribute_setters(
out: TextIO, context: GenerationContext, includes: GeneratedIncludes, interface: Interface
) -> None:
for attribute in interface.regular_attributes:
if "FIXME" in attribute.extended_attributes:
continue
if not attribute_has_setter(attribute):
continue
write_attribute_setter(out, context, includes, interface, attribute)
def write_attribute_setter(
out: TextIO,
context: GenerationContext,
includes: GeneratedIncludes,
interface: Interface,
attribute: Attribute,
receiver_class: Optional[str] = None,
) -> None:
if receiver_class is None:
receiver_class = interface.prototype_class
includes.add("LibJS/Runtime/Value.h")
includes.add("LibWeb/WebIDL/Tracing.h")
out.write(
f"""JS_DEFINE_NATIVE_FUNCTION({receiver_class}::{attribute_setter_callback_name(attribute)})
{{
WebIDL::log_trace(vm, "{receiver_class}::{attribute_setter_callback_name(attribute)}");
[[maybe_unused]] auto& realm = *vm.current_realm();
// 1. Let V be undefined.
auto V = JS::js_undefined();
// 2. If any arguments were passed, then set V to the value of the first argument passed.
if (vm.argument_count() > 0)
V = vm.argument(0);
// 3. Let id be attributes identifier.
// 4. Let idlObject be null.
[[maybe_unused]] {fully_qualified_name_for_interface(interface)}* idl_object = nullptr;
// 5. If attribute is a regular attribute:
// 1. Let jsValue be the this value, if it is not null or undefined, or realms global object otherwise. (This will subsequently cause a TypeError in a few steps, if the global object does not implement target and [LegacyLenientThis] is not specified.)
auto js_value = vm.this_value();
if (js_value.is_nullish())
js_value = &realm.global_object();
// 2. FIXME: If jsValue is a platform object, then perform a security check, passing jsValue, attributes identifier, and "setter".
// 3. Let validThis be true if jsValue implements target, or false otherwise.
auto maybe_idl_object = impl_from(vm, js_value);
// 4. If validThis is false and attribute was not specified with the [LegacyLenientThis] extended attribute, then throw a TypeError.
"""
)
if "LegacyLenientThis" not in attribute.extended_attributes:
out.write(
""" idl_object = TRY(maybe_idl_object);
"""
)
if "Replaceable" in attribute.extended_attributes:
out.write(
f""" // 5. If attribute is declared with the [Replaceable] extended attribute, then:
// 1. Perform ? CreateDataPropertyOrThrow(jsValue, id, V).
TRY(idl_object->create_data_property_or_throw("{attribute.name}"_utf16_fly_string, V));
// 2. Return undefined.
return JS::js_undefined();
}}
"""
)
return
if "LegacyLenientThis" in attribute.extended_attributes:
out.write(
""" // 6. If validThis is false, then return undefined.
if (maybe_idl_object.is_error())
return JS::js_undefined();
// 5. Set idlObject to the IDL interface type value that represents a reference to jsValue.
idl_object = maybe_idl_object.release_value();
"""
)
if "LegacyLenientSetter" in attribute.extended_attributes:
out.write(
""" // 7. If attribute is declared with a [LegacyLenientSetter] extended attribute, then return undefined.
return JS::js_undefined();
}
"""
)
return
if put_forwards_identifier := attribute.extended_attributes.get("PutForwards"):
includes.add("LibJS/Runtime/PropertyKey.h")
out.write(
f""" // 8. If attribute is declared with a [PutForwards] extended attribute, then:
// 1. Let Q be ? Get(jsValue, id).
auto receiver_value = TRY(idl_object->get("{attribute.name}"_utf16_fly_string));
// 2. If Q is not an Object, then throw a TypeError.
if (!receiver_value.is_object())
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObject, receiver_value);
auto& receiver = receiver_value.as_object();
// 3. Let forwardId be the identifier argument of the [PutForwards] extended attribute.
auto forward_id = "{put_forwards_identifier}"_utf16_fly_string;
// 4. Perform ? Set(Q, forwardId, V, false).
TRY(receiver.set(JS::PropertyKey {{ forward_id, JS::PropertyKey::StringMayBeNumber::No }}, V, JS::Object::ShouldThrowExceptions::No));
// 5. Return undefined.
return JS::js_undefined();
}}
"""
)
return
setter_steps = f"TRY(throw_dom_exception_if_needed(vm, [&] {{ return idl_object->set_{idl_implementation_cpp_name(attribute)}(idl_value); }}));\n return {{}};"
is_reflected = "Reflect" in attribute.extended_attributes
is_non_nullable_reflected = is_reflected and not attribute.type.nullable
is_nullable_reflected_string = is_reflected and attribute.type.nullable and attribute.type.name == "DOMString"
is_non_nullable_reflected_string = is_non_nullable_reflected and attribute.type.name == "DOMString"
is_reflected_usv_string = is_non_nullable_reflected and attribute.type.name == "USVString"
if is_reflected and attribute.type.name == "boolean":
setter_steps = f"""if (!idl_value)
idl_object->remove_attribute("{reflected_attribute_name(attribute)}"_fly_string);
else
idl_object->set_attribute_value("{reflected_attribute_name(attribute)}"_fly_string, String {{}});
return {{}};"""
elif is_non_nullable_reflected and attribute.type.name == "unsigned long":
setter_steps = f"""u32 minimum = 0;
u32 new_value = minimum;
if (idl_value >= minimum && idl_value <= 2147483647)
new_value = idl_value;
idl_object->set_attribute_value("{reflected_attribute_name(attribute)}"_fly_string, String::number(new_value));
return {{}};"""
elif is_non_nullable_reflected and attribute.type.name == "long":
setter_steps = f'idl_object->set_attribute_value("{reflected_attribute_name(attribute)}"_fly_string, String::number(idl_value));\n return {{}};'
elif is_reflected_usv_string:
setter_steps = f'idl_object->set_attribute_value("{reflected_attribute_name(attribute)}"_fly_string, idl_value);\n return {{}};'
elif is_non_nullable_reflected_string:
setter_steps = f'idl_object->set_attribute_value("{reflected_attribute_name(attribute)}"_fly_string, idl_value);\n return {{}};'
elif is_nullable_reflected_string:
setter_steps = f"""if (!idl_value.has_value())
idl_object->remove_attribute("{reflected_attribute_name(attribute)}"_fly_string);
else
idl_object->set_attribute_value("{reflected_attribute_name(attribute)}"_fly_string, *idl_value);
return {{}};"""
elif attribute_is_nullable_reflected_element(attribute):
setter_steps = f"""static auto& content_attribute = *new FlyString("{reflected_attribute_name(attribute)}"_fly_string);
if (!idl_value) {{
idl_object->set_{idl_implementation_cpp_name(attribute)}({{}});
idl_object->remove_attribute(content_attribute);
return {{}};
}}
idl_object->set_attribute_value(content_attribute, String {{}});
idl_object->set_{idl_implementation_cpp_name(attribute)}(*idl_value);
return {{}};"""
elif attribute_is_nullable_reflected_frozen_array_of_element(attribute):
includes.add("LibGC/Weak.h")
setter_steps = f"""idl_object->set_cached_{idl_implementation_cpp_name(attribute)}(nullptr);
static auto const& content_attribute = *new FlyString("{reflected_attribute_name(attribute)}"_fly_string);
if (!idl_value.has_value()) {{
idl_object->set_{idl_implementation_cpp_name(attribute)}({{}});
idl_object->remove_attribute(content_attribute);
return {{}};
}}
idl_object->set_attribute_value(content_attribute, String {{}});
Vector<GC::Weak<DOM::Element>> elements;
elements.ensure_capacity(idl_value->size());
for (auto const& element : *idl_value)
elements.unchecked_append(*element);
idl_object->set_{idl_implementation_cpp_name(attribute)}(move(elements));
return {{}};"""
out.write(
""" auto original_steps = [&]() -> JS::ThrowCompletionOr<JS::Value> {
"""
)
if context.enumeration(attribute.type) is not None:
out.write(
f""" // 6. Let idlValue be determined as follows:
// -> attribute's type is an enumeration
// 1. Let S be ? ToString(V).
auto maybe_idl_value = throw_dom_exception_if_needed(vm, [&] {{ return {to_idl_value(attribute, "V", includes, context)}; }});
// 2. If S is not one of the enumeration's values, then return undefined.
if (maybe_idl_value.is_error())
return JS::js_undefined();
// 3. Otherwise, idlValue is the enumeration value equal to S.
auto idl_value = maybe_idl_value.release_value();
"""
)
else:
out.write(
f""" // 6. Let idlValue be determined as follows:
// -> Otherwise, idlValue is the result of converting V to an IDL value of attributes type.
auto idl_value = TRY(throw_dom_exception_if_needed(vm, [&] {{ return {to_idl_value(attribute, "V", includes, context)}; }}));
"""
)
out.write(
f""" // 7. Run the setter steps of attribute with idlObject as this and idlValue as the value.
auto setter_result = [&]() -> JS::ThrowCompletionOr<void> {{
{setter_steps}
}}();
if (setter_result.is_error())
return setter_result.release_error();
return JS::js_undefined();
}};
"""
)
if "CEReactions" in attribute.extended_attributes:
setter_result = wrap_with_ce_reactions(includes, "original_steps()")
else:
setter_result = "original_steps()"
out.write(f"""
// 8. Return undefined.
return TRY({setter_result});
}}
""")
def write_static_attribute_getters(
out: TextIO, context: GenerationContext, includes: GeneratedIncludes, interface: Interface
) -> None:
for attribute in interface.static_attributes:
if "FIXME" in attribute.extended_attributes:
continue
write_static_attribute_getter(out, context, includes, interface, attribute)
def write_static_attribute_getter(
out: TextIO,
context: GenerationContext,
includes: GeneratedIncludes,
interface: Interface,
attribute: Attribute,
) -> None:
out.write(f"""JS_DEFINE_NATIVE_FUNCTION({interface.constructor_class}::{attribute_getter_callback_name(attribute)})
{{
WebIDL::log_trace(vm, "{interface.constructor_class}::{attribute_getter_callback_name(attribute)}");
// Let R be the result of running the getter steps of attribute.
auto R = TRY(throw_dom_exception_if_needed(vm, [&] {{ return {fully_qualified_name_for_interface(interface)}::{idl_implementation_cpp_name(attribute)}(vm); }}));
// Return the result of converting R to a JavaScript value of the type attribute is declared as.
return {to_javascript_value(attribute.type, "R", includes, context)};
}}
""")

View file

@ -0,0 +1,65 @@
# Copyright (c) 2026-present, the Ladybird developers.
#
# SPDX-License-Identifier: BSD-2-Clause
from typing import TextIO
from Generators.libweb_bindings.constants import define_the_constants
from Generators.libweb_bindings.context import GenerationContext
from Generators.libweb_bindings.includes import GeneratedIncludes
from Utils.webidl_parser import Interface
def write_callback_interface_declaration(
out: TextIO, includes: GeneratedIncludes, context: GenerationContext, interface: Interface
) -> None:
if not interface.constants:
return
includes.add("LibJS/Runtime/NativeFunction.h")
includes.add("LibJS/Runtime/Object.h")
out.write(f"""struct {interface.constructor_class} {{
public:
static void initialize(JS::Realm&, JS::NativeFunction&);
}};
struct {interface.prototype_class} {{
public:
static void initialize(JS::Realm&, JS::Object&);
}};
""")
def write_callback_interface_implementation(
out: TextIO, context: GenerationContext, includes: GeneratedIncludes, interface: Interface
) -> None:
if not interface.constants:
return
includes.add("LibJS/Runtime/Realm.h")
includes.add("LibJS/Runtime/ValueInlines.h")
includes.add("LibJS/Runtime/VM.h")
includes.add_binding(interface.implemented_name)
out.write(f"""void {interface.constructor_class}::initialize(JS::Realm& realm, JS::NativeFunction& object)
{{
auto& vm = realm.vm();
[[maybe_unused]] u8 default_attributes = JS::Attribute::Enumerable;
object.define_direct_property(vm.names.length, JS::Value(0), JS::Attribute::Configurable);
object.define_direct_property(vm.names.name, JS::PrimitiveString::create(vm, "{interface.name}"_string), JS::Attribute::Configurable);
""")
define_the_constants(out, context, includes, interface)
out.write(
f"""}}
void {interface.prototype_class}::initialize(JS::Realm& realm, JS::Object& object)
{{
object.set_prototype(realm.intrinsics().object_prototype());
}}
"""
)

View file

@ -0,0 +1,35 @@
# Copyright (c) 2026-present, the Ladybird developers.
#
# SPDX-License-Identifier: BSD-2-Clause
from typing import TextIO
from Generators.libweb_bindings.context import GenerationContext
from Generators.libweb_bindings.includes import GeneratedIncludes
from Generators.libweb_bindings.to_js_value import to_javascript_value
from Utils.webidl_parser import Interface
def define_the_constants(
out: TextIO,
context: GenerationContext,
includes: GeneratedIncludes,
interface: Interface,
) -> None:
if not interface.constants:
return
includes.add("LibJS/Runtime/PropertyDescriptor.h")
out.write("\n")
# 1. For each constant const that is a member of definition:
for constant in interface.constants:
out.write(
f""" // 1. FIXME: If const is not exposed in realm, then continue.
// 2. Let value be the result of converting consts IDL value to a JavaScript value.
// 3. Let desc be the PropertyDescriptor{{[[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false, [[Value]]: value}}.
// 4. Let id be consts identifier.
// 5. Perform ! DefinePropertyOrThrow(target, id, desc).
object.define_direct_property("{constant.name}"_utf16_fly_string, {to_javascript_value(constant.type, constant.value, includes, context)}, JS::Attribute::Enumerable);
"""
)

View file

@ -0,0 +1,259 @@
# Copyright (c) 2026-present, the Ladybird developers.
#
# SPDX-License-Identifier: BSD-2-Clause
from typing import TextIO
from Generators.libweb_bindings import overload_resolution
from Generators.libweb_bindings.arguments import write_operation_parameter_conversions
from Generators.libweb_bindings.context import GenerationContext
from Generators.libweb_bindings.cpp_types import fully_qualified_name_for_interface
from Generators.libweb_bindings.cpp_types import idl_identifier_cpp_name
from Generators.libweb_bindings.includes import GeneratedIncludes
from Generators.libweb_bindings.operations import write_argument_count_check
from Generators.libweb_bindings.overload_resolution import parameter_list_length
from Utils.webidl_parser import Constructor
from Utils.webidl_parser import Interface
def write_constructor_function(
out: TextIO,
context: GenerationContext,
includes: GeneratedIncludes,
interface: Interface,
constructor: Constructor,
overload_index: int,
) -> None:
out.write(
f"""JS::ThrowCompletionOr<GC::Ref<JS::Object>> {interface.constructor_class}::construct{overload_index}([[maybe_unused]] InterfaceConstructor& constructor, [[maybe_unused]] JS::FunctionObject& new_target)
{{
WebIDL::log_trace(constructor.vm(), "{interface.constructor_class}::construct{overload_index}");
"""
)
write_constructor_steps(out, context, includes, interface, constructor)
out.write(
"""}
"""
)
def write_constructor_overload_arbiter(
out: TextIO, context: GenerationContext, includes: GeneratedIncludes, interface: Interface
) -> None:
includes.add("AK/Optional.h")
includes.add("AK/Vector.h")
includes.add("LibIDL/Types.h")
includes.add("LibWeb/WebIDL/OverloadResolution.h")
out.write(
""" auto& vm = constructor.vm();
"""
)
overload_resolution.write_overload_resolution_switch(out, context, interface, interface.constructors)
out.write(
"""
switch (chosen_overload_callable_id.value()) {
"""
)
for overload_index, _ in enumerate(interface.constructors):
out.write(
f""" case {overload_index}:
return construct{overload_index}(constructor, new_target);
"""
)
out.write(
""" default:
VERIFY_NOT_REACHED();
}
"""
)
# https://webidl.spec.whatwg.org/#overridden-constructor-steps
def write_constructor_steps(
out: TextIO,
context: GenerationContext,
includes: GeneratedIncludes,
interface: Interface,
constructor: Constructor,
) -> None:
if "HTMLConstructor" in constructor.extended_attributes:
write_html_constructor_steps(out, interface, constructor, includes)
return
out.write(
f""" auto& vm = constructor.vm();
[[maybe_unused]] auto& realm = *vm.current_realm();
// To internally create a new object implementing the interface {interface.name}:
// 3.2. Let prototype be ? Get(newTarget, "prototype").
auto prototype = TRY(new_target.get(vm.names.prototype));
// 3.3. If Type(prototype) is not Object, then:
if (!prototype.is_object()) {{
// 1. Let targetRealm be ? GetFunctionRealm(newTarget).
auto* target_realm = TRY(JS::get_function_realm(vm, new_target));
// 2. Set prototype to the interface prototype object for interface in targetRealm.
VERIFY(target_realm);
prototype = &Bindings::ensure_web_prototype<{interface.prototype_class}>(*target_realm, "{interface.namespaced_name}"_fly_string);
}}
"""
)
write_argument_count_check(out, interface.name, parameter_list_length(constructor.parameters))
write_operation_parameter_conversions(out, constructor.parameters, includes, context)
arguments = ", ".join(idl_identifier_cpp_name(parameter) for parameter in constructor.parameters)
if arguments:
arguments = f", {arguments}"
out.write(
f""" auto impl = TRY(throw_dom_exception_if_needed(vm, [&] {{ return {fully_qualified_name_for_interface(interface)}::construct_impl(realm{arguments}); }}));
// 7. Set instance.[[Prototype]] to prototype.
VERIFY(prototype.is_object());
impl->set_prototype(&prototype.as_object());
// FIXME: Steps 8...11. of the "internally create a new object implementing the interface {interface.name}" algorithm
// (https://webidl.spec.whatwg.org/#js-platform-objects) are currently not handled, or are handled within {fully_qualified_name_for_interface(interface)}::construct_impl().
return *impl;
"""
)
# https://html.spec.whatwg.org/#htmlconstructor
def write_html_constructor_steps(
out: TextIO, interface: Interface, constructor: Constructor, includes: GeneratedIncludes
) -> None:
if constructor.parameters:
raise RuntimeError(f"Unsupported [HTMLConstructor] with parameters on '{interface.name}'")
includes.add("AK/Optional.h")
includes.add("AK/String.h")
includes.add("AK/TypeCasts.h")
includes.add("LibGC/Ptr.h")
includes.add("LibJS/Runtime/Error.h")
includes.add("LibWeb/Bindings/MainThreadVM.h")
includes.add("LibWeb/DOM/Document.h")
includes.add("LibWeb/DOM/Element.h")
includes.add("LibWeb/DOM/ElementFactory.h")
includes.add("LibWeb/HTML/CustomElements/CustomElementDefinition.h")
includes.add("LibWeb/HTML/CustomElements/CustomElementRegistry.h")
includes.add("LibWeb/HTML/Scripting/SimilarOriginWindowAgent.h")
includes.add("LibWeb/HTML/Window.h")
includes.add("LibWeb/WebIDL/AbstractOperations.h")
out.write(
f""" auto& vm = constructor.vm();
auto& realm = *vm.current_realm();
auto& window = as<HTML::Window>(HTML::current_global_object());
// 1. If NewTarget is equal to the active function object, then throw a TypeError.
if (&new_target == vm.active_function_object())
return vm.throw_completion<JS::TypeError>("Cannot directly construct an HTML element, it must be inherited"sv);
// 2. Let registry be null.
GC::Ptr<HTML::CustomElementRegistry> registry;
// 3. If the surrounding agent's active custom element constructor map[NewTarget] exists:
auto& surrounding_agent = HTML::relevant_similar_origin_window_agent(window);
if (auto registry_for_constructor = surrounding_agent.active_custom_element_constructor_map.get(GC::Ref {{ new_target }}); registry_for_constructor.has_value() && !registry_for_constructor->is_null()) {{
// 1. Set registry to the surrounding agent's active custom element constructor map[NewTarget].
registry = registry_for_constructor.value();
// 2. Remove the surrounding agent's active custom element constructor map[NewTarget].
surrounding_agent.active_custom_element_constructor_map.remove(GC::Ref {{ new_target }});
}}
// 4. Otherwise, set registry to current global object's associated Document's custom element registry.
else {{
registry = window.associated_document().custom_element_registry();
}}
// 5. Let definition be the item in registry's custom element definition set with constructor equal to NewTarget.
// If there is no such item, then throw a TypeError.
auto definition = registry->get_definition_from_new_target(new_target);
if (!definition)
return vm.throw_completion<JS::TypeError>("There is no custom element definition assigned to the given constructor"sv);
// 6. Let isValue be null.
Optional<String> is_value;
// 7. If definition's local name is equal to definition's name (i.e., definition is for an autonomous custom element):
if (definition->local_name() == definition->name()) {{
// 1. If the active function object is not HTMLElement, then throw a TypeError.
{'return vm.throw_completion<JS::TypeError>("Autonomous custom elements can only inherit from HTMLElement"sv);' if interface.name != "HTMLElement" else ""}
}}
// 8. Otherwise (i.e., if definition is for a customized built-in element):
else {{
// 1. Let valid local names be the list of local names for elements defined in this specification or in other applicable specifications that use the active function object as their element interface.
static auto const& valid_local_names = *new auto(MUST(DOM::valid_local_names_for_given_html_element_interface("{interface.name}"sv)));
// 2. If valid local names does not contain definition's local name, then throw a TypeError.
if (!valid_local_names.contains_slow(definition->local_name()))
return vm.throw_completion<JS::TypeError>(MUST(String::formatted("Local name '{{}}' of customized built-in element is not a valid local name for {interface.name}", definition->local_name())));
// 3. Set isValue to definition's name.
is_value = definition->name();
}}
// 9. If definition's construction stack is empty:
if (definition->construction_stack().is_empty()) {{
// 1. Let element be the result of internally creating a new object implementing the interface to which the active function object corresponds, given the current Realm Record and NewTarget.
// 2. Set element's node document to the current global object's associated Document.
// 3. Set element's namespace to the HTML namespace.
// 4. Set element's namespace prefix to null.
// 5. Set element's local name to definition's local name.
auto element = realm.create<{fully_qualified_name_for_interface(interface)}>(window.associated_document(), DOM::QualifiedName {{ definition->local_name(), {{}}, Namespace::HTML }});
// https://webidl.spec.whatwg.org/#internally-create-a-new-object-implementing-the-interface
TRY(WebIDL::set_prototype_from_new_target<{interface.prototype_class}>(vm, new_target, "{interface.namespaced_name}"_fly_string, *element));
// 6. Set element's custom element registry to registry.
element->set_custom_element_registry(registry);
// 7. Set element's custom element state to "custom".
// 8. Set element's custom element definition to definition.
// 9. Set element's is value to isValue.
element->setup_custom_element_from_constructor(*definition, is_value);
// 10. Return element.
return *element;
}}
// 10. Let prototype be ? Get(NewTarget, "prototype").
auto prototype = TRY(new_target.get(vm.names.prototype));
// 11. If Type(prototype) is not Object, then:
if (!prototype.is_object()) {{
// 1. Let realm be ? GetFunctionRealm(NewTarget).
auto* function_realm = TRY(JS::get_function_realm(vm, new_target));
// 2. Set prototype to the interface prototype object of realm whose interface is the same as the interface of the active function object.
VERIFY(function_realm);
prototype = &Bindings::ensure_web_prototype<{interface.prototype_class}>(*function_realm, "{interface.namespaced_name}"_fly_string);
}}
VERIFY(prototype.is_object());
// 12. Let element be the last entry in definition's construction stack.
auto& element = definition->construction_stack().last();
// 13. If element is an already constructed marker, then throw a TypeError.
if (element.has<HTML::AlreadyConstructedCustomElementMarker>())
return vm.throw_completion<JS::TypeError>("Custom element has already been constructed"sv);
// 14. Perform ? element.[[SetPrototypeOf]](prototype).
auto actual_element = element.get<GC::Ref<DOM::Element>>();
TRY(actual_element->internal_set_prototype_of(&prototype.as_object()));
// 15. Replace the last entry in definition's construction stack with an already constructed marker.
definition->construction_stack().last() = HTML::AlreadyConstructedCustomElementMarker {{}};
// 16. Return element.
return *actual_element;
"""
)

View file

@ -0,0 +1,380 @@
# Copyright (c) 2026-present, the Ladybird developers.
#
# SPDX-License-Identifier: BSD-2-Clause
from dataclasses import dataclass
from dataclasses import field
from dataclasses import fields
from dataclasses import is_dataclass
from dataclasses import replace
from typing import Any
from typing import Callable
from typing import Iterable
from typing import Optional
from typing import Protocol
from typing import TypeVar
from typing import Union
from typing import cast
from Utils.webidl_parser import CallbackFunction
from Utils.webidl_parser import Dictionary
from Utils.webidl_parser import Enumeration
from Utils.webidl_parser import IDLParameterizedType
from Utils.webidl_parser import IDLType
from Utils.webidl_parser import IDLUnionType
from Utils.webidl_parser import IncludedMixin
from Utils.webidl_parser import Interface
from Utils.webidl_parser import Module
from Utils.webidl_parser import Typedef
class NamedDefinition(Protocol):
name: str
Definition = TypeVar("Definition", bound=NamedDefinition)
def collect_definitions_by_name(
modules: list[Module], definitions_for_module: Callable[[Module], Iterable[Definition]]
) -> dict[str, Definition]:
return {definition.name: definition for module in modules for definition in definitions_for_module(module)}
def collect_partial_definitions_by_name(
modules: list[Module], definitions_for_module: Callable[[Module], Iterable[Definition]]
) -> dict[str, list[Definition]]:
partial_definitions: dict[str, list[Definition]] = {}
for module in modules:
for definition in definitions_for_module(module):
partial_definitions.setdefault(definition.name, []).append(definition)
return partial_definitions
def merge_partial_definitions_by_name(
modules: list[Module],
definitions_for_module: Callable[[Module], Iterable[Definition]],
partial_definitions_for_module: Callable[[Module], Iterable[Definition]],
merge: Callable[[Definition, list[Definition]], Definition],
) -> dict[str, Definition]:
definitions = collect_definitions_by_name(modules, definitions_for_module)
partial_definitions = collect_partial_definitions_by_name(modules, partial_definitions_for_module)
return {type_: merge(definition, partial_definitions.get(type_, [])) for type_, definition in definitions.items()}
@dataclass
class GenerationContext:
modules: list[Module]
callback_functions: dict[str, CallbackFunction] = field(init=False)
dictionaries: dict[str, Dictionary] = field(init=False)
enumerations: dict[str, Enumeration] = field(init=False)
interfaces: dict[str, Interface] = field(init=False)
typedefs: dict[str, Typedef] = field(init=False)
mixins: dict[str, Interface] = field(init=False)
def __post_init__(self) -> None:
self.callback_functions = collect_definitions_by_name(self.modules, lambda module: module.callback_functions)
self.enumerations = collect_definitions_by_name(self.modules, lambda module: module.enumerations)
self.typedefs = collect_definitions_by_name(self.modules, lambda module: module.typedefs)
self.resolve_dictionaries()
self.resolve_mixins()
self.resolve_interfaces()
self.resolve_typedefs()
self.resolve_modules()
def resolve_dictionaries(self) -> None:
self.dictionaries = merge_partial_definitions_by_name(
self.modules,
lambda module: module.dictionaries,
lambda module: module.partial_dictionaries,
merge_dictionary,
)
def resolve_mixins(self) -> None:
self.mixins = merge_partial_definitions_by_name(
self.modules,
lambda module: module.mixins,
lambda module: module.partial_mixins,
merge_mixin,
)
def resolve_interfaces(self) -> None:
interfaces = {
module.interface.name: module.interface for module in self.modules if module.interface is not None
}
partial_interfaces = collect_partial_definitions_by_name(self.modules, lambda module: module.partial_interfaces)
included_mixins: dict[str, list[IncludedMixin]] = {}
seen_included_mixins: set[tuple[str, str]] = set()
for module in self.modules:
for included_mixin in module.included_mixins:
key = (
included_mixin.interface_name,
included_mixin.mixin_name,
)
if key in seen_included_mixins:
continue
seen_included_mixins.add(key)
included_mixins.setdefault(key[0], []).append(included_mixin)
self.interfaces = {
type_: merge_interface(
interface,
partial_interfaces.get(type_, []),
included_mixins.get(type_, []),
self.mixins,
)
for type_, interface in interfaces.items()
}
def resolve_modules(self) -> None:
self.modules = [self.resolve_module(module) for module in self.modules]
def resolve_module(self, module: Module) -> Module:
return replace(
module,
interface=self.interfaces[module.interface.name] if module.interface is not None else None,
dictionaries=[self.dictionaries[dictionary.name] for dictionary in module.dictionaries],
mixins=[self.mixins[mixin.name] for mixin in module.mixins],
partial_interfaces=[],
partial_dictionaries=[],
partial_mixins=[],
included_mixins=[],
)
def resolve_typedefs(self) -> None:
self.typedefs = {
type_: replace(typedef, type=self.resolve_typedef(typedef.type)) for type_, typedef in self.typedefs.items()
}
self.callback_functions = self.resolve_typedefs_in_mapping(self.callback_functions)
self.dictionaries = self.resolve_typedefs_in_mapping(self.dictionaries)
self.mixins = self.resolve_typedefs_in_mapping(self.mixins)
self.interfaces = self.resolve_typedefs_in_mapping(self.interfaces)
def resolve_typedefs_in_mapping(self, definitions):
return {type_: self.resolve_typedefs_in(definition) for type_, definition in definitions.items()}
def resolve_typedefs_in(self, value: Any) -> Any:
if isinstance(value, IDLType):
return self.resolve_typedef(value)
if isinstance(value, list):
return [self.resolve_typedefs_in(item) for item in value]
if isinstance(value, tuple):
return tuple(self.resolve_typedefs_in(item) for item in value)
if is_dataclass(value):
return replace(
cast(Any, value),
**{
field.name: self.resolve_typedefs_in(getattr(value, field.name))
for field in fields(value)
if field.init
},
)
return value
def callback_function(self, type_: IDLType) -> Optional[CallbackFunction]:
return self.callback_functions.get(type_.name)
def dictionary(self, type_: IDLType) -> Optional[Dictionary]:
return self.dictionaries.get(type_.name)
def dictionary_type_names(self, *types: IDLType) -> set[str]:
return {
nested_type.name
for type_ in types
for nested_type in type_.nested_types()
if self.dictionary(nested_type) is not None
}
def dictionary_parent(self, dictionary: Dictionary) -> Optional[Dictionary]:
if not dictionary.parent_name:
return None
parent_dictionary = self.dictionaries.get(dictionary.parent_name)
if parent_dictionary is None:
raise RuntimeError(
f"Dictionary '{dictionary.name}' inherits from unknown dictionary '{dictionary.parent_name}'"
)
return parent_dictionary
def dictionary_inheritance_stack(self, dictionary: Dictionary) -> list[Dictionary]:
stack = [dictionary]
while stack[-1].parent_name:
parent = self.dictionary_parent(stack[-1])
if parent is None:
break
stack.append(parent)
return stack
# https://webidl.spec.whatwg.org/#create-an-inheritance-stack
def inheritance_stack(self, interface: Interface) -> list[Interface]:
# 1. Let stack be a new stack.
# 2. Push I onto stack.
stack = [interface]
# 3. While I inherits from an interface,
# 1. Let I be that interface.
# 2. Push I onto stack.
while stack[-1].parent_name:
parent = self.interfaces.get(stack[-1].parent_name)
if parent is None:
break
stack.append(parent)
# 4. Return stack.
return stack
def enumeration(self, type_: IDLType) -> Optional[Enumeration]:
return self.enumerations.get(type_.name)
def interface(self, type_: IDLType) -> Optional[Interface]:
return self.interfaces.get(type_.name)
def resolve_typedef(self, type_: IDLType) -> IDLType:
resolved_type = type_
if isinstance(resolved_type, IDLUnionType):
return IDLUnionType(
[self.resolve_typedef(member_type) for member_type in resolved_type.member_types],
resolved_type.nullable,
)
if isinstance(resolved_type, IDLParameterizedType):
return IDLParameterizedType(
resolved_type.name,
[self.resolve_typedef(parameter) for parameter in resolved_type.parameters],
resolved_type.nullable,
)
seen_types: set[IDLType] = set()
while resolved_type.name in self.typedefs:
resolved_type_without_nullable = resolved_type.without_nullable()
if resolved_type_without_nullable in seen_types:
raise RuntimeError(f"Typedef '{resolved_type.name}' resolves recursively")
seen_types.add(resolved_type_without_nullable)
typedef_type = self.typedefs[resolved_type.name].type
resolved_type = typedef_type.clone_with_nullable(resolved_type.nullable or typedef_type.nullable)
if isinstance(resolved_type, IDLUnionType):
return IDLUnionType(
[self.resolve_typedef(member_type) for member_type in resolved_type.member_types],
resolved_type.nullable,
)
if isinstance(resolved_type, IDLParameterizedType):
return IDLParameterizedType(
resolved_type.name,
[self.resolve_typedef(parameter) for parameter in resolved_type.parameters],
resolved_type.nullable,
)
return resolved_type
def merge_interface_members(target: Interface, source: Interface) -> None:
target.constants.extend(source.constants)
regular_attributes = merge_definition_extended_attributes(source, source.regular_attributes)
target.regular_attributes.extend(regular_attributes)
target.static_attributes.extend(merge_definition_extended_attributes(source, source.static_attributes))
target.regular_operations.extend(merge_definition_extended_attributes(source, source.regular_operations))
target.static_operations.extend(merge_definition_extended_attributes(source, source.static_operations))
target.constructors.extend(merge_definition_extended_attributes(source, source.constructors))
if target.stringifier is None and source.stringifier is not None:
if source.stringifier.attribute is None:
target.stringifier = source.stringifier
else:
attribute_index = source.regular_attributes.index(source.stringifier.attribute)
target.stringifier = replace(
source.stringifier,
extended_attributes=regular_attributes[attribute_index].extended_attributes,
attribute=regular_attributes[attribute_index],
)
target.named_property_getter = target.named_property_getter or source.named_property_getter
target.indexed_property_getter = target.indexed_property_getter or source.indexed_property_getter
target.named_property_setter = target.named_property_setter or source.named_property_setter
target.named_property_deleter = target.named_property_deleter or source.named_property_deleter
target.indexed_property_setter = target.indexed_property_setter or source.indexed_property_setter
target.maplike = target.maplike or source.maplike
target.setlike = target.setlike or source.setlike
target.iterable = target.iterable or source.iterable
def merge_interface(
interface: Interface,
partial_interfaces: list[Interface],
included_mixins: list[IncludedMixin],
mixins: dict[str, Interface],
) -> Interface:
if not partial_interfaces and not included_mixins:
return interface
merged_interface = copy_interface(interface)
for partial_interface in partial_interfaces:
if partial_interface.extended_attributes.get("Exposed") == "Nobody":
continue
merge_interface_members(merged_interface, partial_interface)
for included_mixin in included_mixins:
mixin = mixins.get(included_mixin.mixin_name)
if mixin is None:
raise RuntimeError(f"Included mixin '{included_mixin.mixin_name}' does not exist")
merge_interface_members(merged_interface, mixin)
return merged_interface
def merge_mixin(mixin: Interface, partial_mixins: list[Interface]) -> Interface:
if not partial_mixins:
return mixin
merged_mixin = copy_interface(mixin)
for partial_mixin in partial_mixins:
if partial_mixin.extended_attributes.get("Exposed") == "Nobody":
continue
merge_interface_members(merged_mixin, partial_mixin)
return merged_mixin
def copy_interface(interface: Interface) -> Interface:
return replace(
interface,
constants=list(interface.constants),
regular_attributes=list(interface.regular_attributes),
static_attributes=list(interface.static_attributes),
regular_operations=list(interface.regular_operations),
static_operations=list(interface.static_operations),
constructors=list(interface.constructors),
)
def merge_dictionary(dictionary: Dictionary, partial_dictionaries: list[Dictionary]) -> Dictionary:
if not partial_dictionaries:
return dictionary
merged_dictionary = replace(
dictionary,
members=list(dictionary.members),
extended_attributes=dict(dictionary.extended_attributes),
)
for partial_dictionary in partial_dictionaries:
merged_dictionary.members.extend(
merge_definition_extended_attributes(partial_dictionary, partial_dictionary.members)
)
merged_dictionary.members.sort(key=lambda member: member.name)
return merged_dictionary
def merge_definition_extended_attributes(source: Union[Interface, Dictionary], members):
if not source.extended_attributes:
return members
return [
replace(member, extended_attributes={**source.extended_attributes, **member.extended_attributes})
for member in members
]

View file

@ -0,0 +1,583 @@
# Copyright (c) 2026-present, the Ladybird developers.
#
# SPDX-License-Identifier: BSD-2-Clause
from dataclasses import dataclass
from dataclasses import replace
from enum import Enum
from typing import Optional
from typing import Protocol
from typing import Union
from Generators.libweb_bindings.context import GenerationContext
from Generators.libweb_bindings.includes import GeneratedIncludes
from Utils.utils import make_name_acceptable_cpp
from Utils.utils import snake_casify
from Utils.utils import title_case_to_snake_case
from Utils.webidl_parser import Attribute
from Utils.webidl_parser import Dictionary
from Utils.webidl_parser import DictionaryMember
from Utils.webidl_parser import Enumeration
from Utils.webidl_parser import IDLParameterizedType
from Utils.webidl_parser import IDLType
from Utils.webidl_parser import IDLUnionType
from Utils.webidl_parser import Interface
from Utils.webidl_parser import OperationParameter
class ContainedStorageType(Enum):
Vector = "Vector"
ConservativeVector = "GC::ConservativeVector"
RootVector = "GC::RootVector"
class TypeOptionality(Enum):
Required = "Required"
OptionalArgument = "OptionalArgument"
OptionalDictionaryMember = "OptionalDictionaryMember"
@dataclass
class CppType:
name: str
contained_storage_type: ContainedStorageType = ContainedStorageType.Vector
is_nullable: bool = False
is_optional_presence: bool = False
gc_ref_target_type: str = ""
@dataclass
class InterfaceLikeType:
name: str
fully_qualified_name: str
implementation_header: str
DictionaryMemberOrAttribute = Union[DictionaryMember, Attribute, OperationParameter]
class IDLNamed(Protocol):
name: str
extended_attributes: dict[str, str]
ARRAY_BUFFER_VIEW_TYPES = (
"Int8Array",
"Int16Array",
"Int32Array",
"Uint8Array",
"Uint16Array",
"Uint32Array",
"Uint8ClampedArray",
"BigInt64Array",
"BigUint64Array",
"Float16Array",
"Float32Array",
"Float64Array",
"DataView",
)
BUFFER_SOURCE_TYPES = ("ArrayBuffer", *ARRAY_BUFFER_VIEW_TYPES)
TYPED_ARRAY_TYPES = tuple(type_name for type_name in ARRAY_BUFFER_VIEW_TYPES if type_name != "DataView")
def cpp_name(member: DictionaryMember) -> str:
return make_name_acceptable_cpp(title_case_to_snake_case(member.name))
def idl_identifier_cpp_name(identifier: IDLNamed, suffix: Optional[Union[str, int]] = None) -> str:
cpp_name = make_name_acceptable_cpp(snake_casify(title_case_to_snake_case(identifier.name)))
if suffix is not None:
cpp_name = f"{cpp_name}{suffix}"
return cpp_name
def idl_implementation_cpp_name(identifier: IDLNamed) -> str:
return identifier.extended_attributes.get("ImplementedAs", idl_identifier_cpp_name(identifier))
def is_optional_without_default(member: DictionaryMemberOrAttribute) -> bool:
return isinstance(member, DictionaryMember) and not member.required and member.default_value is None
def is_numeric_type(type_name: str) -> bool:
return type_name in (
"byte",
"octet",
"short",
"unsigned short",
"long",
"unsigned long",
"long long",
"unsigned long long",
"float",
"unrestricted float",
"double",
"unrestricted double",
)
def is_string_type(type_name: str) -> bool:
return type_name in ("DOMString", "USVString", "ByteString", "Utf16DOMString", "Utf16USVString")
def cpp_type_name_for_string(type_name: str, extended_attributes: Optional[dict[str, str]] = None) -> str:
is_fly_string = extended_attributes is not None and "FlyString" in extended_attributes
is_utf16_string = "Utf16" in type_name
if is_utf16_string:
return "Utf16FlyString" if is_fly_string else "Utf16String"
return "FlyString" if is_fly_string else "String"
def add_include_for_string_cpp_type(cpp_type_name: str, includes: GeneratedIncludes) -> None:
if cpp_type_name == "FlyString":
includes.add("AK/FlyString.h")
elif cpp_type_name == "Utf16FlyString":
includes.add("AK/Utf16FlyString.h")
elif cpp_type_name == "Utf16String":
includes.add("AK/Utf16String.h")
else:
includes.add("AK/String.h")
def interface_like_type_for_idl_type(
idl_type: IDLType,
context: GenerationContext,
) -> Optional[InterfaceLikeType]:
if idl_type.name == "WindowProxy":
return InterfaceLikeType("WindowProxy", "HTML::WindowProxy", "LibWeb/HTML/WindowProxy.h")
interface = context.interface(idl_type)
if interface is None or interface.is_callback_interface:
return None
return InterfaceLikeType(
interface.name,
fully_qualified_name_for_interface(interface),
implementation_header_for_interface(interface),
)
def is_buffer_source_type(idl_type: IDLType) -> bool:
return idl_type.name in BUFFER_SOURCE_TYPES
def is_typed_array_type(idl_type: IDLType) -> bool:
return idl_type.name in TYPED_ARRAY_TYPES
def add_buffer_source_type_include(idl_type: IDLType, includes: GeneratedIncludes) -> None:
if idl_type.name == "DataView":
includes.add("LibJS/Runtime/DataView.h")
elif idl_type.name == "ArrayBuffer":
includes.add("LibJS/Runtime/ArrayBuffer.h")
else:
includes.add("LibJS/Runtime/TypedArray.h")
def add_include_for_contained_storage_type(
contained_storage_type: ContainedStorageType,
includes: GeneratedIncludes,
) -> None:
if contained_storage_type is ContainedStorageType.ConservativeVector:
includes.add("LibGC/ConservativeVector.h")
elif contained_storage_type is ContainedStorageType.RootVector:
includes.add("LibGC/RootVector.h")
else:
includes.add("AK/Vector.h")
def gc_ref_type(referent_type: str) -> CppType:
return CppType(
name=f"GC::Ref<{referent_type}>",
contained_storage_type=ContainedStorageType.RootVector,
gc_ref_target_type=referent_type,
)
def gc_ptr_type(referent_type: str) -> CppType:
return CppType(
name=f"GC::Ptr<{referent_type}>",
contained_storage_type=ContainedStorageType.RootVector,
is_nullable=True,
gc_ref_target_type=referent_type,
)
def type_contains_gc_like_value(context: GenerationContext, idl_type: IDLType) -> bool:
if isinstance(idl_type, IDLUnionType):
return any(type_contains_gc_like_value(context, member_type) for member_type in idl_type.member_types)
if isinstance(idl_type, IDLParameterizedType):
return any(type_contains_gc_like_value(context, parameter) for parameter in idl_type.parameters)
return (
context.interface(idl_type) is not None
or idl_type.name == "WindowProxy"
or is_buffer_source_type(idl_type)
or context.callback_function(idl_type) is not None
or idl_type.name in ("any", "object", "Promise")
)
def contained_storage_type_for_aggregate_type(context: GenerationContext, idl_type: IDLType) -> ContainedStorageType:
if type_contains_gc_like_value(context, idl_type):
return ContainedStorageType.ConservativeVector
return ContainedStorageType.Vector
def union_type_to_variant(union_type: IDLUnionType, context: GenerationContext) -> str:
cpp_types = [cpp_type_for_idl_type(member_type, context) for member_type in union_type.flattened_member_types()]
if union_type.includes_undefined() or union_type.includes_nullable_type():
cpp_types.append("Empty")
return f"Variant<{', '.join(cpp_types)}>"
def cpp_value_type(member: DictionaryMemberOrAttribute, context: GenerationContext) -> str:
optionality = TypeOptionality.OptionalArgument if is_optional_without_default(member) else TypeOptionality.Required
return cpp_type_for_idl_type(
member.type,
context,
optionality=optionality,
extended_attributes=getattr(member, "extended_attributes", {}),
)
def cpp_type_for_non_nullable_idl_type(
idl_type: IDLType,
context: GenerationContext,
extended_attributes: Optional[dict[str, str]] = None,
) -> CppType:
type_name = idl_type.name
interface_like_type = interface_like_type_for_idl_type(idl_type, context)
if interface_like_type is not None:
return gc_ref_type(interface_like_type.fully_qualified_name)
interface = context.interface(idl_type)
if interface is not None:
return gc_ref_type(fully_qualified_name_for_interface(interface))
if context.callback_function(idl_type) is not None:
return gc_ref_type("WebIDL::CallbackType")
if is_buffer_source_type(idl_type):
return gc_ref_type(f"JS::{type_name}")
if type_name == "any":
return CppType("JS::Value", ContainedStorageType.RootVector)
if type_name == "boolean":
return CppType("bool")
if is_string_type(type_name):
return CppType(cpp_type_name_for_string(type_name, extended_attributes))
if type_name in ("double", "unrestricted double"):
return CppType("double")
if type_name in ("float", "unrestricted float"):
return CppType("float")
if type_name == "undefined":
return CppType("Empty")
if type_name == "object":
return gc_ref_type("JS::Object")
if type_name == "bigint":
return gc_ref_type("JS::BigInt")
if type_name == "byte":
return CppType("WebIDL::Byte")
if type_name == "octet":
return CppType("WebIDL::Octet")
if type_name == "short":
return CppType("WebIDL::Short")
if type_name == "unsigned short":
return CppType("WebIDL::UnsignedShort")
if type_name == "long":
return CppType("WebIDL::Long")
if type_name == "unsigned long":
return CppType("WebIDL::UnsignedLong")
if type_name == "long long":
return CppType("WebIDL::LongLong")
if type_name == "unsigned long long":
return CppType("WebIDL::UnsignedLongLong")
if isinstance(idl_type, IDLParameterizedType):
if type_name == "Promise":
return gc_ref_type("WebIDL::Promise")
if type_name in ("sequence", "FrozenArray"):
sequence_cpp_type = cpp_type_for_idl_type_details(idl_type.parameters[0], context)
storage_type_name = sequence_cpp_type.contained_storage_type.value
return CppType(
f"{storage_type_name}<{sequence_cpp_type.name}>",
sequence_cpp_type.contained_storage_type,
)
if type_name == "record":
key_cpp_type = cpp_type_for_idl_type_details(idl_type.parameters[0], context)
value_cpp_type = cpp_type_for_idl_type_details(idl_type.parameters[1], context)
if (
key_cpp_type.contained_storage_type == ContainedStorageType.ConservativeVector
or value_cpp_type.contained_storage_type == ContainedStorageType.ConservativeVector
):
return CppType(
f"GC::ConservativeHashMap<{key_cpp_type.name}, {value_cpp_type.name}>",
ContainedStorageType.ConservativeVector,
)
if (
key_cpp_type.contained_storage_type == ContainedStorageType.RootVector
or value_cpp_type.contained_storage_type == ContainedStorageType.RootVector
):
return CppType(
f"GC::OrderedRootHashMap<{key_cpp_type.name}, {value_cpp_type.name}>",
ContainedStorageType.RootVector,
)
return CppType(f"OrderedHashMap<{key_cpp_type.name}, {value_cpp_type.name}>")
if isinstance(idl_type, IDLUnionType):
cpp_type = CppType(
union_type_to_variant(idl_type, context), contained_storage_type_for_aggregate_type(context, idl_type)
)
cpp_type.is_nullable = idl_type.includes_undefined() or idl_type.includes_nullable_type()
return cpp_type
return CppType(type_name)
def with_nullable_cpp_type(cpp_type: CppType) -> CppType:
if cpp_type.name == "JS::Value":
return replace(cpp_type, is_nullable=True)
if cpp_type.gc_ref_target_type:
return gc_ptr_type(cpp_type.gc_ref_target_type)
return replace(cpp_type, name=f"Optional<{cpp_type.name}>", is_nullable=True)
def with_optional_cpp_type(cpp_type: CppType) -> CppType:
if cpp_type.gc_ref_target_type and not cpp_type.is_nullable:
return replace(
cpp_type,
name=f"GC::Ptr<{cpp_type.gc_ref_target_type}>",
contained_storage_type=ContainedStorageType.RootVector,
is_nullable=True,
is_optional_presence=True,
)
return replace(
cpp_type,
name=f"Optional<{cpp_type.name}>",
contained_storage_type=ContainedStorageType.Vector,
is_optional_presence=True,
)
def cpp_type_for_idl_type_details(
idl_type: IDLType,
context: GenerationContext,
optionality: TypeOptionality = TypeOptionality.Required,
extended_attributes: Optional[dict[str, str]] = None,
) -> CppType:
if not idl_type.nullable or isinstance(idl_type, IDLUnionType):
cpp_type = cpp_type_for_non_nullable_idl_type(idl_type, context, extended_attributes)
else:
cpp_type = with_nullable_cpp_type(
cpp_type_for_non_nullable_idl_type(
idl_type.clone_with_nullable(False),
context,
extended_attributes,
)
)
if optionality is TypeOptionality.Required or (
optionality is TypeOptionality.OptionalArgument and cpp_type.is_nullable
):
return cpp_type
return with_optional_cpp_type(cpp_type)
def cpp_type_for_idl_type(
idl_type: IDLType,
context: GenerationContext,
optionality: TypeOptionality = TypeOptionality.Required,
extended_attributes: Optional[dict[str, str]] = None,
) -> str:
return cpp_type_for_idl_type_details(idl_type, context, optionality, extended_attributes).name
def cpp_type_details(member: DictionaryMemberOrAttribute, context: GenerationContext) -> CppType:
optionality = (
TypeOptionality.OptionalDictionaryMember if is_optional_without_default(member) else TypeOptionality.Required
)
return cpp_type_for_idl_type_details(
member.type,
context,
optionality=optionality,
extended_attributes=getattr(member, "extended_attributes", {}),
)
def cpp_type(member: DictionaryMemberOrAttribute, context: GenerationContext) -> str:
return cpp_type_details(member, context).name
def cpp_empty_value(member: DictionaryMember, context: GenerationContext) -> str:
if cpp_type_details(member, context).gc_ref_target_type and not member.type.nullable:
return "nullptr"
return "OptionalNone {}"
def cpp_null_value(idl_type: IDLType, context: GenerationContext) -> str:
if isinstance(idl_type, IDLUnionType):
return "Empty {}"
if cpp_type_for_idl_type_details(idl_type, context).gc_ref_target_type:
return "nullptr"
return "OptionalNone {}"
def add_header_includes_for_idl_type(
idl_type: IDLType,
includes: GeneratedIncludes,
context: GenerationContext,
) -> None:
if isinstance(idl_type, IDLUnionType):
includes.add("AK/Variant.h")
if idl_type.includes_undefined() or idl_type.includes_nullable_type():
includes.add("AK/Types.h")
for member_type in idl_type.flattened_member_types():
add_header_includes_for_idl_type(member_type.clone_with_nullable(False), includes, context)
return
if isinstance(idl_type, IDLParameterizedType):
if idl_type.name == "Promise":
includes.add("LibGC/Ptr.h")
includes.add("LibWeb/Forward.h")
return
if idl_type.name in ("sequence", "FrozenArray"):
cpp_type = cpp_type_for_idl_type_details(idl_type, context)
add_include_for_contained_storage_type(cpp_type.contained_storage_type, includes)
for parameter in idl_type.parameters:
add_header_includes_for_idl_type(parameter, includes, context)
return
if idl_type.name == "record":
cpp_type = cpp_type_for_idl_type_details(idl_type, context)
if cpp_type.contained_storage_type is ContainedStorageType.ConservativeVector:
includes.add("LibGC/ConservativeHashMap.h")
elif cpp_type.contained_storage_type is ContainedStorageType.RootVector:
includes.add("LibGC/RootHashMap.h")
else:
includes.add("AK/HashMap.h")
for parameter in idl_type.parameters:
add_header_includes_for_idl_type(parameter, includes, context)
return
type_name = idl_type.name
if type_name == "undefined":
includes.add("AK/Types.h")
return
if type_name == "any":
includes.add("LibJS/Runtime/Value.h")
return
if type_name == "boolean" or type_name in ("float", "unrestricted float", "double", "unrestricted double"):
return
if type_name in (
"byte",
"octet",
"short",
"unsigned short",
"long",
"unsigned long",
"long long",
"unsigned long long",
):
includes.add("LibWeb/WebIDL/Types.h")
return
if is_string_type(type_name):
add_include_for_string_cpp_type(cpp_type_name_for_string(type_name), includes)
return
if context.enumeration(idl_type) is not None:
add_binding_include_for_type(idl_type, includes, context)
return
interface_like_type = interface_like_type_for_idl_type(idl_type, context)
if interface_like_type is not None:
includes.add("LibGC/Ptr.h")
includes.add("LibWeb/Forward.h")
return
interface = context.interface(idl_type)
if interface is not None:
includes.add("LibGC/Ptr.h")
includes.add("LibWeb/Forward.h")
return
if context.callback_function(idl_type) is not None:
includes.add("LibGC/Ptr.h")
includes.add("LibWeb/WebIDL/CallbackType.h")
return
if is_buffer_source_type(idl_type):
includes.add("LibGC/Ptr.h")
add_buffer_source_type_include(idl_type, includes)
return
if cpp_type_for_idl_type(idl_type, context) == type_name:
add_binding_include_for_type(idl_type, includes, context)
def add_header_includes_for_type(
member: DictionaryMember,
includes: GeneratedIncludes,
context: GenerationContext,
) -> None:
member_cpp_type = cpp_type_details(member, context)
if member_cpp_type.is_optional_presence or (member_cpp_type.is_nullable and not member_cpp_type.gc_ref_target_type):
includes.add("AK/Optional.h")
add_header_includes_for_idl_type(member.type, includes, context)
def libweb_include_path(path) -> str:
parts = path.parts
return "/".join(parts[parts.index("LibWeb") :])
def implementation_header_for_interface(interface: Interface) -> str:
return libweb_include_path(interface.path.with_name(f"{interface.implemented_name}.h"))
def fully_qualified_name_for_interface(interface: Interface) -> str:
parts = interface.path.parts
namespace_name = parts[parts.index("LibWeb") + 1]
return f"{namespace_name}::{interface.implemented_name}"
def converter_function_name(definition: Union[IDLType, Dictionary, Enumeration]) -> str:
converter_name = make_name_acceptable_cpp(title_case_to_snake_case(definition.name))
return f"convert_to_idl_value_for_{converter_name}"
def add_binding_include_for_type(idl_type: IDLType, includes: GeneratedIncludes, context: GenerationContext) -> None:
if includes.is_local_type(idl_type.without_nullable().name):
return
dictionary = context.dictionary(idl_type)
if dictionary is not None:
includes.add_binding(dictionary.path.stem)
return
enumeration = context.enumeration(idl_type)
if enumeration is not None:
includes.add_binding(enumeration.path.stem)
return
callback_function = context.callback_function(idl_type)
if callback_function is not None:
includes.add_binding(callback_function.path.stem)
return
interface = context.interface(idl_type)
if interface is not None:
includes.add_binding(interface.implemented_name)
return
includes.add_binding(idl_type.name)

View file

@ -0,0 +1,183 @@
# Copyright (c) 2026-present, the Ladybird developers.
#
# SPDX-License-Identifier: BSD-2-Clause
from dataclasses import replace
from typing import Callable
from typing import Optional
from typing import Union
from Generators.libweb_bindings.context import GenerationContext
from Generators.libweb_bindings.cpp_types import CppType
from Generators.libweb_bindings.cpp_types import cpp_null_value
from Generators.libweb_bindings.cpp_types import cpp_type
from Generators.libweb_bindings.cpp_types import cpp_type_for_idl_type
from Generators.libweb_bindings.cpp_types import cpp_type_for_idl_type_details
from Generators.libweb_bindings.cpp_types import is_numeric_type
from Generators.libweb_bindings.cpp_types import is_string_type
from Utils.utils import string_to_cpp_enum_name
from Utils.webidl_parser import DictionaryMember
from Utils.webidl_parser import IDLParameterizedType
from Utils.webidl_parser import IDLType
from Utils.webidl_parser import IDLUnionType
from Utils.webidl_parser import OperationParameter
DefaultValueMember = Union[DictionaryMember, OperationParameter]
def string_default_value_expression(cpp_type: CppType, default_value: str) -> str:
if cpp_type.name == "String":
return "String {}" if default_value == '""' else f"{default_value}_string"
if cpp_type.name == "FlyString":
return "FlyString {}" if default_value == '""' else f"{default_value}_fly_string"
if cpp_type.name == "ByteString":
return "ByteString {}" if default_value == '""' else f"{default_value}sv"
if cpp_type.name == "Utf16String":
return "Utf16String {}" if default_value == '""' else f"{default_value}_utf16"
if cpp_type.name == "Utf16FlyString":
return "Utf16FlyString {}" if default_value == '""' else f"{default_value}_utf16_fly_string"
raise RuntimeError(f"Unsupported string default value type '{cpp_type.name}'")
def first_flattened_member_type_matching(
union_type: IDLUnionType,
predicate: Callable[[IDLType], bool],
) -> Optional[IDLType]:
return next((member_type for member_type in union_type.flattened_member_types() if predicate(member_type)), None)
def is_numeric_default_value(default_value: str) -> bool:
try:
int(default_value, 0)
return True
except ValueError:
pass
try:
float(default_value)
return True
except ValueError:
return False
def union_member_type_for_default_value(
union_type: IDLUnionType,
default_value: str,
context: GenerationContext,
) -> IDLType:
# Default values are stored as raw IDL text. For unions, pick the union member type that can represent
# that literal so cpp_default_value_conversion() can emit the typed C++ expression for that member.
if default_value == "[]":
sequence_type = first_flattened_member_type_matching(
union_type,
lambda member_type: (
isinstance(member_type, IDLParameterizedType) and member_type.name in ("sequence", "FrozenArray")
),
)
if sequence_type is not None:
return sequence_type
if default_value == "{}":
def accepts_empty_object(member_type: IDLType) -> bool:
return (
isinstance(member_type, IDLParameterizedType)
and member_type.name == "record"
or context.dictionary(member_type) is not None
)
object_type = first_flattened_member_type_matching(
union_type,
accepts_empty_object,
)
if object_type is not None:
return object_type
if default_value.startswith('"') and default_value.endswith('"'):
string_type = first_flattened_member_type_matching(
union_type,
lambda member_type: is_string_type(member_type.name),
)
if string_type is not None:
return string_type
enum_value = default_value.removeprefix('"').removesuffix('"')
def accepts_enum_value(member_type: IDLType) -> bool:
enumeration = context.enumeration(member_type)
return enumeration is not None and enum_value in enumeration.values
enum_type = first_flattened_member_type_matching(
union_type,
accepts_enum_value,
)
if enum_type is not None:
return enum_type
if default_value in ("true", "false"):
boolean_type = first_flattened_member_type_matching(
union_type,
lambda member_type: member_type.name == "boolean",
)
if boolean_type is not None:
return boolean_type
if is_numeric_default_value(default_value):
numeric_type = first_flattened_member_type_matching(
union_type,
lambda member_type: is_numeric_type(member_type.name),
)
if numeric_type is not None:
return numeric_type
if default_value == "null" and (union_type.includes_undefined() or union_type.includes_nullable_type()):
return IDLType("undefined")
raise RuntimeError(f"Unsupported union default value '{default_value}' for '{union_type}'")
def cpp_default_value_conversion(
member: DefaultValueMember,
context: GenerationContext,
) -> str:
if member.default_value is None:
member_kind = "operation parameter" if isinstance(member, OperationParameter) else "dictionary member"
raise RuntimeError(f"{member_kind.capitalize()} '{member.name}' has no default value")
member_type = member.type
if isinstance(member_type, IDLUnionType):
union_member_type = union_member_type_for_default_value(member_type, member.default_value, context)
if union_member_type.name == "undefined":
return f"{cpp_type_for_idl_type(member_type, context)} {{ Empty {{}} }}"
union_member = replace(member, type=union_member_type)
expression = cpp_default_value_conversion(union_member, context)
return f"{cpp_type_for_idl_type(member_type, context)} {{ {expression} }}"
if member.default_value == "{}":
return f"{member_type.name} {{}}"
if member.default_value == "null":
if member_type.name == "any":
return "JS::js_null()"
return cpp_null_value(member_type, context)
if member_type.name == "boolean":
return member.default_value
if is_numeric_type(member_type.name):
return member.default_value
if (
member.default_value == "[]"
and isinstance(member_type, IDLParameterizedType)
and member_type.name in ("sequence", "FrozenArray")
):
return f"{cpp_type(member, context)} {{}}"
if member.default_value.startswith('"') and member.default_value.endswith('"'):
if (enumeration := context.enumeration(member_type)) is not None:
unquoted_default_value = member.default_value.removeprefix('"').removesuffix('"')
return f"{enumeration.name}::{string_to_cpp_enum_name(unquoted_default_value)}"
string_cpp_type = cpp_type_for_idl_type_details(
member_type.without_nullable(),
context,
extended_attributes=member.extended_attributes,
)
return string_default_value_expression(string_cpp_type, member.default_value)
raise RuntimeError(f"Unsupported default value for dictionary member '{member.name}'")

View file

@ -0,0 +1,60 @@
# Copyright (c) 2026-present, the Ladybird developers.
#
# SPDX-License-Identifier: BSD-2-Clause
from Generators.libweb_bindings.includes import GeneratedIncludes
def wrap_with_extended_attribute_exposure_checks(
includes: GeneratedIncludes, extended_attributes: dict[str, str], text: str
) -> str:
if "SecureContext" in extended_attributes:
includes.add("LibWeb/Bindings/PrincipalHostDefined.h")
text = text.replace("\n", "\n ")
text = f""" if (HTML::is_secure_context(Bindings::principal_host_defined_environment_settings_object(realm))) {{
{text} }}
"""
if extended_attributes.get("Exposed") == "Window":
includes.add("AK/TypeCasts.h")
includes.add("LibWeb/HTML/Window.h")
text = text.replace("\n", "\n ")
text = f""" if (is<HTML::Window>(realm.global_object())) {{
{text} }}
"""
if "Experimental" in extended_attributes:
includes.add("LibWeb/HTML/UniversalGlobalScope.h")
text = text.replace("\n", "\n ")
text = f""" if (HTML::UniversalGlobalScopeMixin::expose_experimental_interfaces()) {{
{text} }}
"""
return text
def wrap_with_ce_reactions(includes: GeneratedIncludes, expression: str) -> str:
includes.add("LibWeb/HTML/Scripting/SimilarOriginWindowAgent.h")
includes.add("LibWeb/Bindings/MainThreadVM.h")
return f"""[&]() -> decltype({expression}) {{
// For [CEReactions]: https://html.spec.whatwg.org/multipage/custom-elements.html#cereactions
// 1. Push a new element queue onto this object's relevant agent's custom element reactions stack.
auto& reactions_stack = HTML::relevant_similar_origin_window_agent(*idl_object).custom_element_reactions_stack;
reactions_stack.element_queue_stack.append({{}});
// 2. Run the originally-specified steps for this construct, catching any exceptions. If the steps return a value, let value be the returned value. If they throw an exception, let exception be the thrown exception.
auto value_or_exception = {expression};
// 3. Let queue be the result of popping from this object's relevant agent's custom element reactions stack.
// 4. Invoke custom element reactions in queue.
auto queue = reactions_stack.element_queue_stack.take_last();
Bindings::invoke_custom_element_reactions(queue);
// 5. If an exception exception was thrown by the original steps, rethrow exception.
if (value_or_exception.is_error())
return value_or_exception.release_error();
// 6. If a value value was returned from the original steps, return value.
return value_or_exception.release_value();
}}()"""

View file

@ -0,0 +1,151 @@
# Copyright (c) 2026-present, the Ladybird developers.
#
# SPDX-License-Identifier: BSD-2-Clause
from typing import TextIO
from Generators.libweb_bindings import overload_resolution
from Generators.libweb_bindings.attributes import attribute_getter_callback_name
from Generators.libweb_bindings.attributes import attribute_has_setter
from Generators.libweb_bindings.attributes import attribute_setter_callback_name
from Generators.libweb_bindings.attributes import define_the_regular_attributes
from Generators.libweb_bindings.attributes import define_the_unforgeable_attributes
from Generators.libweb_bindings.attributes import write_attribute_getter
from Generators.libweb_bindings.attributes import write_attribute_setter
from Generators.libweb_bindings.constants import define_the_constants
from Generators.libweb_bindings.context import GenerationContext
from Generators.libweb_bindings.cpp_types import idl_identifier_cpp_name
from Generators.libweb_bindings.includes import GeneratedIncludes
from Generators.libweb_bindings.operations import define_the_regular_operations
from Generators.libweb_bindings.operations import define_the_stringifier
from Generators.libweb_bindings.operations import write_regular_operations_for_receiver
from Generators.libweb_bindings.operations import write_stringifier
from Utils.webidl_parser import Interface
def global_mixin_member_interfaces(interface: Interface, context: GenerationContext) -> list[Interface]:
return [
interface_in_chain
for interface_in_chain in reversed(context.inheritance_stack(interface))
if interface_in_chain.name != "EventTarget"
]
def write_global_mixin_declaration(out: TextIO, context: GenerationContext, interface: Interface) -> None:
out.write(
f"""class {interface.name}GlobalMixin {{
public:
void initialize(JS::Realm&, JS::Object&);
void define_unforgeable_attributes(JS::Realm&, JS::Object&);
{interface.name}GlobalMixin();
virtual ~{interface.name}GlobalMixin();
private:
"""
)
declared_callbacks: set[str] = set()
for member_interface in global_mixin_member_interfaces(interface, context):
for attribute in member_interface.regular_attributes:
if "FIXME" in attribute.extended_attributes:
continue
getter_callback = attribute_getter_callback_name(attribute)
if getter_callback not in declared_callbacks:
declared_callbacks.add(getter_callback)
out.write(f" JS_DECLARE_NATIVE_FUNCTION({getter_callback});\n")
if attribute_has_setter(attribute, include_replaceable=True):
setter_callback = attribute_setter_callback_name(attribute)
if setter_callback not in declared_callbacks:
declared_callbacks.add(setter_callback)
out.write(f" JS_DECLARE_NATIVE_FUNCTION({setter_callback});\n")
for operations in overload_resolution.operation_overload_sets(member_interface).values():
operation = operations[0]
callback = idl_identifier_cpp_name(operation)
if callback not in declared_callbacks:
declared_callbacks.add(callback)
out.write(f" JS_DECLARE_NATIVE_FUNCTION({callback});\n")
if len(operations) > 1:
for overload_index, overloaded_operation in enumerate(operations):
overloaded_callback = idl_identifier_cpp_name(overloaded_operation, suffix=overload_index)
if overloaded_callback not in declared_callbacks:
declared_callbacks.add(overloaded_callback)
out.write(f" JS_DECLARE_NATIVE_FUNCTION({overloaded_callback});\n")
if member_interface.stringifier is not None and "to_string" not in declared_callbacks:
declared_callbacks.add("to_string")
out.write(" JS_DECLARE_NATIVE_FUNCTION(to_string);\n")
out.write(
"""};
"""
)
def write_global_mixin_implementation(
out: TextIO, context: GenerationContext, includes: GeneratedIncludes, interface: Interface
) -> None:
if "Global" not in interface.extended_attributes:
return
includes.add("LibWeb/Bindings/Intrinsics.h")
member_interfaces = global_mixin_member_interfaces(interface, context)
out.write(
f"""{interface.name}GlobalMixin::{interface.name}GlobalMixin() = default;
{interface.name}GlobalMixin::~{interface.name}GlobalMixin() = default;
void {interface.name}GlobalMixin::initialize(JS::Realm& realm, [[maybe_unused]] JS::Object& object)
{{
[[maybe_unused]] auto& vm = realm.vm();
[[maybe_unused]] u8 default_attributes = JS::Attribute::Enumerable | JS::Attribute::Configurable | JS::Attribute::Writable;
object.set_prototype(&ensure_web_prototype<{interface.prototype_class}>(realm, "{interface.namespaced_name}"_fly_string));
"""
)
for member_interface in member_interfaces:
define_the_regular_attributes(out, includes, member_interface, include_replaceable_setters=True)
define_the_regular_operations(out, includes, member_interface)
define_the_stringifier(out, includes, member_interface)
define_the_constants(out, context, includes, member_interface)
out.write(
f"""}}
void {interface.name}GlobalMixin::define_unforgeable_attributes(JS::Realm& realm, [[maybe_unused]] JS::Object& object)
{{
[[maybe_unused]] auto& vm = realm.vm();
[[maybe_unused]] u8 default_attributes = JS::Attribute::Enumerable;
"""
)
for member_interface in member_interfaces:
define_the_unforgeable_attributes(out, includes, member_interface, include_replaceable_setters=True)
define_the_regular_operations(out, includes, member_interface, unforgeable=True)
define_the_stringifier(out, includes, member_interface, unforgeable=True)
out.write(
"""}
"""
)
defined_callbacks: set[str] = set()
for member_interface in member_interfaces:
for attribute in member_interface.regular_attributes:
if "FIXME" in attribute.extended_attributes:
continue
getter_callback = attribute_getter_callback_name(attribute)
if getter_callback not in defined_callbacks:
defined_callbacks.add(getter_callback)
write_attribute_getter(
out, context, includes, member_interface, attribute, f"{interface.name}GlobalMixin"
)
if attribute_has_setter(attribute, include_replaceable=True):
setter_callback = attribute_setter_callback_name(attribute)
if setter_callback not in defined_callbacks:
defined_callbacks.add(setter_callback)
write_attribute_setter(
out, context, includes, member_interface, attribute, f"{interface.name}GlobalMixin"
)
write_regular_operations_for_receiver(
out, context, includes, member_interface, f"{interface.name}GlobalMixin", defined_callbacks
)
if member_interface.stringifier is not None and "to_string" not in defined_callbacks:
defined_callbacks.add("to_string")
write_stringifier(out, context, includes, member_interface, f"{interface.name}GlobalMixin")

View file

@ -0,0 +1,28 @@
# Copyright (c) 2026-present, the Ladybird developers.
#
# SPDX-License-Identifier: BSD-2-Clause
from typing import Optional
from typing import TextIO
class GeneratedIncludes:
def __init__(self, local_type_names: Optional[set[str]] = None) -> None:
self._includes: set[str] = set()
self._local_type_names = local_type_names or set()
def is_local_type(self, type_name: str) -> bool:
return type_name in self._local_type_names
def add(self, include: str) -> None:
self._includes.add(include)
def add_binding(self, name: str) -> None:
self.add(f"LibWeb/Bindings/{name}.h")
def write(self, out: TextIO) -> None:
for include in sorted(self._includes):
out.write(f"#include <{include}>\n")
if self._includes:
out.write("\n")

View file

@ -0,0 +1,185 @@
# Copyright (c) 2026-present, the Ladybird developers.
#
# SPDX-License-Identifier: BSD-2-Clause
from typing import TextIO
from Generators.libweb_bindings import overload_resolution
from Generators.libweb_bindings.attributes import attribute_getter_callback_name
from Generators.libweb_bindings.attributes import attribute_has_setter
from Generators.libweb_bindings.attributes import attribute_setter_callback_name
from Generators.libweb_bindings.callback_interfaces import write_callback_interface_declaration
from Generators.libweb_bindings.context import GenerationContext
from Generators.libweb_bindings.cpp_types import idl_identifier_cpp_name
from Generators.libweb_bindings.global_mixins import write_global_mixin_declaration
from Generators.libweb_bindings.includes import GeneratedIncludes
from Generators.libweb_bindings.iterables import write_async_iterator_prototype_declaration
from Generators.libweb_bindings.iterables import write_iterator_prototype_declaration
from Generators.libweb_bindings.named_and_indexed_properties import interface_supports_named_properties
from Generators.libweb_bindings.named_and_indexed_properties import write_named_properties_object_declaration
from Generators.libweb_bindings.namespaces import write_namespace_declaration
from Generators.libweb_bindings.overload_resolution import operation_callback_names
from Utils.webidl_parser import Interface
def interface_requires_custom_prototype(interface: Interface) -> bool:
return (
"Global" in interface.extended_attributes
or interface.indexed_property_getter is not None
or interface.named_property_getter is not None
or interface.named_property_setter is not None
or interface.named_property_deleter is not None
or interface.indexed_property_setter is not None
or interface.maplike is not None
or interface.setlike is not None
or (interface.iterable is not None and interface.iterable.key_type is not None)
or interface.async_iterable is not None
)
def write_declaration(
out: TextIO, includes: GeneratedIncludes, context: GenerationContext, interface: Interface
) -> None:
if interface.is_callback_interface:
write_callback_interface_declaration(out, includes, context, interface)
return
if interface.is_namespace:
write_namespace_declaration(out, includes, context, interface)
return
includes.add("LibJS/Runtime/NativeFunction.h")
includes.add("LibJS/Runtime/Object.h")
includes.add("LibWeb/Bindings/InterfaceObject.h")
operation_callbacks = operation_callback_names(interface)
out.write(
f"""struct {interface.constructor_class} {{
public:
static void initialize(JS::Realm&, JS::NativeFunction&);
static JS::ThrowCompletionOr<GC::Ref<JS::Object>> construct(InterfaceConstructor&, JS::FunctionObject&);
private:
"""
)
if len(interface.constructors) > 1:
for overload_index, _ in enumerate(interface.constructors):
out.write(
f" static JS::ThrowCompletionOr<GC::Ref<JS::Object>> construct{overload_index}(InterfaceConstructor&, JS::FunctionObject&);\n"
)
for operations in overload_resolution.operation_overload_sets(interface, static=True).values():
operation = operations[0]
out.write(f" JS_DECLARE_NATIVE_FUNCTION({idl_identifier_cpp_name(operation)});\n")
if len(operations) > 1:
for overload_index, overloaded_operation in enumerate(operations):
out.write(
f" JS_DECLARE_NATIVE_FUNCTION({idl_identifier_cpp_name(overloaded_operation, suffix=overload_index)});\n"
)
for attribute in interface.static_attributes:
if "FIXME" in attribute.extended_attributes:
continue
out.write(f" JS_DECLARE_NATIVE_FUNCTION({attribute_getter_callback_name(attribute)});\n")
out.write(
"""\
};
"""
)
if interface_requires_custom_prototype(interface):
out.write(
f"""class {interface.prototype_class} : public JS::Object {{
JS_OBJECT({interface.prototype_class}, JS::Object);
GC_DECLARE_ALLOCATOR({interface.prototype_class});
public:
static void define_unforgeable_attributes(JS::Realm&, JS::Object&);
explicit {interface.prototype_class}(JS::Realm&);
virtual void initialize(JS::Realm&) override;
virtual ~{interface.prototype_class}() override;
"""
)
if "Global" in interface.extended_attributes:
out.write(" virtual JS::ThrowCompletionOr<bool> internal_set_prototype_of(JS::Object*) override;\n")
out.write(
"""
private:
"""
)
else:
out.write(
f"""struct {interface.prototype_class} {{
public:
static void initialize(JS::Realm&, JS::Object&);
static void define_unforgeable_attributes(JS::Realm&, JS::Object&);
private:
"""
)
for attribute in interface.regular_attributes:
if "FIXME" in attribute.extended_attributes:
continue
out.write(f" JS_DECLARE_NATIVE_FUNCTION({attribute_getter_callback_name(attribute)});\n")
if attribute_has_setter(attribute):
out.write(f" JS_DECLARE_NATIVE_FUNCTION({attribute_setter_callback_name(attribute)});\n")
for operations in overload_resolution.operation_overload_sets(interface).values():
operation = operations[0]
out.write(f" JS_DECLARE_NATIVE_FUNCTION({idl_identifier_cpp_name(operation)});\n")
if len(operations) > 1:
for overload_index, overloaded_operation in enumerate(operations):
out.write(
f" JS_DECLARE_NATIVE_FUNCTION({idl_identifier_cpp_name(overloaded_operation, suffix=overload_index)});\n"
)
if interface.stringifier is not None:
out.write(" JS_DECLARE_NATIVE_FUNCTION(to_string);\n")
if interface.indexed_property_getter is not None and interface.indexed_property_getter.name:
out.write(f" JS_DECLARE_NATIVE_FUNCTION({idl_identifier_cpp_name(interface.indexed_property_getter)});\n")
if interface.named_property_getter is not None and interface.named_property_getter.name:
out.write(f" JS_DECLARE_NATIVE_FUNCTION({idl_identifier_cpp_name(interface.named_property_getter)});\n")
if interface.named_property_setter is not None and interface.named_property_setter.name:
out.write(f" JS_DECLARE_NATIVE_FUNCTION({idl_identifier_cpp_name(interface.named_property_setter)});\n")
if interface.named_property_deleter is not None and interface.named_property_deleter.name:
out.write(f" JS_DECLARE_NATIVE_FUNCTION({idl_identifier_cpp_name(interface.named_property_deleter)});\n")
if interface.maplike is not None:
out.write(" JS_DECLARE_NATIVE_FUNCTION(get_size);\n")
out.write(" JS_DECLARE_NATIVE_FUNCTION(entries);\n")
out.write(" JS_DECLARE_NATIVE_FUNCTION(keys);\n")
out.write(" JS_DECLARE_NATIVE_FUNCTION(values);\n")
out.write(" JS_DECLARE_NATIVE_FUNCTION(for_each);\n")
out.write(" JS_DECLARE_NATIVE_FUNCTION(get);\n")
out.write(" JS_DECLARE_NATIVE_FUNCTION(has);\n")
if not interface.maplike.readonly:
out.write(" JS_DECLARE_NATIVE_FUNCTION(delete_);\n")
out.write(" JS_DECLARE_NATIVE_FUNCTION(clear);\n")
if interface.setlike is not None:
out.write(" JS_DECLARE_NATIVE_FUNCTION(get_size);\n")
out.write(" JS_DECLARE_NATIVE_FUNCTION(entries);\n")
out.write(" JS_DECLARE_NATIVE_FUNCTION(values);\n")
out.write(" JS_DECLARE_NATIVE_FUNCTION(for_each);\n")
out.write(" JS_DECLARE_NATIVE_FUNCTION(has);\n")
if not interface.setlike.readonly:
if "add" not in operation_callbacks:
out.write(" JS_DECLARE_NATIVE_FUNCTION(add);\n")
if "delete_" not in operation_callbacks:
out.write(" JS_DECLARE_NATIVE_FUNCTION(delete_);\n")
if "clear" not in operation_callbacks:
out.write(" JS_DECLARE_NATIVE_FUNCTION(clear);\n")
if interface.iterable is not None and interface.iterable.key_type is not None:
out.write(" JS_DECLARE_NATIVE_FUNCTION(entries);\n")
out.write(" JS_DECLARE_NATIVE_FUNCTION(for_each);\n")
out.write(" JS_DECLARE_NATIVE_FUNCTION(keys);\n")
out.write(" JS_DECLARE_NATIVE_FUNCTION(values);\n")
if interface.async_iterable is not None:
out.write(" JS_DECLARE_NATIVE_FUNCTION(values);\n")
out.write(
"""};
"""
)
if "Global" in interface.extended_attributes:
write_global_mixin_declaration(out, context, interface)
if interface_supports_named_properties(interface):
write_named_properties_object_declaration(out, includes, interface)
write_iterator_prototype_declaration(out, interface)
write_async_iterator_prototype_declaration(out, interface)

View file

@ -0,0 +1,279 @@
# Copyright (c) 2026-present, the Ladybird developers.
#
# SPDX-License-Identifier: BSD-2-Clause
from typing import Optional
from typing import TextIO
from Generators.libweb_bindings import attributes
from Generators.libweb_bindings import callback_interfaces
from Generators.libweb_bindings import constants
from Generators.libweb_bindings import constructors
from Generators.libweb_bindings import global_mixins
from Generators.libweb_bindings import interface_declaration
from Generators.libweb_bindings import iterables
from Generators.libweb_bindings import named_and_indexed_properties
from Generators.libweb_bindings import namespaces
from Generators.libweb_bindings import operations
from Generators.libweb_bindings.context import GenerationContext
from Generators.libweb_bindings.cpp_types import fully_qualified_name_for_interface
from Generators.libweb_bindings.cpp_types import implementation_header_for_interface
from Generators.libweb_bindings.includes import GeneratedIncludes
from Generators.libweb_bindings.interface_declaration import interface_requires_custom_prototype
from Generators.libweb_bindings.named_and_indexed_properties import interface_supports_named_properties
from Generators.libweb_bindings.overload_resolution import parameter_list_length
from Utils.webidl_parser import IDLType
from Utils.webidl_parser import Interface
def interface_needs_impl_from(interface: Interface) -> bool:
return (
bool(interface.regular_attributes)
or bool(interface.regular_operations)
or interface.stringifier is not None
or interface.indexed_property_getter is not None
or interface.named_property_getter is not None
or interface.named_property_setter is not None
or interface.named_property_deleter is not None
or interface.maplike is not None
or interface.setlike is not None
or interface.iterable is not None
or interface.async_iterable is not None
)
def write_impl_from(out: TextIO, interface: Interface) -> None:
if not interface_needs_impl_from(interface):
return
window_proxy_special_case = ""
if interface.name in ("EventTarget", "Window"):
window_proxy_special_case = """
if (auto window_proxy = js_value.as_if<HTML::WindowProxy>())
return window_proxy->window().ptr();
"""
out.write(
f"""[[maybe_unused]] static JS::ThrowCompletionOr<{fully_qualified_name_for_interface(interface)}*> impl_from(JS::VM& vm, JS::Value js_value)
{{
{window_proxy_special_case}
if (auto impl = js_value.as_if<{fully_qualified_name_for_interface(interface)}>())
return impl.ptr();
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "{interface.namespaced_name}");
}}
[[maybe_unused]] static JS::ThrowCompletionOr<{fully_qualified_name_for_interface(interface)}*> impl_from(JS::VM& vm)
{{
auto this_value = vm.this_value();
if (this_value.is_nullish())
this_value = &vm.current_realm()->global_object();
return impl_from(vm, this_value);
}}
"""
)
def write_declaration(
out: TextIO, includes: GeneratedIncludes, context: GenerationContext, interface: Optional[Interface]
) -> None:
if interface is None:
return
interface_declaration.write_declaration(out, includes, context, interface)
def write_implementation(
out: TextIO, includes: GeneratedIncludes, context: GenerationContext, interface: Optional[Interface]
) -> None:
if interface is None:
return
if interface.is_callback_interface:
callback_interfaces.write_callback_interface_implementation(out, context, includes, interface)
return
if interface.is_namespace:
namespaces.write_namespace_implementation(out, context, includes, interface)
return
includes.add("LibJS/Runtime/ValueInlines.h")
includes.add("LibWeb/Bindings/Intrinsics.h")
includes.add("LibWeb/WebIDL/Tracing.h")
includes.add_binding(interface.implemented_name)
if interface.parent_name:
parent_interface = context.interface(IDLType(interface.parent_name))
includes.add_binding(parent_interface.implemented_name if parent_interface else interface.parent_name)
includes.add(implementation_header_for_interface(interface))
if interface_needs_impl_from(interface):
includes.add("LibJS/Runtime/Error.h")
includes.add("LibWeb/Bindings/ExceptionOrUtils.h")
if interface.name in ("EventTarget", "Window") and interface_needs_impl_from(interface):
includes.add("LibWeb/HTML/Window.h")
includes.add("LibWeb/HTML/WindowProxy.h")
if interface.constructors:
includes.add("LibJS/Runtime/AbstractOperations.h")
includes.add("LibJS/Runtime/Realm.h")
includes.add("LibWeb/Bindings/ExceptionOrUtils.h")
parent_prototype = "realm.intrinsics().object_prototype()"
if interface.name == "DOMException":
# https://webidl.spec.whatwg.org/#es-DOMException-specialness
# Object.getPrototypeOf(DOMException.prototype) === Error.prototype
parent_prototype = "realm.intrinsics().error_prototype()"
if interface.parent_name:
parent_prototype = f'GC::Ref {{ ensure_web_prototype<{interface.parent_name}Prototype>(realm, "{interface.parent_name}"_fly_string) }}'
constructor_length = 0
if interface.constructors:
constructor_length = min(
parameter_list_length(constructor.parameters) for constructor in interface.constructors
)
out.write(f"""void {interface.constructor_class}::initialize(JS::Realm& realm, JS::NativeFunction& object)
{{
auto& vm = realm.vm();
[[maybe_unused]] u8 default_attributes = JS::Attribute::Enumerable;
{f'object.set_prototype(&ensure_web_constructor<{interface.parent_name}Prototype>(realm, "{interface.parent_name}"_fly_string));' if interface.parent_name else ""}
object.define_direct_property(vm.names.length, JS::Value({constructor_length}), JS::Attribute::Configurable);
object.define_direct_property(vm.names.name, JS::PrimitiveString::create(vm, "{interface.name}"_string), JS::Attribute::Configurable);
object.define_direct_property(vm.names.prototype, &ensure_web_prototype<{interface.prototype_class}>(realm, "{interface.namespaced_name}"_fly_string), 0);
""")
constants.define_the_constants(out, context, includes, interface)
attributes.define_the_static_attributes(out, includes, interface)
operations.define_the_static_operations(out, includes, interface)
out.write(
f"""}}
JS::ThrowCompletionOr<GC::Ref<JS::Object>> {interface.constructor_class}::construct([[maybe_unused]] InterfaceConstructor& constructor, [[maybe_unused]] JS::FunctionObject& new_target)
{{
WebIDL::log_trace(constructor.vm(), "{interface.constructor_class}::construct");
"""
)
if interface.constructors:
if len(interface.constructors) == 1:
constructors.write_constructor_steps(out, context, includes, interface, interface.constructors[0])
else:
constructors.write_constructor_overload_arbiter(out, context, includes, interface)
else:
out.write(
f' return constructor.vm().throw_completion<JS::TypeError>(JS::ErrorType::NotAConstructor, "{interface.name}");\n'
)
out.write("}\n\n")
if len(interface.constructors) > 1:
for overload_index, constructor in enumerate(interface.constructors):
constructors.write_constructor_function(out, context, includes, interface, constructor, overload_index)
if interface_requires_custom_prototype(interface):
out.write(
f"""GC_DEFINE_ALLOCATOR({interface.prototype_class});
{interface.prototype_class}::{interface.prototype_class}([[maybe_unused]] JS::Realm& realm)
: Object(ConstructWithPrototypeTag::Tag, {parent_prototype})
{{
}}
{interface.prototype_class}::~{interface.prototype_class}()
{{
}}
"""
)
if "Global" in interface.extended_attributes:
out.write(f"""JS::ThrowCompletionOr<bool> {interface.prototype_class}::internal_set_prototype_of(JS::Object* prototype)
{{
// 1. Return ? SetImmutablePrototype(O, V).
return set_immutable_prototype(prototype);
}}
""")
out.write(f"""
void {interface.prototype_class}::initialize(JS::Realm& realm)
{{
auto& object = *this;
""")
else:
out.write(f"""void {interface.prototype_class}::initialize(JS::Realm& realm, JS::Object& object)
{{
""")
out.write(
f""" [[maybe_unused]] auto& vm = realm.vm();
[[maybe_unused]] u8 default_attributes = JS::Attribute::Enumerable | JS::Attribute::Configurable | JS::Attribute::Writable;
object.set_prototype({parent_prototype});
"""
)
if interface_supports_named_properties(interface):
includes.add("LibWeb/Bindings/Intrinsics.h")
out.write(
f' object.set_prototype(&ensure_web_prototype<{interface.prototype_class}>(realm, "{interface.name}Properties"_fly_string));\n'
)
if "Global" in interface.extended_attributes:
out.write(
f' object.define_direct_property(vm.well_known_symbol_to_string_tag(), JS::PrimitiveString::create(vm, "{interface.namespaced_name}"_string), JS::Attribute::Configurable);\n'
)
if interface_requires_custom_prototype(interface):
out.write(" Base::initialize(realm);\n")
out.write("}\n\n")
write_impl_from(out, interface)
operations.write_static_operations(out, context, includes, interface)
attributes.write_static_attribute_getters(out, context, includes, interface)
named_and_indexed_properties.write_named_properties_object_implementation(out, includes, interface)
global_mixins.write_global_mixin_implementation(out, context, includes, interface)
return
attributes.define_the_regular_attributes(out, includes, interface)
operations.define_the_regular_operations(out, includes, interface)
operations.define_the_stringifier(out, includes, interface)
named_and_indexed_properties.define_the_indexed_property_getter(out, includes, interface)
iterables.define_the_pair_iterable_declaration(out, includes, interface)
iterables.define_the_async_iterable_declaration(out, interface)
iterables.define_the_maplike_declaration(out, includes, interface)
iterables.define_the_setlike_declaration(out, includes, interface)
named_and_indexed_properties.define_the_named_property_getter(out, context, interface)
named_and_indexed_properties.define_the_named_property_setter(out, context, interface)
named_and_indexed_properties.define_the_named_property_deleter(out, context, interface)
constants.define_the_constants(out, context, includes, interface)
operations.define_unscopable_members(out, includes, interface)
out.write(
f' object.define_direct_property(vm.well_known_symbol_to_string_tag(), JS::PrimitiveString::create(vm, "{interface.namespaced_name}"_string), JS::Attribute::Configurable);'
)
if interface_requires_custom_prototype(interface):
out.write(" Base::initialize(realm);\n")
out.write(f"""}}
void {interface.prototype_class}::define_unforgeable_attributes(JS::Realm& realm, [[maybe_unused]] JS::Object& object)
{{
[[maybe_unused]] auto& vm = realm.vm();
[[maybe_unused]] u8 default_attributes = JS::Attribute::Enumerable;
""")
attributes.define_the_unforgeable_attributes(out, includes, interface)
operations.define_the_regular_operations(out, includes, interface, unforgeable=True)
operations.define_the_stringifier(out, includes, interface, unforgeable=True)
out.write("}\n\n")
write_impl_from(out, interface)
operations.write_static_operations(out, context, includes, interface)
attributes.write_static_attribute_getters(out, context, includes, interface)
attributes.write_attribute_getters(out, context, includes, interface)
attributes.write_attribute_setters(out, context, includes, interface)
operations.write_regular_operations(out, context, includes, interface)
operations.write_stringifier(out, context, includes, interface)
named_and_indexed_properties.write_indexed_property_getter(out, context, includes, interface)
iterables.write_pair_iterable_declaration_functions(out, context, includes, interface)
iterables.write_iterator_prototype_implementation(out, includes, interface)
iterables.write_async_iterable_declaration_functions(out, context, includes, interface)
iterables.write_async_iterator_prototype_implementation(out, includes, interface)
iterables.write_maplike_declaration_functions(out, context, includes, interface)
iterables.write_setlike_declaration_functions(out, context, includes, interface)
named_and_indexed_properties.write_named_property_getter(out, context, includes, interface)
named_and_indexed_properties.write_named_property_setter(out, context, includes, interface)
named_and_indexed_properties.write_named_property_deleter(out, context, includes, interface)
named_and_indexed_properties.write_named_properties_object_implementation(out, includes, interface)
global_mixins.write_global_mixin_implementation(out, context, includes, interface)

View file

@ -1,27 +1,18 @@
#!/usr/bin/env python3
# Copyright (c) 2026-present, the Ladybird developers.
#
# SPDX-License-Identifier: BSD-2-Clause
import argparse
import sys
from dataclasses import dataclass
from dataclasses import field
from pathlib import Path
from typing import Dict
from typing import List
from typing import Optional
from typing import Set
from typing import TextIO
sys.path.append(str(Path(__file__).resolve().parent.parent))
from Generators.libweb_bindings.named_and_indexed_properties import interface_supports_named_properties
from Utils.utils import title_case_to_snake_case
from Utils.webidl_parser import Interface
from Utils.webidl_parser import Module
from Utils.webidl_parser import parse_module
ALL_WORKERS_EXPOSURE = {
"DedicatedWorker",
@ -68,37 +59,18 @@ class LegacyConstructor:
constructor_class: str
def title_case_to_snake_case(value: str) -> str:
parts = []
for index, character in enumerate(value):
if character.isupper() and index > 0:
previous_character = value[index - 1]
next_character = value[index + 1] if index + 1 < len(value) else ""
if previous_character.islower() or (previous_character.isupper() and next_character.islower()):
parts.append("_")
parts.append(character.lower())
return "".join(parts)
def collect_interface_sets(modules: List[Module]) -> InterfaceSets:
interface_sets = InterfaceSets()
for module in modules:
interface = module.interface
if interface is None:
continue
if not should_have_interface_object(interface):
continue
interface_sets.add_interface(interface)
def parse_arguments() -> argparse.Namespace:
argument_parser = argparse.ArgumentParser()
argument_parser.add_argument(
"-o",
"--output-path",
required=True,
type=Path,
help="Path to output generated files into",
)
argument_parser.add_argument("paths", nargs="+", type=Path, help="Paths of every IDL file that could be Exposed")
return argument_parser.parse_args()
def read_input_paths(paths: List[Path]) -> List[Path]:
if len(paths) == 1 and str(paths[0]).startswith("@"):
response_file_path = Path(str(paths[0])[1:])
return [Path(path) for path in response_file_path.read_text().splitlines() if path]
return paths
return interface_sets
def parse_exposure_set(interface_name: str, exposed_value: str) -> Set[str]:
@ -167,6 +139,9 @@ def can_use_shared_interface_prototype(interface: Interface) -> bool:
and not interface.has_special_member
and interface.named_property_getter is None
and interface.indexed_property_getter is None
and interface.named_property_setter is None
and interface.named_property_deleter is None
and interface.indexed_property_setter is None
)
@ -402,6 +377,23 @@ void Intrinsics::create_web_namespace<{interface.namespace_class}>(JS::Realm& re
def write_interface_creation(out: TextIO, interface: Interface) -> None:
if interface.is_callback_interface:
out.write(
f"""template<>
WEB_API void Intrinsics::create_web_prototype_and_constructor<{interface.prototype_class}>(JS::Realm& realm)
{{
static constexpr InterfaceObjectMetadata metadata {{
.name = "{interface.name}"sv,
.namespaced_name = "{interface.namespaced_name}"sv,
.initialize_constructor = &{interface.constructor_class}::initialize,
.initialize_prototype = &{interface.prototype_class}::initialize,
}};
create_web_prototype_and_constructor(realm, metadata);
}}
"""
)
return
if can_use_shared_interface_prototype(interface):
out.write(
f"""template<>
@ -470,14 +462,10 @@ WEB_API void Intrinsics::create_web_prototype_and_constructor<{interface.prototy
"""
)
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:
if interface_supports_named_properties(interface):
out.write(
f""" auto named_properties_object = realm.create<{named_properties_class}>(realm);
m_prototypes.set("{named_properties_class}"_fly_string, named_properties_object);
f""" auto named_properties_object = realm.create<{interface.name}Properties>(realm);
m_prototypes.set("{interface.name}Properties"_fly_string, named_properties_object);
"""
)
@ -504,10 +492,6 @@ WEB_API void Intrinsics::create_web_prototype_and_constructor<{interface.prototy
)
return
named_properties_class = ""
if "Global" in interface.extended_attributes and interface.supports_named_properties:
named_properties_class = f"{interface.name}Properties"
out.write(
f"""template<>
WEB_API void Intrinsics::create_web_prototype_and_constructor<{interface.prototype_class}>(JS::Realm& realm)
@ -517,10 +501,10 @@ WEB_API void Intrinsics::create_web_prototype_and_constructor<{interface.prototy
"""
)
if named_properties_class:
if interface_supports_named_properties(interface):
out.write(
f""" auto named_properties_object = realm.create<{named_properties_class}>(realm);
m_prototypes.set("{named_properties_class}"_fly_string, named_properties_object);
f""" auto named_properties_object = realm.create<{interface.name}Properties>(realm);
m_prototypes.set("{interface.name}Properties"_fly_string, named_properties_object);
"""
)
@ -676,127 +660,3 @@ def write_namespace_global_accessor(out: TextIO, interface: Interface) -> None:
f""" global.define_intrinsic_accessor("{interface.name}"_utf16_fly_string, attr, [](auto& realm) -> JS::Value {{ return &ensure_web_namespace<{interface.namespace_class}>(realm, "{interface.name}"_fly_string); }});
"""
)
def cpp_namespace_for_module_path(path: Path) -> str:
"""A path of Libraries/LibWeb/<namespace>/... should have a namespace of Web::<namespace>."""
parts = path.parts
return parts[parts.index("LibWeb") + 1]
def write_forward_header(out: TextIO, modules: List[Module]) -> None:
out.write(
"""#pragma once
"""
)
interface_names_by_namespace: Dict[str, Set[str]] = {}
for module in modules:
interface = module.interface
if interface is None or interface.is_namespace:
continue
namespace_name = cpp_namespace_for_module_path(interface.path)
if not namespace_name:
continue
interface_names_by_namespace.setdefault(namespace_name, set()).add(interface.implemented_name)
for namespace_name in sorted(interface_names_by_namespace):
out.write(f"namespace Web::{namespace_name} {{\n\n")
for class_name in sorted(interface_names_by_namespace[namespace_name]):
out.write(f"class {class_name};\n")
out.write(
"""
}
"""
)
dictionary_names = {dictionary.name for module in modules for dictionary in module.dictionaries}
out.write(
"""namespace Web::Bindings {
"""
)
for dictionary_name in sorted(dictionary_names):
out.write(f"struct {dictionary_name};\n")
out.write(
"""
}
"""
)
def write_generated_file(path: Path, writer, *args) -> None:
with path.open("w", encoding="utf-8", newline="\n") as output_file:
writer(output_file, *args)
def main() -> int:
arguments = parse_arguments()
output_directory = arguments.output_path
output_directory.mkdir(parents=True, exist_ok=True)
interface_sets = InterfaceSets()
modules: List[Module] = []
for path in read_input_paths(arguments.paths):
module = parse_module(path, path.read_text(encoding="utf-8"))
modules.append(module)
interface = module.interface
if interface is None:
continue
if not should_have_interface_object(interface):
continue
interface_sets.add_interface(interface)
write_generated_file(
output_directory / "IntrinsicDefinitions.h", write_intrinsic_definitions_header, interface_sets
)
write_generated_file(
output_directory / "IntrinsicDefinitions.cpp",
write_intrinsic_definitions_implementation,
interface_sets,
)
for class_name in ("Window", "DedicatedWorker", "SharedWorker"):
write_generated_file(
output_directory / f"{class_name}ExposedInterfaces.h",
write_exposed_interface_header,
class_name,
)
write_generated_file(
output_directory / "WindowExposedInterfaces.cpp",
write_exposed_interface_implementation,
"Window",
interface_sets.window_exposed,
)
write_generated_file(
output_directory / "DedicatedWorkerExposedInterfaces.cpp",
write_exposed_interface_implementation,
"DedicatedWorker",
interface_sets.dedicated_worker_exposed,
)
write_generated_file(
output_directory / "SharedWorkerExposedInterfaces.cpp",
write_exposed_interface_implementation,
"SharedWorker",
interface_sets.shared_worker_exposed,
)
write_generated_file(output_directory / "Forward.h", write_forward_header, modules)
return 0
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -0,0 +1,870 @@
# Copyright (c) 2026-present, the Ladybird developers.
#
# SPDX-License-Identifier: BSD-2-Clause
from typing import TextIO
from Generators.libweb_bindings.arguments import write_operation_parameter_conversions
from Generators.libweb_bindings.context import GenerationContext
from Generators.libweb_bindings.cpp_types import add_header_includes_for_idl_type
from Generators.libweb_bindings.cpp_types import fully_qualified_name_for_interface
from Generators.libweb_bindings.cpp_types import idl_identifier_cpp_name
from Generators.libweb_bindings.cpp_types import libweb_include_path
from Generators.libweb_bindings.includes import GeneratedIncludes
from Generators.libweb_bindings.overload_resolution import operation_callback_names
from Generators.libweb_bindings.overload_resolution import parameter_list_length
from Generators.libweb_bindings.to_idl_value import type_check_idl_value
from Generators.libweb_bindings.to_js_value import to_javascript_value
from Utils.utils import make_name_acceptable_cpp
from Utils.utils import title_case_to_snake_case
from Utils.webidl_parser import Interface
def iterator_implementation_header_for_interface(interface: Interface) -> str:
return libweb_include_path(interface.path.with_name(f"{interface.implemented_name}Iterator.h"))
def async_iterator_implementation_header_for_interface(interface: Interface) -> str:
return libweb_include_path(interface.path.with_name(f"{interface.implemented_name}AsyncIterator.h"))
def write_iterator_prototype_declaration(out: TextIO, interface: Interface) -> None:
if interface.iterable is None or interface.iterable.key_type is None:
return
out.write(
f"""class {interface.name}IteratorPrototype : public JS::Object {{
JS_OBJECT({interface.name}IteratorPrototype, JS::Object);
GC_DECLARE_ALLOCATOR({interface.name}IteratorPrototype);
public:
explicit {interface.name}IteratorPrototype(JS::Realm&);
virtual void initialize(JS::Realm&) override;
virtual ~{interface.name}IteratorPrototype() override;
private:
JS_DECLARE_NATIVE_FUNCTION(next);
}};
"""
)
def write_async_iterator_prototype_declaration(out: TextIO, interface: Interface) -> None:
if interface.async_iterable is None:
return
out.write(
f"""class {interface.name}AsyncIteratorPrototype : public JS::Object {{
JS_OBJECT({interface.name}AsyncIteratorPrototype, JS::Object);
GC_DECLARE_ALLOCATOR({interface.name}AsyncIteratorPrototype);
public:
explicit {interface.name}AsyncIteratorPrototype(JS::Realm&);
virtual void initialize(JS::Realm&) override;
virtual ~{interface.name}AsyncIteratorPrototype() override;
private:
JS_DECLARE_NATIVE_FUNCTION(next);
{"JS_DECLARE_NATIVE_FUNCTION(return_);" if "DefinesAsyncIteratorReturn" in interface.extended_attributes else ""}
}};
"""
)
def define_the_pair_iterable_declaration(
out: TextIO,
includes: GeneratedIncludes,
interface: Interface,
) -> None:
if interface.iterable is None or interface.iterable.key_type is None:
return
includes.add("LibJS/Runtime/ArrayPrototype.h")
out.write(
""" object.define_native_function(realm, vm.names.entries, entries, 0, default_attributes);
object.define_native_function(realm, vm.names.forEach, for_each, 1, default_attributes);
object.define_native_function(realm, vm.names.keys, keys, 0, default_attributes);
object.define_native_function(realm, vm.names.values, values, 0, default_attributes);
object.define_direct_property(vm.well_known_symbol_iterator(), object.get_without_side_effects(vm.names.entries), JS::Attribute::Configurable | JS::Attribute::Writable);
"""
)
def define_the_async_iterable_declaration(
out: TextIO,
interface: Interface,
) -> None:
if interface.async_iterable is None:
return
out.write(
f""" object.define_native_function(realm, vm.names.values, values, {parameter_list_length(interface.async_iterable.parameters)}, default_attributes);
object.define_direct_property(vm.well_known_symbol_async_iterator(), object.get_without_side_effects(vm.names.values), JS::Attribute::Configurable | JS::Attribute::Writable);
"""
)
def define_the_maplike_declaration(
out: TextIO,
includes: GeneratedIncludes,
interface: Interface,
) -> None:
if interface.maplike is None:
return
includes.add("LibJS/Runtime/Map.h")
out.write(
""" object.define_native_accessor(realm, vm.names.size, get_size, nullptr, JS::Attribute::Enumerable | JS::Attribute::Configurable);
object.define_native_function(realm, vm.names.entries, entries, 0, default_attributes);
object.define_direct_property(vm.well_known_symbol_iterator(), object.get_without_side_effects(vm.names.entries), JS::Attribute::Configurable | JS::Attribute::Writable);
object.define_native_function(realm, vm.names.keys, keys, 0, default_attributes);
object.define_native_function(realm, vm.names.values, values, 0, default_attributes);
object.define_native_function(realm, vm.names.forEach, for_each, 1, default_attributes);
object.define_native_function(realm, vm.names.get, get, 1, default_attributes);
object.define_native_function(realm, vm.names.has, has, 1, default_attributes);
"""
)
if not interface.maplike.readonly:
out.write(
""" object.define_native_function(realm, vm.names.delete_, delete_, 1, default_attributes);
object.define_native_function(realm, vm.names.clear, clear, 0, default_attributes);
"""
)
out.write("\n")
def define_the_setlike_declaration(
out: TextIO,
includes: GeneratedIncludes,
interface: Interface,
) -> None:
if interface.setlike is None:
return
includes.add("LibJS/Runtime/Set.h")
operation_callbacks = operation_callback_names(interface)
out.write(
""" object.define_native_accessor(realm, vm.names.size, get_size, nullptr, JS::Attribute::Enumerable | JS::Attribute::Configurable);
object.define_native_function(realm, vm.names.entries, entries, 0, default_attributes);
object.define_native_function(realm, vm.names.keys, values, 0, default_attributes);
object.define_native_function(realm, vm.names.values, values, 0, default_attributes);
object.define_direct_property(vm.well_known_symbol_iterator(), object.get_without_side_effects(vm.names.values), JS::Attribute::Configurable | JS::Attribute::Writable);
object.define_native_function(realm, vm.names.forEach, for_each, 1, default_attributes);
object.define_native_function(realm, vm.names.has, has, 1, default_attributes);
"""
)
if not interface.setlike.readonly:
if "add" not in operation_callbacks:
out.write(" object.define_native_function(realm, vm.names.add, add, 1, default_attributes);\n")
if "delete_" not in operation_callbacks:
out.write(" object.define_native_function(realm, vm.names.delete_, delete_, 1, default_attributes);\n")
if "clear" not in operation_callbacks:
out.write(" object.define_native_function(realm, vm.names.clear, clear, 0, default_attributes);\n")
out.write("\n")
# https://webidl.spec.whatwg.org/#js-iterable
def write_pair_iterable_declaration_functions(
out: TextIO,
context: GenerationContext,
includes: GeneratedIncludes,
interface: Interface,
) -> None:
if interface.iterable is None or interface.iterable.key_type is None:
return
includes.add("LibJS/Runtime/AbstractOperations.h")
includes.add("LibJS/Runtime/Error.h")
includes.add("LibJS/Runtime/ValueInlines.h")
includes.add("LibWeb/Bindings/ExceptionOrUtils.h")
includes.add(iterator_implementation_header_for_interface(interface))
add_header_includes_for_idl_type(interface.iterable.key_type, includes, context)
add_header_includes_for_idl_type(interface.iterable.value_type, includes, context)
out.write(f"""JS_DEFINE_NATIVE_FUNCTION({interface.prototype_class}::entries)
{{
WebIDL::log_trace(vm, "{interface.prototype_class}::entries");
// 1. Let jsValue be ? ToObject(this value).
// 2. If jsValue is a platform object, then perform a security check, passing jsValue, "%Symbol.iterator%", and "method".
// 3. If jsValue does not implement definition, then throw a TypeError.
auto* this_impl = TRY(impl_from(vm));
// 4. Return a newly created default iterator object for definition, with jsValue as its target, "key+value" as its kind, and index set to 0.
return TRY(throw_dom_exception_if_needed(vm, [&] {{ return {fully_qualified_name_for_interface(interface)}Iterator::create(*this_impl, JS::Object::PropertyKind::KeyAndValue); }}));
}}
JS_DEFINE_NATIVE_FUNCTION({interface.prototype_class}::keys)
{{
WebIDL::log_trace(vm, "{interface.prototype_class}::keys");
// 1. Let jsValue be ? ToObject(this value).
// 2. If jsValue is a platform object, then perform a security check, passing jsValue, "keys", and "method".
// 3. If jsValue does not implement definition, then throw a TypeError.
auto* this_impl = TRY(impl_from(vm));
// 4. Return a newly created default iterator object for definition, with jsValue as its target, "key" as its kind, and index set to 0.
return TRY(throw_dom_exception_if_needed(vm, [&] {{ return {fully_qualified_name_for_interface(interface)}Iterator::create(*this_impl, JS::Object::PropertyKind::Key); }}));
}}
JS_DEFINE_NATIVE_FUNCTION({interface.prototype_class}::values)
{{
WebIDL::log_trace(vm, "{interface.prototype_class}::values");
// 1. Let jsValue be ? ToObject(this value).
// 2. If jsValue is a platform object, then perform a security check, passing jsValue, "values", and "method".
// 3. If jsValue does not implement definition, then throw a TypeError.
auto* this_impl = TRY(impl_from(vm));
// 4. Return a newly created default iterator object for definition, with jsValue as its target, "value" as its kind, and index set to 0.
return TRY(throw_dom_exception_if_needed(vm, [&] {{ return {fully_qualified_name_for_interface(interface)}Iterator::create(*this_impl, JS::Object::PropertyKind::Value); }}));
}}
JS_DEFINE_NATIVE_FUNCTION({interface.prototype_class}::for_each)
{{
WebIDL::log_trace(vm, "{interface.prototype_class}::for_each");
auto* this_impl = TRY(impl_from(vm));
auto callback = vm.argument(0);
if (!callback.is_function())
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAFunction, callback);
auto this_value = vm.this_value();
TRY(this_impl->for_each([&](auto key, auto value) -> JS::ThrowCompletionOr<void> {{
JS::Value wrapped_key = {to_javascript_value(interface.iterable.key_type, "key", includes, context)};
JS::Value wrapped_value = {to_javascript_value(interface.iterable.value_type, "value", includes, context)};
TRY(JS::call(vm, callback.as_function(), vm.argument(1), wrapped_value, wrapped_key, this_value));
return {{}};
}}));
return JS::js_undefined();
}}
""")
def write_iterator_prototype_implementation(
out: TextIO,
includes: GeneratedIncludes,
interface: Interface,
) -> None:
if interface.iterable is None or interface.iterable.key_type is None:
return
includes.add("AK/TypeCasts.h")
includes.add("LibJS/Runtime/Error.h")
includes.add("LibJS/Runtime/IteratorPrototype.h")
includes.add("LibJS/Runtime/PrimitiveString.h")
includes.add("LibJS/Runtime/ValueInlines.h")
includes.add("LibWeb/Bindings/ExceptionOrUtils.h")
includes.add(iterator_implementation_header_for_interface(interface))
iterator_interface_name = f"{interface.name}Iterator"
out.write(f"""GC_DEFINE_ALLOCATOR({interface.name}IteratorPrototype);
{interface.name}IteratorPrototype::{interface.name}IteratorPrototype(JS::Realm& realm)
: Object(ConstructWithPrototypeTag::Tag, realm.intrinsics().iterator_prototype())
{{
}}
{interface.name}IteratorPrototype::~{interface.name}IteratorPrototype()
{{
}}
void {interface.name}IteratorPrototype::initialize(JS::Realm& realm)
{{
auto& vm = this->vm();
Base::initialize(realm);
define_native_function(realm, vm.names.next, next, 0, JS::Attribute::Writable | JS::Attribute::Enumerable | JS::Attribute::Configurable);
define_direct_property(vm.well_known_symbol_to_string_tag(), JS::PrimitiveString::create(vm, "{interface.name} Iterator"_string), JS::Attribute::Configurable);
}}
static JS::ThrowCompletionOr<{fully_qualified_name_for_interface(interface)}Iterator*> {make_name_acceptable_cpp(title_case_to_snake_case(iterator_interface_name))}_impl_from(JS::VM& vm)
{{
auto this_object = TRY(vm.this_value().to_object(vm));
if (!is<{fully_qualified_name_for_interface(interface)}Iterator>(*this_object))
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "{iterator_interface_name}");
return static_cast<{fully_qualified_name_for_interface(interface)}Iterator*>(this_object.ptr());
}}
JS_DEFINE_NATIVE_FUNCTION({interface.name}IteratorPrototype::next)
{{
WebIDL::log_trace(vm, "{interface.name}IteratorPrototype::next");
auto* impl = TRY({make_name_acceptable_cpp(title_case_to_snake_case(iterator_interface_name))}_impl_from(vm));
return TRY(throw_dom_exception_if_needed(vm, [&] {{ return impl->next(); }}));
}}
""")
def write_async_iterable_declaration_functions(
out: TextIO,
context: GenerationContext,
includes: GeneratedIncludes,
interface: Interface,
) -> None:
if interface.async_iterable is None:
return
if interface.async_iterable.key_type is not None:
raise RuntimeError(f"Unsupported pair async iterable declaration on '{interface.name}'")
includes.add("LibWeb/Bindings/ExceptionOrUtils.h")
includes.add(async_iterator_implementation_header_for_interface(interface))
add_header_includes_for_idl_type(interface.async_iterable.value_type, includes, context)
out.write(
f"""JS_DEFINE_NATIVE_FUNCTION({interface.prototype_class}::values)
{{
WebIDL::log_trace(vm, "{interface.prototype_class}::values");
auto& realm = *vm.current_realm();
auto* impl = TRY(impl_from(vm));
"""
)
write_operation_parameter_conversions(out, interface.async_iterable.parameters, includes, context)
arguments = ", ".join(idl_identifier_cpp_name(parameter) for parameter in interface.async_iterable.parameters)
if arguments:
arguments = f", {arguments}"
out.write(
f""" return TRY(throw_dom_exception_if_needed(vm, [&] {{ return {fully_qualified_name_for_interface(interface)}AsyncIterator::create(realm, JS::Object::PropertyKind::Value, *impl{arguments}); }}));
}}
"""
)
def write_async_iterator_prototype_implementation(
out: TextIO,
includes: GeneratedIncludes,
interface: Interface,
) -> None:
if interface.async_iterable is None:
return
includes.add("AK/StringView.h")
includes.add("LibJS/Runtime/AsyncIteratorPrototype.h")
includes.add("LibJS/Runtime/PrimitiveString.h")
includes.add("LibWeb/Bindings/ExceptionOrUtils.h")
includes.add("LibWeb/WebIDL/AsyncIterator.h")
includes.add(async_iterator_implementation_header_for_interface(interface))
out.write(
f"""GC_DEFINE_ALLOCATOR({interface.name}AsyncIteratorPrototype);
{interface.name}AsyncIteratorPrototype::{interface.name}AsyncIteratorPrototype(JS::Realm& realm)
: Object(ConstructWithPrototypeTag::Tag, realm.intrinsics().async_iterator_prototype())
{{
}}
{interface.name}AsyncIteratorPrototype::~{interface.name}AsyncIteratorPrototype()
{{
}}
void {interface.name}AsyncIteratorPrototype::initialize(JS::Realm& realm)
{{
auto& vm = this->vm();
Base::initialize(realm);
define_direct_property(vm.well_known_symbol_to_string_tag(), JS::PrimitiveString::create(vm, "{interface.name} AsyncIterator"_string), JS::Attribute::Configurable);
define_native_function(realm, vm.names.next, next, 0, JS::default_attributes);
{"define_native_function(realm, vm.names.return_, return_, 1, JS::default_attributes);" if "DefinesAsyncIteratorReturn" in interface.extended_attributes else ""}
}}
JS_DEFINE_NATIVE_FUNCTION({interface.name}AsyncIteratorPrototype::next)
{{
WebIDL::log_trace(vm, "{interface.name}AsyncIteratorPrototype::next");
auto& realm = *vm.current_realm();
return TRY(throw_dom_exception_if_needed(vm, [&] {{
return WebIDL::AsyncIterator::next<{fully_qualified_name_for_interface(interface)}AsyncIterator>(realm, "{interface.name}AsyncIterator"sv);
}}));
}}
"""
)
if "DefinesAsyncIteratorReturn" in interface.extended_attributes:
out.write(f"""
JS_DEFINE_NATIVE_FUNCTION({interface.name}AsyncIteratorPrototype::return_)
{{
WebIDL::log_trace(vm, "{interface.name}AsyncIteratorPrototype::return");
auto& realm = *vm.current_realm();
auto value = vm.argument(0);
return TRY(throw_dom_exception_if_needed(vm, [&] {{
return WebIDL::AsyncIterator::return_<{fully_qualified_name_for_interface(interface)}AsyncIterator>(realm, "{interface.name}AsyncIterator"sv, value);
}}));
}}
""")
def write_maplike_declaration_functions(
out: TextIO,
context: GenerationContext,
includes: GeneratedIncludes,
interface: Interface,
) -> None:
if interface.maplike is None:
return
includes.add("LibJS/Runtime/AbstractOperations.h")
includes.add("LibJS/Runtime/Error.h")
includes.add("LibJS/Runtime/Map.h")
includes.add("LibJS/Runtime/MapIterator.h")
includes.add("LibJS/Runtime/ValueInlines.h")
includes.add("LibWeb/Bindings/ExceptionOrUtils.h")
out.write(f"""// https://webidl.spec.whatwg.org/#js-map-size
JS_DEFINE_NATIVE_FUNCTION({interface.prototype_class}::get_size)
{{
WebIDL::log_trace(vm, "{interface.prototype_class}::get_size");
// 1. Let O be the this value, implementation-checked against A with identifier "size" and type "getter".
auto* this_impl = TRY(impl_from(vm));
// 2. Let map be the map entries of the IDL value that represents a reference to O.
GC::Ref<JS::Map> map = this_impl->map_entries();
// 3. Return maps size, converted to a JavaScript value.
return map->map_size();
}}
// https://webidl.spec.whatwg.org/#js-map-entries
JS_DEFINE_NATIVE_FUNCTION({interface.prototype_class}::entries)
{{
WebIDL::log_trace(vm, "{interface.prototype_class}::entries");
auto& realm = *vm.current_realm();
// 1. Let O be the this value, implementation-checked against A with identifier "entries" and type "method".
auto* this_impl = TRY(impl_from(vm));
// 2. Let map be the map entries of the IDL value that represents a reference to O.
GC::Ref<JS::Map> map = this_impl->map_entries();
// 3. Return the result of creating a map iterator from map with kind "key+value".
return JS::MapIterator::create(realm, *map, PropertyKind::KeyAndValue);
}}
// https://webidl.spec.whatwg.org/#js-map-keys
JS_DEFINE_NATIVE_FUNCTION({interface.prototype_class}::keys)
{{
WebIDL::log_trace(vm, "{interface.prototype_class}::keys");
auto& realm = *vm.current_realm();
// 1. Let O be the this value, implementation-checked against A with identifier "keys" and type "method".
auto* this_impl = TRY(impl_from(vm));
// 2. Let map be the map entries of the IDL value that represents a reference to O.
GC::Ref<JS::Map> map = this_impl->map_entries();
// 3. Return the result of creating a map iterator from map with kind "key".
return JS::MapIterator::create(realm, *map, PropertyKind::Key);
}}
// https://webidl.spec.whatwg.org/#js-map-values
JS_DEFINE_NATIVE_FUNCTION({interface.prototype_class}::values)
{{
WebIDL::log_trace(vm, "{interface.prototype_class}::values");
auto& realm = *vm.current_realm();
// 1. Let O be the this value, implementation-checked against A with identifier "values" and type "method".
auto* this_impl = TRY(impl_from(vm));
// 2. Let map be the map entries of the IDL value that represents a reference to O.
GC::Ref<JS::Map> map = this_impl->map_entries();
// 3. Return the result of creating a map iterator from map with kind "value".
return JS::MapIterator::create(realm, *map, PropertyKind::Value);
}}
// https://webidl.spec.whatwg.org/#js-map-forEach
JS_DEFINE_NATIVE_FUNCTION({interface.prototype_class}::for_each)
{{
WebIDL::log_trace(vm, "{interface.prototype_class}::for_each");
// 1. Let O be the this value, implementation-checked against A with identifier "forEach" and type "method".
auto* this_impl = TRY(impl_from(vm));
// 2. Let map be the map entries of the IDL value that represents a reference to O.
GC::Ref<JS::Map> map = this_impl->map_entries();
// 3. Let callbackFn be the first argument passed to the function, or undefined if not supplied.
auto callback = vm.argument(0);
// 4. If IsCallable(callbackFn) is false, throw a TypeError.
if (!callback.is_function())
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAFunction, callback);
// 5. Let thisArg be the second argument passed to the function, or undefined if not supplied.
auto this_arg = vm.argument(1);
// 6. For each key value of map:
for (auto& [key, value] : *map) {{
// 1. Let jsKey and jsValue be key and value converted to a JavaScript value.
// 2. Perform ? Call(callbackFn, thisArg, « jsValue, jsKey, O »).
TRY(JS::call(vm, callback.as_function(), this_arg, value, key, this_impl));
}}
// 7. Return undefined.
return JS::js_undefined();
}}
// https://webidl.spec.whatwg.org/#js-map-get
JS_DEFINE_NATIVE_FUNCTION({interface.prototype_class}::get)
{{
WebIDL::log_trace(vm, "{interface.prototype_class}::get");
// 1. Let O be the this value, implementation-checked against A with identifier "get" and type "method".
auto* this_impl = TRY(impl_from(vm));
// 2. Let map be the map entries of the IDL value that represents a reference to O.
GC::Ref<JS::Map> map = this_impl->map_entries();
// 3. Let keyType be the key type specified in the maplike declaration.
// 4. Let keyArg be the first argument passed to this function, or undefined if not supplied.
// 5. Let key be keyArg converted to an IDL value of type keyType.
auto key = vm.argument(0);
{type_check_idl_value(interface.maplike.key_type, "key", includes, context, interface.name)}
// FIXME: 6. If key is -0, set key to +0.
// 7. If map[key] exists, then return map[key], converted to a JavaScript value.
auto result = map->map_get(key);
return result.value_or(JS::js_undefined());
}}
// https://webidl.spec.whatwg.org/#js-map-has
JS_DEFINE_NATIVE_FUNCTION({interface.prototype_class}::has)
{{
WebIDL::log_trace(vm, "{interface.prototype_class}::has");
// 1. Let O be the this value, implementation-checked against A with identifier "has" and type "method".
auto* this_impl = TRY(impl_from(vm));
// 2. Let map be the map entries of the IDL value that represents a reference to O.
GC::Ref<JS::Map> map = this_impl->map_entries();
// 3. Let keyType be the key type specified in the maplike declaration.
// 4. Let keyArg be the first argument passed to this function, or undefined if not supplied.
// 5. Let key be keyArg converted to an IDL value of type keyType.
auto key = vm.argument(0);
{type_check_idl_value(interface.maplike.key_type, "key", includes, context, interface.name)}
// FIXME: 6. If key is -0, set key to +0.
// 7. If map[key] exists, then return true; otherwise return false.
return map->map_has(key);
}}
""")
# If A does not declare a member with identifier "set", and A was declared with a readwrite maplike declaration,
# then there must exist a set data property on As interface prototype object with the following characteristics:
if "set" not in operation_callback_names(interface) and not interface.maplike.readonly:
out.write(f"""// https://webidl.spec.whatwg.org/#js-map-set
JS_DEFINE_NATIVE_FUNCTION({interface.prototype_class}::set)
{{
WebIDL::log_trace(vm, "{interface.prototype_class}::set");
// 1. Let O be the this value, implementation-checked against A with identifier "set" and type "method".
auto* this_impl = TRY(impl_from(vm));
// 2. Let map be the map entries of the IDL value that represents a reference to O.
GC::Ref<JS::Map> map = this_impl->map_entries();
// 3. Let keyType be the key type specified in the maplike declaration, and valueType be the value type.
// 4. Let keyArg be the first argument passed to this function, or undefined if not supplied.
// 5. Let key be keyArg converted to an IDL value of type keyType.
auto key = vm.argument(0);
{type_check_idl_value(interface.maplike.key_type, "key", includes, context, interface.name)}
// FIXME: 6. If key is -0, set key to +0.
// 7. Let valueArg be the second argument passed to this function, or undefined if not supplied.
// 8. Let value be valueArg converted to an IDL value of type valueType.
auto value = vm.argument(1);
{type_check_idl_value(interface.maplike.value_type, "value", includes, context, interface.name)}
// 9. Set map[key] to value.
map->map_set(key, value);
this_impl->on_map_modified_from_js({{}});
// 10. Return O.
return this_impl;
}}
""")
# If A does not declare a member with identifier "delete", and A was declared with a readwrite maplike declaration,
# then there must exist a delete data property on As interface prototype object with the following characteristics:
if "delete_" not in operation_callback_names(interface) and not interface.maplike.readonly:
out.write(f"""// https://webidl.spec.whatwg.org/#js-map-delete
JS_DEFINE_NATIVE_FUNCTION({interface.prototype_class}::delete_)
{{
WebIDL::log_trace(vm, "{interface.prototype_class}::delete_");
// 1. Let O be the this value, implementation-checked against A with identifier "delete" and type "method".
auto* this_impl = TRY(impl_from(vm));
// 2. Let map be the map entries of the IDL value that represents a reference to O.
GC::Ref<JS::Map> map = this_impl->map_entries();
// 3. Let keyType be the key type specified in the maplike declaration.
// 4. Let keyArg be the first argument passed to this function, or undefined if not supplied.
// 5. Let key be keyArg converted to an IDL value of type keyType.
auto key = vm.argument(0);
{type_check_idl_value(interface.maplike.key_type, "key", includes, context, interface.name)}
// FIXME: 6. If key is -0, set key to +0.
// 7. Let retVal be true if map[key] exists, or else false.
// 8. Remove map[key].
auto result = map->map_remove(key);
this_impl->on_map_modified_from_js({{}});
// 9. Return retVal.
return result;
}}
""")
# If A does not declare a member with identifier "clear", and A was declared with a readwrite maplike declaration,
# then there must exist a clear data property on As interface prototype object with the following characteristics:
if "clear" not in operation_callback_names(interface) and not interface.maplike.readonly:
out.write(f"""// https://webidl.spec.whatwg.org/#js-map-delete
JS_DEFINE_NATIVE_FUNCTION({interface.prototype_class}::clear)
{{
WebIDL::log_trace(vm, "{interface.prototype_class}::clear");
// 1. Let O be the this value, implementation-checked against A with identifier "delete" and type "method".
auto* this_impl = TRY(impl_from(vm));
// 2. Let map be the map entries of the IDL value that represents a reference to O.
GC::Ref<JS::Map> map = this_impl->map_entries();
// 3. Clear map.
// NOTE: The map is preserved because there may be existing iterators, currently suspended, iterating over it.
map->map_clear();
this_impl->on_map_modified_from_js({{}});
// 4. Return undefined.
return JS::js_undefined();
}}
""")
def write_setlike_declaration_functions(
out: TextIO,
context: GenerationContext,
includes: GeneratedIncludes,
interface: Interface,
) -> None:
if interface.setlike is None:
return
includes.add("LibJS/Runtime/AbstractOperations.h")
includes.add("LibJS/Runtime/Error.h")
includes.add("LibJS/Runtime/Set.h")
includes.add("LibJS/Runtime/SetIterator.h")
includes.add("LibJS/Runtime/ValueInlines.h")
includes.add("LibWeb/Bindings/ExceptionOrUtils.h")
out.write(f"""// https://webidl.spec.whatwg.org/#js-set-size
JS_DEFINE_NATIVE_FUNCTION({interface.prototype_class}::get_size)
{{
WebIDL::log_trace(vm, "{interface.prototype_class}::size");
// 1. Let O be the this value, implementation-checked against A with identifier "size" and type "getter".
auto* this_impl = TRY(impl_from(vm));
// 2. Let set be the set entries of the IDL value that represents a reference to O.
GC::Ref<JS::Set> set = this_impl->set_entries();
// 3. Return sets size, converted to a JavaScript value.
return set->set_size();
}}
// https://webidl.spec.whatwg.org/#js-set-entries
JS_DEFINE_NATIVE_FUNCTION({interface.prototype_class}::entries)
{{
WebIDL::log_trace(vm, "{interface.prototype_class}::values");
auto& realm = *vm.current_realm();
// 1. Let O be the this value, implementation-checked against A with identifier "entries" and type "method".
auto* this_impl = TRY(impl_from(vm));
// 2. Let set be the set entries of the IDL value that represents a reference to O.
GC::Ref<JS::Set> set = this_impl->set_entries();
// 3. Return the result of creating a set iterator from set with kind "key+value".
return JS::SetIterator::create(realm, *set, PropertyKind::KeyAndValue);
}}
// https://webidl.spec.whatwg.org/#js-set-values
JS_DEFINE_NATIVE_FUNCTION({interface.prototype_class}::values)
{{
WebIDL::log_trace(vm, "{interface.prototype_class}::values");
auto& realm = *vm.current_realm();
// 1. Let O be the this value, implementation-checked against A with identifier "values" and type "method".
auto* this_impl = TRY(impl_from(vm));
// 2. Let set be the set entries of the IDL value that represents a reference to O.
GC::Ref<JS::Set> set = this_impl->set_entries();
// 3. Return the result of creating a set iterator from set with kind "value".
return JS::SetIterator::create(realm, *set, PropertyKind::Value);
}}
// https://webidl.spec.whatwg.org/#js-set-forEach
JS_DEFINE_NATIVE_FUNCTION({interface.prototype_class}::for_each)
{{
WebIDL::log_trace(vm, "{interface.prototype_class}::for_each");
// 1. Let O be the this value, implementation-checked against A with identifier "forEach" and type "method".
auto* this_impl = TRY(impl_from(vm));
// 2. Let set be the set entries of the IDL value that represents a reference to O.
GC::Ref<JS::Set> set = this_impl->set_entries();
// 3. Let callbackFn be the first argument passed to the function, or undefined if not supplied.
// 4. If IsCallable(callbackFn) is false, throw a TypeError.
auto callback = vm.argument(0);
if (!callback.is_function())
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAFunction, callback);
// 5. Let thisArg be the second argument passed to the function, or undefined if not supplied.
auto this_arg = vm.argument(1);
// 6. For each value of set:
for (auto& entry : *set) {{
// 1. Let jsValue be value converted to a JavaScript value.
auto value = entry.key;
// 2. Perform ? Call(callbackFn, thisArg, « jsValue, jsValue, O»).
TRY(JS::call(vm, callback.as_function(), this_arg, value, value, this_impl));
}}
// 7. Return undefined.
return JS::js_undefined();
}}
// https://webidl.spec.whatwg.org/#js-set-has
JS_DEFINE_NATIVE_FUNCTION({interface.prototype_class}::has)
{{
WebIDL::log_trace(vm, "{interface.prototype_class}::has");
// 1. Let O be the this value, implementation-checked against A with identifier "has" and type "method".
auto* this_impl = TRY(impl_from(vm));
// 2. Let set be the set entries of the IDL value that represents a reference to O.
GC::Ref<JS::Set> set = this_impl->set_entries();
// 3. Let valueType be the value type specified in the setlike declaration.
// 4. Let valueArg be the first argument passed to this function, or undefined if not supplied.
// 5. Let value be valueArg converted to an IDL value of type valueType.
// FIXME: 6. If value is -0, set value to +0.
auto value = vm.argument(0);
{type_check_idl_value(interface.setlike.value_type, "value", includes, context, interface.name)}
// 7. If set contains value, then return true, otherwise return false.
return set->set_has(value);
}}
""")
# If A does not declare a member with identifier "add", and A was declared with a readwrite setlike declaration,
# then there must exist an add data property on As interface prototype object with the following characteristics:
if "add" not in operation_callback_names(interface) and not interface.setlike.readonly:
out.write(f"""// https://webidl.spec.whatwg.org/#js-set-add
JS_DEFINE_NATIVE_FUNCTION({interface.prototype_class}::add)
{{
WebIDL::log_trace(vm, "{interface.prototype_class}::add");
// 1. Let O be the this value, implementation-checked against A with identifier "add" and type "method".
auto* this_impl = TRY(impl_from(vm));
// 2. Let set be the set entries of the IDL value that represents a reference to O.
GC::Ref<JS::Set> set = this_impl->set_entries();
// 3. Let valueType be the value type specified in the setlike declaration.
// 4. Let valueArg be the first argument passed to this function, or undefined if not supplied.
// 5. Let value be valueArg converted to an IDL value of type valueType.
// FIXME: 6. If value is -0, set value to +0.
auto value = vm.argument(0);
{type_check_idl_value(interface.setlike.value_type, "value", includes, context, interface.name)}
// 6. Append value to set.
set->set_add(value);
this_impl->on_set_modified_from_js({{}});
return this_impl;
}}
""")
# If A does not declare a member with identifier "delete", and A was declared with a readwrite setlike declaration,
# then there must exist a delete data property on As interface prototype object with the following characteristics:
if "delete_" not in operation_callback_names(interface) and not interface.setlike.readonly:
out.write(f"""// https://webidl.spec.whatwg.org/#js-set-delete
JS_DEFINE_NATIVE_FUNCTION({interface.prototype_class}::delete_)
{{
WebIDL::log_trace(vm, "{interface.prototype_class}::delete_");
// 1. Let O be the this value, implementation-checked against A with identifier "delete" and type "method".
auto* this_impl = TRY(impl_from(vm));
// 2. Let set be Os set entries.
GC::Ref<JS::Set> set = this_impl->set_entries();
// 3. Let valueType be the value type specified in the setlike declaration.
// 4. Let valueArg be the first argument passed to this function, or undefined if not supplied.
// 5. Let value be valueArg converted to an IDL value of type valueType.
// 6. FIXME: If value is -0, set value to +0.
auto value = vm.argument(0);
{type_check_idl_value(interface.setlike.value_type, "value", includes, context, interface.name)}
// 7. Let retVal be true if set contains value, or else false.
// 8. Remove value from set.
auto result = set->set_remove(value);
this_impl->on_set_modified_from_js({{}});
// 9. Return retVal.
return result;
}}
""")
# If A does not declare a member with identifier "clear", and A was declared with a readwrite setlike declaration,
# then there must exist a clear data property on As interface prototype object with the following characteristics:
if "clear" not in operation_callback_names(interface) and not interface.setlike.readonly:
out.write(f"""// https://webidl.spec.whatwg.org/#js-set-clear
JS_DEFINE_NATIVE_FUNCTION({interface.prototype_class}::clear)
{{
WebIDL::log_trace(vm, "{interface.prototype_class}::clear");
// 1. Let O be the this value, implementation-checked against A with identifier "clear" and type "method".
auto* this_impl = TRY(impl_from(vm));
// 2. Let set be the set entries of the IDL value that represents a reference to O.
GC::Ref<JS::Set> set = this_impl->set_entries();
// 3. Empty set.
// NOTE: Note: The set is preserved because there may be existing iterators, currently suspended, iterating over it.
set->set_clear();
this_impl->on_set_modified_from_js({{}});
// 4. Return undefined.
return JS::js_undefined();
}}
""")

View file

@ -0,0 +1,459 @@
# Copyright (c) 2026-present, the Ladybird developers.
#
# SPDX-License-Identifier: BSD-2-Clause
from typing import TextIO
from Generators.libweb_bindings.arguments import write_operation_parameter_conversions
from Generators.libweb_bindings.context import GenerationContext
from Generators.libweb_bindings.cpp_types import fully_qualified_name_for_interface
from Generators.libweb_bindings.cpp_types import idl_identifier_cpp_name
from Generators.libweb_bindings.cpp_types import idl_implementation_cpp_name
from Generators.libweb_bindings.cpp_types import implementation_header_for_interface
from Generators.libweb_bindings.extended_attributes import wrap_with_ce_reactions
from Generators.libweb_bindings.includes import GeneratedIncludes
from Generators.libweb_bindings.to_js_value import to_javascript_value
from Utils.webidl_parser import Interface
from Utils.webidl_parser import SpecialOperation
def interface_supports_named_properties(interface: Interface) -> bool:
return interface.named_property_getter is not None and "Global" in interface.extended_attributes
def write_named_properties_object_declaration(out: TextIO, includes: GeneratedIncludes, interface: Interface) -> None:
includes.add("AK/Optional.h")
includes.add("LibGC/Ptr.h")
includes.add("LibJS/Runtime/Object.h")
includes.add("LibJS/Runtime/PropertyDescriptor.h")
includes.add("LibJS/Runtime/PropertyKey.h")
out.write(
f"""class {interface.name}Properties : public JS::Object {{
JS_OBJECT({interface.name}Properties, JS::Object);
GC_DECLARE_ALLOCATOR({interface.name}Properties);
public:
explicit {interface.name}Properties(JS::Realm&);
virtual void initialize(JS::Realm&) override;
virtual ~{interface.name}Properties() override;
JS::Realm& realm() const {{ return m_realm; }}
private:
virtual JS::ThrowCompletionOr<Optional<JS::PropertyDescriptor>> internal_get_own_property(JS::PropertyKey const&) const override;
virtual JS::ThrowCompletionOr<bool> internal_define_own_property(JS::PropertyKey const&, JS::PropertyDescriptor&, Optional<JS::PropertyDescriptor>* precomputed_get_own_property = nullptr) override;
virtual JS::ThrowCompletionOr<bool> internal_delete(JS::PropertyKey const&) override;
virtual JS::ThrowCompletionOr<bool> internal_set_prototype_of(JS::Object* prototype) override;
virtual JS::ThrowCompletionOr<bool> internal_prevent_extensions() override;
virtual bool eligible_for_own_property_enumeration_fast_path() const override final {{ return false; }}
virtual void visit_edges(Visitor&) override;
GC::Ref<JS::Realm> m_realm;
}};
"""
)
def write_named_properties_object_implementation(
out: TextIO,
includes: GeneratedIncludes,
interface: Interface,
) -> None:
if not interface_supports_named_properties(interface):
return
includes.add("AK/TypeCasts.h")
includes.add("LibJS/Runtime/PrimitiveString.h")
includes.add("LibJS/Runtime/PropertyDescriptor.h")
includes.add("LibJS/Runtime/PropertyKey.h")
includes.add("LibWeb/Bindings/Intrinsics.h")
includes.add(implementation_header_for_interface(interface))
parent_prototype = "realm.intrinsics().object_prototype()"
if interface.parent_name:
parent_prototype = (
f'&ensure_web_prototype<{interface.parent_name}Prototype>(realm, "{interface.parent_name}"_fly_string)'
)
out.write(
f"""GC_DEFINE_ALLOCATOR({interface.name}Properties);
{interface.name}Properties::{interface.name}Properties(JS::Realm& realm)
: JS::Object(realm, nullptr, MayInterfereWithIndexedPropertyAccess::Yes)
, m_realm(realm)
{{
}}
{interface.name}Properties::~{interface.name}Properties()
{{
}}
void {interface.name}Properties::initialize(JS::Realm& realm)
{{
Base::initialize(realm);
auto& vm = realm.vm();
// The class string of a named properties object is the concatenation of the interface's identifier and the string "Properties".
define_direct_property(vm.well_known_symbol_to_string_tag(), JS::PrimitiveString::create(vm, "{interface.name}Properties"_string), JS::Attribute::Configurable);
// 1. Let proto be null.
// 2. If interface is declared to inherit from another interface, then set proto to the interface prototype object in realm for the inherited interface.
// 3. Otherwise, set proto to realm.[[Intrinsics]].[[%Object.prototype%]].
// 10. Set obj.[[Prototype]] to proto.
set_prototype({parent_prototype});
}}
// https://webidl.spec.whatwg.org/#named-properties-object-getownproperty
JS::ThrowCompletionOr<Optional<JS::PropertyDescriptor>> {interface.name}Properties::internal_get_own_property(JS::PropertyKey const& property_name) const
{{
auto& realm = this->realm();
// 1. Let A be the interface for the named properties object O.
using A = {fully_qualified_name_for_interface(interface)};
// 2. Let object be O.[[Realm]]'s global object.
// 3. Assert: object implements A.
auto& object = as<A>(realm.global_object());
// 4. If the result of running the named property visibility algorithm with property name P and object object is true, then:
if (TRY(object.is_named_property_exposed_on_object(property_name))) {{
auto property_name_string = property_name.to_string().to_utf8_but_should_be_ported_to_utf16();
// 1. Let operation be the operation used to declare the named property getter.
// 2. Let value be an uninitialized variable.
// 3. If operation was defined without an identifier, then set value to the result of performing the steps listed in the interface description to determine the value of a named property with P as the name.
// 4. Otherwise, operation was defined with an identifier. Set value to the result of performing the method steps of operation with « P » as the only argument value.
auto value = object.named_item_value(property_name_string);
// 5. Let desc be a newly created Property Descriptor with no fields.
JS::PropertyDescriptor descriptor;
// 6. Set desc.[[Value]] to the result of converting value to an ECMAScript value.
descriptor.value = value;
// 7. If A implements an interface with the [LegacyUnenumerableNamedProperties] extended attribute, then set desc.[[Enumerable]] to false, otherwise set it to true.
descriptor.enumerable = {"false" if "LegacyUnenumerableNamedProperties" in interface.extended_attributes else "true"};
// 8. Set desc.[[Writable]] to true and desc.[[Configurable]] to true.
descriptor.writable = true;
descriptor.configurable = true;
// 9. Return desc.
return descriptor;
}}
// 5. Return OrdinaryGetOwnProperty(O, P).
return JS::Object::internal_get_own_property(property_name);
}}
// https://webidl.spec.whatwg.org/#named-properties-object-defineownproperty
JS::ThrowCompletionOr<bool> {interface.name}Properties::internal_define_own_property(JS::PropertyKey const&, JS::PropertyDescriptor&, Optional<JS::PropertyDescriptor>*)
{{
// 1. Return false.
return false;
}}
// https://webidl.spec.whatwg.org/#named-properties-object-delete
JS::ThrowCompletionOr<bool> {interface.name}Properties::internal_delete(JS::PropertyKey const&)
{{
// 1. Return false.
return false;
}}
// https://webidl.spec.whatwg.org/#named-properties-object-setprototypeof
JS::ThrowCompletionOr<bool> {interface.name}Properties::internal_set_prototype_of(JS::Object* prototype)
{{
// 1. If Os associated realms is global prototype chain mutable is true, return ? OrdinarySetPrototypeOf(O, V).
// NB: This is only ever true for ShadowRealms.
// 2. Return ? SetImmutablePrototype(O, V).
return set_immutable_prototype(prototype);
}}
// https://webidl.spec.whatwg.org/#named-properties-object-preventextensions
JS::ThrowCompletionOr<bool> {interface.name}Properties::internal_prevent_extensions()
{{
// 1. Return false.
// Note: this keeps named properties object extensible by making [[PreventExtensions]] fail.
return false;
}}
void {interface.name}Properties::visit_edges(Visitor& visitor)
{{
Base::visit_edges(visitor);
visitor.visit(m_realm);
}}
"""
)
def define_the_indexed_property_getter(
out: TextIO,
includes: GeneratedIncludes,
interface: Interface,
) -> None:
if interface.indexed_property_getter is None:
return
operation = interface.indexed_property_getter
includes.add("LibJS/Runtime/ArrayPrototype.h")
if operation.name:
out.write(
f""" object.define_native_function(realm, "{operation.name}"_utf16_fly_string, {idl_identifier_cpp_name(operation)}, {len(operation.parameters)}, default_attributes);
"""
)
if interface.named_property_getter is not None and interface.named_property_getter.name:
operation = interface.named_property_getter
out.write(
f""" object.define_native_function(realm, "{operation.name}"_utf16_fly_string, {idl_identifier_cpp_name(operation)}, {len(operation.parameters)}, default_attributes);
"""
)
out.write(
""" object.define_direct_property(vm.well_known_symbol_iterator(), realm.intrinsics().array_prototype()->get_without_side_effects(vm.names.values), JS::Attribute::Configurable | JS::Attribute::Writable);
"""
)
if interface.iterable is not None and interface.iterable.key_type is None:
out.write(
""" object.define_direct_property(vm.names.entries, realm.intrinsics().array_prototype()->get_without_side_effects(vm.names.entries), default_attributes);
object.define_direct_property(vm.names.keys, realm.intrinsics().array_prototype()->get_without_side_effects(vm.names.keys), default_attributes);
object.define_direct_property(vm.names.values, realm.intrinsics().array_prototype()->get_without_side_effects(vm.names.values), default_attributes);
object.define_direct_property(vm.names.forEach, realm.intrinsics().array_prototype()->get_without_side_effects(vm.names.forEach), default_attributes);
"""
)
def define_the_named_property_getter(out: TextIO, context: GenerationContext, interface: Interface) -> None:
if interface.named_property_getter is None:
return
if interface.indexed_property_getter is not None:
return
operation = interface.named_property_getter
if not operation.name:
return
out.write(
f""" object.define_native_function(realm, "{operation.name}"_utf16_fly_string, {idl_identifier_cpp_name(operation)}, {len(operation.parameters)}, default_attributes);
"""
)
def define_the_named_property_setter(out: TextIO, context: GenerationContext, interface: Interface) -> None:
if interface.named_property_setter is None:
return
operation = interface.named_property_setter
if not operation.name:
return
out.write(
f""" object.define_native_function(realm, "{operation.name}"_utf16_fly_string, {idl_identifier_cpp_name(operation)}, {len(operation.parameters)}, default_attributes);
"""
)
def define_the_named_property_deleter(out: TextIO, context: GenerationContext, interface: Interface) -> None:
if interface.named_property_deleter is None:
return
operation = interface.named_property_deleter
if not operation.name:
return
out.write(
f""" object.define_native_function(realm, "{operation.name}"_utf16_fly_string, {idl_identifier_cpp_name(operation)}, {len(operation.parameters)}, default_attributes);
"""
)
def write_indexed_property_getter(
out: TextIO,
context: GenerationContext,
includes: GeneratedIncludes,
interface: Interface,
) -> None:
if interface.indexed_property_getter is None:
return
operation = interface.indexed_property_getter
if not operation.name:
return
if len(operation.parameters) != 1:
raise RuntimeError(f"Unsupported indexed property getter arity on '{interface.name}'")
parameter = operation.parameters[0]
parameter_name = idl_identifier_cpp_name(parameter)
out.write(
f"""JS_DEFINE_NATIVE_FUNCTION({interface.prototype_class}::{idl_identifier_cpp_name(operation)})
{{
WebIDL::log_trace(vm, "{interface.prototype_class}::{idl_identifier_cpp_name(operation)}");
[[maybe_unused]] auto* idl_object = TRY(impl_from(vm));
if (vm.argument_count() < 1)
return vm.throw_completion<JS::TypeError>(JS::ErrorType::BadArgCountOne, "{operation.name}");
"""
)
write_operation_parameter_conversions(out, operation.parameters, includes, context)
out.write(
f"""
auto R = TRY(throw_dom_exception_if_needed(vm, [&] {{ return idl_object->{idl_implementation_cpp_name(operation)}({parameter_name}); }}));
return {to_javascript_value(operation.return_type, "R", includes, context)};
}}
"""
)
def write_named_property_getter(
out: TextIO,
context: GenerationContext,
includes: GeneratedIncludes,
interface: Interface,
) -> None:
if interface.named_property_getter is None:
return
operation = interface.named_property_getter
if not operation.name:
return
if len(operation.parameters) != 1:
raise RuntimeError(f"Unsupported named property getter arity on '{interface.name}'")
parameter = operation.parameters[0]
parameter_name = idl_identifier_cpp_name(parameter)
out.write(
f"""JS_DEFINE_NATIVE_FUNCTION({interface.prototype_class}::{idl_identifier_cpp_name(operation)})
{{
WebIDL::log_trace(vm, "{interface.prototype_class}::{idl_identifier_cpp_name(operation)}");
[[maybe_unused]] auto* idl_object = TRY(impl_from(vm));
if (vm.argument_count() < 1)
return vm.throw_completion<JS::TypeError>(JS::ErrorType::BadArgCountOne, "{operation.name}");
"""
)
write_operation_parameter_conversions(out, operation.parameters, includes, context)
out.write(
f"""
auto R = TRY(throw_dom_exception_if_needed(vm, [&] {{ return idl_object->{idl_implementation_cpp_name(operation)}({parameter_name}); }}));
return {to_javascript_value(operation.return_type, "R", includes, context)};
}}
"""
)
def write_named_property_setter(
out: TextIO,
context: GenerationContext,
includes: GeneratedIncludes,
interface: Interface,
) -> None:
if interface.named_property_setter is None:
return
operation = interface.named_property_setter
if not operation.name:
return
write_named_property_operation(out, context, includes, interface, operation)
def write_named_property_deleter(
out: TextIO,
context: GenerationContext,
includes: GeneratedIncludes,
interface: Interface,
) -> None:
if interface.named_property_deleter is None:
return
operation = interface.named_property_deleter
if not operation.name:
return
write_named_property_operation(out, context, includes, interface, operation)
def write_named_property_operation(
out: TextIO,
context: GenerationContext,
includes: GeneratedIncludes,
interface: Interface,
operation: SpecialOperation,
) -> None:
if not operation.parameters:
raise RuntimeError(f"Unsupported named property operation arity on '{interface.name}'")
return_value = to_javascript_value(operation.return_type, "R", includes, context)
arguments = ", ".join(idl_identifier_cpp_name(parameter) for parameter in operation.parameters)
out.write(
f"""JS_DEFINE_NATIVE_FUNCTION({interface.prototype_class}::{idl_identifier_cpp_name(operation)})
{{
WebIDL::log_trace(vm, "{interface.prototype_class}::{idl_identifier_cpp_name(operation)}");
[[maybe_unused]] auto* idl_object = TRY(impl_from(vm));
"""
)
if len(operation.parameters) == 1:
out.write(
f""" if (vm.argument_count() < 1)
return vm.throw_completion<JS::TypeError>(JS::ErrorType::BadArgCountOne, "{operation.name}");
"""
)
else:
out.write(
f""" if (vm.argument_count() < {len(operation.parameters)})
return vm.throw_completion<JS::TypeError>(JS::ErrorType::BadArgCountMany, "{operation.name}", "{len(operation.parameters)}");
"""
)
write_operation_parameter_conversions(out, operation.parameters, includes, context)
operation_returns_undefined = operation.return_type.name == "undefined"
if "CEReactions" in operation.extended_attributes:
ce_reactions_steps = wrap_with_ce_reactions(includes, "original_steps()")
out.write(
f""" auto original_steps = [&] {{
return throw_dom_exception_if_needed(vm, [&] {{ return idl_object->{idl_implementation_cpp_name(operation)}({arguments}); }});
}};
[[maybe_unused]] auto R = TRY({ce_reactions_steps});
return {return_value};
}}
"""
)
return
return_statement = "return JS::js_undefined();"
if not operation_returns_undefined:
return_statement = f"return {return_value};"
out.write(
f""" [[maybe_unused]] auto R = TRY(throw_dom_exception_if_needed(vm, [&] {{ return idl_object->{idl_implementation_cpp_name(operation)}({arguments}); }}));
{return_statement}
}}
"""
)

View file

@ -0,0 +1,150 @@
# Copyright (c) 2026-present, the Ladybird developers.
#
# SPDX-License-Identifier: BSD-2-Clause
from typing import TextIO
from Generators.libweb_bindings import overload_resolution
from Generators.libweb_bindings.attributes import define_the_regular_attributes
from Generators.libweb_bindings.constants import define_the_constants
from Generators.libweb_bindings.context import GenerationContext
from Generators.libweb_bindings.cpp_types import fully_qualified_name_for_interface
from Generators.libweb_bindings.cpp_types import idl_identifier_cpp_name
from Generators.libweb_bindings.cpp_types import implementation_header_for_interface
from Generators.libweb_bindings.includes import GeneratedIncludes
from Generators.libweb_bindings.operations import define_the_regular_operations
from Generators.libweb_bindings.operations import write_regular_operations
from Utils.webidl_parser import Interface
def write_namespace_declaration(
out: TextIO, includes: GeneratedIncludes, context: GenerationContext, interface: Interface
) -> None:
includes.add("LibJS/Runtime/NativeFunction.h")
includes.add("LibJS/Runtime/Object.h")
out.write(
f"""class {interface.namespace_class} final : public JS::Object {{
JS_OBJECT({interface.namespace_class}, JS::Object);
GC_DECLARE_ALLOCATOR({interface.namespace_class});
public:
explicit {interface.namespace_class}(JS::Realm&);
virtual void initialize(JS::Realm&) override;
virtual ~{interface.namespace_class}() override;
private:
"""
)
if "WithGCVisitor" in interface.extended_attributes:
out.write(" virtual void visit_edges(JS::Cell::Visitor&) override;\n")
if "WithFinalizer" in interface.extended_attributes:
out.write(
"""
public:
static constexpr bool OVERRIDES_FINALIZE = true;
private:
virtual void finalize() override;
"""
)
for operations in overload_resolution.operation_overload_sets(interface).values():
operation = operations[0]
out.write(f" JS_DECLARE_NATIVE_FUNCTION({idl_identifier_cpp_name(operation)});\n")
if len(operations) > 1:
for overload_index, overloaded_operation in enumerate(operations):
out.write(
f" JS_DECLARE_NATIVE_FUNCTION({idl_identifier_cpp_name(overloaded_operation, suffix=overload_index)});\n"
)
out.write(
"""};
"""
)
# https://webidl.spec.whatwg.org/#namespace-object
def write_namespace_implementation(
out: TextIO, context: GenerationContext, includes: GeneratedIncludes, interface: Interface
) -> None:
includes.add("LibJS/Runtime/ValueInlines.h")
includes.add("LibWeb/WebIDL/Tracing.h")
includes.add_binding(interface.implemented_name)
includes.add(implementation_header_for_interface(interface))
# 1. Let namespaceObject be OrdinaryObjectCreate(realm.[[Intrinsics]].[[%Object.prototype%]]).
out.write(
f"""GC_DEFINE_ALLOCATOR({interface.namespace_class});
{interface.namespace_class}::{interface.namespace_class}(JS::Realm& realm)
: Object(ConstructWithPrototypeTag::Tag, realm.intrinsics().object_prototype())
{{
}}
{interface.namespace_class}::~{interface.namespace_class}()
{{
}}
void {interface.namespace_class}::initialize(JS::Realm& realm)
{{
auto& object = *this;
[[maybe_unused]] auto& vm = this->vm();
[[maybe_unused]] u8 default_attributes = JS::Attribute::Writable | JS::Attribute::Enumerable | JS::Attribute::Configurable;
Base::initialize(realm);
// The class string of a namespace object is the namespaces identifier.
define_direct_property(vm.well_known_symbol_to_string_tag(), JS::PrimitiveString::create(vm, "{interface.name}"_string), JS::Attribute::Configurable);
"""
)
# 2. Define the regular attributes of namespace on namespaceObject given realm.
define_the_regular_attributes(out, includes, interface)
# 3. Define the regular operations of namespace on namespaceObject given realm.
define_the_regular_operations(out, includes, interface)
# 4. Define the constants of namespace on namespaceObject given realm.
define_the_constants(out, context, includes, interface)
# 5. For each exposed interface interface which has the [LegacyNamespace] extended attribute with the identifier of namespace as its argument,
# 1. Let id be interfaces identifier.
# 2. Let interfaceObject be the result of creating an interface object for interface with id in realm.
# 3. Perform DefineMethodProperty(namespaceObject, id, interfaceObject, false).
# 6. Return namespaceObject.
# NB: Above is done in intrinsics defintions.
if "WithInitializer" in interface.extended_attributes:
out.write(
f"""
{fully_qualified_name_for_interface(interface).partition("::")[0]}::initialize(*this, realm);
"""
)
out.write(
"""}
"""
)
write_regular_operations(out, context, includes, interface)
if "WithGCVisitor" in interface.extended_attributes:
out.write(
f"""void {interface.namespace_class}::visit_edges(JS::Cell::Visitor& visitor)
{{
Base::visit_edges(visitor);
{fully_qualified_name_for_interface(interface).partition("::")[0]}::visit_edges(*this, visitor);
}}
"""
)
if "WithFinalizer" in interface.extended_attributes:
out.write(
f"""void {interface.namespace_class}::finalize()
{{
Base::finalize();
{fully_qualified_name_for_interface(interface).partition("::")[0]}::finalize(*this);
}}
"""
)

View file

@ -0,0 +1,555 @@
# Copyright (c) 2026-present, the Ladybird developers.
#
# SPDX-License-Identifier: BSD-2-Clause
from typing import Optional
from typing import TextIO
from Generators.libweb_bindings import overload_resolution
from Generators.libweb_bindings.arguments import write_operation_parameter_conversions
from Generators.libweb_bindings.attributes import reflected_attribute_name
from Generators.libweb_bindings.context import GenerationContext
from Generators.libweb_bindings.cpp_types import fully_qualified_name_for_interface
from Generators.libweb_bindings.cpp_types import idl_identifier_cpp_name
from Generators.libweb_bindings.cpp_types import idl_implementation_cpp_name
from Generators.libweb_bindings.cpp_types import is_numeric_type
from Generators.libweb_bindings.cpp_types import is_string_type
from Generators.libweb_bindings.extended_attributes import wrap_with_ce_reactions
from Generators.libweb_bindings.extended_attributes import wrap_with_extended_attribute_exposure_checks
from Generators.libweb_bindings.includes import GeneratedIncludes
from Generators.libweb_bindings.to_js_value import to_javascript_value
from Utils.webidl_parser import Attribute
from Utils.webidl_parser import IDLParameterizedType
from Utils.webidl_parser import IDLType
from Utils.webidl_parser import Interface
from Utils.webidl_parser import Operation
# https://webidl.spec.whatwg.org/#js-default-operations
def operation_is_default_to_json(operation: Operation) -> bool:
return (
operation.name == "toJSON"
and "Default" in operation.extended_attributes
and operation.return_type.name == "object"
)
# https://webidl.spec.whatwg.org/#dfn-json-types
def idl_type_is_json_type(idl_type: IDLType, context: GenerationContext) -> bool:
# The JSON types are:
# * nullable types whose inner type is a JSON type,
if idl_type.nullable:
return idl_type_is_json_type(idl_type.without_nullable(), context)
if isinstance(idl_type, IDLParameterizedType):
# * sequence types whose parameterized type is a JSON type,
# * frozen array types whose parameterized type is a JSON type,
if idl_type.name in ("sequence", "FrozenArray"):
return idl_type_is_json_type(idl_type.parameters[0], context)
# * records where all of their values are JSON types,
if idl_type.name == "record":
return idl_type_is_json_type(idl_type.parameters[1], context)
# * interface types that have a toJSON operation declared on themselves or one of their inherited interfaces.
interface = context.interface(idl_type)
if interface is not None:
return any(
operation.name == "toJSON" and "Default" in operation.extended_attributes
for interface_in_chain in context.inheritance_stack(interface)
for operation in interface_in_chain.regular_operations
)
# FIXME: * dictionary types where the types of all members declared on the dictionary and all its inherited dictionaries are JSON types,
# * numeric types,
# * boolean,
# * string types,
# * object,
return (
is_numeric_type(idl_type.name)
or idl_type.name == "boolean"
or is_string_type(idl_type.name)
or idl_type.name == "object"
or context.enumeration(idl_type) is not None
)
def define_the_regular_operations(
out: TextIO,
includes: GeneratedIncludes,
interface: Interface,
unforgeable: bool = False,
) -> None:
for name, operations in overload_resolution.operation_overload_sets(interface).items():
if any("LegacyUnforgeable" in operation.extended_attributes for operation in operations) != unforgeable:
continue
operation = operations[0]
out.write(
wrap_with_extended_attribute_exposure_checks(
includes,
operation.extended_attributes,
f""" object.define_native_function(realm, "{name}"_utf16_fly_string, {idl_identifier_cpp_name(operation)}, {overload_resolution.operation_overload_set_length(operations)}, default_attributes);
""",
)
)
if unforgeable:
return
for operation in interface.regular_operations:
if "FIXME" not in operation.extended_attributes:
continue
out.write(
f""" object.define_direct_property("{operation.name}"_utf16_fly_string, JS::js_undefined(), default_attributes | JS::Attribute::Unimplemented);
"""
)
def define_the_static_operations(out: TextIO, includes: GeneratedIncludes, interface: Interface) -> None:
for name, operations in overload_resolution.operation_overload_sets(interface, static=True).items():
operation = operations[0]
out.write(
wrap_with_extended_attribute_exposure_checks(
includes,
operation.extended_attributes,
f""" object.define_native_function(realm, "{name}"_utf16_fly_string, {idl_identifier_cpp_name(operation)}, {overload_resolution.operation_overload_set_length(operations)}, JS::Attribute::Enumerable | JS::Attribute::Configurable | JS::Attribute::Writable);
""",
)
)
def define_unscopable_members(out: TextIO, includes: GeneratedIncludes, interface: Interface) -> None:
unscopable_names = []
for attribute in interface.regular_attributes:
if "FIXME" not in attribute.extended_attributes and "Unscopable" in attribute.extended_attributes:
unscopable_names.append(attribute.name)
for name, operations in overload_resolution.operation_overload_sets(interface).items():
if all("Unscopable" in operation.extended_attributes for operation in operations):
unscopable_names.append(name)
if not unscopable_names:
return
includes.add("LibJS/Runtime/Object.h")
out.write(
""" auto unscopable_object = JS::Object::create(realm, nullptr);
"""
)
for name in unscopable_names:
out.write(
f""" MUST(unscopable_object->create_data_property("{name}"_utf16_fly_string, JS::Value(true)));
"""
)
out.write(
""" object.define_direct_property(vm.well_known_symbol_unscopables(), unscopable_object, JS::Attribute::Configurable);
"""
)
def define_the_stringifier(
out: TextIO,
includes: GeneratedIncludes,
interface: Interface,
unforgeable: bool = False,
) -> None:
if interface.stringifier is None:
return
extended_attributes = interface.stringifier.extended_attributes
if ("LegacyUnforgeable" in extended_attributes) != unforgeable:
return
out.write(
wrap_with_extended_attribute_exposure_checks(
includes,
extended_attributes,
""" object.define_native_function(realm, "toString"_utf16_fly_string, to_string, 0, default_attributes);
""",
)
)
out.write(
"""
"""
)
def write_regular_operations(
out: TextIO, context: GenerationContext, includes: GeneratedIncludes, interface: Interface
) -> None:
for operations in overload_resolution.operation_overload_sets(interface).values():
if len(operations) > 1:
receiver_class = interface.namespace_class if interface.is_namespace else interface.prototype_class
overload_resolution.write_overload_arbiter(
out, context, includes, interface, operations, receiver_class=receiver_class
)
for overload_index, operation in enumerate(operations):
write_operation(out, context, includes, interface, operation, overload_index)
else:
write_operation(out, context, includes, interface, operations[0])
def write_regular_operations_for_receiver(
out: TextIO,
context: GenerationContext,
includes: GeneratedIncludes,
interface: Interface,
receiver_class: str,
defined_callbacks: Optional[set[str]] = None,
) -> None:
for operations in overload_resolution.operation_overload_sets(interface).values():
callbacks = [
idl_identifier_cpp_name(operation, suffix=overload_index if len(operations) > 1 else None)
for overload_index, operation in enumerate(operations)
]
callbacks.append(idl_identifier_cpp_name(operations[0]))
if defined_callbacks is not None and all(callback in defined_callbacks for callback in callbacks):
continue
if defined_callbacks is not None:
defined_callbacks.update(callbacks)
if len(operations) > 1:
overload_resolution.write_overload_arbiter(
out, context, includes, interface, operations, receiver_class=receiver_class
)
for overload_index, operation in enumerate(operations):
write_operation(out, context, includes, interface, operation, overload_index, receiver_class)
else:
write_operation(out, context, includes, interface, operations[0], receiver_class=receiver_class)
def write_static_operations(
out: TextIO, context: GenerationContext, includes: GeneratedIncludes, interface: Interface
) -> None:
for operations in overload_resolution.operation_overload_sets(interface, static=True).values():
if len(operations) > 1:
overload_resolution.write_overload_arbiter(
out,
context,
includes,
interface,
operations,
receiver_class=interface.constructor_class,
)
for overload_index, operation in enumerate(operations):
write_operation(out, context, includes, interface, operation, overload_index, emit_as_static=True)
else:
write_operation(out, context, includes, interface, operations[0], emit_as_static=True)
def write_stringifier(
out: TextIO,
context: GenerationContext,
includes: GeneratedIncludes,
interface: Interface,
receiver_class: Optional[str] = None,
) -> None:
if receiver_class is None:
receiver_class = interface.prototype_class
if interface.stringifier is None:
return
attribute = interface.stringifier.attribute
stringifier_type = attribute.type if attribute is not None else IDLType("DOMString")
stringifier_cpp_name = idl_implementation_cpp_name(attribute) if attribute is not None else "to_string"
out.write(
f"""JS_DEFINE_NATIVE_FUNCTION({receiver_class}::to_string)
{{
WebIDL::log_trace(vm, "{receiver_class}::to_string");
auto* idl_object = TRY(impl_from(vm));
auto R = TRY(throw_dom_exception_if_needed(vm, [&] {{ return idl_object->{stringifier_cpp_name}(); }}));
return {to_javascript_value(stringifier_type, "R", includes, context)};
}}
"""
)
# https://webidl.spec.whatwg.org/#dfn-create-operation-function
def write_operation(
out: TextIO,
context: GenerationContext,
includes: GeneratedIncludes,
interface: Interface,
operation: Operation,
overload_index: Optional[int] = None,
receiver_class: Optional[str] = None,
emit_as_static: bool = False,
) -> None:
if operation_is_default_to_json(operation):
write_default_to_json_operation(out, context, includes, interface, operation)
return
return_type_is_promise = operation.return_type.name == "Promise"
arguments = ", ".join(idl_identifier_cpp_name(parameter) for parameter in operation.parameters)
callee_arguments = arguments
operation_invokes_as_static = emit_as_static or interface.is_namespace
if operation_invokes_as_static:
callee_arguments = f"vm, {arguments}" if arguments else "vm"
callback_name = idl_identifier_cpp_name(operation, suffix=overload_index)
if interface.is_namespace:
receiver_class = receiver_class or interface.namespace_class
else:
receiver_class = receiver_class or (
interface.constructor_class if emit_as_static else interface.prototype_class
)
out.write(
f"""JS_DEFINE_NATIVE_FUNCTION({receiver_class}::{callback_name})
{{
WebIDL::log_trace(vm, "{receiver_class}::{callback_name}");
[[maybe_unused]] auto& realm = *vm.current_realm();
"""
)
if return_type_is_promise:
includes.add("LibWeb/WebIDL/Promise.h")
out.write(
""" auto steps = [&realm, &vm]() -> JS::ThrowCompletionOr<GC::Ref<WebIDL::Promise>> {
(void)realm;
"""
)
if not operation_invokes_as_static:
out.write(
f""" [[maybe_unused]] {fully_qualified_name_for_interface(interface)}* idl_object = TRY(impl_from(vm));
"""
)
required_argument_count = overload_resolution.operation_length(operation)
if required_argument_count == 1:
out.write(
f""" if (vm.argument_count() < 1)
return vm.throw_completion<JS::TypeError>(JS::ErrorType::BadArgCountOne, "{operation.name}");
"""
)
elif required_argument_count > 1:
out.write(
f""" if (vm.argument_count() < {required_argument_count})
return vm.throw_completion<JS::TypeError>(JS::ErrorType::BadArgCountMany, "{operation.name}", "{required_argument_count}");
"""
)
write_operation_parameter_conversions(out, operation.parameters, includes, context)
if operation_invokes_as_static:
callee = fully_qualified_name_for_interface(interface)
if interface.is_namespace:
callee = fully_qualified_name_for_interface(interface).partition("::")[0]
out.write(
f""" [[maybe_unused]] auto R = TRY(throw_dom_exception_if_needed(vm, [&] {{ return {callee}::{idl_implementation_cpp_name(operation)}({callee_arguments}); }}));
"""
)
if return_type_is_promise:
out.write(
f""" return R;
}};
auto maybe_R = steps();
// And then, if an exception E was thrown:
// 1. If op has a return type that is a promise type, then return ! Call(%Promise.reject%, %Promise%, «E»).
// 2. Otherwise, end these steps and allow the exception to propagate.
if (maybe_R.is_throw_completion())
return WebIDL::create_rejected_promise(realm, maybe_R.error_value())->promise();
auto R = maybe_R.release_value();
return {to_javascript_value(operation.return_type, "R", includes, context)};
}}
"""
)
else:
out.write(
f""" return {to_javascript_value(operation.return_type, "R", includes, context)};
}}
"""
)
return
if "CEReactions" in operation.extended_attributes:
if return_type_is_promise:
raise RuntimeError(
f"Unsupported promise-returning [CEReactions] operation '{operation.name}' on '{interface.name}'"
)
ce_reactions_steps = wrap_with_ce_reactions(includes, "original_steps()")
out.write(
f""" auto original_steps = [&] {{
return throw_dom_exception_if_needed(vm, [&] {{ return idl_object->{idl_implementation_cpp_name(operation)}({arguments}); }});
}};
[[maybe_unused]] auto R = TRY({ce_reactions_steps});
return {to_javascript_value(operation.return_type, "R", includes, context)};
}}
"""
)
return
if return_type_is_promise:
out.write(
f""" [[maybe_unused]] auto R = TRY(throw_dom_exception_if_needed(vm, [&] {{ return idl_object->{idl_implementation_cpp_name(operation)}({arguments}); }}));
return R;
}};
auto maybe_R = steps();
// And then, if an exception E was thrown:
// 1. If op has a return type that is a promise type, then return ! Call(%Promise.reject%, %Promise%, «E»).
// 2. Otherwise, end these steps and allow the exception to propagate.
if (maybe_R.is_throw_completion())
return WebIDL::create_rejected_promise(realm, maybe_R.error_value())->promise();
return {to_javascript_value(operation.return_type, "maybe_R.release_value()", includes, context)};
}}
"""
)
return
out.write(
f""" [[maybe_unused]] auto R = TRY(throw_dom_exception_if_needed(vm, [&] {{ return idl_object->{idl_implementation_cpp_name(operation)}({arguments}); }}));
return {to_javascript_value(operation.return_type, "R", includes, context)};
}}
"""
)
# FIXME: This belongs is an attribute getter helper somewhere.
def default_to_json_getter_steps(
attribute: Attribute,
value_name: str,
) -> str:
is_reflected = "Reflect" in attribute.extended_attributes
if is_reflected and attribute.type.name == "boolean":
return f'auto {value_name} = idl_object->has_attribute("{reflected_attribute_name(attribute)}"_fly_string);'
if is_reflected:
return (
f'auto {value_name} = idl_object->get_attribute_value("{reflected_attribute_name(attribute)}"_fly_string);'
)
return f"auto {value_name} = TRY(throw_dom_exception_if_needed(vm, [&] {{ return idl_object->{idl_implementation_cpp_name(attribute)}(); }}));"
# https://webidl.spec.whatwg.org/#collect-attribute-values
def collect_attribute_values(
interface: Interface,
context: GenerationContext,
includes: GeneratedIncludes,
attribute_values: list[str],
) -> None:
# 1. If a toJSON operation with a [Default] extended attribute is declared on I
# then for each exposed regular attribute attr that is an interface member of I, in order:
if not any(operation_is_default_to_json(operation) for operation in interface.regular_operations):
return
for attribute in interface.regular_attributes:
# 1. Let id be the identifier of attr.
# 2. Let value be the result of running the getter steps of attr with object as this.
# 3. If value is a JSON type, then set map[id] to value.
if not idl_type_is_json_type(attribute.type, context):
continue
value_name = f"{idl_identifier_cpp_name(attribute)}_{len(attribute_values)}_value"
key_name = f"{idl_identifier_cpp_name(attribute)}_{len(attribute_values)}_key"
js_value_name = f"{value_name}_js"
getter_steps = default_to_json_getter_steps(attribute, value_name)
attribute_values.append(
wrap_with_extended_attribute_exposure_checks(
includes,
attribute.extended_attributes,
f""" {getter_steps}
// 1. Let k be key converted to a JavaScript value.
auto {key_name} = "{attribute.name}"_utf16_fly_string;
// 2. Let v be value converted to a JavaScript value.
auto {js_value_name} = {to_javascript_value(attribute.type, value_name, includes, context)};
// 3. Perform ! CreateDataPropertyOrThrow(result, k, v).
MUST(result->create_data_property({key_name}, {js_value_name}));
""",
)
)
# https://webidl.spec.whatwg.org/#collect-attribute-values-of-an-inheritance-stack
def collect_attribute_values_of_an_inheritance_stack(
stack: list[Interface],
context: GenerationContext,
includes: GeneratedIncludes,
attribute_values: list[str],
) -> None:
# 1. Let I be the result of popping from stack.
interface = stack.pop()
# 2. Invoke collect attribute values given object, I, and map.
collect_attribute_values(interface, context, includes, attribute_values)
# 3. If stack is not empty, then invoke collect attribute values of an inheritance stack given object, stack, and map.
if stack:
collect_attribute_values_of_an_inheritance_stack(stack, context, includes, attribute_values)
# https://webidl.spec.whatwg.org/#js-default-tojson
def write_default_to_json_operation(
out: TextIO,
context: GenerationContext,
includes: GeneratedIncludes,
interface: Interface,
operation: Operation,
) -> None:
includes.add("LibJS/Runtime/Object.h")
includes.add("LibWeb/WebIDL/Tracing.h")
# 1. Let map be a new ordered map.
# 2. Let stack be the result of creating an inheritance stack for interface I.
stack = context.inheritance_stack(interface)
# 3. Invoke collect attribute values of an inheritance stack given this, stack, and map.
attribute_values: list[str] = []
collect_attribute_values_of_an_inheritance_stack(stack, context, includes, attribute_values)
out.write(
f"""JS_DEFINE_NATIVE_FUNCTION({interface.prototype_class}::{idl_identifier_cpp_name(operation)})
{{
WebIDL::log_trace(vm, "{interface.prototype_class}::{idl_identifier_cpp_name(operation)}");
auto& realm = *vm.current_realm();
[[maybe_unused]] auto* idl_object = TRY(impl_from(vm));
// 4. Let result be OrdinaryObjectCreate(%Object.prototype%).
auto result = JS::Object::create(realm, realm.intrinsics().object_prototype());
// 5. For each key value of map:
{"".join(attribute_values)}
// 6. Return result.
return result;
}}
"""
)
def write_argument_count_check(out: TextIO, function_name: str, argument_count: int) -> None:
if argument_count == 0:
return
if argument_count == 1:
out.write(
f""" if (vm.argument_count() < 1)
return vm.throw_completion<JS::TypeError>(JS::ErrorType::BadArgCountOne, "{function_name}");
"""
)
return
out.write(
f""" if (vm.argument_count() < {argument_count})
return vm.throw_completion<JS::TypeError>(JS::ErrorType::BadArgCountMany, "{function_name}", "{argument_count}");
"""
)

View file

@ -0,0 +1,529 @@
# Copyright (c) 2026-present, the Ladybird developers.
#
# SPDX-License-Identifier: BSD-2-Clause
from dataclasses import dataclass
from enum import Enum
from typing import Optional
from typing import Sequence
from typing import TextIO
from typing import Union
from Generators.libweb_bindings.context import GenerationContext
from Generators.libweb_bindings.cpp_types import idl_identifier_cpp_name
from Generators.libweb_bindings.cpp_types import is_numeric_type
from Generators.libweb_bindings.cpp_types import is_string_type
from Generators.libweb_bindings.includes import GeneratedIncludes
from Utils.webidl_parser import Constructor
from Utils.webidl_parser import IDLParameterizedType
from Utils.webidl_parser import IDLType
from Utils.webidl_parser import IDLUnionType
from Utils.webidl_parser import Interface
from Utils.webidl_parser import Operation
from Utils.webidl_parser import OperationParameter
class Optionality(Enum):
Required = "Required"
Optional = "Optional"
Variadic = "Variadic"
@dataclass
class EffectiveOverloadItem:
callable_id: int
types: list[IDLType]
optionality_values: list[Optionality]
def write_overload_resolution_switch(
out: TextIO,
context: GenerationContext,
interface: Interface,
overloads: Sequence[Union[Constructor, Operation]],
) -> None:
maximum_argument_count = 0
effective_overload_sets: dict[int, list[EffectiveOverloadItem]] = {}
for overload in compute_the_effective_overload_set(overloads):
maximum_argument_count = max(maximum_argument_count, len(overload.types))
effective_overload_sets.setdefault(len(overload.types), []).append(overload)
dictionary_types: set[str] = set()
out.write(
f""" Optional<int> chosen_overload_callable_id;
Optional<IDL::EffectiveOverloadSet> effective_overload_set;
switch (min({maximum_argument_count}, vm.argument_count())) {{
"""
)
for argument_count, effective_overload_set in sorted(effective_overload_sets.items()):
if len(effective_overload_set) == 1:
overload = effective_overload_set[0]
dictionary_types.update(context.dictionary_type_names(*overload.types))
out.write(
f""" case {argument_count}:
chosen_overload_callable_id = {overload.callable_id};
break;
"""
)
continue
distinguishing_argument_index = resolve_distinguishing_argument_index(
interface,
effective_overload_set,
argument_count,
context,
)
out.write(
f""" case {argument_count}: {{
Vector<IDL::EffectiveOverloadSet::Item> overloads;
overloads.ensure_capacity({len(effective_overload_set)});
"""
)
for overload in effective_overload_set:
dictionary_types.update(context.dictionary_type_names(*overload.types))
types = ", ".join(constructor_for_idl_type(idl_type, context) for idl_type in overload.types)
optionality_values = ", ".join(
f"IDL::Optionality::{optionality.value}" for optionality in overload.optionality_values
)
out.write(
f""" overloads.empend({overload.callable_id}, Vector<NonnullRefPtr<IDL::Type const>> {{ {types} }}, Vector<IDL::Optionality> {{ {optionality_values} }});
"""
)
out.write(
f""" effective_overload_set.emplace(move(overloads), {distinguishing_argument_index});
break;
}}
"""
)
out.write(""" }
Vector<StringView> dictionary_types {
""")
for dictionary_type in sorted(dictionary_types):
out.write(f' "{dictionary_type}"sv,\n')
out.write(
""" };
if (!chosen_overload_callable_id.has_value()) {
if (!effective_overload_set.has_value())
return vm.throw_completion<JS::TypeError>(JS::ErrorType::OverloadResolutionFailed);
chosen_overload_callable_id = TRY(WebIDL::resolve_overload(vm, effective_overload_set.value(), dictionary_types)).callable_id;
}
"""
)
def write_overload_arbiter(
out: TextIO,
context: GenerationContext,
includes: GeneratedIncludes,
interface: Interface,
operations: list[Operation],
receiver_class: Optional[str] = None,
) -> None:
if receiver_class is None:
receiver_class = interface.prototype_class
includes.add("AK/Optional.h")
includes.add("AK/Vector.h")
includes.add("LibIDL/Types.h")
includes.add("LibWeb/WebIDL/OverloadResolution.h")
includes.add("LibWeb/WebIDL/Tracing.h")
operation = operations[0]
out.write(
f"""JS_DEFINE_NATIVE_FUNCTION({receiver_class}::{idl_identifier_cpp_name(operation)})
{{
WebIDL::log_trace(vm, "{receiver_class}::{idl_identifier_cpp_name(operation)}");
"""
)
write_overload_resolution_switch(out, context, interface, operations)
out.write(
"""
switch (chosen_overload_callable_id.value()) {
"""
)
for overload_index, overload in enumerate(operations):
out.write(
f""" case {overload_index}:
return {idl_identifier_cpp_name(overload, suffix=overload_index)}(vm);
"""
)
out.write(""" default:
VERIFY_NOT_REACHED();
}
}
""")
# https://webidl.spec.whatwg.org/#compute-the-effective-overload-set
def compute_the_effective_overload_set(
operations: Sequence[Union[Constructor, Operation]],
) -> list[EffectiveOverloadItem]:
# 1. Let S be an ordered set.
overloads: list[EffectiveOverloadItem] = []
# 2. Let F be an ordered set with items as follows, according to the kind of effective overload set.
# NOTE: The caller provides the relevant operation overload set.
# 3. Let maxarg be the maximum number of arguments the operations, legacy factory functions, or callback functions
# in F are declared to take. For variadic operations and legacy factory functions, the argument on which the
# ellipsis appears counts as a single argument.
maximum_arguments = max(len(operation.parameters) for operation in operations)
# 4. Let max be max(maxarg, N).
# NOTE: N is a runtime value. The generated arbiter handles this by switching on min(maxarg, argument_count).
# 5. For each operation or extended attribute X in F:
for overload_id, operation in enumerate(operations):
# 1. Let arguments be the list of arguments X is declared to take.
arguments = operation.parameters
# 2. Let n be the size of arguments.
argument_count = len(arguments)
# 3. Let types be a type list.
types: list[IDLType] = []
# 4. Let optionalityValues be an optionality list.
optionality_values: list[Optionality] = []
overload_is_variadic = False
# 5. For each argument in arguments:
for argument in arguments:
# 1. Append the type of argument to types.
types.append(argument.type)
# 2. Append "variadic" to optionalityValues if argument is a final, variadic argument, "optional" if
# argument is optional, and "required" otherwise.
if argument.variadic:
optionality_values.append(Optionality.Variadic)
overload_is_variadic = True
elif argument.optional:
optionality_values.append(Optionality.Optional)
else:
optionality_values.append(Optionality.Required)
# 6. Append the tuple (X, types, optionalityValues) to S.
overloads.append(EffectiveOverloadItem(overload_id, types, optionality_values))
# 7. If X is declared to be variadic, then:
if overload_is_variadic:
# 1. For each i in the range n to max - 1, inclusive:
for i in range(argument_count, maximum_arguments):
item_types = list(types)
item_optionality_values = list(optionality_values)
# 4. For each j in the range n to i, inclusive:
for _ in range(argument_count, i + 1):
# 1. Append types[n - 1] to t.
item_types.append(types[argument_count - 1])
# 2. Append "variadic" to o.
item_optionality_values.append(Optionality.Variadic)
# 5. Append the tuple (X, t, o) to S.
overloads.append(EffectiveOverloadItem(overload_id, item_types, item_optionality_values))
# 8. Let i be n - 1.
i = argument_count - 1
# 9. While i >= 0:
while i >= 0:
# 1. If arguments[i] is not optional, then break.
if not arguments[i].optional and not arguments[i].variadic:
break
# 5. Append the tuple (X, t, o) to S.
overloads.append(EffectiveOverloadItem(overload_id, types[:i], optionality_values[:i]))
# 6. Set i to i - 1.
i -= 1
return overloads
# https://webidl.spec.whatwg.org/#dfn-distinguishing-argument-index
def resolve_distinguishing_argument_index(
interface: Interface,
items: list[EffectiveOverloadItem],
argument_count: int,
context: GenerationContext,
) -> int:
for argument_index in range(argument_count):
found_indistinguishable = False
for first_item_index, first_item in enumerate(items):
for second_item in items[first_item_index + 1 :]:
if not is_distinguishable_from(
first_item.types[argument_index],
second_item.types[argument_index],
interface,
context,
):
found_indistinguishable = True
break
if found_indistinguishable:
break
if not found_indistinguishable:
return argument_index
raise RuntimeError(f"Could not resolve distinguishing argument index for overloads of '{items[0].callable_id}'")
def constructor_for_idl_type(idl_type: IDLType, context: GenerationContext) -> str:
nullable = "true" if idl_type.nullable else "false"
if isinstance(idl_type, IDLParameterizedType):
parameters = ", ".join(constructor_for_idl_type(parameter, context) for parameter in idl_type.parameters)
return (
f'make_ref_counted<IDL::ParameterizedType>("{idl_type.name}", {nullable}, '
f"Vector<NonnullRefPtr<IDL::Type const>> {{ {parameters} }})"
)
if isinstance(idl_type, IDLUnionType):
member_types = ", ".join(
constructor_for_idl_type(member_type, context) for member_type in idl_type.member_types
)
return (
f'make_ref_counted<IDL::UnionType>("{idl_type.name}", {nullable}, '
f"Vector<NonnullRefPtr<IDL::Type const>> {{ {member_types} }})"
)
return f'make_ref_counted<IDL::Type>("{idl_type.name}", {nullable})'
# https://webidl.spec.whatwg.org/#dfn-distinguishable
def is_distinguishable_from(
left: IDLType,
right: IDLType,
interface: Interface,
context: GenerationContext,
) -> bool:
# 1. If one type includes a nullable type and the other type either includes a nullable type,
# is a union type with flattened member types including a dictionary type, or is a dictionary type,
# return false.
if left.includes_nullable_type() and (
right.includes_nullable_type() or any(context.dictionary(member) for member in right.flattened_member_types())
):
return False
# 2. If both types are either a union type or nullable union type, return true if each member type of the one
# is distinguishable with each member type of the other, or false otherwise.
if isinstance(left, IDLUnionType) and isinstance(right, IDLUnionType):
return all(
is_distinguishable_from(left_member, right_member, interface, context)
for left_member in left.member_types
for right_member in right.member_types
)
# 3. If one type is a union type or nullable union type, return true if each member type of the union type is
# distinguishable with the non-union type, or false otherwise.
if isinstance(left, IDLUnionType) or isinstance(right, IDLUnionType):
if isinstance(left, IDLUnionType):
the_union = left
non_union = right
else:
assert isinstance(right, IDLUnionType)
the_union = right
non_union = left
return all(
is_distinguishable_from(non_union, member_type, interface, context)
for member_type in the_union.member_types
)
left_category = distinguishability_category(left, context)
right_category = distinguishability_category(right, context)
if left_category == "InterfaceLike" and right_category == "InterfaceLike":
# The two identified interface-like types are not the same, and
# FIXME: no single platform object implements both interface-like types.
return left.name != right.name
table = {
"Undefined": {
"Boolean",
"Numeric",
"BigInt",
"String",
"Object",
"Symbol",
"InterfaceLike",
"CallbackFunction",
"SequenceLike",
},
"Boolean": {
"Undefined",
"Numeric",
"BigInt",
"String",
"Object",
"Symbol",
"InterfaceLike",
"CallbackFunction",
"DictionaryLike",
"SequenceLike",
},
"Numeric": {
"Undefined",
"Boolean",
"BigInt",
"String",
"Object",
"Symbol",
"InterfaceLike",
"CallbackFunction",
"DictionaryLike",
"SequenceLike",
},
"BigInt": {
"Undefined",
"Boolean",
"Numeric",
"String",
"Object",
"Symbol",
"InterfaceLike",
"CallbackFunction",
"DictionaryLike",
"SequenceLike",
},
"String": {
"Undefined",
"Boolean",
"Numeric",
"BigInt",
"Object",
"Symbol",
"InterfaceLike",
"CallbackFunction",
"DictionaryLike",
"SequenceLike",
},
"Object": {
"Undefined",
"Boolean",
"Numeric",
"BigInt",
"String",
"Symbol",
},
"Symbol": {
"Undefined",
"Boolean",
"Numeric",
"BigInt",
"String",
"Object",
"InterfaceLike",
"CallbackFunction",
"DictionaryLike",
"SequenceLike",
},
"InterfaceLike": {
"Undefined",
"Boolean",
"Numeric",
"BigInt",
"String",
"Symbol",
"CallbackFunction",
"DictionaryLike",
"SequenceLike",
},
"CallbackFunction": {
"Undefined",
"Boolean",
"Numeric",
"BigInt",
"String",
"Symbol",
"InterfaceLike",
"SequenceLike",
},
"DictionaryLike": {
"Boolean",
"Numeric",
"BigInt",
"String",
"Symbol",
"InterfaceLike",
"SequenceLike",
},
"SequenceLike": {
"Undefined",
"Boolean",
"Numeric",
"BigInt",
"String",
"Symbol",
"InterfaceLike",
"CallbackFunction",
"DictionaryLike",
},
}
return right_category in table[left_category]
def distinguishability_category(idl_type: IDLType, context: GenerationContext) -> str:
if idl_type.name == "undefined":
return "Undefined"
if idl_type.name == "boolean":
return "Boolean"
if is_numeric_type(idl_type.name):
return "Numeric"
if idl_type.name == "bigint":
return "BigInt"
if is_string_type(idl_type.name):
return "String"
if idl_type.name == "object":
return "Object"
if idl_type.name == "symbol":
return "Symbol"
if context.callback_function(idl_type) is not None:
return "CallbackFunction"
if context.dictionary(idl_type) is not None or idl_type.name == "record":
return "DictionaryLike"
if isinstance(idl_type, IDLParameterizedType) and idl_type.name in ("sequence", "FrozenArray"):
return "SequenceLike"
return "InterfaceLike"
def operation_overload_sets(interface: Interface, static: bool = False) -> dict[str, list[Operation]]:
overload_sets: dict[str, list[Operation]] = {}
operations = interface.static_operations if static else interface.regular_operations
for operation in operations:
if "FIXME" in operation.extended_attributes:
continue
overload_sets.setdefault(operation.name, []).append(operation)
return overload_sets
def operation_overload_set_length(operations: list[Operation]) -> int:
return min(operation_length(operation) for operation in operations)
def operation_length(operation: Operation) -> int:
return parameter_list_length(operation.parameters)
def parameter_list_length(parameters: list[OperationParameter]) -> int:
return sum(1 for parameter in parameters if not parameter.optional and not parameter.variadic)
def operation_callback_names(interface: Interface) -> set[str]:
callbacks = set()
for operations in operation_overload_sets(interface).values():
operation = operations[0]
callbacks.add(idl_identifier_cpp_name(operation))
if len(operations) > 1:
for overload_index, overloaded_operation in enumerate(operations):
callbacks.add(idl_identifier_cpp_name(overloaded_operation, suffix=overload_index))
return callbacks

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,626 @@
# Copyright (c) 2026-present, the Ladybird developers.
#
# SPDX-License-Identifier: BSD-2-Clause
from typing import TextIO
from Generators.libweb_bindings.context import GenerationContext
from Generators.libweb_bindings.cpp_types import add_binding_include_for_type
from Generators.libweb_bindings.cpp_types import add_buffer_source_type_include
from Generators.libweb_bindings.cpp_types import cpp_name
from Generators.libweb_bindings.cpp_types import cpp_type_details
from Generators.libweb_bindings.cpp_types import cpp_type_for_idl_type
from Generators.libweb_bindings.cpp_types import cpp_type_for_idl_type_details
from Generators.libweb_bindings.cpp_types import implementation_header_for_interface
from Generators.libweb_bindings.cpp_types import interface_like_type_for_idl_type
from Generators.libweb_bindings.cpp_types import is_buffer_source_type
from Generators.libweb_bindings.cpp_types import is_optional_without_default
from Generators.libweb_bindings.cpp_types import is_string_type
from Generators.libweb_bindings.includes import GeneratedIncludes
from Utils.utils import make_name_acceptable_cpp
from Utils.utils import string_to_cpp_enum_name
from Utils.utils import title_case_to_snake_case
from Utils.webidl_parser import Dictionary
from Utils.webidl_parser import DictionaryMember
from Utils.webidl_parser import Enumeration
from Utils.webidl_parser import IDLParameterizedType
from Utils.webidl_parser import IDLType
from Utils.webidl_parser import IDLUnionType
def unsupported_to_javascript_value(idl_type: IDLType) -> str:
raise RuntimeError(f"Unsupported IDL value conversion for '{idl_type.name}'")
def to_value_function_name(dictionary: Dictionary) -> str:
return f"{make_name_acceptable_cpp(title_case_to_snake_case(dictionary.name))}_to_value"
def write_enumeration_to_javascript_value_declaration(
out: TextIO,
enumeration: Enumeration,
includes: GeneratedIncludes,
) -> None:
includes.add("AK/String.h")
out.write(f"String idl_enum_to_string({enumeration.name});\n\n")
def write_dictionary_to_javascript_value_declaration(out: TextIO, dictionary: Dictionary) -> None:
if "GenerateToValue" not in dictionary.extended_attributes:
return
out.write(f"JS::Value {to_value_function_name(dictionary)}(JS::Realm&, {dictionary.name} const&);\n\n")
def write_enumeration_to_javascript_value_conversion(out: TextIO, enumeration: Enumeration) -> None:
out.write(
f"""// https://webidl.spec.whatwg.org/#idl-enumeration
String idl_enum_to_string({enumeration.name} value)
{{
// The result of converting an IDL enumeration type value to a JavaScript value is the String value that represents the same sequence of code units as the enumeration value.
switch (value) {{
"""
)
for value in enumeration.values:
out.write(f" case {enumeration.name}::{string_to_cpp_enum_name(value)}:\n")
out.write(f' return "{value}"_string;\n')
out.write(
""" }
VERIFY_NOT_REACHED();
}
"""
)
def write_dictionary_to_javascript_value_conversion(
out: TextIO,
dictionary: Dictionary,
includes: GeneratedIncludes,
context: GenerationContext,
) -> None:
if "GenerateToValue" not in dictionary.extended_attributes:
return
includes.add("LibJS/Runtime/Object.h")
out.write(
f"""JS::Value {to_value_function_name(dictionary)}(JS::Realm& realm, {dictionary.name} const& dictionary)
{{
auto& vm = realm.vm();
return {to_javascript_value(IDLType(dictionary.name), "dictionary", includes, context)};
}}
"""
)
def integer_to_javascript_value(cpp_type_name: str, value: str, includes: GeneratedIncludes) -> str:
includes.add("LibWeb/WebIDL/Types.h")
return f"JS::Value(static_cast<{cpp_type_name}>({value}))"
# 3.2.1. any, https://webidl.spec.whatwg.org/#js-any
def any_to_javascript_value(value: str) -> str:
# An IDL any value is converted to a JavaScript value according to the rules for converting the specific type of the
# NB: We're getting passed a JS::Value - which is already our JS representation, so we can just return it as-is.
return value
# 3.2.2. undefined, https://webidl.spec.whatwg.org/#js-undefined
def undefined_to_javascript_value() -> str:
# The unique IDL undefined value is converted to the JavaScript undefined value.
return "JS::js_undefined()"
# 3.2.3. boolean, https://webidl.spec.whatwg.org/#js-boolean
def boolean_to_javascript_value(value: str) -> str:
# The IDL boolean value true is converted to the JavaScript true value and the IDL boolean value false is converted
# to the JavaScript false value.
return f"JS::Value({value})"
# 3.2.4.1. byte, https://webidl.spec.whatwg.org/#js-byte
def byte_to_javascript_value(value: str, includes: GeneratedIncludes) -> str:
# The result of converting an IDL byte value to a JavaScript value is a Number that represents the same numeric value
# as the IDL byte value. The Number value will be an integer in the range [128, 127].
return integer_to_javascript_value("WebIDL::Byte", value, includes)
# 3.2.4.2. octet, https://webidl.spec.whatwg.org/#js-octet
def octet_to_javascript_value(value: str, includes: GeneratedIncludes) -> str:
# The result of converting an IDL octet value to a JavaScript value is a Number that represents the same numeric value
# as the IDL octet value. The Number value will be an integer in the range [0, 255].
return integer_to_javascript_value("WebIDL::Octet", value, includes)
# 3.2.4.3. short, https://webidl.spec.whatwg.org/#js-short
def short_to_javascript_value(value: str, includes: GeneratedIncludes) -> str:
# The result of converting an IDL short value to a JavaScript value is a Number that represents the same numeric value
# as the IDL short value. The Number value will be an integer in the range [32768, 32767].
return integer_to_javascript_value("WebIDL::Short", value, includes)
# 3.2.4.4. unsigned short, https://webidl.spec.whatwg.org/#js-unsigned-short
def unsigned_short_to_javascript_value(value: str, includes: GeneratedIncludes) -> str:
# The result of converting an IDL unsigned short value to a JavaScript value is a Number that represents the same numeric
# value as the IDL unsigned short value. The Number value will be an integer in the range [0, 65535].
return integer_to_javascript_value("WebIDL::UnsignedShort", value, includes)
# 3.2.4.5. long, https://webidl.spec.whatwg.org/#js-long
def long_to_javascript_value(value: str, includes: GeneratedIncludes) -> str:
# The result of converting an IDL long value to a JavaScript value is a Number that represents the same numeric value as
# the IDL long value. The Number value will be an integer in the range [2147483648, 2147483647].
return integer_to_javascript_value("WebIDL::Long", value, includes)
# 3.2.4.6. unsigned long, https://webidl.spec.whatwg.org/#js-unsigned-long
def unsigned_long_to_javascript_value(value: str, includes: GeneratedIncludes) -> str:
# The result of converting an IDL unsigned long value to a JavaScript value is a Number that represents the same numeric
# value as the IDL unsigned long value. The Number value will be an integer in the range [0, 4294967295].
return integer_to_javascript_value("WebIDL::UnsignedLong", value, includes)
# 3.2.4.7. long long,
def long_long_to_javascript_value(value: str, includes: GeneratedIncludes) -> str:
# The result of converting an IDL long long value to a JavaScript value is a Number value that represents the closest
# numeric value to the long long, choosing the numeric value with an even significand if there are two equally close
# values. If the long long is in the range [2^53 + 1, 2^53 1], then the Number will be able to represent exactly
# the same value as the long long.
return integer_to_javascript_value("double", value, includes)
# 3.2.4.8. unsigned long long, https://webidl.spec.whatwg.org/#js-unsigned-long-long
def unsigned_long_long_to_javascript_value(value: str, includes: GeneratedIncludes) -> str:
# The result of converting an IDL unsigned long long value to a JavaScript value is a Number value that represents
# the closest numeric value to the unsigned long long, choosing the numeric value with an even significand if there
# are two equally close values. If the unsigned long long is less than or equal to 253 1, then the Number will be
# able to represent exactly the same value as the unsigned long long.
return integer_to_javascript_value("double", value, includes)
# 3.2.5. float, https://webidl.spec.whatwg.org/#js-float
def float_to_javascript_value(value: str) -> str:
# The result of converting an IDL float value to a JavaScript value is the Number value that represents the same
# numeric value as the IDL float value.
return f"JS::Value({value})"
# 3.2.6. unrestricted float, https://webidl.spec.whatwg.org/#js-unrestricted-float
def unrestricted_float_to_javascript_value(value: str) -> str:
# 1. If the IDL unrestricted float value is a NaN, then the Number value is NaN.
# 2. Otherwise, the Number value is the one that represents the same numeric value as the IDL unrestricted float value.
return f"JS::Value({value})"
# 3.2.7. double, https://webidl.spec.whatwg.org/#js-double
def double_to_javascript_value(value: str) -> str:
# The result of converting an IDL double value to a JavaScript value is the Number value that represents the same
# numeric value as the IDL double value.
return f"JS::Value({value})"
# 3.2.8. unrestricted double, https://webidl.spec.whatwg.org/#js-unrestricted-double
def unrestricted_double_to_javascript_value(value: str) -> str:
# 1. If the IDL unrestricted double value is a NaN, then the Number value is NaN.
# 2. Otherwise, the Number value is the one that represents the same numeric value as the IDL unrestricted double value.
return f"JS::Value({value})"
# 3.2.10. DOMString, https://webidl.spec.whatwg.org/#js-DOMString
def domstring_to_javascript_value(value: str, includes: GeneratedIncludes) -> str:
includes.add("LibJS/Runtime/PrimitiveString.h")
return f"JS::PrimitiveString::create(vm, {value})"
# 3.2.11. ByteString, https://webidl.spec.whatwg.org/#js-ByteString
def bytestring_to_javascript_value(value: str, includes: GeneratedIncludes) -> str:
includes.add("LibJS/Runtime/PrimitiveString.h")
# The result of converting an IDL ByteString value to a JavaScript value is a String value whose length is the length
# of the ByteString, and the value of each element of which is the value of the corresponding element of the ByteString.
return f"JS::PrimitiveString::create(vm, {value})"
# 3.2.12. USVString, https://webidl.spec.whatwg.org/#js-USVString
def usvstring_to_javascript_value(value: str, includes: GeneratedIncludes) -> str:
includes.add("LibJS/Runtime/PrimitiveString.h")
# The result of converting an IDL USVString value S to a JavaScript value is S.
return f"JS::PrimitiveString::create(vm, {value})"
# 3.2.13. object, https://webidl.spec.whatwg.org/#js-object
def object_to_javascript_value(value: str) -> str:
# The result of converting an IDL object value to a JavaScript value is the Object value that represents a reference to
# the same object that the IDL object represents.
return f"JS::Value({value})"
# 3.2.15. Interface types, https://webidl.spec.whatwg.org/#js-interface
def interface_to_javascript_value(value: str, includes: GeneratedIncludes, interface_like_type) -> str:
includes.add(interface_like_type.implementation_header)
# FIXME: Do we need this const cast?
# The result of converting an IDL interface type value to a JavaScript value is the Object value that represents a
# reference to the same object that the IDL interface type value represents.
return f"JS::Value({value})"
# 3.2.16. Callback interface types, https://webidl.spec.whatwg.org/#js-callback-interface
def callback_interface_to_javascript_value(value: str, includes: GeneratedIncludes, interface) -> str:
# The result of converting an IDL callback interface type value to a JavaScript value is the Object value that represents
# a reference to the same object that the IDL callback interface type value represents.
includes.add(implementation_header_for_interface(interface))
return f"{value}->callback().callback"
def dictionary_member_to_javascript_conversion(
member: DictionaryMember,
dictionary_value: str,
includes: GeneratedIncludes,
context: GenerationContext,
) -> tuple[str, str]:
member_value = f"{dictionary_value}.{cpp_name(member)}"
member_type = member.type
member_exists = ""
if is_optional_without_default(member):
cpp_type = cpp_type_details(member, context)
if cpp_type.gc_ref_target_type and not member.type.nullable:
member_exists = member_value
else:
member_exists = f"{member_value}.has_value()"
member_value = f"{member_value}.value()"
if member.type.nullable and not isinstance(member.type, IDLUnionType) and not cpp_type.is_optional_presence:
member_type = member.type.without_nullable()
return member_exists, to_javascript_value(member_type, member_value, includes, context)
# 3.2.17. Dictionary types, https://webidl.spec.whatwg.org/#js-dictionary
def dictionary_to_javascript_value(
idl_type: IDLType,
value: str,
includes: GeneratedIncludes,
context: GenerationContext,
) -> str:
dictionary = context.dictionary(idl_type)
if dictionary is None:
raise RuntimeError(f"Unknown dictionary '{idl_type.name}'")
add_binding_include_for_type(idl_type, includes, context)
includes.add("LibJS/Runtime/Object.h")
generated_conversion = """[&]() -> JS::Value {
// 1. Let O be OrdinaryObjectCreate(%Object.prototype%).
auto dictionary_object = JS::Object::create(realm, realm.intrinsics().object_prototype());
"""
# 2. Let dictionaries be a list consisting of D and all of D's inherited dictionaries, in order from least to most derived.
# 3. For each dictionary dictionary in dictionaries, in order:
for dictionary_in_stack in reversed(context.dictionary_inheritance_stack(dictionary)):
# 1. For each dictionary member member declared on dictionary, in lexicographical order:
for member in dictionary_in_stack.members:
member_exists, converted_member_value = dictionary_member_to_javascript_conversion(
member, value, includes, context
)
generated_conversion += """
// 1. Let key be the identifier of member.
// 2. If V[key] exists, then:
"""
if member_exists:
generated_conversion += f" if ({member_exists}) {{\n"
generated_conversion += f"""
// 1. Let idlValue be V[key].
// 2. Let value be the result of converting idlValue to a JavaScript value.
// 3. Perform ! CreateDataPropertyOrThrow(O, key, value).
MUST(dictionary_object->create_data_property("{member.name}"_utf16_fly_string, {converted_member_value}));
"""
if member_exists:
generated_conversion += " }\n"
generated_conversion += """
// 4. Return O.
return dictionary_object;
}()"""
return generated_conversion
# 3.2.18. Enumeration types, https://webidl.spec.whatwg.org/#js-enumeration
def enumeration_to_javascript_value(
idl_type: IDLType,
value: str,
includes: GeneratedIncludes,
context: GenerationContext,
) -> str:
add_binding_include_for_type(idl_type, includes, context)
includes.add("LibJS/Runtime/PrimitiveString.h")
# The result of converting an IDL enumeration type value to a JavaScript value is the String value that represents the
# same sequence of code units as the enumeration value.
return f"JS::PrimitiveString::create(vm, idl_enum_to_string({value}))"
# 3.2.19. Callback function types, https://webidl.spec.whatwg.org/#js-callback-function
def callback_function_to_javascript_value(value: str, includes: GeneratedIncludes) -> str:
includes.add("LibWeb/WebIDL/CallbackType.h")
# The result of converting an IDL callback function type value to a JavaScript value is a reference to the same object
# that the IDL callback function type value represents.
return f"{value}->callback"
# 3.2.20. Nullable types — T?, https://webidl.spec.whatwg.org/#js-nullable-type
def nullable_to_javascript_value(
idl_type: IDLType,
value: str,
includes: GeneratedIncludes,
context: GenerationContext,
) -> str:
inner_type = idl_type.clone_with_nullable(False)
value_is_nullable_pointer = bool(cpp_type_for_idl_type_details(idl_type, context).gc_ref_target_type)
inner_value = value if value_is_nullable_pointer else f"{value}.value()"
has_value = value if value_is_nullable_pointer else f"{value}.has_value()"
return f"""[&]() -> JS::Value {{
// 1. If the IDL nullable type T? value is null, then the JavaScript value is null.
if (!{has_value})
return JS::js_null();
// 2. Otherwise, the JavaScript value is the result of converting the IDL nullable type value to the inner IDL type T.
return JS::Value({to_javascript_value(inner_type, inner_value, includes, context)});
}}()"""
# 3.2.21. Sequences — sequence<T>, https://webidl.spec.whatwg.org/#js-sequence
def sequence_to_javascript_value(
sequence_type: IDLParameterizedType,
value: str,
includes: GeneratedIncludes,
context: GenerationContext,
freeze: bool = False,
) -> str:
includes.add("LibJS/Runtime/Array.h")
element_type = sequence_type.parameters[0]
length_name = "sequence_length"
array_name = "sequence_array"
index_name = "sequence_index"
element_name = "sequence_element"
js_element_name = "js_sequence_element"
converted_element = to_javascript_value(element_type, element_name, includes, context)
freeze_array = ""
if freeze:
freeze_array = f"""
MUST({array_name}->set_integrity_level(JS::Object::IntegrityLevel::Frozen));
"""
return f"""[&]() -> JS::Value {{
// An IDL sequence<T> value S is converted to a JavaScript value as follows:
// 1. Let n be the length of S.
auto {length_name} = {value}.size();
// 2. Let A be a new Array object created as if by the expression [].
auto {array_name} = MUST(JS::Array::create(realm, {length_name}));
// 3. Initialize i to be 0.
// 4. While i < n:
for (size_t {index_name} = 0; {index_name} < {length_name}; ++{index_name}) {{
// 1. Let V be the value in S at index i.
auto& {element_name} = {value}.at({index_name});
// 2. Let E be the result of converting V to a JavaScript value.
JS::Value {js_element_name} = {converted_element};
// 3. Let P be the result of calling ! ToString(i).
// 4. Perform ! CreateDataPropertyOrThrow(A, P, E).
MUST({array_name}->create_data_property(JS::PropertyKey {{ {index_name} }}, {js_element_name}));
// 5. Set i to i + 1.
}}
{freeze_array}
// 5. Return A.
return {array_name};
}}()"""
# 3.2.23. Records — record<K, V>, https://webidl.spec.whatwg.org/#js-record
def record_to_javascript_value(
record_type: IDLParameterizedType,
value: str,
includes: GeneratedIncludes,
context: GenerationContext,
) -> str:
if len(record_type.parameters) != 2:
raise RuntimeError("Record type must have two parameters")
key_type = record_type.parameters[0]
if not is_string_type(key_type.name):
raise RuntimeError(f"Unsupported record key type '{key_type.name}'")
includes.add("AK/Utf16FlyString.h")
includes.add("LibJS/Runtime/Object.h")
value_type = record_type.parameters[1]
object_name = "record_object"
key_name = "record_key"
value_name = "record_value"
converted_value = to_javascript_value(value_type, value_name, includes, context)
return f"""[&]() -> JS::Value {{
// 1. Let result be OrdinaryObjectCreate(%Object.prototype%).
auto {object_name} = JS::Object::create(realm, realm.intrinsics().object_prototype());
// 2. For each key value of D:
for (auto const& [{key_name}, {value_name}] : {value}) {{
// 1. Let jsKey be key converted to a JavaScript value.
// 2. Let jsValue be value converted to a JavaScript value.
// 3. Let created be ! CreateDataProperty(result, jsKey, jsValue).
// 4. Assert: created is true.
MUST({object_name}->create_data_property(Utf16FlyString::from_utf8({key_name}), {converted_value}));
}}
// 3. Return result.
return {object_name};
}}()"""
# 3.2.24. Promise types — Promise<T>, https://webidl.spec.whatwg.org/#js-promise
def promise_to_javascript_value(value: str, includes: GeneratedIncludes) -> str:
includes.add("AK/TypeCasts.h")
includes.add("LibJS/Runtime/Promise.h")
includes.add("LibWeb/WebIDL/Promise.h")
# The result of converting an IDL promise type value to a JavaScript value is the value of the [[Promise]] field of the
# record that IDL promise type represents.
return f"GC::Ref {{ as<JS::Promise>(*{value}->promise()) }}"
# 3.2.25. Union types, https://webidl.spec.whatwg.org/#js-union
def union_to_javascript_value(
union_type: IDLUnionType,
value: str,
includes: GeneratedIncludes,
context: GenerationContext,
) -> str:
includes.add("AK/Variant.h")
# An IDL union type value is converted to a JavaScript value according to the rules for converting the specific type of the
# IDL union type value as described in this section (§3.2 JavaScript type mapping).
conversions = []
for index, member_type in enumerate(union_type.flattened_member_types()):
if member_type.name == "undefined":
continue
inner_type = member_type.clone_with_nullable(False)
visited_value = f"visited_union_value{index}"
visited_cpp_type = cpp_type_for_idl_type(inner_type, context)
converted_value = to_javascript_value(inner_type, visited_value, includes, context)
conversions.append(
f""" [&]({visited_cpp_type} const& {visited_value}) -> JS::Value
{{
return {converted_value};
}}"""
)
if union_type.includes_nullable_type():
conversions.append(
""" [](Empty) -> JS::Value
{
return JS::js_null();
}"""
)
elif union_type.includes_undefined():
conversions.append(
""" [](Empty) -> JS::Value
{
return JS::js_undefined();
}"""
)
joined_conversions = ",\n".join(conversions)
return f"""{value}.visit(
{joined_conversions}
)"""
# 3.2.26. Buffer source types, https://webidl.spec.whatwg.org/#js-buffer-source-types
def buffer_source_to_javascript_value(idl_type: IDLType, value: str, includes: GeneratedIncludes) -> str:
add_buffer_source_type_include(idl_type, includes)
# The result of converting an IDL value of any buffer source type to a JavaScript value is the Object value that
# represents a reference to the same object that the IDL value represents.
return f"JS::Value({value})"
# 3.2.27. Frozen arrays — FrozenArray<T>, https://webidl.spec.whatwg.org/#js-frozen-array
def frozen_array_to_javascript_value(
frozen_array_type: IDLParameterizedType,
value: str,
includes: GeneratedIncludes,
context: GenerationContext,
) -> str:
# The result of converting an IDL FrozenArray<T> value to a JavaScript value is the Object value that represents a
# reference to the same object that the IDL FrozenArray<T> represents.
return sequence_to_javascript_value(frozen_array_type, value, includes, context, freeze=True)
def to_javascript_value(
idl_type: IDLType,
value: str,
includes: GeneratedIncludes,
context: GenerationContext,
) -> str:
includes.add("LibJS/Runtime/Value.h")
type_name = idl_type.name
if isinstance(idl_type, IDLUnionType):
return union_to_javascript_value(idl_type, value, includes, context)
if idl_type.nullable:
return nullable_to_javascript_value(idl_type, value, includes, context)
if type_name == "undefined":
return undefined_to_javascript_value()
if type_name == "any":
return any_to_javascript_value(value)
if type_name == "object":
return object_to_javascript_value(value)
if is_buffer_source_type(idl_type):
return buffer_source_to_javascript_value(idl_type, value, includes)
if type_name == "boolean":
return boolean_to_javascript_value(value)
if type_name == "byte":
return byte_to_javascript_value(value, includes)
if type_name == "octet":
return octet_to_javascript_value(value, includes)
if type_name == "short":
return short_to_javascript_value(value, includes)
if type_name == "unsigned short":
return unsigned_short_to_javascript_value(value, includes)
if type_name == "long":
return long_to_javascript_value(value, includes)
if type_name == "unsigned long":
return unsigned_long_to_javascript_value(value, includes)
if type_name == "long long":
return long_long_to_javascript_value(value, includes)
if type_name == "unsigned long long":
return unsigned_long_long_to_javascript_value(value, includes)
if type_name == "float":
return float_to_javascript_value(value)
if type_name == "unrestricted float":
return unrestricted_float_to_javascript_value(value)
if type_name == "double":
return double_to_javascript_value(value)
if type_name == "unrestricted double":
return unrestricted_double_to_javascript_value(value)
if type_name in ("DOMString", "Utf16DOMString"):
return domstring_to_javascript_value(value, includes)
if type_name in ("ByteString",):
return bytestring_to_javascript_value(value, includes)
if type_name in ("USVString", "Utf16USVString"):
return usvstring_to_javascript_value(value, includes)
if context.enumeration(idl_type) is not None:
return enumeration_to_javascript_value(idl_type, value, includes, context)
if context.dictionary(idl_type) is not None:
return dictionary_to_javascript_value(idl_type, value, includes, context)
if type_name == "Promise":
return promise_to_javascript_value(value, includes)
interface_like_type = interface_like_type_for_idl_type(idl_type, context)
if interface_like_type is not None:
return interface_to_javascript_value(value, includes, interface_like_type)
interface = context.interface(idl_type)
if interface is not None and interface.is_callback_interface:
return callback_interface_to_javascript_value(value, includes, interface)
if context.callback_function(idl_type) is not None:
return callback_function_to_javascript_value(value, includes)
if isinstance(idl_type, IDLParameterizedType) and type_name == "sequence":
return sequence_to_javascript_value(idl_type, value, includes, context)
if isinstance(idl_type, IDLParameterizedType) and type_name == "record":
return record_to_javascript_value(idl_type, value, includes, context)
if isinstance(idl_type, IDLParameterizedType) and type_name == "FrozenArray":
return frozen_array_to_javascript_value(idl_type, value, includes, context)
return unsupported_to_javascript_value(idl_type)

View file

@ -63,6 +63,36 @@ def title_casify(dashy_name: str) -> str:
return "".join(part[0].upper() + part[1:] for part in dashy_name.split("-") if part)
def string_to_cpp_enum_name(value: str) -> str:
if not value:
return "Empty"
def title_case_words(text: str) -> str:
result = ""
word = ""
for ch in text:
if ch.isalnum():
word += ch
elif word:
result += word[0].upper() + word[1:].lower()
word = ""
if word:
result += word[0].upper() + word[1:].lower()
return result
name = ""
for i, slash_segment in enumerate(value.split("/")):
combined = "".join(title_case_words(s) for s in slash_segment.replace(".", "+").split("+"))
if combined:
name += ("_" if i > 0 else "") + combined
if not name:
return "Empty"
if name[0].isdigit():
name = f"_{name}"
return make_name_acceptable_cpp(name)
def camel_casify(dashy_name: str) -> str:
parts = [part for part in dashy_name.split("-") if part]
if not parts:
@ -80,6 +110,18 @@ def snake_casify(dashy_name: str, trim_leading_underscores: bool = False) -> str
return snake_case
def title_case_to_snake_case(value: str) -> str:
parts = []
for index, character in enumerate(value):
if character.isupper() and index > 0:
previous_character = value[index - 1]
next_character = value[index + 1] if index + 1 < len(value) else ""
if previous_character.islower() or next_character.islower():
parts.append("_")
parts.append(character.lower())
return "".join(parts)
def underlying_type_for_enum(member_count: int) -> str:
if member_count <= 0xFF:
return "u8"