LibIDL+LibWeb: Add support for WebIDL setlike declarations on interfaces

This commit is contained in:
Andrew Kaster 2024-05-21 20:17:16 -06:00 committed by Andreas Kling
commit 7077e4d002
Notes: sideshowbarker 2024-07-17 16:23:55 +09:00
4 changed files with 237 additions and 0 deletions

View file

@ -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<JS::Set> 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<JS::Set> 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<JS::Set> 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<JS::Set> set = impl->set_entries();
auto callback = vm.argument(0);
if (!callback.is_function())
return vm.throw_completion<JS::TypeError>(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<JS::Set> 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::TypeError>(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<JS::Set> 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::TypeError>(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<JS::Set> 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::TypeError>(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<JS::Set> set = impl->set_entries();
set->set_clear();
return JS::js_undefined();
}
)~~~");
}
}
}
void generate_namespace_header(IDL::Interface const& interface, StringBuilder& builder)

View file

@ -292,8 +292,11 @@ void Parser::parse_attribute(HashMap<ByteString, ByteString>& extended_attribute
if (readonly)
consume_whitespace();
// FIXME: Should we parse 'readonly setlike<T>' 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;

View file

@ -58,6 +58,7 @@ private:
void parse_deleter(HashMap<ByteString, ByteString>& extended_attributes, Interface&);
void parse_stringifier(HashMap<ByteString, ByteString>& extended_attributes, Interface&);
void parse_iterable(Interface&);
void parse_setlike(Interface&, bool is_readonly);
Function parse_function(HashMap<ByteString, ByteString>& extended_attributes, Interface&, IsStatic is_static = IsStatic::No, IsSpecialOperation is_special_operation = IsSpecialOperation::No);
Vector<Parameter> parse_parameters();
NonnullRefPtr<Type const> parse_type();

View file

@ -281,6 +281,8 @@ public:
Optional<NonnullRefPtr<Type const>> value_iterator_type;
Optional<Tuple<NonnullRefPtr<Type const>, NonnullRefPtr<Type const>>> pair_iterator_types;
Optional<NonnullRefPtr<Type const>> set_entry_type;
bool is_set_readonly { false };
Optional<Function> named_property_getter;
Optional<Function> named_property_setter;