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:
parent
5c169091de
commit
dba62aefc0
25 changed files with 8119 additions and 230 deletions
|
|
@ -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()
|
||||
|
|
|
|||
270
Meta/Generators/generate_libweb_bindings.py
Normal file
270
Meta/Generators/generate_libweb_bindings.py
Normal 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())
|
||||
3
Meta/Generators/libweb_bindings/__init__.py
Normal file
3
Meta/Generators/libweb_bindings/__init__.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Copyright (c) 2026-present, the Ladybird developers.
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
114
Meta/Generators/libweb_bindings/arguments.py
Normal file
114
Meta/Generators/libweb_bindings/arguments.py
Normal 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));
|
||||
}}
|
||||
}}
|
||||
|
||||
"""
|
||||
)
|
||||
712
Meta/Generators/libweb_bindings/attributes.py
Normal file
712
Meta/Generators/libweb_bindings/attributes.py
Normal 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 attr’s 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 attr’s type is an observable array type with type argument T, then set target’s backing observable array exotic object for attr to the result of creating an observable array exotic object in realm, given T, attr’s set an indexed value algorithm, and attr’s 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 attribute’s 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 attribute’s 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 realm’s 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, attribute’s 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 attribute’s 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)};
|
||||
}}
|
||||
|
||||
""")
|
||||
65
Meta/Generators/libweb_bindings/callback_interfaces.py
Normal file
65
Meta/Generators/libweb_bindings/callback_interfaces.py
Normal 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());
|
||||
}}
|
||||
|
||||
"""
|
||||
)
|
||||
35
Meta/Generators/libweb_bindings/constants.py
Normal file
35
Meta/Generators/libweb_bindings/constants.py
Normal 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 const’s IDL value to a JavaScript value.
|
||||
// 3. Let desc be the PropertyDescriptor{{[[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false, [[Value]]: value}}.
|
||||
// 4. Let id be const’s 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);
|
||||
"""
|
||||
)
|
||||
259
Meta/Generators/libweb_bindings/constructors.py
Normal file
259
Meta/Generators/libweb_bindings/constructors.py
Normal 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;
|
||||
"""
|
||||
)
|
||||
380
Meta/Generators/libweb_bindings/context.py
Normal file
380
Meta/Generators/libweb_bindings/context.py
Normal 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
|
||||
]
|
||||
583
Meta/Generators/libweb_bindings/cpp_types.py
Normal file
583
Meta/Generators/libweb_bindings/cpp_types.py
Normal 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)
|
||||
183
Meta/Generators/libweb_bindings/default_values.py
Normal file
183
Meta/Generators/libweb_bindings/default_values.py
Normal 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}'")
|
||||
60
Meta/Generators/libweb_bindings/extended_attributes.py
Normal file
60
Meta/Generators/libweb_bindings/extended_attributes.py
Normal 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();
|
||||
}}()"""
|
||||
151
Meta/Generators/libweb_bindings/global_mixins.py
Normal file
151
Meta/Generators/libweb_bindings/global_mixins.py
Normal 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")
|
||||
28
Meta/Generators/libweb_bindings/includes.py
Normal file
28
Meta/Generators/libweb_bindings/includes.py
Normal 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")
|
||||
185
Meta/Generators/libweb_bindings/interface_declaration.py
Normal file
185
Meta/Generators/libweb_bindings/interface_declaration.py
Normal 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)
|
||||
279
Meta/Generators/libweb_bindings/interfaces.py
Normal file
279
Meta/Generators/libweb_bindings/interfaces.py
Normal 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)
|
||||
|
|
@ -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())
|
||||
870
Meta/Generators/libweb_bindings/iterables.py
Normal file
870
Meta/Generators/libweb_bindings/iterables.py
Normal 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 map’s 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 read–write maplike declaration,
|
||||
# then there must exist a set data property on A’s 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 read–write maplike declaration,
|
||||
# then there must exist a delete data property on A’s 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 read–write maplike declaration,
|
||||
# then there must exist a clear data property on A’s 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 set’s 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 read–write setlike declaration,
|
||||
# then there must exist an add data property on A’s 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 read–write setlike declaration,
|
||||
# then there must exist a delete data property on A’s 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 O’s 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 read–write setlike declaration,
|
||||
# then there must exist a clear data property on A’s 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();
|
||||
}}
|
||||
|
||||
""")
|
||||
459
Meta/Generators/libweb_bindings/named_and_indexed_properties.py
Normal file
459
Meta/Generators/libweb_bindings/named_and_indexed_properties.py
Normal 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 O’s associated realm’s 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}
|
||||
}}
|
||||
|
||||
"""
|
||||
)
|
||||
150
Meta/Generators/libweb_bindings/namespaces.py
Normal file
150
Meta/Generators/libweb_bindings/namespaces.py
Normal 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 namespace’s 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 interface’s 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);
|
||||
}}
|
||||
|
||||
"""
|
||||
)
|
||||
555
Meta/Generators/libweb_bindings/operations.py
Normal file
555
Meta/Generators/libweb_bindings/operations.py
Normal 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}");
|
||||
|
||||
"""
|
||||
)
|
||||
529
Meta/Generators/libweb_bindings/overload_resolution.py
Normal file
529
Meta/Generators/libweb_bindings/overload_resolution.py
Normal 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
|
||||
1488
Meta/Generators/libweb_bindings/to_idl_value.py
Normal file
1488
Meta/Generators/libweb_bindings/to_idl_value.py
Normal file
File diff suppressed because it is too large
Load diff
626
Meta/Generators/libweb_bindings/to_js_value.py
Normal file
626
Meta/Generators/libweb_bindings/to_js_value.py
Normal 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)
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in a new issue