LibWeb: Add support for passing sequences into IDL functions

This commit is contained in:
Luke Wilde 2022-01-25 19:23:08 +00:00 committed by Andreas Kling
parent 9773c3cabc
commit fb7ca09f04
Notes: sideshowbarker 2024-07-17 19:56:44 +09:00

View file

@ -81,6 +81,17 @@ static size_t get_function_length(FunctionType& function)
return length;
}
enum class SequenceStorageType {
Vector, // Used to safely store non-JS values
MarkedValueList, // Used to safely store JS::Value
MarkedVector, // Used to safely store anything that inherits JS::Cell, e.g. JS::Object
};
struct CppType {
String name;
SequenceStorageType sequence_storage_type;
};
struct Type : public RefCounted<Type> {
Type() = default;
@ -97,20 +108,6 @@ struct Type : public RefCounted<Type> {
bool is_string() const { return name.is_one_of("ByteString", "CSSOMString", "DOMString", "USVString"); }
};
struct ParameterizedType : public Type {
ParameterizedType() = default;
ParameterizedType(String name, bool nullable, NonnullRefPtrVector<Type> parameters)
: Type(move(name), nullable)
, parameters(move(parameters))
{
}
virtual ~ParameterizedType() override = default;
NonnullRefPtrVector<Type> parameters;
};
struct Parameter {
NonnullRefPtr<Type> type;
String name;
@ -166,6 +163,22 @@ struct Dictionary {
Vector<DictionaryMember> members;
};
struct ParameterizedType : public Type {
ParameterizedType() = default;
ParameterizedType(String name, bool nullable, NonnullRefPtrVector<Type> parameters)
: Type(move(name), nullable)
, parameters(move(parameters))
{
}
virtual ~ParameterizedType() override = default;
NonnullRefPtrVector<Type> parameters;
void generate_sequence_from_iterable(SourceGenerator& generator, String const& cpp_name, String const& iterable_cpp_name, String const& iterator_method_cpp_name, HashMap<String, Dictionary> const& dictionaries, size_t recursion_depth) const;
};
struct Interface {
String name;
String parent_name;
@ -752,6 +765,97 @@ static NonnullOwnPtr<Interface> parse_interface(StringView filename, StringView
return interface;
}
static bool is_wrappable_type(Type const& type)
{
if (type.name == "Node")
return true;
if (type.name == "Document")
return true;
if (type.name == "Text")
return true;
if (type.name == "DocumentType")
return true;
if (type.name.ends_with("Element"))
return true;
if (type.name.ends_with("Event"))
return true;
if (type.name == "ImageData")
return true;
if (type.name == "Window")
return true;
if (type.name == "Range")
return true;
if (type.name == "Selection")
return true;
if (type.name == "Attribute")
return true;
if (type.name == "NamedNodeMap")
return true;
if (type.name == "TextMetrics")
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::MarkedValueList:
return "JS::MarkedValueList"sv;
case SequenceStorageType::MarkedVector:
return "JS::MarkedVector"sv;
default:
VERIFY_NOT_REACHED();
}
}
static CppType idl_type_name_to_cpp_type(Type const& type)
{
if (is_wrappable_type(type)) {
if (type.nullable)
return { .name = String::formatted("RefPtr<{}>", type.name), .sequence_storage_type = SequenceStorageType::Vector };
return { .name = String::formatted("NonnullRefPtr<{}>", type.name), .sequence_storage_type = SequenceStorageType::Vector };
}
if (type.is_string())
return { .name = "String", .sequence_storage_type = SequenceStorageType::Vector };
if (type.name == "double" && !type.nullable)
return { .name = "double", .sequence_storage_type = SequenceStorageType::Vector };
if (type.name == "boolean" && !type.nullable)
return { .name = "bool", .sequence_storage_type = SequenceStorageType::Vector };
if (type.name == "unsigned long" && !type.nullable)
return { .name = "u32", .sequence_storage_type = SequenceStorageType::Vector };
if (type.name == "unsigned short" && !type.nullable)
return { .name = "u16", .sequence_storage_type = SequenceStorageType::Vector };
if (type.name == "long" && !type.nullable)
return { .name = "i32", .sequence_storage_type = SequenceStorageType::Vector };
if (type.name == "any")
return { .name = "JS::Value", .sequence_storage_type = SequenceStorageType::MarkedValueList };
if (type.name == "sequence") {
auto& parameterized_type = verify_cast<ParameterizedType>(type);
auto& sequence_type = parameterized_type.parameters.first();
auto sequence_cpp_type = idl_type_name_to_cpp_type(sequence_type);
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::MarkedValueList || sequence_cpp_type.sequence_storage_type == SequenceStorageType::MarkedVector)
return { .name = storage_type_name, .sequence_storage_type = SequenceStorageType::Vector };
return { .name = String::formatted("{}<{}>", storage_type_name, sequence_cpp_type.name), .sequence_storage_type = SequenceStorageType::Vector };
}
dbgln("Unimplemented type for idl_type_name_to_cpp_type: {}{}", type.name, type.nullable ? "?" : "");
TODO();
}
}
static void generate_constructor_header(IDL::Interface const&);
@ -914,42 +1018,12 @@ static bool should_emit_wrapper_factory(IDL::Interface const& interface)
return true;
}
static bool is_wrappable_type(IDL::Type const& type)
{
if (type.name == "Node")
return true;
if (type.name == "Document")
return true;
if (type.name == "Text")
return true;
if (type.name == "DocumentType")
return true;
if (type.name.ends_with("Element"))
return true;
if (type.name.ends_with("Event"))
return true;
if (type.name == "ImageData")
return true;
if (type.name == "Window")
return true;
if (type.name == "Range")
return true;
if (type.name == "Selection")
return true;
if (type.name == "Attribute")
return true;
if (type.name == "NamedNodeMap")
return true;
if (type.name == "TextMetrics")
return true;
return false;
}
template<typename ParameterType>
static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter, String const& js_name, String const& js_suffix, String const& cpp_name, HashMap<String, IDL::Dictionary> const& dictionaries, bool legacy_null_to_empty_string = false, bool optional = false, Optional<String> optional_default_value = {}, bool variadic = false)
static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter, String const& js_name, String const& js_suffix, String const& cpp_name, HashMap<String, IDL::Dictionary> const& dictionaries, bool legacy_null_to_empty_string = false, bool optional = false, Optional<String> optional_default_value = {}, bool variadic = false, size_t recursion_depth = 0)
{
auto scoped_generator = generator.fork();
scoped_generator.set("cpp_name", make_input_acceptable_cpp(cpp_name));
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");
@ -1028,7 +1102,7 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter
auto @cpp_name@ = adopt_ref(*new EventListener(JS::make_handle(&@js_name@@js_suffix@.as_function())));
)~~~");
}
} else if (is_wrappable_type(*parameter.type)) {
} else if (IDL::is_wrappable_type(*parameter.type)) {
if (!parameter.type->nullable) {
scoped_generator.append(R"~~~(
auto @cpp_name@_object = TRY(@js_name@@js_suffix@.to_object(global_object));
@ -1222,6 +1296,29 @@ static void generate_to_cpp(SourceGenerator& generator, ParameterType& parameter
VERIFY(dictionaries.contains(current_dictionary->parent_name));
current_dictionary = &dictionaries.find(current_dictionary->parent_name)->value;
}
} else if (parameter.type->name == "sequence") {
// https://webidl.spec.whatwg.org/#es-sequence
auto sequence_generator = scoped_generator.fork();
auto& parameterized_type = verify_cast<IDL::ParameterizedType>(*parameter.type);
sequence_generator.set("recursion_depth", String::number(recursion_depth));
// An ECMAScript value V is converted to an IDL sequence<T> 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.
sequence_generator.append(R"~~~(
if (!@js_name@@js_suffix@.is_object())
return vm.throw_completion<JS::TypeError>(global_object, JS::ErrorType::NotAnObject, @js_name@@js_suffix@.to_string_without_side_effects());
auto* iterator_method@recursion_depth@ = TRY(@js_name@@js_suffix@.get_method(global_object, *vm.well_known_symbol_iterator()));
if (!iterator_method@recursion_depth@)
return vm.throw_completion<JS::TypeError>(global_object, JS::ErrorType::NotIterable, @js_name@@js_suffix@.to_string_without_side_effects());
)~~~");
parameterized_type.generate_sequence_from_iterable(sequence_generator, acceptable_cpp_name, String::formatted("{}{}", js_name, js_suffix), String::formatted("iterator_method{}", recursion_depth), dictionaries, recursion_depth + 1);
} else {
dbgln("Unimplemented JS-to-C++ conversion: {}", parameter.type->name);
VERIFY_NOT_REACHED();
@ -1275,6 +1372,61 @@ static void generate_arguments(SourceGenerator& generator, Vector<IDL::Parameter
arguments_builder.join(", ", parameter_names);
}
// https://webidl.spec.whatwg.org/#create-sequence-from-iterable
void IDL::ParameterizedType::generate_sequence_from_iterable(SourceGenerator& generator, String const& cpp_name, String const& iterable_cpp_name, String const& iterator_method_cpp_name, HashMap<String, IDL::Dictionary> const& dictionaries, 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", String::number(recursion_depth));
auto sequence_cpp_type = idl_type_name_to_cpp_type(parameters.first());
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<T> 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<T> 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(global_object, @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@ { global_object.heap() };
)~~~");
}
sequence_generator.append(R"~~~(
for (;;) {
auto* next@recursion_depth@ = TRY(JS::iterator_step(global_object, iterator@recursion_depth@));
if (!next@recursion_depth@)
break;
auto next_item@recursion_depth@ = TRY(JS::iterator_value(global_object, *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", String::number(recursion_depth), String::formatted("sequence_item{}", recursion_depth), dictionaries, false, false, {}, false, recursion_depth);
sequence_generator.append(R"~~~(
@cpp_name@.append(sequence_item@recursion_depth@);
}
)~~~");
}
enum class WrappingReference {
No,
Yes,
@ -2500,6 +2652,7 @@ void generate_constructor_implementation(IDL::Interface const& interface)
generator.append(R"~~~(
#include <LibJS/Heap/Heap.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/IteratorOperations.h>
#include <LibWeb/Bindings/@constructor_class@.h>
#include <LibWeb/Bindings/@prototype_class@.h>
#include <LibWeb/Bindings/@wrapper_class@.h>
@ -2774,6 +2927,7 @@ void generate_prototype_implementation(IDL::Interface const& interface)
#include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/FunctionObject.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/IteratorOperations.h>
#include <LibJS/Runtime/TypedArray.h>
#include <LibWeb/Bindings/@prototype_class@.h>
#include <LibWeb/Bindings/@wrapper_class@.h>