3490 lines
133 KiB
C++
3490 lines
133 KiB
C++
/*
|
||
* Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
|
||
* Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
|
||
* Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
|
||
*
|
||
* SPDX-License-Identifier: BSD-2-Clause
|
||
*/
|
||
|
||
#include <AK/ByteBuffer.h>
|
||
#include <AK/Debug.h>
|
||
#include <AK/GenericLexer.h>
|
||
#include <AK/HashMap.h>
|
||
#include <AK/LexicalPath.h>
|
||
#include <AK/NonnullOwnPtrVector.h>
|
||
#include <AK/OwnPtr.h>
|
||
#include <AK/QuickSort.h>
|
||
#include <AK/SourceGenerator.h>
|
||
#include <AK/StringBuilder.h>
|
||
#include <AK/Tuple.h>
|
||
#include <LibCore/ArgsParser.h>
|
||
#include <LibCore/File.h>
|
||
#include <ctype.h>
|
||
|
||
static String make_input_acceptable_cpp(String const& input)
|
||
{
|
||
if (input.is_one_of("class", "template", "for", "default", "char", "namespace", "delete")) {
|
||
StringBuilder builder;
|
||
builder.append(input);
|
||
builder.append('_');
|
||
return builder.to_string();
|
||
}
|
||
|
||
return input.replace("-", "_");
|
||
}
|
||
|
||
[[noreturn]] static void report_parsing_error(StringView message, StringView filename, StringView input, size_t offset)
|
||
{
|
||
// FIXME: Spaghetti code ahead.
|
||
|
||
size_t lineno = 1;
|
||
size_t colno = 1;
|
||
size_t start_line = 0;
|
||
size_t line_length = 0;
|
||
for (size_t index = 0; index < input.length(); ++index) {
|
||
if (offset == index)
|
||
colno = index - start_line + 1;
|
||
|
||
if (input[index] == '\n') {
|
||
if (index >= offset)
|
||
break;
|
||
|
||
start_line = index + 1;
|
||
line_length = 0;
|
||
++lineno;
|
||
} else {
|
||
++line_length;
|
||
}
|
||
}
|
||
|
||
StringBuilder error_message;
|
||
error_message.appendff("{}\n", input.substring_view(start_line, line_length));
|
||
for (size_t i = 0; i < colno - 1; ++i)
|
||
error_message.append(' ');
|
||
error_message.append("\033[1;31m^\n");
|
||
error_message.appendff("{}:{}: error: {}\033[0m\n", filename, lineno, message);
|
||
|
||
warnln("{}", error_message.string_view());
|
||
exit(EXIT_FAILURE);
|
||
}
|
||
|
||
namespace IDL {
|
||
|
||
template<typename FunctionType>
|
||
static size_t get_function_length(FunctionType& function)
|
||
{
|
||
size_t length = 0;
|
||
for (auto& parameter : function.parameters) {
|
||
if (!parameter.optional)
|
||
length++;
|
||
}
|
||
return length;
|
||
}
|
||
|
||
struct Type {
|
||
String name;
|
||
bool nullable { false };
|
||
bool is_string() const { return name.is_one_of("ByteString", "CSSOMString", "DOMString", "USVString"); }
|
||
};
|
||
|
||
struct Parameter {
|
||
Type type;
|
||
String name;
|
||
bool optional { false };
|
||
Optional<String> optional_default_value;
|
||
HashMap<String, String> extended_attributes;
|
||
};
|
||
|
||
struct Function {
|
||
Type return_type;
|
||
String name;
|
||
Vector<Parameter> parameters;
|
||
HashMap<String, String> extended_attributes;
|
||
|
||
size_t length() const { return get_function_length(*this); }
|
||
};
|
||
|
||
struct Constructor {
|
||
String name;
|
||
Vector<Parameter> parameters;
|
||
|
||
size_t length() const { return get_function_length(*this); }
|
||
};
|
||
|
||
struct Constant {
|
||
Type type;
|
||
String name;
|
||
String value;
|
||
};
|
||
|
||
struct Attribute {
|
||
bool readonly { false };
|
||
Type type;
|
||
String name;
|
||
HashMap<String, String> extended_attributes;
|
||
|
||
// Added for convenience after parsing
|
||
String getter_callback_name;
|
||
String setter_callback_name;
|
||
};
|
||
|
||
struct DictionaryMember {
|
||
bool required { false };
|
||
Type type;
|
||
String name;
|
||
HashMap<String, String> extended_attributes;
|
||
Optional<String> default_value;
|
||
};
|
||
|
||
struct Dictionary {
|
||
String parent_name;
|
||
Vector<DictionaryMember> members;
|
||
};
|
||
|
||
struct Interface {
|
||
String name;
|
||
String parent_name;
|
||
|
||
HashMap<String, String> extended_attributes;
|
||
|
||
Vector<Attribute> attributes;
|
||
Vector<Constant> constants;
|
||
Vector<Constructor> constructors;
|
||
Vector<Function> functions;
|
||
Vector<Function> static_functions;
|
||
bool has_stringifier { false };
|
||
Optional<String> stringifier_attribute;
|
||
bool has_unscopable_member { false };
|
||
|
||
Optional<Type> value_iterator_type;
|
||
Optional<Tuple<Type, Type>> pair_iterator_types;
|
||
|
||
Optional<Function> named_property_getter;
|
||
Optional<Function> named_property_setter;
|
||
|
||
Optional<Function> indexed_property_getter;
|
||
Optional<Function> indexed_property_setter;
|
||
|
||
Optional<Function> named_property_deleter;
|
||
|
||
HashMap<String, Dictionary> dictionaries;
|
||
|
||
// Added for convenience after parsing
|
||
String wrapper_class;
|
||
String wrapper_base_class;
|
||
String fully_qualified_name;
|
||
String constructor_class;
|
||
String prototype_class;
|
||
String prototype_base_class;
|
||
|
||
// https://heycam.github.io/webidl/#dfn-support-indexed-properties
|
||
bool supports_indexed_properties() const { return indexed_property_getter.has_value(); }
|
||
|
||
// https://heycam.github.io/webidl/#dfn-support-named-properties
|
||
bool supports_named_properties() const { return named_property_getter.has_value(); }
|
||
|
||
// https://heycam.github.io/webidl/#dfn-legacy-platform-object
|
||
bool is_legacy_platform_object() const { return !extended_attributes.contains("Global") && (supports_indexed_properties() || supports_named_properties()); }
|
||
};
|
||
|
||
static NonnullOwnPtr<Interface> parse_interface(StringView filename, StringView input, StringView import_base_path)
|
||
{
|
||
auto interface = make<Interface>();
|
||
|
||
GenericLexer lexer(input);
|
||
|
||
auto assert_specific = [&](char ch) {
|
||
if (!lexer.consume_specific(ch))
|
||
report_parsing_error(String::formatted("expected '{}'", ch), filename, input, lexer.tell());
|
||
};
|
||
|
||
auto consume_whitespace = [&] {
|
||
bool consumed = true;
|
||
while (consumed) {
|
||
consumed = lexer.consume_while([](char ch) { return isspace(ch); }).length() > 0;
|
||
|
||
if (lexer.consume_specific("//")) {
|
||
lexer.consume_until('\n');
|
||
consumed = true;
|
||
}
|
||
}
|
||
};
|
||
|
||
auto assert_string = [&](StringView const& expected) {
|
||
if (!lexer.consume_specific(expected))
|
||
report_parsing_error(String::formatted("expected '{}'", expected), filename, input, lexer.tell());
|
||
};
|
||
|
||
auto parse_extended_attributes = [&] {
|
||
HashMap<String, String> extended_attributes;
|
||
for (;;) {
|
||
consume_whitespace();
|
||
if (lexer.consume_specific(']'))
|
||
break;
|
||
auto name = lexer.consume_until([](auto ch) { return ch == ']' || ch == '=' || ch == ','; });
|
||
if (lexer.consume_specific('=')) {
|
||
auto value = lexer.consume_until([](auto ch) { return ch == ']' || ch == ','; });
|
||
extended_attributes.set(name, value);
|
||
} else {
|
||
extended_attributes.set(name, {});
|
||
}
|
||
lexer.consume_specific(',');
|
||
}
|
||
consume_whitespace();
|
||
return extended_attributes;
|
||
};
|
||
|
||
auto resolve_import = [&](auto path) {
|
||
auto include_path = LexicalPath::join(import_base_path, path).string();
|
||
if (!Core::File::exists(include_path))
|
||
report_parsing_error(String::formatted("{}: No such file or directory", include_path), filename, input, lexer.tell());
|
||
|
||
auto file_or_error = Core::File::open(include_path, Core::OpenMode::ReadOnly);
|
||
if (file_or_error.is_error())
|
||
report_parsing_error(String::formatted("Failed to open {}: {}", include_path, file_or_error.error()), filename, input, lexer.tell());
|
||
|
||
auto data = file_or_error.value()->read_all();
|
||
return IDL::parse_interface(include_path, data, import_base_path);
|
||
};
|
||
|
||
NonnullOwnPtrVector<Interface> imports;
|
||
while (lexer.consume_specific("#import")) {
|
||
consume_whitespace();
|
||
assert_specific('<');
|
||
imports.append(resolve_import(lexer.consume_until('>')));
|
||
consume_whitespace();
|
||
}
|
||
|
||
if (lexer.consume_specific('['))
|
||
interface->extended_attributes = parse_extended_attributes();
|
||
|
||
auto parse_type = [&] {
|
||
bool unsigned_ = lexer.consume_specific("unsigned");
|
||
if (unsigned_)
|
||
consume_whitespace();
|
||
auto name = lexer.consume_until([](auto ch) { return !isalnum(ch) && ch != '_'; });
|
||
auto nullable = lexer.consume_specific('?');
|
||
StringBuilder builder;
|
||
if (unsigned_)
|
||
builder.append("unsigned ");
|
||
builder.append(name);
|
||
return Type { builder.to_string(), nullable };
|
||
};
|
||
|
||
auto parse_attribute = [&](HashMap<String, String>& extended_attributes) {
|
||
bool readonly = lexer.consume_specific("readonly");
|
||
if (readonly)
|
||
consume_whitespace();
|
||
|
||
if (lexer.consume_specific("attribute"))
|
||
consume_whitespace();
|
||
|
||
auto type = parse_type();
|
||
consume_whitespace();
|
||
auto name = lexer.consume_until([](auto ch) { return isspace(ch) || ch == ';'; });
|
||
consume_whitespace();
|
||
|
||
assert_specific(';');
|
||
Attribute attribute;
|
||
attribute.readonly = readonly;
|
||
attribute.type = type;
|
||
attribute.name = name;
|
||
attribute.getter_callback_name = String::formatted("{}_getter", attribute.name.to_snakecase());
|
||
attribute.setter_callback_name = String::formatted("{}_setter", attribute.name.to_snakecase());
|
||
attribute.extended_attributes = move(extended_attributes);
|
||
interface->attributes.append(move(attribute));
|
||
};
|
||
|
||
auto parse_constant = [&] {
|
||
lexer.consume_specific("const");
|
||
consume_whitespace();
|
||
|
||
Constant constant;
|
||
constant.type = parse_type();
|
||
consume_whitespace();
|
||
constant.name = lexer.consume_until([](auto ch) { return isspace(ch) || ch == '='; });
|
||
consume_whitespace();
|
||
lexer.consume_specific('=');
|
||
consume_whitespace();
|
||
constant.value = lexer.consume_while([](auto ch) { return !isspace(ch) && ch != ';'; });
|
||
consume_whitespace();
|
||
assert_specific(';');
|
||
|
||
interface->constants.append(move(constant));
|
||
};
|
||
|
||
auto parse_parameters = [&] {
|
||
consume_whitespace();
|
||
Vector<Parameter> parameters;
|
||
for (;;) {
|
||
if (lexer.next_is(')'))
|
||
break;
|
||
HashMap<String, String> extended_attributes;
|
||
if (lexer.consume_specific('['))
|
||
extended_attributes = parse_extended_attributes();
|
||
bool optional = lexer.consume_specific("optional");
|
||
if (optional)
|
||
consume_whitespace();
|
||
auto type = parse_type();
|
||
consume_whitespace();
|
||
auto name = lexer.consume_until([](auto ch) { return isspace(ch) || ch == ',' || ch == ')' || ch == '='; });
|
||
Parameter parameter = { move(type), move(name), optional, {}, extended_attributes };
|
||
consume_whitespace();
|
||
if (lexer.next_is(')')) {
|
||
parameters.append(parameter);
|
||
break;
|
||
}
|
||
if (lexer.next_is('=') && optional) {
|
||
assert_specific('=');
|
||
consume_whitespace();
|
||
auto default_value = lexer.consume_until([](auto ch) { return isspace(ch) || ch == ',' || ch == ')'; });
|
||
parameter.optional_default_value = default_value;
|
||
}
|
||
parameters.append(parameter);
|
||
if (lexer.next_is(')'))
|
||
break;
|
||
assert_specific(',');
|
||
consume_whitespace();
|
||
}
|
||
return parameters;
|
||
};
|
||
|
||
// https://heycam.github.io/webidl/#dfn-special-operation
|
||
// A special operation is a getter, setter or deleter.
|
||
enum class IsSpecialOperation {
|
||
No,
|
||
Yes,
|
||
};
|
||
|
||
auto parse_function = [&](HashMap<String, String>& extended_attributes, IsSpecialOperation is_special_operation = IsSpecialOperation::No) {
|
||
bool static_ = false;
|
||
if (lexer.consume_specific("static")) {
|
||
static_ = true;
|
||
consume_whitespace();
|
||
}
|
||
|
||
auto return_type = parse_type();
|
||
consume_whitespace();
|
||
auto name = lexer.consume_until([](auto ch) { return isspace(ch) || ch == '('; });
|
||
consume_whitespace();
|
||
assert_specific('(');
|
||
auto parameters = parse_parameters();
|
||
assert_specific(')');
|
||
consume_whitespace();
|
||
assert_specific(';');
|
||
|
||
Function function { move(return_type), name, move(parameters), move(extended_attributes) };
|
||
|
||
// "Defining a special operation with an identifier is equivalent to separating the special operation out into its own declaration without an identifier."
|
||
if (is_special_operation == IsSpecialOperation::No || (is_special_operation == IsSpecialOperation::Yes && !name.is_empty())) {
|
||
if (!static_)
|
||
interface->functions.append(function);
|
||
else
|
||
interface->static_functions.append(function);
|
||
}
|
||
|
||
return function;
|
||
};
|
||
|
||
auto parse_constructor = [&] {
|
||
assert_string("constructor");
|
||
consume_whitespace();
|
||
assert_specific('(');
|
||
auto parameters = parse_parameters();
|
||
assert_specific(')');
|
||
consume_whitespace();
|
||
assert_specific(';');
|
||
|
||
interface->constructors.append(Constructor { interface->name, move(parameters) });
|
||
};
|
||
|
||
auto parse_stringifier = [&](HashMap<String, String>& extended_attributes) {
|
||
assert_string("stringifier");
|
||
consume_whitespace();
|
||
interface->has_stringifier = true;
|
||
if (lexer.next_is("readonly") || lexer.next_is("attribute")) {
|
||
parse_attribute(extended_attributes);
|
||
interface->stringifier_attribute = interface->attributes.last().name;
|
||
} else {
|
||
assert_specific(';');
|
||
}
|
||
};
|
||
|
||
auto parse_iterable = [&]() {
|
||
assert_string("iterable");
|
||
assert_specific('<');
|
||
auto first_type = parse_type();
|
||
if (lexer.next_is(',')) {
|
||
if (interface->supports_indexed_properties())
|
||
report_parsing_error("Interfaces with a pair iterator must not supported indexed properties.", filename, input, lexer.tell());
|
||
|
||
assert_specific(',');
|
||
consume_whitespace();
|
||
auto second_type = parse_type();
|
||
interface->pair_iterator_types = Tuple { move(first_type), move(second_type) };
|
||
} else {
|
||
if (!interface->supports_indexed_properties())
|
||
report_parsing_error("Interfaces with a value iterator must supported indexed properties.", filename, input, lexer.tell());
|
||
|
||
interface->value_iterator_type = move(first_type);
|
||
}
|
||
assert_specific('>');
|
||
assert_specific(';');
|
||
};
|
||
|
||
auto parse_getter = [&](HashMap<String, String>& extended_attributes) {
|
||
assert_string("getter");
|
||
consume_whitespace();
|
||
auto function = parse_function(extended_attributes, IsSpecialOperation::Yes);
|
||
|
||
if (function.parameters.size() != 1)
|
||
report_parsing_error(String::formatted("Named/indexed property getters must have only 1 parameter, got {} parameters.", function.parameters.size()), filename, input, lexer.tell());
|
||
|
||
auto& identifier = function.parameters.first();
|
||
|
||
if (identifier.type.nullable)
|
||
report_parsing_error("identifier's type must not be nullable.", filename, input, lexer.tell());
|
||
|
||
if (identifier.optional)
|
||
report_parsing_error("identifier must not be optional.", filename, input, lexer.tell());
|
||
|
||
// FIXME: Disallow variadic functions once they're supported.
|
||
|
||
if (identifier.type.name == "DOMString") {
|
||
if (interface->named_property_getter.has_value())
|
||
report_parsing_error("An interface can only have one named property getter.", filename, input, lexer.tell());
|
||
|
||
interface->named_property_getter = move(function);
|
||
} else if (identifier.type.name == "unsigned long") {
|
||
if (interface->indexed_property_getter.has_value())
|
||
report_parsing_error("An interface can only have one indexed property getter.", filename, input, lexer.tell());
|
||
|
||
interface->indexed_property_getter = move(function);
|
||
} else {
|
||
report_parsing_error(String::formatted("Named/indexed property getter's identifier's type must be either 'DOMString' or 'unsigned long', got '{}'.", identifier.type.name), filename, input, lexer.tell());
|
||
}
|
||
};
|
||
|
||
auto parse_setter = [&](HashMap<String, String>& extended_attributes) {
|
||
assert_string("setter");
|
||
consume_whitespace();
|
||
auto function = parse_function(extended_attributes, IsSpecialOperation::Yes);
|
||
|
||
if (function.parameters.size() != 2)
|
||
report_parsing_error(String::formatted("Named/indexed property setters must have only 2 parameters, got {} parameter(s).", function.parameters.size()), filename, input, lexer.tell());
|
||
|
||
auto& identifier = function.parameters.first();
|
||
|
||
if (identifier.type.nullable)
|
||
report_parsing_error("identifier's type must not be nullable.", filename, input, lexer.tell());
|
||
|
||
if (identifier.optional)
|
||
report_parsing_error("identifier must not be optional.", filename, input, lexer.tell());
|
||
|
||
// FIXME: Disallow variadic functions once they're supported.
|
||
|
||
if (identifier.type.name == "DOMString") {
|
||
if (interface->named_property_setter.has_value())
|
||
report_parsing_error("An interface can only have one named property setter.", filename, input, lexer.tell());
|
||
|
||
if (!interface->named_property_getter.has_value())
|
||
report_parsing_error("A named property setter must be accompanied by a named property getter.", filename, input, lexer.tell());
|
||
|
||
interface->named_property_setter = move(function);
|
||
} else if (identifier.type.name == "unsigned long") {
|
||
if (interface->indexed_property_setter.has_value())
|
||
report_parsing_error("An interface can only have one indexed property setter.", filename, input, lexer.tell());
|
||
|
||
if (!interface->indexed_property_getter.has_value())
|
||
report_parsing_error("An indexed property setter must be accompanied by an indexed property getter.", filename, input, lexer.tell());
|
||
|
||
interface->indexed_property_setter = move(function);
|
||
} else {
|
||
report_parsing_error(String::formatted("Named/indexed property setter's identifier's type must be either 'DOMString' or 'unsigned long', got '{}'.", identifier.type.name), filename, input, lexer.tell());
|
||
}
|
||
};
|
||
|
||
auto parse_deleter = [&](HashMap<String, String>& extended_attributes) {
|
||
assert_string("deleter");
|
||
consume_whitespace();
|
||
auto function = parse_function(extended_attributes, IsSpecialOperation::Yes);
|
||
|
||
if (function.parameters.size() != 1)
|
||
report_parsing_error(String::formatted("Named property deleter must have only 1 parameter, got {} parameters.", function.parameters.size()), filename, input, lexer.tell());
|
||
|
||
auto& identifier = function.parameters.first();
|
||
|
||
if (identifier.type.nullable)
|
||
report_parsing_error("identifier's type must not be nullable.", filename, input, lexer.tell());
|
||
|
||
if (identifier.optional)
|
||
report_parsing_error("identifier must not be optional.", filename, input, lexer.tell());
|
||
|
||
// FIXME: Disallow variadic functions once they're supported.
|
||
|
||
if (identifier.type.name == "DOMString") {
|
||
if (interface->named_property_deleter.has_value())
|
||
report_parsing_error("An interface can only have one named property deleter.", filename, input, lexer.tell());
|
||
|
||
if (!interface->named_property_getter.has_value())
|
||
report_parsing_error("A named property deleter must be accompanied by a named property getter.", filename, input, lexer.tell());
|
||
|
||
interface->named_property_deleter = move(function);
|
||
} else {
|
||
report_parsing_error(String::formatted("Named property deleter's identifier's type must be 'DOMString', got '{}'.", identifier.type.name), filename, input, lexer.tell());
|
||
}
|
||
};
|
||
|
||
if (lexer.consume_specific("interface")) {
|
||
consume_whitespace();
|
||
interface->name = lexer.consume_until([](auto ch) { return isspace(ch); });
|
||
consume_whitespace();
|
||
if (lexer.consume_specific(':')) {
|
||
consume_whitespace();
|
||
interface->parent_name = lexer.consume_until([](auto ch) { return isspace(ch); });
|
||
consume_whitespace();
|
||
}
|
||
assert_specific('{');
|
||
|
||
for (;;) {
|
||
HashMap<String, String> extended_attributes;
|
||
|
||
consume_whitespace();
|
||
|
||
if (lexer.consume_specific('}')) {
|
||
consume_whitespace();
|
||
assert_specific(';');
|
||
break;
|
||
}
|
||
|
||
if (lexer.consume_specific('[')) {
|
||
extended_attributes = parse_extended_attributes();
|
||
if (!interface->has_unscopable_member && extended_attributes.contains("Unscopable"))
|
||
interface->has_unscopable_member = true;
|
||
}
|
||
|
||
if (lexer.next_is("constructor")) {
|
||
parse_constructor();
|
||
continue;
|
||
}
|
||
|
||
if (lexer.next_is("const")) {
|
||
parse_constant();
|
||
continue;
|
||
}
|
||
|
||
if (lexer.next_is("stringifier")) {
|
||
parse_stringifier(extended_attributes);
|
||
continue;
|
||
}
|
||
|
||
if (lexer.next_is("iterable")) {
|
||
parse_iterable();
|
||
continue;
|
||
}
|
||
|
||
if (lexer.next_is("readonly") || lexer.next_is("attribute")) {
|
||
parse_attribute(extended_attributes);
|
||
continue;
|
||
}
|
||
|
||
if (lexer.next_is("getter")) {
|
||
parse_getter(extended_attributes);
|
||
continue;
|
||
}
|
||
|
||
if (lexer.next_is("setter")) {
|
||
parse_setter(extended_attributes);
|
||
continue;
|
||
}
|
||
|
||
if (lexer.next_is("deleter")) {
|
||
parse_deleter(extended_attributes);
|
||
continue;
|
||
}
|
||
|
||
parse_function(extended_attributes);
|
||
}
|
||
|
||
interface->wrapper_class = String::formatted("{}Wrapper", interface->name);
|
||
interface->wrapper_base_class = String::formatted("{}Wrapper", interface->parent_name.is_empty() ? String::empty() : interface->parent_name);
|
||
interface->constructor_class = String::formatted("{}Constructor", interface->name);
|
||
interface->prototype_class = String::formatted("{}Prototype", interface->name);
|
||
interface->prototype_base_class = String::formatted("{}Prototype", interface->parent_name.is_empty() ? "Object" : interface->parent_name);
|
||
consume_whitespace();
|
||
}
|
||
|
||
while (!lexer.is_eof()) {
|
||
assert_string("dictionary");
|
||
consume_whitespace();
|
||
|
||
Dictionary dictionary {};
|
||
|
||
auto name = lexer.consume_until([](auto ch) { return isspace(ch); });
|
||
consume_whitespace();
|
||
|
||
if (lexer.consume_specific(':')) {
|
||
consume_whitespace();
|
||
dictionary.parent_name = lexer.consume_until([](auto ch) { return isspace(ch); });
|
||
consume_whitespace();
|
||
}
|
||
assert_specific('{');
|
||
|
||
for (;;) {
|
||
consume_whitespace();
|
||
|
||
if (lexer.consume_specific('}')) {
|
||
consume_whitespace();
|
||
assert_specific(';');
|
||
break;
|
||
}
|
||
|
||
DictionaryMember member {};
|
||
if (lexer.consume_specific("required")) {
|
||
member.required = true;
|
||
consume_whitespace();
|
||
if (lexer.consume_specific('['))
|
||
member.extended_attributes = parse_extended_attributes();
|
||
}
|
||
|
||
member.type = parse_type();
|
||
consume_whitespace();
|
||
|
||
member.name = lexer.consume_until([](auto ch) { return isspace(ch) || ch == ';'; });
|
||
consume_whitespace();
|
||
|
||
if (lexer.consume_specific('=')) {
|
||
VERIFY(!member.required);
|
||
consume_whitespace();
|
||
auto default_value = lexer.consume_until([](auto ch) { return isspace(ch) || ch == ';'; });
|
||
member.default_value = default_value;
|
||
consume_whitespace();
|
||
}
|
||
|
||
assert_specific(';');
|
||
dictionary.members.append(move(member));
|
||
}
|
||
|
||
// dictionary members need to be evaluated in lexicographical order
|
||
quick_sort(dictionary.members, [&](auto& one, auto& two) {
|
||
return one.name < two.name;
|
||
});
|
||
|
||
interface->dictionaries.set(name, move(dictionary));
|
||
consume_whitespace();
|
||
}
|
||
|
||
for (auto& import : imports) { // FIXME: Instead of copying every imported dictionary into the current interface, query imports directly
|
||
for (auto& dictionary : import.dictionaries)
|
||
interface->dictionaries.set(dictionary.key, dictionary.value);
|
||
}
|
||
|
||
return interface;
|
||
}
|
||
|
||
}
|
||
|
||
static void generate_constructor_header(IDL::Interface const&);
|
||
static void generate_constructor_implementation(IDL::Interface const&);
|
||
static void generate_prototype_header(IDL::Interface const&);
|
||
static void generate_prototype_implementation(IDL::Interface const&);
|
||
static void generate_header(IDL::Interface const&);
|
||
static void generate_implementation(IDL::Interface const&);
|
||
static void generate_iterator_prototype_header(IDL::Interface const&);
|
||
static void generate_iterator_prototype_implementation(IDL::Interface const&);
|
||
static void generate_iterator_header(IDL::Interface const&);
|
||
static void generate_iterator_implementation(IDL::Interface const&);
|
||
|
||
int main(int argc, char** argv)
|
||
{
|
||
Core::ArgsParser args_parser;
|
||
StringView path = nullptr;
|
||
StringView import_base_path = nullptr;
|
||
bool header_mode = false;
|
||
bool implementation_mode = false;
|
||
bool constructor_header_mode = false;
|
||
bool constructor_implementation_mode = false;
|
||
bool prototype_header_mode = false;
|
||
bool prototype_implementation_mode = false;
|
||
bool iterator_header_mode = false;
|
||
bool iterator_implementation_mode = false;
|
||
bool iterator_prototype_header_mode = false;
|
||
bool iterator_prototype_implementation_mode = false;
|
||
args_parser.add_option(header_mode, "Generate the wrapper .h file", "header", 'H');
|
||
args_parser.add_option(implementation_mode, "Generate the wrapper .cpp file", "implementation", 'I');
|
||
args_parser.add_option(constructor_header_mode, "Generate the constructor .h file", "constructor-header", 'C');
|
||
args_parser.add_option(constructor_implementation_mode, "Generate the constructor .cpp file", "constructor-implementation", 'O');
|
||
args_parser.add_option(prototype_header_mode, "Generate the prototype .h file", "prototype-header", 'P');
|
||
args_parser.add_option(prototype_implementation_mode, "Generate the prototype .cpp file", "prototype-implementation", 'R');
|
||
args_parser.add_option(iterator_header_mode, "Generate the iterator wrapper .h file", "iterator-header", 0);
|
||
args_parser.add_option(iterator_implementation_mode, "Generate the iterator wrapper .cpp file", "iterator-implementation", 0);
|
||
args_parser.add_option(iterator_prototype_header_mode, "Generate the iterator prototype .h file", "iterator-prototype-header", 0);
|
||
args_parser.add_option(iterator_prototype_implementation_mode, "Generate the iterator prototype .cpp file", "iterator-prototype-implementation", 0);
|
||
args_parser.add_positional_argument(path, "IDL file", "idl-file");
|
||
args_parser.add_positional_argument(import_base_path, "Import base path", "import-base-path", Core::ArgsParser::Required::No);
|
||
args_parser.parse(argc, argv);
|
||
|
||
auto file_or_error = Core::File::open(path, Core::OpenMode::ReadOnly);
|
||
if (file_or_error.is_error()) {
|
||
warnln("Failed to open {}: {}", path, file_or_error.error());
|
||
return 1;
|
||
}
|
||
|
||
LexicalPath lexical_path(path);
|
||
auto& namespace_ = lexical_path.parts_view().at(lexical_path.parts_view().size() - 2);
|
||
|
||
auto data = file_or_error.value()->read_all();
|
||
|
||
if (import_base_path.is_null())
|
||
import_base_path = lexical_path.dirname();
|
||
|
||
auto interface = IDL::parse_interface(path, data, import_base_path);
|
||
|
||
if (namespace_.is_one_of("Crypto", "CSS", "DOM", "HTML", "UIEvents", "Geometry", "HighResolutionTime", "NavigationTiming", "RequestIdleCallback", "SVG", "XHR", "URL")) {
|
||
StringBuilder builder;
|
||
builder.append(namespace_);
|
||
builder.append("::");
|
||
builder.append(interface->name);
|
||
interface->fully_qualified_name = builder.to_string();
|
||
} else {
|
||
interface->fully_qualified_name = interface->name;
|
||
}
|
||
|
||
if constexpr (WRAPPER_GENERATOR_DEBUG) {
|
||
dbgln("Attributes:");
|
||
for (auto& attribute : interface->attributes) {
|
||
dbgln(" {}{}{} {}",
|
||
attribute.readonly ? "readonly " : "",
|
||
attribute.type.name,
|
||
attribute.type.nullable ? "?" : "",
|
||
attribute.name);
|
||
}
|
||
|
||
dbgln("Functions:");
|
||
for (auto& function : interface->functions) {
|
||
dbgln(" {}{} {}",
|
||
function.return_type.name,
|
||
function.return_type.nullable ? "?" : "",
|
||
function.name);
|
||
for (auto& parameter : function.parameters) {
|
||
dbgln(" {}{} {}",
|
||
parameter.type.name,
|
||
parameter.type.nullable ? "?" : "",
|
||
parameter.name);
|
||
}
|
||
}
|
||
|
||
dbgln("Static Functions:");
|
||
for (auto& function : interface->static_functions) {
|
||
dbgln(" static {}{} {}",
|
||
function.return_type.name,
|
||
function.return_type.nullable ? "?" : "",
|
||
function.name);
|
||
for (auto& parameter : function.parameters) {
|
||
dbgln(" {}{} {}",
|
||
parameter.type.name,
|
||
parameter.type.nullable ? "?" : "",
|
||
parameter.name);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (header_mode)
|
||
generate_header(*interface);
|
||
|
||
if (implementation_mode)
|
||
generate_implementation(*interface);
|
||
|
||
if (constructor_header_mode)
|
||
generate_constructor_header(*interface);
|
||
|
||
if (constructor_implementation_mode)
|
||
generate_constructor_implementation(*interface);
|
||
|
||
if (prototype_header_mode)
|
||
generate_prototype_header(*interface);
|
||
|
||
if (prototype_implementation_mode)
|
||
generate_prototype_implementation(*interface);
|
||
|
||
if (iterator_header_mode)
|
||
generate_iterator_header(*interface);
|
||
|
||
if (iterator_implementation_mode)
|
||
generate_iterator_implementation(*interface);
|
||
|
||
if (iterator_prototype_header_mode)
|
||
generate_iterator_prototype_header(*interface);
|
||
|
||
if (iterator_prototype_implementation_mode)
|
||
generate_iterator_prototype_implementation(*interface);
|
||
|
||
return 0;
|
||
}
|
||
|
||
static bool should_emit_wrapper_factory(IDL::Interface const& interface)
|
||
{
|
||
// FIXME: This is very hackish.
|
||
if (interface.name == "Event")
|
||
return false;
|
||
if (interface.name == "EventTarget")
|
||
return false;
|
||
if (interface.name == "Node")
|
||
return false;
|
||
if (interface.name == "Text")
|
||
return false;
|
||
if (interface.name == "Document")
|
||
return false;
|
||
if (interface.name == "DocumentType")
|
||
return false;
|
||
if (interface.name.ends_with("Element"))
|
||
return false;
|
||
if (interface.name.starts_with("CSS") && interface.name.ends_with("Rule"))
|
||
return false;
|
||
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;
|
||
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 return_void = false, bool legacy_null_to_empty_string = false, bool optional = false, Optional<String> optional_default_value = {})
|
||
{
|
||
auto scoped_generator = generator.fork();
|
||
scoped_generator.set("cpp_name", make_input_acceptable_cpp(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 (parameter.type.name == "Window")
|
||
scoped_generator.set("wrapper_name", "WindowObject");
|
||
else
|
||
scoped_generator.set("wrapper_name", String::formatted("{}Wrapper", parameter.type.name));
|
||
|
||
if (optional_default_value.has_value())
|
||
scoped_generator.set("parameter.optional_default_value", *optional_default_value);
|
||
|
||
if (return_void)
|
||
scoped_generator.set("return_statement", "return;");
|
||
else
|
||
scoped_generator.set("return_statement", "return {};");
|
||
|
||
// FIXME: Add support for optional, nullable and default values to all types
|
||
if (parameter.type.is_string()) {
|
||
if (!optional) {
|
||
if (!parameter.type.nullable) {
|
||
scoped_generator.append(R"~~~(
|
||
auto @cpp_name@ = @js_name@@js_suffix@.to_string(global_object, @legacy_null_to_empty_string@);
|
||
if (vm.exception())
|
||
@return_statement@
|
||
)~~~");
|
||
} else {
|
||
scoped_generator.append(R"~~~(
|
||
String @cpp_name@;
|
||
if (!@js_name@@js_suffix@.is_nullish()) {
|
||
@cpp_name@ = @js_name@@js_suffix@.to_string(global_object, @legacy_null_to_empty_string@);
|
||
if (vm.exception())
|
||
@return_statement@
|
||
}
|
||
)~~~");
|
||
}
|
||
} else {
|
||
scoped_generator.append(R"~~~(
|
||
String @cpp_name@;
|
||
if (!@js_name@@js_suffix@.is_undefined()) {
|
||
@cpp_name@ = @js_name@@js_suffix@.to_string(global_object, @legacy_null_to_empty_string@);
|
||
if (vm.exception())
|
||
@return_statement@
|
||
})~~~");
|
||
if (optional_default_value.has_value() && (!parameter.type.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 == "EventListener") {
|
||
if (parameter.type.nullable) {
|
||
scoped_generator.append(R"~~~(
|
||
RefPtr<EventListener> @cpp_name@;
|
||
if (!@js_name@@js_suffix@.is_nullish()) {
|
||
if (!@js_name@@js_suffix@.is_function()) {
|
||
vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::NotAnObjectOfType, "Function");
|
||
@return_statement@
|
||
}
|
||
@cpp_name@ = adopt_ref(*new EventListener(JS::make_handle(&@js_name@@js_suffix@.as_function())));
|
||
}
|
||
)~~~");
|
||
} else {
|
||
scoped_generator.append(R"~~~(
|
||
if (!@js_name@@js_suffix@.is_function()) {
|
||
vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::NotAnObjectOfType, "Function");
|
||
@return_statement@
|
||
}
|
||
auto @cpp_name@ = adopt_ref(*new EventListener(JS::make_handle(&@js_name@@js_suffix@.as_function())));
|
||
)~~~");
|
||
}
|
||
} else if (is_wrappable_type(parameter.type)) {
|
||
if (!parameter.type.nullable) {
|
||
scoped_generator.append(R"~~~(
|
||
auto @cpp_name@_object = @js_name@@js_suffix@.to_object(global_object);
|
||
if (vm.exception())
|
||
@return_statement@
|
||
|
||
if (!is<@wrapper_name@>(@cpp_name@_object)) {
|
||
vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::NotAnObjectOfType, "@parameter.type.name@");
|
||
@return_statement@
|
||
}
|
||
|
||
auto& @cpp_name@ = static_cast<@wrapper_name@*>(@cpp_name@_object)->impl();
|
||
)~~~");
|
||
} else {
|
||
scoped_generator.append(R"~~~(
|
||
@parameter.type.name@* @cpp_name@ = nullptr;
|
||
if (!@js_name@@js_suffix@.is_nullish()) {
|
||
auto @cpp_name@_object = @js_name@@js_suffix@.to_object(global_object);
|
||
if (vm.exception())
|
||
@return_statement@
|
||
|
||
if (!is<@wrapper_name@>(@cpp_name@_object)) {
|
||
vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::NotAnObjectOfType, "@parameter.type.name@");
|
||
@return_statement@
|
||
}
|
||
|
||
@cpp_name@ = &static_cast<@wrapper_name@*>(@cpp_name@_object)->impl();
|
||
}
|
||
)~~~");
|
||
}
|
||
} else if (parameter.type.name == "double") {
|
||
if (!optional) {
|
||
scoped_generator.append(R"~~~(
|
||
double @cpp_name@ = @js_name@@js_suffix@.to_double(global_object);
|
||
if (vm.exception())
|
||
@return_statement@
|
||
)~~~");
|
||
} else {
|
||
if (optional_default_value.has_value()) {
|
||
scoped_generator.append(R"~~~(
|
||
double @cpp_name@;
|
||
)~~~");
|
||
} else {
|
||
scoped_generator.append(R"~~~(
|
||
Optional<double> @cpp_name@;
|
||
)~~~");
|
||
}
|
||
scoped_generator.append(R"~~~(
|
||
if (!@js_name@@js_suffix@.is_undefined()) {
|
||
@cpp_name@ = @js_name@@js_suffix@.to_double(global_object);
|
||
if (vm.exception())
|
||
@return_statement@
|
||
}
|
||
)~~~");
|
||
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) {
|
||
scoped_generator.append(R"~~~(
|
||
bool @cpp_name@ = @js_name@@js_suffix@.to_boolean();
|
||
)~~~");
|
||
} else {
|
||
if (optional_default_value.has_value()) {
|
||
scoped_generator.append(R"~~~(
|
||
bool @cpp_name@;
|
||
)~~~");
|
||
} else {
|
||
scoped_generator.append(R"~~~(
|
||
Optional<bool> @cpp_name@;
|
||
)~~~");
|
||
}
|
||
scoped_generator.append(R"~~~(
|
||
if (!@js_name@@js_suffix@.is_undefined())
|
||
@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 {
|
||
scoped_generator.append(R"~~~(
|
||
)~~~");
|
||
}
|
||
}
|
||
} else if (parameter.type.name == "unsigned long") {
|
||
scoped_generator.append(R"~~~(
|
||
auto @cpp_name@ = @js_name@@js_suffix@.to_u32(global_object);
|
||
if (vm.exception())
|
||
@return_statement@
|
||
)~~~");
|
||
} else if (parameter.type.name == "unsigned short") {
|
||
scoped_generator.append(R"~~~(
|
||
auto @cpp_name@ = (u16)@js_name@@js_suffix@.to_u32(global_object);
|
||
if (vm.exception())
|
||
@return_statement@
|
||
)~~~");
|
||
} else if (parameter.type.name == "long") {
|
||
scoped_generator.append(R"~~~(
|
||
auto @cpp_name@ = @js_name@@js_suffix@.to_i32(global_object);
|
||
if (vm.exception())
|
||
@return_statement@
|
||
)~~~");
|
||
} else if (parameter.type.name == "EventHandler") {
|
||
// x.onfoo = function() { ... }
|
||
scoped_generator.append(R"~~~(
|
||
HTML::EventHandler @cpp_name@;
|
||
if (@js_name@@js_suffix@.is_function()) {
|
||
@cpp_name@.callback = JS::make_handle(&@js_name@@js_suffix@.as_function());
|
||
} else if (@js_name@@js_suffix@.is_string()) {
|
||
@cpp_name@.string = @js_name@@js_suffix@.as_string().string();
|
||
} else {
|
||
return JS::js_undefined();
|
||
}
|
||
)~~~");
|
||
} 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@;
|
||
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 {
|
||
TODO();
|
||
}
|
||
}
|
||
}
|
||
} else if (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()) {
|
||
vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::NotAnObjectOfType, "@parameter.type.name@");
|
||
@return_statement@
|
||
}
|
||
@parameter.type.name@ @cpp_name@ {};
|
||
)~~~");
|
||
auto* current_dictionary = &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());
|
||
dictionary_generator.set("member_name", member_js_name);
|
||
dictionary_generator.append(R"~~~(
|
||
JS::Value @member_name@;
|
||
if (@js_name@@js_suffix@.is_nullish()) {
|
||
@member_name@ = JS::js_undefined();
|
||
} else {
|
||
auto @member_name@_or_error = @js_name@@js_suffix@.as_object().get("@member_key@");
|
||
if (@member_name@_or_error.is_error())
|
||
@return_statement@
|
||
@member_name@ = @member_name@_or_error.release_value();
|
||
}
|
||
)~~~");
|
||
if (member.required) {
|
||
dictionary_generator.append(R"~~~(
|
||
if (@member_name@.is_undefined()) {
|
||
vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::MissingRequiredProperty, "@member_key@");
|
||
@return_statement@
|
||
}
|
||
)~~~");
|
||
}
|
||
|
||
auto member_value_name = String::formatted("{}_value", member_js_name);
|
||
dictionary_generator.set("member_value_name", member_value_name);
|
||
generate_to_cpp(dictionary_generator, member, member_js_name, "", member_value_name, dictionaries, return_void, member.extended_attributes.contains("LegacyNullToEmptyString"), !member.required, member.default_value);
|
||
dictionary_generator.append(R"~~~(
|
||
@cpp_name@.@member_name@ = @member_value_name@;
|
||
)~~~");
|
||
}
|
||
if (current_dictionary->parent_name.is_null())
|
||
break;
|
||
VERIFY(dictionaries.contains(current_dictionary->parent_name));
|
||
current_dictionary = &dictionaries.find(current_dictionary->parent_name)->value;
|
||
}
|
||
} else {
|
||
dbgln("Unimplemented JS-to-C++ conversion: {}", parameter.type.name);
|
||
VERIFY_NOT_REACHED();
|
||
}
|
||
}
|
||
|
||
template<typename FunctionType>
|
||
static void generate_argument_count_check(SourceGenerator& generator, FunctionType& function)
|
||
{
|
||
auto argument_count_check_generator = generator.fork();
|
||
argument_count_check_generator.set("function.name", function.name);
|
||
argument_count_check_generator.set("function.nargs", String::number(function.length()));
|
||
|
||
if (function.length() == 0)
|
||
return;
|
||
if (function.length() == 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", String::formatted(", \"{}\"", function.length()));
|
||
}
|
||
|
||
argument_count_check_generator.append(R"~~~(
|
||
if (vm.argument_count() < @function.nargs@) {
|
||
vm.throw_exception<JS::TypeError>(global_object, @.bad_arg_count@, "@function.name@"@.arg_count_suffix@);
|
||
return {};
|
||
}
|
||
)~~~");
|
||
}
|
||
|
||
static void generate_arguments(SourceGenerator& generator, Vector<IDL::Parameter> const& parameters, StringBuilder& arguments_builder, HashMap<String, IDL::Dictionary> const& dictionaries, bool return_void = false)
|
||
{
|
||
auto arguments_generator = generator.fork();
|
||
|
||
Vector<String> parameter_names;
|
||
size_t argument_index = 0;
|
||
for (auto& parameter : parameters) {
|
||
parameter_names.append(make_input_acceptable_cpp(parameter.name.to_snakecase()));
|
||
arguments_generator.set("argument.index", String::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", String::number(argument_index), parameter.name.to_snakecase(), dictionaries, return_void, legacy_null_to_empty_string, parameter.optional, parameter.optional_default_value);
|
||
++argument_index;
|
||
}
|
||
|
||
arguments_builder.join(", ", parameter_names);
|
||
}
|
||
|
||
static void generate_wrap_statement(SourceGenerator& generator, String const& value, IDL::Type const& type, StringView const& result_expression)
|
||
{
|
||
auto scoped_generator = generator.fork();
|
||
scoped_generator.set("value", value);
|
||
scoped_generator.set("type", type.name);
|
||
scoped_generator.set("result_expression", result_expression);
|
||
|
||
if (type.name == "undefined") {
|
||
scoped_generator.append(R"~~~(
|
||
@result_expression@ JS::js_undefined();
|
||
)~~~");
|
||
return;
|
||
}
|
||
|
||
if (type.nullable) {
|
||
if (type.is_string()) {
|
||
scoped_generator.append(R"~~~(
|
||
if (@value@.is_null()) {
|
||
@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::js_string(vm, @value@);
|
||
)~~~");
|
||
} else if (type.name == "ArrayFromVector") {
|
||
// FIXME: Remove this fake type hack once it's no longer needed.
|
||
// Basically once we have sequence<T> we can throw this out.
|
||
scoped_generator.append(R"~~~(
|
||
auto* new_array = JS::Array::create(global_object, 0);
|
||
for (auto& element : @value@)
|
||
new_array->indexed_properties().append(wrap(global_object, element));
|
||
|
||
@result_expression@ new_array;
|
||
)~~~");
|
||
} else if (type.name == "boolean" || type.name == "double") {
|
||
scoped_generator.append(R"~~~(
|
||
@result_expression@ JS::Value(@value@);
|
||
)~~~");
|
||
} else if (type.name == "short" || type.name == "unsigned short" || type.name == "long" || type.name == "unsigned long") {
|
||
scoped_generator.append(R"~~~(
|
||
@result_expression@ JS::Value((i32)@value@);
|
||
)~~~");
|
||
} else if (type.name == "Uint8ClampedArray" || type.name == "any") {
|
||
scoped_generator.append(R"~~~(
|
||
@result_expression@ @value@;
|
||
)~~~");
|
||
} else if (type.name == "EventHandler") {
|
||
scoped_generator.append(R"~~~(
|
||
if (@value@.callback.is_null())
|
||
@result_expression@ JS::js_null();
|
||
else
|
||
@result_expression@ @value@.callback.cell();
|
||
)~~~");
|
||
} else if (type.name == "Location") {
|
||
// Location is special cased as it is already a JS::Object.
|
||
scoped_generator.append(R"~~~(
|
||
@result_expression@ JS::Value(@value@);
|
||
)~~~");
|
||
} else {
|
||
scoped_generator.append(R"~~~(
|
||
@result_expression@ wrap(global_object, const_cast<@type@&>(*@value@));
|
||
)~~~");
|
||
}
|
||
|
||
if (type.nullable) {
|
||
scoped_generator.append(R"~~~(
|
||
}
|
||
)~~~");
|
||
}
|
||
}
|
||
|
||
enum class StaticFunction {
|
||
No,
|
||
Yes,
|
||
};
|
||
|
||
static void generate_return_statement(SourceGenerator& generator, IDL::Type const& return_type)
|
||
{
|
||
return generate_wrap_statement(generator, "retval", return_type, "return"sv);
|
||
}
|
||
|
||
static void generate_variable_statement(SourceGenerator& generator, String const& variable_name, IDL::Type const& value_type, String const& value_name)
|
||
{
|
||
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, String::formatted("{} = ", variable_name));
|
||
}
|
||
|
||
static void generate_function(SourceGenerator& generator, IDL::Function const& function, StaticFunction is_static_function, String const& class_name, String const& interface_fully_qualified_name, HashMap<String, IDL::Dictionary> const& dictionaries)
|
||
{
|
||
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()));
|
||
|
||
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@)
|
||
{
|
||
)~~~");
|
||
|
||
if (is_static_function == StaticFunction::No) {
|
||
function_generator.append(R"~~~(
|
||
auto* impl = impl_from(vm, global_object);
|
||
if (!impl)
|
||
return {};
|
||
)~~~");
|
||
}
|
||
|
||
generate_argument_count_check(generator, function);
|
||
|
||
StringBuilder arguments_builder;
|
||
generate_arguments(generator, function.parameters, arguments_builder, dictionaries);
|
||
function_generator.set(".arguments", arguments_builder.string_view());
|
||
|
||
if (is_static_function == StaticFunction::No) {
|
||
function_generator.append(R"~~~(
|
||
auto result = throw_dom_exception_if_needed(vm, global_object, [&] { return impl->@function.cpp_name@(@.arguments@); });
|
||
)~~~");
|
||
} else {
|
||
function_generator.append(R"~~~(
|
||
auto result = throw_dom_exception_if_needed(vm, global_object, [&] { return @interface_fully_qualified_name@::@function.cpp_name@(@.arguments@); });
|
||
)~~~");
|
||
}
|
||
|
||
function_generator.append(R"~~~(
|
||
if (should_return_empty(result))
|
||
return JS::Value();
|
||
|
||
[[maybe_unused]] auto retval = result.release_value();
|
||
)~~~");
|
||
|
||
generate_return_statement(generator, function.return_type);
|
||
|
||
function_generator.append(R"~~~(
|
||
}
|
||
)~~~");
|
||
}
|
||
|
||
static void generate_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("wrapper_base_class", interface.wrapper_base_class);
|
||
generator.set("wrapper_class", interface.wrapper_class);
|
||
generator.set("wrapper_class:snakecase", interface.wrapper_class.to_snakecase());
|
||
|
||
generator.append(R"~~~(
|
||
#pragma once
|
||
|
||
#include <LibWeb/Bindings/Wrapper.h>
|
||
|
||
// FIXME: This is very strange.
|
||
#if __has_include(<LibWeb/Crypto/@name@.h>)
|
||
# include <LibWeb/Crypto/@name@.h>
|
||
#elif __has_include(<LibWeb/CSS/@name@.h>)
|
||
# include <LibWeb/CSS/@name@.h>
|
||
#elif __has_include(<LibWeb/DOM/@name@.h>)
|
||
# include <LibWeb/DOM/@name@.h>
|
||
#elif __has_include(<LibWeb/Geometry/@name@.h>)
|
||
# include <LibWeb/Geometry/@name@.h>
|
||
#elif __has_include(<LibWeb/HTML/@name@.h>)
|
||
# include <LibWeb/HTML/@name@.h>
|
||
#elif __has_include(<LibWeb/UIEvents/@name@.h>)
|
||
# include <LibWeb/UIEvents/@name@.h>
|
||
#elif __has_include(<LibWeb/HighResolutionTime/@name@.h>)
|
||
# include <LibWeb/HighResolutionTime/@name@.h>
|
||
#elif __has_include(<LibWeb/NavigationTiming/@name@.h>)
|
||
# include <LibWeb/NavigationTiming/@name@.h>
|
||
#elif __has_include(<LibWeb/RequestIdleCallback/@name@.h>)
|
||
# include <LibWeb/RequestIdleCallback/@name@.h>
|
||
#elif __has_include(<LibWeb/SVG/@name@.h>)
|
||
# include <LibWeb/SVG/@name@.h>
|
||
#elif __has_include(<LibWeb/XHR/@name@.h>)
|
||
# include <LibWeb/XHR/@name@.h>
|
||
#elif __has_include(<LibWeb/URL/@name@.h>)
|
||
# include <LibWeb/URL/@name@.h>
|
||
#endif
|
||
)~~~");
|
||
|
||
if (interface.wrapper_base_class != "Wrapper") {
|
||
generator.append(R"~~~(
|
||
#include <LibWeb/Bindings/@wrapper_base_class@.h>
|
||
)~~~");
|
||
}
|
||
|
||
generator.append(R"~~~(
|
||
namespace Web::Bindings {
|
||
|
||
class @wrapper_class@ : public @wrapper_base_class@ {
|
||
JS_OBJECT(@name@, @wrapper_base_class@);
|
||
public:
|
||
static @wrapper_class@* create(JS::GlobalObject&, @fully_qualified_name@&);
|
||
|
||
@wrapper_class@(JS::GlobalObject&, @fully_qualified_name@&);
|
||
virtual void initialize(JS::GlobalObject&) override;
|
||
virtual ~@wrapper_class@() override;
|
||
)~~~");
|
||
|
||
if (interface.extended_attributes.contains("CustomGet")) {
|
||
generator.append(R"~~~(
|
||
virtual JS::ThrowCompletionOr<JS::Value> internal_get(JS::PropertyName const&, JS::Value receiver) const override;
|
||
)~~~");
|
||
}
|
||
if (interface.extended_attributes.contains("CustomSet")) {
|
||
generator.append(R"~~~(
|
||
virtual JS::ThrowCompletionOr<bool> internal_set(JS::PropertyName const&, JS::Value, JS::Value receiver) override;
|
||
)~~~");
|
||
}
|
||
|
||
if (interface.extended_attributes.contains("CustomHasProperty")) {
|
||
generator.append(R"~~~(
|
||
virtual JS::ThrowCompletionOr<bool> internal_has_property(JS::PropertyName const&) const override;
|
||
)~~~");
|
||
}
|
||
|
||
if (interface.extended_attributes.contains("CustomVisit")) {
|
||
generator.append(R"~~~(
|
||
virtual void visit_edges(JS::Cell::Visitor&) override;
|
||
)~~~");
|
||
}
|
||
|
||
if (interface.is_legacy_platform_object()) {
|
||
generator.append(R"~~~(
|
||
virtual JS::ThrowCompletionOr<Optional<JS::PropertyDescriptor>> internal_get_own_property(JS::PropertyName const&) const override;
|
||
virtual JS::ThrowCompletionOr<bool> internal_set(JS::PropertyName const&, JS::Value, JS::Value) override;
|
||
virtual JS::ThrowCompletionOr<bool> internal_define_own_property(JS::PropertyName const&, JS::PropertyDescriptor const&) override;
|
||
virtual JS::ThrowCompletionOr<bool> internal_delete(JS::PropertyName const&) override;
|
||
virtual JS::ThrowCompletionOr<bool> internal_prevent_extensions() override;
|
||
virtual JS::ThrowCompletionOr<JS::MarkedValueList> internal_own_property_keys() const override;
|
||
)~~~");
|
||
}
|
||
|
||
if (interface.wrapper_base_class == "Wrapper") {
|
||
generator.append(R"~~~(
|
||
@fully_qualified_name@& impl() { return *m_impl; }
|
||
@fully_qualified_name@ const& impl() const { return *m_impl; }
|
||
)~~~");
|
||
} else {
|
||
generator.append(R"~~~(
|
||
@fully_qualified_name@& impl() { return static_cast<@fully_qualified_name@&>(@wrapper_base_class@::impl()); }
|
||
@fully_qualified_name@ const& impl() const { return static_cast<@fully_qualified_name@ const&>(@wrapper_base_class@::impl()); }
|
||
)~~~");
|
||
}
|
||
|
||
generator.append(R"~~~(
|
||
private:
|
||
)~~~");
|
||
|
||
if (interface.is_legacy_platform_object()) {
|
||
generator.append(R"~~~(
|
||
bool is_named_property_exposed_on_object(JS::PropertyName const&) const;
|
||
Optional<JS::PropertyDescriptor> legacy_platform_object_get_own_property_for_get_own_property_slot(JS::PropertyName const&) const;
|
||
Optional<JS::PropertyDescriptor> legacy_platform_object_get_own_property_for_set_slot(JS::PropertyName const&) const;
|
||
)~~~");
|
||
}
|
||
|
||
if (interface.wrapper_base_class == "Wrapper") {
|
||
generator.append(R"~~~(
|
||
NonnullRefPtr<@fully_qualified_name@> m_impl;
|
||
)~~~");
|
||
}
|
||
|
||
generator.append(R"~~~(
|
||
};
|
||
)~~~");
|
||
|
||
if (should_emit_wrapper_factory(interface)) {
|
||
generator.append(R"~~~(
|
||
@wrapper_class@* wrap(JS::GlobalObject&, @fully_qualified_name@&);
|
||
)~~~");
|
||
}
|
||
|
||
generator.append(R"~~~(
|
||
} // namespace Web::Bindings
|
||
)~~~");
|
||
|
||
outln("{}", generator.as_string_view());
|
||
}
|
||
|
||
void generate_implementation(IDL::Interface const& interface)
|
||
{
|
||
StringBuilder builder;
|
||
SourceGenerator generator { builder };
|
||
|
||
generator.set("name", interface.name);
|
||
generator.set("wrapper_class", interface.wrapper_class);
|
||
generator.set("wrapper_base_class", interface.wrapper_base_class);
|
||
generator.set("prototype_class", interface.prototype_class);
|
||
generator.set("fully_qualified_name", interface.fully_qualified_name);
|
||
|
||
generator.append(R"~~~(
|
||
#include <AK/FlyString.h>
|
||
#include <LibJS/Runtime/Array.h>
|
||
#include <LibJS/Runtime/Error.h>
|
||
#include <LibJS/Runtime/FunctionObject.h>
|
||
#include <LibJS/Runtime/GlobalObject.h>
|
||
#include <LibJS/Runtime/TypedArray.h>
|
||
#include <LibJS/Runtime/Value.h>
|
||
#include <LibWeb/Bindings/@prototype_class@.h>
|
||
#include <LibWeb/Bindings/@wrapper_class@.h>
|
||
#include <LibWeb/Bindings/CSSRuleListWrapper.h>
|
||
#include <LibWeb/Bindings/CSSRuleWrapper.h>
|
||
#include <LibWeb/Bindings/CSSRuleWrapperFactory.h>
|
||
#include <LibWeb/Bindings/CSSStyleSheetWrapper.h>
|
||
#include <LibWeb/Bindings/CanvasRenderingContext2DWrapper.h>
|
||
#include <LibWeb/Bindings/CommentWrapper.h>
|
||
#include <LibWeb/Bindings/DOMImplementationWrapper.h>
|
||
#include <LibWeb/Bindings/DOMRectWrapper.h>
|
||
#include <LibWeb/Bindings/DocumentFragmentWrapper.h>
|
||
#include <LibWeb/Bindings/DocumentTypeWrapper.h>
|
||
#include <LibWeb/Bindings/DocumentWrapper.h>
|
||
#include <LibWeb/Bindings/EventTargetWrapperFactory.h>
|
||
#include <LibWeb/Bindings/EventWrapperFactory.h>
|
||
#include <LibWeb/Bindings/ExceptionOrUtils.h>
|
||
#include <LibWeb/Bindings/HTMLCanvasElementWrapper.h>
|
||
#include <LibWeb/Bindings/HTMLCollectionWrapper.h>
|
||
#include <LibWeb/Bindings/HTMLFormElementWrapper.h>
|
||
#include <LibWeb/Bindings/HTMLHeadElementWrapper.h>
|
||
#include <LibWeb/Bindings/HTMLImageElementWrapper.h>
|
||
#include <LibWeb/Bindings/HTMLTableCaptionElementWrapper.h>
|
||
#include <LibWeb/Bindings/HTMLTableSectionElementWrapper.h>
|
||
#include <LibWeb/Bindings/IDLAbstractOperations.h>
|
||
#include <LibWeb/Bindings/ImageDataWrapper.h>
|
||
#include <LibWeb/Bindings/MessagePortWrapper.h>
|
||
#include <LibWeb/Bindings/NodeWrapperFactory.h>
|
||
#include <LibWeb/Bindings/TextWrapper.h>
|
||
#include <LibWeb/Bindings/WindowObject.h>
|
||
#include <LibWeb/DOM/Element.h>
|
||
#include <LibWeb/DOM/EventListener.h>
|
||
#include <LibWeb/HTML/HTMLElement.h>
|
||
#include <LibWeb/Origin.h>
|
||
|
||
// 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::Geometry;
|
||
using namespace Web::HTML;
|
||
using namespace Web::RequestIdleCallback;
|
||
|
||
namespace Web::Bindings {
|
||
|
||
@wrapper_class@* @wrapper_class@::create(JS::GlobalObject& global_object, @fully_qualified_name@& impl)
|
||
{
|
||
return global_object.heap().allocate<@wrapper_class@>(global_object, global_object, impl);
|
||
}
|
||
|
||
)~~~");
|
||
|
||
if (interface.wrapper_base_class == "Wrapper") {
|
||
generator.append(R"~~~(
|
||
@wrapper_class@::@wrapper_class@(JS::GlobalObject& global_object, @fully_qualified_name@& impl)
|
||
: Wrapper(static_cast<WindowObject&>(global_object).ensure_web_prototype<@prototype_class@>("@name@"))
|
||
, m_impl(impl)
|
||
{
|
||
}
|
||
)~~~");
|
||
} else {
|
||
generator.append(R"~~~(
|
||
@wrapper_class@::@wrapper_class@(JS::GlobalObject& global_object, @fully_qualified_name@& impl)
|
||
: @wrapper_base_class@(global_object, impl)
|
||
{
|
||
set_prototype(&static_cast<WindowObject&>(global_object).ensure_web_prototype<@prototype_class@>("@name@"));
|
||
}
|
||
)~~~");
|
||
}
|
||
|
||
generator.append(R"~~~(
|
||
void @wrapper_class@::initialize(JS::GlobalObject& global_object)
|
||
{
|
||
@wrapper_base_class@::initialize(global_object);
|
||
}
|
||
|
||
@wrapper_class@::~@wrapper_class@()
|
||
{
|
||
}
|
||
)~~~");
|
||
|
||
if (should_emit_wrapper_factory(interface)) {
|
||
generator.append(R"~~~(
|
||
@wrapper_class@* wrap(JS::GlobalObject& global_object, @fully_qualified_name@& impl)
|
||
{
|
||
return static_cast<@wrapper_class@*>(wrap_impl(global_object, impl));
|
||
}
|
||
)~~~");
|
||
}
|
||
|
||
if (interface.extended_attributes.contains("CustomVisit")) {
|
||
generator.append(R"~~~(
|
||
void @wrapper_class@::visit_edges(JS::Cell::Visitor& visitor)
|
||
{
|
||
@wrapper_base_class@::visit_edges(visitor);
|
||
impl().visit_edges(visitor);
|
||
}
|
||
)~~~");
|
||
}
|
||
|
||
if (interface.is_legacy_platform_object()) {
|
||
auto scoped_generator = generator.fork();
|
||
scoped_generator.set("class_name", interface.wrapper_class);
|
||
scoped_generator.set("fully_qualified_name", interface.fully_qualified_name);
|
||
|
||
// FIXME: This is a hack to avoid duplicating/refactoring a lot of code.
|
||
scoped_generator.append(R"~~~(
|
||
static JS::Value wrap_for_legacy_platform_object_get_own_property(JS::GlobalObject& global_object, [[maybe_unused]] auto& retval)
|
||
{
|
||
[[maybe_unused]] auto& vm = global_object.vm();
|
||
)~~~");
|
||
|
||
if (interface.named_property_getter.has_value()) {
|
||
generate_return_statement(scoped_generator, interface.named_property_getter->return_type);
|
||
} else {
|
||
VERIFY(interface.indexed_property_getter.has_value());
|
||
generate_return_statement(scoped_generator, interface.indexed_property_getter->return_type);
|
||
}
|
||
|
||
scoped_generator.append(R"~~~(
|
||
}
|
||
)~~~");
|
||
|
||
if (interface.supports_named_properties()) {
|
||
// https://heycam.github.io/webidl/#dfn-named-property-visibility
|
||
|
||
scoped_generator.append(R"~~~(
|
||
bool @class_name@::is_named_property_exposed_on_object(JS::PropertyName const& property_name) const
|
||
{
|
||
[[maybe_unused]] auto& vm = this->vm();
|
||
|
||
// The spec doesn't say anything about the type of the property name here.
|
||
// Numbers can be converted to a string, which is fine and what other engines do.
|
||
// However, since a symbol cannot be converted to a string, it cannot be a supported property name. Return early if it's a symbol.
|
||
if (property_name.is_symbol())
|
||
return false;
|
||
|
||
// 1. If P is not a supported property name of O, then return false.
|
||
// NOTE: This is in it's own variable to enforce the type.
|
||
// FIXME: Can this throw?
|
||
Vector<String> supported_property_names = impl().supported_property_names();
|
||
auto property_name_string = property_name.to_string();
|
||
if (!supported_property_names.contains_slow(property_name_string))
|
||
return false;
|
||
|
||
// 2. If O has an own property named P, then return false.
|
||
// NOTE: This has to be done manually instead of using Object::has_own_property, as that would use the overrided internal_get_own_property.
|
||
auto own_property_named_p = TRY_OR_DISCARD(Object::internal_get_own_property(property_name));
|
||
|
||
if (own_property_named_p.has_value())
|
||
return false;
|
||
)~~~");
|
||
|
||
if (interface.extended_attributes.contains("LegacyOverrideBuiltIns")) {
|
||
scoped_generator.append(R"~~~(
|
||
// 3. If O implements an interface that has the [LegacyOverrideBuiltIns] extended attribute, then return true.
|
||
return true;
|
||
}
|
||
)~~~");
|
||
} else {
|
||
scoped_generator.append(R"~~~(
|
||
// NOTE: Step 3 is not here as the interface doesn't have the LegacyOverrideBuiltIns extended attribute.
|
||
// 4. Let prototype be O.[[GetPrototypeOf]]().
|
||
auto* prototype = TRY_OR_DISCARD(internal_get_prototype_of());
|
||
|
||
// 5. While prototype is not null:
|
||
while (prototype) {
|
||
// FIXME: 1. If prototype is not a named properties object, and prototype has an own property named P, then return false.
|
||
// (It currently does not check for named property objects)
|
||
bool prototype_has_own_property_named_p = TRY_OR_DISCARD(prototype->has_own_property(property_name));
|
||
if (prototype_has_own_property_named_p)
|
||
return false;
|
||
|
||
// 2. Set prototype to prototype.[[GetPrototypeOf]]().
|
||
prototype = TRY_OR_DISCARD(prototype->internal_get_prototype_of());
|
||
}
|
||
|
||
// 6. Return true.
|
||
return true;
|
||
}
|
||
)~~~");
|
||
}
|
||
}
|
||
|
||
enum class IgnoreNamedProps {
|
||
No,
|
||
Yes,
|
||
};
|
||
|
||
auto generate_legacy_platform_object_get_own_property_function = [&](IgnoreNamedProps ignore_named_props, String const& for_which_internal_method) {
|
||
// https://heycam.github.io/webidl/#LegacyPlatformObjectGetOwnProperty
|
||
|
||
auto get_own_property_generator = scoped_generator.fork();
|
||
|
||
get_own_property_generator.set("internal_method"sv, for_which_internal_method);
|
||
|
||
get_own_property_generator.append(R"~~~(
|
||
Optional<JS::PropertyDescriptor> @class_name@::legacy_platform_object_get_own_property_for_@internal_method@_slot(JS::PropertyName const& property_name) const
|
||
{
|
||
)~~~");
|
||
|
||
get_own_property_generator.append(R"~~~(
|
||
[[maybe_unused]] auto& vm = this->vm();
|
||
[[maybe_unused]] auto& global_object = this->global_object();
|
||
)~~~");
|
||
|
||
// 1. If O supports indexed properties...
|
||
if (interface.supports_indexed_properties()) {
|
||
// ...and P is an array index, then:
|
||
get_own_property_generator.append(R"~~~(
|
||
if (IDL::is_an_array_index(global_object, property_name)) {
|
||
// 1. Let index be the result of calling ToUint32(P).
|
||
u32 index = property_name.as_number();
|
||
|
||
// 2. If index is a supported property index, then:
|
||
// FIXME: Can this throw?
|
||
if (impl().is_supported_property_index(index)) {
|
||
)~~~");
|
||
// 1. Let operation be the operation used to declare the indexed property getter. (NOTE: Not necessary)
|
||
// 2. Let value be an uninitialized variable. (NOTE: Not necessary)
|
||
|
||
// 3. If operation was defined without an identifier, then set value to the result of performing the steps listed in the interface description to determine the value of an indexed property with index as the index.
|
||
if (interface.indexed_property_getter->name.is_empty()) {
|
||
get_own_property_generator.append(R"~~~(
|
||
auto result = throw_dom_exception_if_needed(vm, global_object, [&] { return impl().determine_value_of_indexed_property(index); });
|
||
)~~~");
|
||
}
|
||
|
||
// 4. Otherwise, operation was defined with an identifier. Set value to the result of performing the method steps of operation with O as this and « index » as the argument values.
|
||
else {
|
||
auto function_scoped_generator = get_own_property_generator.fork();
|
||
|
||
function_scoped_generator.set("function.cpp_name", make_input_acceptable_cpp(interface.indexed_property_getter->name.to_snakecase()));
|
||
|
||
function_scoped_generator.append(R"~~~(
|
||
auto result = throw_dom_exception_if_needed(vm, global_object, [&] { return impl().@function.cpp_name@(index); });
|
||
)~~~");
|
||
}
|
||
|
||
get_own_property_generator.append(R"~~~(
|
||
if (should_return_empty(result))
|
||
return {};
|
||
|
||
auto value = result.release_value();
|
||
|
||
// 5. Let desc be a newly created Property Descriptor with no fields.
|
||
JS::PropertyDescriptor descriptor;
|
||
|
||
// 6. Set desc.[[Value]] to the result of converting value to an ECMAScript value.
|
||
descriptor.value = wrap_for_legacy_platform_object_get_own_property(global_object, value);
|
||
)~~~");
|
||
|
||
// 7. If O implements an interface with an indexed property setter, then set desc.[[Writable]] to true, otherwise set it to false.
|
||
if (interface.indexed_property_setter.has_value()) {
|
||
get_own_property_generator.append(R"~~~(
|
||
descriptor.writable = true;
|
||
)~~~");
|
||
} else {
|
||
get_own_property_generator.append(R"~~~(
|
||
descriptor.writable = false;
|
||
)~~~");
|
||
}
|
||
|
||
get_own_property_generator.append(R"~~~(
|
||
|
||
// 8. Set desc.[[Enumerable]] and desc.[[Configurable]] to true.
|
||
descriptor.enumerable = true;
|
||
descriptor.configurable = true;
|
||
|
||
// 9. Return desc.
|
||
return descriptor;
|
||
}
|
||
|
||
// 3. Set ignoreNamedProps to true.
|
||
// NOTE: To reduce complexity of WrapperGenerator, this just returns early instead of keeping track of another variable.
|
||
return TRY_OR_DISCARD(Object::internal_get_own_property(property_name));
|
||
}
|
||
)~~~");
|
||
}
|
||
|
||
// 2. If O supports named properties and ignoreNamedProps is false, then:
|
||
if (interface.supports_named_properties() && ignore_named_props == IgnoreNamedProps::No) {
|
||
get_own_property_generator.append(R"~~~(
|
||
// 1. If the result of running the named property visibility algorithm with property name P and object O is true, then:
|
||
if (is_named_property_exposed_on_object(property_name)) {
|
||
// FIXME: It's unfortunate that this is done twice, once in is_named_property_exposed_on_object and here.
|
||
auto property_name_string = property_name.to_string();
|
||
)~~~");
|
||
|
||
// 1. Let operation be the operation used to declare the named property getter. (NOTE: Not necessary)
|
||
// 2. Let value be an uninitialized variable. (NOTE: Not necessary)
|
||
|
||
// 3. If operation was defined without an identifier, then set value to the result of performing the steps listed in the interface description to determine the value of a named property with P as the name.
|
||
if (interface.named_property_getter->name.is_empty()) {
|
||
get_own_property_generator.append(R"~~~(
|
||
auto result = throw_dom_exception_if_needed(vm, global_object, [&] { return impl().determine_value_of_named_property(property_name_string); });
|
||
)~~~");
|
||
}
|
||
|
||
// 4. Otherwise, operation was defined with an identifier. Set value to the result of performing the method steps of operation with O as this and « index » as the argument values.
|
||
else {
|
||
auto function_scoped_generator = get_own_property_generator.fork();
|
||
function_scoped_generator.set("function.cpp_name", make_input_acceptable_cpp(interface.named_property_getter->name.to_snakecase()));
|
||
|
||
function_scoped_generator.append(R"~~~(
|
||
auto result = throw_dom_exception_if_needed(vm, global_object, [&] { return impl().@function.cpp_name@(property_name_string); });
|
||
)~~~");
|
||
}
|
||
|
||
get_own_property_generator.append(R"~~~(
|
||
if (should_return_empty(result))
|
||
return {};
|
||
|
||
auto value = result.release_value();
|
||
|
||
// 5. Let desc be a newly created Property Descriptor with no fields.
|
||
JS::PropertyDescriptor descriptor;
|
||
|
||
// 6. Set desc.[[Value]] to the result of converting value to an ECMAScript value.
|
||
descriptor.value = wrap_for_legacy_platform_object_get_own_property(global_object, value);
|
||
)~~~");
|
||
|
||
// 7. If O implements an interface with a named property setter, then set desc.[[Writable]] to true, otherwise set it to false.
|
||
if (interface.named_property_setter.has_value()) {
|
||
get_own_property_generator.append(R"~~~(
|
||
descriptor.writable = true;
|
||
)~~~");
|
||
} else {
|
||
get_own_property_generator.append(R"~~~(
|
||
descriptor.writable = false;
|
||
)~~~");
|
||
}
|
||
|
||
// 8. If O implements an interface with the [LegacyUnenumerableNamedProperties] extended attribute, then set desc.[[Enumerable]] to false, otherwise set it to true.
|
||
if (interface.extended_attributes.contains("LegacyUnenumerableNamedProperties")) {
|
||
get_own_property_generator.append(R"~~~(
|
||
descriptor.enumerable = false;
|
||
)~~~");
|
||
} else {
|
||
get_own_property_generator.append(R"~~~(
|
||
descriptor.enumerable = true;
|
||
)~~~");
|
||
}
|
||
|
||
get_own_property_generator.append(R"~~~(
|
||
// 9. Set desc.[[Configurable]] to true.
|
||
descriptor.configurable = true;
|
||
|
||
// 10. Return desc.
|
||
return descriptor;
|
||
}
|
||
)~~~");
|
||
}
|
||
|
||
// 3. Return OrdinaryGetOwnProperty(O, P).
|
||
get_own_property_generator.append(R"~~~(
|
||
return TRY_OR_DISCARD(Object::internal_get_own_property(property_name));
|
||
}
|
||
)~~~");
|
||
};
|
||
|
||
// Step 1 of [[GetOwnProperty]]: Return LegacyPlatformObjectGetOwnProperty(O, P, false).
|
||
generate_legacy_platform_object_get_own_property_function(IgnoreNamedProps::No, "get_own_property");
|
||
|
||
// Step 2 of [[Set]]: Let ownDesc be LegacyPlatformObjectGetOwnProperty(O, P, true).
|
||
generate_legacy_platform_object_get_own_property_function(IgnoreNamedProps::Yes, "set");
|
||
|
||
if (interface.named_property_setter.has_value()) {
|
||
// https://heycam.github.io/webidl/#invoke-named-setter
|
||
// NOTE: All users of invoke_named_property_setter check that JS::PropertyName is a String before calling it.
|
||
// FIXME: It's not necessary to determine "creating" if the named property setter specifies an identifier.
|
||
// Try avoiding it somehow, e.g. by enforcing supported_property_names doesn't have side effects so it can be skipped.
|
||
scoped_generator.append(R"~~~(
|
||
static void invoke_named_property_setter(JS::GlobalObject& global_object, @fully_qualified_name@& impl, String const& property_name, JS::Value value)
|
||
{
|
||
auto& vm = global_object.vm();
|
||
|
||
// 1. Let creating be true if P is not a supported property name, and false otherwise.
|
||
// NOTE: This is in it's own variable to enforce the type.
|
||
// FIXME: Can this throw?
|
||
Vector<String> supported_property_names = impl.supported_property_names();
|
||
[[maybe_unused]] bool creating = !supported_property_names.contains_slow(property_name);
|
||
)~~~");
|
||
// 2. Let operation be the operation used to declare the named property setter. (NOTE: Not necessary)
|
||
// 3. Let T be the type of the second argument of operation. (NOTE: Not necessary)
|
||
|
||
// 4. Let value be the result of converting V to an IDL value of type T.
|
||
// NOTE: This takes the last parameter as it's enforced that there's only two parameters.
|
||
generate_to_cpp(scoped_generator, interface.named_property_setter->parameters.last(), "value", "", "converted_value", interface.dictionaries, true);
|
||
|
||
// 5. If operation was defined without an identifier, then:
|
||
if (interface.named_property_setter->name.is_empty()) {
|
||
scoped_generator.append(R"~~~(
|
||
if (creating) {
|
||
// 5.1. If creating is true, then perform the steps listed in the interface description to set the value of a new named property with P as the name and value as the value.
|
||
auto result = throw_dom_exception_if_needed(vm, global_object, [&] { impl.set_value_of_new_named_property(property_name, converted_value); });
|
||
if (should_return_empty(result))
|
||
return;
|
||
} else {
|
||
// 5.2 Otherwise, creating is false. Perform the steps listed in the interface description to set the value of an existing named property with P as the name and value as the value.
|
||
auto result = throw_dom_exception_if_needed(vm, global_object, [&] { impl.set_value_of_existing_named_property(property_name, converted_value); });
|
||
if (should_return_empty(result))
|
||
return;
|
||
}
|
||
)~~~");
|
||
} else {
|
||
// 6. Otherwise, operation was defined with an identifier.
|
||
// Perform the method steps of operation with O as this and « P, value » as the argument values.
|
||
auto function_scoped_generator = scoped_generator.fork();
|
||
function_scoped_generator.set("function.cpp_name", make_input_acceptable_cpp(interface.named_property_setter->name.to_snakecase()));
|
||
|
||
function_scoped_generator.append(R"~~~(
|
||
auto result = throw_dom_exception_if_needed(vm, global_object, [&] { impl.@function.cpp_name@(property_name, converted_value); });
|
||
if (should_return_empty(result))
|
||
return;
|
||
)~~~");
|
||
}
|
||
|
||
scoped_generator.append(R"~~~(
|
||
}
|
||
)~~~");
|
||
}
|
||
|
||
if (interface.indexed_property_setter.has_value()) {
|
||
// https://heycam.github.io/webidl/#invoke-indexed-setter
|
||
// NOTE: All users of invoke_indexed_property_setter check if property name is an IDL array index before calling it.
|
||
// FIXME: It's not necessary to determine "creating" if the indexed property setter specifies an identifier.
|
||
// Try avoiding it somehow, e.g. by enforcing supported_property_indices doesn't have side effects so it can be skipped.
|
||
scoped_generator.append(R"~~~(
|
||
static void invoke_indexed_property_setter(JS::GlobalObject& global_object, @fully_qualified_name@& impl, JS::PropertyName const& property_name, JS::Value value)
|
||
{
|
||
auto& vm = global_object.vm();
|
||
|
||
// 1. Let index be the result of calling ToUint32(P).
|
||
u32 index = property_name.as_number();
|
||
|
||
// 2. Let creating be true if index is not a supported property index, and false otherwise.
|
||
// FIXME: Can this throw?
|
||
[[maybe_unused]] bool creating = !impl.is_supported_property_index(index);
|
||
)~~~");
|
||
|
||
// 3. Let operation be the operation used to declare the named property setter. (NOTE: Not necessary)
|
||
// 4. Let T be the type of the second argument of operation. (NOTE: Not necessary)
|
||
|
||
// 5. Let value be the result of converting V to an IDL value of type T.
|
||
// NOTE: This takes the last parameter as it's enforced that there's only two parameters.
|
||
generate_to_cpp(scoped_generator, interface.named_property_setter->parameters.last(), "value", "", "converted_value", interface.dictionaries, true);
|
||
|
||
// 6. If operation was defined without an identifier, then:
|
||
if (interface.indexed_property_setter->name.is_empty()) {
|
||
scoped_generator.append(R"~~~(
|
||
if (creating) {
|
||
// 6.1 If creating is true, then perform the steps listed in the interface description to set the value of a new indexed property with index as the index and value as the value.
|
||
auto result = throw_dom_exception_if_needed(vm, global_object, [&] { impl.set_value_of_new_indexed_property(index, converted_value); });
|
||
if (should_return_empty(result))
|
||
return;
|
||
} else {
|
||
// 6.2 Otherwise, creating is false. Perform the steps listed in the interface description to set the value of an existing indexed property with index as the index and value as the value.
|
||
auto result = throw_dom_exception_if_needed(vm, global_object, [&] { impl.set_value_of_existing_indexed_property(index, converted_value); });
|
||
if (should_return_empty(result))
|
||
return;
|
||
}
|
||
)~~~");
|
||
} else {
|
||
// 7. Otherwise, operation was defined with an identifier.
|
||
// Perform the method steps of operation with O as this and « index, value » as the argument values.
|
||
auto function_scoped_generator = scoped_generator.fork();
|
||
function_scoped_generator.set("function.cpp_name", make_input_acceptable_cpp(interface.indexed_property_setter->name.to_snakecase()));
|
||
|
||
function_scoped_generator.append(R"~~~(
|
||
auto result = throw_dom_exception_if_needed(vm, global_object, [&] { impl.@function.cpp_name@(index, converted_value); });
|
||
if (should_return_empty(result))
|
||
return;
|
||
)~~~");
|
||
}
|
||
|
||
scoped_generator.append(R"~~~(
|
||
}
|
||
)~~~");
|
||
}
|
||
|
||
// == Internal Slot Generation ==
|
||
|
||
// 3.9.1. [[GetOwnProperty]], https://heycam.github.io/webidl/#legacy-platform-object-getownproperty
|
||
scoped_generator.append(R"~~~(
|
||
JS::ThrowCompletionOr<Optional<JS::PropertyDescriptor>> @class_name@::internal_get_own_property(JS::PropertyName const& property_name) const
|
||
{
|
||
// 1. Return LegacyPlatformObjectGetOwnProperty(O, P, false).
|
||
return legacy_platform_object_get_own_property_for_get_own_property_slot(property_name);
|
||
}
|
||
)~~~");
|
||
|
||
// 3.9.2. [[Set]], https://heycam.github.io/webidl/#legacy-platform-object-set
|
||
scoped_generator.append(R"~~~(
|
||
JS::ThrowCompletionOr<bool> @class_name@::internal_set(JS::PropertyName const& property_name, JS::Value value, JS::Value receiver)
|
||
{
|
||
auto& vm = this->vm();
|
||
[[maybe_unused]] auto& global_object = this->global_object();
|
||
)~~~");
|
||
|
||
// The step 1 if statement will be empty if the interface has no setters, so don't generate the if statement if there's no setters.
|
||
if (interface.named_property_setter.has_value() || interface.indexed_property_setter.has_value()) {
|
||
scoped_generator.append(R"~~~(
|
||
// 1. If O and Receiver are the same object, then:
|
||
if (JS::same_value(this, receiver)) {
|
||
)~~~");
|
||
|
||
// 1. If O implements an interface with an indexed property setter...
|
||
if (interface.indexed_property_setter.has_value()) {
|
||
// ...and P is an array index, then:
|
||
scoped_generator.append(R"~~~(
|
||
if (IDL::is_an_array_index(global_object, property_name)) {
|
||
// 1. Invoke the indexed property setter on O with P and V.
|
||
invoke_indexed_property_setter(global_object, impl(), property_name, value);
|
||
if (auto* exception = vm.exception())
|
||
return JS::throw_completion(exception->value());
|
||
|
||
// 2. Return true.
|
||
return true;
|
||
}
|
||
)~~~");
|
||
}
|
||
|
||
// 2. If O implements an interface with a named property setter...
|
||
if (interface.named_property_setter.has_value()) {
|
||
// ... and Type(P) is String, then:
|
||
scoped_generator.append(R"~~~(
|
||
if (property_name.is_string()) {
|
||
// 1. Invoke the named property setter on O with P and V.
|
||
invoke_named_property_setter(global_object, impl(), property_name.as_string(), value);
|
||
if (auto* exception = vm.exception())
|
||
return JS::throw_completion(exception->value());
|
||
|
||
// 2. Return true.
|
||
return true;
|
||
}
|
||
)~~~");
|
||
}
|
||
|
||
scoped_generator.append(R"~~~(
|
||
}
|
||
)~~~");
|
||
}
|
||
|
||
scoped_generator.append(R"~~~(
|
||
// 2. Let ownDesc be LegacyPlatformObjectGetOwnProperty(O, P, true).
|
||
auto own_descriptor = legacy_platform_object_get_own_property_for_set_slot(property_name);
|
||
if (auto* exception = vm.exception())
|
||
return JS::throw_completion(exception->value());
|
||
|
||
// 3. Perform ? OrdinarySetWithOwnDescriptor(O, P, V, Receiver, ownDesc).
|
||
// NOTE: The spec says "perform" instead of "return", meaning nothing will be returned on this path according to the spec, which isn't possible to do.
|
||
// Let's treat it as though it says "return" instead of "perform".
|
||
auto result = ordinary_set_with_own_descriptor(property_name, value, receiver, own_descriptor);
|
||
if (auto* exception = vm.exception())
|
||
return JS::throw_completion(exception->value());
|
||
return result;
|
||
}
|
||
)~~~");
|
||
|
||
// 3.9.3. [[DefineOwnProperty]], https://heycam.github.io/webidl/#legacy-platform-object-defineownproperty
|
||
scoped_generator.append(R"~~~(
|
||
JS::ThrowCompletionOr<bool> @class_name@::internal_define_own_property(JS::PropertyName const& property_name, JS::PropertyDescriptor const& property_descriptor)
|
||
{
|
||
[[maybe_unused]] auto& vm = this->vm();
|
||
auto& global_object = this->global_object();
|
||
)~~~");
|
||
|
||
// 1. If O supports indexed properties...
|
||
if (interface.supports_indexed_properties()) {
|
||
// ...and P is an array index, then:
|
||
scoped_generator.append(R"~~~(
|
||
if (IDL::is_an_array_index(global_object, property_name)) {
|
||
// 1. If the result of calling IsDataDescriptor(Desc) is false, then return false.
|
||
if (!property_descriptor.is_data_descriptor())
|
||
return false;
|
||
)~~~");
|
||
|
||
// 2. If O does not implement an interface with an indexed property setter, then return false.
|
||
if (!interface.indexed_property_setter.has_value()) {
|
||
scoped_generator.append(R"~~~(
|
||
return false;
|
||
)~~~");
|
||
} else {
|
||
scoped_generator.append(R"~~~(
|
||
// 3. Invoke the indexed property setter on O with P and Desc.[[Value]].
|
||
invoke_indexed_property_setter(global_object, impl(), property_name, *property_descriptor.value);
|
||
if (auto* exception = vm.exception())
|
||
return JS::throw_completion(exception->value());
|
||
|
||
// 4. Return true.
|
||
return true;
|
||
)~~~");
|
||
}
|
||
|
||
scoped_generator.append(R"~~~(
|
||
}
|
||
)~~~");
|
||
}
|
||
|
||
// 2. If O supports named properties, O does not implement an interface with the [Global] extended attribute,
|
||
if (interface.supports_named_properties() && !interface.extended_attributes.contains("Global")) {
|
||
// Type(P) is String,
|
||
// FIXME: and P is not an unforgeable property name of O, then:
|
||
// FIXME: It's not necessary to determine "creating" if the named property setter specifies an identifier.
|
||
// Try avoiding it somehow, e.g. by enforcing supported_property_names doesn't have side effects so it can be skipped.
|
||
scoped_generator.append(R"~~~(
|
||
if (property_name.is_string()) {
|
||
auto& property_name_as_string = property_name.as_string();
|
||
|
||
// 1. Let creating be true if P is not a supported property name, and false otherwise.
|
||
// NOTE: This is in it's own variable to enforce the type.
|
||
// FIXME: Can this throw?
|
||
Vector<String> supported_property_names = impl().supported_property_names();
|
||
[[maybe_unused]] bool creating = !supported_property_names.contains_slow(property_name_as_string);
|
||
)~~~");
|
||
|
||
// 2. If O implements an interface with the [LegacyOverrideBuiltIns] extended attribute or O does not have an own property named P, then:
|
||
if (!interface.extended_attributes.contains("LegacyOverrideBuiltIns")) {
|
||
scoped_generator.append(R"~~~(
|
||
// NOTE: This has to be done manually instead of using Object::has_own_property, as that would use the overrided internal_get_own_property.
|
||
auto own_property_named_p = TRY(Object::internal_get_own_property(property_name));
|
||
|
||
if (!own_property_named_p.has_value()))~~~");
|
||
}
|
||
|
||
// A scope is created regardless of the fact that the interface may have [LegacyOverrideBuiltIns] specified to prevent code duplication.
|
||
scoped_generator.append(R"~~~(
|
||
{
|
||
)~~~");
|
||
|
||
// 1. If creating is false and O does not implement an interface with a named property setter, then return false.
|
||
if (!interface.named_property_setter.has_value()) {
|
||
scoped_generator.append(R"~~~(
|
||
if (!creating)
|
||
return false;
|
||
)~~~");
|
||
} else {
|
||
// 2. If O implements an interface with a named property setter, then:
|
||
scoped_generator.append(R"~~~(
|
||
// 1. If the result of calling IsDataDescriptor(Desc) is false, then return false.
|
||
if (!property_descriptor.is_data_descriptor())
|
||
return false;
|
||
|
||
// 2. Invoke the named property setter on O with P and Desc.[[Value]].
|
||
invoke_named_property_setter(global_object, impl(), property_name_as_string, *property_descriptor.value);
|
||
if (auto* exception = vm.exception())
|
||
return JS::throw_completion(exception->value());
|
||
|
||
// 3. Return true.
|
||
return true;
|
||
)~~~");
|
||
}
|
||
|
||
scoped_generator.append(R"~~~(
|
||
}
|
||
)~~~");
|
||
|
||
scoped_generator.append(R"~~~(
|
||
}
|
||
)~~~");
|
||
}
|
||
|
||
// 3. If O does not implement an interface with the [Global] extended attribute, then set Desc.[[Configurable]] to true.
|
||
if (!interface.extended_attributes.contains("Global")) {
|
||
scoped_generator.append(R"~~~(
|
||
// property_descriptor is a const&, thus we need to create a copy here to set [[Configurable]]
|
||
JS::PropertyDescriptor descriptor_copy(property_descriptor);
|
||
descriptor_copy.configurable = true;
|
||
|
||
// 4. Return OrdinaryDefineOwnProperty(O, P, Desc).
|
||
return Object::internal_define_own_property(property_name, descriptor_copy);
|
||
)~~~");
|
||
} else {
|
||
scoped_generator.append(R"~~~(
|
||
// 4. Return OrdinaryDefineOwnProperty(O, P, Desc).
|
||
return Object::internal_define_own_property(property_name, property_descriptor);
|
||
)~~~");
|
||
}
|
||
|
||
scoped_generator.append(R"~~~(
|
||
}
|
||
)~~~");
|
||
|
||
// 3.9.4. [[Delete]], https://heycam.github.io/webidl/#legacy-platform-object-delete
|
||
scoped_generator.append(R"~~~(
|
||
JS::ThrowCompletionOr<bool> @class_name@::internal_delete(JS::PropertyName const& property_name)
|
||
{
|
||
[[maybe_unused]] auto& vm = this->vm();
|
||
auto& global_object = this->global_object();
|
||
)~~~");
|
||
|
||
// 1. If O supports indexed properties...
|
||
if (interface.supports_indexed_properties()) {
|
||
// ...and P is an array index, then:
|
||
scoped_generator.append(R"~~~(
|
||
if (IDL::is_an_array_index(global_object, property_name)) {
|
||
// 1. Let index be the result of calling ToUint32(P).
|
||
u32 index = property_name.as_number();
|
||
|
||
// 2. If index is not a supported property index, then return true.
|
||
// FIXME: Can this throw?
|
||
if (!impl().is_supported_property_index(index))
|
||
return true;
|
||
|
||
// 3. Return false.
|
||
return false;
|
||
}
|
||
)~~~");
|
||
}
|
||
|
||
// 2. If O supports named properties, O does not implement an interface with the [Global] extended attribute...
|
||
if (interface.supports_named_properties() && !interface.extended_attributes.contains("Global")) {
|
||
// ...and the result of calling the named property visibility algorithm with property name P and object O is true, then:
|
||
scoped_generator.append(R"~~~(
|
||
if (is_named_property_exposed_on_object(property_name)) {
|
||
)~~~");
|
||
|
||
// 1. If O does not implement an interface with a named property deleter, then return false.
|
||
if (!interface.named_property_deleter.has_value()) {
|
||
scoped_generator.append(R"~~~(
|
||
return false;
|
||
)~~~");
|
||
} else {
|
||
// 2. Let operation be the operation used to declare the named property deleter. (NOTE: Not necessary)
|
||
|
||
scoped_generator.append(R"~~~(
|
||
// FIXME: It's unfortunate that this is done twice, once in is_named_property_exposed_on_object and here.
|
||
auto property_name_string = property_name.to_string();
|
||
)~~~");
|
||
|
||
// 3. If operation was defined without an identifier, then:
|
||
if (interface.named_property_deleter->name.is_empty()) {
|
||
scoped_generator.append(R"~~~(
|
||
// 1. Perform the steps listed in the interface description to delete an existing named property with P as the name.
|
||
auto result = throw_dom_exception_if_needed(vm, global_object, [&] { return impl().delete_existing_named_property(property_name_string); });
|
||
// FIXME: Make this nicer for ThrowCompletionOr.
|
||
if (should_return_empty(result))
|
||
return JS::throw_completion(vm.exception()->value());
|
||
|
||
bool succeeded = result.release_value();
|
||
|
||
// 2. If the steps indicated that the deletion failed, then return false.
|
||
if (!succeeded)
|
||
return false;
|
||
)~~~");
|
||
} else {
|
||
// 4. Otherwise, operation was defined with an identifier:
|
||
auto function_scoped_generator = scoped_generator.fork();
|
||
function_scoped_generator.set("function.cpp_name", make_input_acceptable_cpp(interface.named_property_deleter->name.to_snakecase()));
|
||
|
||
function_scoped_generator.append(R"~~~(
|
||
// 1. Perform method steps of operation with O as this and « P » as the argument values.
|
||
auto result = throw_dom_exception_if_needed(vm, global_object, [&] { return impl().@function.cpp_name@(property_name_string); });
|
||
// FIXME: Make this nicer for ThrowCompletionOr.
|
||
if (should_return_empty(result))
|
||
return JS::throw_completion(vm.exception()->value());
|
||
)~~~");
|
||
|
||
// 2. If operation was declared with a return type of boolean and the steps returned false, then return false.
|
||
if (interface.named_property_deleter->return_type.name == "boolean") {
|
||
function_scoped_generator.append(R"~~~(
|
||
bool succeeded = result.release_value();
|
||
if (!succeeded)
|
||
return false;
|
||
)~~~");
|
||
}
|
||
}
|
||
|
||
scoped_generator.append(R"~~~(
|
||
// 5. Return true.
|
||
return true;
|
||
)~~~");
|
||
}
|
||
|
||
scoped_generator.append(R"~~~(
|
||
}
|
||
)~~~");
|
||
}
|
||
|
||
scoped_generator.append(R"~~~(
|
||
// 3. If O has an own property with name P, then:
|
||
auto own_property_named_p_descriptor = TRY(Object::internal_get_own_property(property_name));
|
||
|
||
if (own_property_named_p_descriptor.has_value()) {
|
||
// 1. If the property is not configurable, then return false.
|
||
// 2. Otherwise, remove the property from O.
|
||
if (*own_property_named_p_descriptor->configurable)
|
||
storage_delete(property_name);
|
||
else
|
||
return false;
|
||
}
|
||
|
||
// 4. Return true.
|
||
return true;
|
||
}
|
||
)~~~");
|
||
|
||
// 3.9.5. [[PreventExtensions]], https://heycam.github.io/webidl/#legacy-platform-object-preventextensions
|
||
scoped_generator.append(R"~~~(
|
||
JS::ThrowCompletionOr<bool> @class_name@::internal_prevent_extensions()
|
||
{
|
||
// 1. Return false.
|
||
return false;
|
||
}
|
||
)~~~");
|
||
|
||
// 3.9.6. [[OwnPropertyKeys]], https://heycam.github.io/webidl/#legacy-platform-object-ownpropertykeys
|
||
scoped_generator.append(R"~~~(
|
||
JS::ThrowCompletionOr<JS::MarkedValueList> @class_name@::internal_own_property_keys() const
|
||
{
|
||
auto& vm = this->vm();
|
||
|
||
// 1. Let keys be a new empty list of ECMAScript String and Symbol values.
|
||
JS::MarkedValueList keys { heap() };
|
||
|
||
)~~~");
|
||
|
||
// 2. If O supports indexed properties, then for each index of O’s supported property indices, in ascending numerical order, append ! ToString(index) to keys.
|
||
if (interface.supports_indexed_properties()) {
|
||
scoped_generator.append(R"~~~(
|
||
for (u64 index = 0; index <= NumericLimits<u32>::max(); ++index) {
|
||
if (impl().is_supported_property_index(index))
|
||
keys.append(js_string(vm, String::number(index)));
|
||
else
|
||
break;
|
||
}
|
||
)~~~");
|
||
}
|
||
|
||
// 3. If O supports named properties, then for each P of O’s supported property names that is visible according to the named property visibility algorithm, append P to keys.
|
||
if (interface.supports_named_properties()) {
|
||
scoped_generator.append(R"~~~(
|
||
for (auto& named_property : impl().supported_property_names()) {
|
||
if (is_named_property_exposed_on_object(named_property))
|
||
keys.append(js_string(vm, named_property));
|
||
}
|
||
)~~~");
|
||
}
|
||
|
||
scoped_generator.append(R"~~~(
|
||
// 4. For each P of O’s own property keys that is a String, in ascending chronological order of property creation, append P to keys.
|
||
for (auto& it : shape().property_table_ordered()) {
|
||
if (it.key.is_string())
|
||
keys.append(it.key.to_value(vm));
|
||
}
|
||
|
||
// 5. For each P of O’s own property keys that is a Symbol, in ascending chronological order of property creation, append P to keys.
|
||
for (auto& it : shape().property_table_ordered()) {
|
||
if (it.key.is_symbol())
|
||
keys.append(it.key.to_value(vm));
|
||
}
|
||
|
||
// FIXME: 6. Assert: keys has no duplicate items.
|
||
|
||
// 7. Return keys.
|
||
return { move(keys) };
|
||
}
|
||
)~~~");
|
||
}
|
||
|
||
generator.append(R"~~~(
|
||
} // namespace Web::Bindings
|
||
)~~~");
|
||
|
||
outln("{}", generator.as_string_view());
|
||
}
|
||
|
||
static 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 <LibJS/Runtime/NativeFunction.h>
|
||
|
||
namespace Web::Bindings {
|
||
|
||
class @constructor_class@ : public JS::NativeFunction {
|
||
JS_OBJECT(@constructor_class@, JS::NativeFunction);
|
||
public:
|
||
explicit @constructor_class@(JS::GlobalObject&);
|
||
virtual void initialize(JS::GlobalObject&) override;
|
||
virtual ~@constructor_class@() override;
|
||
|
||
virtual JS::Value call() override;
|
||
virtual JS::Value construct(JS::FunctionObject& new_target) override;
|
||
|
||
private:
|
||
virtual bool has_constructor() const override { return true; }
|
||
)~~~");
|
||
|
||
for (auto& function : interface.static_functions) {
|
||
auto function_generator = generator.fork();
|
||
function_generator.set("function.name:snakecase", make_input_acceptable_cpp(function.name.to_snakecase()));
|
||
function_generator.append(R"~~~(
|
||
JS_DECLARE_NATIVE_FUNCTION(@function.name:snakecase@);
|
||
)~~~");
|
||
}
|
||
|
||
generator.append(R"~~~(
|
||
};
|
||
|
||
} // namespace Web::Bindings
|
||
)~~~");
|
||
|
||
outln("{}", generator.as_string_view());
|
||
}
|
||
|
||
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("wrapper_class", interface.wrapper_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 <LibJS/Heap/Heap.h>
|
||
#include <LibJS/Runtime/GlobalObject.h>
|
||
#include <LibWeb/Bindings/@constructor_class@.h>
|
||
#include <LibWeb/Bindings/@prototype_class@.h>
|
||
#include <LibWeb/Bindings/@wrapper_class@.h>
|
||
#include <LibWeb/Bindings/ExceptionOrUtils.h>
|
||
#include <LibWeb/Bindings/WindowObject.h>
|
||
#if __has_include(<LibWeb/Crypto/@name@.h>)
|
||
# include <LibWeb/Crypto/@name@.h>
|
||
#elif __has_include(<LibWeb/CSS/@name@.h>)
|
||
# include <LibWeb/CSS/@name@.h>
|
||
#elif __has_include(<LibWeb/DOM/@name@.h>)
|
||
# include <LibWeb/DOM/@name@.h>
|
||
#elif __has_include(<LibWeb/Geometry/@name@.h>)
|
||
# include <LibWeb/Geometry/@name@.h>
|
||
#elif __has_include(<LibWeb/HTML/@name@.h>)
|
||
# include <LibWeb/HTML/@name@.h>
|
||
#elif __has_include(<LibWeb/UIEvents/@name@.h>)
|
||
# include <LibWeb/UIEvents/@name@.h>
|
||
#elif __has_include(<LibWeb/HighResolutionTime/@name@.h>)
|
||
# include <LibWeb/HighResolutionTime/@name@.h>
|
||
#elif __has_include(<LibWeb/NavigationTiming/@name@.h>)
|
||
# include <LibWeb/NavigationTiming/@name@.h>
|
||
#elif __has_include(<LibWeb/RequestIdleCallback/@name@.h>)
|
||
# include <LibWeb/RequestIdleCallback/@name@.h>
|
||
#elif __has_include(<LibWeb/SVG/@name@.h>)
|
||
# include <LibWeb/SVG/@name@.h>
|
||
#elif __has_include(<LibWeb/XHR/@name@.h>)
|
||
# include <LibWeb/XHR/@name@.h>
|
||
#elif __has_include(<LibWeb/URL/@name@.h>)
|
||
# include <LibWeb/URL/@name@.h>
|
||
#endif
|
||
|
||
// 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::Geometry;
|
||
using namespace Web::HTML;
|
||
using namespace Web::RequestIdleCallback;
|
||
using namespace Web::UIEvents;
|
||
using namespace Web::XHR;
|
||
|
||
namespace Web::Bindings {
|
||
|
||
@constructor_class@::@constructor_class@(JS::GlobalObject& global_object)
|
||
: NativeFunction(*global_object.function_prototype())
|
||
{
|
||
}
|
||
|
||
@constructor_class@::~@constructor_class@()
|
||
{
|
||
}
|
||
|
||
JS::Value @constructor_class@::call()
|
||
{
|
||
vm().throw_exception<JS::TypeError>(global_object(), JS::ErrorType::ConstructorWithoutNew, "@name@");
|
||
return {};
|
||
}
|
||
|
||
JS::Value @constructor_class@::construct(FunctionObject&)
|
||
{
|
||
)~~~");
|
||
|
||
if (interface.constructors.is_empty()) {
|
||
// No constructor
|
||
generator.set("constructor.length", "0");
|
||
generator.append(R"~~~(
|
||
vm().throw_exception<JS::TypeError>(global_object(), JS::ErrorType::NotAConstructor, "@name@");
|
||
return {};
|
||
)~~~");
|
||
} else if (interface.constructors.size() == 1) {
|
||
// Single constructor
|
||
|
||
auto& constructor = interface.constructors[0];
|
||
generator.set("constructor.length", String::number(constructor.length()));
|
||
|
||
generator.append(R"~~~(
|
||
[[maybe_unused]] auto& vm = this->vm();
|
||
auto& global_object = this->global_object();
|
||
|
||
auto& window = static_cast<WindowObject&>(global_object);
|
||
)~~~");
|
||
|
||
if (!constructor.parameters.is_empty()) {
|
||
generate_argument_count_check(generator, constructor);
|
||
|
||
StringBuilder arguments_builder;
|
||
generate_arguments(generator, constructor.parameters, arguments_builder, interface.dictionaries);
|
||
generator.set(".constructor_arguments", arguments_builder.string_view());
|
||
|
||
generator.append(R"~~~(
|
||
auto impl = throw_dom_exception_if_needed(vm, global_object, [&] { return @fully_qualified_name@::create_with_global_object(window, @.constructor_arguments@); });
|
||
)~~~");
|
||
} else {
|
||
generator.append(R"~~~(
|
||
auto impl = throw_dom_exception_if_needed(vm, global_object, [&] { return @fully_qualified_name@::create_with_global_object(window); });
|
||
)~~~");
|
||
}
|
||
generator.append(R"~~~(
|
||
if (should_return_empty(impl))
|
||
return JS::Value();
|
||
return @wrapper_class@::create(global_object, impl.release_value());
|
||
)~~~");
|
||
} else {
|
||
// Multiple constructor overloads - can't do that yet.
|
||
TODO();
|
||
}
|
||
|
||
generator.append(R"~~~(
|
||
}
|
||
|
||
void @constructor_class@::initialize(JS::GlobalObject& global_object)
|
||
{
|
||
auto& vm = this->vm();
|
||
auto& window = static_cast<WindowObject&>(global_object);
|
||
[[maybe_unused]] u8 default_attributes = JS::Attribute::Enumerable;
|
||
|
||
NativeFunction::initialize(global_object);
|
||
define_direct_property(vm.names.prototype, &window.ensure_web_prototype<@prototype_class@>("@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);
|
||
constant_generator.set("constant.value", constant.value);
|
||
|
||
constant_generator.append(R"~~~(
|
||
define_direct_property("@constant.name@", JS::Value((i32)@constant.value@), JS::Attribute::Enumerable);
|
||
)~~~");
|
||
}
|
||
|
||
// https://heycam.github.io/webidl/#es-operations
|
||
for (auto& function : interface.static_functions) {
|
||
auto function_generator = generator.fork();
|
||
function_generator.set("function.name", function.name);
|
||
function_generator.set("function.name:snakecase", make_input_acceptable_cpp(function.name.to_snakecase()));
|
||
function_generator.set("function.length", String::number(function.length()));
|
||
|
||
function_generator.append(R"~~~(
|
||
define_native_function("@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.dictionaries);
|
||
}
|
||
|
||
generator.append(R"~~~(
|
||
} // namespace Web::Bindings
|
||
)~~~");
|
||
|
||
outln("{}", generator.as_string_view());
|
||
}
|
||
|
||
static 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 <LibJS/Runtime/Object.h>
|
||
|
||
namespace Web::Bindings {
|
||
|
||
class @prototype_class@ : public JS::Object {
|
||
JS_OBJECT(@prototype_class@, JS::Object);
|
||
public:
|
||
explicit @prototype_class@(JS::GlobalObject&);
|
||
virtual void initialize(JS::GlobalObject&) override;
|
||
virtual ~@prototype_class@() override;
|
||
private:
|
||
)~~~");
|
||
|
||
for (auto& function : interface.functions) {
|
||
auto function_generator = generator.fork();
|
||
function_generator.set("function.name:snakecase", make_input_acceptable_cpp(function.name.to_snakecase()));
|
||
function_generator.append(R"~~~(
|
||
JS_DECLARE_NATIVE_FUNCTION(@function.name:snakecase@);
|
||
)~~~");
|
||
}
|
||
|
||
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"~~~(
|
||
};
|
||
|
||
} // namespace Web::Bindings
|
||
)~~~");
|
||
|
||
outln("{}", generator.as_string_view());
|
||
}
|
||
|
||
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("wrapper_class", interface.wrapper_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", String::formatted("{}Iterator", interface.name));
|
||
generator.set("iterator_wrapper_class", String::formatted("{}IteratorWrapper", interface.name));
|
||
generator.append(R"~~~(
|
||
#include <LibWeb/Bindings/@iterator_wrapper_class@.h>
|
||
)~~~");
|
||
}
|
||
|
||
generator.append(R"~~~(
|
||
#include <AK/Function.h>
|
||
#include <LibJS/Runtime/Array.h>
|
||
#include <LibJS/Runtime/Error.h>
|
||
#include <LibJS/Runtime/FunctionObject.h>
|
||
#include <LibJS/Runtime/GlobalObject.h>
|
||
#include <LibJS/Runtime/TypedArray.h>
|
||
#include <LibWeb/Bindings/@prototype_class@.h>
|
||
#include <LibWeb/Bindings/@wrapper_class@.h>
|
||
#include <LibWeb/Bindings/AbortSignalWrapper.h>
|
||
#include <LibWeb/Bindings/CSSRuleListWrapper.h>
|
||
#include <LibWeb/Bindings/CSSRuleWrapper.h>
|
||
#include <LibWeb/Bindings/CSSRuleWrapperFactory.h>
|
||
#include <LibWeb/Bindings/CSSStyleDeclarationWrapper.h>
|
||
#include <LibWeb/Bindings/CSSStyleSheetWrapper.h>
|
||
#include <LibWeb/Bindings/CanvasRenderingContext2DWrapper.h>
|
||
#include <LibWeb/Bindings/CommentWrapper.h>
|
||
#include <LibWeb/Bindings/DOMImplementationWrapper.h>
|
||
#include <LibWeb/Bindings/DOMRectWrapper.h>
|
||
#include <LibWeb/Bindings/DOMStringMapWrapper.h>
|
||
#include <LibWeb/Bindings/DocumentFragmentWrapper.h>
|
||
#include <LibWeb/Bindings/DocumentTypeWrapper.h>
|
||
#include <LibWeb/Bindings/DocumentWrapper.h>
|
||
#include <LibWeb/Bindings/EventTargetWrapperFactory.h>
|
||
#include <LibWeb/Bindings/EventWrapper.h>
|
||
#include <LibWeb/Bindings/EventWrapperFactory.h>
|
||
#include <LibWeb/Bindings/ExceptionOrUtils.h>
|
||
#include <LibWeb/Bindings/HTMLCanvasElementWrapper.h>
|
||
#include <LibWeb/Bindings/HTMLCollectionWrapper.h>
|
||
#include <LibWeb/Bindings/HTMLFormElementWrapper.h>
|
||
#include <LibWeb/Bindings/HTMLHeadElementWrapper.h>
|
||
#include <LibWeb/Bindings/HTMLImageElementWrapper.h>
|
||
#include <LibWeb/Bindings/HTMLTableCaptionElementWrapper.h>
|
||
#include <LibWeb/Bindings/HTMLTableSectionElementWrapper.h>
|
||
#include <LibWeb/Bindings/ImageDataWrapper.h>
|
||
#include <LibWeb/Bindings/LocationObject.h>
|
||
#include <LibWeb/Bindings/MessagePortWrapper.h>
|
||
#include <LibWeb/Bindings/NodeListWrapper.h>
|
||
#include <LibWeb/Bindings/NodeWrapperFactory.h>
|
||
#include <LibWeb/Bindings/PerformanceTimingWrapper.h>
|
||
#include <LibWeb/Bindings/RangeWrapper.h>
|
||
#include <LibWeb/Bindings/StyleSheetListWrapper.h>
|
||
#include <LibWeb/Bindings/TextWrapper.h>
|
||
#include <LibWeb/Bindings/URLSearchParamsWrapper.h>
|
||
#include <LibWeb/Bindings/WindowObject.h>
|
||
#include <LibWeb/DOM/Element.h>
|
||
#include <LibWeb/DOM/EventListener.h>
|
||
#include <LibWeb/DOM/Range.h>
|
||
#include <LibWeb/DOM/Window.h>
|
||
#include <LibWeb/HTML/EventHandler.h>
|
||
#include <LibWeb/HTML/HTMLElement.h>
|
||
#include <LibWeb/NavigationTiming/PerformanceTiming.h>
|
||
#include <LibWeb/Origin.h>
|
||
|
||
#if __has_include(<LibWeb/Bindings/@prototype_base_class@.h>)
|
||
# include <LibWeb/Bindings/@prototype_base_class@.h>
|
||
#endif
|
||
#if __has_include(<LibWeb/Crypto/@name@.h>)
|
||
# include <LibWeb/Crypto/@name@.h>
|
||
#elif __has_include(<LibWeb/CSS/@name@.h>)
|
||
# include <LibWeb/CSS/@name@.h>
|
||
#elif __has_include(<LibWeb/DOM/@name@.h>)
|
||
# include <LibWeb/DOM/@name@.h>
|
||
#elif __has_include(<LibWeb/Geometry/@name@.h>)
|
||
# include <LibWeb/Geometry/@name@.h>
|
||
#elif __has_include(<LibWeb/HTML/@name@.h>)
|
||
# include <LibWeb/HTML/@name@.h>
|
||
#elif __has_include(<LibWeb/UIEvents/@name@.h>)
|
||
# include <LibWeb/UIEvents/@name@.h>
|
||
#elif __has_include(<LibWeb/HighResolutionTime/@name@.h>)
|
||
# include <LibWeb/HighResolutionTime/@name@.h>
|
||
#elif __has_include(<LibWeb/NavigationTiming/@name@.h>)
|
||
# include <LibWeb/NavigationTiming/@name@.h>
|
||
#elif __has_include(<LibWeb/RequestIdleCallback/@name@.h>)
|
||
# include <LibWeb/RequestIdleCallback/@name@.h>
|
||
#elif __has_include(<LibWeb/SVG/@name@.h>)
|
||
# include <LibWeb/SVG/@name@.h>
|
||
#elif __has_include(<LibWeb/XHR/@name@.h>)
|
||
# include <LibWeb/XHR/@name@.h>
|
||
#elif __has_include(<LibWeb/URL/@name@.h>)
|
||
# include <LibWeb/URL/@name@.h>
|
||
#endif
|
||
|
||
)~~~");
|
||
|
||
if (interface.pair_iterator_types.has_value()) {
|
||
generator.append(R"~~~(
|
||
#if __has_include(<LibWeb/CSS/@iterator_name@.h>)
|
||
# include <LibWeb/CSS/@iterator_name@.h>
|
||
#elif __has_include(<LibWeb/DOM/@iterator_name@.h>)
|
||
# include <LibWeb/DOM/@iterator_name@.h>
|
||
#elif __has_include(<LibWeb/Geometry/@iterator_name@.h>)
|
||
# include <LibWeb/Geometry/@iterator_name@.h>
|
||
#elif __has_include(<LibWeb/HTML/@iterator_name@.h>)
|
||
# include <LibWeb/HTML/@iterator_name@.h>
|
||
#elif __has_include(<LibWeb/UIEvents/@iterator_name@.h>)
|
||
# include <LibWeb/UIEvents/@iterator_name@.h>
|
||
#elif __has_include(<LibWeb/HighResolutionTime/@iterator_name@.h>)
|
||
# include <LibWeb/HighResolutionTime/@iterator_name@.h>
|
||
#elif __has_include(<LibWeb/NavigationTiming/@iterator_name@.h>)
|
||
# include <LibWeb/NavigationTiming/@iterator_name@.h>
|
||
#elif __has_include(<LibWeb/RequestIdleCallback/@iterator_name@.h>)
|
||
# include <LibWeb/RequestIdleCallback/@iterator_name@.h>
|
||
#elif __has_include(<LibWeb/SVG/@iterator_name@.h>)
|
||
# include <LibWeb/SVG/@iterator_name@.h>
|
||
#elif __has_include(<LibWeb/XHR/@iterator_name@.h>)
|
||
# include <LibWeb/XHR/@iterator_name@.h>
|
||
#elif __has_include(<LibWeb/URL/@iterator_name@.h>)
|
||
# include <LibWeb/URL/@iterator_name@.h>
|
||
#endif
|
||
)~~~");
|
||
}
|
||
|
||
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::Geometry;
|
||
using namespace Web::HTML;
|
||
using namespace Web::NavigationTiming;
|
||
using namespace Web::RequestIdleCallback;
|
||
using namespace Web::XHR;
|
||
using namespace Web::URL;
|
||
|
||
namespace Web::Bindings {
|
||
|
||
@prototype_class@::@prototype_class@([[maybe_unused]] JS::GlobalObject& global_object))~~~");
|
||
if (interface.name == "DOMException") {
|
||
// https://heycam.github.io/webidl/#es-DOMException-specialness
|
||
// Object.getPrototypeOf(DOMException.prototype) === Error.prototype
|
||
generator.append(R"~~~(
|
||
: Object(*global_object.error_prototype())
|
||
)~~~");
|
||
} else if (!interface.parent_name.is_empty()) {
|
||
generator.append(R"~~~(
|
||
: Object(static_cast<WindowObject&>(global_object).ensure_web_prototype<@prototype_base_class@>("@parent_name@"))
|
||
)~~~");
|
||
} else {
|
||
generator.append(R"~~~(
|
||
: Object(*global_object.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::GlobalObject& global_object)
|
||
{
|
||
[[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(global_object, nullptr);
|
||
)~~~");
|
||
}
|
||
|
||
// https://heycam.github.io/webidl/#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("@attribute.name@", @attribute.getter_callback@, @attribute.setter_callback@, default_attributes);
|
||
)~~~");
|
||
}
|
||
|
||
// https://heycam.github.io/webidl/#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);
|
||
constant_generator.set("constant.value", constant.value);
|
||
|
||
constant_generator.append(R"~~~(
|
||
define_direct_property("@constant.name@", JS::Value((i32)@constant.value@), JS::Attribute::Enumerable);
|
||
)~~~");
|
||
}
|
||
|
||
// https://heycam.github.io/webidl/#es-operations
|
||
for (auto& function : interface.functions) {
|
||
auto function_generator = generator.fork();
|
||
function_generator.set("function.name", function.name);
|
||
function_generator.set("function.name:snakecase", make_input_acceptable_cpp(function.name.to_snakecase()));
|
||
function_generator.set("function.length", String::number(function.length()));
|
||
|
||
if (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("@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("toString", to_string, 0, default_attributes);
|
||
)~~~");
|
||
}
|
||
|
||
// https://heycam.github.io/webidl/#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(), global_object.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, global_object.array_prototype()->get_without_side_effects(vm.names.entries), default_attributes);
|
||
define_direct_property(vm.names.keys, global_object.array_prototype()->get_without_side_effects(vm.names.keys), default_attributes);
|
||
define_direct_property(vm.names.values, global_object.array_prototype()->get_without_side_effects(vm.names.values), default_attributes);
|
||
define_direct_property(vm.names.forEach, global_object.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(vm.names.entries, entries, 0, default_attributes);
|
||
define_native_function(vm.names.forEach, for_each, 1, default_attributes);
|
||
define_native_function(vm.names.keys, keys, 0, default_attributes);
|
||
define_native_function(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"~~~(
|
||
Object::initialize(global_object);
|
||
}
|
||
)~~~");
|
||
|
||
if (!interface.attributes.is_empty() || !interface.functions.is_empty() || interface.has_stringifier) {
|
||
generator.append(R"~~~(
|
||
static @fully_qualified_name@* impl_from(JS::VM& vm, JS::GlobalObject& global_object)
|
||
{
|
||
auto* this_object = vm.this_value(global_object).to_object(global_object);
|
||
if (!this_object)
|
||
return {};
|
||
)~~~");
|
||
|
||
if (interface.name == "EventTarget") {
|
||
generator.append(R"~~~(
|
||
if (is<WindowObject>(this_object)) {
|
||
return &static_cast<WindowObject*>(this_object)->impl();
|
||
}
|
||
)~~~");
|
||
}
|
||
|
||
generator.append(R"~~~(
|
||
if (!is<@wrapper_class@>(this_object)) {
|
||
vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::NotAnObjectOfType, "@fully_qualified_name@");
|
||
return nullptr;
|
||
}
|
||
|
||
return &static_cast<@wrapper_class@*>(this_object)->impl();
|
||
}
|
||
)~~~");
|
||
}
|
||
|
||
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);
|
||
attribute_generator.set("attribute.name:snakecase", attribute.name.to_snakecase());
|
||
|
||
if (attribute.extended_attributes.contains("ImplementedAs")) {
|
||
auto implemented_as = attribute.extended_attributes.get("ImplementedAs").value();
|
||
attribute_generator.set("attribute.cpp_getter_name", implemented_as);
|
||
} else {
|
||
attribute_generator.set("attribute.cpp_getter_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@)
|
||
{
|
||
auto* impl = impl_from(vm, global_object);
|
||
if (!impl)
|
||
return {};
|
||
)~~~");
|
||
|
||
if (attribute.extended_attributes.contains("ReturnNullIfCrossOrigin")) {
|
||
attribute_generator.append(R"~~~(
|
||
if (!impl->may_access_from_origin(static_cast<WindowObject&>(global_object).origin()))
|
||
return JS::js_null();
|
||
)~~~");
|
||
}
|
||
|
||
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 = impl->@attribute.cpp_getter_name@();
|
||
)~~~");
|
||
}
|
||
|
||
generate_return_statement(generator, attribute.type);
|
||
|
||
attribute_generator.append(R"~~~(
|
||
}
|
||
)~~~");
|
||
|
||
if (!attribute.readonly) {
|
||
attribute_generator.append(R"~~~(
|
||
JS_DEFINE_NATIVE_FUNCTION(@prototype_class@::@attribute.setter_callback@)
|
||
{
|
||
auto* impl = impl_from(vm, global_object);
|
||
if (!impl)
|
||
return {};
|
||
|
||
auto value = vm.argument(0);
|
||
)~~~");
|
||
|
||
generate_to_cpp(generator, attribute, "value", "", "cpp_value", interface.dictionaries, false, attribute.extended_attributes.contains("LegacyNullToEmptyString"));
|
||
|
||
if (attribute.extended_attributes.contains("Reflect")) {
|
||
if (attribute.type.name != "boolean") {
|
||
attribute_generator.append(R"~~~(
|
||
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
|
||
impl->set_attribute(HTML::AttributeNames::@attribute.reflect_name@, String::empty());
|
||
)~~~");
|
||
}
|
||
} else {
|
||
attribute_generator.append(R"~~~(
|
||
auto result = throw_dom_exception_if_needed(vm, global_object, [&] { return impl->set_@attribute.name:snakecase@(cpp_value); });
|
||
|
||
if (should_return_empty(result))
|
||
return JS::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.dictionaries);
|
||
}
|
||
|
||
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)
|
||
{
|
||
auto* impl = impl_from(vm, global_object);
|
||
if (!impl)
|
||
return {};
|
||
|
||
)~~~");
|
||
if (interface.stringifier_attribute.has_value()) {
|
||
stringifier_generator.append(R"~~~(
|
||
auto retval = impl->@attribute.cpp_getter_name@();
|
||
)~~~");
|
||
} else {
|
||
stringifier_generator.append(R"~~~(
|
||
auto result = throw_dom_exception_if_needed(vm, global_object, [&] { return impl->to_string(); });
|
||
|
||
if (should_return_empty(result))
|
||
return JS::Value();
|
||
|
||
auto retval = result.release_value();
|
||
)~~~");
|
||
}
|
||
stringifier_generator.append(R"~~~(
|
||
|
||
return JS::js_string(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 = impl_from(vm, global_object);
|
||
if (!impl)
|
||
return {};
|
||
|
||
return wrap(global_object, @iterator_name@::create(*impl, Object::PropertyKind::KeyAndValue));
|
||
}
|
||
|
||
JS_DEFINE_NATIVE_FUNCTION(@prototype_class@::for_each)
|
||
{
|
||
auto* impl = impl_from(vm, global_object);
|
||
if (!impl)
|
||
return {};
|
||
|
||
auto callback = vm.argument(0);
|
||
if (!callback.is_function()) {
|
||
vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::NotAFunction, callback.to_string_without_side_effects());
|
||
return {};
|
||
}
|
||
|
||
auto this_value = vm.this_value(global_object);
|
||
impl->for_each([&](auto key, auto value) {
|
||
)~~~");
|
||
generate_variable_statement(iterator_generator, "wrapped_key", interface.pair_iterator_types->get<0>(), "key");
|
||
generate_variable_statement(iterator_generator, "wrapped_value", interface.pair_iterator_types->get<1>(), "value");
|
||
iterator_generator.append(R"~~~(
|
||
auto result = vm.call(callback.as_function(), vm.argument(1), wrapped_value, wrapped_key, this_value);
|
||
return result.is_error() ? IterationDecision::Break : IterationDecision::Continue;
|
||
});
|
||
if (vm.exception())
|
||
return {};
|
||
|
||
return JS::js_undefined();
|
||
}
|
||
|
||
JS_DEFINE_NATIVE_FUNCTION(@prototype_class@::keys)
|
||
{
|
||
auto* impl = impl_from(vm, global_object);
|
||
if (!impl)
|
||
return {};
|
||
|
||
return wrap(global_object, @iterator_name@::create(*impl, Object::PropertyKind::Key));
|
||
}
|
||
|
||
JS_DEFINE_NATIVE_FUNCTION(@prototype_class@::values)
|
||
{
|
||
auto* impl = impl_from(vm, global_object);
|
||
if (!impl)
|
||
return {};
|
||
|
||
return wrap(global_object, @iterator_name@::create(*impl, Object::PropertyKind::Value));
|
||
}
|
||
)~~~");
|
||
}
|
||
|
||
generator.append(R"~~~(
|
||
} // namespace Web::Bindings
|
||
)~~~");
|
||
|
||
outln("{}", generator.as_string_view());
|
||
}
|
||
|
||
static void generate_iterator_header(IDL::Interface const& interface)
|
||
{
|
||
VERIFY(interface.pair_iterator_types.has_value());
|
||
StringBuilder builder;
|
||
SourceGenerator generator { builder };
|
||
|
||
generator.set("name", String::formatted("{}Iterator", interface.name));
|
||
generator.set("fully_qualified_name", String::formatted("{}Iterator", interface.fully_qualified_name));
|
||
generator.set("wrapper_class", String::formatted("{}IteratorWrapper", interface.name));
|
||
|
||
generator.append(R"~~~(
|
||
#pragma once
|
||
|
||
#include <LibWeb/Bindings/Wrapper.h>
|
||
|
||
// FIXME: This is very strange.
|
||
#if __has_include(<LibWeb/Crypto/@name@.h>)
|
||
# include <LibWeb/Crypto/@name@.h>
|
||
#elif __has_include(<LibWeb/CSS/@name@.h>)
|
||
# include <LibWeb/CSS/@name@.h>
|
||
#elif __has_include(<LibWeb/DOM/@name@.h>)
|
||
# include <LibWeb/DOM/@name@.h>
|
||
#elif __has_include(<LibWeb/Geometry/@name@.h>)
|
||
# include <LibWeb/Geometry/@name@.h>
|
||
#elif __has_include(<LibWeb/HTML/@name@.h>)
|
||
# include <LibWeb/HTML/@name@.h>
|
||
#elif __has_include(<LibWeb/UIEvents/@name@.h>)
|
||
# include <LibWeb/UIEvents/@name@.h>
|
||
#elif __has_include(<LibWeb/HighResolutionTime/@name@.h>)
|
||
# include <LibWeb/HighResolutionTime/@name@.h>
|
||
#elif __has_include(<LibWeb/NavigationTiming/@name@.h>)
|
||
# include <LibWeb/NavigationTiming/@name@.h>
|
||
#elif __has_include(<LibWeb/RequestIdleCallback/@name@.h>)
|
||
# include <LibWeb/RequestIdleCallback/@name@.h>
|
||
#elif __has_include(<LibWeb/SVG/@name@.h>)
|
||
# include <LibWeb/SVG/@name@.h>
|
||
#elif __has_include(<LibWeb/XHR/@name@.h>)
|
||
# include <LibWeb/XHR/@name@.h>
|
||
#elif __has_include(<LibWeb/URL/@name@.h>)
|
||
# include <LibWeb/URL/@name@.h>
|
||
#endif
|
||
|
||
namespace Web::Bindings {
|
||
|
||
class @wrapper_class@ : public Wrapper {
|
||
JS_OBJECT(@name@, Wrapper);
|
||
public:
|
||
static @wrapper_class@* create(JS::GlobalObject&, @fully_qualified_name@&);
|
||
|
||
@wrapper_class@(JS::GlobalObject&, @fully_qualified_name@&);
|
||
virtual void initialize(JS::GlobalObject&) override;
|
||
virtual ~@wrapper_class@() override;
|
||
|
||
@fully_qualified_name@& impl() { return *m_impl; }
|
||
@fully_qualified_name@ const& impl() const { return *m_impl; }
|
||
|
||
private:
|
||
virtual void visit_edges(Cell::Visitor&) override; // The Iterator implementation has to visit the wrapper it's iterating
|
||
|
||
NonnullRefPtr<@fully_qualified_name@> m_impl;
|
||
};
|
||
|
||
@wrapper_class@* wrap(JS::GlobalObject&, @fully_qualified_name@&);
|
||
|
||
} // namespace Web::Bindings
|
||
)~~~");
|
||
|
||
outln("{}", generator.as_string_view());
|
||
}
|
||
|
||
void generate_iterator_implementation(IDL::Interface const& interface)
|
||
{
|
||
VERIFY(interface.pair_iterator_types.has_value());
|
||
StringBuilder builder;
|
||
SourceGenerator generator { builder };
|
||
|
||
generator.set("name", String::formatted("{}Iterator", interface.name));
|
||
generator.set("fully_qualified_name", String::formatted("{}Iterator", interface.fully_qualified_name));
|
||
generator.set("prototype_class", String::formatted("{}IteratorPrototype", interface.name));
|
||
generator.set("wrapper_class", String::formatted("{}IteratorWrapper", interface.name));
|
||
|
||
generator.append(R"~~~(
|
||
#include <AK/FlyString.h>
|
||
#include <LibJS/Runtime/Array.h>
|
||
#include <LibJS/Runtime/Error.h>
|
||
#include <LibJS/Runtime/FunctionObject.h>
|
||
#include <LibJS/Runtime/GlobalObject.h>
|
||
#include <LibJS/Runtime/TypedArray.h>
|
||
#include <LibJS/Runtime/Value.h>
|
||
#include <LibWeb/Bindings/@prototype_class@.h>
|
||
#include <LibWeb/Bindings/@wrapper_class@.h>
|
||
#include <LibWeb/Bindings/IDLAbstractOperations.h>
|
||
#include <LibWeb/Bindings/WindowObject.h>
|
||
|
||
// 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::Geometry;
|
||
using namespace Web::HTML;
|
||
using namespace Web::RequestIdleCallback;
|
||
|
||
namespace Web::Bindings {
|
||
|
||
@wrapper_class@* @wrapper_class@::create(JS::GlobalObject& global_object, @fully_qualified_name@& impl)
|
||
{
|
||
return global_object.heap().allocate<@wrapper_class@>(global_object, global_object, impl);
|
||
}
|
||
|
||
@wrapper_class@::@wrapper_class@(JS::GlobalObject& global_object, @fully_qualified_name@& impl)
|
||
: Wrapper(static_cast<WindowObject&>(global_object).ensure_web_prototype<@prototype_class@>("@name@"))
|
||
, m_impl(impl)
|
||
{
|
||
}
|
||
|
||
void @wrapper_class@::initialize(JS::GlobalObject& global_object)
|
||
{
|
||
Wrapper::initialize(global_object);
|
||
}
|
||
|
||
@wrapper_class@::~@wrapper_class@()
|
||
{
|
||
}
|
||
|
||
void @wrapper_class@::visit_edges(Cell::Visitor& visitor)
|
||
{
|
||
Wrapper::visit_edges(visitor);
|
||
impl().visit_edges(visitor);
|
||
}
|
||
|
||
@wrapper_class@* wrap(JS::GlobalObject& global_object, @fully_qualified_name@& impl)
|
||
{
|
||
return static_cast<@wrapper_class@*>(wrap_impl(global_object, impl));
|
||
}
|
||
|
||
} // namespace Web::Bindings
|
||
)~~~");
|
||
|
||
outln("{}", generator.as_string_view());
|
||
}
|
||
|
||
static void generate_iterator_prototype_header(IDL::Interface const& interface)
|
||
{
|
||
VERIFY(interface.pair_iterator_types.has_value());
|
||
StringBuilder builder;
|
||
SourceGenerator generator { builder };
|
||
|
||
generator.set("prototype_class", String::formatted("{}IteratorPrototype", interface.name));
|
||
|
||
generator.append(R"~~~(
|
||
#pragma once
|
||
|
||
#include <LibJS/Runtime/Object.h>
|
||
|
||
namespace Web::Bindings {
|
||
|
||
class @prototype_class@ : public JS::Object {
|
||
JS_OBJECT(@prototype_class@, JS::Object);
|
||
public:
|
||
explicit @prototype_class@(JS::GlobalObject&);
|
||
virtual void initialize(JS::GlobalObject&) override;
|
||
virtual ~@prototype_class@() override;
|
||
|
||
private:
|
||
JS_DECLARE_NATIVE_FUNCTION(next);
|
||
};
|
||
|
||
} // namespace Web::Bindings
|
||
)~~~");
|
||
|
||
outln("{}", generator.as_string_view());
|
||
}
|
||
|
||
void generate_iterator_prototype_implementation(IDL::Interface const& interface)
|
||
{
|
||
VERIFY(interface.pair_iterator_types.has_value());
|
||
StringBuilder builder;
|
||
SourceGenerator generator { builder };
|
||
|
||
generator.set("name", String::formatted("{}Iterator", interface.name));
|
||
generator.set("prototype_class", String::formatted("{}IteratorPrototype", interface.name));
|
||
generator.set("wrapper_class", String::formatted("{}IteratorWrapper", interface.name));
|
||
generator.set("fully_qualified_name", String::formatted("{}Iterator", interface.fully_qualified_name));
|
||
|
||
generator.append(R"~~~(
|
||
#include <AK/Function.h>
|
||
#include <LibJS/Runtime/Array.h>
|
||
#include <LibJS/Runtime/Error.h>
|
||
#include <LibJS/Runtime/FunctionObject.h>
|
||
#include <LibJS/Runtime/GlobalObject.h>
|
||
#include <LibJS/Runtime/TypedArray.h>
|
||
#include <LibWeb/Bindings/@prototype_class@.h>
|
||
#include <LibWeb/Bindings/@wrapper_class@.h>
|
||
#include <LibWeb/Bindings/ExceptionOrUtils.h>
|
||
#include <LibWeb/Bindings/WindowObject.h>
|
||
|
||
#if __has_include(<LibWeb/Crypto/@name@.h>)
|
||
# include <LibWeb/Crypto/@name@.h>
|
||
#elif __has_include(<LibWeb/CSS/@name@.h>)
|
||
# include <LibWeb/CSS/@name@.h>
|
||
#elif __has_include(<LibWeb/DOM/@name@.h>)
|
||
# include <LibWeb/DOM/@name@.h>
|
||
#elif __has_include(<LibWeb/Geometry/@name@.h>)
|
||
# include <LibWeb/Geometry/@name@.h>
|
||
#elif __has_include(<LibWeb/HTML/@name@.h>)
|
||
# include <LibWeb/HTML/@name@.h>
|
||
#elif __has_include(<LibWeb/UIEvents/@name@.h>)
|
||
# include <LibWeb/UIEvents/@name@.h>
|
||
#elif __has_include(<LibWeb/HighResolutionTime/@name@.h>)
|
||
# include <LibWeb/HighResolutionTime/@name@.h>
|
||
#elif __has_include(<LibWeb/NavigationTiming/@name@.h>)
|
||
# include <LibWeb/NavigationTiming/@name@.h>
|
||
#elif __has_include(<LibWeb/RequestIdleCallback/@name@.h>)
|
||
# include <LibWeb/RequestIdleCallback/@name@.h>
|
||
#elif __has_include(<LibWeb/SVG/@name@.h>)
|
||
# include <LibWeb/SVG/@name@.h>
|
||
#elif __has_include(<LibWeb/XHR/@name@.h>)
|
||
# include <LibWeb/XHR/@name@.h>
|
||
#elif __has_include(<LibWeb/URL/@name@.h>)
|
||
# include <LibWeb/URL/@name@.h>
|
||
#endif
|
||
|
||
// 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::Geometry;
|
||
using namespace Web::HTML;
|
||
using namespace Web::NavigationTiming;
|
||
using namespace Web::RequestIdleCallback;
|
||
using namespace Web::XHR;
|
||
using namespace Web::URL;
|
||
|
||
namespace Web::Bindings {
|
||
|
||
@prototype_class@::@prototype_class@(JS::GlobalObject& global_object)
|
||
: Object(*global_object.iterator_prototype())
|
||
{
|
||
}
|
||
|
||
@prototype_class@::~@prototype_class@()
|
||
{
|
||
}
|
||
|
||
void @prototype_class@::initialize(JS::GlobalObject& global_object)
|
||
{
|
||
auto& vm = this->vm();
|
||
Object::initialize(global_object);
|
||
|
||
define_native_function(vm.names.next, next, 0, JS::Attribute::Configurable | JS::Attribute::Writable);
|
||
define_direct_property(*vm.well_known_symbol_to_string_tag(), js_string(vm, "Iterator"), JS::Attribute::Configurable);
|
||
}
|
||
|
||
static @fully_qualified_name@* impl_from(JS::VM& vm, JS::GlobalObject& global_object)
|
||
{
|
||
auto* this_object = vm.this_value(global_object).to_object(global_object);
|
||
if (!this_object)
|
||
return {};
|
||
if (!is<@wrapper_class@>(this_object)) {
|
||
vm.throw_exception<JS::TypeError>(global_object, JS::ErrorType::NotAnObjectOfType, "@fully_qualified_name@");
|
||
return nullptr;
|
||
}
|
||
return &static_cast<@wrapper_class@*>(this_object)->impl();
|
||
}
|
||
|
||
JS_DEFINE_NATIVE_FUNCTION(@prototype_class@::next)
|
||
{
|
||
auto* impl = impl_from(vm, global_object);
|
||
if (!impl)
|
||
return {};
|
||
|
||
auto result = throw_dom_exception_if_needed(vm, global_object, [&] { return impl->next(); });
|
||
|
||
if (should_return_empty(result))
|
||
return JS::Value();
|
||
|
||
return result.release_value();
|
||
}
|
||
|
||
} // namespace Web::Bindings
|
||
)~~~");
|
||
|
||
outln("{}", generator.as_string_view());
|
||
}
|