From efe144552ceffe253b28a0193d9ea1f32b38a1ba Mon Sep 17 00:00:00 2001 From: Shannon Booth Date: Sat, 6 Jun 2026 20:15:23 +0200 Subject: [PATCH] Meta: Remove obsolete LibWeb BindingsGenerator Remove the old C++ LibWeb bindings generator now that the build uses the Python generator. --- .github/CODEOWNERS | 1 - AK/Debug.h.in | 4 - Meta/CMake/all_the_debug_macros.cmake | 2 - Meta/Lagom/Tools/CMakeLists.txt | 2 - .../Lagom/Tools/CodeGenerators/CMakeLists.txt | 1 - .../LibWeb/BindingsGenerator/CMakeLists.txt | 7 - .../BindingsGenerator/IDLGenerators.cpp | 7209 ----------------- .../LibWeb/BindingsGenerator/IDLGenerators.h | 27 - .../LibWeb/BindingsGenerator/main.cpp | 175 - .../CodeGenerators/LibWeb/CMakeLists.txt | 3 - 10 files changed, 7431 deletions(-) delete mode 100644 Meta/Lagom/Tools/CodeGenerators/CMakeLists.txt delete mode 100644 Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/CMakeLists.txt delete mode 100644 Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp delete mode 100644 Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.h delete mode 100644 Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/main.cpp delete mode 100644 Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9da89517a9..1197598d31 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,4 +1,3 @@ -/Lagom/Tools/CodeGenerators/LibWeb @AtkinsSJ /Libraries/LibCrypto @alimpfard /Libraries/LibHTTP @alimpfard /Libraries/LibJS/Runtime/Intl @trflynn89 diff --git a/AK/Debug.h.in b/AK/Debug.h.in index 84b4aab34c..4d27c8157b 100644 --- a/AK/Debug.h.in +++ b/AK/Debug.h.in @@ -18,10 +18,6 @@ # cmakedefine01 BMP_DEBUG #endif -#ifndef BINDINGS_GENERATOR_DEBUG -# cmakedefine01 BINDINGS_GENERATOR_DEBUG -#endif - #ifndef CACHE_DEBUG # cmakedefine01 CACHE_DEBUG #endif diff --git a/Meta/CMake/all_the_debug_macros.cmake b/Meta/CMake/all_the_debug_macros.cmake index d591a2e572..055b7b48bf 100644 --- a/Meta/CMake/all_the_debug_macros.cmake +++ b/Meta/CMake/all_the_debug_macros.cmake @@ -80,8 +80,6 @@ set(XML_PARSER_DEBUG ON) # False positive: ANDROID_LOG_DEBUG is a log level, not a debug flag # set(ANDROID_LOG_DEBUG ON) -# Clogs up build: The BindingsGenerator stuff is run at compile time. -# set(BINDINGS_GENERATOR_DEBUG ON) # Third-party: skia vcpkg port overlay # set(gn_defines_DEBUG ON) # set(gn_include_dirs_DEBUG ON) diff --git a/Meta/Lagom/Tools/CMakeLists.txt b/Meta/Lagom/Tools/CMakeLists.txt index db36c13b4f..0462a4083d 100644 --- a/Meta/Lagom/Tools/CMakeLists.txt +++ b/Meta/Lagom/Tools/CMakeLists.txt @@ -15,5 +15,3 @@ function(lagom_tool tool) ) endif() endfunction() - -add_subdirectory(CodeGenerators) diff --git a/Meta/Lagom/Tools/CodeGenerators/CMakeLists.txt b/Meta/Lagom/Tools/CodeGenerators/CMakeLists.txt deleted file mode 100644 index 972df10014..0000000000 --- a/Meta/Lagom/Tools/CodeGenerators/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -add_subdirectory(LibWeb) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/CMakeLists.txt b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/CMakeLists.txt deleted file mode 100644 index 98e74e4583..0000000000 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -set(SOURCES - IDLGenerators.cpp - main.cpp -) - -lagom_tool(BindingsGenerator LIBS LibIDL LibMain) -target_compile_options(BindingsGenerator PUBLIC -g) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp deleted file mode 100644 index a316418043..0000000000 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp +++ /dev/null @@ -1,7209 +0,0 @@ -/* - * Copyright (c) 2020-2023, Andreas Kling - * Copyright (c) 2021-2023, Linus Groh - * Copyright (c) 2021-2025, Luke Wilde - * Copyright (c) 2022, Ali Mohammad Pur - * Copyright (c) 2023-2024, Kenneth Myhra - * Copyright (c) 2023-2026, Shannon Booth - * Copyright (c) 2023-2024, Matthew Olsson - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "IDLGenerators.h" -#include -#include -#include -#include -#include -#include -#include - -namespace IDL { - -Vector g_header_search_paths; - -enum class TypeOptionality { - Required, - OptionalArgument, - OptionalDictionaryMember, -}; - -struct GeneratedIncludes; - -template -static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter, ByteString const& js_name, ByteString const& js_suffix, ByteString const& cpp_name, Context const&, GeneratedIncludes&, bool optional = false, Optional optional_default_value = {}, bool variadic = false, size_t recursion_depth = 0, TypeOptionality = TypeOptionality::OptionalArgument); -static bool dictionary_member_needs_builder(Context const& context, DictionaryMember const& member); - -enum class ContainedStorageType { - Vector, // Used to store values that do not need GC-aware storage. - ConservativeVector, // Used to conservatively root aggregate values that may contain GC pointers. - RootVector, // Used to root GC values directly. -}; - -struct CppType { - ByteString name; - bool is_nullable { false }; - bool is_optional_presence { false }; - ContainedStorageType contained_storage_type { ContainedStorageType::Vector }; - ByteString gc_ref_target_type; -}; - -static CppType make_cpp_type(ByteString name, ContainedStorageType contained_storage_type = ContainedStorageType::Vector) -{ - CppType cpp_type; - cpp_type.name = move(name); - cpp_type.contained_storage_type = contained_storage_type; - return cpp_type; -} - -// https://webidl.spec.whatwg.org/#dfn-platform-object -static bool is_platform_object(Context const& context, Type const& type) -{ - // Platform objects are objects that implement an interface. - // NB: WindowProxy is a special case as it is not defined over IDL, but implements the Window interface. - return context.interfaces.contains(type.name()) || type.name() == "WindowProxy"sv; -} - -static ByteString interface_cpp_type_name(Interface const& interface) -{ - if (!interface.fully_qualified_name.is_empty()) - return interface.fully_qualified_name; - return interface.implemented_name; -} - -static ByteString interface_cpp_type_name(Context const& context, Type const& type) -{ - if (type.name() == "WindowProxy"sv) - return "HTML::WindowProxy"; - - auto interface = context.interfaces.get(type.name()); - if (interface.has_value()) - return interface_cpp_type_name(**interface); - - return type.name(); -} - -static ByteString cpp_type_name(Type const& type, Context const& context) -{ - if (is_platform_object(context, type)) - return interface_cpp_type_name(context, type); - if (type.is_buffer_source()) - return ByteString::formatted("JS::{}", type.name()); - return type.name(); -} - -static Interface const* callback_interface_for_type(Context const& context, Type const& type) -{ - if (auto it = context.interfaces.find(type.name()); it != context.interfaces.end() && it->value->is_callback_interface) - return it->value; - return nullptr; -} - -enum class TypeTraversalMode { - TypeStructureOnly, - ExpandDictionariesAndCallbacks, -}; - -template -static void for_each_type_reference(Context const& context, Type const& type, TypeTraversalMode traversal_mode, Callback&& callback) -{ - callback(type); - - if (traversal_mode == TypeTraversalMode::ExpandDictionariesAndCallbacks) { - if (auto dictionary = context.dictionaries.find(type.name()); dictionary != context.dictionaries.end()) { - if (!dictionary->value.parent_name.is_empty()) { - auto parent_type = adopt_ref(*new Type(dictionary->value.parent_name, false)); - for_each_type_reference(context, *parent_type, traversal_mode, callback); - } - - for (auto const& member : dictionary->value.members) - for_each_type_reference(context, *member.type, traversal_mode, callback); - } - - if (auto callback_function = context.callback_functions.find(type.name()); callback_function != context.callback_functions.end()) { - for_each_type_reference(context, *callback_function->value.return_type, traversal_mode, callback); - for (auto const& parameter : callback_function->value.parameters) - for_each_type_reference(context, *parameter.type, traversal_mode, callback); - } - } - - if (type.is_parameterized()) { - for (auto const& parameter : type.as_parameterized().parameters()) - for_each_type_reference(context, *parameter, traversal_mode, callback); - return; - } - - if (type.is_union()) { - for (auto const& member_type : type.as_union().member_types()) - for_each_type_reference(context, *member_type, traversal_mode, callback); - } -} - -static StringView contained_storage_type_to_cpp_name(ContainedStorageType contained_storage_type) -{ - switch (contained_storage_type) { - case ContainedStorageType::Vector: - return "Vector"sv; - case ContainedStorageType::ConservativeVector: - return "GC::ConservativeVector"sv; - case ContainedStorageType::RootVector: - return "GC::RootVector"sv; - default: - VERIFY_NOT_REACHED(); - } -} - -static bool is_nullable_frozen_array_of_single_type(Type const& type, StringView type_name) -{ - if (!type.is_nullable() || type.name() != "FrozenArray"sv) - return false; - - auto const& parameters = type.as_parameterized().parameters(); - if (parameters.size() != 1) - return false; - - return parameters.first()->name() == type_name; -} - -static CppType cpp_type_for_idl_type(Context const& context, Type const& type, TypeOptionality, Optional const&> extended_attributes = {}); - -static ByteString union_type_to_variant(UnionType const& union_type, Context const& context) -{ - StringBuilder builder; - builder.append("Variant<"sv); - - auto flattened_types = union_type.flattened_member_types(); - for (size_t type_index = 0; type_index < flattened_types.size(); ++type_index) { - auto& type = flattened_types.at(type_index); - - if (type_index > 0) - builder.append(", "sv); - - auto cpp_type = cpp_type_for_idl_type(context, type, TypeOptionality::Required); - builder.append(cpp_type.name); - } - - if (union_type.includes_undefined() || union_type.includes_nullable_type()) - builder.append(", Empty"sv); - - builder.append('>'); - return builder.to_byte_string(); -} - -static bool type_contains_gc_like_value(Context const& context, Type const& type) -{ - bool contains_gc_like_value = false; - for_each_type_reference(context, type, TypeTraversalMode::ExpandDictionariesAndCallbacks, [&](Type const& referenced_type) { - if (is_platform_object(context, referenced_type) - || referenced_type.is_buffer_source() - || callback_interface_for_type(context, referenced_type) - || context.callback_functions.contains(referenced_type.name()) - || referenced_type.name().is_one_of("any"sv, "object"sv, "Promise"sv)) { - contains_gc_like_value = true; - } - }); - return contains_gc_like_value; -} - -static ContainedStorageType contained_storage_type_for_aggregate_type(Context const& context, Type const& type) -{ - return type_contains_gc_like_value(context, type) ? ContainedStorageType::ConservativeVector : ContainedStorageType::Vector; -} - -static CppType gc_ref_type(ByteString referent_type) -{ - auto type = make_cpp_type(ByteString::formatted("GC::Ref<{}>", referent_type), ContainedStorageType::RootVector); - type.gc_ref_target_type = move(referent_type); - return type; -} - -static CppType gc_ptr_type(ByteString referent_type) -{ - auto type = make_cpp_type(ByteString::formatted("GC::Ptr<{}>", referent_type), ContainedStorageType::RootVector); - type.is_nullable = true; - type.gc_ref_target_type = move(referent_type); - return type; -} - -static bool is_direct_gc_ref_cpp_type(CppType const& cpp_type) -{ - return !cpp_type.gc_ref_target_type.is_empty() && !cpp_type.is_nullable; -} - -static CppType cpp_type_for_non_nullable_idl_type(Context const& context, Type const& type, Optional const&> extended_attributes) -{ - VERIFY(!type.is_nullable() || is(type)); - - if (is_platform_object(context, type)) - return gc_ref_type(interface_cpp_type_name(context, type)); - - if (type.is_buffer_source()) - return gc_ref_type(ByteString::formatted("JS::{}", type.name())); - - if (auto const* callback_interface = callback_interface_for_type(context, type)) - return gc_ref_type(interface_cpp_type_name(*callback_interface)); - - if (context.callback_functions.contains(type.name())) - return gc_ref_type("WebIDL::CallbackType"sv); - - if (type.is_string()) { - auto is_fly_string = extended_attributes.has_value() && extended_attributes->contains("FlyString"sv); - if (type.name().contains("Utf16"sv)) - return make_cpp_type(is_fly_string ? "Utf16FlyString"sv : "Utf16String"sv); - return make_cpp_type(is_fly_string ? "FlyString"sv : "String"sv); - } - - if (type.name() == "double" || type.name() == "unrestricted double") - return make_cpp_type("double"sv); - - if (type.name() == "float" || type.name() == "unrestricted float") - return make_cpp_type("float"sv); - - if (type.name() == "boolean") - return make_cpp_type("bool"sv); - - if (type.name() == "byte") - return make_cpp_type("WebIDL::Byte"sv); - - if (type.name() == "octet") - return make_cpp_type("WebIDL::Octet"sv); - - if (type.name() == "short") - return make_cpp_type("WebIDL::Short"sv); - - if (type.name() == "unsigned short") - return make_cpp_type("WebIDL::UnsignedShort"sv); - - if (type.name() == "long") - return make_cpp_type("WebIDL::Long"sv); - - if (type.name() == "unsigned long") - return make_cpp_type("WebIDL::UnsignedLong"sv); - - if (type.name() == "long long") - return make_cpp_type("WebIDL::LongLong"sv); - - if (type.name() == "unsigned long long") - return make_cpp_type("WebIDL::UnsignedLongLong"sv); - - if (type.name() == "any") - return make_cpp_type("JS::Value"sv, ContainedStorageType::RootVector); - - // NOTE: undefined is a somewhat special case that may be used in a union to represent the javascript 'undefined' (and - // only ever js_undefined). Therefore, we say that the type is Empty here, so that a union of (T, undefined) is - // generated as Variant, which is then returned in the Variant's visit as undefined if it is Empty. - if (type.name() == "undefined") - return make_cpp_type("Empty"sv); - - if (type.name() == "object") - return gc_ref_type("JS::Object"sv); - - if (type.name() == "Promise") - return gc_ref_type("WebIDL::Promise"sv); - - if (type.name().is_one_of("sequence"sv, "FrozenArray"sv)) { - auto& parameterized_type = as(type); - auto& sequence_type = parameterized_type.parameters().first(); - auto sequence_cpp_type = cpp_type_for_idl_type(context, sequence_type, TypeOptionality::Required); - auto storage_type_name = contained_storage_type_to_cpp_name(sequence_cpp_type.contained_storage_type); - return make_cpp_type(ByteString::formatted("{}<{}>", storage_type_name, sequence_cpp_type.name)); - } - - if (type.name() == "record") { - auto& parameterized_type = as(type); - auto& record_key_type = parameterized_type.parameters()[0]; - auto& record_value_type = parameterized_type.parameters()[1]; - auto record_key_cpp_type = cpp_type_for_idl_type(context, record_key_type, TypeOptionality::Required); - auto record_value_cpp_type = cpp_type_for_idl_type(context, record_value_type, TypeOptionality::Required); - - if (record_key_cpp_type.contained_storage_type == ContainedStorageType::ConservativeVector || record_value_cpp_type.contained_storage_type == ContainedStorageType::ConservativeVector) - return make_cpp_type(ByteString::formatted("GC::ConservativeHashMap<{}, {}>", record_key_cpp_type.name, record_value_cpp_type.name)); - - if (record_key_cpp_type.contained_storage_type == ContainedStorageType::RootVector || record_value_cpp_type.contained_storage_type == ContainedStorageType::RootVector) - return make_cpp_type(ByteString::formatted("GC::OrderedRootHashMap<{}, {}>", record_key_cpp_type.name, record_value_cpp_type.name)); - - return make_cpp_type(ByteString::formatted("OrderedHashMap<{}, {}>", record_key_cpp_type.name, record_value_cpp_type.name)); - } - - if (is(type)) { - auto& union_type = as(type); - auto cpp_type = make_cpp_type(union_type_to_variant(union_type, context), contained_storage_type_for_aggregate_type(context, type)); - cpp_type.is_nullable = union_type.includes_undefined() || union_type.includes_nullable_type(); - return cpp_type; - } - - if (context.dictionaries.contains(type.name())) - return make_cpp_type(type.name(), contained_storage_type_for_aggregate_type(context, type)); - - if (context.enumerations.contains(type.name())) - return make_cpp_type(type.name()); - - dbgln("Unimplemented type for idl_type_name_to_cpp_type: {}", type.name()); - TODO(); -} - -static CppType with_nullable_cpp_type(CppType cpp_type) -{ - if (cpp_type.name == "JS::Value"sv) { - cpp_type.is_nullable = true; - return cpp_type; - } - - if (!cpp_type.gc_ref_target_type.is_empty()) - return gc_ptr_type(move(cpp_type.gc_ref_target_type)); - - auto nullable_cpp_type = make_cpp_type(ByteString::formatted("Optional<{}>", cpp_type.name)); - nullable_cpp_type.is_nullable = true; - return nullable_cpp_type; -} - -static CppType with_optional_cpp_type(CppType cpp_type) -{ - if (is_direct_gc_ref_cpp_type(cpp_type)) { - cpp_type.name = ByteString::formatted("GC::Ptr<{}>", cpp_type.gc_ref_target_type); - cpp_type.is_nullable = true; - cpp_type.is_optional_presence = true; - cpp_type.contained_storage_type = ContainedStorageType::RootVector; - return cpp_type; - } - - cpp_type.name = ByteString::formatted("Optional<{}>", cpp_type.name); - cpp_type.is_optional_presence = true; - cpp_type.contained_storage_type = ContainedStorageType::Vector; - return cpp_type; -} - -static CppType cpp_type_for_idl_type(Context const& context, Type const& type, TypeOptionality presence, Optional const&> extended_attributes) -{ - auto cpp_type = [&] { - if (!type.is_nullable() || is(type)) - return cpp_type_for_non_nullable_idl_type(context, type, extended_attributes); - - auto inner_type = clone_type(type, false); - return with_nullable_cpp_type(cpp_type_for_non_nullable_idl_type(context, *inner_type, extended_attributes)); - }(); - - if (presence == TypeOptionality::Required || (presence == TypeOptionality::OptionalArgument && cpp_type.is_nullable)) - return cpp_type; - - return with_optional_cpp_type(move(cpp_type)); -} - -static CppType idl_type_name_to_cpp_type(Type const& type, Context const& context, Optional const&> extended_attributes = {}) -{ - return cpp_type_for_idl_type(context, type, TypeOptionality::Required, extended_attributes); -} - -static ByteString make_input_acceptable_cpp(ByteString const& input) -{ - if (input.is_one_of( - "break", - "char", - "class", - "continue", - "default", - "delete", - "for", - "initialize", - "inline", - "mutable", - "namespace", - "operator", - "register", - "switch", - "template")) { - StringBuilder builder; - builder.append(input); - builder.append('_'); - return builder.to_byte_string(); - } - - return input.replace("-"sv, "_"sv, ReplaceMode::All); -} - -static ByteString idl_value_conversion_function_name(ByteString const& type_name) -{ - return ByteString::formatted("convert_to_idl_value_for_{}", make_input_acceptable_cpp(type_name.to_snakecase())); -} - -static ByteString relative_module_path(ByteString const& module_own_path) -{ - auto path_string = module_own_path; - for (auto& search_path : g_header_search_paths) { - if (!module_own_path.starts_with(search_path)) - continue; - auto relative_path = *LexicalPath::relative_path(module_own_path, search_path); - if (relative_path.length() < path_string.length()) - path_string = relative_path; - } - - return path_string; -} - -static ByteString header_for_source_module(Module const& module) -{ - VERIFY(module.interface.has_value()); - - auto path_string = relative_module_path(module.module_own_path); - LexicalPath include_path { path_string }; - ByteString include_title = include_path.title().to_byte_string(); - if (!module.interface->implemented_name.is_empty()) - include_title = module.interface->implemented_name; - return ByteString::formatted("{}/{}.h", include_path.dirname(), include_title); -} - -static Module const& module_for_path(Context const& context, ByteString const& module_own_path) -{ - for (auto const& module : context.owned_modules) { - if (module->module_own_path == module_own_path) - return *module; - } - - VERIFY_NOT_REACHED(); -} - -struct GeneratedIncludes { - void set_bindings_module_to_skip(ByteString const& module_own_path) - { - bindings_module_own_path_to_skip = module_own_path; - } - - void add_header(ByteString header) - { - if (header_paths.set(header) != AK::HashSetResult::InsertedNewEntry) - return; - headers.append(move(header)); - } - - void add_header(StringView header) - { - add_header(ByteString { header }); - } - - void add_source_module(Module const& module) - { - add_header(header_for_source_module(module)); - } - - void add_bindings_module(Module const& module) - { - if (bindings_module_own_path_to_skip.has_value() && module.module_own_path == *bindings_module_own_path_to_skip) - return; - - add_header(ByteString::formatted("LibWeb/Bindings/{}.h", LexicalPath { module.module_own_path }.title())); - } - - void add_dictionary_dependency(Context const& context, ByteString const& dictionary_name) - { - auto it = context.dictionaries.find(dictionary_name); - if (it == context.dictionaries.end()) - return; - add_bindings_module(module_for_path(context, it->value.module_own_path)); - } - - void add_enumeration_dependency(Context const& context, ByteString const& enumeration_name) - { - auto it = context.enumerations.find(enumeration_name); - if (it == context.enumerations.end()) - return; - add_bindings_module(module_for_path(context, it->value.module_own_path)); - } - - void add_iterator(StringView iterator_path) - { - add_header(ByteString::formatted("LibWeb/{}.h", iterator_path)); - } - - void emit_headers(SourceGenerator& generator) - { - quick_sort(headers, [](auto const& a, auto const& b) { - return a < b; - }); - - for (auto const& header : headers) { - auto forked_generator = generator.fork(); - forked_generator.set("include.path", header); - forked_generator.appendln("#include <@include.path@>"); - } - } - - Vector headers; - HashTable header_paths; - Optional bindings_module_own_path_to_skip; -}; - -static void add_javascript_builtin_buffer_source_type_include(Type const& type, GeneratedIncludes& includes) -{ - VERIFY(type.is_buffer_source()); - - if (type.name() == "DataView"sv) - includes.add_header("LibJS/Runtime/DataView.h"sv); - else if (type.name().is_one_of("ArrayBuffer"sv, "SharedArrayBuffer"sv)) - includes.add_header("LibJS/Runtime/ArrayBuffer.h"sv); - else - includes.add_header("LibJS/Runtime/TypedArray.h"sv); -} - -static void collect_include_dependencies(Context const& context, Type const& type, GeneratedIncludes& includes); - -static void collect_include_dependencies(Context const& context, Vector const& parameters, GeneratedIncludes& includes) -{ - for (auto const& parameter : parameters) - collect_include_dependencies(context, *parameter.type, includes); -} - -static void collect_include_dependencies(Context const& context, Function const& function, GeneratedIncludes& includes) -{ - collect_include_dependencies(context, *function.return_type, includes); - collect_include_dependencies(context, function.parameters, includes); -} - -static void collect_include_dependencies(Context const& context, Type const& type, GeneratedIncludes& includes) -{ - for_each_type_reference(context, type, TypeTraversalMode::ExpandDictionariesAndCallbacks, [&](Type const& referenced_type) { - if (referenced_type.name() == "WindowProxy"sv) - includes.add_header("LibWeb/HTML/WindowProxy.h"sv); - - if (auto referenced_interface = context.interfaces.get(referenced_type.name()); referenced_interface.has_value()) - includes.add_source_module(module_for_path(context, referenced_interface.value()->module_own_path)); - - includes.add_dictionary_dependency(context, referenced_type.name()); - includes.add_enumeration_dependency(context, referenced_type.name()); - }); -} - -static bool nullable_callback_function_treats_non_object_as_null(Context const& context, Type const& type) -{ - auto callback_function = context.callback_functions.find(type.name()); - if (callback_function == context.callback_functions.end()) - return false; - - return callback_function->value.is_legacy_treat_non_object_as_null; -} - -static ByteString optional_string_default_value_expression(ByteString const& cpp_type, ByteString const& default_value) -{ - if (cpp_type == "String"sv) - return default_value == "\"\""sv ? ByteString { "String {}"sv } : ByteString::formatted("{}_string", default_value); - if (cpp_type == "FlyString"sv) - return default_value == "\"\""sv ? ByteString { "FlyString {}"sv } : ByteString::formatted("FlyString::from_utf8_without_validation({}sv)", default_value); - if (cpp_type == "ByteString"sv) - return default_value == "\"\""sv ? ByteString { "ByteString {}"sv } : ByteString::formatted("ByteString {{ {}sv }}", default_value); - if (cpp_type == "Utf16String"sv) - return default_value == "\"\""sv ? ByteString { "Utf16String {}"sv } : ByteString::formatted("Utf16String::from_utf8_without_validation({}sv)", default_value); - if (cpp_type == "Utf16FlyString"sv) - return default_value == "\"\""sv ? ByteString { "Utf16FlyString {}"sv } : ByteString::formatted("Utf16FlyString::from_utf8_without_validation({}sv)", default_value); - - TODO(); -} - -static TypeOptionality dictionary_member_presence(DictionaryMember const& member) -{ - return !member.required && !member.default_value.has_value() - ? TypeOptionality::OptionalDictionaryMember - : TypeOptionality::Required; -} - -static CppType cpp_type_for_dictionary_member(Context const& context, DictionaryMember const& member) -{ - return cpp_type_for_idl_type(context, *member.type, dictionary_member_presence(member), member.extended_attributes); -} - -static bool dictionary_member_uses_optional(Context const& context, DictionaryMember const& member) -{ - return cpp_type_for_dictionary_member(context, member).is_optional_presence; -} - -static bool dictionary_needs_builder_for_default_construction(Context const& context, ByteString const& dictionary_name) -{ - auto dictionary = context.dictionaries.find(dictionary_name); - VERIFY(dictionary != context.dictionaries.end()); - - if (!dictionary->value.parent_name.is_empty() && dictionary_needs_builder_for_default_construction(context, dictionary->value.parent_name)) - return true; - - for (auto const& member : dictionary->value.members) { - if (dictionary_member_needs_builder(context, member)) - return true; - } - - return false; -} - -static Optional dictionary_default_initializers(Context const& context, ByteString const& dictionary_name) -{ - auto dictionary = context.dictionaries.find(dictionary_name); - VERIFY(dictionary != context.dictionaries.end()); - - Vector initializers; - - if (!dictionary->value.parent_name.is_empty()) { - if (auto parent_initializers = dictionary_default_initializers(context, dictionary->value.parent_name); parent_initializers.has_value()) - initializers.append(ByteString::formatted("{} {{ {} }}", dictionary->value.parent_name, *parent_initializers)); - } - - for (auto const& member : dictionary->value.members) { - if (!dictionary_member_needs_builder(context, member)) - continue; - initializers.append(ByteString::formatted(".{} = {{}}", make_input_acceptable_cpp(member.name.to_snakecase()))); - } - - if (initializers.is_empty()) - return {}; - return ByteString::join(", "sv, initializers); -} - -static ByteString cpp_default_expression(Context const& context, Type const& type, Optional const& default_value = {}, Optional const&> extended_attributes = {}); - -static Type const& union_member_type_for_default_value(Context const& context, UnionType const& union_type, ByteString const& default_value) -{ - auto types = union_type.flattened_member_types(); - - if (default_value == "[]"sv) { - auto sequence_type = types.find_if([](auto const& type) { return type->name().is_one_of("sequence"sv, "FrozenArray"sv); }); - VERIFY(sequence_type != types.end()); - return **sequence_type; - } - - if (default_value == "{}"sv) { - auto object_type = types.find_if([&context](auto const& type) { - return type->name() == "record"sv || context.dictionaries.contains(type->name()); - }); - VERIFY(object_type != types.end()); - return **object_type; - } - - if (default_value.starts_with("\""sv) && default_value.ends_with("\""sv)) { - auto enum_member_name = default_value.substring_view(1, default_value.length() - 2); - - auto enumeration_type = types.find_if([&](auto const& type) { - if (auto enumeration = context.enumerations.find(type->name()); enumeration != context.enumerations.end()) - return enumeration->value.translated_cpp_names.contains(enum_member_name); - return false; - }); - if (enumeration_type != types.end()) - return **enumeration_type; - - auto string_type = types.find_if([](auto const& type) { return type->is_string(); }); - VERIFY(string_type != types.end()); - return **string_type; - } - - if (default_value == "true"sv || default_value == "false"sv) { - auto boolean_type = types.find_if([](auto const& type) { return type->is_boolean(); }); - VERIFY(boolean_type != types.end()); - return **boolean_type; - } - - if (default_value.to_number().has_value() || default_value.to_number().has_value() || default_value.to_number().has_value()) { - auto numeric_type = types.find_if([](auto const& type) { return type->is_numeric(); }); - VERIFY(numeric_type != types.end()); - return **numeric_type; - } - - TODO(); -} - -static ByteString cpp_default_expression(Context const& context, Type const& type, Optional const& default_value, Optional const&> extended_attributes) -{ - auto cpp_type = cpp_type_for_idl_type(context, type, TypeOptionality::Required, extended_attributes); - - if (is(type)) { - auto const& union_type = as(type); - if (!default_value.has_value()) { - if (union_type.includes_undefined() || union_type.includes_nullable_type()) - return "Empty {}"sv; - - auto types = union_type.flattened_member_types(); - VERIFY(!types.is_empty()); - return cpp_default_expression(context, *types.first()); - } - - if (*default_value == "null"sv) { - if (union_type.includes_undefined() || union_type.includes_nullable_type()) - return "Empty {}"sv; - return "{}"sv; - } - - auto const& union_member_type = union_member_type_for_default_value(context, union_type, *default_value); - auto expression = cpp_default_expression(context, union_member_type, default_value); - if (expression == "{}"sv) - return ByteString::formatted("{} {{}}", cpp_type_for_idl_type(context, union_member_type, TypeOptionality::Required).name); - return expression; - } - - if (!default_value.has_value()) { - if (type.is_nullable()) - return "{}"sv; - - if (type.is_string()) - return ByteString::formatted("{} {{}}", cpp_type.name); - - if (type.is_boolean()) - return "false"sv; - - if (type.is_numeric()) - return "0"sv; - - if (context.enumerations.contains(type.name())) { - auto const& enumeration = context.enumerations.find(type.name())->value; - auto default_value_cpp_name = enumeration.translated_cpp_names.get(enumeration.first_member); - VERIFY(default_value_cpp_name.has_value()); - return ByteString::formatted("{}::{}", type.name(), *default_value_cpp_name); - } - - if (type.name() == "any"sv) - return "JS::js_undefined()"sv; - - if (type.name().is_one_of("sequence"sv, "FrozenArray"sv, "record"sv)) - return "{}"sv; - - if (context.dictionaries.contains(type.name())) { - if (auto initializers = dictionary_default_initializers(context, type.name()); initializers.has_value()) - return ByteString::formatted("{} {{ {} }}", cpp_type.name, *initializers); - return ByteString::formatted("{} {{}}", cpp_type.name); - } - - if (IDL::is_platform_object(context, type) - || callback_interface_for_type(context, type) - || context.callback_functions.contains(type.name()) - || type.name().is_one_of("Promise"sv, "object"sv)) { - return "{}"sv; - } - - TODO(); - } - - auto const& explicit_default_value = *default_value; - auto inner_type = clone_type(type, false); - auto inner_cpp_type = cpp_type_for_idl_type(context, *inner_type, TypeOptionality::Required, extended_attributes); - - if (type.is_nullable() && explicit_default_value == "null"sv) - return "{}"sv; - - if (type.name() == "any"sv) { - if (explicit_default_value == "null"sv) - return "JS::js_null()"sv; - if (explicit_default_value == "true"sv || explicit_default_value == "false"sv) - return ByteString::formatted("JS::Value({})", explicit_default_value); - if (explicit_default_value.to_number().has_value() || explicit_default_value.to_number().has_value() || explicit_default_value.to_number().has_value()) - return ByteString::formatted("JS::Value({})", explicit_default_value); - } - - if ((IDL::is_platform_object(context, type) - || callback_interface_for_type(context, type) - || context.callback_functions.contains(type.name()) - || type.name() == "Promise"sv - || type.name() == "object"sv) - && explicit_default_value == "null"sv) { - return "{}"sv; - } - - if (type.is_string()) - return optional_string_default_value_expression(inner_cpp_type.name, explicit_default_value); - - if (type.is_boolean() || type.is_numeric()) - return explicit_default_value; - - if (type.name().is_one_of("sequence"sv, "FrozenArray"sv)) { - VERIFY(explicit_default_value == "[]"sv); - return "{}"sv; - } - - if (type.name() == "record"sv) { - VERIFY(explicit_default_value == "{}"sv); - return "{}"sv; - } - - if (context.dictionaries.contains(type.name())) { - VERIFY(explicit_default_value == "{}"sv); - if (auto initializers = dictionary_default_initializers(context, type.name()); initializers.has_value()) - return ByteString::formatted("{} {{ {} }}", inner_cpp_type.name, *initializers); - return ByteString::formatted("{} {{}}", inner_cpp_type.name); - } - - if (context.enumerations.contains(type.name())) { - VERIFY(explicit_default_value.length() >= 2 && explicit_default_value[0] == '"' && explicit_default_value[explicit_default_value.length() - 1] == '"'); - auto enum_member_name = explicit_default_value.substring_view(1, explicit_default_value.length() - 2); - auto default_value_cpp_name = context.enumerations.find(type.name())->value.translated_cpp_names.get(enum_member_name); - VERIFY(default_value_cpp_name.has_value()); - return ByteString::formatted("{}::{}", type.name(), *default_value_cpp_name); - } - - TODO(); -} - -template -static void generate_optional_to_cpp(SourceGenerator& generator, ParameterType& parameter, ByteString const& js_name, ByteString const& js_suffix, ByteString const& cpp_name, Context const& context, GeneratedIncludes& includes, Optional optional_default_value, TypeOptionality optionality, size_t recursion_depth) -{ - auto optional_generator = generator.fork(); - auto declaration_optionality = optional_default_value.has_value() ? TypeOptionality::Required : optionality; - auto optional_cpp_type = cpp_type_for_idl_type(context, *parameter.type, declaration_optionality, parameter.extended_attributes); - auto inner_cpp_name = ByteString::formatted("{}_non_optional", cpp_name); - - optional_generator.set("optional.cpp_type", optional_cpp_type.name); - optional_generator.set("optional.inner_cpp_name", inner_cpp_name); - - if (optional_default_value == "{}"sv && context.dictionaries.contains(parameter.type->name()) && dictionary_needs_builder_for_default_construction(context, parameter.type->name())) { - optional_generator.set("parameter.type.idl_value_conversion_function", idl_value_conversion_function_name(parameter.type->name())); - optional_generator.append(R"~~~( - auto @cpp_name@ = TRY(@parameter.type.idl_value_conversion_function@(vm, JS::js_undefined())); - if (!@js_name@@js_suffix@.is_undefined()) { -)~~~"); - - generate_to_cpp(optional_generator, parameter, js_name, js_suffix, inner_cpp_name, context, includes, false, {}, false, recursion_depth); - - optional_generator.append(R"~~~( - @cpp_name@ = @optional.inner_cpp_name@; - } -)~~~"); - return; - } - - Optional initial_value; - if (optional_default_value.has_value()) - initial_value = cpp_default_expression(context, *parameter.type, optional_default_value, parameter.extended_attributes); - - if (initial_value == "{}"sv) { - optional_generator.append(R"~~~( - @optional.cpp_type@ @cpp_name@ {}; -)~~~"); - } else if (initial_value.has_value()) { - optional_generator.set("optional.initial_value", *initial_value); - optional_generator.append(R"~~~( - @optional.cpp_type@ @cpp_name@ { @optional.initial_value@ }; -)~~~"); - } else { - optional_generator.append(R"~~~( - @optional.cpp_type@ @cpp_name@ {}; -)~~~"); - } - - optional_generator.append(R"~~~( - if (!@js_name@@js_suffix@.is_undefined()) { -)~~~"); - - generate_to_cpp(optional_generator, parameter, js_name, js_suffix, inner_cpp_name, context, includes, false, {}, false, recursion_depth); - - optional_generator.append(R"~~~( - @cpp_name@ = @optional.inner_cpp_name@; - } -)~~~"); -} - -template -static void generate_nullable_to_cpp(SourceGenerator& generator, ParameterType const& parameter, ByteString const& js_name, ByteString const& js_suffix, ByteString const& cpp_name, Context const& context, GeneratedIncludes& includes, size_t recursion_depth) -{ - auto nullable_generator = generator.fork(); - auto nullable_cpp_type = cpp_type_for_idl_type(context, *parameter.type, TypeOptionality::Required, parameter.extended_attributes).name; - auto inner_cpp_name = ByteString::formatted("{}_non_nullable", cpp_name); - auto inner_type = clone_type(*parameter.type, false); - auto inner_parameter = parameter; - inner_parameter.type = inner_type; - auto legacy_treat_non_object_as_null = nullable_callback_function_treats_non_object_as_null(context, *parameter.type); - auto inner_type_includes_undefined = inner_type->includes_undefined(); - - nullable_generator.set("nullable.cpp_type", nullable_cpp_type); - nullable_generator.set("nullable.inner_cpp_name", inner_cpp_name); - - nullable_generator.append(R"~~~( - // 1. If V is not an Object, and the conversion to an IDL value is being performed due to V being assigned to an attribute whose type is a nullable callback function that is annotated with [LegacyTreatNonObjectAsNull], then return the IDL nullable type T? value null. - @nullable.cpp_type@ @cpp_name@; -)~~~"); - - if (legacy_treat_non_object_as_null) { - nullable_generator.append(R"~~~( - if (@js_name@@js_suffix@.is_object()) { -)~~~"); - } - - if (inner_type_includes_undefined) { - nullable_generator.append(R"~~~( - // 2. Otherwise, if V is undefined, and T includes undefined, return the unique undefined value. - if (@js_name@@js_suffix@.is_undefined()) { - @cpp_name@ = Empty {}; - } - // 3. Otherwise, if V is null or undefined, then return the IDL nullable type T? value null. - else if (!@js_name@@js_suffix@.is_null()) { -)~~~"); - } else { - nullable_generator.append(R"~~~( - // 3. Otherwise, if V is null or undefined, then return the IDL nullable type T? value null. - if (!@js_name@@js_suffix@.is_nullish()) { -)~~~"); - } - - nullable_generator.append(R"~~~( - // 4. Otherwise, return the result of converting V using the rules for the inner IDL type T. -)~~~"); - generate_to_cpp(nullable_generator, inner_parameter, js_name, js_suffix, inner_cpp_name, context, includes, false, {}, false, recursion_depth); - - nullable_generator.append(R"~~~( - @cpp_name@ = @nullable.inner_cpp_name@; - } -)~~~"); - - if (legacy_treat_non_object_as_null) { - nullable_generator.append(R"~~~( - } -)~~~"); - } -} - -template -static void generate_variadic_to_cpp(SourceGenerator& generator, ParameterType& parameter, ByteString const& cpp_name, Context const& context, GeneratedIncludes& includes, size_t recursion_depth) -{ - auto variadic_generator = generator.fork(); - auto variadic_cpp_type = idl_type_name_to_cpp_type(*parameter.type, context, parameter.extended_attributes); - auto variadic_storage_type = contained_storage_type_to_cpp_name(variadic_cpp_type.contained_storage_type); - auto inner_cpp_name = ByteString::formatted("{}_item", cpp_name); - auto value_name = ByteString::formatted("variadic_argument_{}", recursion_depth); - - variadic_generator.set("variadic.storage_type", variadic_storage_type); - variadic_generator.set("variadic.item_type", variadic_cpp_type.name); - variadic_generator.set("variadic.inner_cpp_name", inner_cpp_name); - variadic_generator.set("variadic.value_name", value_name); - - variadic_generator.append(R"~~~( - @variadic.storage_type@<@variadic.item_type@> @cpp_name@; - - if (vm.argument_count() > @js_suffix@) { - @cpp_name@.ensure_capacity(vm.argument_count() - @js_suffix@); - - for (size_t i = @js_suffix@; i < vm.argument_count(); ++i) { - auto @variadic.value_name@ = vm.argument(i); -)~~~"); - - generate_to_cpp(variadic_generator, parameter, value_name, "", inner_cpp_name, context, includes, false, {}, false, recursion_depth + 1); - - variadic_generator.append(R"~~~( - @cpp_name@.unchecked_append(move(@variadic.inner_cpp_name@)); - } - } -)~~~"); -} - -template -static void generate_to_string(SourceGenerator& scoped_generator, ParameterType const& parameter, GeneratedIncludes& includes) -{ - includes.add_header("LibWeb/WebIDL/AbstractOperations.h"sv); - - auto is_utf16_string = parameter.type->name().contains("Utf16"sv); - auto is_fly_string = parameter.extended_attributes.contains("FlyString"sv); - - if (is_utf16_string) { - scoped_generator.set("string_type", is_fly_string ? "Utf16FlyString"sv : "Utf16String"sv); - scoped_generator.set("string_suffix", "_utf16"sv); - } else { - scoped_generator.set("string_type", is_fly_string ? "FlyString"sv : "String"sv); - scoped_generator.set("string_suffix", "_string"sv); - } - - if (parameter.type->name().is_one_of("USVString"sv, "Utf16USVString"sv)) - scoped_generator.set("to_string", is_utf16_string ? "to_utf16_usv_string"sv : "to_usv_string"sv); - else if (parameter.type->name() == "ByteString") - scoped_generator.set("to_string", "to_byte_string"sv); - else - scoped_generator.set("to_string", is_utf16_string ? "to_utf16_string"sv : "to_string"sv); - - scoped_generator.append(R"~~~( - @string_type@ @cpp_name@; - if (!@legacy_null_to_empty_string@ || !@js_name@@js_suffix@.is_null()) { - @cpp_name@ = TRY(WebIDL::@to_string@(vm, @js_name@@js_suffix@)); - } -)~~~"); -} - -static void generate_from_integral(SourceGenerator& scoped_generator, IDL::Type const& type, bool const optional_integral_type) -{ - struct TypeMap { - StringView idl_type; - StringView cpp_type; - }; - static constexpr auto idl_type_map = to_array({ - { "byte"sv, "WebIDL::Byte"sv }, - { "octet"sv, "WebIDL::Octet"sv }, - { "short"sv, "WebIDL::Short"sv }, - { "unsigned short"sv, "WebIDL::UnsignedShort"sv }, - { "long"sv, "WebIDL::Long"sv }, - { "unsigned long"sv, "WebIDL::UnsignedLong"sv }, - { "long long"sv, "double"sv }, - { "unsigned long long"sv, "double"sv }, - }); - - auto it = find_if(idl_type_map.begin(), idl_type_map.end(), [&](auto const& entry) { - return entry.idl_type == type.name(); - }); - - VERIFY(it != idl_type_map.end()); - scoped_generator.set("cpp_type"sv, it->cpp_type); - - if (type.is_nullable() || optional_integral_type) { - scoped_generator.append(R"~~~( - @result_expression@ JS::Value(static_cast<@cpp_type@>(@value@.value())); -)~~~"); - } else { - scoped_generator.append(R"~~~( - @result_expression@ JS::Value(static_cast<@cpp_type@>(@value@)); -)~~~"); - } -} - -template -static void generate_to_integral(SourceGenerator& scoped_generator, ParameterType const& parameter, GeneratedIncludes& includes) -{ - struct TypeMap { - StringView idl_type; - StringView cpp_type; - }; - static constexpr auto idl_type_map = to_array({ - { "boolean"sv, "bool"sv }, - { "byte"sv, "WebIDL::Byte"sv }, - { "octet"sv, "WebIDL::Octet"sv }, - { "short"sv, "WebIDL::Short"sv }, - { "unsigned short"sv, "WebIDL::UnsignedShort"sv }, - { "long"sv, "WebIDL::Long"sv }, - { "long long"sv, "WebIDL::LongLong"sv }, - { "unsigned long"sv, "WebIDL::UnsignedLong"sv }, - { "unsigned long long"sv, "WebIDL::UnsignedLongLong"sv }, - }); - - auto it = find_if(idl_type_map.begin(), idl_type_map.end(), [&](auto const& entry) { - return entry.idl_type == parameter.type->name(); - }); - - VERIFY(it != idl_type_map.end()); - scoped_generator.set("cpp_type"sv, it->cpp_type); - scoped_generator.set("enforce_range", parameter.extended_attributes.contains("EnforceRange") ? "Yes" : "No"); - scoped_generator.set("clamp", parameter.extended_attributes.contains("Clamp") ? "Yes" : "No"); - - scoped_generator.append(R"~~~( - @cpp_type@ @cpp_name@; -)~~~"); - - if (it->cpp_type == "bool"sv) { - scoped_generator.append(R"~~~( - @cpp_name@ = @js_name@@js_suffix@.to_boolean(); -)~~~"); - } else { - includes.add_header("LibWeb/WebIDL/AbstractOperations.h"sv); - - scoped_generator.append(R"~~~( - @cpp_name@ = TRY(WebIDL::convert_to_int<@cpp_type@>(vm, @js_name@@js_suffix@, WebIDL::EnforceRange::@enforce_range@, WebIDL::Clamp::@clamp@)); -)~~~"); - } -} - -// https://webidl.spec.whatwg.org/#es-dictionary -static void generate_dictionary_to_cpp(SourceGenerator& generator, Context const& context, IDL::Dictionary const& dictionary, ByteString dictionary_name, GeneratedIncludes& includes) -{ - includes.add_header("LibJS/Runtime/Error.h"sv); - includes.add_header("LibJS/Runtime/ValueInlines.h"sv); - - struct DictionaryConversion { - ByteString name; - Dictionary const* dictionary { nullptr }; - Vector designated_member_initializers; - Vector positional_member_initializers; - }; - - Vector dictionaries; - auto current_dictionary_name = move(dictionary_name); - auto const* current_dictionary = &dictionary; - while (true) { - dictionaries.append({ current_dictionary_name, current_dictionary, {}, {} }); - - if (current_dictionary->parent_name.is_empty()) - break; - - VERIFY(context.dictionaries.contains(current_dictionary->parent_name)); - current_dictionary_name = current_dictionary->parent_name; - current_dictionary = &context.dictionaries.find(current_dictionary_name)->value; - } - - generator.append(R"~~~( - if (!@js_name@@js_suffix@.is_nullish() && !@js_name@@js_suffix@.is_object()) - return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "@parameter.type.name@"); -)~~~"); - // FIXME: This (i) is a hack to make sure we don't generate duplicate variable names. - static auto i = 0; - - for (auto& dictionary_conversion : dictionaries) { - for (auto& member : dictionary_conversion.dictionary->members) { - generator.set("member_key", member.name); - auto member_js_name = make_input_acceptable_cpp(member.name.to_snakecase()); - auto member_value_name = ByteString::formatted("{}_value_{}", member_js_name, i); - auto member_property_value_name = ByteString::formatted("{}_property_value_{}", member_js_name, i); - generator.set("member_value_name", member_value_name); - generator.set("member_property_value_name", member_property_value_name); - generator.append(R"~~~( - auto @member_property_value_name@ = JS::js_undefined(); - if (@js_name@@js_suffix@.is_object()) - @member_property_value_name@ = TRY(@js_name@@js_suffix@.as_object().get("@member_key@"_utf16_fly_string)); -)~~~"); - if (member.required) { - generator.append(R"~~~( - if (@member_property_value_name@.is_undefined()) - return vm.throw_completion(JS::ErrorType::MissingRequiredProperty, "@member_key@"); -)~~~"); - } - - collect_include_dependencies(context, *member.type, includes); - generate_to_cpp(generator, member, member_property_value_name, "", member_value_name, context, includes, !member.required, member.default_value, false, 0, dictionary_member_presence(member)); - - dictionary_conversion.designated_member_initializers.append(ByteString::formatted(".{} = {}", member_js_name, member_value_name)); - dictionary_conversion.positional_member_initializers.append(member_value_name); - i++; - } - } - - ByteString initializer; - for (size_t dictionary_index = dictionaries.size(); dictionary_index > 0; --dictionary_index) { - auto& dictionary_conversion = dictionaries[dictionary_index - 1]; - Vector initializers; - auto has_parent_initializer = dictionary_index < dictionaries.size(); - - if (has_parent_initializer) { - auto& parent_dictionary = dictionaries[dictionary_index]; - initializers.append(initializer.is_empty() - ? ByteString::formatted("{} {{}}", parent_dictionary.name) - : ByteString::formatted("{} {{ {} }}", parent_dictionary.name, initializer)); - } - - initializers.extend(has_parent_initializer - ? dictionary_conversion.positional_member_initializers - : dictionary_conversion.designated_member_initializers); - initializer = ByteString::join(", "sv, initializers); - } - - if (initializer.is_empty()) { - generator.append(R"~~~( - @parameter.type.name.normalized@ @cpp_name@ {}; -)~~~"); - } else { - generator.set("dictionary.initializers", initializer); - generator.append(R"~~~( - @parameter.type.name.normalized@ @cpp_name@ { @dictionary.initializers@ }; -)~~~"); - } -} - -static void generate_callback_interface_to_cpp(SourceGenerator& scoped_generator, IDL::Interface const& callback_interface, GeneratedIncludes& includes) -{ - includes.add_header("LibGC/Heap.h"sv); - includes.add_header("LibWeb/Bindings/ExceptionOrUtils.h"sv); - includes.add_header("LibWeb/HTML/Scripting/Environments.h"sv); - includes.add_header("LibWeb/WebIDL/CallbackType.h"sv); - - scoped_generator.set("cpp_type", interface_cpp_type_name(callback_interface)); - - scoped_generator.append(R"~~~( - if (!@js_name@@js_suffix@.is_object()) - return vm.throw_completion(JS::ErrorType::NotAnObject, @js_name@@js_suffix@); - - auto callback_type = vm.heap().allocate(@js_name@@js_suffix@.as_object(), HTML::incumbent_realm()); - auto @cpp_name@ = TRY(throw_dom_exception_if_needed(vm, [&] { return @cpp_type@::create(realm, callback_type); })); -)~~~"); -} - -static void generate_platform_object_to_cpp(SourceGenerator& scoped_generator, GeneratedIncludes& includes) -{ - includes.add_header("AK/TypeCasts.h"sv); - - scoped_generator.append(R"~~~( - if (!@js_name@@js_suffix@.is_object() || !is<@parameter.type.name.normalized@>(@js_name@@js_suffix@.as_object())) - return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "@parameter.type.name@"); - - auto& @cpp_name@ = static_cast<@parameter.type.name.normalized@&>(@js_name@@js_suffix@.as_object()); -)~~~"); -} - -static void generate_floating_point_to_cpp(SourceGenerator& scoped_generator, IDL::Type const& type) -{ - if (type.name() == "unrestricted float") { - scoped_generator.set("parameter.type.name", "float"); - } else if (type.name() == "unrestricted double") { - scoped_generator.set("parameter.type.name", "double"); - } - - scoped_generator.append(R"~~~( - @parameter.type.name@ @cpp_name@ = TRY(@js_name@@js_suffix@.to_double(vm)); -)~~~"); - - if (type.is_restricted_floating_point()) { - scoped_generator.append(R"~~~( - if (isinf(@cpp_name@) || isnan(@cpp_name@)) { - return vm.throw_completion(JS::ErrorType::InvalidRestrictedFloatingPointParameter, "@parameter.name@"); - } - )~~~"); - } -} - -static void generate_promise_to_cpp(SourceGenerator& scoped_generator, GeneratedIncludes& includes) -{ - includes.add_header("LibJS/Runtime/AbstractOperations.h"sv); - includes.add_header("LibJS/Runtime/FunctionObject.h"sv); - includes.add_header("LibJS/Runtime/PromiseConstructor.h"sv); - - // https://webidl.spec.whatwg.org/#js-promise - scoped_generator.append(R"~~~( - // 1. Let promiseCapability be ? NewPromiseCapability(%Promise%). - auto promise_capability = TRY(JS::new_promise_capability(vm, realm.intrinsics().promise_constructor())); - // 2. Perform ? Call(promiseCapability.[[Resolve]], undefined, « V »). - TRY(JS::call(vm, *promise_capability->resolve(), JS::js_undefined(), @js_name@@js_suffix@)); - // 3. Return promiseCapability. - GC::Ref @cpp_name@ = promise_capability; -)~~~"); -} - -static void generate_object_to_cpp(SourceGenerator& scoped_generator) -{ - // https://webidl.spec.whatwg.org/#js-object - // 1. If V is not an Object, then throw a TypeError. - // 2. Return the IDL object value that is a reference to the same object as V. - scoped_generator.append(R"~~~( - if (!@js_name@@js_suffix@.is_object()) - return vm.throw_completion(JS::ErrorType::NotAnObject, @js_name@@js_suffix@); - GC::Ref @cpp_name@ = @js_name@@js_suffix@.as_object(); -)~~~"); -} - -// https://webidl.spec.whatwg.org/#js-buffer-source-types -static void generate_buffer_source_to_cpp(SourceGenerator& scoped_generator, IDL::Type const& type, HashMap const& extended_attributes, GeneratedIncludes& includes) -{ - add_javascript_builtin_buffer_source_type_include(type, includes); - scoped_generator.set("parameter.type.buffer_cpp", ByteString::formatted("JS::{}", type.name())); - - // A JavaScript value V is converted to an IDL ArrayBuffer value by running the following algorithm: - if (type.name() == "ArrayBuffer"sv) { - // 1. If V is not an Object, or V does not have an [[ArrayBufferData]] internal slot, then throw a TypeError. - scoped_generator.append(R"~~~( - auto @cpp_name@_builtin_buffer = @js_name@@js_suffix@.as_if<@parameter.type.buffer_cpp@>(); - if (!@cpp_name@_builtin_buffer) - return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "@parameter.type.name@"); -)~~~"); - - // 2. If IsSharedArrayBuffer(V) is true, then throw a TypeError. - scoped_generator.append(R"~~~( - if (@cpp_name@_builtin_buffer->is_shared_array_buffer()) - return vm.throw_completion(JS::ErrorType::SharedArrayBuffer); -)~~~"); - - // 3. If the conversion is not to an IDL type associated with the [AllowResizable] extended attribute, and - // IsFixedLengthArrayBuffer(V) is false, then throw a TypeError. - if (!extended_attributes.contains("AllowResizable"sv)) { - scoped_generator.append(R"~~~( - if (!@cpp_name@_builtin_buffer->is_fixed_length()) - return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "fixed-length @parameter.type.name@"); -)~~~"); - } - - // 4. Return the IDL ArrayBuffer value that is a reference to the same object as V. - scoped_generator.append(R"~~~( - GC::Ref<@parameter.type.buffer_cpp@> @cpp_name@ = *@cpp_name@_builtin_buffer; -)~~~"); - return; - } - - // A JavaScript value V is converted to an IDL SharedArrayBuffer value by running the following algorithm: - if (type.name() == "SharedArrayBuffer"sv) { - // 1. If V is not an Object, or V does not have an [[ArrayBufferData]] internal slot, then throw a TypeError. - scoped_generator.append(R"~~~( - auto @cpp_name@_builtin_buffer = @js_name@@js_suffix@.as_if<@parameter.type.buffer_cpp@>(); - if (!@cpp_name@_builtin_buffer) - return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "@parameter.type.name@"); -)~~~"); - - // 2. If IsSharedArrayBuffer(V) is false, then throw a TypeError. - scoped_generator.append(R"~~~( - if (!@cpp_name@_builtin_buffer->is_shared_array_buffer()) - return vm.throw_completion(JS::ErrorType::NotASharedArrayBuffer); -)~~~"); - - // 3. If the conversion is not to an IDL type associated with the [AllowResizable] extended attribute, and - // IsFixedLengthArrayBuffer(V) is false, then throw a TypeError. - if (!extended_attributes.contains("AllowResizable"sv)) { - scoped_generator.append(R"~~~( - if (!@cpp_name@_builtin_buffer->is_fixed_length()) - return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "fixed-length @parameter.type.name@"); -)~~~"); - } - - // 4. Return the IDL SharedArrayBuffer value that is a reference to the same object as V. - scoped_generator.append(R"~~~( - GC::Ref<@parameter.type.buffer_cpp@> @cpp_name@ = *@cpp_name@_builtin_buffer; -)~~~"); - return; - } - - // A JavaScript value V is converted to an IDL DataView value by running the following algorithm: - if (type.name() == "DataView"sv) { - // 1. If V is not an Object, or V does not have a [[DataView]] internal slot, then throw a TypeError. - scoped_generator.append(R"~~~( - auto @cpp_name@_builtin_buffer = @js_name@@js_suffix@.as_if<@parameter.type.buffer_cpp@>(); - if (!@cpp_name@_builtin_buffer) - return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "@parameter.type.name@"); - - auto& @cpp_name@_viewed_array_buffer = *@cpp_name@_builtin_buffer->viewed_array_buffer(); -)~~~"); - - // 2. If the conversion is not to an IDL type associated with the [AllowShared] extended attribute, and - // IsSharedArrayBuffer(V.[[ViewedArrayBuffer]]) is true, then throw a TypeError. - if (!extended_attributes.contains("AllowShared"sv)) { - scoped_generator.append(R"~~~( - if (@cpp_name@_viewed_array_buffer.is_shared_array_buffer()) - return vm.throw_completion(JS::ErrorType::SharedArrayBuffer); -)~~~"); - } - - // 3. If the conversion is not to an IDL type associated with the [AllowResizable] extended attribute, and - // IsFixedLengthArrayBuffer(V.[[ViewedArrayBuffer]]) is false, then throw a TypeError. - if (!extended_attributes.contains("AllowResizable"sv)) { - scoped_generator.append(R"~~~( - if (!@cpp_name@_viewed_array_buffer.is_fixed_length()) - return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "fixed-length @parameter.type.name@"); -)~~~"); - } - - // 4. Return the IDL DataView value that is a reference to the same object as V. - scoped_generator.append(R"~~~( - GC::Ref<@parameter.type.buffer_cpp@> @cpp_name@ = *@cpp_name@_builtin_buffer; -)~~~"); - return; - } - - // A JavaScript value V is converted to an IDL Int8Array, Int16Array, Int32Array, Uint8Array, Uint16Array, Uint32Array, - // Uint8ClampedArray, BigInt64Array, BigUint64Array, Float16Array, Float32Array, or Float64Array value by running the - // following algorithm: - VERIFY(type.is_typed_array()); - - // 1. Let T be the IDL type V is being converted to. - - // 2. If V is not an Object, or V does not have a [[TypedArrayName]] internal slot with a value equal to T’s name, - // then throw a TypeError. - scoped_generator.append(R"~~~( - auto @cpp_name@_builtin_buffer = @js_name@@js_suffix@.as_if<@parameter.type.buffer_cpp@>(); - if (!@cpp_name@_builtin_buffer) - return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "@parameter.type.name@"); - - auto& @cpp_name@_viewed_array_buffer = *@cpp_name@_builtin_buffer->viewed_array_buffer(); -)~~~"); - - // 3. If the conversion is not to an IDL type associated with the [AllowShared] extended attribute, and - // IsSharedArrayBuffer(V.[[ViewedArrayBuffer]]) is true, then throw a TypeError. - if (!extended_attributes.contains("AllowShared"sv)) { - scoped_generator.append(R"~~~( - if (@cpp_name@_viewed_array_buffer.is_shared_array_buffer()) - return vm.throw_completion(JS::ErrorType::SharedArrayBuffer); -)~~~"); - } - - // 4. If the conversion is not to an IDL type associated with the [AllowResizable] extended attribute, and - // IsFixedLengthArrayBuffer(V.[[ViewedArrayBuffer]]) is false, then throw a TypeError. - if (!extended_attributes.contains("AllowResizable"sv)) { - scoped_generator.append(R"~~~( - if (!@cpp_name@_viewed_array_buffer.is_fixed_length()) - return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "fixed-length @parameter.type.name@"); -)~~~"); - } - - // 5. Return the IDL value of type T that is a reference to the same object as V. - scoped_generator.append(R"~~~( - GC::Ref<@parameter.type.buffer_cpp@> @cpp_name@ = *@cpp_name@_builtin_buffer; -)~~~"); -} - -static void generate_any_to_cpp(SourceGenerator& scoped_generator) -{ - scoped_generator.append(R"~~~( - auto @cpp_name@ = @js_name@@js_suffix@; -)~~~"); -} - -enum class ThrowOnInvalidEnumValue { - Yes, - No, -}; - -static void generate_enum_to_cpp(SourceGenerator& scoped_generator, Enumeration const& enumeration, ThrowOnInvalidEnumValue throw_on_invalid) -{ - auto enum_generator = scoped_generator.fork(); - auto default_value_cpp_name = enumeration.translated_cpp_names.get(enumeration.first_member); - VERIFY(default_value_cpp_name.has_value()); - enum_generator.set("enum.default.cpp_value", *default_value_cpp_name); - - if (throw_on_invalid == ThrowOnInvalidEnumValue::Yes) { - enum_generator.append(R"~~~( - auto @cpp_name@ = TRY(@parameter.type.idl_value_conversion_function@(vm, @js_name@@js_suffix@)); -)~~~"); - return; - } - - enum_generator.set("js_name.as_string", ByteString::formatted("{}{}_string", enum_generator.get("js_name"sv), enum_generator.get("js_suffix"sv))); - enum_generator.append(R"~~~( - @parameter.type.name.normalized@ @cpp_name@ { @parameter.type.name.normalized@::@enum.default.cpp_value@ }; - auto @js_name.as_string@ = TRY(@js_name@@js_suffix@.to_string(vm)); -)~~~"); - auto first = true; - VERIFY(enumeration.translated_cpp_names.size() >= 1); - for (auto& it : enumeration.translated_cpp_names) { - enum_generator.set("enum.alt.name", it.key); - enum_generator.set("enum.alt.value", it.value); - enum_generator.set("else", first ? "" : "else "); - first = false; - - enum_generator.append(R"~~~( - @else@if (@js_name.as_string@ == "@enum.alt.name@"sv) - @cpp_name@ = @parameter.type.name.normalized@::@enum.alt.value@; -)~~~"); - } - - enum_generator.append(R"~~~( - else - return JS::js_undefined(); -)~~~"); -} - -// https://webidl.spec.whatwg.org/#es-record -static void generate_record_to_cpp(SourceGenerator& scoped_generator, IDL::ParameterizedType const& type, ByteString const& cpp_name, Context const& context, GeneratedIncludes& includes, size_t recursion_depth) -{ - auto record_generator = scoped_generator.fork(); - record_generator.set("recursion_depth", ByteString::number(recursion_depth)); - - // A record can only have two types: key type and value type. - VERIFY(type.parameters().size() == 2); - - // A record only allows the key to be a string. - VERIFY(type.parameters()[0]->is_string()); - - // An ECMAScript value O is converted to an IDL record value as follows: - // 1. If Type(O) is not Object, throw a TypeError. - // 2. Let result be a new empty instance of record. - // 3. Let keys be ? O.[[OwnPropertyKeys]](). - // 4. For each key of keys: - // 1. Let desc be ? O.[[GetOwnProperty]](key). - // 2. If desc is not undefined and desc.[[Enumerable]] is true: - // 1. Let typedKey be key converted to an IDL value of type K. - // 2. Let value be ? Get(O, key). - // 3. Let typedValue be value converted to an IDL value of type V. - // 4. Set result[typedKey] to typedValue. - // 5. Return result. - - auto record_cpp_type = idl_type_name_to_cpp_type(type, context); - record_generator.set("record.type", record_cpp_type.name); - - // If this is a recursive call to generate_to_cpp, assume that the caller has already handled converting the JS value to an object for us. - // This affects record types in unions for example. - if (recursion_depth == 0) { - record_generator.append(R"~~~( - if (!@js_name@@js_suffix@.is_object()) - return vm.throw_completion(JS::ErrorType::NotAnObject, @js_name@@js_suffix@); - - auto& @js_name@@js_suffix@_object = @js_name@@js_suffix@.as_object(); -)~~~"); - } - - record_generator.append(R"~~~( - @record.type@ @cpp_name@; - - auto record_keys@recursion_depth@ = TRY(@js_name@@js_suffix@_object.internal_own_property_keys()); - - for (auto& key@recursion_depth@ : record_keys@recursion_depth@) { - auto property_key@recursion_depth@ = MUST(JS::PropertyKey::from_value(vm, key@recursion_depth@)); - - auto descriptor@recursion_depth@ = TRY(@js_name@@js_suffix@_object.internal_get_own_property(property_key@recursion_depth@)); - - if (!descriptor@recursion_depth@.has_value() || !descriptor@recursion_depth@->enumerable.has_value() || !descriptor@recursion_depth@->enumerable.value()) - continue; -)~~~"); - - IDL::Parameter key_parameter { .type = type.parameters()[0], .name = cpp_name, .optional_default_value = {}, .extended_attributes = {} }; - generate_to_cpp(record_generator, key_parameter, "key", ByteString::number(recursion_depth), ByteString::formatted("typed_key{}", recursion_depth), context, includes, false, {}, false, recursion_depth + 1); - - record_generator.append(R"~~~( - auto value@recursion_depth@ = TRY(@js_name@@js_suffix@_object.get(property_key@recursion_depth@)); -)~~~"); - - // FIXME: Record value types should be TypeWithExtendedAttributes, which would allow us to get [LegacyNullToEmptyString] here. - IDL::Parameter value_parameter { .type = type.parameters()[1], .name = cpp_name, .optional_default_value = {}, .extended_attributes = {} }; - generate_to_cpp(record_generator, value_parameter, "value", ByteString::number(recursion_depth), ByteString::formatted("typed_value{}", recursion_depth), context, includes, false, {}, false, recursion_depth + 1); - - record_generator.append(R"~~~( - @cpp_name@.set(typed_key@recursion_depth@, typed_value@recursion_depth@); - } -)~~~"); -} - -static void generate_callback_function_to_cpp(SourceGenerator& scoped_generator, IDL::CallbackFunction const& callback_function, GeneratedIncludes& includes) -{ - includes.add_header("LibGC/Heap.h"sv); - includes.add_header("LibJS/Runtime/FunctionObject.h"sv); - includes.add_header("LibWeb/HTML/Scripting/Environments.h"sv); - includes.add_header("LibWeb/WebIDL/CallbackType.h"sv); - - // https://webidl.spec.whatwg.org/#es-callback-function - - auto callback_function_generator = scoped_generator.fork(); - - if (callback_function.return_type->is_parameterized() && callback_function.return_type->name() == "Promise") - callback_function_generator.set("operation_returns_promise", "WebIDL::OperationReturnsPromise::Yes"); - else - callback_function_generator.set("operation_returns_promise", "WebIDL::OperationReturnsPromise::No"); - - // An ECMAScript value V is converted to an IDL callback function type value by running the following algorithm: - // 1. If the result of calling IsCallable(V) is false and the conversion to an IDL value is not being performed due to V being assigned to an attribute whose type is a nullable callback function that is annotated with [LegacyTreatNonObjectAsNull], then throw a TypeError. - // 2. Return the IDL callback function type value that represents a reference to the same object that V represents, with the incumbent realm as the callback context. - if (!callback_function.is_legacy_treat_non_object_as_null) { - callback_function_generator.append(R"~~~( - if (!@js_name@@js_suffix@.is_function()) - return vm.throw_completion(JS::ErrorType::NotAFunction, @js_name@@js_suffix@); -)~~~"); - } - callback_function_generator.append(R"~~~( - auto @cpp_name@ = vm.heap().allocate(@js_name@@js_suffix@.as_object(), HTML::incumbent_realm(), @operation_returns_promise@); -)~~~"); -} - -static void generate_sequence_from_iterable(SourceGenerator& generator, IDL::ParameterizedType const& type, ByteString const& cpp_name, ByteString const& iterable_cpp_name, ByteString const& iterator_method_cpp_name, IDL::Context const& context, GeneratedIncludes& includes, size_t recursion_depth); - -static void generate_sequence_to_cpp(SourceGenerator& scoped_generator, IDL::ParameterizedType const& type, ByteString const& js_name, ByteString const& js_suffix, ByteString const& cpp_name, IDL::Context const& context, GeneratedIncludes& includes, size_t recursion_depth) -{ - // https://webidl.spec.whatwg.org/#js-sequence - // https://webidl.spec.whatwg.org/#js-frozen-array - - auto sequence_generator = scoped_generator.fork(); - sequence_generator.set("recursion_depth", ByteString::number(recursion_depth)); - - // A JavaScript value V is converted to an IDL sequence value as follows: - // 1. If V is not an Object, throw a TypeError. - // 2. Let method be ? GetMethod(V, %Symbol.iterator%). - // 3. If method is undefined, throw a TypeError. - // 4. Return the result of creating a sequence from V and method. - - // A JavaScript value V is converted to an IDL FrozenArray value by running the following algorithm: - // 1. Let values be the result of converting V to IDL type sequence. - // 2. Return the result of creating a frozen array from values. - - sequence_generator.append(R"~~~( - if (!@js_name@@js_suffix@.is_object()) - return vm.throw_completion(JS::ErrorType::NotAnObject, @js_name@@js_suffix@); - - auto @js_name@@js_suffix@_iterator_method@recursion_depth@ = TRY(@js_name@@js_suffix@.get_method(vm, vm.well_known_symbol_iterator())); - if (!@js_name@@js_suffix@_iterator_method@recursion_depth@) - return vm.throw_completion(JS::ErrorType::NotIterable, @js_name@@js_suffix@); -)~~~"); - - generate_sequence_from_iterable(sequence_generator, type, cpp_name, ByteString::formatted("{}{}", js_name, js_suffix), ByteString::formatted("{}{}_iterator_method{}", js_name, js_suffix, recursion_depth), context, includes, recursion_depth + 1); -} - -template -static void generate_union_to_cpp(SourceGenerator& scoped_generator, ParameterType& parameter, ByteString const& js_name, ByteString const& js_suffix, ByteString const& cpp_name, Context const& context, GeneratedIncludes& includes, size_t recursion_depth) -{ - // https://webidl.spec.whatwg.org/#es-union - - auto union_generator = scoped_generator.fork(); - - auto& union_type = as(*parameter.type); - union_generator.set("union_type", union_type_to_variant(union_type, context)); - union_generator.set("recursion_depth", ByteString::number(recursion_depth)); - - // NOTE: This is handled out here as we need the dictionary conversion code for the {} optional default value. - // 3. Let types be the flattened member types of the union type. - auto types = union_type.flattened_member_types(); - - RefPtr dictionary_type; - for (auto& dictionary : context.dictionaries) { - for (auto& type : types) { - if (type->name() == dictionary.key) { - dictionary_type = type; - break; - } - } - - if (dictionary_type) - break; - } - - if (dictionary_type) { - union_generator.set("dictionary.type", dictionary_type->name()); - union_generator.set("dictionary.type.idl_value_conversion_function", idl_value_conversion_function_name(dictionary_type->name())); - } - - // A lambda is used because Variants without "Empty" can't easily be default initialized. - // Plus, this would require the user of union types to always accept a Variant with an Empty type. - - // Additionally, it handles the case of unconditionally throwing a TypeError at the end if none of the types match. - // This is because we cannot unconditionally throw in generate_to_cpp as generate_to_cpp is supposed to assign to a variable and then continue. - // Note that all the other types only throw on a condition. - - union_generator.append(R"~~~( - auto @js_name@@js_suffix@_to_variant = [&vm, &realm](JS::Value @js_name@@js_suffix@) -> JS::ThrowCompletionOr<@union_type@> { - // These might be unused. - (void)vm; - (void)realm; -)~~~"); - - // 1. If the union type includes undefined and V is undefined, then return the unique undefined value. - if (union_type.includes_undefined()) { - scoped_generator.append(R"~~~( - if (@js_name@@js_suffix@.is_undefined()) - return Empty {}; -)~~~"); - } - - // 2. If the union type includes a nullable type and V is null or undefined, then return the IDL value null. - if (union_type.includes_nullable_type()) { - union_generator.append(R"~~~( - if (@js_name@@js_suffix@.is_nullish()) - return Empty {}; -)~~~"); - } else if (dictionary_type) { - // 4. If V is null or undefined, then - // 4.1 If types includes a dictionary type, then return the result of converting V to that dictionary type. - union_generator.append(R"~~~( - if (@js_name@@js_suffix@.is_nullish()) - return @union_type@ { TRY(@dictionary.type.idl_value_conversion_function@(vm, @js_name@@js_suffix@)) }; -)~~~"); - } - - bool includes_object = any_of(types, [](auto const& type) { return type->name() == "object"; }); - - // FIXME: Don't generate this if the union type doesn't include any object types. - union_generator.append(R"~~~( - if (@js_name@@js_suffix@.is_object()) { - [[maybe_unused]] auto& @js_name@@js_suffix@_object = @js_name@@js_suffix@.as_object(); -)~~~"); - - bool includes_platform_object = any_of(types, [&context](auto const& type) { return is_platform_object(context, type); }); - - if (includes_platform_object) { - includes.add_header("AK/TypeCasts.h"sv); - includes.add_header("LibWeb/Bindings/PlatformObject.h"sv); - - // 5. If V is a platform object, then: - union_generator.append(R"~~~( - if (is(@js_name@@js_suffix@_object)) { -)~~~"); - - // NOTE: This codegen assumes that all union types are cells or values we can create a handle for. - - // 1. If types includes an interface type that V implements, then return the IDL value that is a reference to the object V. - for (auto& type : types) { - if (!IDL::is_platform_object(context, type)) - continue; - - auto union_platform_object_type_generator = union_generator.fork(); - union_platform_object_type_generator.set("platform_object_type", interface_cpp_type_name(context, *type)); - - union_platform_object_type_generator.append(R"~~~( - if (auto* @js_name@@js_suffix@_result = as_if<@platform_object_type@>(@js_name@@js_suffix@_object)) - return @union_type@ { GC::Ref { *@js_name@@js_suffix@_result } }; -)~~~"); - } - - // 2. If types includes object, then return the IDL value that is a reference to the object V. - if (includes_object) { - union_generator.append(R"~~~( - return @union_type@ { GC::Ref { @js_name@@js_suffix@_object } }; -)~~~"); - } - - union_generator.append(R"~~~( - } -)~~~"); - } - - bool includes_window_proxy = any_of(types, [](auto const& type) { return type->name() == "WindowProxy"; }); - - if (includes_window_proxy) { - includes.add_header("AK/TypeCasts.h"sv); - union_generator.append(R"~~~( - if (auto* @js_name@@js_suffix@_result = as_if(@js_name@@js_suffix@_object)) - return @union_type@ { GC::Ref { *@js_name@@js_suffix@_result } }; -)~~~"); - } - - auto generate_buffer_source_union_return = [&](SourceGenerator& generator, Type const& type, ByteString const& union_cpp_name) { - IDL::Parameter idl_parameter { .type = type, .name = parameter.name, .optional_default_value = {}, .extended_attributes = parameter.extended_attributes }; - generate_to_cpp(generator, idl_parameter, js_name, js_suffix, union_cpp_name, context, includes, false, {}, false, recursion_depth + 1); - - generator.set("buffer_source.union_cpp_name", union_cpp_name); - generator.append(R"~~~( - return @union_type@ { @buffer_source.union_cpp_name@ }; -)~~~"); - }; - - RefPtr array_buffer_type; - RefPtr shared_array_buffer_type; - RefPtr data_view_type; - for (auto& type : types) { - if (type->name() == "ArrayBuffer"sv) - array_buffer_type = type; - else if (type->name() == "SharedArrayBuffer"sv) - shared_array_buffer_type = type; - else if (type->name() == "DataView"sv) - data_view_type = type; - } - - // 6. If Type(V) is Object, V has an [[ArrayBufferData]] internal slot, and IsSharedArrayBuffer(V) is false, then: - // 1. If types includes ArrayBuffer, then return the result of converting V to ArrayBuffer. - // 2. If types includes object, then return the IDL value that is a reference to the object V. - // 7. If Type(V) is Object, V has an [[ArrayBufferData]] internal slot, and IsSharedArrayBuffer(V) is true, then: - // 1. If types includes SharedArrayBuffer, then return the result of converting V to SharedArrayBuffer. - // 2. If types includes object, then return the IDL value that is a reference to the object V. - if (array_buffer_type || shared_array_buffer_type) { - includes.add_header("AK/TypeCasts.h"sv); - includes.add_header("LibJS/Runtime/ArrayBuffer.h"sv); - union_generator.append(R"~~~( - if (auto* array_buffer = as_if(@js_name@@js_suffix@_object)) { -)~~~"); - - if (array_buffer_type) { - union_generator.append(R"~~~( - if (!array_buffer->is_shared_array_buffer()) { -)~~~"); - generate_buffer_source_union_return(union_generator, *array_buffer_type, "array_buffer_union_type"sv); - union_generator.append(R"~~~( - } -)~~~"); - } - - if (shared_array_buffer_type) { - union_generator.append(R"~~~( - if (array_buffer->is_shared_array_buffer()) { -)~~~"); - generate_buffer_source_union_return(union_generator, *shared_array_buffer_type, "shared_array_buffer_union_type"sv); - union_generator.append(R"~~~( - } -)~~~"); - } - - union_generator.append(R"~~~( - } -)~~~"); - } - - // 8. If Type(V) is Object and V has a [[DataView]] internal slot, then: - // 1. If types includes DataView, then return the result of converting V to DataView. - // 2. If types includes object, then return the IDL value that is a reference to the object V. - if (data_view_type) { - includes.add_header("AK/TypeCasts.h"sv); - includes.add_header("LibJS/Runtime/DataView.h"sv); - union_generator.append(R"~~~( - if (as_if(@js_name@@js_suffix@_object)) { -)~~~"); - generate_buffer_source_union_return(union_generator, *data_view_type, "data_view_union_type"sv); - union_generator.append(R"~~~( - } -)~~~"); - } - - // 9. If Type(V) is Object and V has a [[TypedArrayName]] internal slot, then: - // 1. If types includes a typed array type whose name is the value of V’s [[TypedArrayName]] internal slot, then return the result of converting V to that type. - // 2. If types includes object, then return the IDL value that is a reference to the object V. - if (any_of(types, [](auto const& type) { return type->is_typed_array(); })) { - includes.add_header("AK/TypeCasts.h"sv); - includes.add_header("LibJS/Runtime/TypedArray.h"sv); - - for (auto const& type : types) { - if (!type->is_typed_array()) - continue; - - auto typed_array_generator = union_generator.fork(); - typed_array_generator.set("typed_array_type", type->name()); - typed_array_generator.append(R"~~~( - if (as_if(@js_name@@js_suffix@_object)) { -)~~~"); - generate_buffer_source_union_return(typed_array_generator, *type, ByteString::formatted("{}_union_type", type->name().to_snakecase())); - typed_array_generator.append(R"~~~( - } -)~~~"); - } - } - - // 10. If IsCallable(V) is true, then: - // 1. If types includes a callback function type, then return the result of converting V to that callback function type. - // 2. If types includes object, then return the IDL value that is a reference to the object V. - bool includes_callback_function = any_of(types, [&context](auto const& type) { return context.callback_functions.contains(type->name()); }); - - if (includes_callback_function) { - includes.add_header("LibGC/Heap.h"sv); - includes.add_header("LibJS/Runtime/FunctionObject.h"sv); - includes.add_header("LibWeb/HTML/Scripting/Environments.h"sv); - includes.add_header("LibWeb/WebIDL/CallbackType.h"sv); - - union_generator.append(R"~~~( - if (@js_name@@js_suffix@_object.is_function()) - return vm.heap().allocate(@js_name@@js_suffix@.as_function(), HTML::incumbent_realm()); -)~~~"); - } - - // 11. If Type(V) is Object, then: - // 1. If types includes a sequence type, then: - RefPtr sequence_type; - for (auto& type : types) { - if (type->name() == "sequence") { - sequence_type = as(*type); - break; - } - } - - if (sequence_type) { - // 1. Let method be ? GetMethod(V, @@iterator). - union_generator.append(R"~~~( - auto method = TRY(@js_name@@js_suffix@.get_method(vm, vm.well_known_symbol_iterator())); -)~~~"); - - // 2. If method is not undefined, return the result of creating a sequence of that type from V and method. - union_generator.append(R"~~~( - if (method) { -)~~~"); - - generate_sequence_from_iterable(union_generator, *sequence_type, cpp_name, ByteString::formatted("{}{}", js_name, js_suffix), "method", context, includes, recursion_depth + 1); - - union_generator.append(R"~~~( - - return @cpp_name@; - } -)~~~"); - } - - // FIXME: 2. If types includes a frozen array type, then - // 1. Let method be ? GetMethod(V, @@iterator). - // 2. If method is not undefined, return the result of creating a frozen array of that type from V and method. - - // 3. If types includes a dictionary type, then return the result of converting V to that dictionary type. - if (dictionary_type) { - union_generator.append(R"~~~( - return @union_type@ { TRY(@dictionary.type.idl_value_conversion_function@(vm, @js_name@@js_suffix@)) }; -)~~~"); - } - - // 4. If types includes a record type, then return the result of converting V to that record type. - RefPtr record_type; - for (auto& type : types) { - if (type->name() == "record") { - record_type = as(*type); - break; - } - } - - if (record_type) { - IDL::Parameter record_parameter { .type = *record_type, .name = cpp_name, .optional_default_value = {}, .extended_attributes = {} }; - generate_to_cpp(union_generator, record_parameter, js_name, js_suffix, "record_union_type"sv, context, includes, false, {}, false, recursion_depth + 1); - - union_generator.append(R"~~~( - return record_union_type; -)~~~"); - } - - // 5. If types includes a callback interface type, then return the result of converting V to that callback interface type. - for (auto& type : types) { - if (!callback_interface_for_type(context, type)) - continue; - - IDL::Parameter callback_interface_parameter { .type = *type, .name = cpp_name, .optional_default_value = {}, .extended_attributes = {} }; - generate_to_cpp(union_generator, callback_interface_parameter, js_name, js_suffix, "callback_interface_union_type"sv, context, includes, false, {}, false, recursion_depth + 1); - - union_generator.append(R"~~~( - return callback_interface_union_type; -)~~~"); - break; - } - - // 6. If types includes object, then return the IDL value that is a reference to the object V. - if (includes_object) { - union_generator.append(R"~~~( - return @js_name@@js_suffix@_object; -)~~~"); - } - - // End of is_object. - union_generator.append(R"~~~( - } -)~~~"); - - // 11. If Type(V) is Boolean, then: - // 1. If types includes boolean, then return the result of converting V to boolean. - bool includes_boolean = any_of(types, [](auto const& type) { return type->is_boolean(); }); - - if (includes_boolean) { - union_generator.append(R"~~~( - if (@js_name@@js_suffix@.is_boolean()) - return @union_type@ { @js_name@@js_suffix@.as_bool() }; -)~~~"); - } - - RefPtr numeric_type; - for (auto& type : types) { - if (type->is_numeric()) { - numeric_type = type; - break; - } - } - - // 12. If Type(V) is Number, then: - // 1. If types includes a numeric type, then return the result of converting V to that numeric type. - if (numeric_type) { - union_generator.append(R"~~~( - if (@js_name@@js_suffix@.is_number()) { -)~~~"); - // NOTE: generate_to_cpp doesn't use the parameter name. - // NOTE: generate_to_cpp will use to_{u32,etc.} which uses to_number internally and will thus use TRY, but it cannot throw as we know we are dealing with a number. - IDL::Parameter idl_parameter { .type = *numeric_type, .name = parameter.name, .optional_default_value = {}, .extended_attributes = {} }; - generate_to_cpp(union_generator, idl_parameter, js_name, js_suffix, ByteString::formatted("{}{}_number", js_name, js_suffix), context, includes, false, {}, false, recursion_depth + 1); - - union_generator.append(R"~~~( - return { @js_name@@js_suffix@_number }; - } -)~~~"); - } - - // 13. If Type(V) is BigInt, then: - // 1. If types includes bigint, then return the result of converting V to bigint - bool includes_bigint = any_of(types, [](auto const& type) { return type->is_bigint(); }); - - if (includes_bigint) { - union_generator.append(R"~~~( - if (@js_name@@js_suffix@.is_bigint()) - return @js_name@@js_suffix@.as_bigint(); -)~~~"); - } - - bool includes_enumeration = false; - for (auto& type : types) { - if (context.enumerations.contains(type->name())) { - includes_enumeration = true; - break; - } - } - - // If V is a string and types includes an enumeration type, attempt to convert the string to that enumeration. - // Example: Libraries/LibWeb/WebAudio/AudioContext.idl (AudioContextOptions.latencyHint: (AudioContextLatencyCategory or double)). - if (includes_enumeration) { - union_generator.append(R"~~~( - if (@js_name@@js_suffix@.is_string()) { - auto @js_name@@js_suffix@_enum_string = TRY(@js_name@@js_suffix@.to_string(vm)); -)~~~"); - - for (auto& type : types) { - if (!context.enumerations.contains(type->name())) - continue; - - auto& enumeration = context.enumerations.find(type->name())->value; - auto enum_type = IDL::idl_type_name_to_cpp_type(*type, context); - - auto enum_generator = union_generator.fork(); - enum_generator.set("enum.type", enum_type.name); - - for (auto& it : enumeration.translated_cpp_names) { - enum_generator.set("enum.alt.name", it.key); - enum_generator.set("enum.alt.value", it.value); - enum_generator.append(R"~~~( - if (@js_name@@js_suffix@_enum_string == "@enum.alt.name@"sv) - return @union_type@ { @enum.type@::@enum.alt.value@ }; -)~~~"); - } - } - - union_generator.append(R"~~~( - } -)~~~"); - } - - RefPtr string_type; - for (auto& type : types) { - if (type->is_string()) { - string_type = type; - break; - } - } - - if (string_type) { - // 14. If types includes a string type, then return the result of converting V to that type. - // NOTE: Currently all string types are converted to String. - - IDL::Parameter idl_parameter { .type = *string_type, .name = parameter.name, .optional_default_value = {}, .extended_attributes = parameter.extended_attributes }; - generate_to_cpp(union_generator, idl_parameter, js_name, js_suffix, ByteString::formatted("{}{}_string", js_name, js_suffix), context, includes, false, {}, false, recursion_depth + 1); - - union_generator.append(R"~~~( - return { @js_name@@js_suffix@_string }; -)~~~"); - } else if (numeric_type && includes_bigint) { - // 15. If types includes a numeric type and bigint, then return the result of converting V to either that numeric type or bigint. - // https://webidl.spec.whatwg.org/#converted-to-a-numeric-type-or-bigint - // NOTE: This algorithm is only used here. - - // An ECMAScript value V is converted to an IDL numeric type T or bigint value by running the following algorithm: - // 1. Let x be ? ToNumeric(V). - // 2. If Type(x) is BigInt, then - // 1. Return the IDL bigint value that represents the same numeric value as x. - // 3. Assert: Type(x) is Number. - // 4. Return the result of converting x to T. - - auto union_numeric_type_generator = union_generator.fork(); - - union_numeric_type_generator.append(R"~~~( - auto x = TRY(@js_name@@js_suffix@.to_numeric(vm)); - if (x.is_bigint()) - return x.as_bigint(); - VERIFY(x.is_number()); -)~~~"); - - // NOTE: generate_to_cpp doesn't use the parameter name. - // NOTE: generate_to_cpp will use to_{u32,etc.} which uses to_number internally and will thus use TRY, but it cannot throw as we know we are dealing with a number. - IDL::Parameter idl_parameter { .type = *numeric_type, .name = parameter.name, .optional_default_value = {}, .extended_attributes = {} }; - generate_to_cpp(union_numeric_type_generator, idl_parameter, "x", ByteString::empty(), "x_number", context, includes, false, {}, false, recursion_depth + 1); - - union_numeric_type_generator.append(R"~~~( - return x_number; -)~~~"); - } else if (numeric_type) { - // 16. If types includes a numeric type, then return the result of converting V to that numeric type. - - // NOTE: generate_to_cpp doesn't use the parameter name. - // NOTE: generate_to_cpp will use to_{u32,etc.} which uses to_number internally and will thus use TRY, but it cannot throw as we know we are dealing with a number. - IDL::Parameter idl_parameter { .type = *numeric_type, .name = parameter.name, .optional_default_value = {}, .extended_attributes = {} }; - generate_to_cpp(union_generator, idl_parameter, js_name, js_suffix, ByteString::formatted("{}{}_number", js_name, js_suffix), context, includes, false, {}, false, recursion_depth + 1); - - union_generator.append(R"~~~( - return { @js_name@@js_suffix@_number }; -)~~~"); - } else if (includes_boolean) { - // 17. If types includes boolean, then return the result of converting V to boolean. - union_generator.append(R"~~~( - return @union_type@ { @js_name@@js_suffix@.to_boolean() }; -)~~~"); - } else if (includes_bigint) { - // 18. If types includes bigint, then return the result of converting V to bigint. - union_generator.append(R"~~~( - return TRY(@js_name@@js_suffix@.to_bigint(vm)); -)~~~"); - } else { - // 19. Throw a TypeError. - // FIXME: Replace the error message with something more descriptive. - union_generator.append(R"~~~( - return vm.throw_completion("No union types matched"sv); -)~~~"); - } - - // Close the lambda and then perform the conversion. - union_generator.append(R"~~~( - }; -)~~~"); - - union_generator.append(R"~~~( - @union_type@ @cpp_name@ = TRY(@js_name@@js_suffix@_to_variant(@js_name@@js_suffix@)); -)~~~"); -} - -template -static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter, ByteString const& js_name, ByteString const& js_suffix, ByteString const& cpp_name, Context const& context, GeneratedIncludes& includes, bool optional, Optional optional_default_value, bool variadic, size_t recursion_depth, TypeOptionality optionality) -{ - auto scoped_generator = generator.fork(); - auto acceptable_cpp_name = make_input_acceptable_cpp(cpp_name); - auto legacy_null_to_empty_string = parameter.extended_attributes.contains("LegacyNullToEmptyString"sv); - scoped_generator.set("cpp_name", acceptable_cpp_name); - scoped_generator.set("js_name", js_name); - scoped_generator.set("js_suffix", js_suffix); - scoped_generator.set("legacy_null_to_empty_string", legacy_null_to_empty_string ? "true" : "false"); - - auto const& type = parameter.type; - scoped_generator.set("parameter.type.name", type->name()); - - auto normalized_parameter_type_name = cpp_type_name(*type, context); - scoped_generator.set("parameter.type.name.normalized", normalized_parameter_type_name); - - scoped_generator.set("parameter.name", parameter.name); - - if (variadic) { - generate_variadic_to_cpp(scoped_generator, parameter, acceptable_cpp_name, context, includes, recursion_depth); - return; - } - - if (optional) { - generate_optional_to_cpp(scoped_generator, parameter, js_name, js_suffix, acceptable_cpp_name, context, includes, optional_default_value, optionality, recursion_depth); - return; - } - - if (parameter.type->is_nullable()) { - generate_nullable_to_cpp(scoped_generator, parameter, js_name, js_suffix, acceptable_cpp_name, context, includes, recursion_depth); - return; - } - - if (parameter.type->is_string()) { - generate_to_string(scoped_generator, parameter, includes); - } else if (parameter.type->is_boolean() || parameter.type->is_integer()) { - generate_to_integral(scoped_generator, parameter, includes); - } else if (auto const* callback_interface = callback_interface_for_type(context, parameter.type)) { - generate_callback_interface_to_cpp(scoped_generator, *callback_interface, includes); - } else if (IDL::is_platform_object(context, *parameter.type)) { - generate_platform_object_to_cpp(scoped_generator, includes); - } else if (parameter.type->is_floating_point()) { - generate_floating_point_to_cpp(scoped_generator, *parameter.type); - } else if (parameter.type->name() == "Promise") { - generate_promise_to_cpp(scoped_generator, includes); - } else if (parameter.type->name() == "object") { - generate_object_to_cpp(scoped_generator); - } else if (parameter.type->is_buffer_source()) { - generate_buffer_source_to_cpp(scoped_generator, *parameter.type, parameter.extended_attributes, includes); - } else if (parameter.type->name() == "any") { - generate_any_to_cpp(scoped_generator); - } else if (context.enumerations.contains(parameter.type->name())) { - // NOTE: Attribute setters return undefined instead of throwing when the string doesn't match an enum value. - ThrowOnInvalidEnumValue throw_on_invalid = ThrowOnInvalidEnumValue::No; - if constexpr (!IsSame>) - throw_on_invalid = ThrowOnInvalidEnumValue::Yes; - scoped_generator.set("parameter.type.idl_value_conversion_function", idl_value_conversion_function_name(normalized_parameter_type_name)); - generate_enum_to_cpp(scoped_generator, context.enumerations.find(parameter.type->name())->value, throw_on_invalid); - } else if (context.dictionaries.contains(parameter.type->name())) { - scoped_generator.set("parameter.type.idl_value_conversion_function", idl_value_conversion_function_name(normalized_parameter_type_name)); - scoped_generator.append(R"~~~( - auto @cpp_name@ = TRY(@parameter.type.idl_value_conversion_function@(vm, @js_name@@js_suffix@)); -)~~~"); - } else if (context.callback_functions.contains(parameter.type->name())) { - auto& callback_function = context.callback_functions.find(parameter.type->name())->value; - generate_callback_function_to_cpp(scoped_generator, callback_function, includes); - } else if (parameter.type->name().is_one_of("sequence"sv, "FrozenArray"sv)) { - generate_sequence_to_cpp(scoped_generator, as(*parameter.type), js_name, js_suffix, acceptable_cpp_name, context, includes, recursion_depth); - } else if (parameter.type->name() == "record") { - generate_record_to_cpp(scoped_generator, as(*parameter.type), acceptable_cpp_name, context, includes, recursion_depth); - } else if (is(*parameter.type)) { - generate_union_to_cpp(scoped_generator, parameter, js_name, js_suffix, acceptable_cpp_name, context, includes, recursion_depth); - } else { - dbgln("Unimplemented JS-to-C++ conversion: {}", parameter.type->name()); - VERIFY_NOT_REACHED(); - } -} - -static void generate_argument_count_check(SourceGenerator& generator, ByteString const& function_name, size_t argument_count) -{ - if (argument_count == 0) - return; - - auto argument_count_check_generator = generator.fork(); - argument_count_check_generator.set("function.name", function_name); - argument_count_check_generator.set("function.nargs", ByteString::number(argument_count)); - - if (argument_count == 1) { - argument_count_check_generator.set(".bad_arg_count", "JS::ErrorType::BadArgCountOne"); - argument_count_check_generator.set(".arg_count_suffix", ""); - } else { - argument_count_check_generator.set(".bad_arg_count", "JS::ErrorType::BadArgCountMany"); - argument_count_check_generator.set(".arg_count_suffix", ByteString::formatted(", \"{}\"", argument_count)); - } - - argument_count_check_generator.append(R"~~~( - if (vm.argument_count() < @function.nargs@) - return vm.throw_completion(@.bad_arg_count@, "@function.name@"@.arg_count_suffix@); -)~~~"); -} - -static void generate_arguments(SourceGenerator& generator, Vector const& parameters, StringBuilder& arguments_builder, IDL::Interface const& interface, GeneratedIncludes& includes) -{ - auto arguments_generator = generator.fork(); - - Vector parameter_names; - size_t argument_index = 0; - for (auto& parameter : parameters) { - auto parameter_name = make_input_acceptable_cpp(parameter.name.to_snakecase()); - - if (parameter.variadic) { - // GC::RootVector is non-copyable, and the implementations likely want ownership of the - // list, so we move() it into the parameter list. - parameter_names.append(ByteString::formatted("move({})", parameter_name)); - } else { - parameter_names.append(move(parameter_name)); - - arguments_generator.set("argument.index", ByteString::number(argument_index)); - - arguments_generator.append(R"~~~( - auto arg@argument.index@ = vm.argument(@argument.index@); -)~~~"); - } - - generate_to_cpp(generator, parameter, "arg", ByteString::number(argument_index), parameter.name.to_snakecase(), interface.context, includes, parameter.optional, parameter.optional_default_value, parameter.variadic, 0, TypeOptionality::OptionalArgument); - ++argument_index; - } - - arguments_builder.join(", "sv, parameter_names); -} - -// https://webidl.spec.whatwg.org/#create-sequence-from-iterable -static void generate_sequence_from_iterable(SourceGenerator& generator, IDL::ParameterizedType const& type, ByteString const& cpp_name, ByteString const& iterable_cpp_name, ByteString const& iterator_method_cpp_name, IDL::Context const& context, GeneratedIncludes& includes, size_t recursion_depth) -{ - includes.add_header("LibJS/Runtime/Iterator.h"sv); - - auto sequence_generator = generator.fork(); - sequence_generator.set("cpp_name", cpp_name); - sequence_generator.set("iterable_cpp_name", iterable_cpp_name); - sequence_generator.set("iterator_method_cpp_name", iterator_method_cpp_name); - sequence_generator.set("recursion_depth", ByteString::number(recursion_depth)); - auto sequence_cpp_type = idl_type_name_to_cpp_type(type.parameters().first(), context); - sequence_generator.set("sequence.type", sequence_cpp_type.name); - sequence_generator.set("sequence.storage_type", contained_storage_type_to_cpp_name(sequence_cpp_type.contained_storage_type)); - - sequence_generator.append(R"~~~( - // To create an IDL value of type sequence given an iterable iterable and an iterator getter method, perform the following steps: - // 1. Let iteratorRecord be ? GetIteratorFromMethod(iterable, method). - auto @iterable_cpp_name@_iterator@recursion_depth@ = TRY(JS::get_iterator_from_method(vm, @iterable_cpp_name@, *@iterator_method_cpp_name@)); - - @sequence.storage_type@<@sequence.type@> @cpp_name@; - - // 2. Initialize i to be 0. - // 3. Repeat - for (;;) { - // 1. Let next be ? IteratorStepValue(iteratorRecord). - auto next@recursion_depth@ = TRY(JS::iterator_step_value(vm, @iterable_cpp_name@_iterator@recursion_depth@)); - - // 2. If next is done, then return an IDL sequence value of type sequence of length i, where the value of the element at index j is Sj. - if (!next@recursion_depth@.has_value()) - break; - - // 3. Initialize Si to the result of converting next to an IDL value of type T. - // 4. Set i to i + 1. - auto next_value@recursion_depth@ = next@recursion_depth@.release_value(); -)~~~"); - - // FIXME: Sequences types should be TypeWithExtendedAttributes, which would allow us to get [LegacyNullToEmptyString] here. - IDL::Parameter parameter { .type = type.parameters().first(), .name = iterable_cpp_name, .optional_default_value = {}, .extended_attributes = {} }; - generate_to_cpp(sequence_generator, parameter, "next_value", ByteString::number(recursion_depth), ByteString::formatted("sequence_item{}", recursion_depth), context, includes, false, {}, false, recursion_depth, TypeOptionality::OptionalArgument); - - sequence_generator.append(R"~~~( - @cpp_name@.append(sequence_item@recursion_depth@); - } -)~~~"); -} - -static void generate_wrap_statement(SourceGenerator& generator, ByteString const& value, IDL::Type const& type, Context const& context, GeneratedIncludes& includes, StringView result_expression, size_t recursion_depth = 0, bool is_optional = false, size_t iteration_index = 0) -{ - auto scoped_generator = generator.fork(); - scoped_generator.set("value", value); - auto value_cpp_name = value.replace("->"sv, "_"sv); - value_cpp_name = value_cpp_name.replace("."sv, "_"sv); - scoped_generator.set("value_cpp_name", value_cpp_name); - // Use one non-optional expression for wrapping. - // Some optional values are Optional and need .value(), while others are pointer-like and do not. - auto value_non_optional = value; - - bool optional_uses_value_access = false; - if (is_optional) { - optional_uses_value_access = is(type) - || type.is_string() - || type.name().is_one_of("sequence"sv, "FrozenArray"sv, "record"sv) - || type.is_primitive() - || context.enumerations.contains(type.name()) - || context.dictionaries.contains(type.name()); - } - - if (optional_uses_value_access) - value_non_optional = ByteString::formatted("{}.value()", value); - scoped_generator.set("value_non_optional", value_non_optional); - scoped_generator.set("type", cpp_type_name(type, context)); - scoped_generator.set("result_expression", result_expression); - scoped_generator.set("recursion_depth", ByteString::number(recursion_depth)); - scoped_generator.set("iteration_index", ByteString::number(iteration_index)); - - if (type.name() == "undefined") { - scoped_generator.append(R"~~~( - @result_expression@ JS::js_undefined(); -)~~~"); - return; - } - - bool generate_optional_integral_type = false; - if ((is_optional || type.is_nullable()) && !is(type)) { - generate_optional_integral_type = true; - if (type.is_string()) { - scoped_generator.append(R"~~~( - if (@value@.has_value()) { -)~~~"); - } else if (type.name().is_one_of("sequence"sv, "FrozenArray"sv, "record"sv)) { - generate_optional_integral_type = true; - scoped_generator.append(R"~~~( - if (@value@.has_value()) { -)~~~"); - } else if (type.is_primitive() || context.enumerations.contains(type.name()) || context.dictionaries.contains(type.name())) { - generate_optional_integral_type = true; - scoped_generator.append(R"~~~( - if (@value@.has_value()) { -)~~~"); - } else { - scoped_generator.append(R"~~~( - if (@value@) { -)~~~"); - } - } - if (is_optional && is(type)) { - scoped_generator.append(R"~~~( - if (@value@.has_value()) { -)~~~"); - } - - if (type.is_string()) { - if (type.is_nullable() || is_optional) { - scoped_generator.append(R"~~~( - @result_expression@ JS::PrimitiveString::create(vm, @value@.value()); -)~~~"); - } else { - scoped_generator.append(R"~~~( - @result_expression@ JS::PrimitiveString::create(vm, @value@); -)~~~"); - } - } else if (type.name().is_one_of("sequence"sv, "FrozenArray"sv)) { - includes.add_header("LibJS/Runtime/Array.h"sv); - // https://webidl.spec.whatwg.org/#js-sequence - // https://webidl.spec.whatwg.org/#js-frozen-array - auto& sequence_generic_type = as(type); - - scoped_generator.append(R"~~~( - auto new_array@recursion_depth@_@iteration_index@ = MUST(JS::Array::create(realm, 0)); -)~~~"); - - if (type.is_nullable() || is_optional) { - scoped_generator.append(R"~~~( - auto& @value_cpp_name@_non_optional = @value@.value(); - for (size_t i@recursion_depth@ = 0; i@recursion_depth@ < @value_cpp_name@_non_optional.size(); ++i@recursion_depth@) { - auto& element@recursion_depth@ = @value_cpp_name@_non_optional.at(i@recursion_depth@); -)~~~"); - } else { - scoped_generator.append(R"~~~( - for (size_t i@recursion_depth@ = 0; i@recursion_depth@ < @value@.size(); ++i@recursion_depth@) { - auto& element@recursion_depth@ = @value@.at(i@recursion_depth@); -)~~~"); - } - - // If the type is a platform object we currently return a GC-aware vector from the - // C++ implementation, thus allowing us to unwrap the element like below. - if (is_platform_object(context, sequence_generic_type.parameters().first())) { - scoped_generator.append(R"~~~( - auto* wrapped_element@recursion_depth@ = &(*element@recursion_depth@); -)~~~"); - } else { - scoped_generator.append("JS::Value wrapped_element@recursion_depth@;\n"sv); - generate_wrap_statement(scoped_generator, ByteString::formatted("element{}", recursion_depth), sequence_generic_type.parameters().first(), context, includes, ByteString::formatted("wrapped_element{} =", recursion_depth), recursion_depth + 1); - } - - scoped_generator.append(R"~~~( - auto property_index@recursion_depth@ = JS::PropertyKey { i@recursion_depth@ }; - MUST(new_array@recursion_depth@_@iteration_index@->create_data_property(property_index@recursion_depth@, wrapped_element@recursion_depth@)); - } -)~~~"); - - if (type.name() == "FrozenArray"sv) { - scoped_generator.append(R"~~~( - TRY(new_array@recursion_depth@_@iteration_index@->set_integrity_level(JS::Object::IntegrityLevel::Frozen)); -)~~~"); - } - - scoped_generator.append(R"~~~( - @result_expression@ new_array@recursion_depth@_@iteration_index@; -)~~~"); - } else if (type.name() == "record") { - // https://webidl.spec.whatwg.org/#es-record - - auto& parameterized_type = as(type); - VERIFY(parameterized_type.parameters().size() == 2); - VERIFY(parameterized_type.parameters()[0]->is_string()); - - scoped_generator.append(R"~~~( - { - // An IDL record<…> value D is converted to a JavaScript value as follows: - // 1. Let result be OrdinaryObjectCreate(%Object.prototype%). - auto result = JS::Object::create(realm, realm.intrinsics().object_prototype()); - - // 2. For each key → value of D: - for (auto const& [key, value] : @value_non_optional@) { - // 1. Let jsKey be key converted to a JavaScript value. - auto js_key = JS::PropertyKey { Utf16FlyString::from_utf8(key) }; - - // 2. Let jsValue be value converted to a JavaScript value. -)~~~"); - generate_wrap_statement(scoped_generator, "value"sv, parameterized_type.parameters()[1], context, includes, "auto js_value ="sv, recursion_depth + 1); - scoped_generator.append(R"~~~( - - // 3. Let created be ! CreateDataProperty(result, jsKey, jsValue). - bool created = MUST(result->create_data_property(js_key, js_value)); - - // 4. Assert: created is true. - VERIFY(created); - } - - @result_expression@ result; - } -)~~~"); - } else if (type.name() == "boolean" || type.is_floating_point()) { - if (type.is_nullable() && is_optional) { - scoped_generator.append(R"~~~( - if (@value_non_optional@.has_value()) { - @result_expression@ JS::Value(@value_non_optional@.release_value()); - } else { - @result_expression@ JS::js_null(); - } -)~~~"); - } else if (type.is_nullable()) { - scoped_generator.append(R"~~~( - @result_expression@ JS::Value(@value@.release_value()); -)~~~"); - } else if (is_optional) { - scoped_generator.append(R"~~~( - @result_expression@ JS::Value(@value_non_optional@); -)~~~"); - } else { - scoped_generator.append(R"~~~( - @result_expression@ JS::Value(@value@); -)~~~"); - } - } else if (type.is_integer()) { - generate_from_integral(scoped_generator, type, generate_optional_integral_type); - } else if (type.name() == "any") { - scoped_generator.append(R"~~~( - @result_expression@ @value_non_optional@; -)~~~"); - } else if (type.name() == "Promise") { - includes.add_header("LibWeb/WebIDL/Promise.h"sv); - scoped_generator.append(R"~~~( - @result_expression@ GC::Ref { as(*@value_non_optional@->promise()) }; -)~~~"); - } else if (is(type)) { - auto& union_type = as(type); - auto union_types = union_type.flattened_member_types(); - auto union_generator = scoped_generator.fork(); - - union_generator.append(R"~~~( - @result_expression@ @value_non_optional@.visit( -)~~~"); - - for (size_t current_union_type_index = 0; current_union_type_index < union_types.size(); ++current_union_type_index) { - auto& current_union_type = union_types.at(current_union_type_index); - auto cpp_type = idl_type_name_to_cpp_type(current_union_type, context); - union_generator.set("current_type", cpp_type.name); - union_generator.append(R"~~~( - [&vm, &realm]([[maybe_unused]] @current_type@ const& visited_union_value@recursion_depth@) -> JS::Value { - // These may be unused. - (void)vm; - (void)realm; -)~~~"); - - // NOTE: While we are using const&, the underlying type for wrappable types in unions is (Nonnull)RefPtr, which are not references. - generate_wrap_statement(union_generator, ByteString::formatted("visited_union_value{}", recursion_depth), current_union_type, context, includes, "return"sv, recursion_depth + 1); - - // End of current visit lambda. - // The last lambda cannot have a trailing comma on the closing brace, unless the type includes a nullable type, where an extra lambda will be generated for the Empty case. - if (current_union_type_index != union_types.size() - 1 || union_type.includes_nullable_type()) { - union_generator.append(R"~~~( - }, -)~~~"); - } else { - union_generator.append(R"~~~( - } -)~~~"); - } - } - - if (union_type.includes_nullable_type()) { - union_generator.append(R"~~~( - [](Empty) -> JS::Value { - return JS::js_null(); - } -)~~~"); - } - - // End of visit. - union_generator.append(R"~~~( - ); -)~~~"); - } else if (context.enumerations.contains(type.name())) { - // Handle Enum? values, which were null-checked above - if (type.is_nullable() || is_optional) - scoped_generator.set("value", ByteString::formatted("{}.value()", value)); - scoped_generator.append(R"~~~( - @result_expression@ JS::PrimitiveString::create(vm, idl_enum_to_string(@value@)); -)~~~"); - } else if (context.callback_functions.contains(type.name())) { - // https://webidl.spec.whatwg.org/#es-callback-function - - auto& callback_function = context.callback_functions.find(type.name())->value; - - // The result of converting an IDL callback function type value to an ECMAScript value is a reference to the same object that the IDL callback function type value represents. - - if (callback_function.is_legacy_treat_non_object_as_null && !type.is_nullable()) { - scoped_generator.append(R"~~~( - if (!@value_non_optional@) { - @result_expression@ JS::js_null(); - } else { - @result_expression@ @value_non_optional@->callback; - } -)~~~"); - } else { - scoped_generator.append(R"~~~( - @result_expression@ @value_non_optional@->callback; -)~~~"); - } - } else if (callback_interface_for_type(context, type)) { - scoped_generator.append(R"~~~( - @result_expression@ @value@->callback().callback; -)~~~"); - } else if (context.dictionaries.contains(type.name())) { - // https://webidl.spec.whatwg.org/#es-dictionary - auto dictionary_generator = scoped_generator.fork(); - - dictionary_generator.append(R"~~~( - { - auto dictionary_object@recursion_depth@ = JS::Object::create(realm, realm.intrinsics().object_prototype()); -)~~~"); - - auto next_iteration_index = iteration_index + 1; - auto* current_dictionary = &context.dictionaries.find(type.name())->value; - while (true) { - for (auto& member : current_dictionary->members) { - dictionary_generator.set("member_key", member.name); - auto member_key_js_name = ByteString::formatted("{}{}", make_input_acceptable_cpp(member.name.to_snakecase()), recursion_depth); - dictionary_generator.set("member_name", member_key_js_name); - auto member_value_js_name = ByteString::formatted("{}_value", member_key_js_name); - dictionary_generator.set("member_value", member_value_js_name); - - auto wrapped_value_name = ByteString::formatted("wrapped_{}", member_value_js_name); - dictionary_generator.set("wrapped_value_name", wrapped_value_name); - - bool is_optional = dictionary_member_uses_optional(context, member); - if (is_optional) { - dictionary_generator.append(R"~~~( - Optional @wrapped_value_name@; -)~~~"); - } else { - dictionary_generator.append(R"~~~( - JS::Value @wrapped_value_name@; -)~~~"); - } - - next_iteration_index++; - generate_wrap_statement(dictionary_generator, ByteString::formatted("{}{}{}", value_non_optional, type.is_nullable() ? "->" : ".", member.name.to_snakecase()), member.type, context, includes, ByteString::formatted("{} =", wrapped_value_name), recursion_depth + 1, is_optional, next_iteration_index); - - if (is_optional) { - dictionary_generator.append(R"~~~( - if (@wrapped_value_name@.has_value()) - MUST(dictionary_object@recursion_depth@->create_data_property("@member_key@"_utf16_fly_string, @wrapped_value_name@.release_value())); -)~~~"); - } else { - dictionary_generator.append(R"~~~( - MUST(dictionary_object@recursion_depth@->create_data_property("@member_key@"_utf16_fly_string, @wrapped_value_name@)); -)~~~"); - } - } - - if (current_dictionary->parent_name.is_empty()) - break; - VERIFY(context.dictionaries.contains(current_dictionary->parent_name)); - current_dictionary = &context.dictionaries.find(current_dictionary->parent_name)->value; - } - - dictionary_generator.append(R"~~~( - @result_expression@ dictionary_object@recursion_depth@; - } -)~~~"); - } else if (type.name() == "object") { - scoped_generator.append(R"~~~( - @result_expression@ JS::Value(const_cast(@value_non_optional@)); -)~~~"); - } else { - if (type.is_buffer_source()) - add_javascript_builtin_buffer_source_type_include(type, includes); - scoped_generator.append(R"~~~( - @result_expression@ &const_cast<@type@&>(*@value_non_optional@); -)~~~"); - } - - if (type.is_nullable() && !is(type) && !is_optional) { - scoped_generator.append(R"~~~( - } else { - @result_expression@ JS::js_null(); - } -)~~~"); - } else if (is_optional) { - // Optional return values should not be assigned any value (including null) if the value is not present. - scoped_generator.append(R"~~~( - } -)~~~"); - } -} - -enum class StaticFunction { - No, - Yes, -}; - -enum class IsConstructor { - No, - Yes -}; - -static void generate_return_statement(SourceGenerator& generator, IDL::Type const& return_type, IDL::Interface const& interface, GeneratedIncludes& includes) -{ - generate_wrap_statement(generator, "retval", return_type, interface.context, includes, "return"sv); -} - -static void generate_variable_statement(SourceGenerator& generator, ByteString const& variable_name, IDL::Type const& value_type, ByteString const& value_name, IDL::Interface const& interface, GeneratedIncludes& includes) -{ - auto variable_generator = generator.fork(); - variable_generator.set("variable_name", variable_name); - variable_generator.append(R"~~~( - JS::Value @variable_name@; -)~~~"); - generate_wrap_statement(generator, value_name, value_type, interface.context, includes, ByteString::formatted("{} = ", variable_name)); -} - -static void generate_function(SourceGenerator& generator, IDL::Function const& function, StaticFunction is_static_function, ByteString const& class_name, ByteString const& interface_fully_qualified_name, IDL::Interface const& interface, GeneratedIncludes& includes) -{ - includes.add_header("LibWeb/WebIDL/Tracing.h"sv); - collect_include_dependencies(interface.context, function, includes); - - auto function_generator = generator.fork(); - function_generator.set("class_name", class_name); - function_generator.set("interface_fully_qualified_name", interface_fully_qualified_name); - function_generator.set("function.name", function.name); - function_generator.set("function.name:snakecase", make_input_acceptable_cpp(function.name.to_snakecase())); - function_generator.set("overload_suffix", function.is_overloaded ? ByteString::number(function.overload_index) : ByteString::empty()); - - if (function.extended_attributes.contains("ImplementedAs")) { - auto implemented_as = function.extended_attributes.get("ImplementedAs").value(); - function_generator.set("function.cpp_name", implemented_as); - } else { - function_generator.set("function.cpp_name", make_input_acceptable_cpp(function.name.to_snakecase())); - } - - function_generator.append(R"~~~( -JS_DEFINE_NATIVE_FUNCTION(@class_name@::@function.name:snakecase@@overload_suffix@) -{ - WebIDL::log_trace(vm, "@class_name@::@function.name:snakecase@@overload_suffix@"); - [[maybe_unused]] auto& realm = *vm.current_realm(); -)~~~"); - - // NOTE: Create a wrapper lambda so that if the function steps return an exception, we can return that in a rejected promise. - if (function.return_type->name() == "Promise"sv) { - includes.add_header("LibWeb/WebIDL/Promise.h"sv); - function_generator.append(R"~~~( - auto steps = [&realm, &vm]() -> JS::ThrowCompletionOr> { - (void)realm; -)~~~"); - } - - if (is_static_function == StaticFunction::No) { - function_generator.append(R"~~~( - auto* impl = TRY(impl_from(vm)); -)~~~"); - } - - // Optimization: overloaded functions' arguments count is checked by the overload arbiter - if (!function.is_overloaded) - generate_argument_count_check(generator, function.name, function.shortest_length()); - - StringBuilder arguments_builder; - generate_arguments(generator, function.parameters, arguments_builder, interface, includes); - function_generator.set(".arguments", arguments_builder.string_view()); - - if (is_static_function == StaticFunction::No) { - // For [CEReactions]: https://html.spec.whatwg.org/multipage/custom-elements.html#cereactions - - if (function.extended_attributes.contains("CEReactions")) { - includes.add_header("LibWeb/HTML/Scripting/SimilarOriginWindowAgent.h"sv); - - // 1. Push a new element queue onto this object's relevant agent's custom element reactions stack. - function_generator.append(R"~~~( - auto& reactions_stack = HTML::relevant_similar_origin_window_agent(*impl).custom_element_reactions_stack; - reactions_stack.element_queue_stack.append({}); -)~~~"); - } - - if (!function.extended_attributes.contains("CEReactions")) { - function_generator.append(R"~~~( - [[maybe_unused]] auto retval = TRY(throw_dom_exception_if_needed(vm, [&] { return impl->@function.cpp_name@(@.arguments@); })); -)~~~"); - } else { - includes.add_header("LibWeb/Bindings/MainThreadVM.h"sv); - // 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. - // 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. - // 5. If an exception exception was thrown by the original steps, rethrow exception. - // 6. If a value value was returned from the original steps, return value. - function_generator.append(R"~~~( - auto retval_or_exception = throw_dom_exception_if_needed(vm, [&] { return impl->@function.cpp_name@(@.arguments@); }); - - auto queue = reactions_stack.element_queue_stack.take_last(); - Bindings::invoke_custom_element_reactions(queue); - - if (retval_or_exception.is_error()) - return retval_or_exception.release_error(); - - [[maybe_unused]] auto retval = retval_or_exception.release_value(); -)~~~"); - } - } else { - // Make sure first argument for static functions is the Realm. - if (arguments_builder.is_empty()) - function_generator.set(".arguments", "vm"); - else - function_generator.set(".arguments", ByteString::formatted("vm, {}", arguments_builder.string_view())); - - function_generator.append(R"~~~( - [[maybe_unused]] auto retval = TRY(throw_dom_exception_if_needed(vm, [&] { return @interface_fully_qualified_name@::@function.cpp_name@(@.arguments@); })); -)~~~"); - } - - if (function.return_type->name() == "Promise"sv) { - // https://webidl.spec.whatwg.org/#dfn-create-operation-function - // If we had an exception running the steps and are meant to return a Promise, wrap that exception in a rejected promise. - function_generator.append(R"~~~( - return retval; - }; - - auto maybe_retval = 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. - // NOTE: We know that this is a Promise return type statically by the IDL. - if (maybe_retval.is_throw_completion()) - return WebIDL::create_rejected_promise(realm, maybe_retval.error_value())->promise(); - - auto retval = maybe_retval.release_value(); -)~~~"); - } - - generate_return_statement(generator, *function.return_type, interface, includes); - - function_generator.append(R"~~~( -} -)~~~"); -} - -// https://webidl.spec.whatwg.org/#compute-the-effective-overload-set -static Vector compute_the_effective_overload_set(auto const& overload_set) -{ - // 1. Let S be an ordered set. - Vector overloads; - - // 2. Let F be an ordered set with items as follows, according to the kind of effective overload set: - // Note: This is determined by the caller of generate_overload_arbiter() - - // 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. - auto overloaded_functions = overload_set.value; - auto maximum_arguments = 0; - for (auto const& function : overloaded_functions) - maximum_arguments = max(maximum_arguments, static_cast(function.parameters.size())); - - // 4. Let max be max(maxarg, N). - // NOTE: We don't do this step. `N` is a runtime value, so we just use `maxarg` here instead. - // Later, `generate_overload_arbiter()` produces individual overload sets for each possible N. - - // 5. For each operation or extended attribute X in F: - auto overload_id = 0; - for (auto const& overload : overloaded_functions) { - // 1. Let arguments be the list of arguments X is declared to take. - auto const& arguments = overload.parameters; - - // 2. Let n be the size of arguments. - int argument_count = (int)arguments.size(); - - // 3. Let types be a type list. - Vector> types; - - // 4. Let optionalityValues be an optionality list. - Vector optionality_values; - - bool overload_is_variadic = false; - - // 5. For each argument in arguments: - for (auto const& argument : 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; - } else if (argument.optional) { - optionality_values.append(Optionality::Optional); - } else { - optionality_values.append(Optionality::Required); - } - } - - // 6. Append the tuple (X, types, optionalityValues) to S. - overloads.empend(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 (auto i = argument_count; i < maximum_arguments; ++i) { - // 1. Let t be a type list. - // 2. Let o be an optionality list. - // NOTE: We hold both of these in an Item instead. - EffectiveOverloadSet::Item item; - item.callable_id = overload_id; - - // 3. For each j in the range 0 to n − 1, inclusive: - for (auto j = 0; j < argument_count; ++j) { - // 1. Append types[j] to t. - item.types.append(types[j]); - - // 2. Append optionalityValues[j] to o. - item.optionality_values.append(optionality_values[j]); - } - - // 4. For each j in the range n to i, inclusive: - for (auto j = argument_count; j <= i; ++j) { - // 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(move(item)); - } - } - - // 8. Let i be n − 1. - auto i = argument_count - 1; - - // 9. While i ≥ 0: - while (i >= 0) { - // 1. If arguments[i] is not optional (i.e., it is not marked as "optional" and is not a final, variadic argument), then break. - if (!arguments[i].optional && !arguments[i].variadic) - break; - - // 2. Let t be a type list. - // 3. Let o be an optionality list. - // NOTE: We hold both of these in an Item instead. - EffectiveOverloadSet::Item item; - item.callable_id = overload_id; - - // 4. For each j in the range 0 to i − 1, inclusive: - for (auto j = 0; j < i; ++j) { - // 1. Append types[j] to t. - item.types.append(types[j]); - - // 2. Append optionalityValues[j] to o. - item.optionality_values.append(optionality_values[j]); - } - - // 5. Append the tuple (X, t, o) to S. - overloads.append(move(item)); - - // 6. Set i to i − 1. - --i; - } - - overload_id++; - } - - return overloads; -} - -static ByteString generate_constructor_for_idl_type(Type const& type) -{ - auto append_type_list = [](auto& builder, auto const& type_list) { - bool first = true; - for (auto const& child_type : type_list) { - if (first) { - first = false; - } else { - builder.append(", "sv); - } - - builder.append(generate_constructor_for_idl_type(child_type)); - } - }; - - switch (type.kind()) { - case Type::Kind::Plain: - return ByteString::formatted("make_ref_counted(\"{}\", {})", type.name(), type.is_nullable()); - case Type::Kind::Parameterized: { - auto const& parameterized_type = type.as_parameterized(); - StringBuilder builder; - builder.appendff("make_ref_counted(\"{}\", {}, Vector> {{", type.name(), type.is_nullable()); - append_type_list(builder, parameterized_type.parameters()); - builder.append("})"sv); - return builder.to_byte_string(); - } - case Type::Kind::Union: { - auto const& union_type = type.as_union(); - StringBuilder builder; - builder.appendff("make_ref_counted(\"{}\", {}, Vector> {{", type.name(), type.is_nullable()); - append_type_list(builder, union_type.member_types()); - builder.append("})"sv); - return builder.to_byte_string(); - } - } - - VERIFY_NOT_REACHED(); -} - -// https://webidl.spec.whatwg.org/#dfn-distinguishing-argument-index -static size_t resolve_distinguishing_argument_index(Interface const& interface, Vector const& items, size_t argument_count) -{ - for (auto argument_index = 0u; argument_index < argument_count; ++argument_index) { - bool found_indistinguishable = false; - - for (auto first_item_index = 0u; first_item_index < items.size(); ++first_item_index) { - for (auto second_item_index = first_item_index + 1; second_item_index < items.size(); ++second_item_index) { - if (!items[first_item_index].types[argument_index]->is_distinguishable_from(interface, items[second_item_index].types[argument_index])) { - found_indistinguishable = true; - break; - } - } - if (found_indistinguishable) - break; - } - - if (!found_indistinguishable) - return argument_index; - } - - VERIFY_NOT_REACHED(); -} - -static void generate_dictionary_types(SourceGenerator& generator, Vector const& dictionary_types) -{ - generator.append(R"~~~( - Vector dictionary_types { -)~~~"); - - for (auto const& dictionary : dictionary_types) { - generator.append(" \""); - generator.append(dictionary); - generator.appendln("\"sv,"); - } - - generator.append("};\n"); -} - -enum class SharedConstructor { - No, - Yes, -}; - -static void generate_overload_arbiter(SourceGenerator& generator, auto const& overload_set, IDL::Interface const& interface, ByteString const& class_name, IsConstructor is_constructor, GeneratedIncludes& includes, SharedConstructor shared_constructor = SharedConstructor::No) -{ - includes.add_header("LibWeb/WebIDL/Tracing.h"sv); - includes.add_header("LibIDL/Types.h"sv); - includes.add_header("LibWeb/WebIDL/OverloadResolution.h"sv); - - auto function_generator = generator.fork(); - if (is_constructor == IsConstructor::Yes) - function_generator.set("constructor_class", class_name); - else - function_generator.set("class_name", class_name); - - function_generator.set("function.name:snakecase", make_input_acceptable_cpp(overload_set.key.to_snakecase())); - - HashTable dictionary_types; - - if (is_constructor == IsConstructor::Yes && shared_constructor == SharedConstructor::Yes) { - function_generator.append(R"~~~( -JS::ThrowCompletionOr> @constructor_class@::construct(InterfaceConstructor& constructor, JS::FunctionObject& new_target) -{ - auto& vm = constructor.vm(); - WebIDL::log_trace(vm, "@constructor_class@::construct"); -)~~~"); - } else if (is_constructor == IsConstructor::Yes) { - function_generator.append(R"~~~( -JS::ThrowCompletionOr> @constructor_class@::construct(JS::FunctionObject& new_target) -{ - auto& vm = this->vm(); - WebIDL::log_trace(vm, "@constructor_class@::construct"); -)~~~"); - } else { - function_generator.append(R"~~~( -JS_DEFINE_NATIVE_FUNCTION(@class_name@::@function.name:snakecase@) -{ - WebIDL::log_trace(vm, "@class_name@::@function.name:snakecase@"); -)~~~"); - } - - function_generator.append(R"~~~( - Optional chosen_overload_callable_id; - Optional effective_overload_set; -)~~~"); - - auto overloads_set = compute_the_effective_overload_set(overload_set); - auto maximum_argument_count = 0u; - for (auto const& overload : overloads_set) - maximum_argument_count = max(maximum_argument_count, overload.types.size()); - function_generator.set("max_argument_count", ByteString::number(maximum_argument_count)); - function_generator.appendln(" switch (min(@max_argument_count@, vm.argument_count())) {"); - - // Generate the effective overload set for each argument count. - // This skips part of the Overload Resolution Algorithm https://webidl.spec.whatwg.org/#es-overloads - // Namely, since that discards any overloads that don't have the exact number of arguments that were given, - // we simply only provide the overloads that do have that number of arguments. - for (auto argument_count = 0u; argument_count <= maximum_argument_count; ++argument_count) { - Vector effective_overload_set; - for (auto const& overload : overloads_set) { - if (overload.types.size() == argument_count) - effective_overload_set.append(overload); - } - - if (effective_overload_set.size() == 0) - continue; - - auto distinguishing_argument_index = 0u; - if (effective_overload_set.size() > 1) - distinguishing_argument_index = resolve_distinguishing_argument_index(interface, effective_overload_set, argument_count); - - function_generator.set("current_argument_count", ByteString::number(argument_count)); - - // When there's only a single overload for this argument count, we can skip constructing an EffectiveOverloadSet - // and calling resolve_overload() entirely, avoiding multiple heap allocations. - if (effective_overload_set.size() == 1) { - for (auto const& type : effective_overload_set[0].types) { - if (interface.context.dictionaries.contains(type->name())) - dictionary_types.set(type->name()); - } - - function_generator.set("overload.callable_id", ByteString::number(effective_overload_set[0].callable_id)); - function_generator.appendln(R"~~~( - case @current_argument_count@: - chosen_overload_callable_id = @overload.callable_id@; - break; -)~~~"); - } else { - function_generator.set("overload_count", ByteString::number(effective_overload_set.size())); - function_generator.appendln(R"~~~( - case @current_argument_count@: { - Vector overloads; - overloads.ensure_capacity(@overload_count@); -)~~~"); - - for (auto& overload : effective_overload_set) { - StringBuilder types_builder; - types_builder.append("Vector> { "sv); - StringBuilder optionality_builder; - optionality_builder.append("Vector { "sv); - - for (auto i = 0u; i < overload.types.size(); ++i) { - if (i > 0) { - types_builder.append(", "sv); - optionality_builder.append(", "sv); - } - - auto const& type = overload.types[i]; - if (interface.context.dictionaries.contains(type->name())) - dictionary_types.set(type->name()); - - types_builder.append(generate_constructor_for_idl_type(overload.types[i])); - - optionality_builder.append("IDL::Optionality::"sv); - switch (overload.optionality_values[i]) { - case Optionality::Required: - optionality_builder.append("Required"sv); - break; - case Optionality::Optional: - optionality_builder.append("Optional"sv); - break; - case Optionality::Variadic: - optionality_builder.append("Variadic"sv); - break; - } - } - - types_builder.append("}"sv); - optionality_builder.append("}"sv); - - function_generator.set("overload.callable_id", ByteString::number(overload.callable_id)); - function_generator.set("overload.types", types_builder.to_byte_string()); - function_generator.set("overload.optionality_values", optionality_builder.to_byte_string()); - - function_generator.appendln(" overloads.empend(@overload.callable_id@, @overload.types@, @overload.optionality_values@);"); - } - - function_generator.set("overload_set.distinguishing_argument_index", ByteString::number(distinguishing_argument_index)); - function_generator.append(R"~~~( - effective_overload_set.emplace(move(overloads), @overload_set.distinguishing_argument_index@); - break; - } -)~~~"); - } - } - - function_generator.append(R"~~~( - } -)~~~"); - - generate_dictionary_types(function_generator, dictionary_types.values()); - - function_generator.append(R"~~~( - - if (!chosen_overload_callable_id.has_value()) { - if (!effective_overload_set.has_value()) - return vm.throw_completion(JS::ErrorType::OverloadResolutionFailed); - chosen_overload_callable_id = TRY(WebIDL::resolve_overload(vm, effective_overload_set.value(), dictionary_types)).callable_id; - } - - switch (chosen_overload_callable_id.value()) { -)~~~"); - - for (auto i = 0u; i < overload_set.value.size(); ++i) { - function_generator.set("overload_id", ByteString::number(i)); - function_generator.append(R"~~~( - case @overload_id@: -)~~~"); - if (is_constructor == IsConstructor::Yes) { - if (shared_constructor == SharedConstructor::Yes) { - function_generator.append(R"~~~( - return construct@overload_id@(constructor, new_target); -)~~~"); - } else { - function_generator.append(R"~~~( - return construct@overload_id@(new_target); -)~~~"); - } - } else { - function_generator.append(R"~~~( - return @function.name:snakecase@@overload_id@(vm); -)~~~"); - } - } - - function_generator.append(R"~~~( - default: - VERIFY_NOT_REACHED(); - } -} -)~~~"); -} - -static void generate_html_constructor(SourceGenerator& generator, IDL::Constructor const& constructor, IDL::Interface const& interface, GeneratedIncludes& includes) -{ - includes.add_header("LibWeb/DOM/Document.h"sv); - includes.add_header("LibWeb/DOM/ElementFactory.h"sv); - includes.add_header("LibWeb/HTML/Window.h"sv); - includes.add_header("LibWeb/HTML/CustomElements/CustomElementDefinition.h"sv); - includes.add_header("LibWeb/HTML/CustomElements/CustomElementRegistry.h"sv); - includes.add_header("LibWeb/HTML/Scripting/SimilarOriginWindowAgent.h"sv); - includes.add_header("LibWeb/WebIDL/AbstractOperations.h"sv); - - auto constructor_generator = generator.fork(); - // NOTE: A HTMLConstrcuctor must not have any parameters. - constructor_generator.set("constructor.length", "0"); - - // https://html.spec.whatwg.org/multipage/dom.html#html-element-constructors - // NOTE: The active function object in this context is always going to be the current constructor that has just been called. - - // The [HTMLConstructor] extended attribute must take no arguments, and must only appear on constructor operations. It must - // appear only once on a constructor operation, and the interface must contain only the single, annotated constructor - // operation, and no others - if (interface.constructors.size() != 1) { - dbgln("Interface {}'s constructor annotated with [HTMLConstructor] must be the only constructor", interface.name); - VERIFY_NOT_REACHED(); - } - - if (!constructor.parameters.is_empty()) { - dbgln("Interface {}'s constructor marked with [HTMLConstructor] must not have any parameters", interface.name); - VERIFY_NOT_REACHED(); - } - - constructor_generator.append(R"~~~( - auto& window = as(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("Cannot directly construct an HTML element, it must be inherited"sv); - - // 2. Let registry be null. - GC::Ptr 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("There is no custom element definition assigned to the given constructor"sv); - - // 6. Let isValue be null. - Optional 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. -)~~~"); - - if (interface.name != "HTMLElement") { - constructor_generator.append(R"~~~( - return vm.throw_completion("Autonomous custom elements can only inherit from HTMLElement"sv); -)~~~"); - } else { - constructor_generator.append(R"~~~( - // Do nothing, as this is the HTMLElement constructor. -)~~~"); - } - - constructor_generator.append(R"~~~( - } - - // 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("@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(MUST(String::formatted("Local name '{}' of customized built-in element is not a valid local name for @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@>(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<@prototype_class@>(vm, new_target, "@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<@prototype_class@>(*function_realm, "@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()) - return vm.throw_completion("Custom element has already been constructed"sv); - - // 14. Perform ? element.[[SetPrototypeOf]](prototype). - auto actual_element = element.get>(); - 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; -} -)~~~"); -} - -static void generate_constructor(SourceGenerator& generator, IDL::Constructor const& constructor, IDL::Interface const& interface, bool is_html_constructor, GeneratedIncludes& includes) -{ - includes.add_header("LibWeb/Bindings/Intrinsics.h"sv); - includes.add_header("LibWeb/WebIDL/Tracing.h"sv); - includes.add_header("LibJS/Runtime/AbstractOperations.h"sv); - collect_include_dependencies(interface.context, constructor.parameters, includes); - - auto constructor_generator = generator.fork(); - constructor_generator.set("name", interface.name); - constructor_generator.set("namespaced_name", interface.namespaced_name); - constructor_generator.set("prototype_class", interface.prototype_class); - constructor_generator.set("constructor_class", interface.constructor_class); - constructor_generator.set("fully_qualified_name", interface.fully_qualified_name); - constructor_generator.set("interface_fully_qualified_name", interface.fully_qualified_name); - constructor_generator.set("overload_suffix", constructor.is_overloaded ? ByteString::number(constructor.overload_index) : ByteString::empty()); - - constructor_generator.append(R"~~~( -JS::ThrowCompletionOr> @constructor_class@::construct@overload_suffix@([[maybe_unused]] FunctionObject& new_target) -{ - WebIDL::log_trace(vm(), "@constructor_class@::construct@overload_suffix@"); -)~~~"); - - generator.append(R"~~~( - auto& vm = this->vm(); - auto& realm = *vm.current_realm(); -)~~~"); - - if (is_html_constructor) { - generate_html_constructor(generator, constructor, interface, includes); - } else { - generator.append(R"~~~( - // To internally create a new object implementing the 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<@prototype_class@>(*target_realm, "@namespaced_name@"_fly_string); - } - - // 4. Let instance be MakeBasicObject( « [[Prototype]], [[Extensible]], [[Realm]], [[PrimaryInterface]] »). - // 5. Set instance.[[Realm]] to realm. - // 6. Set instance.[[PrimaryInterface]] to interface. -)~~~"); - if (!constructor.parameters.is_empty()) { - generate_argument_count_check(generator, constructor.name, constructor.shortest_length()); - - StringBuilder arguments_builder; - generate_arguments(generator, constructor.parameters, arguments_builder, interface, includes); - constructor_generator.set(".constructor_arguments", arguments_builder.string_view()); - - constructor_generator.append(R"~~~( - auto impl = TRY(throw_dom_exception_if_needed(vm, [&] { return @fully_qualified_name@::construct_impl(realm, @.constructor_arguments@); })); -)~~~"); - } else { - constructor_generator.append(R"~~~( - auto impl = TRY(throw_dom_exception_if_needed(vm, [&] { return @fully_qualified_name@::construct_impl(realm); })); -)~~~"); - } - - constructor_generator.append(R"~~~( - // 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 @name@" algorithm - // (https://webidl.spec.whatwg.org/#js-platform-objects) are currently not handled, or are handled within @fully_qualified_name@::construct_impl(). - // 8. Let interfaces be the inclusive inherited interfaces of interface. - // 9. For every interface ancestor interface in interfaces: - // 9.1. Let unforgeables be the value of the [[Unforgeables]] slot of the interface object of ancestor interface in realm. - // 9.2. Let keys be ! unforgeables.[[OwnPropertyKeys]](). - // 9.3. For each element key of keys: - // 9.3.1. Let descriptor be ! unforgeables.[[GetOwnProperty]](key). - // 9.3.2. Perform ! DefinePropertyOrThrow(instance, key, descriptor). - // 10. If interface is declared with the [Global] extended attribute, then: - // 10.1. Define the regular operations of interface on instance, given realm. - // 10.2. Define the regular attributes of interface on instance, given realm. - // 10.3. Define the iteration methods of interface on instance given realm. - // 10.4. Define the asynchronous iteration methods of interface on instance given realm. - // 10.5. Define the global property references on instance, given realm. - // 10.6. Set instance.[[SetPrototypeOf]] as defined in § 3.8.1 [[SetPrototypeOf]]. - // 11. Otherwise, if interfaces contains an interface which supports indexed properties, named properties, or both: - // 11.1. Set instance.[[GetOwnProperty]] as defined in § 3.9.1 [[GetOwnProperty]]. - // 11.2. Set instance.[[Set]] as defined in § 3.9.2 [[Set]]. - // 11.3. Set instance.[[DefineOwnProperty]] as defined in § 3.9.3 [[DefineOwnProperty]]. - // 11.4. Set instance.[[Delete]] as defined in § 3.9.4 [[Delete]]. - // 11.5. Set instance.[[PreventExtensions]] as defined in § 3.9.5 [[PreventExtensions]]. - // 11.6. Set instance.[[OwnPropertyKeys]] as defined in § 3.9.6 [[OwnPropertyKeys]]. - - return *impl; -} -)~~~"); - } -} - -static void generate_constructors(SourceGenerator& generator, IDL::Interface const& interface, GeneratedIncludes& includes) -{ - auto shortest_length = interface.constructors.is_empty() ? 0u : NumericLimits::max(); - bool has_html_constructor = false; - for (auto const& constructor : interface.constructors) { - shortest_length = min(shortest_length, constructor.shortest_length()); - - if (constructor.extended_attributes.contains("HTMLConstructor"sv)) { - has_html_constructor = true; - break; - } - } - - if (has_html_constructor && interface.constructors.size() != 1) { - dbgln("Interface {}'s constructor annotated with [HTMLConstructor] must be the only constructor", interface.name); - VERIFY_NOT_REACHED(); - } - - generator.set("constructor.length", ByteString::number(shortest_length)); - - // Implementation: Constructors - if (interface.constructors.is_empty()) { - // No constructor - generator.append(R"~~~( -JS::ThrowCompletionOr> @constructor_class@::construct([[maybe_unused]] FunctionObject& new_target) -{ - WebIDL::log_trace(vm(), "@constructor_class@::construct"); -)~~~"); - generator.append(R"~~~( - return vm().throw_completion(JS::ErrorType::NotAConstructor, "@namespaced_name@"); -} -)~~~"); - } else { - for (auto& constructor : interface.constructors) { - generate_constructor(generator, constructor, interface, has_html_constructor, includes); - } - } - for (auto const& overload_set : interface.constructor_overload_sets) { - if (overload_set.value.size() == 1) - continue; - generate_overload_arbiter(generator, overload_set, interface, interface.constructor_class, IsConstructor::Yes, includes); - } -} - -static void generate_shared_constructor(SourceGenerator& generator, IDL::Constructor const& constructor, IDL::Interface const& interface, bool is_html_constructor, GeneratedIncludes& includes) -{ - includes.add_header("LibWeb/Bindings/Intrinsics.h"sv); - includes.add_header("LibWeb/WebIDL/Tracing.h"sv); - includes.add_header("LibJS/Runtime/AbstractOperations.h"sv); - collect_include_dependencies(interface.context, constructor.parameters, includes); - - auto constructor_generator = generator.fork(); - constructor_generator.set("name", interface.name); - constructor_generator.set("namespaced_name", interface.namespaced_name); - constructor_generator.set("prototype_class", interface.prototype_class); - constructor_generator.set("constructor_class", interface.constructor_class); - constructor_generator.set("fully_qualified_name", interface.fully_qualified_name); - constructor_generator.set("interface_fully_qualified_name", interface.fully_qualified_name); - constructor_generator.set("overload_suffix", constructor.is_overloaded ? ByteString::number(constructor.overload_index) : ByteString::empty()); - - constructor_generator.append(R"~~~( -JS::ThrowCompletionOr> @constructor_class@::construct@overload_suffix@(InterfaceConstructor& constructor, [[maybe_unused]] JS::FunctionObject& new_target) -{ - WebIDL::log_trace(constructor.vm(), "@constructor_class@::construct@overload_suffix@"); - auto& vm = constructor.vm(); - auto& realm = *vm.current_realm(); -)~~~"); - - if (is_html_constructor) { - generate_html_constructor(constructor_generator, constructor, interface, includes); - } else { - constructor_generator.append(R"~~~( - // To internally create a new object implementing the 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<@prototype_class@>(*target_realm, "@namespaced_name@"_fly_string); - } - - // 4. Let instance be MakeBasicObject( « [[Prototype]], [[Extensible]], [[Realm]], [[PrimaryInterface]] »). - // 5. Set instance.[[Realm]] to realm. - // 6. Set instance.[[PrimaryInterface]] to interface. -)~~~"); - if (!constructor.parameters.is_empty()) { - generate_argument_count_check(constructor_generator, constructor.name, constructor.shortest_length()); - - StringBuilder arguments_builder; - generate_arguments(constructor_generator, constructor.parameters, arguments_builder, interface, includes); - constructor_generator.set(".constructor_arguments", arguments_builder.string_view()); - - constructor_generator.append(R"~~~( - auto impl = TRY(throw_dom_exception_if_needed(vm, [&] { return @fully_qualified_name@::construct_impl(realm, @.constructor_arguments@); })); -)~~~"); - } else { - constructor_generator.append(R"~~~( - auto impl = TRY(throw_dom_exception_if_needed(vm, [&] { return @fully_qualified_name@::construct_impl(realm); })); -)~~~"); - } - - constructor_generator.append(R"~~~( - // 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 @name@" algorithm - // (https://webidl.spec.whatwg.org/#js-platform-objects) are currently not handled, or are handled within @fully_qualified_name@::construct_impl(). - // 8. Let interfaces be the inclusive inherited interfaces of interface. - // 9. For every interface ancestor interface in interfaces: - // 9.1. Let unforgeables be the value of the [[Unforgeables]] slot of the interface object of ancestor interface in realm. - // 9.2. Let keys be ! unforgeables.[[OwnPropertyKeys]](). - // 9.3. For each element key of keys: - // 9.3.1. Let descriptor be ! unforgeables.[[GetOwnProperty]](key). - // 9.3.2. Perform ! DefinePropertyOrThrow(instance, key, descriptor). - // 10. If interface is declared with the [Global] extended attribute, then: - // 10.1. Define the regular operations of interface on instance, given realm. - // 10.2. Define the regular attributes of interface on instance, given realm. - // 10.3. Define the iteration methods of interface on instance given realm. - // 10.4. Define the asynchronous iteration methods of interface on instance given realm. - // 10.5. Define the global property references on instance, given realm. - // 10.6. Set instance.[[SetPrototypeOf]] as defined in § 3.8.1 [[SetPrototypeOf]]. - // 11. Otherwise, if interfaces contains an interface which supports indexed properties, named properties, or both: - // 11.1. Set instance.[[GetOwnProperty]] as defined in § 3.9.1 [[GetOwnProperty]]. - // 11.2. Set instance.[[Set]] as defined in § 3.9.2 [[Set]]. - // 11.3. Set instance.[[DefineOwnProperty]] as defined in § 3.9.3 [[DefineOwnProperty]]. - // 11.4. Set instance.[[Delete]] as defined in § 3.9.4 [[Delete]]. - // 11.5. Set instance.[[PreventExtensions]] as defined in § 3.9.5 [[PreventExtensions]]. - // 11.6. Set instance.[[OwnPropertyKeys]] as defined in § 3.9.6 [[OwnPropertyKeys]]. - - return *impl; -} -)~~~"); - } -} - -static void generate_shared_constructors(SourceGenerator& generator, IDL::Interface const& interface, GeneratedIncludes& includes) -{ - includes.add_header("LibWeb/WebIDL/Tracing.h"sv); - - bool has_html_constructor = false; - for (auto const& constructor : interface.constructors) { - if (constructor.extended_attributes.contains("HTMLConstructor"sv)) { - has_html_constructor = true; - break; - } - } - - if (has_html_constructor && interface.constructors.size() != 1) { - dbgln("Interface {}'s constructor annotated with [HTMLConstructor] must be the only constructor", interface.name); - VERIFY_NOT_REACHED(); - } - - if (interface.constructors.is_empty()) { - generator.append(R"~~~( -JS::ThrowCompletionOr> @constructor_class@::construct([[maybe_unused]] InterfaceConstructor& constructor, [[maybe_unused]] JS::FunctionObject& new_target) -{ - WebIDL::log_trace(constructor.vm(), "@constructor_class@::construct"); - return constructor.vm().throw_completion(JS::ErrorType::NotAConstructor, "@namespaced_name@"); -} -)~~~"); - } else { - for (auto& constructor : interface.constructors) - generate_shared_constructor(generator, constructor, interface, has_html_constructor, includes); - } - - for (auto const& overload_set : interface.constructor_overload_sets) { - if (overload_set.value.size() == 1) - continue; - generate_overload_arbiter(generator, overload_set, interface, interface.constructor_class, IsConstructor::Yes, includes, SharedConstructor::Yes); - } -} - -static ByteString get_best_value_for_underlying_enum_type(size_t size) -{ - - if (size < NumericLimits::max()) { - return "u8"; - } else if (size < NumericLimits::max()) { - return "u16"; - } - - VERIFY_NOT_REACHED(); -} - -static void generate_dictionaries(SourceGenerator& generator, IDL::Interface const& interface, GeneratedIncludes& includes) -{ - for (auto const& dictionary_name : interface.own_dictionaries) { - auto it = interface.context.dictionaries.find(dictionary_name); - VERIFY(it != interface.context.dictionaries.end()); - if (!it->value.is_original_definition) - continue; - if (!it->value.extended_attributes.contains("GenerateToValue"sv)) - continue; - auto dictionary_generator = generator.fork(); - dictionary_generator.set("dictionary.name", make_input_acceptable_cpp(it->key)); - dictionary_generator.set("dictionary.name:snakecase", make_input_acceptable_cpp(it->key.to_snakecase())); - dictionary_generator.append(R"~~~( -JS::Value @dictionary.name:snakecase@_to_value(JS::Realm&, @dictionary.name@ const&); -JS::Value @dictionary.name:snakecase@_to_value(JS::Realm& realm, @dictionary.name@ const& dictionary) -{ - auto& vm = realm.vm(); - @dictionary.name@ copy = dictionary; -)~~~"); - // FIXME: Support generating wrap statements for lvalues and get rid of the copy above - auto dictionary_type = adopt_ref(*new Type(it->key, false)); - generate_wrap_statement(dictionary_generator, "copy", dictionary_type, interface.context, includes, "return"sv); - - dictionary_generator.append(R"~~~( -} -)~~~"); - } -} - -static void emit_dictionary_support_includes(StringBuilder& builder) -{ - builder.append(R"~~~( -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -)~~~"sv); -} - -static void emit_idl_value_conversion_support_includes(StringBuilder& builder) -{ - builder.append(R"~~~( -#include -#include -#include - -)~~~"sv); -} - -static bool cpp_default_expression_needs_builder(Context const& context, Type const& type, ByteString const& default_value) -{ - if (is(type)) { - if (default_value == "null"sv) - return false; - return cpp_default_expression_needs_builder(context, union_member_type_for_default_value(context, as(type), default_value), default_value); - } - - if (context.dictionaries.contains(type.name()) && default_value == "{}"sv) - return dictionary_needs_builder_for_default_construction(context, type.name()); - - return false; -} - -static bool dictionary_member_needs_builder(Context const& context, DictionaryMember const& member) -{ - if (!member.required && !member.default_value.has_value()) - return false; - - if (dictionary_member_uses_optional(context, member)) - return false; - - auto member_cpp_type = cpp_type_for_dictionary_member(context, member); - if (is_direct_gc_ref_cpp_type(member_cpp_type)) - return true; - - if (is(*member.type) - && member.required - && !member.default_value.has_value()) { - return true; - } - - if (member.default_value.has_value() && cpp_default_expression_needs_builder(context, *member.type, *member.default_value)) - return true; - - return false; -} - -static ByteString dictionary_member_initializer(Context const& context, DictionaryMember const& member) -{ - auto is_optional = cpp_type_for_dictionary_member(context, member).is_optional_presence; - - if (dictionary_member_needs_builder(context, member)) - return ""sv; - - if (member.default_value.has_value()) { - if (is_optional && *member.default_value == "null"sv) - return " {}"sv; - - auto default_value_expression = cpp_default_expression(context, *member.type, member.default_value, member.extended_attributes); - if (default_value_expression == "{}"sv) - return " {}"sv; - - return ByteString::formatted(" {{ {} }}", default_value_expression); - } - - if (is(*member.type) && !is_optional) - return ByteString::formatted(" {{ {} }}", cpp_default_expression(context, *member.type, {}, member.extended_attributes)); - - return " {}"sv; -} - -static void generate_dictionary_dependencies(Context const& context, ByteString const& module_own_path, Type const& type, StringBuilder& builder, HashTable& emitted_dictionaries, GeneratedIncludes& includes); - -static void generate_dictionary_struct(Context const& context, ByteString const& dictionary_name, StringBuilder& builder, HashTable& emitted_dictionaries, GeneratedIncludes& includes) -{ - if (emitted_dictionaries.set(dictionary_name) != AK::HashSetResult::InsertedNewEntry) - return; - - auto dictionary = context.dictionaries.find(dictionary_name); - VERIFY(dictionary != context.dictionaries.end()); - - if (!dictionary->value.parent_name.is_empty()) { - includes.add_dictionary_dependency(context, dictionary->value.parent_name); - auto parent_dictionary = context.dictionaries.find(dictionary->value.parent_name); - VERIFY(parent_dictionary != context.dictionaries.end()); - if (parent_dictionary->value.module_own_path == dictionary->value.module_own_path) - generate_dictionary_struct(context, dictionary->value.parent_name, builder, emitted_dictionaries, includes); - } - - auto const& members = dictionary->value.members; - - for (auto const& member : members) - generate_dictionary_dependencies(context, dictionary->value.module_own_path, *member.type, builder, emitted_dictionaries, includes); - - SourceGenerator generator { builder }; - generator.set("dictionary.name", dictionary_name); - - if (!dictionary->value.parent_name.is_empty()) - generator.set("dictionary.parent_cpp_type", dictionary->value.parent_name); - - generator.append("struct @dictionary.name@"); - if (!dictionary->value.parent_name.is_empty()) - generator.append(" : public @dictionary.parent_cpp_type@"); - generator.append(" {\n"); - - for (auto const& member : members) { - auto member_generator = generator.fork(); - member_generator.set("member.type", cpp_type_for_dictionary_member(context, member).name); - member_generator.set("member.name", make_input_acceptable_cpp(member.name.to_snakecase())); - member_generator.set("member.initializer", dictionary_member_initializer(context, member)); - member_generator.append(" @member.type@ @member.name@@member.initializer@;\n"); - } - generator.set("dictionary.idl_value_conversion_function", idl_value_conversion_function_name(dictionary_name)); - generator.append(R"~~~( -}; - -JS::ThrowCompletionOr<@dictionary.name@> @dictionary.idl_value_conversion_function@(JS::VM&, JS::Value); -)~~~"); -} - -static void generate_dictionary_dependencies(Context const& context, ByteString const& module_own_path, Type const& type, StringBuilder& builder, HashTable& emitted_dictionaries, GeneratedIncludes& includes) -{ - for_each_type_reference(context, type, TypeTraversalMode::TypeStructureOnly, [&](Type const& referenced_type) { - includes.add_dictionary_dependency(context, referenced_type.name()); - includes.add_enumeration_dependency(context, referenced_type.name()); - - if (auto dictionary = context.dictionaries.find(referenced_type.name()); dictionary != context.dictionaries.end()) { - if (dictionary->value.module_own_path == module_own_path) - generate_dictionary_struct(context, referenced_type.name(), builder, emitted_dictionaries, includes); - } - }); -} - -static void generate_dictionary_structs(Context const& context, OrderedHashTable const& own_dictionaries, StringBuilder& builder, GeneratedIncludes& includes) -{ - if (own_dictionaries.is_empty()) - return; - - HashTable emitted_dictionaries; - for (auto const& dictionary_name : own_dictionaries) - generate_dictionary_struct(context, dictionary_name, builder, emitted_dictionaries, includes); -} - -static void generate_enumerations(IDL::Context const& context, OrderedHashTable const& own_enumerations, StringBuilder& builder) -{ - SourceGenerator generator { builder }; - - for (auto const& enumeration_name : own_enumerations) { - auto it = context.enumerations.find(enumeration_name); - VERIFY(it != context.enumerations.end()); - if (!it->value.is_original_definition) - continue; - auto enum_generator = generator.fork(); - enum_generator.set("enum.type.name", it->key); - enum_generator.set("enum.underlying_type", get_best_value_for_underlying_enum_type(it->value.translated_cpp_names.size())); - enum_generator.set("enum.idl_value_conversion_function", idl_value_conversion_function_name(it->key)); - enum_generator.append(R"~~~( -enum class @enum.type.name@ : @enum.underlying_type@ { -)~~~"); - for (auto const& entry : it->value.translated_cpp_names) { - enum_generator.set("enum.entry", entry.value); - enum_generator.append(R"~~~( - @enum.entry@, -)~~~"); - } - - enum_generator.append(R"~~~( -}; - -JS::ThrowCompletionOr<@enum.type.name@> @enum.idl_value_conversion_function@(JS::VM&, JS::Value); - -String idl_enum_to_string(@enum.type.name@); -)~~~"); - } -} - -static void generate_dictionary_idl_value_conversions(IDL::Context const& context, OrderedHashTable const& own_dictionaries, StringBuilder& builder, GeneratedIncludes& includes) -{ - includes.add_header("LibJS/Runtime/Realm.h"sv); - includes.add_header("LibJS/Runtime/VM.h"sv); - includes.add_header("LibJS/Runtime/Value.h"sv); - - SourceGenerator generator { builder }; - - for (auto const& dictionary_name : own_dictionaries) { - auto it = context.dictionaries.find(dictionary_name); - VERIFY(it != context.dictionaries.end()); - if (!it->value.is_original_definition) - continue; - - auto dictionary_generator = generator.fork(); - dictionary_generator.set("dictionary.name", it->key); - dictionary_generator.set("parameter.type.name", it->key); - dictionary_generator.set("parameter.type.name.normalized", it->key); - dictionary_generator.set("dictionary.idl_value_conversion_function", idl_value_conversion_function_name(it->key)); - dictionary_generator.set("js_name", "value"); - dictionary_generator.set("js_suffix", ""); - dictionary_generator.set("cpp_name", "converted_value"); - dictionary_generator.append(R"~~~( -JS::ThrowCompletionOr<@dictionary.name@> @dictionary.idl_value_conversion_function@(JS::VM& vm, JS::Value value) -{ - [[maybe_unused]] auto& realm = *vm.current_realm(); -)~~~"); - - generate_dictionary_to_cpp(dictionary_generator, context, it->value, dictionary_name, includes); - - dictionary_generator.append(R"~~~( - return converted_value; -} -)~~~"); - } -} - -static void generate_enumeration_idl_value_conversions(IDL::Context const& context, OrderedHashTable const& own_enumerations, StringBuilder& builder, GeneratedIncludes& includes) -{ - includes.add_header("LibJS/Runtime/Error.h"sv); - includes.add_header("LibJS/Runtime/VM.h"sv); - includes.add_header("LibJS/Runtime/Value.h"sv); - - SourceGenerator generator { builder }; - - for (auto const& enumeration_name : own_enumerations) { - auto it = context.enumerations.find(enumeration_name); - VERIFY(it != context.enumerations.end()); - if (!it->value.is_original_definition) - continue; - - auto enum_generator = generator.fork(); - enum_generator.set("enum.type.name", it->key); - enum_generator.set("enum.idl_value_conversion_function", idl_value_conversion_function_name(it->key)); - enum_generator.append(R"~~~( -JS::ThrowCompletionOr<@enum.type.name@> @enum.idl_value_conversion_function@(JS::VM& vm, JS::Value value) -{ - auto value_as_string = TRY(value.to_string(vm)); -)~~~"); - - bool first = true; - for (auto const& entry : it->value.translated_cpp_names) { - enum_generator.set("enum.entry", entry.value); - enum_generator.set("enum.string", entry.key); - enum_generator.set("else", first ? "" : "else "); - first = false; - enum_generator.append(R"~~~( - @else@if (value_as_string == "@enum.string@"sv) - return @enum.type.name@::@enum.entry@; -)~~~"); - } - - enum_generator.append(R"~~~( - return vm.throw_completion(JS::ErrorType::InvalidEnumerationValue, value_as_string, "@enum.type.name@"); -} - -String idl_enum_to_string(@enum.type.name@ value) -{ - switch (value) { -)~~~"); - - for (auto const& entry : it->value.translated_cpp_names) { - enum_generator.set("enum.entry", entry.value); - enum_generator.set("enum.string", entry.key); - enum_generator.append(R"~~~( - case @enum.type.name@::@enum.entry@: - return "@enum.string@"_string; -)~~~"); - } - - enum_generator.append(R"~~~( - } - VERIFY_NOT_REACHED(); -} -)~~~"); - } -} - -static void generate_prototype_or_global_mixin_declarations(IDL::Interface const& interface, StringBuilder& builder) -{ - SourceGenerator generator { builder }; - - for (auto const& overload_set : interface.overload_sets) { - auto function_generator = generator.fork(); - function_generator.set("function.name:snakecase", make_input_acceptable_cpp(overload_set.key.to_snakecase())); - function_generator.append(R"~~~( - JS_DECLARE_NATIVE_FUNCTION(@function.name:snakecase@); - )~~~"); - if (overload_set.value.size() > 1) { - for (auto i = 0u; i < overload_set.value.size(); ++i) { - function_generator.set("overload_suffix", ByteString::number(i)); - function_generator.append(R"~~~( - JS_DECLARE_NATIVE_FUNCTION(@function.name:snakecase@@overload_suffix@); -)~~~"); - } - } - } - - if (interface.has_stringifier) { - auto stringifier_generator = generator.fork(); - stringifier_generator.append(R"~~~( - JS_DECLARE_NATIVE_FUNCTION(to_string); - )~~~"); - } - - if (interface.pair_iterator_types.has_value()) { - auto iterator_generator = generator.fork(); - iterator_generator.append(R"~~~( - JS_DECLARE_NATIVE_FUNCTION(entries); - JS_DECLARE_NATIVE_FUNCTION(for_each); - JS_DECLARE_NATIVE_FUNCTION(keys); - JS_DECLARE_NATIVE_FUNCTION(values); - )~~~"); - } - - if (interface.async_value_iterator_type.has_value()) { - auto iterator_generator = generator.fork(); - iterator_generator.append(R"~~~( - JS_DECLARE_NATIVE_FUNCTION(values); - )~~~"); - } - - if (interface.set_entry_type.has_value()) { - auto setlike_generator = generator.fork(); - - setlike_generator.append(R"~~~( - JS_DECLARE_NATIVE_FUNCTION(get_size); - JS_DECLARE_NATIVE_FUNCTION(entries); - JS_DECLARE_NATIVE_FUNCTION(values); - JS_DECLARE_NATIVE_FUNCTION(for_each); - JS_DECLARE_NATIVE_FUNCTION(has); -)~~~"); - if (!interface.overload_sets.contains("add"sv) && !interface.is_set_readonly) { - setlike_generator.append(R"~~~( - JS_DECLARE_NATIVE_FUNCTION(add); -)~~~"); - } - if (!interface.overload_sets.contains("delete"sv) && !interface.is_set_readonly) { - setlike_generator.append(R"~~~( - JS_DECLARE_NATIVE_FUNCTION(delete_); -)~~~"); - } - if (!interface.overload_sets.contains("clear"sv) && !interface.is_set_readonly) { - setlike_generator.append(R"~~~( - JS_DECLARE_NATIVE_FUNCTION(clear); -)~~~"); - } - } - - if (interface.map_key_type.has_value()) { - auto maplike_generator = generator.fork(); - - maplike_generator.append(R"~~~( - JS_DECLARE_NATIVE_FUNCTION(get_size); - JS_DECLARE_NATIVE_FUNCTION(entries); - JS_DECLARE_NATIVE_FUNCTION(keys); - JS_DECLARE_NATIVE_FUNCTION(values); - JS_DECLARE_NATIVE_FUNCTION(for_each); - JS_DECLARE_NATIVE_FUNCTION(get); - JS_DECLARE_NATIVE_FUNCTION(has); -)~~~"); - - if (!interface.overload_sets.contains("set"sv) && !interface.is_map_readonly) - maplike_generator.appendln(" JS_DECLARE_NATIVE_FUNCTION(set);"); - - if (!interface.overload_sets.contains("delete"sv) && !interface.is_map_readonly) - maplike_generator.appendln(" JS_DECLARE_NATIVE_FUNCTION(delete_);"); - - if (!interface.overload_sets.contains("clear"sv) && !interface.is_map_readonly) - maplike_generator.appendln(" JS_DECLARE_NATIVE_FUNCTION(clear);"); - } - - for (auto& attribute : interface.attributes) { - if (attribute.extended_attributes.contains("FIXME")) - continue; - auto attribute_generator = generator.fork(); - attribute_generator.set("attribute.getter_callback", attribute.getter_callback_name); - attribute_generator.append(R"~~~( - JS_DECLARE_NATIVE_FUNCTION(@attribute.getter_callback@); -)~~~"); - - if (!attribute.readonly || attribute.extended_attributes.contains("Replaceable"sv) || attribute.extended_attributes.contains("PutForwards"sv) || attribute.extended_attributes.contains("LegacyLenientSetter")) { - attribute_generator.set("attribute.setter_callback", attribute.setter_callback_name); - attribute_generator.append(R"~~~( - JS_DECLARE_NATIVE_FUNCTION(@attribute.setter_callback@); -)~~~"); - } - } - - generator.append(R"~~~( - -}; - -)~~~"); -} - -// https://webidl.spec.whatwg.org/#create-an-inheritance-stack -static Vector create_an_inheritance_stack(IDL::Interface const& start_interface) -{ - // 1. Let stack be a new stack. - Vector inheritance_chain; - - // 2. Push I onto stack. - inheritance_chain.append(start_interface); - - // 3. While I inherits from an interface, - auto const* current_interface = &start_interface; - while (current_interface && !current_interface->parent_name.is_empty()) { - // 1. Let I be that interface. - auto inhereted_interface = start_interface.context.interfaces.get(current_interface->parent_name); - VERIFY(inhereted_interface.has_value()); - - // 2. Push I onto stack. - inheritance_chain.append(*inhereted_interface.value()); - current_interface = inhereted_interface.value(); - } - - // 4. Return stack. - return inheritance_chain; -} - -// https://webidl.spec.whatwg.org/#collect-attribute-values-of-an-inheritance-stack -static void collect_attribute_values_of_an_inheritance_stack(SourceGenerator& function_generator, Vector const& inheritance_chain, GeneratedIncludes& includes) -{ - // 1. Let I be the result of popping from stack. - // 3. If stack is not empty, then invoke collect attribute values of an inheritance stack given object, stack, and map. - for (auto const& interface_in_chain : inheritance_chain.in_reverse()) { - // 2. Invoke collect attribute values given object, I, and map. - // https://webidl.spec.whatwg.org/#collect-attribute-values - // 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: - auto to_json_iterator = interface_in_chain.functions.find_if([](IDL::Function const& function) { - return function.name == "toJSON" && function.extended_attributes.contains("Default"); - }); - - if (to_json_iterator == interface_in_chain.functions.end()) - continue; - - // FIXME: Check if the attributes are exposed. - - // NOTE: Add more specified exposed global interface groups when needed. - StringBuilder window_exposed_only_members_builder; - SourceGenerator window_exposed_only_members_generator { window_exposed_only_members_builder, function_generator.clone_mapping() }; - auto generator_for_member = [&](auto const& name, auto& extended_attributes) -> SourceGenerator { - if (auto maybe_exposed = extended_attributes.get("Exposed"); maybe_exposed.has_value()) { - auto exposed_to = MUST(IDL::parse_exposure_set(name, *maybe_exposed)); - if (exposed_to == IDL::ExposedTo::Window) { - return window_exposed_only_members_generator.fork(); - } - } - return function_generator.fork(); - }; - - // 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. - // Since we are flatly generating the attributes, the consequent is replaced with these steps from "default toJSON steps": - // 5. For each key → value of map, - // 1. Let k be key converted to an ECMAScript value. - // 2. Let v be value converted to an ECMAScript value. - // 3. Perform ! CreateDataProperty(result, k, v). - - // NOTE: Functions, constructors and static functions cannot be JSON types, so they're not checked here. - - for (auto& attribute : interface_in_chain.attributes) { - if (attribute.extended_attributes.contains("FIXME")) - continue; - if (!attribute.type->is_json(interface_in_chain.context)) - continue; - - auto attribute_generator = generator_for_member(attribute.name, attribute.extended_attributes); - auto return_value_name = ByteString::formatted("{}_retval", attribute.name.to_snakecase()); - - attribute_generator.set("attribute.name", attribute.name); - attribute_generator.set("attribute.return_value_name", return_value_name); - - if (attribute.extended_attributes.contains("ImplementedAs")) { - auto implemented_as = attribute.extended_attributes.get("ImplementedAs").value(); - attribute_generator.set("attribute.cpp_name", implemented_as); - } else { - attribute_generator.set("attribute.cpp_name", make_input_acceptable_cpp(attribute.name.to_snakecase())); - } - - if (attribute.extended_attributes.contains("Reflect")) { - auto attribute_name = attribute.extended_attributes.get("Reflect").value(); - if (attribute_name.is_empty()) - attribute_name = attribute.name; - - attribute_generator.set("attribute.reflect_name", attribute_name); - } else { - attribute_generator.set("attribute.reflect_name", attribute.name.to_snakecase()); - } - - if (attribute.extended_attributes.contains("Reflect")) { - if (attribute.type->name() != "boolean") { - attribute_generator.append(R"~~~( - auto @attribute.return_value_name@ = impl->get_attribute_value("@attribute.reflect_name@"_fly_string); -)~~~"); - } else { - attribute_generator.append(R"~~~( - auto @attribute.return_value_name@ = impl->has_attribute("@attribute.reflect_name@"_fly_string); -)~~~"); - } - } else { - attribute_generator.append(R"~~~( - auto @attribute.return_value_name@ = TRY(throw_dom_exception_if_needed(vm, [&] { return impl->@attribute.cpp_name@(); })); -)~~~"); - } - - attribute_generator.append(R"~~~( - JS::Value @attribute.return_value_name@_wrapped; -)~~~"); - collect_include_dependencies(interface_in_chain.context, *attribute.type, includes); - generate_wrap_statement(attribute_generator, return_value_name, attribute.type, interface_in_chain.context, includes, ByteString::formatted("{}_wrapped =", return_value_name)); - - attribute_generator.append(R"~~~( - MUST(result->create_data_property("@attribute.name@"_utf16_fly_string, @attribute.return_value_name@_wrapped)); -)~~~"); - } - - if (!window_exposed_only_members_generator.as_string_view().is_empty()) { - includes.add_header("LibWeb/HTML/Window.h"sv); - - auto window_only_property_declarations = function_generator.fork(); - window_only_property_declarations.set("defines", window_exposed_only_members_generator.as_string_view()); - window_only_property_declarations.append(R"~~~( - if (is(realm.global_object())) { -@defines@ - } -)~~~"); - } - } -} - -// https://webidl.spec.whatwg.org/#default-tojson-steps -static void generate_default_to_json_function(SourceGenerator& generator, ByteString const& class_name, IDL::Interface const& start_interface, GeneratedIncludes& includes) -{ - includes.add_header("LibWeb/WebIDL/Tracing.h"sv); - - // NOTE: This is done heavily out of order since the spec mixes parse time and run time type information together. - - auto function_generator = generator.fork(); - function_generator.set("class_name", class_name); - - // 4. Let result be OrdinaryObjectCreate(%Object.prototype%). - function_generator.append(R"~~~( -JS_DEFINE_NATIVE_FUNCTION(@class_name@::to_json) -{ - WebIDL::log_trace(vm, "@class_name@::to_json"); - auto& realm = *vm.current_realm(); - auto* impl = TRY(impl_from(vm)); - - auto result = JS::Object::create(realm, realm.intrinsics().object_prototype()); -)~~~"); - - // 1. Let map be a new ordered map. - // NOTE: Instead of making a map, we flatly generate the attributes. - - // 2. Let stack be the result of creating an inheritance stack for interface I. - auto inheritance_chain = create_an_inheritance_stack(start_interface); - - // 3. Invoke collect attribute values of an inheritance stack given this, stack, and map. - collect_attribute_values_of_an_inheritance_stack(function_generator, inheritance_chain, includes); - - // NOTE: Step 5 is done as part of collect_attribute_values_of_an_inheritance_stack, due to us flatly generating the attributes. - - // 6. Return result. - function_generator.append(R"~~~( - return result; -} -)~~~"); -} - -static void generate_named_properties_object_declarations(IDL::Interface const& interface, StringBuilder& builder) -{ - SourceGenerator generator { builder }; - - generator.set("named_properties_class", ByteString::formatted("{}Properties", interface.name)); - - generator.append(R"~~~( -class @named_properties_class@ : public JS::Object { - JS_OBJECT(@named_properties_class@, JS::Object); - GC_DECLARE_ALLOCATOR(@named_properties_class@); -public: - explicit @named_properties_class@(JS::Realm&); - virtual void initialize(JS::Realm&) override; - virtual ~@named_properties_class@() override; - - JS::Realm& realm() const { return m_realm; } -private: - virtual JS::ThrowCompletionOr> internal_get_own_property(JS::PropertyKey const&) const override; - virtual JS::ThrowCompletionOr internal_define_own_property(JS::PropertyKey const&, JS::PropertyDescriptor&, Optional* precomputed_get_own_property = nullptr) override; - virtual JS::ThrowCompletionOr internal_delete(JS::PropertyKey const&) override; - virtual JS::ThrowCompletionOr internal_set_prototype_of(JS::Object* prototype) override; - virtual JS::ThrowCompletionOr 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 m_realm; // [[Realm]] -}; -)~~~"); -} - -static void generate_named_properties_object_definitions(IDL::Interface const& interface, StringBuilder& builder) -{ - SourceGenerator generator { builder }; - - generator.set("name", interface.name); - generator.set("fully_qualified_name", interface.fully_qualified_name); - generator.set("parent_name", interface.parent_name); - generator.set("prototype_base_class", interface.prototype_base_class); - generator.set("named_properties_class", ByteString::formatted("{}Properties", interface.name)); - - // https://webidl.spec.whatwg.org/#create-a-named-properties-object - generator.append(R"~~~( -GC_DEFINE_ALLOCATOR(@named_properties_class@); - -@named_properties_class@::@named_properties_class@(JS::Realm& realm) - : JS::Object(realm, nullptr, MayInterfereWithIndexedPropertyAccess::Yes) - , m_realm(realm) -{ -} - -@named_properties_class@::~@named_properties_class@() -{ -} - -void @named_properties_class@::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, "@named_properties_class@"_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%]]. - // NOTE: Steps 4-9 handled by constructor + other overridden functions - // 10. Set obj.[[Prototype]] to proto. - if (interface.prototype_base_class == "ObjectPrototype") { - generator.append(R"~~~( - - set_prototype(realm.intrinsics().object_prototype()); -)~~~"); - } else { - generator.append(R"~~~( - - set_prototype(&ensure_web_prototype<@prototype_base_class@>(realm, "@parent_name@"_fly_string)); -)~~~"); - } - - generator.append(R"~~~( -}; - -// https://webidl.spec.whatwg.org/#named-properties-object-getownproperty -JS::ThrowCompletionOr> @named_properties_class@::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@; - - // 2. Let object be O.[[Realm]]'s global object. - // 3. Assert: object implements A. - auto& object = as(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; -)~~~"); - if (interface.extended_attributes.contains("LegacyUnenumerableNamedProperties")) - generator.append(R"~~~( - // 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; -)~~~"); - else { - generator.append(R"~~~( - // 7. If A implements an interface with the [LegacyUnenumerableNamedProperties] extended attribute, then set desc.[[Enumerable]] to false, otherwise set it to true. - descriptor.enumerable = true; -)~~~"); - } - generator.append(R"~~~( - // 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 @named_properties_class@::internal_define_own_property(JS::PropertyKey const&, JS::PropertyDescriptor&, Optional*) -{ - // 1. Return false. - return false; -} - -// https://webidl.spec.whatwg.org/#named-properties-object-delete -JS::ThrowCompletionOr @named_properties_class@::internal_delete(JS::PropertyKey const&) -{ - // 1. Return false. - return false; -} - -// https://webidl.spec.whatwg.org/#named-properties-object-setprototypeof -JS::ThrowCompletionOr @named_properties_class@::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 @named_properties_class@::internal_prevent_extensions() -{ - // 1. Return false. - // Note: this keeps named properties object extensible by making [[PreventExtensions]] fail. - return false; -} - -void @named_properties_class@::visit_edges(Visitor& visitor) -{ - Base::visit_edges(visitor); - visitor.visit(m_realm); -} -)~~~"); -} - -// https://webidl.spec.whatwg.org/#interface-prototype-object -static bool interface_prototype_has_immutable_prototype(IDL::Interface const& interface) -{ - // 9. Otherwise, if interface is declared with the [Global] extended attribute, or interface is in the set of - // inherited interfaces of an interface that is declared with the [Global] extended attribute, then: - // NB: This currently assumes only Workers and Window can be globals. - return interface.extended_attributes.contains("Global") - || interface.name == "WorkerGlobalScope"sv - || interface.name == "EventTarget"sv; -} - -static bool can_use_shared_interface_constructor(IDL::Interface const& interface) -{ - return !interface.is_namespace - && !interface.is_callback_interface; -} - -static bool can_use_shared_interface_prototype(IDL::Interface const& interface) -{ - return !interface.is_namespace - && !interface.is_callback_interface - && !interface.extended_attributes.contains("Global") - && !interface.value_iterator_type.has_value() - && !interface.pair_iterator_types.has_value() - && !interface.async_value_iterator_type.has_value() - && !interface.set_entry_type.has_value() - && !interface.map_key_type.has_value() - && !interface.supports_named_properties() - && !interface.supports_indexed_properties() - && !interface.named_property_deleter.has_value() - && !interface.named_property_setter.has_value() - && !interface.indexed_property_setter.has_value(); -} - -enum class GenerateUnforgeables { - No, - Yes, -}; - -enum class InitializeExistingObject { - No, - Yes, -}; - -static void generate_prototype_or_global_mixin_initialization(IDL::Interface const&, StringBuilder&, GenerateUnforgeables, InitializeExistingObject, GeneratedIncludes&); - -static void generate_prototype_or_global_mixin_initialization(IDL::Interface const& interface, StringBuilder& builder, GenerateUnforgeables generate_unforgeables, GeneratedIncludes& includes) -{ - generate_prototype_or_global_mixin_initialization(interface, builder, generate_unforgeables, InitializeExistingObject::No, includes); -} - -static void generate_prototype_or_global_mixin_initialization(IDL::Interface const& interface, StringBuilder& builder, GenerateUnforgeables generate_unforgeables, InitializeExistingObject initialize_existing_object, GeneratedIncludes& includes) -{ - SourceGenerator generator { builder }; - - auto is_global_interface = interface.extended_attributes.contains("Global"); - auto class_name = is_global_interface ? interface.global_mixin_class : interface.prototype_class; - generator.set("name", interface.name); - generator.set("namespaced_name", interface.namespaced_name); - generator.set("class_name", class_name); - generator.set("fully_qualified_name", interface.fully_qualified_name); - generator.set("parent_name", interface.parent_name); - generator.set("prototype_base_class", interface.prototype_base_class); - generator.set("prototype_name", interface.prototype_class); // Used for Global Mixin - - if (interface.pair_iterator_types.has_value()) { - generator.set("iterator_name", ByteString::formatted("{}Iterator", interface.fully_qualified_name)); - } - - bool define_on_existing_object = is_global_interface || generate_unforgeables == GenerateUnforgeables::Yes || initialize_existing_object == InitializeExistingObject::Yes; - - if (define_on_existing_object) { - generator.set("define_direct_accessor", "object.define_direct_accessor"); - generator.set("define_direct_property", "object.define_direct_property"); - generator.set("define_native_accessor", "object.define_native_accessor"); - generator.set("define_native_function", "object.define_native_function"); - generator.set("set_prototype", "object.set_prototype"); - } else { - generator.set("define_direct_accessor", "define_direct_accessor"); - generator.set("define_direct_property", "define_direct_property"); - generator.set("define_native_accessor", "define_native_accessor"); - generator.set("define_native_function", "define_native_function"); - generator.set("set_prototype", "set_prototype"); - } - - if (generate_unforgeables == GenerateUnforgeables::Yes) { - generator.append(R"~~~( -void @class_name@::define_unforgeable_attributes(JS::Realm& realm, [[maybe_unused]] JS::Object& object) -{ -)~~~"); - } else if (is_global_interface || initialize_existing_object == InitializeExistingObject::Yes) { - generator.append(R"~~~( -void @class_name@::initialize(JS::Realm& realm, JS::Object& object) -{ -)~~~"); - } else { - generator.append(R"~~~( -void @class_name@::initialize(JS::Realm& realm) -{ -)~~~"); - } - - generator.append(R"~~~( - - [[maybe_unused]] auto& vm = realm.vm(); - -)~~~"); - - // FIXME: Currently almost everything gets default_attributes but it should be configurable per attribute. - // See the spec links for details - if (generate_unforgeables == GenerateUnforgeables::No) { - generator.append(R"~~~( - [[maybe_unused]] u8 default_attributes = JS::Attribute::Enumerable | JS::Attribute::Configurable | JS::Attribute::Writable; -)~~~"); - } else { - generator.append(R"~~~( - [[maybe_unused]] u8 default_attributes = JS::Attribute::Enumerable; -)~~~"); - } - - if (generate_unforgeables == GenerateUnforgeables::No) { - if (interface.name == "DOMException"sv) { - generator.append(R"~~~( - - @set_prototype@(realm.intrinsics().error_prototype()); -)~~~"); - } - - else if (interface.prototype_base_class == "ObjectPrototype") { - generator.append(R"~~~( - - @set_prototype@(realm.intrinsics().object_prototype()); - -)~~~"); - } else if (is_global_interface) { - generator.append(R"~~~( - @set_prototype@(&ensure_web_prototype<@prototype_name@>(realm, "@name@"_fly_string)); -)~~~"); - } else { - generator.append(R"~~~( - - @set_prototype@(&ensure_web_prototype<@prototype_base_class@>(realm, "@parent_name@"_fly_string)); - -)~~~"); - } - } - - if (interface.has_unscopable_member && generate_unforgeables == GenerateUnforgeables::No) { - generator.append(R"~~~( - auto unscopable_object = JS::Object::create(realm, nullptr); -)~~~"); - } - - // NOTE: Add more specified exposed global interface groups when needed. - StringBuilder window_exposed_only_members_builder; - SourceGenerator window_exposed_only_members_generator { window_exposed_only_members_builder, generator.clone_mapping() }; - auto generator_for_member = [&](auto const& name, auto& extended_attributes) -> SourceGenerator { - if (auto maybe_exposed = extended_attributes.get("Exposed"); maybe_exposed.has_value()) { - auto exposed_to = MUST(IDL::parse_exposure_set(name, *maybe_exposed)); - if (exposed_to == IDL::ExposedTo::Window) { - return window_exposed_only_members_generator.fork(); - } - } - return generator.fork(); - }; - - // https://webidl.spec.whatwg.org/#es-attributes - for (auto& attribute : interface.attributes) { - bool has_unforgeable_attribute = attribute.extended_attributes.contains("LegacyUnforgeable"sv); - if ((generate_unforgeables == GenerateUnforgeables::Yes && !has_unforgeable_attribute) || (generate_unforgeables == GenerateUnforgeables::No && has_unforgeable_attribute)) - continue; - - auto attribute_generator = generator_for_member(attribute.name, attribute.extended_attributes); - - // AD-HOC: Do not expose experimental attributes unless instructed to do so. - if (attribute.extended_attributes.contains("Experimental")) { - attribute_generator.append(R"~~~( - if (HTML::UniversalGlobalScopeMixin::expose_experimental_interfaces()) {)~~~"); - } - - if (attribute.extended_attributes.contains("SecureContext")) { - includes.add_header("LibWeb/Bindings/PrincipalHostDefined.h"sv); - attribute_generator.append(R"~~~( - if (HTML::is_secure_context(Bindings::principal_host_defined_environment_settings_object(realm))) {)~~~"); - } - - if (attribute.extended_attributes.contains("FIXME")) { - attribute_generator.set("attribute.name", attribute.name); - attribute_generator.append(R"~~~( - @define_direct_property@("@attribute.name@"_utf16_fly_string, JS::js_undefined(), default_attributes | JS::Attribute::Unimplemented); - )~~~"); - if (attribute.extended_attributes.contains("SecureContext")) { - attribute_generator.append(R"~~~( - })~~~"); - } - continue; - } - - attribute_generator.set("attribute.name", attribute.name); - attribute_generator.set("attribute.getter_callback", attribute.getter_callback_name); - attribute_generator.set("attribute.setter_callback", attribute.setter_callback_name); - - if (has_unforgeable_attribute) { - attribute_generator.append(R"~~~( - auto native_@attribute.getter_callback@ = host_defined_intrinsics(realm).ensure_web_unforgeable_function("@namespaced_name@"_utf16_fly_string, "@attribute.name@"_utf16_fly_string, @attribute.getter_callback@, UnforgeableKey::Type::Getter); -)~~~"); - } else { - attribute_generator.append(R"~~~( - auto native_@attribute.getter_callback@ = JS::NativeFunction::create(realm, @attribute.getter_callback@, 0, "@attribute.name@"_utf16_fly_string, &realm, "get"sv); -)~~~"); - } - - if (!attribute.readonly || attribute.extended_attributes.contains("Replaceable"sv) || attribute.extended_attributes.contains("PutForwards"sv) || attribute.extended_attributes.contains("LegacyLenientSetter")) { - if (has_unforgeable_attribute) { - attribute_generator.append(R"~~~( - auto native_@attribute.setter_callback@ = host_defined_intrinsics(realm).ensure_web_unforgeable_function("@namespaced_name@"_utf16_fly_string, "@attribute.name@"_utf16_fly_string, @attribute.setter_callback@, UnforgeableKey::Type::Setter); -)~~~"); - } else { - attribute_generator.append(R"~~~( - auto native_@attribute.setter_callback@ = JS::NativeFunction::create(realm, @attribute.setter_callback@, 1, "@attribute.name@"_utf16_fly_string, &realm, "set"sv); -)~~~"); - } - } else { - attribute_generator.append(R"~~~( - GC::Ptr native_@attribute.setter_callback@; -)~~~"); - } - - if (attribute.extended_attributes.contains("Unscopable")) { - attribute_generator.append(R"~~~( - MUST(unscopable_object->create_data_property("@attribute.name@"_utf16_fly_string, JS::Value(true))); -)~~~"); - } - - attribute_generator.append(R"~~~( - @define_direct_accessor@("@attribute.name@"_utf16_fly_string, native_@attribute.getter_callback@, native_@attribute.setter_callback@, default_attributes); -)~~~"); - - if (attribute.extended_attributes.contains("SecureContext")) { - attribute_generator.append(R"~~~( - })~~~"); - } - - if (attribute.extended_attributes.contains("Experimental")) { - attribute_generator.append(R"~~~( - })~~~"); - } - } - - for (auto& function : interface.functions) { - bool has_unforgeable_attribute = function.extended_attributes.contains("LegacyUnforgeable"sv); - if ((generate_unforgeables == GenerateUnforgeables::Yes && !has_unforgeable_attribute) || (generate_unforgeables == GenerateUnforgeables::No && has_unforgeable_attribute)) - continue; - - if (function.extended_attributes.contains("FIXME")) { - auto function_generator = generator_for_member(function.name, function.extended_attributes); - function_generator.set("function.name", function.name); - function_generator.append(R"~~~( - @define_direct_property@("@function.name@"_utf16_fly_string, JS::js_undefined(), default_attributes | JS::Attribute::Unimplemented); - )~~~"); - } - } - - // https://webidl.spec.whatwg.org/#es-constants - if (generate_unforgeables == GenerateUnforgeables::No) { - for (auto& constant : interface.constants) { - // FIXME: Do constants need to be added to the unscopable list? - - auto constant_generator = generator.fork(); - constant_generator.set("constant.name", constant.name); - - collect_include_dependencies(interface.context, *constant.type, includes); - generate_wrap_statement(constant_generator, constant.value, constant.type, interface.context, includes, ByteString::formatted("auto constant_{}_value =", constant.name)); - - constant_generator.append(R"~~~( - @define_direct_property@("@constant.name@"_utf16_fly_string, constant_@constant.name@_value, JS::Attribute::Enumerable); -)~~~"); - } - } - - // https://webidl.spec.whatwg.org/#es-operations - for (auto const& overload_set : interface.overload_sets) { - // NOTE: This assumes that every function in the overload set has the same attribute set. - bool has_unforgeable_attribute = any_of(overload_set.value, [](auto const& function) { return function.extended_attributes.contains("LegacyUnforgeable"); }); - if ((generate_unforgeables == GenerateUnforgeables::Yes && !has_unforgeable_attribute) || (generate_unforgeables == GenerateUnforgeables::No && has_unforgeable_attribute)) - continue; - - auto const& function = overload_set.value.first(); - auto function_generator = generator_for_member(function.name, function.extended_attributes); - function_generator.set("function.name", overload_set.key); - function_generator.set("function.name:snakecase", make_input_acceptable_cpp(overload_set.key.to_snakecase())); - function_generator.set("function.length", ByteString::number(get_shortest_function_length(overload_set.value))); - - if (function.extended_attributes.contains("SecureContext")) { - includes.add_header("LibWeb/Bindings/PrincipalHostDefined.h"sv); - function_generator.append(R"~~~( - if (HTML::is_secure_context(Bindings::principal_host_defined_environment_settings_object(realm))) {)~~~"); - } - - if (any_of(overload_set.value, [](auto const& function) { return function.extended_attributes.contains("Unscopable"); })) { - VERIFY(all_of(overload_set.value, [](auto const& function) { return function.extended_attributes.contains("Unscopable"); })); - function_generator.append(R"~~~( - MUST(unscopable_object->create_data_property("@function.name@"_utf16_fly_string, JS::Value(true))); -)~~~"); - } - - function_generator.append(R"~~~( - @define_native_function@(realm, "@function.name@"_utf16_fly_string, @function.name:snakecase@, @function.length@, default_attributes); -)~~~"); - - if (function.extended_attributes.contains("SecureContext")) { - function_generator.append(R"~~~( - })~~~"); - } - } - - bool should_generate_stringifier = true; - if (interface.stringifier_attribute.has_value()) { - bool has_unforgeable_attribute = interface.stringifier_attribute.value().extended_attributes.contains("LegacyUnforgeable"sv); - if ((generate_unforgeables == GenerateUnforgeables::Yes && !has_unforgeable_attribute) || (generate_unforgeables == GenerateUnforgeables::No && has_unforgeable_attribute)) - should_generate_stringifier = false; - } - if (interface.has_stringifier && should_generate_stringifier) { - // FIXME: Do stringifiers need to be added to the unscopable list? - auto stringifier_generator = interface.stringifier_extended_attributes.has_value() - ? generator_for_member("stringifier"sv, *interface.stringifier_extended_attributes) - : generator.fork(); - stringifier_generator.append(R"~~~( - @define_native_function@(realm, "toString"_utf16_fly_string, to_string, 0, default_attributes); -)~~~"); - } - - // https://webidl.spec.whatwg.org/#define-the-iteration-methods - // This applies to this if block and the following if block. - if (interface.indexed_property_getter.has_value() && generate_unforgeables == GenerateUnforgeables::No) { - auto iterator_generator = generator.fork(); - iterator_generator.append(R"~~~( - @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.value_iterator_type.has_value()) { - collect_include_dependencies(interface.context, **interface.value_iterator_type, includes); - iterator_generator.append(R"~~~( - @define_direct_property@(vm.names.entries, realm.intrinsics().array_prototype()->get_without_side_effects(vm.names.entries), default_attributes); - @define_direct_property@(vm.names.keys, realm.intrinsics().array_prototype()->get_without_side_effects(vm.names.keys), default_attributes); - @define_direct_property@(vm.names.values, realm.intrinsics().array_prototype()->get_without_side_effects(vm.names.values), default_attributes); - @define_direct_property@(vm.names.forEach, realm.intrinsics().array_prototype()->get_without_side_effects(vm.names.forEach), default_attributes); -)~~~"); - } - } - - if (interface.pair_iterator_types.has_value() && generate_unforgeables == GenerateUnforgeables::No) { - // FIXME: Do pair iterators need to be added to the unscopable list? - - auto iterator_generator = generator.fork(); - iterator_generator.append(R"~~~( - @define_native_function@(realm, vm.names.entries, entries, 0, default_attributes); - @define_native_function@(realm, vm.names.forEach, for_each, 1, default_attributes); - @define_native_function@(realm, vm.names.keys, keys, 0, default_attributes); - @define_native_function@(realm, vm.names.values, values, 0, default_attributes); - - @define_direct_property@(vm.well_known_symbol_iterator(), get_without_side_effects(vm.names.entries), JS::Attribute::Configurable | JS::Attribute::Writable); -)~~~"); - } - - // https://webidl.spec.whatwg.org/#define-the-asynchronous-iteration-methods - if (interface.async_value_iterator_type.has_value() && generate_unforgeables == GenerateUnforgeables::No) { - auto iterator_generator = generator.fork(); - iterator_generator.append(R"~~~( - @define_native_function@(realm, vm.names.values, values, 0, default_attributes); - - @define_direct_property@(vm.well_known_symbol_async_iterator(), get_without_side_effects(vm.names.values), JS::Attribute::Configurable | JS::Attribute::Writable); -)~~~"); - } - - // https://webidl.spec.whatwg.org/#js-setlike - if (interface.set_entry_type.has_value() && generate_unforgeables == GenerateUnforgeables::No) { - - auto setlike_generator = generator.fork(); - - setlike_generator.append(R"~~~( - @define_native_accessor@(realm, vm.names.size, get_size, nullptr, JS::Attribute::Enumerable | JS::Attribute::Configurable); - @define_native_function@(realm, vm.names.entries, entries, 0, default_attributes); - // NOTE: Keys intentionally returns values for setlike - @define_native_function@(realm, vm.names.keys, values, 0, default_attributes); - @define_native_function@(realm, vm.names.values, values, 0, default_attributes); - @define_direct_property@(vm.well_known_symbol_iterator(), get_without_side_effects(vm.names.values), JS::Attribute::Configurable | JS::Attribute::Writable); - @define_native_function@(realm, vm.names.forEach, for_each, 1, default_attributes); - @define_native_function@(realm, vm.names.has, has, 1, default_attributes); -)~~~"); - - if (!interface.overload_sets.contains("add"sv) && !interface.is_set_readonly) { - setlike_generator.append(R"~~~( - @define_native_function@(realm, vm.names.add, add, 1, default_attributes); -)~~~"); - } - if (!interface.overload_sets.contains("delete"sv) && !interface.is_set_readonly) { - setlike_generator.append(R"~~~( - @define_native_function@(realm, vm.names.delete_, delete_, 1, default_attributes); -)~~~"); - } - if (!interface.overload_sets.contains("clear"sv) && !interface.is_set_readonly) { - setlike_generator.append(R"~~~( - @define_native_function@(realm, vm.names.clear, clear, 0, default_attributes); -)~~~"); - } - } - - if (interface.map_key_type.has_value() && generate_unforgeables == GenerateUnforgeables::No) { - auto maplike_generator = generator.fork(); - - maplike_generator.append(R"~~~( - @define_native_accessor@(realm, vm.names.size, get_size, nullptr, JS::Attribute::Enumerable | JS::Attribute::Configurable); - @define_native_function@(realm, vm.names.entries, entries, 0, default_attributes); - @define_direct_property@(vm.well_known_symbol_iterator(), get_without_side_effects(vm.names.entries), JS::Attribute::Configurable | JS::Attribute::Writable); - @define_native_function@(realm, vm.names.keys, keys, 0, default_attributes); - @define_native_function@(realm, vm.names.values, values, 0, default_attributes); - @define_native_function@(realm, vm.names.forEach, for_each, 1, default_attributes); - @define_native_function@(realm, vm.names.get, get, 1, default_attributes); - @define_native_function@(realm, vm.names.has, has, 1, default_attributes); -)~~~"); - - if (!interface.overload_sets.contains("set"sv) && !interface.is_map_readonly) - maplike_generator.appendln(" @define_native_function@(realm, vm.names.set, set, 2, default_attributes);"); - - if (!interface.overload_sets.contains("delete"sv) && !interface.is_map_readonly) - maplike_generator.appendln(" @define_native_function@(realm, vm.names.delete_, delete_, 1, default_attributes);"); - - if (!interface.overload_sets.contains("clear"sv) && !interface.is_map_readonly) - maplike_generator.appendln(" @define_native_function@(realm, vm.names.clear, clear, 0, default_attributes);"); - } - - if (interface.has_unscopable_member && generate_unforgeables == GenerateUnforgeables::No) { - generator.append(R"~~~( - @define_direct_property@(vm.well_known_symbol_unscopables(), unscopable_object, JS::Attribute::Configurable); -)~~~"); - } - - if (generate_unforgeables == GenerateUnforgeables::No) { - generator.append(R"~~~( - @define_direct_property@(vm.well_known_symbol_to_string_tag(), JS::PrimitiveString::create(vm, "@namespaced_name@"_string), JS::Attribute::Configurable); -)~~~"); - } - - if (!window_exposed_only_members_generator.as_string_view().is_empty()) { - includes.add_header("LibWeb/HTML/Window.h"sv); - - auto window_only_property_declarations = generator.fork(); - window_only_property_declarations.set("defines", window_exposed_only_members_generator.as_string_view()); - window_only_property_declarations.append(R"~~~( - if (is(realm.global_object())) { -@defines@ - } -)~~~"); - } - - if (!define_on_existing_object) { - generator.append(R"~~~( - Base::initialize(realm); -)~~~"); - } - - generator.append(R"~~~( -} -)~~~"); -} - -// https://webidl.spec.whatwg.org/#dfn-attribute-setter -static void generate_attribute_setter(SourceGenerator& attribute_generator, IDL::Attribute const& attribute, IDL::Interface const& interface, GeneratedIncludes& includes) -{ - includes.add_header("LibWeb/WebIDL/Tracing.h"sv); - - attribute_generator.append(R"~~~( -JS_DEFINE_NATIVE_FUNCTION(@class_name@::@attribute.setter_callback@) -{ - WebIDL::log_trace(vm, "@class_name@::@attribute.setter_callback@"); - [[maybe_unused]] auto& realm = *vm.current_realm(); - - // 1. Let V be undefined. - auto value = 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) - value = vm.argument(0); - - // 3. Let id be attribute’s identifier. - // 4. Let idlObject be null. - // 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.) - // FIXME: 2. If jsValue is a platform object, then perform a security check, passing jsValue, id, and "setter". - // 3. Let validThis be true if jsValue implements target, or false otherwise. - auto maybe_impl = impl_from(vm); - - // 4. If validThis is false and attribute was not specified with the [LegacyLenientThis] extended attribute, then throw a TypeError. -)~~~"); - if (!attribute.extended_attributes.contains("LegacyLenientThis")) { - attribute_generator.append(R"~~~( - auto impl = TRY(maybe_impl); -)~~~"); - } - - // For [CEReactions]: https://html.spec.whatwg.org/multipage/custom-elements.html#cereactions - - if (attribute.extended_attributes.contains("CEReactions")) { - includes.add_header("LibWeb/HTML/Scripting/SimilarOriginWindowAgent.h"sv); - - // 1. Push a new element queue onto this object's relevant agent's custom element reactions stack. - attribute_generator.append(R"~~~( - auto& reactions_stack = HTML::relevant_similar_origin_window_agent(*impl).custom_element_reactions_stack; - reactions_stack.element_queue_stack.append({}); -)~~~"); - } - - // 5. If attribute is declared with the [Replaceable] extended attribute, then: - if (attribute.extended_attributes.contains("Replaceable"sv)) { - attribute_generator.append(R"~~~( - // 1. Perform ? CreateDataPropertyOrThrow(jsValue, id, V). - TRY(impl->create_data_property_or_throw("@attribute.name@"_utf16_fly_string, value)); - - // 2. Return undefined. - return JS::js_undefined(); -} -)~~~"); - return; - } - - // 6. If validThis is false, then return undefined. - // NB: This is only possible if LegacyLenientThis is defined. - if (attribute.extended_attributes.contains("LegacyLenientThis")) { - attribute_generator.append(R"~~~( - if (maybe_impl.is_error()) - return JS::js_undefined(); - - auto impl = maybe_impl.release_value(); -)~~~"); - } - - // 7. If attribute is declared with a [LegacyLenientSetter] extended attribute, then return undefined. - if (auto legacy_lenient_setter_identifier = attribute.extended_attributes.get("LegacyLenientSetter"sv); legacy_lenient_setter_identifier.has_value()) { - attribute_generator.append(R"~~~( - (void)impl; - return JS::js_undefined(); -} -)~~~"); - return; - } - - // 8. If attribute is declared with a [PutForwards] extended attribute, then: - if (auto put_forwards_identifier = attribute.extended_attributes.get("PutForwards"sv); put_forwards_identifier.has_value()) { - attribute_generator.set("put_forwards_identifier"sv, *put_forwards_identifier); - VERIFY(!put_forwards_identifier->is_empty() && !is_ascii_digit(put_forwards_identifier->byte_at(0))); // Ensure `PropertyKey`s are not Numbers. - - attribute_generator.append(R"~~~( - // 1. Let Q be ? Get(jsValue, id). - auto receiver_value = TRY(impl->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::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 }, value, JS::Object::ShouldThrowExceptions::No)); - - // 5. Return undefined. - return JS::js_undefined(); -} -)~~~"); - return; - } - - generate_to_cpp(attribute_generator, attribute, "value", "", "cpp_value", interface.context, includes, false, {}, false, 0, TypeOptionality::OptionalArgument); - if (attribute.extended_attributes.contains("Reflect")) { - if (attribute.type->name() == "boolean") { - attribute_generator.append(R"~~~( - if (!cpp_value) - impl->remove_attribute("@attribute.reflect_name@"_fly_string); - else - impl->set_attribute_value("@attribute.reflect_name@"_fly_string, String {}); -)~~~"); - } else if (attribute.type->name() == "unsigned long") { - // The setter steps are: - // FIXME: 1. If the reflected IDL attribute is limited to only positive numbers and the given value is 0, then throw an "IndexSizeError" DOMException. - // 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. - // 4. Let newValue be minimum. - // FIXME: 5. If the reflected IDL attribute has a default value, then set newValue to defaultValue. - // 6. If the given value is in the range minimum to 2147483647, inclusive, then set newValue to it. - // 7. Run this's set the content attribute with newValue converted to the shortest possible string representing the number as a valid non-negative integer. - attribute_generator.append(R"~~~( - u32 minimum = 0; - u32 new_value = minimum; - if (cpp_value >= minimum && cpp_value <= 2147483647) - new_value = cpp_value; - impl->set_attribute_value("@attribute.reflect_name@"_fly_string, String::number(new_value)); -)~~~"); - } else if (attribute.type->is_integer() && !attribute.type->is_nullable()) { - attribute_generator.append(R"~~~( - impl->set_attribute_value("@attribute.reflect_name@"_fly_string, String::number(cpp_value)); -)~~~"); - } - // If a reflected IDL attribute has the type T?, where T is either Element or an interface that inherits - // from Element, then with attr being the reflected content attribute name: - // FIXME: Handle "an interface that inherits from Element". - else if (attribute.type->is_nullable() && attribute.type->name() == "Element") { - // The setter steps are: - // 1. If the given value is null, then: - // 1. Set this's explicitly set attr-element to null. - // 2. Run this's delete the content attribute. - // 3. Return. - attribute_generator.append(R"~~~( - static auto const& content_attribute = *new FlyString("@attribute.reflect_name@"_fly_string); - - if (!cpp_value) { - impl->set_@attribute.cpp_name@({}); - impl->remove_attribute(content_attribute); - return JS::js_undefined(); - } -)~~~"); - // 2. Run this's set the content attribute with the empty string. - attribute_generator.append(R"~~~( - impl->set_attribute_value(content_attribute, String {}); -)~~~"); - // 3. Set this's explicitly set attr-element to a weak reference to the given value. - attribute_generator.append(R"~~~( - impl->set_@attribute.cpp_name@(*cpp_value); -)~~~"); - } - // If a reflected IDL attribute has the type FrozenArray?, where T is either Element or an interface - // that inherits from Element, then with attr being the reflected content attribute name: - // FIXME: Handle "an interface that inherits from Element". - else if (is_nullable_frozen_array_of_single_type(attribute.type, "Element"sv)) { - // 1. If the given value is null: - // 1. Set this's explicitly set attr-elements to null. - // 2. Run this's delete the content attribute. - // 3. Return. - attribute_generator.append(R"~~~( - static auto const& content_attribute = *new FlyString("@attribute.reflect_name@"_fly_string); - - if (!cpp_value.has_value()) { - impl->set_@attribute.cpp_name@({}); - impl->remove_attribute(content_attribute); - return JS::js_undefined(); - } -)~~~"); - - // 2. Run this's set the content attribute with the empty string. - attribute_generator.append(R"~~~( - impl->set_attribute_value(content_attribute, String {}); -)~~~"); - - // 3. Let elements be an empty list. - // 4. For each element in the given value: - // 1. Append a weak reference to element to elements. - // 5. Set this's explicitly set attr-elements to elements. - attribute_generator.append(R"~~~( - Vector> elements; - elements.ensure_capacity(cpp_value->size()); - - for (auto const& element : *cpp_value) { - elements.unchecked_append(*element); - } - - impl->set_@attribute.cpp_name@(move(elements)); -)~~~"); - } else if (attribute.type->is_nullable()) { - attribute_generator.append(R"~~~( - if (!cpp_value.has_value()) - impl->remove_attribute("@attribute.reflect_name@"_fly_string); - else - impl->set_attribute_value("@attribute.reflect_name@"_fly_string, cpp_value.value()); -)~~~"); - } else { - attribute_generator.append(R"~~~( - impl->set_attribute_value("@attribute.reflect_name@"_fly_string, cpp_value); -)~~~"); - } - - if (attribute.extended_attributes.contains("CEReactions")) { - includes.add_header("LibWeb/Bindings/MainThreadVM.h"sv); - // 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. - // 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. - // 5. If an exception exception was thrown by the original steps, rethrow exception. - // 6. If a value value was returned from the original steps, return value. - attribute_generator.append(R"~~~( - auto queue = reactions_stack.element_queue_stack.take_last(); - Bindings::invoke_custom_element_reactions(queue); -)~~~"); - } - } else { - if (!attribute.extended_attributes.contains("CEReactions")) { - attribute_generator.append(R"~~~( - TRY(throw_dom_exception_if_needed(vm, [&] { return impl->set_@attribute.cpp_name@(cpp_value); })); -)~~~"); - } else { - includes.add_header("LibWeb/Bindings/MainThreadVM.h"sv); - // 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. - // 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. - // 5. If an exception exception was thrown by the original steps, rethrow exception. - // 6. If a value value was returned from the original steps, return value. - attribute_generator.append(R"~~~( - auto maybe_exception = throw_dom_exception_if_needed(vm, [&] { return impl->set_@attribute.cpp_name@(cpp_value); }); - - auto queue = reactions_stack.element_queue_stack.take_last(); - Bindings::invoke_custom_element_reactions(queue); - - if (maybe_exception.is_error()) - return maybe_exception.release_error(); -)~~~"); - } - } - attribute_generator.append(R"~~~( - return JS::js_undefined(); -} -)~~~"); -} - -// https://webidl.spec.whatwg.org/#interface-prototype-object -static void generate_prototype_or_global_mixin_definitions(IDL::Interface const& interface, StringBuilder& builder, GeneratedIncludes& includes) -{ - SourceGenerator generator { builder }; - - auto is_global_interface = interface.extended_attributes.contains("Global"); - auto class_name = is_global_interface ? interface.global_mixin_class : interface.prototype_class; - generator.set("name", interface.name); - generator.set("namespaced_name", interface.namespaced_name); - generator.set("class_name", class_name); - generator.set("fully_qualified_name", interface.fully_qualified_name); - generator.set("parent_name", interface.parent_name); - generator.set("prototype_base_class", interface.prototype_base_class); - generator.set("prototype_name", interface.prototype_class); // Used for Global Mixin - - if (interface.pair_iterator_types.has_value()) { - generator.set("iterator_name", ByteString::formatted("{}Iterator", interface.fully_qualified_name)); - } - - if (interface.is_callback_interface) - return; - - if (!interface.attributes.is_empty() || !interface.functions.is_empty() || interface.has_stringifier || interface.set_entry_type.has_value() || interface.map_key_type.has_value()) { - generator.append(R"~~~( -[[maybe_unused]] static JS::ThrowCompletionOr<@fully_qualified_name@*> impl_from(JS::VM& vm, JS::Value js_value) -{ -)~~~"); - if (interface.name.is_one_of("EventTarget", "Window")) { - includes.add_header("LibWeb/HTML/Window.h"sv); - includes.add_header("LibWeb/HTML/WindowProxy.h"sv); - - generator.append(R"~~~( - if (auto window_proxy = js_value.as_if()) - return window_proxy->window().ptr(); -)~~~"); - } - - generator.append(R"~~~( - if (auto impl = js_value.as_if<@fully_qualified_name@>()) - return impl.ptr(); - return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "@namespaced_name@"); -} - -[[maybe_unused]] static JS::ThrowCompletionOr<@fully_qualified_name@*> 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); -} - -)~~~"); - } - - for (auto& attribute : interface.attributes) { - if (attribute.extended_attributes.contains("FIXME")) - continue; - collect_include_dependencies(interface.context, *attribute.type, includes); - - bool generated_reflected_element_array = false; - - auto attribute_generator = generator.fork(); - attribute_generator.set("attribute.name", attribute.name); - attribute_generator.set("attribute.getter_callback", attribute.getter_callback_name); - attribute_generator.set("attribute.setter_callback", attribute.setter_callback_name); - - if (attribute.extended_attributes.contains("ImplementedAs")) { - auto implemented_as = attribute.extended_attributes.get("ImplementedAs").value(); - attribute_generator.set("attribute.cpp_name", implemented_as); - } else { - attribute_generator.set("attribute.cpp_name", make_input_acceptable_cpp(attribute.name.to_snakecase())); - } - - if (attribute.extended_attributes.contains("Reflect")) { - // https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#using-reflect-via-idl-extended-attributes:reflected-content-attribute-name - // For one of these primary reflection extended attributes, its reflected content attribute name is the - // string value it takes, if one is provided; otherwise it is the IDL attribute name converted to ASCII - // lowercase. - auto attribute_name = attribute.extended_attributes.get("Reflect").value(); - if (attribute_name.is_empty()) - attribute_name = attribute.name.to_lowercase(); - - attribute_generator.set("attribute.reflect_name", attribute_name); - } else { - attribute_generator.set("attribute.reflect_name", attribute.name.to_snakecase()); - } - - // For [CEReactions]: https://html.spec.whatwg.org/multipage/custom-elements.html#cereactions - includes.add_header("LibWeb/WebIDL/Tracing.h"sv); - - attribute_generator.append(R"~~~( -JS_DEFINE_NATIVE_FUNCTION(@class_name@::@attribute.getter_callback@) -{ - WebIDL::log_trace(vm, "@class_name@::@attribute.getter_callback@"); - [[maybe_unused]] auto& realm = *vm.current_realm(); -)~~~"); - - // NOTE: Create a wrapper lambda so that if the function steps return an exception, we can return that in a rejected promise. - if (attribute.type->name() == "Promise"sv) { - includes.add_header("LibWeb/WebIDL/Promise.h"sv); - attribute_generator.append(R"~~~( - auto steps = [&]() -> JS::ThrowCompletionOr> { -)~~~"); - } - - attribute_generator.append(R"~~~( - [[maybe_unused]] auto* impl = TRY(impl_from(vm)); -)~~~"); - - auto cache_result = false; - if (attribute.extended_attributes.contains("CachedAttribute")) { - VERIFY(attribute.readonly); - cache_result = true; - attribute_generator.append(R"~~~( - auto cached_@attribute.cpp_name@ = impl->cached_@attribute.cpp_name@(); - if (cached_@attribute.cpp_name@) - return cached_@attribute.cpp_name@; -)~~~"); - } - - // https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#reflecting-content-attributes-in-idl-attributes - if (attribute.extended_attributes.contains("Reflect")) { - if (attribute.type->name() == "DOMString") { - if (!attribute.type->is_nullable()) { - // If a reflected IDL attribute has the type DOMString: - // * The getter steps are: - - // 1. Let element be the result of running this's get the element. - // NOTE: this is "impl" above - - // 2. Let contentAttributeValue be the result of running this's get the content attribute. - attribute_generator.append(R"~~~( - auto contentAttributeValue = impl->attribute("@attribute.reflect_name@"_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. - // NOTE: this is "attribute" above - - // NOTE: We do steps 5 and 6 here to have a field to assign to - // 5. If contentAttributeValue is null, then return the empty string. - // 6. Return contentAttributeValue. - attribute_generator.append(R"~~~( - auto retval = contentAttributeValue.value_or(String {}); -)~~~"); - - // 4. If attributeDefinition indicates it is an enumerated attribute and the reflected IDL attribute is defined to be limited to only known values: - if (attribute.extended_attributes.contains("Enumerated")) { - auto valid_enumerations_type = attribute.extended_attributes.get("Enumerated").value(); - auto valid_enumerations = interface.context.enumerations.get(valid_enumerations_type).value(); - - auto missing_value_default = valid_enumerations.extended_attributes.get("MissingValueDefault"); - auto invalid_value_default = valid_enumerations.extended_attributes.get("InvalidValueDefault"); - - attribute_generator.set("missing_enum_default_value", missing_value_default.has_value() ? missing_value_default.value().view() : ""sv); - attribute_generator.set("invalid_enum_default_value", invalid_value_default.has_value() ? invalid_value_default.value().view() : ""sv); - attribute_generator.set("valid_enum_values", MUST(String::join(", "sv, valid_enumerations.values.values(), "\"{}\"_string"sv))); - - // 1. If contentAttributeValue does not correspond to any state of attributeDefinition (e.g., it is null and there is no missing value default), - // or that it is in a state of attributeDefinition with no associated keyword value, then return the empty string. - // NOTE: @invalid_enum_default_value@ is set to the empty string if it isn't present. - attribute_generator.append(R"~~~( - auto did_set_to_missing_value = false; - if (!contentAttributeValue.has_value()) { - retval = "@missing_enum_default_value@"_string; - did_set_to_missing_value = true; - } - - Array valid_values { @valid_enum_values@ }; - - auto has_keyword = false; - for (auto const& value : valid_values) { - if (value.equals_ignoring_ascii_case(retval)) { - has_keyword = true; - retval = value; - break; - } - } - - if (!has_keyword && !did_set_to_missing_value) - retval = "@invalid_enum_default_value@"_string; - )~~~"); - - // 2. Return the canonical keyword for the state of attributeDefinition that contentAttributeValue corresponds to. - // NOTE: This is known to be a valid keyword at this point, so we can just return "retval" - } - } else { - // If a reflected IDL attribute has the type DOMString?: - // * The getter steps are: - - // 1. Let element be the result of running this's get the element. - // NOTE: this is "impl" above - - // 2. Let contentAttributeValue be the result of running this's get the content attribute. - - attribute_generator.append(R"~~~( - auto content_attribute_value = impl->attribute("@attribute.reflect_name@"_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. - // NOTE: this is "attribute" above - - // 4. If attributeDefinition indicates it is an enumerated attribute: - auto is_enumerated = attribute.extended_attributes.contains("Enumerated"); - if (is_enumerated) { - - // NOTE: We run step 4 here to have a field to assign to - // 4. Return the canonical keyword for the state of attributeDefinition that contentAttributeValue corresponds to. - attribute_generator.append(R"~~~( - auto retval = impl->attribute("@attribute.reflect_name@"_fly_string); -)~~~"); - - // 1. Assert: the reflected IDL attribute is limited to only known values. - // NOTE: This is checked by the "Enumerated" extended attribute, so there's nothing additional to assert. - - // 2. Assert: contentAttributeValue corresponds to a state of attributeDefinition. - auto valid_enumerations_type = attribute.extended_attributes.get("Enumerated").value(); - auto valid_enumerations = interface.context.enumerations.get(valid_enumerations_type).value(); - - auto missing_value_default = valid_enumerations.extended_attributes.get("MissingValueDefault"); - auto invalid_value_default = valid_enumerations.extended_attributes.get("InvalidValueDefault"); - - attribute_generator.set("missing_enum_default_value", missing_value_default.has_value() ? missing_value_default.value().view() : ""sv); - attribute_generator.set("invalid_enum_default_value", invalid_value_default.has_value() ? invalid_value_default.value().view() : ""sv); - attribute_generator.set("valid_enum_values", MUST(String::join(", "sv, valid_enumerations.values.values(), "\"{}\"_string"sv))); - - attribute_generator.append(R"~~~( - Array valid_values { @valid_enum_values@ }; - )~~~"); - if (invalid_value_default.has_value()) { - attribute_generator.append(R"~~~( - - if (retval.has_value()) { - auto found = false; - for (auto const& value : valid_values) { - if (value.equals_ignoring_ascii_case(retval.value())) { - found = true; - retval = value; - break; - } - } - - if (!found) - retval = "@invalid_enum_default_value@"_string; - } - )~~~"); - } - - if (missing_value_default.has_value()) { - attribute_generator.append(R"~~~( - if (!retval.has_value()) - retval = "@missing_enum_default_value@"_string; - )~~~"); - } - - attribute_generator.append(R"~~~( - VERIFY(!retval.has_value() || valid_values.contains_slow(retval.value())); -)~~~"); - - // FIXME: 3. If contentAttributeValue corresponds to a state of attributeDefinition with no associated keyword value, then return null. - } else { - // 5. Return contentAttributeValue. - attribute_generator.append(R"~~~( - auto retval = move(content_attribute_value); -)~~~"); - } - } - } - // If a reflected IDL attribute has the type boolean: - else if (attribute.type->name() == "boolean") { - // The getter steps are: - // 1. Let contentAttributeValue be the result of running this's get the content attribute. - // 2. If contentAttributeValue is null, then return false - attribute_generator.append(R"~~~( - auto retval = impl->has_attribute("@attribute.reflect_name@"_fly_string); -)~~~"); - } - // If a reflected IDL attribute has the type long: - else if (attribute.type->name() == "long") { - includes.add_header("LibWeb/HTML/Numbers.h"sv); - // The getter steps are: - // 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 if the reflected IDL attribute is not limited to only non-negative numbers; - // otherwise the result of non-negative integer parsing contentAttributeValue. - // 2. If parsedValue is not an error and is within the long range, then return parsedValue. - attribute_generator.append(R"~~~( - i32 retval = 0; - auto content_attribute_value = impl->get_attribute("@attribute.reflect_name@"_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()) - retval = *maybe_parsed_value; - } -)~~~"); - } - // If a reflected IDL attribute has the type unsigned long, - // FIXME: optionally limited to only positive numbers, limited to only positive numbers with fallback, or clamped to the range [clampedMin, clampedMax], and optionally with a default value defaultValue: - else if (attribute.type->name() == "unsigned long") { - includes.add_header("LibWeb/HTML/Numbers.h"sv); - // The getter steps are: - // 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. - // FIXME: 3. If parsedValue is not an error and the reflected IDL attribute is clamped to the range: - // FIXME: 1. If parsedValue is less than minimum, then return minimum. - // FIXME: 2. Return maximum. - attribute_generator.append(R"~~~( - u32 retval = 0; - auto content_attribute_value = impl->get_attribute("@attribute.reflect_name@"_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) { - retval = *parsed_value; - } - } - } -)~~~"); - } - - // If a reflected IDL attribute has the type USVString: - else if (attribute.type->name() == "USVString") { - // The getter steps are: - // 1. Let element be the result of running this's get the element. - // NOTE: this is "impl" above - // 2. Let contentAttributeValue be the result of running this's get the content attribute. - attribute_generator.append(R"~~~( - auto content_attribute_value = impl->attribute("@attribute.reflect_name@"_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. - // NOTE: this is "attribute" above - - // 4. If attributeDefinition indicates it contains a URL: - if (attribute.extended_attributes.contains("URL")) { - includes.add_header("LibWeb/DOM/Document.h"sv); - // 1. If contentAttributeValue is null, then return the empty string. - // 2. Let urlString be the result of encoding-parsing-and-serializing a URL given contentAttributeValue, relative to element's node document. - // 3. If urlString is not failure, then return urlString. - attribute_generator.append(R"~~~( - if (!content_attribute_value.has_value()) - return JS::PrimitiveString::create(vm, String {}); - - auto url_string = impl->document().encoding_parse_and_serialize_url(*content_attribute_value); - if (url_string.has_value()) - return JS::PrimitiveString::create(vm, url_string.release_value()); -)~~~"); - } - - // 5. Return contentAttributeValue, converted to a scalar value string. - includes.add_header("LibWeb/Infra/Strings.h"sv); - attribute_generator.append(R"~~~( - String retval; - if (content_attribute_value.has_value()) - retval = MUST(Infra::convert_to_scalar_value_string(*content_attribute_value)); -)~~~"); - } - // If a reflected IDL attribute has the type T?, where T is either Element or an interface that inherits - // from Element, then with attr being the reflected content attribute name: - // FIXME: Handle "an interface that inherits from Element". - else if (attribute.type->is_nullable() && attribute.type->name() == "Element") { - // The getter steps are to return the result of running this's get the attr-associated element. - attribute_generator.append(R"~~~( - static auto const& content_attribute = *new FlyString("@attribute.reflect_name@"_fly_string); - - auto retval = impl->get_the_attribute_associated_element(content_attribute, TRY(throw_dom_exception_if_needed(vm, [&] { return impl->@attribute.cpp_name@(); }))); -)~~~"); - } - // If a reflected IDL attribute has the type FrozenArray?, where T is either Element or an interface that - // inherits from Element, then with attr being the reflected content attribute name: - // FIXME: Handle "an interface that inherits from Element". - else if (is_nullable_frozen_array_of_single_type(attribute.type, "Element"sv)) { - generated_reflected_element_array = true; - - // 1. Let elements be the result of running this's get the attr-associated elements. - attribute_generator.append(R"~~~( - static auto const& content_attribute = *new FlyString("@attribute.reflect_name@"_fly_string); - - auto retval = impl->get_the_attribute_associated_elements(content_attribute, TRY(throw_dom_exception_if_needed(vm, [&] { return impl->@attribute.cpp_name@(); }))); -)~~~"); - } else { - attribute_generator.append(R"~~~( - auto retval = impl->get_attribute_value("@attribute.reflect_name@"_fly_string); -)~~~"); - } - - if (generated_reflected_element_array) { - // 2. If the contents of elements is equal to the contents of this's cached attr-associated elements, - // then return this's cached attr-associated elements object. - attribute_generator.append(R"~~~( - auto cached_@attribute.cpp_name@ = TRY(throw_dom_exception_if_needed(vm, [&] { return impl->cached_@attribute.cpp_name@(); })); - if (WebIDL::lists_contain_same_elements(cached_@attribute.cpp_name@, retval)) - return cached_@attribute.cpp_name@; - - auto result = TRY([&]() -> JS::ThrowCompletionOr { -)~~~"); - } - - } else { - attribute_generator.append(R"~~~( - auto retval = TRY(throw_dom_exception_if_needed(vm, [&] { return impl->@attribute.cpp_name@(); })); -)~~~"); - } - - if (attribute.type->name() == "Promise"sv) { - attribute_generator.append(R"~~~( - return retval; - }; - - auto maybe_retval = steps(); - - // And then, if an exception E was thrown: - // 1. If attribute’s type is a promise type, then return ! Call(%Promise.reject%, %Promise%, «E»). - // 2. Otherwise, end these steps and allow the exception to propagate. - if (maybe_retval.is_throw_completion()) - return WebIDL::create_rejected_promise(realm, maybe_retval.error_value())->promise(); - - auto retval = maybe_retval.release_value(); -)~~~"); - } - - if (cache_result) { - generate_wrap_statement(generator, "retval", *attribute.type, interface.context, includes, ByteString::formatted("cached_{} =", attribute_generator.get("attribute.cpp_name"))); - attribute_generator.append(R"~~~( - impl->set_cached_@attribute.cpp_name@(cached_@attribute.cpp_name@); - return cached_@attribute.cpp_name@; -)~~~"); - } else { - generate_return_statement(generator, *attribute.type, interface, includes); - } - - if (generated_reflected_element_array) { - // 3. Let elementsAsFrozenArray be elements, converted to a FrozenArray?. - // 4. Set this's cached attr-associated elements to elements. - // 5. Set this's cached attr-associated elements object to elementsAsFrozenArray. - attribute_generator.append(R"~~~( - }()); - - if (result.is_null()) { - TRY(throw_dom_exception_if_needed(vm, [&] { impl->set_cached_@attribute.cpp_name@({}); })); - } else { - auto& array = as(result.as_object()); - TRY(throw_dom_exception_if_needed(vm, [&] { impl->set_cached_@attribute.cpp_name@(&array); })); - } - - return result; -)~~~"); - } - - attribute_generator.append(R"~~~( -} -)~~~"); - - // https://webidl.spec.whatwg.org/#dfn-attribute-setter - // 2. If attribute is read only and does not have a [LegacyLenientSetter], [PutForwards] or [Replaceable] extended attribute, return undefined; there is no attribute setter function. - if (!attribute.readonly || attribute.extended_attributes.contains("LegacyLenientSetter"sv) || attribute.extended_attributes.contains("PutForwards"sv) || attribute.extended_attributes.contains("Replaceable"sv)) { - generate_attribute_setter(attribute_generator, attribute, interface, includes); - } - } - - // Implementation: Functions - for (auto& function : interface.functions) { - if (function.extended_attributes.contains("FIXME")) - continue; - if (function.extended_attributes.contains("Default")) { - if (function.name == "toJSON"sv && function.return_type->name() == "object"sv) { - generate_default_to_json_function(generator, class_name, interface, includes); - continue; - } - - dbgln("Unknown default operation: {} {}()", function.return_type->name(), function.name); - VERIFY_NOT_REACHED(); - } - - generate_function(generator, function, StaticFunction::No, class_name, interface.fully_qualified_name, interface, includes); - } - - for (auto const& overload_set : interface.overload_sets) { - if (overload_set.value.size() == 1) - continue; - generate_overload_arbiter(generator, overload_set, interface, class_name, IsConstructor::No, includes); - } - - if (interface.has_stringifier) { - includes.add_header("LibWeb/WebIDL/Tracing.h"sv); - - auto stringifier_generator = generator.fork(); - stringifier_generator.set("class_name", class_name); - if (interface.stringifier_attribute.has_value()) - stringifier_generator.set("attribute.cpp_getter_name", interface.stringifier_attribute.value().name.to_snakecase()); - - stringifier_generator.append(R"~~~( -JS_DEFINE_NATIVE_FUNCTION(@class_name@::to_string) -{ - WebIDL::log_trace(vm, "@class_name@::to_string"); - [[maybe_unused]] auto& realm = *vm.current_realm(); - auto* impl = TRY(impl_from(vm)); - -)~~~"); - if (interface.stringifier_attribute.has_value()) { - stringifier_generator.append(R"~~~( - auto retval = TRY(throw_dom_exception_if_needed(vm, [&] { return impl->@attribute.cpp_getter_name@(); })); -)~~~"); - } else { - stringifier_generator.append(R"~~~( - auto retval = TRY(throw_dom_exception_if_needed(vm, [&] { return impl->to_string(); })); -)~~~"); - } - stringifier_generator.append(R"~~~( - - return JS::PrimitiveString::create(vm, move(retval)); -} -)~~~"); - } - - if (interface.pair_iterator_types.has_value()) { - collect_include_dependencies(interface.context, *interface.pair_iterator_types->get<0>(), includes); - collect_include_dependencies(interface.context, *interface.pair_iterator_types->get<1>(), includes); - includes.add_header("LibWeb/WebIDL/Tracing.h"sv); - includes.add_header("LibJS/Runtime/AbstractOperations.h"sv); - - auto iterator_generator = generator.fork(); - iterator_generator.append(R"~~~( -JS_DEFINE_NATIVE_FUNCTION(@class_name@::entries) -{ - WebIDL::log_trace(vm, "@class_name@::entries"); - auto* impl = TRY(impl_from(vm)); - - return TRY(throw_dom_exception_if_needed(vm, [&] { return @iterator_name@::create(*impl, Object::PropertyKind::KeyAndValue); })); -} - -JS_DEFINE_NATIVE_FUNCTION(@class_name@::for_each) -{ - WebIDL::log_trace(vm, "@class_name@::for_each"); - [[maybe_unused]] auto& realm = *vm.current_realm(); - auto* impl = TRY(impl_from(vm)); - - auto callback = vm.argument(0); - if (!callback.is_function()) - return vm.throw_completion(JS::ErrorType::NotAFunction, callback); - - auto this_value = vm.this_value(); - TRY(impl->for_each([&](auto key, auto value) -> JS::ThrowCompletionOr { -)~~~"); - generate_variable_statement(iterator_generator, "wrapped_key", interface.pair_iterator_types->get<0>(), "key", interface, includes); - generate_variable_statement(iterator_generator, "wrapped_value", interface.pair_iterator_types->get<1>(), "value", interface, includes); - iterator_generator.append(R"~~~( - TRY(JS::call(vm, callback.as_function(), vm.argument(1), wrapped_value, wrapped_key, this_value)); - return {}; - })); - - return JS::js_undefined(); -} - -JS_DEFINE_NATIVE_FUNCTION(@class_name@::keys) -{ - WebIDL::log_trace(vm, "@class_name@::keys"); - auto* impl = TRY(impl_from(vm)); - - return TRY(throw_dom_exception_if_needed(vm, [&] { return @iterator_name@::create(*impl, Object::PropertyKind::Key); })); -} - -JS_DEFINE_NATIVE_FUNCTION(@class_name@::values) -{ - WebIDL::log_trace(vm, "@class_name@::values"); - auto* impl = TRY(impl_from(vm)); - - return TRY(throw_dom_exception_if_needed(vm, [&] { return @iterator_name@::create(*impl, Object::PropertyKind::Value); })); -} -)~~~"); - } - - // https://webidl.spec.whatwg.org/#js-asynchronous-iterable - if (interface.async_value_iterator_type.has_value()) { - collect_include_dependencies(interface.context, **interface.async_value_iterator_type, includes); - collect_include_dependencies(interface.context, interface.async_value_iterator_parameters, includes); - includes.add_header("LibWeb/WebIDL/Tracing.h"sv); - - auto iterator_generator = generator.fork(); - iterator_generator.set("iterator_name"sv, ByteString::formatted("{}AsyncIterator", interface.fully_qualified_name)); - iterator_generator.append(R"~~~( -JS_DEFINE_NATIVE_FUNCTION(@class_name@::values) -{ - WebIDL::log_trace(vm, "@class_name@::values"); - auto& realm = *vm.current_realm(); - auto* impl = TRY(impl_from(vm)); -)~~~"); - - StringBuilder arguments_builder; - generate_arguments(generator, interface.async_value_iterator_parameters, arguments_builder, interface, includes); - - iterator_generator.append(R"~~~( - return TRY(throw_dom_exception_if_needed(vm, [&] { return @iterator_name@::create(realm, Object::PropertyKind::Value, *impl)~~~"); - - if (!arguments_builder.is_empty()) { - iterator_generator.set("iterator_arguments"sv, MUST(arguments_builder.to_string())); - iterator_generator.append(", @iterator_arguments@"); - } - - iterator_generator.append(R"~~~(); })); -} -)~~~"); - } - - if (interface.set_entry_type.has_value()) { - auto setlike_generator = generator.fork(); - auto const& set_entry_type = *interface.set_entry_type.value(); - collect_include_dependencies(interface.context, set_entry_type, includes); - auto value_type = cpp_type_name(set_entry_type, interface.context); - setlike_generator.set("value_type", value_type); - - if (set_entry_type.is_string()) { - setlike_generator.set("value_type_check", R"~~~( - if (!value_arg.is_string()) { - return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "String"); - } -)~~~"); - } else { - setlike_generator.set("value_type_check", - MUST(String::formatted(R"~~~( - if (!value_arg.is_object() || !is<{0}>(value_arg.as_object())) {{ - return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "{0}"); - }} -)~~~", - value_type))); - } - - includes.add_header("LibJS/Runtime/AbstractOperations.h"sv); - setlike_generator.append(R"~~~( -// https://webidl.spec.whatwg.org/#js-set-size -JS_DEFINE_NATIVE_FUNCTION(@class_name@::get_size) -{ - WebIDL::log_trace(vm, "@class_name@::size"); - auto* impl = TRY(impl_from(vm)); - - GC::Ref set = impl->set_entries(); - - return set->set_size(); -} - -// https://webidl.spec.whatwg.org/#js-set-entries -JS_DEFINE_NATIVE_FUNCTION(@class_name@::entries) -{ - WebIDL::log_trace(vm, "@class_name@::entries"); - auto& realm = *vm.current_realm(); - auto* impl = TRY(impl_from(vm)); - - GC::Ref set = impl->set_entries(); - - return TRY(throw_dom_exception_if_needed(vm, [&] { return JS::SetIterator::create(realm, *set, Object::PropertyKind::KeyAndValue); })); -} - -// https://webidl.spec.whatwg.org/#js-set-values -JS_DEFINE_NATIVE_FUNCTION(@class_name@::values) -{ - WebIDL::log_trace(vm, "@class_name@::values"); - auto& realm = *vm.current_realm(); - auto* impl = TRY(impl_from(vm)); - - GC::Ref set = impl->set_entries(); - - return TRY(throw_dom_exception_if_needed(vm, [&] { return JS::SetIterator::create(realm, *set, Object::PropertyKind::Value); })); -} - -// https://webidl.spec.whatwg.org/#js-set-forEach -JS_DEFINE_NATIVE_FUNCTION(@class_name@::for_each) -{ - WebIDL::log_trace(vm, "@class_name@::for_each"); - auto* impl = TRY(impl_from(vm)); - - GC::Ref set = impl->set_entries(); - - auto callback = vm.argument(0); - if (!callback.is_function()) - return vm.throw_completion(JS::ErrorType::NotAFunction, callback); - - for (auto& entry : *set) { - auto value = entry.key; - TRY(JS::call(vm, callback.as_function(), vm.argument(1), value, value, impl)); - } - - return JS::js_undefined(); -} - -// https://webidl.spec.whatwg.org/#js-set-has -JS_DEFINE_NATIVE_FUNCTION(@class_name@::has) -{ - WebIDL::log_trace(vm, "@class_name@::has"); - auto* impl = TRY(impl_from(vm)); - - GC::Ref set = impl->set_entries(); - - auto value_arg = vm.argument(0); - @value_type_check@ - - // FIXME: If value is -0, set value to +0. - // What? Which interfaces have a number as their set type? - - return set->set_has(value_arg); -} -)~~~"); - - if (!interface.overload_sets.contains("add"sv) && !interface.is_set_readonly) { - setlike_generator.append(R"~~~( -// https://webidl.spec.whatwg.org/#js-set-add -JS_DEFINE_NATIVE_FUNCTION(@class_name@::add) -{ - WebIDL::log_trace(vm, "@class_name@::add"); - auto* impl = TRY(impl_from(vm)); - - GC::Ref set = impl->set_entries(); - - auto value_arg = vm.argument(0); - @value_type_check@ - - // FIXME: If value is -0, set value to +0. - // What? Which interfaces have a number as their set type? - - set->set_add(value_arg); - impl->on_set_modified_from_js({}); - - return impl; -} -)~~~"); - } - if (!interface.overload_sets.contains("delete"sv) && !interface.is_set_readonly) { - setlike_generator.append(R"~~~( -// https://webidl.spec.whatwg.org/#js-set-delete -JS_DEFINE_NATIVE_FUNCTION(@class_name@::delete_) -{ - WebIDL::log_trace(vm, "@class_name@::delete_"); - auto* impl = TRY(impl_from(vm)); - - GC::Ref set = impl->set_entries(); - - auto value_arg = vm.argument(0); - @value_type_check@ - - // FIXME: If value is -0, set value to +0. - // What? Which interfaces have a number as their set type? - - auto result = set->set_remove(value_arg); - impl->on_set_modified_from_js({}); - return result; -} -)~~~"); - } - if (!interface.overload_sets.contains("clear"sv) && !interface.is_set_readonly) { - setlike_generator.append(R"~~~( -// https://webidl.spec.whatwg.org/#js-set-clear -JS_DEFINE_NATIVE_FUNCTION(@class_name@::clear) -{ - WebIDL::log_trace(vm, "@class_name@::clear"); - auto* impl = TRY(impl_from(vm)); - - GC::Ref set = impl->set_entries(); - - set->set_clear(); - impl->on_set_modified_from_js({}); - - return JS::js_undefined(); -} -)~~~"); - } - } - - if (interface.map_key_type.has_value()) { - auto maplike_generator = generator.fork(); - collect_include_dependencies(interface.context, **interface.map_key_type, includes); - collect_include_dependencies(interface.context, **interface.map_value_type, includes); - - if (interface.map_key_type.value()->is_string()) { - maplike_generator.set("key_arg_converted_to_idl_type", "JS::PrimitiveString::create(vm, TRY(key_arg.to_string(vm)));"); - } else { - TODO(); - } - - if (interface.map_value_type.value()->is_sequence() && interface.map_value_type.value()->as_parameterized().parameters().at(0)->is_numeric()) { - // FIXME: We should convert rather than just fail if we have the wrong type. - maplike_generator.set("value_arg_converted_to_idl_type", R"~~~([&](){ - if (!value_arg.is_object() || !is(value_arg.as_object())) { - return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "Array"); - } - - for (auto const& item : as(value_arg.as_object())) { - if (!item.is_numeric()) { - return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "Number"); - } - } - - return value_arg; -}(); -)~~~"); - } else { - TODO(); - } - - maplike_generator.append(R"~~~( -// https://webidl.spec.whatwg.org/#js-map-size -JS_DEFINE_NATIVE_FUNCTION(@class_name@::get_size) -{ - WebIDL::log_trace(vm, "@class_name@::size"); - auto* impl = TRY(impl_from(vm)); - - GC::Ref map = impl->map_entries(); - - return map->map_size(); -} - -// https://webidl.spec.whatwg.org/#js-map-entries -JS_DEFINE_NATIVE_FUNCTION(@class_name@::entries) -{ - WebIDL::log_trace(vm, "@class_name@::entries"); - auto& realm = *vm.current_realm(); - auto* impl = TRY(impl_from(vm)); - - GC::Ref map = impl->map_entries(); - - return TRY(throw_dom_exception_if_needed(vm, [&] { return JS::MapIterator::create(realm, *map, Object::PropertyKind::KeyAndValue); })); -} - -// https://webidl.spec.whatwg.org/#js-map-keys -JS_DEFINE_NATIVE_FUNCTION(@class_name@::keys) -{ - WebIDL::log_trace(vm, "@class_name@::keys"); - auto& realm = *vm.current_realm(); - auto* impl = TRY(impl_from(vm)); - - GC::Ref map = impl->map_entries(); - - return TRY(throw_dom_exception_if_needed(vm, [&] { return JS::MapIterator::create(realm, *map, Object::PropertyKind::Key); })); -} - -// https://webidl.spec.whatwg.org/#js-map-values -JS_DEFINE_NATIVE_FUNCTION(@class_name@::values) -{ - WebIDL::log_trace(vm, "@class_name@::values"); - auto& realm = *vm.current_realm(); - auto* impl = TRY(impl_from(vm)); - - GC::Ref map = impl->map_entries(); - - return TRY(throw_dom_exception_if_needed(vm, [&] { return JS::MapIterator::create(realm, *map, Object::PropertyKind::Value); })); -} - -// https://webidl.spec.whatwg.org/#js-map-forEach -JS_DEFINE_NATIVE_FUNCTION(@class_name@::for_each) -{ - WebIDL::log_trace(vm, "@class_name@::for_each"); - auto* impl = TRY(impl_from(vm)); - - GC::Ref map = impl->map_entries(); - - auto callback = vm.argument(0); - if (!callback.is_function()) - return vm.throw_completion(JS::ErrorType::NotAFunction, callback); - - for (auto& entry : *map) - TRY(JS::call(vm, callback.as_function(), vm.argument(1), entry.key, entry.value, impl)); - - return JS::js_undefined(); -} - -// https://webidl.spec.whatwg.org/#js-map-get -JS_DEFINE_NATIVE_FUNCTION(@class_name@::get) -{ - WebIDL::log_trace(vm, "@class_name@::get"); - auto* impl = TRY(impl_from(vm)); - - GC::Ref map = impl->map_entries(); - - auto key_arg = vm.argument(0); - auto key = @key_arg_converted_to_idl_type@ - - // FIXME: If key is -0, set key to +0. - // What? Which interfaces have a number as their map key type? - - auto result = map->map_get(key); - - if (!result.has_value()) - return JS::js_undefined(); - - return result.release_value(); -} - -// https://webidl.spec.whatwg.org/#js-map-has -JS_DEFINE_NATIVE_FUNCTION(@class_name@::has) -{ - WebIDL::log_trace(vm, "@class_name@::has"); - auto* impl = TRY(impl_from(vm)); - - GC::Ref map = impl->map_entries(); - - auto key_arg = vm.argument(0); - auto key = @key_arg_converted_to_idl_type@ - - // FIXME: If key is -0, set key to +0. - // What? Which interfaces have a number as their map key type? - - return map->map_has(key); -} -)~~~"); - - if (!interface.overload_sets.contains("set"sv) && !interface.is_map_readonly) { - maplike_generator.append(R"~~~( -// https://webidl.spec.whatwg.org/#js-map-set -JS_DEFINE_NATIVE_FUNCTION(@class_name@::set) -{ - WebIDL::log_trace(vm, "@class_name@::set"); - auto* impl = TRY(impl_from(vm)); - - GC::Ref map = impl->map_entries(); - - auto key_arg = vm.argument(0); - auto key = @key_arg_converted_to_idl_type@ - - // FIXME: If value is -0, set value to +0. - // What? Which interfaces have a number as their set type? - - auto value_arg = vm.argument(1); - auto value = @value_arg_converted_to_idl_type@ - - map->map_set(key, value); - impl->on_map_modified_from_js({}); - - return impl; -} -)~~~"); - } - if (!interface.overload_sets.contains("delete"sv) && !interface.is_map_readonly) { - maplike_generator.append(R"~~~( -// https://webidl.spec.whatwg.org/#js-map-delete -JS_DEFINE_NATIVE_FUNCTION(@class_name@::delete_) -{ - WebIDL::log_trace(vm, "@class_name@::delete_"); - auto* impl = TRY(impl_from(vm)); - - GC::Ref map = impl->map_entries(); - - auto key_arg = vm.argument(0); - auto key = @key_arg_converted_to_idl_type@ - - // FIXME: If key is -0, set key to +0. - // What? Which interfaces have a number as their map key type? - - auto result = map->map_remove(key); - impl->on_map_modified_from_js({}); - - return result; -} -)~~~"); - } - if (!interface.overload_sets.contains("clear"sv) && !interface.is_map_readonly) { - maplike_generator.append(R"~~~( -// https://webidl.spec.whatwg.org/#js-map-clear -JS_DEFINE_NATIVE_FUNCTION(@class_name@::clear) -{ - WebIDL::log_trace(vm, "@class_name@::clear"); - auto* impl = TRY(impl_from(vm)); - - GC::Ref map = impl->map_entries(); - - map->map_clear(); - impl->on_map_modified_from_js({}); - - return JS::js_undefined(); -} -)~~~"); - } - } - - generate_dictionaries(generator, interface, includes); -} - -static void generate_namespace_header(IDL::Interface const& interface, StringBuilder& builder) -{ - SourceGenerator generator { builder }; - - generator.set("namespace_class", interface.namespace_class); - - generator.append(R"~~~( -class @namespace_class@ final : public JS::Object { - JS_OBJECT(@namespace_class@, JS::Object); - GC_DECLARE_ALLOCATOR(@namespace_class@); -public: - explicit @namespace_class@(JS::Realm&); - virtual void initialize(JS::Realm&) override; - virtual ~@namespace_class@() override; - -private: -)~~~"); - - if (interface.extended_attributes.contains("WithGCVisitor"sv)) { - generator.append(R"~~~( - virtual void visit_edges(JS::Cell::Visitor&) override; -)~~~"); - } - - if (interface.extended_attributes.contains("WithFinalizer"sv)) { - generator.append(R"~~~( -public: - static constexpr bool OVERRIDES_FINALIZE = true; - -private: - virtual void finalize() override; -)~~~"); - } - - for (auto const& overload_set : interface.overload_sets) { - auto function_generator = generator.fork(); - function_generator.set("function.name:snakecase", make_input_acceptable_cpp(overload_set.key.to_snakecase())); - function_generator.append(R"~~~( - JS_DECLARE_NATIVE_FUNCTION(@function.name:snakecase@); -)~~~"); - if (overload_set.value.size() > 1) { - for (auto i = 0u; i < overload_set.value.size(); ++i) { - function_generator.set("overload_suffix", ByteString::number(i)); - function_generator.append(R"~~~( - JS_DECLARE_NATIVE_FUNCTION(@function.name:snakecase@@overload_suffix@); -)~~~"); - } - } - } - - generator.append(R"~~~( -}; -)~~~"); -} - -// https://webidl.spec.whatwg.org/#define-the-operations -static void define_the_operations(SourceGenerator& generator, OrderedHashMap> const& operations, StringView define_native_function = "define_native_function"sv) -{ - for (auto const& operation : operations) { - auto function_generator = generator.fork(); - function_generator.set("define_native_function", define_native_function); - function_generator.set("function.name", operation.key); - function_generator.set("function.name:snakecase", make_input_acceptable_cpp(operation.key.to_snakecase())); - function_generator.set("function.length", ByteString::number(get_shortest_function_length(operation.value))); - - // NOTE: This assumes that every function in the overload set has the same attribute set. - if (operation.value[0].extended_attributes.contains("LegacyUnforgable"sv)) - function_generator.set("function.attributes", "JS::Attribute::Enumerable"); - else - function_generator.set("function.attributes", "JS::Attribute::Writable | JS::Attribute::Enumerable | JS::Attribute::Configurable"); - - function_generator.append(R"~~~( - @define_native_function@(realm, "@function.name@"_utf16_fly_string, @function.name:snakecase@, @function.length@, @function.attributes@); -)~~~"); - } -} - -static void generate_constructor_declarations(IDL::Interface const& interface, StringBuilder& builder) -{ - SourceGenerator generator { builder }; - - for (auto const& overload_set : interface.constructor_overload_sets) { - auto constructor_generator = generator.fork(); - if (overload_set.value.size() > 1) { - for (auto i = 0u; i < overload_set.value.size(); ++i) { - constructor_generator.set("overload_suffix", ByteString::number(i)); - constructor_generator.append(R"~~~( - static JS::ThrowCompletionOr> construct@overload_suffix@(InterfaceConstructor&, JS::FunctionObject&); -)~~~"); - } - } - } - - for (auto& attribute : interface.static_attributes) { - auto attribute_generator = generator.fork(); - attribute_generator.set("attribute.name:snakecase", attribute.name.to_snakecase()); - attribute_generator.append(R"~~~( - JS_DECLARE_NATIVE_FUNCTION(@attribute.name:snakecase@_getter); -)~~~"); - - if (!attribute.readonly) { - attribute_generator.append(R"~~~( - JS_DECLARE_NATIVE_FUNCTION(@attribute.name:snakecase@_setter); -)~~~"); - } - } - - for (auto const& overload_set : interface.static_overload_sets) { - auto function_generator = generator.fork(); - function_generator.set("function.name:snakecase", make_input_acceptable_cpp(overload_set.key.to_snakecase())); - function_generator.append(R"~~~( - JS_DECLARE_NATIVE_FUNCTION(@function.name:snakecase@); -)~~~"); - if (overload_set.value.size() > 1) { - for (auto i = 0u; i < overload_set.value.size(); ++i) { - function_generator.set("overload_suffix", ByteString::number(i)); - function_generator.append(R"~~~( - JS_DECLARE_NATIVE_FUNCTION(@function.name:snakecase@@overload_suffix@); -)~~~"); - } - } - } -} - -static void generate_constructor_initialization_for_existing_object(IDL::Interface const& interface, StringBuilder& builder, GeneratedIncludes& includes) -{ - includes.add_header("LibWeb/Bindings/Intrinsics.h"sv); - - SourceGenerator generator { builder }; - - generator.set("name", interface.name); - generator.set("namespaced_name", interface.namespaced_name); - generator.set("prototype_class", interface.prototype_class); - generator.set("constructor_class", interface.constructor_class); - generator.set("parent_name", interface.parent_name); - generator.set("prototype_base_class", interface.prototype_base_class); - - auto shortest_length = interface.constructors.is_empty() ? 0u : NumericLimits::max(); - for (auto const& constructor : interface.constructors) - shortest_length = min(shortest_length, constructor.shortest_length()); - generator.set("constructor.length", ByteString::number(shortest_length)); - - generator.append(R"~~~( -void @constructor_class@::initialize(JS::Realm& realm, JS::NativeFunction& object) -{ - auto& vm = realm.vm(); - [[maybe_unused]] u8 default_attributes = JS::Attribute::Enumerable; -)~~~"); - - if (!interface.is_callback_interface && interface.prototype_base_class != "ObjectPrototype") { - generator.append(R"~~~( - object.set_prototype(&ensure_web_constructor<@prototype_base_class@>(realm, "@parent_name@"_fly_string)); -)~~~"); - } - - generator.append(R"~~~( - 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, "@name@"_string), JS::Attribute::Configurable); -)~~~"); - - if (!interface.is_callback_interface) { - generator.append(R"~~~( - object.define_direct_property(vm.names.prototype, &ensure_web_prototype<@prototype_class@>(realm, "@namespaced_name@"_fly_string), 0); -)~~~"); - } - - for (auto& constant : interface.constants) { - auto constant_generator = generator.fork(); - constant_generator.set("constant.name", constant.name); - - collect_include_dependencies(interface.context, *constant.type, includes); - generate_wrap_statement(constant_generator, constant.value, constant.type, interface.context, includes, ByteString::formatted("auto constant_{}_value =", constant.name)); - - constant_generator.append(R"~~~( - object.define_direct_property("@constant.name@"_utf16_fly_string, constant_@constant.name@_value, JS::Attribute::Enumerable); -)~~~"); - } - - for (auto& attribute : interface.static_attributes) { - collect_include_dependencies(interface.context, *attribute.type, includes); - - auto attribute_generator = generator.fork(); - attribute_generator.set("attribute.name", attribute.name); - attribute_generator.set("attribute.getter_callback", attribute.getter_callback_name); - - if (!attribute.readonly) - attribute_generator.set("attribute.setter_callback", attribute.setter_callback_name); - else - attribute_generator.set("attribute.setter_callback", "nullptr"); - - attribute_generator.append(R"~~~( - object.define_native_accessor(realm, "@attribute.name@"_utf16_fly_string, @attribute.getter_callback@, @attribute.setter_callback@, default_attributes); -)~~~"); - } - - define_the_operations(generator, interface.static_overload_sets, "object.define_native_function"sv); - - generator.append(R"~~~( -} -)~~~"); -} - -static void generate_constructor_static_member_definitions(IDL::Interface const& interface, StringBuilder& builder, GeneratedIncludes& includes) -{ - SourceGenerator generator { builder }; - - generator.set("constructor_class", interface.constructor_class); - generator.set("namespaced_name", interface.namespaced_name); - generator.set("fully_qualified_name", interface.fully_qualified_name); - - generate_shared_constructors(generator, interface, includes); - - for (auto& attribute : interface.static_attributes) { - collect_include_dependencies(interface.context, *attribute.type, includes); - - auto attribute_generator = generator.fork(); - attribute_generator.set("attribute.name", attribute.name); - attribute_generator.set("attribute.getter_callback", attribute.getter_callback_name); - attribute_generator.set("attribute.setter_callback", attribute.setter_callback_name); - - if (attribute.extended_attributes.contains("ImplementedAs")) { - auto implemented_as = attribute.extended_attributes.get("ImplementedAs").value(); - attribute_generator.set("attribute.cpp_name", implemented_as); - } else { - attribute_generator.set("attribute.cpp_name", attribute.name.to_snakecase()); - } - - attribute_generator.append(R"~~~( -JS_DEFINE_NATIVE_FUNCTION(@constructor_class@::@attribute.getter_callback@) -{ - WebIDL::log_trace(vm, "@constructor_class@::@attribute.getter_callback@"); - auto retval = TRY(throw_dom_exception_if_needed(vm, [&] { return @fully_qualified_name@::@attribute.cpp_name@(vm); })); -)~~~"); - - generate_return_statement(generator, *attribute.type, interface, includes); - - attribute_generator.append(R"~~~( -} -)~~~"); - } - - for (auto& function : interface.static_functions) { - if (function.extended_attributes.contains("FIXME")) - continue; - generate_function(generator, function, StaticFunction::Yes, interface.constructor_class, interface.fully_qualified_name, interface, includes); - } - for (auto const& overload_set : interface.static_overload_sets) { - if (overload_set.value.size() == 1) - continue; - generate_overload_arbiter(generator, overload_set, interface, interface.constructor_class, IsConstructor::No, includes); - } - - generator.append(R"~~~( -)~~~"); -} - -static void generate_namespace_implementation(IDL::Interface const& interface, StringBuilder& builder, GeneratedIncludes& includes) -{ - SourceGenerator generator { builder }; - - generator.set("name", interface.name); - generator.set("namespace_class", interface.namespace_class); - generator.set("interface_name", interface.name); - generator.append(R"~~~( -GC_DEFINE_ALLOCATOR(@namespace_class@); - -@namespace_class@::@namespace_class@(JS::Realm& realm) - : Object(ConstructWithPrototypeTag::Tag, realm.intrinsics().object_prototype()) -{ -} - -@namespace_class@::~@namespace_class@() -{ -} - -void @namespace_class@::initialize(JS::Realm& realm) -{ - [[maybe_unused]] auto& vm = this->vm(); - - Base::initialize(realm); - - define_direct_property(vm.well_known_symbol_to_string_tag(), JS::PrimitiveString::create(vm, "@interface_name@"_string), JS::Attribute::Configurable); - -)~~~"); - - define_the_operations(generator, interface.overload_sets); - - if (interface.extended_attributes.contains("WithInitializer"sv)) { - generator.append(R"~~~( - - @name@::initialize(*this, realm); -)~~~"); - } - - generator.append(R"~~~( -} -)~~~"); - - if (interface.extended_attributes.contains("WithGCVisitor"sv)) { - generator.append(R"~~~( -void @namespace_class@::visit_edges(JS::Cell::Visitor& visitor) -{ - Base::visit_edges(visitor); - @name@::visit_edges(*this, visitor); -} -)~~~"); - } - - if (interface.extended_attributes.contains("WithFinalizer"sv)) { - generator.append(R"~~~( -void @namespace_class@::finalize() -{ - Base::finalize(); - @name@::finalize(*this); -} -)~~~"); - } - - for (auto const& function : interface.functions) { - if (function.extended_attributes.contains("FIXME")) - continue; - generate_function(generator, function, StaticFunction::Yes, interface.namespace_class, interface.name, interface, includes); - } - for (auto const& overload_set : interface.overload_sets) { - if (overload_set.value.size() == 1) - continue; - generate_overload_arbiter(generator, overload_set, interface, interface.namespace_class, IsConstructor::No, includes); - } -} - -static void generate_constructor_header(IDL::Interface const& interface, StringBuilder& builder) -{ - SourceGenerator generator { builder }; - - generator.set("constructor_class", interface.constructor_class); - - generator.append(R"~~~( -class @constructor_class@ : public JS::NativeFunction { - JS_OBJECT(@constructor_class@, JS::NativeFunction); - GC_DECLARE_ALLOCATOR(@constructor_class@); -public: - explicit @constructor_class@(JS::Realm&); - virtual void initialize(JS::Realm&) override; - virtual ~@constructor_class@() override; - - virtual JS::ThrowCompletionOr call() override; -)~~~"); - - if (!interface.is_callback_interface) { - generator.append(R"~~~( - virtual JS::ThrowCompletionOr> construct(JS::FunctionObject& new_target) override; - -private: - virtual bool has_constructor() const override { return true; } -)~~~"); - } - - for (auto& attribute : interface.static_attributes) { - auto attribute_generator = generator.fork(); - attribute_generator.set("attribute.name:snakecase", attribute.name.to_snakecase()); - attribute_generator.append(R"~~~( - JS_DECLARE_NATIVE_FUNCTION(@attribute.name:snakecase@_getter); -)~~~"); - - if (!attribute.readonly) { - attribute_generator.append(R"~~~( - JS_DECLARE_NATIVE_FUNCTION(@attribute.name:snakecase@_setter); -)~~~"); - } - } - - for (auto const& overload_set : interface.constructor_overload_sets) { - auto constructor_generator = generator.fork(); - if (overload_set.value.size() > 1) { - for (auto i = 0u; i < overload_set.value.size(); ++i) { - constructor_generator.set("overload_suffix", ByteString::number(i)); - constructor_generator.append(R"~~~( - JS::ThrowCompletionOr> construct@overload_suffix@(JS::FunctionObject& new_target); -)~~~"); - } - } - } - - for (auto const& overload_set : interface.static_overload_sets) { - auto function_generator = generator.fork(); - function_generator.set("function.name:snakecase", make_input_acceptable_cpp(overload_set.key.to_snakecase())); - function_generator.append(R"~~~( - JS_DECLARE_NATIVE_FUNCTION(@function.name:snakecase@); -)~~~"); - if (overload_set.value.size() > 1) { - for (auto i = 0u; i < overload_set.value.size(); ++i) { - function_generator.set("overload_suffix", ByteString::number(i)); - function_generator.append(R"~~~( - JS_DECLARE_NATIVE_FUNCTION(@function.name:snakecase@@overload_suffix@); -)~~~"); - } - } - } - - generator.append(R"~~~( -}; -)~~~"); -} - -static void generate_constructor_implementation(IDL::Interface const& interface, StringBuilder& builder, GeneratedIncludes& includes) -{ - SourceGenerator generator { builder }; - - generator.set("name", interface.name); - generator.set("namespaced_name", interface.namespaced_name); - generator.set("prototype_class", interface.prototype_class); - generator.set("constructor_class", interface.constructor_class); - generator.set("fully_qualified_name", interface.fully_qualified_name); - generator.set("parent_name", interface.parent_name); - generator.set("prototype_base_class", interface.prototype_base_class); - generator.set("constructor.length", "0"); - generator.append(R"~~~( -GC_DEFINE_ALLOCATOR(@constructor_class@); - -@constructor_class@::@constructor_class@(JS::Realm& realm) - : NativeFunction("@name@"_utf16_fly_string, realm.intrinsics().function_prototype()) -{ -} - -@constructor_class@::~@constructor_class@() -{ -} - -JS::ThrowCompletionOr @constructor_class@::call() -{ - return vm().throw_completion(JS::ErrorType::ConstructorWithoutNew, "@namespaced_name@"); -} - -)~~~"); - - if (!interface.is_callback_interface) - generate_constructors(generator, interface, includes); - - generator.append(R"~~~( - -void @constructor_class@::initialize(JS::Realm& realm) -{ - auto& vm = this->vm(); - [[maybe_unused]] u8 default_attributes = JS::Attribute::Enumerable; - - Base::initialize(realm); -)~~~"); - - if (!interface.is_callback_interface && interface.prototype_base_class != "ObjectPrototype") { - generator.append(R"~~~( - set_prototype(&ensure_web_constructor<@prototype_base_class@>(realm, "@parent_name@"_fly_string)); -)~~~"); - } - - generator.append(R"~~~( - define_direct_property(vm.names.length, JS::Value(@constructor.length@), JS::Attribute::Configurable); - define_direct_property(vm.names.name, JS::PrimitiveString::create(vm, "@name@"_string), JS::Attribute::Configurable); -)~~~"); - - if (!interface.is_callback_interface) { - generator.append(R"~~~( - define_direct_property(vm.names.prototype, &ensure_web_prototype<@prototype_class@>(realm, "@namespaced_name@"_fly_string), 0); -)~~~"); - } - - for (auto& constant : interface.constants) { - auto constant_generator = generator.fork(); - constant_generator.set("constant.name", constant.name); - - collect_include_dependencies(interface.context, *constant.type, includes); - generate_wrap_statement(constant_generator, constant.value, constant.type, interface.context, includes, ByteString::formatted("auto constant_{}_value =", constant.name)); - - constant_generator.append(R"~~~( - define_direct_property("@constant.name@"_utf16_fly_string, constant_@constant.name@_value, JS::Attribute::Enumerable); -)~~~"); - } - - for (auto& attribute : interface.static_attributes) { - auto attribute_generator = generator.fork(); - attribute_generator.set("attribute.name", attribute.name); - attribute_generator.set("attribute.getter_callback", attribute.getter_callback_name); - attribute_generator.set("attribute.getter_callback", attribute.getter_callback_name); - - if (!attribute.readonly) - attribute_generator.set("attribute.setter_callback", attribute.setter_callback_name); - else - attribute_generator.set("attribute.setter_callback", "nullptr"); - - attribute_generator.append(R"~~~( - define_native_accessor(realm, "@attribute.name@"_utf16_fly_string, @attribute.getter_callback@, @attribute.setter_callback@, default_attributes); -)~~~"); - } - - define_the_operations(generator, interface.static_overload_sets); - - generator.append(R"~~~( -} -)~~~"); - - // Implementation: Static Attributes - for (auto& attribute : interface.static_attributes) { - auto attribute_generator = generator.fork(); - attribute_generator.set("attribute.name", attribute.name); - attribute_generator.set("attribute.getter_callback", attribute.getter_callback_name); - attribute_generator.set("attribute.setter_callback", attribute.setter_callback_name); - - if (attribute.extended_attributes.contains("ImplementedAs")) { - auto implemented_as = attribute.extended_attributes.get("ImplementedAs").value(); - attribute_generator.set("attribute.cpp_name", implemented_as); - } else { - attribute_generator.set("attribute.cpp_name", attribute.name.to_snakecase()); - } - - attribute_generator.append(R"~~~( -JS_DEFINE_NATIVE_FUNCTION(@constructor_class@::@attribute.getter_callback@) -{ - WebIDL::log_trace(vm, "@constructor_class@::@attribute.getter_callback@"); - auto retval = TRY(throw_dom_exception_if_needed(vm, [&] { return @fully_qualified_name@::@attribute.cpp_name@(vm); })); -)~~~"); - - generate_return_statement(generator, *attribute.type, interface, includes); - - attribute_generator.append(R"~~~( -} -)~~~"); - - // FIXME: Add support for static attribute setters. - } - - // Implementation: Static Functions - for (auto& function : interface.static_functions) { - if (function.extended_attributes.contains("FIXME")) - continue; - generate_function(generator, function, StaticFunction::Yes, interface.constructor_class, interface.fully_qualified_name, interface, includes); - } - for (auto const& overload_set : interface.static_overload_sets) { - if (overload_set.value.size() == 1) - continue; - generate_overload_arbiter(generator, overload_set, interface, interface.constructor_class, IsConstructor::No, includes); - } - - generator.append(R"~~~( -)~~~"); -} - -static void generate_prototype_header(IDL::Interface const& interface, StringBuilder& builder) -{ - SourceGenerator generator { builder }; - - generator.set("prototype_class", interface.prototype_class); - auto has_immutable_prototype = interface_prototype_has_immutable_prototype(interface); - - generator.append(R"~~~( -class @prototype_class@ : public JS::Object { - JS_OBJECT(@prototype_class@, JS::Object); - GC_DECLARE_ALLOCATOR(@prototype_class@); -public: - static void define_unforgeable_attributes(JS::Realm&, JS::Object&); - - explicit @prototype_class@(JS::Realm&); - virtual void initialize(JS::Realm&) override; - virtual ~@prototype_class@() override; -)~~~"); - - if (has_immutable_prototype) { - generator.append(R"~~~( -private: - virtual JS::ThrowCompletionOr internal_set_prototype_of(JS::Object* prototype) override; -)~~~"); - } - - // Generate an empty prototype object for global interfaces. - auto is_global_interface = interface.extended_attributes.contains("Global"); - if (is_global_interface) { - generator.append(R"~~~( -}; -)~~~"); - if (interface.supports_named_properties()) { - generate_named_properties_object_declarations(interface, builder); - } - } else { - generate_prototype_or_global_mixin_declarations(interface, builder); - } -} - -static void generate_prototype_implementation(IDL::Interface const& interface, StringBuilder& builder, GeneratedIncludes& includes) -{ - SourceGenerator generator { builder }; - - generator.set("parent_name", interface.parent_name); - generator.set("prototype_class", interface.prototype_class); - generator.set("prototype_base_class", interface.prototype_base_class); - auto has_immutable_prototype = interface_prototype_has_immutable_prototype(interface); - - generator.append(R"~~~( -GC_DEFINE_ALLOCATOR(@prototype_class@); - -@prototype_class@::@prototype_class@([[maybe_unused]] JS::Realm& realm))~~~"); - if (interface.name == "DOMException") { - // https://webidl.spec.whatwg.org/#es-DOMException-specialness - // Object.getPrototypeOf(DOMException.prototype) === Error.prototype - generator.append(R"~~~( - : Object(ConstructWithPrototypeTag::Tag, realm.intrinsics().error_prototype()) -)~~~"); - } else if (!interface.parent_name.is_empty()) { - generator.append(R"~~~( - : Object(realm, nullptr) -)~~~"); - } else { - generator.append(R"~~~( - : Object(ConstructWithPrototypeTag::Tag, realm.intrinsics().object_prototype()) -)~~~"); - } - - generator.append(R"~~~( -{ -} - -@prototype_class@::~@prototype_class@() -{ -} -)~~~"); - - if (has_immutable_prototype) { - generator.append(R"~~~( - -// https://webidl.spec.whatwg.org/#es-interface-prototype-object -JS::ThrowCompletionOr @prototype_class@::internal_set_prototype_of(JS::Object* prototype) -{ - return set_immutable_prototype(prototype); -} -)~~~"); - } - - // Generate a mostly empty prototype object for global interfaces. - auto is_global_interface = interface.extended_attributes.contains("Global"); - if (is_global_interface) { - generator.append(R"~~~( -void @prototype_class@::initialize(JS::Realm& realm) -{ - Base::initialize(realm); -)~~~"); - if (interface.supports_named_properties()) { - generator.set("named_properties_class", ByteString::formatted("{}Properties", interface.name)); - generator.set("namespaced_name", interface.namespaced_name); - generator.append(R"~~~( - define_direct_property(vm().well_known_symbol_to_string_tag(), JS::PrimitiveString::create(vm(), "@namespaced_name@"_string), JS::Attribute::Configurable); - set_prototype(&ensure_web_prototype<@prototype_class@>(realm, "@named_properties_class@"_fly_string)); -)~~~"); - } else { - generator.append(R"~~~( - set_prototype(&ensure_web_prototype<@prototype_base_class@>(realm, "@parent_name@"_fly_string)); -)~~~"); - } - generator.append(R"~~~( -} -)~~~"); - if (interface.supports_named_properties()) - generate_named_properties_object_definitions(interface, builder); - } else if (interface.is_callback_interface) { - generator.append(R"~~~( -void @prototype_class@::initialize(JS::Realm& realm) -{ - Base::initialize(realm); - set_prototype(realm.intrinsics().object_prototype()); -} -)~~~"); - } else { - generate_prototype_or_global_mixin_initialization(interface, builder, GenerateUnforgeables::No, includes); - generate_prototype_or_global_mixin_initialization(interface, builder, GenerateUnforgeables::Yes, includes); - generate_prototype_or_global_mixin_definitions(interface, builder, includes); - } -} - -static void generate_iterator_prototype_header(IDL::Interface const& interface, StringBuilder& builder) -{ - VERIFY(interface.pair_iterator_types.has_value()); - SourceGenerator generator { builder }; - - generator.set("prototype_class", ByteString::formatted("{}IteratorPrototype", interface.name)); - - generator.append(R"~~~( -class @prototype_class@ : public JS::Object { - JS_OBJECT(@prototype_class@, JS::Object); - GC_DECLARE_ALLOCATOR(@prototype_class@); -public: - explicit @prototype_class@(JS::Realm&); - virtual void initialize(JS::Realm&) override; - virtual ~@prototype_class@() override; - -private: - JS_DECLARE_NATIVE_FUNCTION(next); -}; -)~~~"); -} - -static void generate_iterator_prototype_implementation(IDL::Interface const& interface, StringBuilder& builder, GeneratedIncludes& includes) -{ - VERIFY(interface.pair_iterator_types.has_value()); - includes.add_iterator(ByteString::formatted("{}Iterator", interface.fully_qualified_name.replace("::"sv, "/"sv, ReplaceMode::All))); - - SourceGenerator generator { builder }; - - generator.set("name", ByteString::formatted("{}Iterator", interface.name)); - generator.set("parent_name", interface.parent_name); - generator.set("prototype_class", ByteString::formatted("{}IteratorPrototype", interface.name)); - generator.set("to_string_tag", ByteString::formatted("{} Iterator", interface.name)); - generator.set("prototype_base_class", interface.prototype_base_class); - generator.set("fully_qualified_name", ByteString::formatted("{}Iterator", interface.fully_qualified_name)); - generator.set("possible_include_path", ByteString::formatted("{}Iterator", interface.name.replace("::"sv, "/"sv, ReplaceMode::All))); - generator.append(R"~~~( -GC_DEFINE_ALLOCATOR(@prototype_class@); - -@prototype_class@::@prototype_class@(JS::Realm& realm) - : Object(ConstructWithPrototypeTag::Tag, realm.intrinsics().iterator_prototype()) -{ -} - -@prototype_class@::~@prototype_class@() -{ -} - -void @prototype_class@::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, "@to_string_tag@"_string), JS::Attribute::Configurable); -} - -static JS::ThrowCompletionOr<@fully_qualified_name@*> iterator_impl_from(JS::VM& vm) -{ - auto this_object = TRY(vm.this_value().to_object(vm)); - if (!is<@fully_qualified_name@>(*this_object)) - return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "@name@"); - return static_cast<@fully_qualified_name@*>(this_object.ptr()); -} - -JS_DEFINE_NATIVE_FUNCTION(@prototype_class@::next) -{ - WebIDL::log_trace(vm, "@prototype_class@::next"); - auto* impl = TRY(iterator_impl_from(vm)); - return TRY(throw_dom_exception_if_needed(vm, [&] { return impl->next(); })); -} -)~~~"); -} - -static void generate_async_iterator_prototype_header(IDL::Interface const& interface, StringBuilder& builder) -{ - VERIFY(interface.async_value_iterator_type.has_value()); - SourceGenerator generator { builder }; - - generator.set("prototype_class", ByteString::formatted("{}AsyncIteratorPrototype", interface.name)); - - generator.append(R"~~~( -class @prototype_class@ : public JS::Object { - JS_OBJECT(@prototype_class@, JS::Object); - GC_DECLARE_ALLOCATOR(@prototype_class@); - -public: - explicit @prototype_class@(JS::Realm&); - virtual void initialize(JS::Realm&) override; - virtual ~@prototype_class@() override; - -private: - JS_DECLARE_NATIVE_FUNCTION(next); - )~~~"); - - if (interface.extended_attributes.contains("DefinesAsyncIteratorReturn")) { - generator.append(R"~~~( - JS_DECLARE_NATIVE_FUNCTION(return_); -)~~~"); - } - - generator.append(R"~~~( -}; -)~~~"); -} - -static void generate_async_iterator_prototype_implementation(IDL::Interface const& interface, StringBuilder& builder, GeneratedIncludes& includes) -{ - VERIFY(interface.async_value_iterator_type.has_value()); - includes.add_iterator(ByteString::formatted("{}AsyncIterator", interface.fully_qualified_name.replace("::"sv, "/"sv, ReplaceMode::All))); - includes.add_header("LibWeb/WebIDL/AsyncIterator.h"sv); - - SourceGenerator generator { builder }; - - generator.set("name", ByteString::formatted("{}AsyncIterator", interface.name)); - generator.set("parent_name", interface.parent_name); - generator.set("prototype_class", ByteString::formatted("{}AsyncIteratorPrototype", interface.name)); - generator.set("to_string_tag", ByteString::formatted("{} AsyncIterator", interface.name)); - generator.set("prototype_base_class", interface.prototype_base_class); - generator.set("fully_qualified_name", ByteString::formatted("{}AsyncIterator", interface.fully_qualified_name)); - generator.set("possible_include_path", ByteString::formatted("{}AsyncIterator", interface.name.replace("::"sv, "/"sv, ReplaceMode::All))); - generator.append(R"~~~( -GC_DEFINE_ALLOCATOR(@prototype_class@); - -@prototype_class@::@prototype_class@(JS::Realm& realm) - : Object(ConstructWithPrototypeTag::Tag, realm.intrinsics().async_iterator_prototype()) -{ -} - -@prototype_class@::~@prototype_class@() -{ -} - -void @prototype_class@::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, "@to_string_tag@"_string), JS::Attribute::Configurable); - - define_native_function(realm, vm.names.next, next, 0, JS::default_attributes);)~~~"); - - if (interface.extended_attributes.contains("DefinesAsyncIteratorReturn")) { - generator.append(R"~~~( - define_native_function(realm, vm.names.return_, return_, 1, JS::default_attributes);)~~~"); - } - - generator.append(R"~~~( -} - -JS_DEFINE_NATIVE_FUNCTION(@prototype_class@::next) -{ - WebIDL::log_trace(vm, "@prototype_class@::next"); - auto& realm = *vm.current_realm(); - - return TRY(throw_dom_exception_if_needed(vm, [&] { - return WebIDL::AsyncIterator::next<@fully_qualified_name@>(realm, "@name@"sv); - })); -} -)~~~"); - - if (interface.extended_attributes.contains("DefinesAsyncIteratorReturn")) { - generator.append(R"~~~( -JS_DEFINE_NATIVE_FUNCTION(@prototype_class@::return_) -{ - WebIDL::log_trace(vm, "@prototype_class@::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@>(realm, "@name@"sv, value); - })); -} -)~~~"); - } -} - -static void generate_global_mixin_header(IDL::Interface const& interface, StringBuilder& builder) -{ - SourceGenerator generator { builder }; - - generator.set("class_name", interface.global_mixin_class); - - generator.append(R"~~~( -class @class_name@ { -public: - void initialize(JS::Realm&, JS::Object&); - void define_unforgeable_attributes(JS::Realm&, JS::Object&); - @class_name@(); - virtual ~@class_name@(); - -private: -)~~~"); - - generate_prototype_or_global_mixin_declarations(interface, builder); -} - -static void generate_global_mixin_implementation(IDL::Interface const& interface, StringBuilder& builder, GeneratedIncludes& includes) -{ - SourceGenerator generator { builder }; - - generator.set("class_name", interface.global_mixin_class); - generator.set("prototype_name", interface.prototype_class); - generator.append(R"~~~( -@class_name@::@class_name@() = default; -@class_name@::~@class_name@() = default; -)~~~"); - - generate_prototype_or_global_mixin_initialization(interface, builder, GenerateUnforgeables::No, includes); - generate_prototype_or_global_mixin_initialization(interface, builder, GenerateUnforgeables::Yes, includes); - generate_prototype_or_global_mixin_definitions(interface, builder, includes); -} - -static void generate_idl_value_conversion_implementations(IDL::Module const& module, StringBuilder& builder, GeneratedIncludes& includes) -{ - if (module.own_dictionaries.is_empty() && module.own_enumerations.is_empty()) - return; - - includes.add_header(ByteString::formatted("LibWeb/Bindings/{}.h", LexicalPath { module.module_own_path }.basename(LexicalPath::StripExtension::Yes))); - generate_enumeration_idl_value_conversions(*module.context, module.own_enumerations, builder, includes); - generate_dictionary_idl_value_conversions(*module.context, module.own_dictionaries, builder, includes); -} - -static void emit_interface_header_includes(StringBuilder& builder) -{ - builder.append(R"~~~( -#include -#include -#include - -)~~~"sv); -} - -static void emit_idl_value_conversion_header_includes(IDL::Module const& module, StringBuilder& builder, GeneratedIncludes& includes) -{ - if (module.own_dictionaries.is_empty() && module.own_enumerations.is_empty()) - return; - - SourceGenerator generator { builder }; - emit_idl_value_conversion_support_includes(builder); - if (!module.own_dictionaries.is_empty()) { - includes.emit_headers(generator); - emit_dictionary_support_includes(builder); - } -} - -static void generate_idl_value_conversion_header_declarations(IDL::Module const& module, StringBuilder& builder, GeneratedIncludes& includes) -{ - generate_enumerations(*module.context, module.own_enumerations, builder); - generate_dictionary_structs(*module.context, module.own_dictionaries, builder, includes); -} - -static void generate_header_for_interface(IDL::Interface const& interface, StringBuilder& builder) -{ - auto can_use_shared_constructor = can_use_shared_interface_constructor(interface); - auto can_use_shared_prototype = can_use_shared_interface_prototype(interface); - - if (can_use_shared_constructor) { - SourceGenerator generator { builder }; - generator.set("constructor_class", interface.constructor_class); - generator.append(R"~~~( -struct @constructor_class@ { -public: - static void initialize(JS::Realm&, JS::NativeFunction&); - static JS::ThrowCompletionOr> construct(InterfaceConstructor&, JS::FunctionObject&); - -private: -)~~~"); - generate_constructor_declarations(interface, builder); - generator.append(R"~~~( -}; -)~~~"); - } - - if (can_use_shared_prototype) { - SourceGenerator generator { builder }; - generator.set("prototype_class", interface.prototype_class); - generator.append(R"~~~( -struct @prototype_class@ { -public: - static void initialize(JS::Realm&, JS::Object&); - static void define_unforgeable_attributes(JS::Realm&, JS::Object&); - -private: -)~~~"); - generate_prototype_or_global_mixin_declarations(interface, builder); - return; - } - - if (interface.is_namespace) { - generate_namespace_header(interface, builder); - } else { - if (!can_use_shared_constructor) - generate_constructor_header(interface, builder); - if (!can_use_shared_prototype) - generate_prototype_header(interface, builder); - } - - if (interface.pair_iterator_types.has_value()) - generate_iterator_prototype_header(interface, builder); - - if (interface.async_value_iterator_type.has_value()) - generate_async_iterator_prototype_header(interface, builder); - - if (interface.extended_attributes.contains("Global")) - generate_global_mixin_header(interface, builder); -} - -void generate_header(IDL::Module const& module, StringBuilder& builder) -{ - builder.append("#pragma once\n"sv); - - GeneratedIncludes header_includes; - header_includes.set_bindings_module_to_skip(module.module_own_path); - StringBuilder body_builder; - generate_idl_value_conversion_header_declarations(module, body_builder, header_includes); - if (module.interface.has_value()) - generate_header_for_interface(*module.interface, body_builder); - - if (module.interface.has_value()) - emit_interface_header_includes(builder); - emit_idl_value_conversion_header_includes(module, builder, header_includes); - - builder.append(R"~~~( -namespace Web::Bindings { - -)~~~"sv); - builder.append(body_builder.string_view()); - - builder.append(R"~~~( -} // namespace Web::Bindings -)~~~"sv); -} - -static void generate_implementation_body(IDL::Interface const& interface, StringBuilder& builder, GeneratedIncludes& includes) -{ - includes.add_header(ByteString::formatted("LibWeb/Bindings/{}.h", LexicalPath { interface.module_own_path }.basename(LexicalPath::StripExtension::Yes))); - includes.add_source_module(module_for_path(interface.context, interface.module_own_path)); - includes.add_header("LibWeb/Bindings/ExceptionOrUtils.h"sv); - includes.add_header("LibJS/Runtime/ValueInlines.h"sv); - if (!interface.parent_name.is_empty()) - includes.add_header(ByteString::formatted("LibWeb/Bindings/{}.h", interface.parent_name)); - - auto can_use_shared_constructor = can_use_shared_interface_constructor(interface); - auto can_use_shared_prototype = can_use_shared_interface_prototype(interface); - - if (interface.is_namespace) { - generate_namespace_implementation(interface, builder, includes); - } else { - if (can_use_shared_constructor) { - generate_constructor_initialization_for_existing_object(interface, builder, includes); - generate_constructor_static_member_definitions(interface, builder, includes); - } else { - generate_constructor_implementation(interface, builder, includes); - } - - if (can_use_shared_prototype) { - generate_prototype_or_global_mixin_initialization(interface, builder, GenerateUnforgeables::No, InitializeExistingObject::Yes, includes); - generate_prototype_or_global_mixin_initialization(interface, builder, GenerateUnforgeables::Yes, includes); - generate_prototype_or_global_mixin_definitions(interface, builder, includes); - } else { - generate_prototype_implementation(interface, builder, includes); - } - } - - if (interface.pair_iterator_types.has_value()) - generate_iterator_prototype_implementation(interface, builder, includes); - - if (interface.async_value_iterator_type.has_value()) - generate_async_iterator_prototype_implementation(interface, builder, includes); - - if (interface.extended_attributes.contains("Global")) - generate_global_mixin_implementation(interface, builder, includes); -} - -void generate_implementation(IDL::Module const& module, StringBuilder& builder) -{ - GeneratedIncludes includes; - includes.set_bindings_module_to_skip(module.module_own_path); - StringBuilder body_builder; - - if (module.interface.has_value()) - generate_implementation_body(*module.interface, body_builder, includes); - - generate_idl_value_conversion_implementations(module, body_builder, includes); - - if (body_builder.is_empty()) - return; - - SourceGenerator generator { builder }; - includes.emit_headers(generator); - - builder.append(R"~~~( -namespace Web::Bindings { - -)~~~"sv); - builder.append(body_builder.string_view()); - builder.append(R"~~~( -} // namespace Web::Bindings -)~~~"sv); -} - -} diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.h b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.h deleted file mode 100644 index 547e20f981..0000000000 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2020-2023, Andreas Kling - * Copyright (c) 2021-2023, Linus Groh - * Copyright (c) 2021-2023, Luke Wilde - * Copyright (c) 2022, Ali Mohammad Pur - * Copyright (c) 2023-2024, Kenneth Myhra - * Copyright (c) 2023-2024, Shannon Booth - * Copyright (c) 2023-2024, Matthew Olsson - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include - -namespace IDL { - -void generate_header(Module const&, StringBuilder&); -void generate_implementation(Module const&, StringBuilder&); - -extern Vector g_header_search_paths; - -} diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/main.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/main.cpp deleted file mode 100644 index c835ea93d3..0000000000 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/main.cpp +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (c) 2020-2021, Andreas Kling - * Copyright (c) 2021-2023, Linus Groh - * Copyright (c) 2021, Luke Wilde - * Copyright (c) 2022, Ali Mohammad Pur - * Copyright (c) 2023, Andrew Kaster - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "IDLGenerators.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -template -static ErrorOr write_if_changed(GeneratorFunction generator_function, Input const& input, StringView file_path) -{ - StringBuilder output_builder; - generator_function(input, output_builder); - - auto current_file_or_error = Core::File::open(file_path, Core::File::OpenMode::Read); - if (current_file_or_error.is_error() && current_file_or_error.error().code() != ENOENT) - return current_file_or_error.release_error(); - - bool file_exists = !current_file_or_error.is_error(); - ByteBuffer current_contents; - if (file_exists) - current_contents = TRY(current_file_or_error.value()->read_until_eof()); - - if (!file_exists || current_contents != output_builder.string_view().bytes()) { - auto output_file = TRY(Core::File::open(file_path, Core::File::OpenMode::Write | Core::File::OpenMode::Truncate)); - TRY(output_file->write_until_depleted(output_builder.string_view().bytes())); - } - - return {}; -} - -static ErrorOr generate_depfile(StringView depfile_path, ReadonlySpan dependency_paths, ReadonlySpan output_files) -{ - auto depfile = TRY(Core::File::open_file_or_standard_stream(depfile_path, Core::File::OpenMode::Write)); - - StringBuilder depfile_builder; - bool first_output = true; - for (auto const& s : output_files) { - if (!first_output) - depfile_builder.append(' '); - - depfile_builder.append(s); - first_output = false; - } - depfile_builder.append(':'); - for (auto const& path : dependency_paths) { - depfile_builder.append(" \\\n "sv); - depfile_builder.append(path); - } - depfile_builder.append('\n'); - return depfile->write_until_depleted(depfile_builder.string_view().bytes()); -} - -static ByteString cpp_namespace_for_module_path(ByteString const& module_own_path) -{ - auto path = LexicalPath { module_own_path }; - auto parts = path.parts_view(); - for (size_t i = 0; i + 2 < parts.size(); ++i) { - if (parts[i] != "LibWeb"sv) - continue; - - return parts[i + 1].to_byte_string(); - } - - if (parts.size() >= 2) - return parts[parts.size() - 2].to_byte_string(); - - return {}; -} - -static void assign_fully_qualified_name(IDL::Interface& interface) -{ - auto namespace_name = cpp_namespace_for_module_path(interface.module_own_path); - if (namespace_name.is_empty()) { - interface.fully_qualified_name = interface.implemented_name; - return; - } - - interface.fully_qualified_name = ByteString::formatted("{}::{}", namespace_name, interface.implemented_name); -} - -ErrorOr ladybird_main(Main::Arguments arguments) -{ - Core::ArgsParser args_parser; - Vector paths; - StringView output_path = "-"sv; - StringView depfile_path; - - args_parser.add_option(Core::ArgsParser::Option { - .argument_mode = Core::ArgsParser::OptionArgumentMode::Required, - .help_string = "Add a header search path passed to the compiler", - .long_name = "header-include-path", - .short_name = 'i', - .value_name = "path", - .accept_value = [&](StringView s) { - IDL::g_header_search_paths.append(LexicalPath::canonicalized_path(s)); - return true; - }, - }); - args_parser.add_option(output_path, "Path to output generated files into", "output-path", 'o', "output-path"); - args_parser.add_option(depfile_path, "Path to write dependency file to", "depfile", 'd', "depfile-path"); - args_parser.add_positional_argument(paths, "IDL file(s)", "idl-files"); - args_parser.parse(arguments); - - if (paths.first().starts_with("@"sv)) { - auto file = TRY(Core::File::open(paths.first().substring_view(1), Core::File::OpenMode::Read)); - paths.remove(0); - VERIFY(paths.is_empty()); - - auto string = TRY(file->read_until_eof()); - for (auto const& path : StringView(string).split_view('\n')) { - if (path.is_empty()) - continue; - paths.append(path); - } - } - - IDL::Context context; - Vector dependency_paths; - HashTable seen_dependency_paths; - Vector output_files; - - auto append_dependency_path = [&](ByteString const& dependency_path) { - if (seen_dependency_paths.set(dependency_path) != AK::HashSetResult::InsertedNewEntry) - return; - dependency_paths.append(dependency_path); - }; - - for (auto const& path : paths) { - auto file = TRY(Core::MappedFile::map(path, Core::MappedFile::Mode::ReadOnly)); - auto module = IDL::Parser::parse(path, file->bytes(), context); - append_dependency_path(path); - } - - context.resolve(); - - for (auto& interface : context.owned_interfaces) - assign_fully_qualified_name(*interface); - - for (auto const& module : context.owned_modules) { - auto lexical_path = LexicalPath { module->module_own_path }; - auto path_prefix = LexicalPath::join(output_path, lexical_path.basename(LexicalPath::StripExtension::Yes)); - auto header_path = ByteString::formatted("{}.h", path_prefix); - auto implementation_path = ByteString::formatted("{}.cpp", path_prefix); - - if constexpr (BINDINGS_GENERATOR_DEBUG) { - if (module->interface.has_value()) - module->interface->dump(); - } - - TRY(write_if_changed(&IDL::generate_header, *module, header_path)); - TRY(write_if_changed(&IDL::generate_implementation, *module, implementation_path)); - - output_files.append(header_path); - output_files.append(implementation_path); - } - - if (!depfile_path.is_empty()) { - TRY(generate_depfile(depfile_path, dependency_paths, output_files)); - } - return 0; -} diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt b/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt deleted file mode 100644 index 7c53e06aa4..0000000000 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -set(SOURCES "") # avoid pulling SOURCES from parent scope - -add_subdirectory(BindingsGenerator)