diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp index a6789140c74..5089940d6cb 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp @@ -2643,6 +2643,33 @@ static void generate_prototype_or_global_mixin_declarations(IDL::Interface const )~~~"); } + if (interface.set_entry_type.has_value()) { + auto setlike_generator = generator.fork(); + + setlike_generator.append(R"~~~( + JS_DECLARE_NATIVE_FUNCTION(get_size); + JS_DECLARE_NATIVE_FUNCTION(entries); + JS_DECLARE_NATIVE_FUNCTION(values); + JS_DECLARE_NATIVE_FUNCTION(for_each); + JS_DECLARE_NATIVE_FUNCTION(has); +)~~~"); + if (!interface.overload_sets.contains("add"sv) && !interface.is_set_readonly) { + setlike_generator.append(R"~~~( + JS_DECLARE_NATIVE_FUNCTION(add); +)~~~"); + } + if (!interface.overload_sets.contains("delete"sv) && !interface.is_set_readonly) { + setlike_generator.append(R"~~~( + JS_DECLARE_NATIVE_FUNCTION(delete_); +)~~~"); + } + if (!interface.overload_sets.contains("clear"sv) && !interface.is_set_readonly) { + setlike_generator.append(R"~~~( + JS_DECLARE_NATIVE_FUNCTION(clear); +)~~~"); + } + } + for (auto& attribute : interface.attributes) { auto attribute_generator = generator.fork(); attribute_generator.set("attribute.name:snakecase", attribute.name.to_snakecase()); @@ -3172,6 +3199,39 @@ void @class_name@::initialize(JS::Realm& realm) )~~~"); } + // https://webidl.spec.whatwg.org/#js-setlike + if (interface.set_entry_type.has_value()) { + + auto setlike_generator = generator.fork(); + + setlike_generator.append(R"~~~( + define_native_accessor(realm, vm.names.size, get_size, nullptr, JS::Attribute::Enumerable | JS::Attribute::Configurable); + define_native_function(realm, vm.names.entries, entries, 0, default_attributes); + // NOTE: Keys intentionally returns values for setlike + define_native_function(realm, vm.names.keys, values, 0, default_attributes); + define_native_function(realm, vm.names.values, values, 0, default_attributes); + define_direct_property(vm.well_known_symbol_iterator(), get_without_side_effects(vm.names.values), JS::Attribute::Configurable | JS::Attribute::Writable); + define_native_function(realm, vm.names.forEach, for_each, 1, default_attributes); + define_native_function(realm, vm.names.has, has, 1, default_attributes); +)~~~"); + + if (!interface.overload_sets.contains("add"sv) && !interface.is_set_readonly) { + setlike_generator.append(R"~~~( + define_native_function(realm, vm.names.add, add, 1, default_attributes); +)~~~"); + } + if (!interface.overload_sets.contains("delete"sv) && !interface.is_set_readonly) { + setlike_generator.append(R"~~~( + define_native_function(realm, vm.names.delete_, delete_, 1, default_attributes); +)~~~"); + } + if (!interface.overload_sets.contains("clear"sv) && !interface.is_set_readonly) { + setlike_generator.append(R"~~~( + define_native_function(realm, vm.names.clear, clear, 0, default_attributes); +)~~~"); + } + } + if (interface.has_unscopable_member) { generator.append(R"~~~( define_direct_property(vm.well_known_symbol_unscopables(), unscopable_object, JS::Attribute::Configurable); @@ -3771,6 +3831,149 @@ JS_DEFINE_NATIVE_FUNCTION(@class_name@::values) } )~~~"); } + + if (interface.set_entry_type.has_value()) { + auto setlike_generator = generator.fork(); + setlike_generator.set("value_type", interface.set_entry_type.value()->name()); + setlike_generator.append(R"~~~( +// https://webidl.spec.whatwg.org/#js-set-size +JS_DEFINE_NATIVE_FUNCTION(@class_name@::get_size) +{ + WebIDL::log_trace(vm, "@class_name@::size"); + auto* impl = TRY(impl_from(vm)); + + JS::NonnullGCPtr set = impl->set_entries(); + + return set->set_size(); +} + +// https://webidl.spec.whatwg.org/#js-set-entries +JS_DEFINE_NATIVE_FUNCTION(@class_name@::entries) +{ + WebIDL::log_trace(vm, "@class_name@::entries"); + auto& realm = *vm.current_realm(); + auto* impl = TRY(impl_from(vm)); + + JS::NonnullGCPtr set = impl->set_entries(); + + return TRY(throw_dom_exception_if_needed(vm, [&] { return JS::SetIterator::create(realm, *set, Object::PropertyKind::KeyAndValue); })); +} + +// https://webidl.spec.whatwg.org/#js-set-values +JS_DEFINE_NATIVE_FUNCTION(@class_name@::values) +{ + WebIDL::log_trace(vm, "@class_name@::values"); + auto& realm = *vm.current_realm(); + auto* impl = TRY(impl_from(vm)); + + JS::NonnullGCPtr set = impl->set_entries(); + + return TRY(throw_dom_exception_if_needed(vm, [&] { return JS::SetIterator::create(realm, *set, Object::PropertyKind::Value); })); +} + +// https://webidl.spec.whatwg.org/#js-set-forEach +JS_DEFINE_NATIVE_FUNCTION(@class_name@::for_each) +{ + WebIDL::log_trace(vm, "@class_name@::for_each"); + auto* impl = TRY(impl_from(vm)); + + JS::NonnullGCPtr set = impl->set_entries(); + + auto callback = vm.argument(0); + if (!callback.is_function()) + return vm.throw_completion(JS::ErrorType::NotAFunction, callback.to_string_without_side_effects()); + + for (auto& entry : *set) { + auto value = entry.key; + TRY(call(vm, callback.as_function(), vm.argument(1), value, value, impl)); + } + + return JS::js_undefined(); +} + +// https://webidl.spec.whatwg.org/#js-set-has +JS_DEFINE_NATIVE_FUNCTION(@class_name@::has) +{ + WebIDL::log_trace(vm, "@class_name@::has"); + auto* impl = TRY(impl_from(vm)); + + JS::NonnullGCPtr set = impl->set_entries(); + + auto value_arg = vm.argument(0); + if (!value_arg.is_object() && !is<@value_type@>(value_arg.as_object())) { + return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "@value_type@"); + } + + // FIXME: If value is -0, set value to +0. + // What? Which interfaces have a number as their set type? + + return set->set_has(value_arg); +} +)~~~"); + + if (!interface.overload_sets.contains("add"sv) && !interface.is_set_readonly) { + setlike_generator.append(R"~~~( +// https://webidl.spec.whatwg.org/#js-set-add +JS_DEFINE_NATIVE_FUNCTION(@class_name@::add) +{ + WebIDL::log_trace(vm, "@class_name@::add"); + auto* impl = TRY(impl_from(vm)); + + JS::NonnullGCPtr set = impl->set_entries(); + + auto value_arg = vm.argument(0); + if (!value_arg.is_object() && !is<@value_type@>(value_arg.as_object())) { + return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "@value_type@"); + } + + // FIXME: If value is -0, set value to +0. + // What? Which interfaces have a number as their set type? + + set->set_add(value_arg); + + return impl; +} +)~~~"); + } + if (!interface.overload_sets.contains("delete"sv) && !interface.is_set_readonly) { + setlike_generator.append(R"~~~( +// https://webidl.spec.whatwg.org/#js-set-delete +JS_DEFINE_NATIVE_FUNCTION(@class_name@::delete_) +{ + WebIDL::log_trace(vm, "@class_name@::delete_"); + auto* impl = TRY(impl_from(vm)); + + JS::NonnullGCPtr set = impl->set_entries(); + + auto value_arg = vm.argument(0); + if (!value_arg.is_object() && !is<@value_type@>(value_arg.as_object())) { + return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "@value_type@"); + } + + // FIXME: If value is -0, set value to +0. + // What? Which interfaces have a number as their set type? + + return set->set_remove(value_arg); +} +)~~~"); + } + if (!interface.overload_sets.contains("clear"sv) && !interface.is_set_readonly) { + setlike_generator.append(R"~~~( +// https://webidl.spec.whatwg.org/#js-set-clear +JS_DEFINE_NATIVE_FUNCTION(@class_name@::clear) +{ + WebIDL::log_trace(vm, "@class_name@::clear"); + auto* impl = TRY(impl_from(vm)); + + JS::NonnullGCPtr set = impl->set_entries(); + + set->set_clear(); + + return JS::js_undefined(); +} +)~~~"); + } + } } void generate_namespace_header(IDL::Interface const& interface, StringBuilder& builder) diff --git a/Userland/Libraries/LibIDL/IDLParser.cpp b/Userland/Libraries/LibIDL/IDLParser.cpp index c6f730b9405..41f8272dd43 100644 --- a/Userland/Libraries/LibIDL/IDLParser.cpp +++ b/Userland/Libraries/LibIDL/IDLParser.cpp @@ -292,8 +292,11 @@ void Parser::parse_attribute(HashMap& extended_attribute if (readonly) consume_whitespace(); + // FIXME: Should we parse 'readonly setlike' differently than this? if (lexer.consume_specific("attribute"sv)) consume_whitespace(); + else if (lexer.consume_specific("setlike"sv) && !inherit) + parse_setlike(interface, readonly); else report_parsing_error("expected 'attribute'"sv, filename, input, lexer.tell()); @@ -466,6 +469,28 @@ void Parser::parse_iterable(Interface& interface) interface.value_iterator_type = move(first_type); } + + if (interface.set_entry_type.has_value()) + report_parsing_error("Interfaces with an iterable declaration must not have a setlike declaration."sv, filename, input, lexer.tell()); + + assert_specific('>'); + assert_specific(';'); +} + +void Parser::parse_setlike(Interface& interface, bool is_readonly) +{ + if (interface.supports_indexed_properties()) + report_parsing_error("Interfaces with a setlike declaration must not supported indexed properties."sv, filename, input, lexer.tell()); + + if (interface.value_iterator_type.has_value() || interface.pair_iterator_types.has_value()) + report_parsing_error("Interfaces with a setlike declaration must not must not be iterable."sv, filename, input, lexer.tell()); + + assert_string("setlike"sv); + assert_specific('<'); + + interface.set_entry_type = parse_type(); + interface.is_set_readonly = is_readonly; + assert_specific('>'); assert_specific(';'); } @@ -625,6 +650,12 @@ void Parser::parse_interface(Interface& interface) continue; } + if (lexer.next_is("setlike")) { + bool is_readonly = false; + parse_setlike(interface, is_readonly); + continue; + } + if (lexer.next_is("inherit") || lexer.next_is("readonly") || lexer.next_is("attribute")) { parse_attribute(extended_attributes, interface); continue; diff --git a/Userland/Libraries/LibIDL/IDLParser.h b/Userland/Libraries/LibIDL/IDLParser.h index 330e1a42159..ec1cd2cb70c 100644 --- a/Userland/Libraries/LibIDL/IDLParser.h +++ b/Userland/Libraries/LibIDL/IDLParser.h @@ -58,6 +58,7 @@ private: void parse_deleter(HashMap& extended_attributes, Interface&); void parse_stringifier(HashMap& extended_attributes, Interface&); void parse_iterable(Interface&); + void parse_setlike(Interface&, bool is_readonly); Function parse_function(HashMap& extended_attributes, Interface&, IsStatic is_static = IsStatic::No, IsSpecialOperation is_special_operation = IsSpecialOperation::No); Vector parse_parameters(); NonnullRefPtr parse_type(); diff --git a/Userland/Libraries/LibIDL/Types.h b/Userland/Libraries/LibIDL/Types.h index ba83512426a..8f15c3ebd44 100644 --- a/Userland/Libraries/LibIDL/Types.h +++ b/Userland/Libraries/LibIDL/Types.h @@ -281,6 +281,8 @@ public: Optional> value_iterator_type; Optional, NonnullRefPtr>> pair_iterator_types; + Optional> set_entry_type; + bool is_set_readonly { false }; Optional named_property_getter; Optional named_property_setter;