/* * Copyright (c) 2020-2021, Andreas Kling * Copyright (c) 2021-2022, Linus Groh * Copyright (c) 2021, Luke Wilde * Copyright (c) 2022, Ali Mohammad Pur * * SPDX-License-Identifier: BSD-2-Clause */ #include "Namespaces.h" #include #include #include #include Vector s_header_search_paths; namespace IDL { // FIXME: Generate this automatically somehow. static bool is_platform_object(Type const& type) { // NOTE: This is a hand-curated subset of platform object types that are actually relevant // in places where this function is used. If you add IDL code and get compile errors, you // might simply need to add another type here. static constexpr Array types = { "AbortSignal"sv, "Attr"sv, "Blob"sv, "CanvasRenderingContext2D"sv, "Document"sv, "DocumentType"sv, "EventTarget"sv, "FileList"sv, "ImageData"sv, "MutationRecord"sv, "NamedNodeMap"sv, "Node"sv, "Path2D"sv, "Range"sv, "ReadableStream"sv, "Request"sv, "Selection"sv, "Text"sv, "TextMetrics"sv, "URLSearchParams"sv, "WebGLRenderingContext"sv, "Window"sv, }; if (type.name().ends_with("Element"sv)) return true; if (type.name().ends_with("Event"sv)) return true; if (types.span().contains_slow(type.name())) return true; return false; } static StringView sequence_storage_type_to_cpp_storage_type_name(SequenceStorageType sequence_storage_type) { switch (sequence_storage_type) { case SequenceStorageType::Vector: return "Vector"sv; case SequenceStorageType::MarkedVector: return "JS::MarkedVector"sv; default: VERIFY_NOT_REACHED(); } } CppType idl_type_name_to_cpp_type(Type const& type, Interface const& interface); static DeprecatedString union_type_to_variant(UnionType const& union_type, Interface const& interface) { 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 = idl_type_name_to_cpp_type(type, interface); builder.append(cpp_type.name); } if (union_type.includes_undefined()) builder.append(", Empty"sv); builder.append('>'); return builder.to_deprecated_string(); } CppType idl_type_name_to_cpp_type(Type const& type, Interface const& interface) { if (is_platform_object(type)) return { .name = DeprecatedString::formatted("JS::Handle<{}>", type.name()), .sequence_storage_type = SequenceStorageType::MarkedVector }; if (type.is_string()) return { .name = "DeprecatedString", .sequence_storage_type = SequenceStorageType::Vector }; if (type.name() == "double" && !type.is_nullable()) return { .name = "double", .sequence_storage_type = SequenceStorageType::Vector }; if (type.name() == "float" && !type.is_nullable()) return { .name = "float", .sequence_storage_type = SequenceStorageType::Vector }; if (type.name() == "boolean" && !type.is_nullable()) return { .name = "bool", .sequence_storage_type = SequenceStorageType::Vector }; if (type.name() == "unsigned long" && !type.is_nullable()) return { .name = "u32", .sequence_storage_type = SequenceStorageType::Vector }; if (type.name() == "unsigned short" && !type.is_nullable()) return { .name = "u16", .sequence_storage_type = SequenceStorageType::Vector }; if (type.name() == "long long" && !type.is_nullable()) return { .name = "i64", .sequence_storage_type = SequenceStorageType::Vector }; if (type.name() == "unsigned long long" && !type.is_nullable()) return { .name = "u64", .sequence_storage_type = SequenceStorageType::Vector }; if (type.name() == "long" && !type.is_nullable()) return { .name = "i32", .sequence_storage_type = SequenceStorageType::Vector }; if (type.name() == "any") return { .name = "JS::Value", .sequence_storage_type = SequenceStorageType::MarkedVector }; if (type.name() == "BufferSource") return { .name = "JS::Handle", .sequence_storage_type = SequenceStorageType::MarkedVector }; if (type.name() == "sequence") { auto& parameterized_type = verify_cast(type); auto& sequence_type = parameterized_type.parameters().first(); auto sequence_cpp_type = idl_type_name_to_cpp_type(sequence_type, interface); auto storage_type_name = sequence_storage_type_to_cpp_storage_type_name(sequence_cpp_type.sequence_storage_type); if (sequence_cpp_type.sequence_storage_type == SequenceStorageType::MarkedVector) return { .name = storage_type_name, .sequence_storage_type = SequenceStorageType::Vector }; return { .name = DeprecatedString::formatted("{}<{}>", storage_type_name, sequence_cpp_type.name), .sequence_storage_type = SequenceStorageType::Vector }; } if (type.name() == "record") { auto& parameterized_type = verify_cast(type); auto& record_key_type = parameterized_type.parameters()[0]; auto& record_value_type = parameterized_type.parameters()[1]; auto record_key_cpp_type = idl_type_name_to_cpp_type(record_key_type, interface); auto record_value_cpp_type = idl_type_name_to_cpp_type(record_value_type, interface); return { .name = DeprecatedString::formatted("OrderedHashMap<{}, {}>", record_key_cpp_type.name, record_value_cpp_type.name), .sequence_storage_type = SequenceStorageType::Vector }; } if (is(type)) { auto& union_type = verify_cast(type); return { .name = union_type_to_variant(union_type, interface), .sequence_storage_type = SequenceStorageType::Vector }; } if (!type.is_nullable()) { for (auto& dictionary : interface.dictionaries) { if (type.name() == dictionary.key) return { .name = type.name(), .sequence_storage_type = SequenceStorageType::Vector }; } } dbgln("Unimplemented type for idl_type_name_to_cpp_type: {}{}", type.name(), type.is_nullable() ? "?" : ""); TODO(); } static DeprecatedString make_input_acceptable_cpp(DeprecatedString const& input) { if (input.is_one_of("class", "template", "for", "default", "char", "namespace", "delete", "inline")) { StringBuilder builder; builder.append(input); builder.append('_'); return builder.to_deprecated_string(); } return input.replace("-"sv, "_"sv, ReplaceMode::All); } static void generate_include_for_iterator(auto& generator, auto& iterator_path, auto& iterator_name) { auto iterator_generator = generator.fork(); iterator_generator.set("iterator_class.path", iterator_path); iterator_generator.set("iterator_class.name", iterator_name); // FIXME: These may or may not exist, because REASONS. iterator_generator.append(R"~~~( //#if __has_include() # include //#endif )~~~"); } static void generate_include_for(auto& generator, auto& path) { auto forked_generator = generator.fork(); auto path_string = path; for (auto& search_path : s_header_search_paths) { if (!path.starts_with(search_path)) continue; auto relative_path = LexicalPath::relative_path(path, search_path); if (relative_path.length() < path_string.length()) path_string = relative_path; } LexicalPath include_path { path_string }; forked_generator.set("include.path", DeprecatedString::formatted("{}/{}.h", include_path.dirname(), include_path.title())); forked_generator.append(R"~~~( #include <@include.path@> )~~~"); } static void emit_includes_for_all_imports(auto& interface, auto& generator, bool is_iterator = false) { Queue const*> interfaces; HashTable paths_imported; interfaces.enqueue(&interface); while (!interfaces.is_empty()) { auto interface = interfaces.dequeue(); if (paths_imported.contains(interface->module_own_path)) continue; paths_imported.set(interface->module_own_path); for (auto& imported_interface : interface->imported_modules) { if (!paths_imported.contains(imported_interface.module_own_path)) interfaces.enqueue(&imported_interface); } if (!interface->will_generate_code()) continue; generate_include_for(generator, interface->module_own_path); if (is_iterator) { auto iterator_name = DeprecatedString::formatted("{}Iterator", interface->name); auto iterator_path = DeprecatedString::formatted("{}Iterator", interface->fully_qualified_name.replace("::"sv, "/"sv, ReplaceMode::All)); generate_include_for_iterator(generator, iterator_path, iterator_name); } } } template static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter, DeprecatedString const& js_name, DeprecatedString const& js_suffix, DeprecatedString const& cpp_name, IDL::Interface const& interface, bool legacy_null_to_empty_string = false, bool optional = false, Optional optional_default_value = {}, bool variadic = false, size_t recursion_depth = 0) { auto scoped_generator = generator.fork(); auto acceptable_cpp_name = make_input_acceptable_cpp(cpp_name); 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"); scoped_generator.set("parameter.type.name", parameter.type->name()); if (optional_default_value.has_value()) scoped_generator.set("parameter.optional_default_value", *optional_default_value); // FIXME: Add support for optional, variadic, nullable and default values to all types if (parameter.type->is_string()) { if (variadic) { scoped_generator.append(R"~~~( Vector @cpp_name@; @cpp_name@.ensure_capacity(vm.argument_count() - @js_suffix@); for (size_t i = @js_suffix@; i < vm.argument_count(); ++i) { auto to_string_result = TRY(vm.argument(i).to_string(vm)); @cpp_name@.append(move(to_string_result)); } )~~~"); } else if (!optional) { if (!parameter.type->is_nullable()) { scoped_generator.append(R"~~~( DeprecatedString @cpp_name@; if (@js_name@@js_suffix@.is_null() && @legacy_null_to_empty_string@) { @cpp_name@ = DeprecatedString::empty(); } else { @cpp_name@ = TRY(@js_name@@js_suffix@.to_string(vm)); } )~~~"); } else { scoped_generator.append(R"~~~( DeprecatedString @cpp_name@; if (!@js_name@@js_suffix@.is_nullish()) @cpp_name@ = TRY(@js_name@@js_suffix@.to_string(vm)); )~~~"); } } else { scoped_generator.append(R"~~~( DeprecatedString @cpp_name@; if (!@js_name@@js_suffix@.is_undefined()) { if (@js_name@@js_suffix@.is_null() && @legacy_null_to_empty_string@) @cpp_name@ = DeprecatedString::empty(); else @cpp_name@ = TRY(@js_name@@js_suffix@.to_string(vm)); })~~~"); if (optional_default_value.has_value() && (!parameter.type->is_nullable() || optional_default_value.value() != "null")) { scoped_generator.append(R"~~~( else { @cpp_name@ = @parameter.optional_default_value@; } )~~~"); } else { scoped_generator.append(R"~~~( )~~~"); } } } else if (parameter.type->name().is_one_of("EventListener", "NodeFilter")) { // FIXME: Replace this with support for callback interfaces. https://webidl.spec.whatwg.org/#idl-callback-interface if (parameter.type->name() == "EventListener") scoped_generator.set("cpp_type", "IDLEventListener"); else scoped_generator.set("cpp_type", parameter.type->name()); if (parameter.type->is_nullable()) { scoped_generator.append(R"~~~( @cpp_type@* @cpp_name@ = nullptr; if (!@js_name@@js_suffix@.is_nullish()) { if (!@js_name@@js_suffix@.is_object()) return vm.throw_completion(JS::ErrorType::NotAnObject, @js_name@@js_suffix@.to_string_without_side_effects()); auto* callback_type = vm.heap().allocate_without_realm(@js_name@@js_suffix@.as_object(), HTML::incumbent_settings_object()); @cpp_name@ = @cpp_type@::create(realm, *callback_type).ptr(); } )~~~"); } else { scoped_generator.append(R"~~~( if (!@js_name@@js_suffix@.is_object()) return vm.throw_completion(JS::ErrorType::NotAnObject, @js_name@@js_suffix@.to_string_without_side_effects()); auto* callback_type = vm.heap().allocate_without_realm(@js_name@@js_suffix@.as_object(), HTML::incumbent_settings_object()); auto @cpp_name@ = adopt_ref(*new @cpp_type@(move(callback_type))); )~~~"); } } else if (IDL::is_platform_object(*parameter.type)) { if (!parameter.type->is_nullable()) { if (!optional) { scoped_generator.append(R"~~~( if (!@js_name@@js_suffix@.is_object() || !is<@parameter.type.name@>(@js_name@@js_suffix@.as_object())) return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "@parameter.type.name@"); auto& @cpp_name@ = static_cast<@parameter.type.name@&>(@js_name@@js_suffix@.as_object()); )~~~"); } else { scoped_generator.append(R"~~~( Optional> @cpp_name@; if (!@js_name@@js_suffix@.is_undefined()) { if (!@js_name@@js_suffix@.is_object() || !is<@parameter.type.name@>(@js_name@@js_suffix@.as_object())) return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "@parameter.type.name@"); @cpp_name@ = static_cast<@parameter.type.name@&>(@js_name@@js_suffix@.as_object()); } )~~~"); } } else { scoped_generator.append(R"~~~( @parameter.type.name@* @cpp_name@ = nullptr; if (!@js_name@@js_suffix@.is_nullish()) { if (!@js_name@@js_suffix@.is_object() || !is<@parameter.type.name@>(@js_name@@js_suffix@.as_object())) return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "@parameter.type.name@"); @cpp_name@ = &static_cast<@parameter.type.name@&>(@js_name@@js_suffix@.as_object()); } )~~~"); } } else if (parameter.type->name() == "double" || parameter.type->name() == "float") { if (!optional) { scoped_generator.append(R"~~~( @parameter.type.name@ @cpp_name@ = TRY(@js_name@@js_suffix@.to_double(vm)); )~~~"); } else { if (optional_default_value.has_value()) { scoped_generator.append(R"~~~( @parameter.type.name@ @cpp_name@; )~~~"); } else { scoped_generator.append(R"~~~( Optional<@parameter.type.name@> @cpp_name@; )~~~"); } scoped_generator.append(R"~~~( if (!@js_name@@js_suffix@.is_undefined()) @cpp_name@ = TRY(@js_name@@js_suffix@.to_double(vm)); )~~~"); if (optional_default_value.has_value()) { scoped_generator.append(R"~~~( else @cpp_name@ = @parameter.optional_default_value@; )~~~"); } else { scoped_generator.append(R"~~~( )~~~"); } } } else if (parameter.type->name() == "boolean") { if (!optional || optional_default_value.has_value()) { scoped_generator.append(R"~~~( bool @cpp_name@; )~~~"); } else { scoped_generator.append(R"~~~( Optional @cpp_name@; )~~~"); } if (optional) { scoped_generator.append(R"~~~( if (!@js_name@@js_suffix@.is_undefined()) )~~~"); } scoped_generator.append(R"~~~( @cpp_name@ = @js_name@@js_suffix@.to_boolean(); )~~~"); if (optional_default_value.has_value()) { scoped_generator.append(R"~~~( else @cpp_name@ = @parameter.optional_default_value@; )~~~"); } } else if (parameter.type->name() == "unsigned long") { if (!optional || optional_default_value.has_value()) { scoped_generator.append(R"~~~( u32 @cpp_name@; )~~~"); } else { scoped_generator.append(R"~~~( Optional @cpp_name@; )~~~"); } if (optional) { scoped_generator.append(R"~~~( if (!@js_name@@js_suffix@.is_undefined()) )~~~"); } scoped_generator.append(R"~~~( @cpp_name@ = TRY(@js_name@@js_suffix@.to_u32(vm)); )~~~"); if (optional_default_value.has_value()) { scoped_generator.append(R"~~~( else @cpp_name@ = @parameter.optional_default_value@UL; )~~~"); } } else if (parameter.type->name() == "unsigned short") { if (!optional || optional_default_value.has_value()) { scoped_generator.append(R"~~~( u16 @cpp_name@; )~~~"); } else { scoped_generator.append(R"~~~( Optional @cpp_name@; )~~~"); } if (optional) { scoped_generator.append(R"~~~( if (!@js_name@@js_suffix@.is_undefined()) )~~~"); } scoped_generator.append(R"~~~( @cpp_name@ = TRY(@js_name@@js_suffix@.to_u16(vm)); )~~~"); if (optional_default_value.has_value()) { scoped_generator.append(R"~~~( else @cpp_name@ = @parameter.optional_default_value@; )~~~"); } } else if (parameter.type->name() == "long") { if (!optional || optional_default_value.has_value()) { scoped_generator.append(R"~~~( i32 @cpp_name@; )~~~"); } else { scoped_generator.append(R"~~~( Optional @cpp_name@; )~~~"); } if (optional) { scoped_generator.append(R"~~~( if (!@js_name@@js_suffix@.is_undefined()) )~~~"); } scoped_generator.append(R"~~~( @cpp_name@ = TRY(@js_name@@js_suffix@.to_i32(vm)); )~~~"); if (optional_default_value.has_value()) { scoped_generator.append(R"~~~( else @cpp_name@ = @parameter.optional_default_value@L; )~~~"); } } else if (parameter.type->name() == "long long") { if (!optional || optional_default_value.has_value()) { scoped_generator.append(R"~~~( i64 @cpp_name@; )~~~"); } else { scoped_generator.append(R"~~~( Optional @cpp_name@; )~~~"); } if (optional) { scoped_generator.append(R"~~~( if (!@js_name@@js_suffix@.is_undefined()) )~~~"); } scoped_generator.append(R"~~~( @cpp_name@ = TRY(@js_name@@js_suffix@.to_bigint_int64(vm)); )~~~"); if (optional_default_value.has_value()) { scoped_generator.append(R"~~~( else @cpp_name@ = @parameter.optional_default_value@L; )~~~"); } } else if (parameter.type->name() == "Promise") { // NOTE: It's not clear to me where the implicit wrapping of non-Promise values in a resolved // Promise is defined in the spec; https://webidl.spec.whatwg.org/#idl-promise doesn't say // anything of this sort. Both Gecko and Blink do it, however, so I'm sure it's correct. scoped_generator.append(R"~~~( if (!@js_name@@js_suffix@.is_object() || !is(@js_name@@js_suffix@.as_object())) { auto new_promise = JS::Promise::create(realm); new_promise->fulfill(@js_name@@js_suffix@); @js_name@@js_suffix@ = new_promise; } auto @cpp_name@ = JS::make_handle(&static_cast(@js_name@@js_suffix@.as_object())); )~~~"); } else if (parameter.type->name() == "BufferSource") { scoped_generator.append(R"~~~( if (!@js_name@@js_suffix@.is_object() || !(is(@js_name@@js_suffix@.as_object()) || is(@js_name@@js_suffix@.as_object()) || is(@js_name@@js_suffix@.as_object()))) return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "@parameter.type.name@"); // TODO: Should we make this a Variant? auto @cpp_name@ = JS::make_handle(&@js_name@@js_suffix@.as_object()); )~~~"); } else if (parameter.type->name() == "any") { if (!optional) { scoped_generator.append(R"~~~( auto @cpp_name@ = @js_name@@js_suffix@; )~~~"); } else { scoped_generator.append(R"~~~( JS::Value @cpp_name@ = JS::js_undefined(); if (!@js_name@@js_suffix@.is_undefined()) @cpp_name@ = @js_name@@js_suffix@; )~~~"); if (optional_default_value.has_value()) { if (optional_default_value == "null") { scoped_generator.append(R"~~~( else @cpp_name@ = JS::js_null(); )~~~"); } else if (optional_default_value->to_int().has_value() || optional_default_value->to_uint().has_value()) { scoped_generator.append(R"~~~( else @cpp_name@ = JS::Value(@parameter.optional_default_value@); )~~~"); } else { TODO(); } } } } else if (interface.enumerations.contains(parameter.type->name())) { auto enum_generator = scoped_generator.fork(); auto& enumeration = interface.enumerations.find(parameter.type->name())->value; StringView enum_member_name; if (optional_default_value.has_value()) { VERIFY(optional_default_value->length() >= 2 && (*optional_default_value)[0] == '"' && (*optional_default_value)[optional_default_value->length() - 1] == '"'); enum_member_name = optional_default_value->substring_view(1, optional_default_value->length() - 2); } else { enum_member_name = enumeration.first_member; } auto default_value_cpp_name = enumeration.translated_cpp_names.get(enum_member_name); VERIFY(default_value_cpp_name.has_value()); enum_generator.set("enum.default.cpp_value", *default_value_cpp_name); enum_generator.set("js_name.as_string", DeprecatedString::formatted("{}{}_string", enum_generator.get("js_name"sv), enum_generator.get("js_suffix"sv))); enum_generator.append(R"~~~( @parameter.type.name@ @cpp_name@ { @parameter.type.name@::@enum.default.cpp_value@ }; )~~~"); if (optional) { enum_generator.append(R"~~~( if (!@js_name@@js_suffix@.is_undefined()) { )~~~"); } enum_generator.append(R"~~~( 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@::@enum.alt.value@; )~~~"); } // NOTE: Attribute setters return undefined instead of throwing when the string doesn't match an enum value. if constexpr (!IsSame>) { enum_generator.append(R"~~~( else return vm.throw_completion(JS::ErrorType::InvalidEnumerationValue, @js_name.as_string@, "@parameter.type.name@"); )~~~"); } else { enum_generator.append(R"~~~( else return JS::js_undefined(); )~~~"); } if (optional) { enum_generator.append(R"~~~( } )~~~"); } } else if (interface.dictionaries.contains(parameter.type->name())) { if (optional_default_value.has_value() && optional_default_value != "{}") TODO(); auto dictionary_generator = scoped_generator.fork(); dictionary_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@"); @parameter.type.name@ @cpp_name@ {}; )~~~"); auto* current_dictionary = &interface.dictionaries.find(parameter.type->name())->value; while (true) { for (auto& member : current_dictionary->members) { dictionary_generator.set("member_key", member.name); auto member_js_name = make_input_acceptable_cpp(member.name.to_snakecase()); auto member_value_name = DeprecatedString::formatted("{}_value", member_js_name); auto member_property_value_name = DeprecatedString::formatted("{}_property_value", member_js_name); dictionary_generator.set("member_name", member_js_name); dictionary_generator.set("member_value_name", member_value_name); dictionary_generator.set("member_property_value_name", member_property_value_name); dictionary_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@")); )~~~"); if (member.required) { dictionary_generator.append(R"~~~( if (@member_property_value_name@.is_undefined()) return vm.throw_completion(JS::ErrorType::MissingRequiredProperty, "@member_key@"); )~~~"); } else if (!member.default_value.has_value()) { // Assume struct member is Optional and _don't_ assign the generated default // value (e.g. first enum member) when the dictionary member is optional (i.e. // no `required` and doesn't have a default value). // This is needed so that "dictionary has member" checks work as expected. dictionary_generator.append(R"~~~( if (!@member_property_value_name@.is_undefined()) { )~~~"); } generate_to_cpp(dictionary_generator, member, member_property_value_name, "", member_value_name, interface, member.extended_attributes.contains("LegacyNullToEmptyString"), !member.required, member.default_value); dictionary_generator.append(R"~~~( @cpp_name@.@member_name@ = @member_value_name@; )~~~"); if (!member.required && !member.default_value.has_value()) { dictionary_generator.append(R"~~~( } )~~~"); } } if (current_dictionary->parent_name.is_null()) break; VERIFY(interface.dictionaries.contains(current_dictionary->parent_name)); current_dictionary = &interface.dictionaries.find(current_dictionary->parent_name)->value; } } else if (interface.callback_functions.contains(parameter.type->name())) { // https://webidl.spec.whatwg.org/#es-callback-function auto callback_function_generator = scoped_generator.fork(); auto& callback_function = interface.callback_functions.find(parameter.type->name())->value; // 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. 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@.to_string_without_side_effects()); )~~~"); } // 2. Return the IDL callback function type value that represents a reference to the same object that V represents, with the incumbent settings object as the callback context. if (callback_function.is_legacy_treat_non_object_as_null) { callback_function_generator.append(R"~~~( WebIDL::CallbackType* @cpp_name@ = nullptr; if (@js_name@@js_suffix@.is_object()) @cpp_name@ = vm.heap().allocate_without_realm(@js_name@@js_suffix@.as_object(), HTML::incumbent_settings_object()); )~~~"); } else { callback_function_generator.append(R"~~~( auto @cpp_name@ = vm.heap().allocate_without_realm(@js_name@@js_suffix@.as_object(), HTML::incumbent_settings_object()); )~~~"); } } else if (parameter.type->name() == "sequence") { // https://webidl.spec.whatwg.org/#es-sequence auto sequence_generator = scoped_generator.fork(); auto& parameterized_type = verify_cast(*parameter.type); sequence_generator.set("recursion_depth", DeprecatedString::number(recursion_depth)); // An ECMAScript value V is converted to an IDL sequence value as follows: // 1. If Type(V) is not Object, throw a TypeError. // 2. Let method be ? GetMethod(V, @@iterator). // 3. If method is undefined, throw a TypeError. // 4. Return the result of creating a sequence from V and method. if (optional) { auto sequence_cpp_type = idl_type_name_to_cpp_type(parameterized_type.parameters().first(), interface); sequence_generator.set("sequence.type", sequence_cpp_type.name); sequence_generator.set("sequence.storage_type", sequence_storage_type_to_cpp_storage_type_name(sequence_cpp_type.sequence_storage_type)); if (!optional_default_value.has_value()) { if (sequence_cpp_type.sequence_storage_type == IDL::SequenceStorageType::Vector) { sequence_generator.append(R"~~~( Optional<@sequence.storage_type@<@sequence.type@>> @cpp_name@; )~~~"); } else { sequence_generator.append(R"~~~( Optional<@sequence.storage_type@> @cpp_name@; )~~~"); } } else { if (optional_default_value != "[]") TODO(); if (sequence_cpp_type.sequence_storage_type == IDL::SequenceStorageType::Vector) { sequence_generator.append(R"~~~( @sequence.storage_type@<@sequence.type@> @cpp_name@; )~~~"); } else { sequence_generator.append(R"~~~( @sequence.storage_type@ @cpp_name@ { global_object.heap() }; )~~~"); } } sequence_generator.append(R"~~~( if (!@js_name@@js_suffix@.is_undefined()) { )~~~"); } sequence_generator.append(R"~~~( if (!@js_name@@js_suffix@.is_object()) return vm.throw_completion(JS::ErrorType::NotAnObject, @js_name@@js_suffix@.to_string_without_side_effects()); auto* iterator_method@recursion_depth@ = TRY(@js_name@@js_suffix@.get_method(vm, *vm.well_known_symbol_iterator())); if (!iterator_method@recursion_depth@) return vm.throw_completion(JS::ErrorType::NotIterable, @js_name@@js_suffix@.to_string_without_side_effects()); )~~~"); parameterized_type.generate_sequence_from_iterable(sequence_generator, DeprecatedString::formatted("{}{}", acceptable_cpp_name, optional ? "_non_optional" : ""), DeprecatedString::formatted("{}{}", js_name, js_suffix), DeprecatedString::formatted("iterator_method{}", recursion_depth), interface, recursion_depth + 1); if (optional) { sequence_generator.append(R"~~~( @cpp_name@ = move(@cpp_name@_non_optional); } )~~~"); } } else if (parameter.type->name() == "record") { // https://webidl.spec.whatwg.org/#es-record auto record_generator = scoped_generator.fork(); auto& parameterized_type = verify_cast(*parameter.type); record_generator.set("recursion_depth", DeprecatedString::number(recursion_depth)); // A record can only have two types: key type and value type. VERIFY(parameterized_type.parameters().size() == 2); // A record only allows the key to be a string. VERIFY(parameterized_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::idl_type_name_to_cpp_type(parameterized_type, interface); 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@.to_string_without_side_effects()); 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 = parameterized_type.parameters()[0], .name = acceptable_cpp_name, .optional_default_value = {}, .extended_attributes = {} }; generate_to_cpp(record_generator, key_parameter, "key", DeprecatedString::number(recursion_depth), DeprecatedString::formatted("typed_key{}", recursion_depth), interface, false, 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 = parameterized_type.parameters()[1], .name = acceptable_cpp_name, .optional_default_value = {}, .extended_attributes = {} }; generate_to_cpp(record_generator, value_parameter, "value", DeprecatedString::number(recursion_depth), DeprecatedString::formatted("typed_value{}", recursion_depth), interface, false, false, {}, false, recursion_depth + 1); record_generator.append(R"~~~( @cpp_name@.set(typed_key@recursion_depth@, typed_value@recursion_depth@); } )~~~"); } else if (is(*parameter.type)) { // https://webidl.spec.whatwg.org/#es-union auto union_generator = scoped_generator.fork(); auto& union_type = verify_cast(*parameter.type); union_generator.set("union_type", union_type_to_variant(union_type, interface)); union_generator.set("recursion_depth", DeprecatedString::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 : interface.dictionaries) { for (auto& type : types) { if (type.name() == dictionary.key) { dictionary_type = type; break; } } if (dictionary_type) break; } if (dictionary_type) { auto dictionary_generator = union_generator.fork(); dictionary_generator.set("dictionary.type", dictionary_type->name()); // The lambda must take the JS::Value to convert as a parameter instead of capturing it in order to support union types being variadic. dictionary_generator.append(R"~~~( auto @js_name@@js_suffix@_to_dictionary = [&vm](JS::Value @js_name@@js_suffix@) -> JS::ThrowCompletionOr<@dictionary.type@> { )~~~"); IDL::Parameter dictionary_parameter { .type = *dictionary_type, .name = acceptable_cpp_name, .optional_default_value = {}, .extended_attributes = {} }; generate_to_cpp(dictionary_generator, dictionary_parameter, js_name, js_suffix, "dictionary_union_type"sv, interface, false, false, {}, false, recursion_depth + 1); dictionary_generator.append(R"~~~( return dictionary_union_type; }; )~~~"); } // 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. // The lambda must take the JS::Value to convert as a parameter instead of capturing it in order to support union types being variadic. StringBuilder to_variant_captures; to_variant_captures.append("&vm, &realm"sv); if (dictionary_type) to_variant_captures.append(DeprecatedString::formatted(", &{}{}_to_dictionary", js_name, js_suffix)); union_generator.set("to_variant_captures", to_variant_captures.to_deprecated_string()); union_generator.append(R"~~~( auto @js_name@@js_suffix@_to_variant = [@to_variant_captures@](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 {}; )~~~"); } // FIXME: 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()) { // Implement me } 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(@js_name@@js_suffix@_to_dictionary(@js_name@@js_suffix@)) }; )~~~"); } bool includes_object = false; for (auto& type : types) { if (type.name() == "object") { includes_object = true; break; } } // 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 = false; for (auto& type : types) { if (IDL::is_platform_object(type)) { includes_platform_object = true; break; } } if (includes_platform_object) { // 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(type)) continue; auto union_platform_object_type_generator = union_generator.fork(); union_platform_object_type_generator.set("platform_object_type", type.name()); union_platform_object_type_generator.append(R"~~~( if (is<@platform_object_type@>(@js_name@@js_suffix@_object)) return JS::make_handle(static_cast<@platform_object_type@&>(@js_name@@js_suffix@_object)); )~~~"); } // 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 JS::make_handle(@js_name@@js_suffix@_object); )~~~"); } union_generator.append(R"~~~( } )~~~"); } // 6. If Type(V) is Object and V has an [[ArrayBufferData]] internal slot, then // 1. If types includes ArrayBuffer, then return the result of converting V to ArrayBuffer. for (auto& type : types) { if (type.name() == "BufferSource") { union_generator.append(R"~~~( if (is(@js_name@@js_suffix@_object)) return JS::make_handle(@js_name@@js_suffix@_object); )~~~"); } } // 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 @js_name@@js_suffix@_object; )~~~"); } // FIXME: 7. 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. // FIXME: 8. 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. // FIXME: 9. 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. // 10. 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 = verify_cast(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) { )~~~"); sequence_type->generate_sequence_from_iterable(union_generator, acceptable_cpp_name, DeprecatedString::formatted("{}{}", js_name, js_suffix), "method", interface, 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(@js_name@@js_suffix@_to_dictionary(@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 = verify_cast(type); break; } } if (record_type) { IDL::Parameter record_parameter { .type = *record_type, .name = acceptable_cpp_name, .optional_default_value = {}, .extended_attributes = {} }; generate_to_cpp(union_generator, record_parameter, js_name, js_suffix, "record_union_type"sv, interface, false, false, {}, false, recursion_depth + 1); union_generator.append(R"~~~( return record_union_type; )~~~"); } // FIXME: 5. If types includes a callback interface type, then return the result of converting V to that callback interface type. // 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 = false; for (auto& type : types) { if (type.name() == "boolean") { includes_boolean = true; break; } } 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 parameter { .type = *numeric_type, .name = DeprecatedString::empty(), .optional_default_value = {}, .extended_attributes = {} }; generate_to_cpp(union_generator, parameter, js_name, js_suffix, DeprecatedString::formatted("{}{}_number", js_name, js_suffix), interface, false, 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 = false; for (auto& type : types) { if (type.name() == "bigint") { includes_bigint = true; break; } } if (includes_bigint) { union_generator.append(R"~~~( if (@js_name@@js_suffix@.is_bigint()) return @js_name@@js_suffix@.as_bigint(); )~~~"); } bool includes_string = false; for (auto& type : types) { if (type.is_string()) { includes_string = true; break; } } if (includes_string) { // 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. union_generator.append(R"~~~( return TRY(@js_name@@js_suffix@.to_string(vm)); )~~~"); } 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(); auto cpp_type = IDL::idl_type_name_to_cpp_type(*numeric_type, interface); union_numeric_type_generator.set("numeric_type", cpp_type.name); 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 parameter { .type = *numeric_type, .name = DeprecatedString::empty(), .optional_default_value = {}, .extended_attributes = {} }; generate_to_cpp(union_numeric_type_generator, parameter, "x", DeprecatedString::empty(), "x_number", interface, false, 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 parameter { .type = *numeric_type, .name = DeprecatedString::empty(), .optional_default_value = {}, .extended_attributes = {} }; generate_to_cpp(union_generator, parameter, js_name, js_suffix, DeprecatedString::formatted("{}{}_number", js_name, js_suffix), interface, false, 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"); )~~~"); } // Close the lambda and then perform the conversion. union_generator.append(R"~~~( }; )~~~"); if (!variadic) { if (!optional) { union_generator.append(R"~~~( @union_type@ @cpp_name@ = TRY(@js_name@@js_suffix@_to_variant(@js_name@@js_suffix@)); )~~~"); } else { if (!optional_default_value.has_value() || optional_default_value == "null"sv) { union_generator.append(R"~~~( Optional<@union_type@> @cpp_name@; if (!@js_name@@js_suffix@.is_nullish()) @cpp_name@ = TRY(@js_name@@js_suffix@_to_variant(@js_name@@js_suffix@)); )~~~"); } else { if (optional_default_value == "\"\"") { union_generator.append(R"~~~( @union_type@ @cpp_name@ = @js_name@@js_suffix@.is_undefined() ? DeprecatedString::empty() : TRY(@js_name@@js_suffix@_to_variant(@js_name@@js_suffix@)); )~~~"); } else if (optional_default_value == "{}") { VERIFY(dictionary_type); union_generator.append(R"~~~( @union_type@ @cpp_name@ = @js_name@@js_suffix@.is_undefined() ? TRY(@js_name@@js_suffix@_to_dictionary(@js_name@@js_suffix@)) : TRY(@js_name@@js_suffix@_to_variant(@js_name@@js_suffix@)); )~~~"); } else if (optional_default_value->to_int().has_value() || optional_default_value->to_uint().has_value()) { union_generator.append(R"~~~( @union_type@ @cpp_name@ = @js_name@@js_suffix@.is_undefined() ? @parameter.optional_default_value@ : TRY(@js_name@@js_suffix@_to_variant(@js_name@@js_suffix@)); )~~~"); } else { TODO(); } } } } else { union_generator.append(R"~~~( Vector<@union_type@> @cpp_name@; @cpp_name@.ensure_capacity(vm.argument_count() - @js_suffix@); for (size_t i = @js_suffix@; i < vm.argument_count(); ++i) { auto result = TRY(@js_name@@js_suffix@_to_variant(vm.argument(i))); @cpp_name@.append(move(result)); } )~~~"); } } else { dbgln("Unimplemented JS-to-C++ conversion: {}", parameter.type->name()); VERIFY_NOT_REACHED(); } } static void generate_argument_count_check(SourceGenerator& generator, DeprecatedString 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", DeprecatedString::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", DeprecatedString::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) { auto arguments_generator = generator.fork(); Vector parameter_names; size_t argument_index = 0; for (auto& parameter : parameters) { parameter_names.append(make_input_acceptable_cpp(parameter.name.to_snakecase())); if (!parameter.variadic) { arguments_generator.set("argument.index", DeprecatedString::number(argument_index)); arguments_generator.append(R"~~~( auto arg@argument.index@ = vm.argument(@argument.index@); )~~~"); } bool legacy_null_to_empty_string = parameter.extended_attributes.contains("LegacyNullToEmptyString"); generate_to_cpp(generator, parameter, "arg", DeprecatedString::number(argument_index), parameter.name.to_snakecase(), interface, legacy_null_to_empty_string, parameter.optional, parameter.optional_default_value, parameter.variadic, 0); ++argument_index; } arguments_builder.join(", "sv, parameter_names); } // https://webidl.spec.whatwg.org/#create-sequence-from-iterable void IDL::ParameterizedType::generate_sequence_from_iterable(SourceGenerator& generator, DeprecatedString const& cpp_name, DeprecatedString const& iterable_cpp_name, DeprecatedString const& iterator_method_cpp_name, IDL::Interface const& interface, size_t recursion_depth) const { 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", DeprecatedString::number(recursion_depth)); auto sequence_cpp_type = idl_type_name_to_cpp_type(parameters().first(), interface); sequence_generator.set("sequence.type", sequence_cpp_type.name); sequence_generator.set("sequence.storage_type", sequence_storage_type_to_cpp_storage_type_name(sequence_cpp_type.sequence_storage_type)); // To create an IDL value of type sequence given an iterable iterable and an iterator getter method, perform the following steps: // 1. Let iter be ? GetIterator(iterable, sync, method). // 2. Initialize i to be 0. // 3. Repeat // 1. Let next be ? IteratorStep(iter). // 2. If next is false, then return an IDL sequence value of type sequence of length i, where the value of the element at index j is Sj. // 3. Let nextItem be ? IteratorValue(next). // 4. Initialize Si to the result of converting nextItem to an IDL value of type T. // 5. Set i to i + 1. sequence_generator.append(R"~~~( auto iterator@recursion_depth@ = TRY(JS::get_iterator(vm, @iterable_cpp_name@, JS::IteratorHint::Sync, @iterator_method_cpp_name@)); )~~~"); if (sequence_cpp_type.sequence_storage_type == SequenceStorageType::Vector) { sequence_generator.append(R"~~~( @sequence.storage_type@<@sequence.type@> @cpp_name@; )~~~"); } else { sequence_generator.append(R"~~~( @sequence.storage_type@ @cpp_name@ { vm.heap() }; )~~~"); } sequence_generator.append(R"~~~( for (;;) { auto* next@recursion_depth@ = TRY(JS::iterator_step(vm, iterator@recursion_depth@)); if (!next@recursion_depth@) break; auto next_item@recursion_depth@ = TRY(JS::iterator_value(vm, *next@recursion_depth@)); )~~~"); // FIXME: Sequences types should be TypeWithExtendedAttributes, which would allow us to get [LegacyNullToEmptyString] here. IDL::Parameter parameter { .type = parameters().first(), .name = iterable_cpp_name, .optional_default_value = {}, .extended_attributes = {} }; generate_to_cpp(sequence_generator, parameter, "next_item", DeprecatedString::number(recursion_depth), DeprecatedString::formatted("sequence_item{}", recursion_depth), interface, false, false, {}, false, recursion_depth); sequence_generator.append(R"~~~( @cpp_name@.append(sequence_item@recursion_depth@); } )~~~"); } enum class WrappingReference { No, Yes, }; static void generate_wrap_statement(SourceGenerator& generator, DeprecatedString const& value, IDL::Type const& type, IDL::Interface const& interface, StringView result_expression, WrappingReference wrapping_reference = WrappingReference::No, size_t recursion_depth = 0) { auto scoped_generator = generator.fork(); scoped_generator.set("value", value); if (!libweb_interface_namespaces.span().contains_slow(type.name())) { scoped_generator.set("type", type.name()); } else { // e.g. Document.getSelection which returns Selection, which is in the Selection namespace. StringBuilder builder; builder.append(type.name()); builder.append("::"sv); builder.append(type.name()); scoped_generator.set("type", builder.to_deprecated_string()); } scoped_generator.set("result_expression", result_expression); scoped_generator.set("recursion_depth", DeprecatedString::number(recursion_depth)); if (type.name() == "undefined") { scoped_generator.append(R"~~~( @result_expression@ JS::js_undefined(); )~~~"); return; } if (type.is_nullable() && !is(type)) { if (type.is_string()) { scoped_generator.append(R"~~~( if (@value@.is_null()) { @result_expression@ JS::js_null(); } else { )~~~"); } else if (type.name() == "sequence") { scoped_generator.append(R"~~~( if (!@value@.has_value()) { @result_expression@ JS::js_null(); } else { )~~~"); } else { scoped_generator.append(R"~~~( if (!@value@) { @result_expression@ JS::js_null(); } else { )~~~"); } } if (type.is_string()) { scoped_generator.append(R"~~~( @result_expression@ JS::PrimitiveString::create(vm, @value@); )~~~"); } else if (type.name() == "sequence") { // https://webidl.spec.whatwg.org/#es-sequence auto& sequence_generic_type = verify_cast(type); scoped_generator.append(R"~~~( auto new_array@recursion_depth@ = MUST(JS::Array::create(realm, 0)); )~~~"); if (!type.is_nullable()) { 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@); )~~~"); } else { scoped_generator.append(R"~~~( auto& @value@_non_optional = @value@.value(); for (size_t i@recursion_depth@ = 0; i@recursion_depth@ < @value@_non_optional.size(); ++i@recursion_depth@) { auto& element@recursion_depth@ = @value@_non_optional.at(i@recursion_depth@); )~~~"); } // If the type is a platform object we currently return a Vector> from the // C++ implementation, thus allowing us to unwrap the element (a handle) like below. // This might need to change if we switch to a MarkedVector. if (is_platform_object(sequence_generic_type.parameters().first())) { scoped_generator.append(R"~~~( auto* wrapped_element@recursion_depth@ = &(*element@recursion_depth@); )~~~"); } else { generate_wrap_statement(scoped_generator, DeprecatedString::formatted("element{}", recursion_depth), sequence_generic_type.parameters().first(), interface, DeprecatedString::formatted("auto wrapped_element{} =", recursion_depth), WrappingReference::Yes, recursion_depth + 1); } scoped_generator.append(R"~~~( auto property_index@recursion_depth@ = JS::PropertyKey { i@recursion_depth@ }; MUST(new_array@recursion_depth@->create_data_property(property_index@recursion_depth@, wrapped_element@recursion_depth@)); } @result_expression@ new_array@recursion_depth@; )~~~"); } else if (type.name() == "boolean" || type.name() == "double" || type.name() == "float") { scoped_generator.append(R"~~~( @result_expression@ JS::Value(@value@); )~~~"); } else if (type.name() == "short" || type.name() == "long" || type.name() == "unsigned short") { scoped_generator.append(R"~~~( @result_expression@ JS::Value((i32)@value@); )~~~"); } else if (type.name() == "unsigned long") { scoped_generator.append(R"~~~( @result_expression@ JS::Value((u32)@value@); )~~~"); } else if (type.name() == "long long") { scoped_generator.append(R"~~~( @result_expression@ JS::Value((double)@value@); )~~~"); } else if (type.name() == "unsigned long long") { scoped_generator.append(R"~~~( @result_expression@ JS::Value((double)@value@); )~~~"); } else if (type.name() == "Location" || type.name() == "Promise" || type.name() == "Uint8Array" || type.name() == "Uint8ClampedArray" || type.name() == "any") { scoped_generator.append(R"~~~( @result_expression@ @value@; )~~~"); } else if (is(type)) { auto& union_type = verify_cast(type); auto union_types = union_type.flattened_member_types(); auto union_generator = scoped_generator.fork(); union_generator.append(R"~~~( @result_expression@ @value@.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::idl_type_name_to_cpp_type(current_union_type, interface); union_generator.set("current_type", cpp_type.name); union_generator.append(R"~~~( [&vm, &realm](@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, DeprecatedString::formatted("visited_union_value{}", recursion_depth), current_union_type, interface, "return"sv, WrappingReference::No, recursion_depth + 1); // End of current visit lambda. // The last lambda cannot have a trailing comma on the closing brace, unless the type is nullable, where an extra lambda will be generated for the Empty case. if (current_union_type_index != union_types.size() - 1 || type.is_nullable()) { union_generator.append(R"~~~( }, )~~~"); } else { union_generator.append(R"~~~( } )~~~"); } } if (type.is_nullable()) { union_generator.append(R"~~~( [](Empty) -> JS::Value { return JS::js_null(); } )~~~"); } // End of visit. union_generator.append(R"~~~( ); )~~~"); } else if (interface.enumerations.contains(type.name())) { scoped_generator.append(R"~~~( @result_expression@ JS::PrimitiveString::create(vm, Bindings::idl_enum_to_deprecated_string(@value@)); )~~~"); } else if (interface.callback_functions.contains(type.name())) { // https://webidl.spec.whatwg.org/#es-callback-function auto& callback_function = interface.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@) { @result_expression@ JS::js_null(); } else { @result_expression@ &@value@->callback; } )~~~"); } else { scoped_generator.append(R"~~~( @result_expression@ &@value@->callback; )~~~"); } } else if (interface.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* current_dictionary = &interface.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 = DeprecatedString::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 = DeprecatedString::formatted("{}_value", member_key_js_name); dictionary_generator.set("member_value", member_value_js_name); auto wrapped_value_name = DeprecatedString::formatted("auto wrapped_{}", member_value_js_name); dictionary_generator.set("wrapped_value_name", wrapped_value_name); generate_wrap_statement(dictionary_generator, DeprecatedString::formatted("{}.{}", value, member.name), member.type, interface, wrapped_value_name, WrappingReference::No, recursion_depth + 1); dictionary_generator.append(R"~~~( MUST(dictionary_object@recursion_depth@->create_data_property("@member_key@", @wrapped_value_name@)); )~~~"); } if (current_dictionary->parent_name.is_null()) break; VERIFY(interface.dictionaries.contains(current_dictionary->parent_name)); current_dictionary = &interface.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@)); )~~~"); } else { if (wrapping_reference == WrappingReference::No) { scoped_generator.append(R"~~~( @result_expression@ &const_cast<@type@&>(*@value@); )~~~"); } else { scoped_generator.append(R"~~~( @result_expression@ &const_cast<@type@&>(@value@); )~~~"); } } if (type.is_nullable() && !is(type)) { scoped_generator.append(R"~~~( } )~~~"); } } enum class StaticFunction { No, Yes, }; static void generate_return_statement(SourceGenerator& generator, IDL::Type const& return_type, IDL::Interface const& interface) { return generate_wrap_statement(generator, "retval", return_type, interface, "return"sv); } static void generate_variable_statement(SourceGenerator& generator, DeprecatedString const& variable_name, IDL::Type const& value_type, DeprecatedString const& value_name, IDL::Interface const& interface) { auto variable_generator = generator.fork(); variable_generator.set("variable_name", variable_name); variable_generator.append(R"~~~( JS::Value @variable_name@; )~~~"); return generate_wrap_statement(generator, value_name, value_type, interface, DeprecatedString::formatted("{} = ", variable_name)); } static void generate_function(SourceGenerator& generator, IDL::Function const& function, StaticFunction is_static_function, DeprecatedString const& class_name, DeprecatedString const& interface_fully_qualified_name, IDL::Interface const& interface) { 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 ? DeprecatedString::number(function.overload_index) : DeprecatedString::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@) { [[maybe_unused]] auto& realm = *vm.current_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); function_generator.set(".arguments", arguments_builder.string_view()); if (is_static_function == StaticFunction::No) { function_generator.append(R"~~~( [[maybe_unused]] auto retval = TRY(throw_dom_exception_if_needed(vm, [&] { return impl->@function.cpp_name@(@.arguments@); })); )~~~"); } 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", DeprecatedString::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@); })); )~~~"); } generate_return_statement(generator, *function.return_type, interface); function_generator.append(R"~~~( } )~~~"); } // https://webidl.spec.whatwg.org/#compute-the-effective-overload-set static EffectiveOverloadSet 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. NonnullRefPtrVector 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 EffectiveOverloadSet { move(overloads) }; } static DeprecatedString 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 DeprecatedString::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(\"{}\", {}, NonnullRefPtrVector {{", type.name(), type.is_nullable()); append_type_list(builder, parameterized_type.parameters()); builder.append("})"sv); return builder.to_deprecated_string(); } case Type::Kind::Union: { auto const& union_type = type.as_union(); StringBuilder builder; builder.appendff("make_ref_counted(\"{}\", {}, NonnullRefPtrVector {{", type.name(), type.is_nullable()); append_type_list(builder, union_type.member_types()); builder.append("})"sv); return builder.to_deprecated_string(); } } VERIFY_NOT_REACHED(); } static void generate_overload_arbiter(SourceGenerator& generator, auto const& overload_set, DeprecatedString const& class_name) { auto function_generator = generator.fork(); function_generator.set("class_name", class_name); function_generator.set("function.name:snakecase", make_input_acceptable_cpp(overload_set.key.to_snakecase())); function_generator.append(R"~~~( JS_DEFINE_NATIVE_FUNCTION(@class_name@::@function.name:snakecase@) { Optional effective_overload_set; )~~~"); auto all_possible_effective_overloads = compute_the_effective_overload_set(overload_set); auto overloads_set = all_possible_effective_overloads.items(); 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", DeprecatedString::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) { // FIXME: Calculate the distinguishing argument index now instead of at runtime. auto effective_overload_count = 0; for (auto const& overload : overloads_set) { if (overload.types.size() == argument_count) effective_overload_count++; } if (effective_overload_count == 0) continue; function_generator.set("current_argument_count", DeprecatedString::number(argument_count)); function_generator.set("overload_count", DeprecatedString::number(effective_overload_count)); function_generator.appendln(R"~~~( case @current_argument_count@: { Vector overloads; overloads.ensure_capacity(@overload_count@); )~~~"); for (auto& overload : overloads_set) { if (overload.types.size() != argument_count) continue; StringBuilder types_builder; types_builder.append("NonnullRefPtrVector { "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); } 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", DeprecatedString::number(overload.callable_id)); function_generator.set("overload.types", types_builder.to_deprecated_string()); function_generator.set("overload.optionality_values", optionality_builder.to_deprecated_string()); function_generator.appendln(" overloads.empend(@overload.callable_id@, @overload.types@, @overload.optionality_values@);"); } function_generator.append(R"~~~( effective_overload_set.emplace(move(overloads)); break; } )~~~"); } function_generator.append(R"~~~( } if (!effective_overload_set.has_value()) return vm.throw_completion(JS::ErrorType::OverloadResolutionFailed); auto chosen_overload = TRY(WebIDL::resolve_overload(vm, effective_overload_set.value())); switch (chosen_overload.callable_id) { )~~~"); for (auto i = 0u; i < overload_set.value.size(); ++i) { function_generator.set("overload_id", DeprecatedString::number(i)); function_generator.append(R"~~~( case @overload_id@: return @function.name:snakecase@@overload_id@(vm); )~~~"); } function_generator.append(R"~~~( default: VERIFY_NOT_REACHED(); } } )~~~"); } void generate_constructor_header(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("constructor_class", interface.constructor_class); generator.set("constructor_class:snakecase", interface.constructor_class.to_snakecase()); generator.append(R"~~~( #pragma once #include namespace Web::Bindings { class @constructor_class@ : public JS::NativeFunction { JS_OBJECT(@constructor_class@, JS::NativeFunction); public: explicit @constructor_class@(JS::Realm&); virtual void initialize(JS::Realm&) override; virtual ~@constructor_class@() override; virtual JS::ThrowCompletionOr call() override; virtual JS::ThrowCompletionOr construct(JS::FunctionObject& new_target) override; private: virtual bool has_constructor() const override { return true; } )~~~"); 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", DeprecatedString::number(i)); function_generator.append(R"~~~( JS_DECLARE_NATIVE_FUNCTION(@function.name:snakecase@@overload_suffix@); )~~~"); } } } generator.append(R"~~~( }; } // namespace Web::Bindings )~~~"); } void generate_constructor_implementation(IDL::Interface const& interface, StringBuilder& builder) { SourceGenerator generator { builder }; generator.set("name", interface.name); generator.set("prototype_class", interface.prototype_class); generator.set("constructor_class", interface.constructor_class); generator.set("prototype_class:snakecase", interface.prototype_class.to_snakecase()); generator.set("fully_qualified_name", interface.fully_qualified_name); generator.append(R"~~~( #include #include #include #include #include #include #include #include #if __has_include() # include #elif __has_include() # include #elif __has_include() # include #elif __has_include() # include #elif __has_include() # include #elif __has_include() # include #elif __has_include() # include #elif __has_include() # include #elif __has_include() # include #elif __has_include() # include #elif __has_include() # include #elif __has_include() # include #elif __has_include() # include #elif __has_include() # include #elif __has_include() # include #elif __has_include() # include #elif __has_include() # include #elif __has_include() # include #elif __has_include() # include #endif )~~~"); for (auto& path : interface.required_imported_paths) generate_include_for(generator, path); emit_includes_for_all_imports(interface, generator, interface.pair_iterator_types.has_value()); generator.append(R"~~~( // FIXME: This is a total hack until we can figure out the namespace for a given type somehow. using namespace Web::CSS; using namespace Web::DOM; using namespace Web::DOMParsing; using namespace Web::Fetch; using namespace Web::FileAPI; using namespace Web::Geometry; using namespace Web::HTML; using namespace Web::IntersectionObserver; using namespace Web::RequestIdleCallback; using namespace Web::ResizeObserver; using namespace Web::Selection; using namespace Web::Streams; using namespace Web::UIEvents; using namespace Web::URL; using namespace Web::XHR; using namespace Web::WebGL; using namespace Web::WebIDL; namespace Web::Bindings { @constructor_class@::@constructor_class@(JS::Realm& realm) : NativeFunction(*realm.intrinsics().function_prototype()) { } @constructor_class@::~@constructor_class@() { } JS::ThrowCompletionOr @constructor_class@::call() { return vm().throw_completion(JS::ErrorType::ConstructorWithoutNew, "@name@"); } JS::ThrowCompletionOr @constructor_class@::construct(FunctionObject&) { )~~~"); if (interface.constructors.is_empty()) { // No constructor generator.set("constructor.length", "0"); generator.append(R"~~~( return vm().throw_completion(JS::ErrorType::NotAConstructor, "@name@"); )~~~"); } else if (interface.constructors.size() == 1) { // Single constructor auto& constructor = interface.constructors[0]; generator.set("constructor.length", DeprecatedString::number(constructor.shortest_length())); generator.append(R"~~~( auto& vm = this->vm(); auto& realm = *vm.current_realm(); )~~~"); 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); generator.set(".constructor_arguments", arguments_builder.string_view()); generator.append(R"~~~( auto impl = TRY(throw_dom_exception_if_needed(vm, [&] { return @fully_qualified_name@::construct_impl(realm, @.constructor_arguments@); })); )~~~"); } else { generator.append(R"~~~( auto impl = TRY(throw_dom_exception_if_needed(vm, [&] { return @fully_qualified_name@::construct_impl(realm); })); )~~~"); } generator.append(R"~~~( return &(*impl); )~~~"); } else { // Multiple constructor overloads - can't do that yet. TODO(); } generator.append(R"~~~( } void @constructor_class@::initialize(JS::Realm& realm) { auto& vm = this->vm(); [[maybe_unused]] u8 default_attributes = JS::Attribute::Enumerable; NativeFunction::initialize(realm); define_direct_property(vm.names.prototype, &ensure_web_prototype<@prototype_class@>(realm, "@name@"), 0); define_direct_property(vm.names.length, JS::Value(@constructor.length@), JS::Attribute::Configurable); )~~~"); for (auto& constant : interface.constants) { auto constant_generator = generator.fork(); constant_generator.set("constant.name", constant.name); generate_wrap_statement(constant_generator, constant.value, constant.type, interface, DeprecatedString::formatted("auto constant_{}_value =", constant.name)); constant_generator.append(R"~~~( define_direct_property("@constant.name@", constant_@constant.name@_value, JS::Attribute::Enumerable); )~~~"); } // https://webidl.spec.whatwg.org/#es-operations for (auto const& overload_set : interface.static_overload_sets) { auto function_generator = generator.fork(); 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", DeprecatedString::number(get_shortest_function_length(overload_set.value))); function_generator.append(R"~~~( define_native_function(realm, "@function.name@", @function.name:snakecase@, @function.length@, default_attributes); )~~~"); } generator.append(R"~~~( } )~~~"); // Implementation: Static Functions for (auto& function : interface.static_functions) generate_function(generator, function, StaticFunction::Yes, interface.constructor_class, interface.fully_qualified_name, interface); for (auto const& overload_set : interface.static_overload_sets) { if (overload_set.value.size() == 1) continue; generate_overload_arbiter(generator, overload_set, interface.constructor_class); } generator.append(R"~~~( } // namespace Web::Bindings )~~~"); } void generate_prototype_header(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("prototype_class", interface.prototype_class); generator.set("prototype_class:snakecase", interface.prototype_class.to_snakecase()); generator.append(R"~~~( #pragma once #include namespace Web::Bindings { class @prototype_class@ : public JS::Object { JS_OBJECT(@prototype_class@, JS::Object); public: explicit @prototype_class@(JS::Realm&); virtual void initialize(JS::Realm&) override; virtual ~@prototype_class@() override; private: )~~~"); 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", DeprecatedString::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); )~~~"); } for (auto& attribute : interface.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); )~~~"); } } generator.append(R"~~~( }; )~~~"); for (auto& it : interface.enumerations) { if (!it.value.is_original_definition) continue; auto enum_generator = generator.fork(); enum_generator.set("enum.type.name", it.key); enum_generator.append(R"~~~( enum class @enum.type.name@ { )~~~"); for (auto& entry : it.value.translated_cpp_names) { enum_generator.set("enum.entry", entry.value); enum_generator.append(R"~~~( @enum.entry@, )~~~"); } enum_generator.append(R"~~~( }; inline DeprecatedString idl_enum_to_deprecated_string(@enum.type.name@ value) { switch(value) { )~~~"); for (auto& 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@"; )~~~"); } enum_generator.append(R"~~~( default: return ""; }; } )~~~"); } generator.append(R"~~~( } // namespace Web::Bindings )~~~"); } void generate_prototype_implementation(IDL::Interface const& interface, StringBuilder& builder) { SourceGenerator generator { builder }; generator.set("name", interface.name); generator.set("parent_name", interface.parent_name); generator.set("prototype_class", interface.prototype_class); generator.set("prototype_base_class", interface.prototype_base_class); generator.set("constructor_class", interface.constructor_class); generator.set("prototype_class:snakecase", interface.prototype_class.to_snakecase()); generator.set("fully_qualified_name", interface.fully_qualified_name); if (interface.pair_iterator_types.has_value()) { generator.set("iterator_name", DeprecatedString::formatted("{}Iterator", interface.name)); } generator.append(R"~~~( #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if __has_include() # include #endif )~~~"); for (auto& path : interface.required_imported_paths) generate_include_for(generator, path); emit_includes_for_all_imports(interface, generator, interface.pair_iterator_types.has_value()); generator.append(R"~~~( // FIXME: This is a total hack until we can figure out the namespace for a given type somehow. using namespace Web::Crypto; using namespace Web::CSS; using namespace Web::DOM; using namespace Web::DOMParsing; using namespace Web::Fetch; using namespace Web::FileAPI; using namespace Web::Geometry; using namespace Web::HTML; using namespace Web::IntersectionObserver; using namespace Web::NavigationTiming; using namespace Web::RequestIdleCallback; using namespace Web::ResizeObserver; using namespace Web::Selection; using namespace Web::Streams; using namespace Web::SVG; using namespace Web::UIEvents; using namespace Web::URL; using namespace Web::WebSockets; using namespace Web::XHR; using namespace Web::WebGL; using namespace Web::WebIDL; namespace Web::Bindings { @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(*realm.intrinsics().error_prototype()) )~~~"); } else if (!interface.parent_name.is_empty()) { generator.append(R"~~~( : Object(ensure_web_prototype<@prototype_base_class@>(realm, "@parent_name@")) )~~~"); } else { generator.append(R"~~~( : Object(*realm.intrinsics().object_prototype()) )~~~"); } // FIXME: Currently almost everything gets default_attributes but it should be configurable per attribute. // See the spec links for details generator.append(R"~~~( { } @prototype_class@::~@prototype_class@() { } void @prototype_class@::initialize(JS::Realm& realm) { [[maybe_unused]] auto& vm = this->vm(); [[maybe_unused]] u8 default_attributes = JS::Attribute::Enumerable | JS::Attribute::Configurable | JS::Attribute::Writable; )~~~"); if (interface.has_unscopable_member) { generator.append(R"~~~( auto unscopable_object = JS::Object::create(realm, nullptr); )~~~"); } // https://webidl.spec.whatwg.org/#es-attributes for (auto& attribute : interface.attributes) { 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", "nullptr"); else attribute_generator.set("attribute.setter_callback", attribute.setter_callback_name); if (attribute.extended_attributes.contains("Unscopable")) { attribute_generator.append(R"~~~( MUST(unscopable_object->create_data_property("@attribute.name@", JS::Value(true))); )~~~"); } attribute_generator.append(R"~~~( define_native_accessor(realm, "@attribute.name@", @attribute.getter_callback@, @attribute.setter_callback@, default_attributes); )~~~"); } // https://webidl.spec.whatwg.org/#es-constants 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); generate_wrap_statement(constant_generator, constant.value, constant.type, interface, DeprecatedString::formatted("auto constant_{}_value =", constant.name)); constant_generator.append(R"~~~( define_direct_property("@constant.name@", constant_@constant.name@_value, JS::Attribute::Enumerable); )~~~"); } // https://webidl.spec.whatwg.org/#es-operations for (auto const& overload_set : interface.overload_sets) { auto function_generator = generator.fork(); 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", DeprecatedString::number(get_shortest_function_length(overload_set.value))); // FIXME: What if only some of the overloads are Unscopable? if (any_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@", JS::Value(true))); )~~~"); } function_generator.append(R"~~~( define_native_function(realm, "@function.name@", @function.name:snakecase@, @function.length@, default_attributes); )~~~"); } if (interface.has_stringifier) { // FIXME: Do stringifiers need to be added to the unscopable list? auto stringifier_generator = generator.fork(); stringifier_generator.append(R"~~~( define_native_function(realm, "toString", 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()) { 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()) { 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()) { // 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); )~~~"); } if (interface.has_unscopable_member) { generator.append(R"~~~( define_direct_property(*vm.well_known_symbol_unscopables(), unscopable_object, JS::Attribute::Configurable); )~~~"); } generator.append(R"~~~( define_direct_property(*vm.well_known_symbol_to_string_tag(), JS::PrimitiveString::create(vm, "@name@"), JS::Attribute::Configurable); Object::initialize(realm); } )~~~"); if (!interface.attributes.is_empty() || !interface.functions.is_empty() || interface.has_stringifier) { generator.append(R"~~~( static JS::ThrowCompletionOr<@fully_qualified_name@*> impl_from(JS::VM& vm) { auto this_value = vm.this_value(); JS::Object* this_object = nullptr; if (this_value.is_nullish()) this_object = &vm.current_realm()->global_object(); else this_object = TRY(this_value.to_object(vm)); )~~~"); if (interface.name == "EventTarget") { generator.append(R"~~~( if (is(this_object)) { return static_cast(this_object); } if (is(this_object)) { return static_cast(this_object)->window().ptr(); } )~~~"); } generator.append(R"~~~( if (!is<@fully_qualified_name@>(this_object)) return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "@fully_qualified_name@"); return static_cast<@fully_qualified_name@*>(this_object); } )~~~"); } for (auto& attribute : interface.attributes) { auto attribute_generator = generator.fork(); 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()); } if (attribute.extended_attributes.contains("Reflect")) { auto attribute_name = attribute.extended_attributes.get("Reflect").value(); if (attribute_name.is_null()) attribute_name = attribute.name; attribute_name = make_input_acceptable_cpp(attribute_name); attribute_generator.set("attribute.reflect_name", attribute_name); } else { attribute_generator.set("attribute.reflect_name", attribute.name.to_snakecase()); } attribute_generator.append(R"~~~( JS_DEFINE_NATIVE_FUNCTION(@prototype_class@::@attribute.getter_callback@) { [[maybe_unused]] auto& realm = *vm.current_realm(); auto* impl = TRY(impl_from(vm)); )~~~"); if (attribute.extended_attributes.contains("Reflect")) { if (attribute.type->name() != "boolean") { attribute_generator.append(R"~~~( auto retval = impl->attribute(HTML::AttributeNames::@attribute.reflect_name@); )~~~"); } else { attribute_generator.append(R"~~~( auto retval = impl->has_attribute(HTML::AttributeNames::@attribute.reflect_name@); )~~~"); } } else { attribute_generator.append(R"~~~( auto retval = TRY(throw_dom_exception_if_needed(vm, [&] { return impl->@attribute.cpp_name@(); })); )~~~"); } generate_return_statement(generator, *attribute.type, interface); attribute_generator.append(R"~~~( } )~~~"); if (!attribute.readonly) { attribute_generator.append(R"~~~( JS_DEFINE_NATIVE_FUNCTION(@prototype_class@::@attribute.setter_callback@) { [[maybe_unused]] auto& realm = *vm.current_realm(); auto* impl = TRY(impl_from(vm)); auto value = vm.argument(0); )~~~"); generate_to_cpp(generator, attribute, "value", "", "cpp_value", interface, attribute.extended_attributes.contains("LegacyNullToEmptyString")); if (attribute.extended_attributes.contains("Reflect")) { if (attribute.type->name() != "boolean") { attribute_generator.append(R"~~~( MUST(impl->set_attribute(HTML::AttributeNames::@attribute.reflect_name@, cpp_value)); )~~~"); } else { attribute_generator.append(R"~~~( if (!cpp_value) impl->remove_attribute(HTML::AttributeNames::@attribute.reflect_name@); else MUST(impl->set_attribute(HTML::AttributeNames::@attribute.reflect_name@, DeprecatedString::empty())); )~~~"); } } else { attribute_generator.append(R"~~~( TRY(throw_dom_exception_if_needed(vm, [&] { return impl->set_@attribute.cpp_name@(cpp_value); })); )~~~"); } attribute_generator.append(R"~~~( return JS::js_undefined(); } )~~~"); } } // Implementation: Functions for (auto& function : interface.functions) generate_function(generator, function, StaticFunction::No, interface.prototype_class, interface.fully_qualified_name, interface); for (auto const& overload_set : interface.overload_sets) { if (overload_set.value.size() == 1) continue; generate_overload_arbiter(generator, overload_set, interface.prototype_class); } if (interface.has_stringifier) { auto stringifier_generator = generator.fork(); stringifier_generator.set("class_name", interface.prototype_class); if (interface.stringifier_attribute.has_value()) stringifier_generator.set("attribute.cpp_getter_name", interface.stringifier_attribute->to_snakecase()); stringifier_generator.append(R"~~~( JS_DEFINE_NATIVE_FUNCTION(@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 = impl->@attribute.cpp_getter_name@(); )~~~"); } else { stringifier_generator.append(R"~~~( auto retval = TRY(throw_dom_exception_if_needed(vm, [&] { return impl->to_deprecated_string(); })); )~~~"); } stringifier_generator.append(R"~~~( return JS::PrimitiveString::create(vm, move(retval)); } )~~~"); } if (interface.pair_iterator_types.has_value()) { auto iterator_generator = generator.fork(); iterator_generator.append(R"~~~( JS_DEFINE_NATIVE_FUNCTION(@prototype_class@::entries) { auto* impl = TRY(impl_from(vm)); return @iterator_name@::create(*impl, Object::PropertyKind::KeyAndValue).ptr(); } JS_DEFINE_NATIVE_FUNCTION(@prototype_class@::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.to_string_without_side_effects()); 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); generate_variable_statement(iterator_generator, "wrapped_value", interface.pair_iterator_types->get<1>(), "value", interface); iterator_generator.append(R"~~~( TRY(call(vm, callback.as_function(), vm.argument(1), wrapped_value, wrapped_key, this_value)); return {}; })); return JS::js_undefined(); } JS_DEFINE_NATIVE_FUNCTION(@prototype_class@::keys) { auto* impl = TRY(impl_from(vm)); return @iterator_name@::create(*impl, Object::PropertyKind::Key).ptr(); } JS_DEFINE_NATIVE_FUNCTION(@prototype_class@::values) { auto* impl = TRY(impl_from(vm)); return @iterator_name@::create(*impl, Object::PropertyKind::Value).ptr(); } )~~~"); } generator.append(R"~~~( } // namespace Web::Bindings )~~~"); } 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", DeprecatedString::formatted("{}IteratorPrototype", interface.name)); generator.append(R"~~~( #pragma once #include namespace Web::Bindings { class @prototype_class@ : public JS::Object { JS_OBJECT(@prototype_class@, JS::Object); public: explicit @prototype_class@(JS::Realm&); virtual void initialize(JS::Realm&) override; virtual ~@prototype_class@() override; private: JS_DECLARE_NATIVE_FUNCTION(next); }; } // namespace Web::Bindings )~~~"); } void generate_iterator_prototype_implementation(IDL::Interface const& interface, StringBuilder& builder) { VERIFY(interface.pair_iterator_types.has_value()); SourceGenerator generator { builder }; generator.set("name", DeprecatedString::formatted("{}Iterator", interface.name)); generator.set("prototype_class", DeprecatedString::formatted("{}IteratorPrototype", interface.name)); generator.set("fully_qualified_name", DeprecatedString::formatted("{}Iterator", interface.fully_qualified_name)); generator.set("possible_include_path", DeprecatedString::formatted("{}Iterator", interface.name.replace("::"sv, "/"sv, ReplaceMode::All))); generator.append(R"~~~( #include #include #include #include #include #include #include #include #include #if __has_include() # include #endif )~~~"); emit_includes_for_all_imports(interface, generator, true); generator.append(R"~~~( // FIXME: This is a total hack until we can figure out the namespace for a given type somehow. using namespace Web::CSS; using namespace Web::DOM; using namespace Web::DOMParsing; using namespace Web::Fetch; using namespace Web::FileAPI; using namespace Web::Geometry; using namespace Web::HTML; using namespace Web::IntersectionObserver; using namespace Web::NavigationTiming; using namespace Web::RequestIdleCallback; using namespace Web::ResizeObserver; using namespace Web::Selection; using namespace Web::XHR; using namespace Web::UIEvents; using namespace Web::URL; using namespace Web::WebGL; using namespace Web::WebIDL; namespace Web::Bindings { @prototype_class@::@prototype_class@(JS::Realm& realm) : Object(*realm.intrinsics().iterator_prototype()) { } @prototype_class@::~@prototype_class@() { } void @prototype_class@::initialize(JS::Realm& realm) { auto& vm = this->vm(); Object::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, "Iterator"), JS::Attribute::Configurable); } static JS::ThrowCompletionOr<@fully_qualified_name@*> 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, "@fully_qualified_name@"); return static_cast<@fully_qualified_name@*>(this_object); } JS_DEFINE_NATIVE_FUNCTION(@prototype_class@::next) { auto* impl = TRY(impl_from(vm)); return TRY(throw_dom_exception_if_needed(vm, [&] { return impl->next(); })); } } // namespace Web::Bindings )~~~"); } }