diff --git a/Libraries/LibWeb/Bindings/CanvasRenderingContext2DWrapper.cpp b/Libraries/LibWeb/Bindings/CanvasRenderingContext2DWrapper.cpp index 99b39cc95c..c5b9f50d42 100644 --- a/Libraries/LibWeb/Bindings/CanvasRenderingContext2DWrapper.cpp +++ b/Libraries/LibWeb/Bindings/CanvasRenderingContext2DWrapper.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include diff --git a/Libraries/LibWeb/Bindings/DocumentWrapper.cpp b/Libraries/LibWeb/Bindings/DocumentWrapper.cpp deleted file mode 100644 index 021e9f147b..0000000000 --- a/Libraries/LibWeb/Bindings/DocumentWrapper.cpp +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (c) 2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace Web { -namespace Bindings { - -DocumentWrapper::DocumentWrapper(JS::GlobalObject& global_object, Document& document) - : NodeWrapper(global_object, document) -{ -} - -void DocumentWrapper::initialize(JS::Interpreter& interpreter, JS::GlobalObject& global_object) -{ - NodeWrapper::initialize(interpreter, global_object); - define_native_function("getElementById", get_element_by_id, 1); - define_native_function("querySelector", query_selector, 1); - define_native_function("querySelectorAll", query_selector_all, 1); -} - -DocumentWrapper::~DocumentWrapper() -{ -} - -Document& DocumentWrapper::node() -{ - return static_cast(NodeWrapper::node()); -} - -const Document& DocumentWrapper::node() const -{ - return static_cast(NodeWrapper::node()); -} - -static Document* impl_from(JS::Interpreter& interpreter, JS::GlobalObject& global_object) -{ - auto* this_object = interpreter.this_value(global_object).to_object(interpreter, global_object); - if (!this_object) - return {}; - if (StringView("DocumentWrapper") != this_object->class_name()) { - interpreter.throw_exception(JS::ErrorType::NotA, "DocumentWrapper"); - return {}; - } - return &static_cast(this_object)->node(); -} - -JS_DEFINE_NATIVE_FUNCTION(DocumentWrapper::get_element_by_id) -{ - auto* document = impl_from(interpreter, global_object); - if (!document) - return {}; - if (!interpreter.argument_count()) - return interpreter.throw_exception(JS::ErrorType::BadArgCountOne, "getElementById"); - auto id = interpreter.argument(0).to_string(interpreter); - if (interpreter.exception()) - return {}; - auto* element = document->get_element_by_id(id); - if (!element) - return JS::js_null(); - return wrap(interpreter.heap(), const_cast(*element)); -} - -JS_DEFINE_NATIVE_FUNCTION(DocumentWrapper::query_selector) -{ - auto* document = impl_from(interpreter, global_object); - if (!document) - return {}; - if (!interpreter.argument_count()) - return interpreter.throw_exception(JS::ErrorType::BadArgCountOne, "querySelector"); - auto selector = interpreter.argument(0).to_string(interpreter); - if (interpreter.exception()) - return {}; - // FIXME: Throw if selector is invalid - auto element = document->query_selector(selector); - if (!element) - return JS::js_null(); - return wrap(interpreter.heap(), *element); -} - -JS_DEFINE_NATIVE_FUNCTION(DocumentWrapper::query_selector_all) -{ - auto* document = impl_from(interpreter, global_object); - if (!document) - return {}; - if (!interpreter.argument_count()) - return interpreter.throw_exception(JS::ErrorType::BadArgCountOne, "querySelectorAll"); - auto selector = interpreter.argument(0).to_string(interpreter); - if (interpreter.exception()) - return {}; - // FIXME: Throw if selector is invalid - auto elements = document->query_selector_all(selector); - // FIXME: This should be a static NodeList, not a plain JS::Array. - auto* node_list = JS::Array::create(global_object); - for (auto& element : elements) { - node_list->indexed_properties().append(wrap(interpreter.heap(), element)); - } - return node_list; -} - -} -} diff --git a/Libraries/LibWeb/Bindings/DocumentWrapper.h b/Libraries/LibWeb/Bindings/DocumentWrapper.h deleted file mode 100644 index 3c175f4d82..0000000000 --- a/Libraries/LibWeb/Bindings/DocumentWrapper.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#pragma once - -#include - -namespace Web { -namespace Bindings { - -class DocumentWrapper : public NodeWrapper { -public: - DocumentWrapper(JS::GlobalObject&, Document&); - virtual void initialize(JS::Interpreter&, JS::GlobalObject&) override; - virtual ~DocumentWrapper() override; - - Document& node(); - const Document& node() const; - -private: - virtual const char* class_name() const override { return "DocumentWrapper"; } - - JS_DECLARE_NATIVE_FUNCTION(get_element_by_id); - JS_DECLARE_NATIVE_FUNCTION(query_selector); - JS_DECLARE_NATIVE_FUNCTION(query_selector_all); -}; - -} -} diff --git a/Libraries/LibWeb/Bindings/ElementWrapper.cpp b/Libraries/LibWeb/Bindings/ElementWrapper.cpp index 7b18b46f66..1fca778af3 100644 --- a/Libraries/LibWeb/Bindings/ElementWrapper.cpp +++ b/Libraries/LibWeb/Bindings/ElementWrapper.cpp @@ -61,12 +61,12 @@ ElementWrapper::~ElementWrapper() Element& ElementWrapper::node() { - return static_cast(NodeWrapper::node()); + return static_cast(NodeWrapper::impl()); } const Element& ElementWrapper::node() const { - return static_cast(NodeWrapper::node()); + return static_cast(NodeWrapper::impl()); } static Element* impl_from(JS::Interpreter& interpreter, JS::GlobalObject& global_object) diff --git a/Libraries/LibWeb/Bindings/HTMLCanvasElementWrapper.cpp b/Libraries/LibWeb/Bindings/HTMLCanvasElementWrapper.cpp index fdf30bd244..1e7e904b62 100644 --- a/Libraries/LibWeb/Bindings/HTMLCanvasElementWrapper.cpp +++ b/Libraries/LibWeb/Bindings/HTMLCanvasElementWrapper.cpp @@ -57,12 +57,12 @@ HTMLCanvasElementWrapper::~HTMLCanvasElementWrapper() HTMLCanvasElement& HTMLCanvasElementWrapper::node() { - return static_cast(NodeWrapper::node()); + return static_cast(NodeWrapper::impl()); } const HTMLCanvasElement& HTMLCanvasElementWrapper::node() const { - return static_cast(NodeWrapper::node()); + return static_cast(NodeWrapper::impl()); } static HTMLCanvasElement* impl_from(JS::Interpreter& interpreter, JS::GlobalObject& global_object) diff --git a/Libraries/LibWeb/Bindings/HTMLImageElementWrapper.cpp b/Libraries/LibWeb/Bindings/HTMLImageElementWrapper.cpp index 5265d4f2e7..1bfd457349 100644 --- a/Libraries/LibWeb/Bindings/HTMLImageElementWrapper.cpp +++ b/Libraries/LibWeb/Bindings/HTMLImageElementWrapper.cpp @@ -46,12 +46,12 @@ HTMLImageElementWrapper::~HTMLImageElementWrapper() HTMLImageElement& HTMLImageElementWrapper::node() { - return static_cast(NodeWrapper::node()); + return static_cast(NodeWrapper::impl()); } const HTMLImageElement& HTMLImageElementWrapper::node() const { - return static_cast(NodeWrapper::node()); + return static_cast(NodeWrapper::impl()); } } diff --git a/Libraries/LibWeb/Bindings/NodeWrapper.cpp b/Libraries/LibWeb/Bindings/NodeWrapperFactory.cpp similarity index 77% rename from Libraries/LibWeb/Bindings/NodeWrapper.cpp rename to Libraries/LibWeb/Bindings/NodeWrapperFactory.cpp index bb822fe01c..a15708318a 100644 --- a/Libraries/LibWeb/Bindings/NodeWrapper.cpp +++ b/Libraries/LibWeb/Bindings/NodeWrapperFactory.cpp @@ -24,9 +24,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include -#include -#include +#include #include #include #include @@ -52,30 +50,5 @@ NodeWrapper* wrap(JS::Heap& heap, Node& node) return static_cast(wrap_impl(heap, node)); } -NodeWrapper::NodeWrapper(JS::GlobalObject& global_object, Node& node) - : EventTargetWrapper(global_object, node) -{ -} - -void NodeWrapper::initialize(JS::Interpreter& interpreter, JS::GlobalObject& global_object) -{ - EventTargetWrapper::initialize(interpreter, global_object); - put("nodeName", JS::js_string(interpreter.heap(), node().node_name())); -} - -NodeWrapper::~NodeWrapper() -{ -} - -Node& NodeWrapper::node() -{ - return static_cast(EventTargetWrapper::impl()); -} - -const Node& NodeWrapper::node() const -{ - return static_cast(EventTargetWrapper::impl()); -} - } } diff --git a/Libraries/LibWeb/Bindings/NodeWrapper.h b/Libraries/LibWeb/Bindings/NodeWrapperFactory.h similarity index 78% rename from Libraries/LibWeb/Bindings/NodeWrapper.h rename to Libraries/LibWeb/Bindings/NodeWrapperFactory.h index 0cbb315192..33f162bbe3 100644 --- a/Libraries/LibWeb/Bindings/NodeWrapper.h +++ b/Libraries/LibWeb/Bindings/NodeWrapperFactory.h @@ -26,24 +26,12 @@ #pragma once -#include +#include +#include namespace Web { namespace Bindings { -class NodeWrapper : public EventTargetWrapper { -public: - NodeWrapper(JS::GlobalObject&, Node&); - virtual void initialize(JS::Interpreter&, JS::GlobalObject&) override; - virtual ~NodeWrapper() override; - - Node& node(); - const Node& node() const; - -private: - virtual const char* class_name() const override { return "NodeWrapper"; } -}; - NodeWrapper* wrap(JS::Heap&, Node&); } diff --git a/Libraries/LibWeb/Bindings/WindowObject.cpp b/Libraries/LibWeb/Bindings/WindowObject.cpp index a078548d5d..79ad8096f2 100644 --- a/Libraries/LibWeb/Bindings/WindowObject.cpp +++ b/Libraries/LibWeb/Bindings/WindowObject.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 086caea7cb..3772bad89a 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -1,6 +1,7 @@ set(SOURCES Bindings/CanvasRenderingContext2DWrapper.cpp Bindings/DocumentWrapper.cpp + Bindings/DocumentWrapper.h Bindings/ElementWrapper.cpp Bindings/EventListenerWrapper.cpp Bindings/EventTargetWrapper.cpp @@ -12,6 +13,8 @@ set(SOURCES Bindings/MouseEventWrapper.cpp Bindings/NavigatorObject.cpp Bindings/NodeWrapper.cpp + Bindings/NodeWrapper.h + Bindings/NodeWrapperFactory.cpp Bindings/WindowObject.cpp Bindings/Wrappable.cpp Bindings/XMLHttpRequestConstructor.cpp @@ -123,6 +126,46 @@ set(GENERATED_SOURCES ../../Services/ProtocolServer/ProtocolServerEndpoint.h ) +add_custom_command( + OUTPUT Bindings/NodeWrapper.h + COMMAND /bin/mkdir -p Bindings + COMMAND WrapperGenerator --header ${CMAKE_CURRENT_SOURCE_DIR}/DOM/Node.idl > Bindings/NodeWrapper.h + VERBATIM + DEPENDS WrapperGenerator + MAIN_DEPENDENCY DOM/Node.idl +) +add_custom_target(generate_NodeWrapper.h DEPENDS Bindings/NodeWrapper.h) + +add_custom_command( + OUTPUT Bindings/NodeWrapper.cpp + COMMAND /bin/mkdir -p Bindings + COMMAND WrapperGenerator --implementation ${CMAKE_CURRENT_SOURCE_DIR}/DOM/Node.idl > Bindings/NodeWrapper.cpp + VERBATIM + DEPENDS WrapperGenerator + MAIN_DEPENDENCY DOM/Node.idl +) +add_custom_target(generate_NodeWrapper.cpp DEPENDS Bindings/NodeWrapper.cpp) + +add_custom_command( + OUTPUT Bindings/DocumentWrapper.h + COMMAND /bin/mkdir -p Bindings + COMMAND WrapperGenerator --header ${CMAKE_CURRENT_SOURCE_DIR}/DOM/Document.idl > Bindings/DocumentWrapper.h + VERBATIM + DEPENDS WrapperGenerator + MAIN_DEPENDENCY DOM/Document.idl +) +add_custom_target(generate_DocumentWrapper.h DEPENDS Bindings/DocumentWrapper.h) + +add_custom_command( + OUTPUT Bindings/DocumentWrapper.cpp + COMMAND /bin/mkdir -p Bindings + COMMAND WrapperGenerator --implementation ${CMAKE_CURRENT_SOURCE_DIR}/DOM/Document.idl > Bindings/DocumentWrapper.cpp + VERBATIM + DEPENDS WrapperGenerator + MAIN_DEPENDENCY DOM/Document.idl +) +add_custom_target(generate_DocumentWrapper.cpp DEPENDS Bindings/DocumentWrapper.cpp) + add_custom_command( OUTPUT CSS/PropertyID.h COMMAND /bin/mkdir -p CSS diff --git a/Libraries/LibWeb/CodeGenerators/CMakeLists.txt b/Libraries/LibWeb/CodeGenerators/CMakeLists.txt index 132ddc54e3..d0b86cd864 100644 --- a/Libraries/LibWeb/CodeGenerators/CMakeLists.txt +++ b/Libraries/LibWeb/CodeGenerators/CMakeLists.txt @@ -1,4 +1,6 @@ add_executable(Generate_CSS_PropertyID_h Generate_CSS_PropertyID_h.cpp) add_executable(Generate_CSS_PropertyID_cpp Generate_CSS_PropertyID_cpp.cpp) +add_executable(WrapperGenerator WrapperGenerator.cpp) target_link_libraries(Generate_CSS_PropertyID_h LagomCore) target_link_libraries(Generate_CSS_PropertyID_cpp LagomCore) +target_link_libraries(WrapperGenerator LagomCore) diff --git a/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp b/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp new file mode 100644 index 0000000000..eec1757eca --- /dev/null +++ b/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp @@ -0,0 +1,487 @@ +/* + * Copyright (c) 2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +static String snake_name(const StringView& camel_name) +{ + StringBuilder builder; + for (auto ch : camel_name) { + if (isupper(ch)) { + builder.append('_'); + builder.append(tolower(ch)); + } else { + builder.append(ch); + } + } + return builder.to_string(); +} + +namespace IDL { + +struct Type { + String name; + bool nullable { false }; +}; + +struct Parameter { + Type type; + String name; +}; + +struct Function { + Type return_type; + String name; + Vector parameters; + + size_t length() const + { + // FIXME: Take optional arguments into account + return parameters.size(); + } +}; + +struct Attribute { + bool readonly { false }; + Type type; + String name; + + // Added for convenience after parsing + String getter_callback_name; + String setter_callback_name; +}; + +struct Interface { + String name; + String parent_name; + + Vector attributes; + Vector functions; + + // Added for convenience after parsing + String wrapper_class; + String wrapper_base_class; +}; + +static String snake_name(const StringView& camel_name) +{ + StringBuilder builder; + for (auto ch : camel_name) { + if (isupper(ch)) { + builder.append('_'); + builder.append(tolower(ch)); + } else { + builder.append(ch); + } + } + return builder.to_string(); +} + +OwnPtr parse_interface(const StringView& input) +{ + auto interface = make(); + + size_t index = 0; + + auto peek = [&](size_t offset = 0) -> char { + if (index + offset > input.length()) + return 0; + return input[index + offset]; + }; + + auto consume = [&] { + return input[index++]; + }; + + auto consume_if = [&](auto ch) { + if (peek() == ch) { + consume(); + return true; + } + return false; + }; + + auto consume_specific = [&](char ch) { + auto consumed = consume(); + if (consumed != ch) { + dbg() << "Expected '" << ch << "' at offset " << index << " but got '" << consumed << "'"; + ASSERT_NOT_REACHED(); + } + }; + + auto consume_whitespace = [&] { + while (isspace(peek())) + consume(); + }; + + auto consume_string = [&](const StringView& string) { + for (size_t i = 0; i < string.length(); ++i) { + ASSERT(consume() == string[i]); + } + }; + + auto next_is = [&](const StringView& string) { + for (size_t i = 0; i < string.length(); ++i) { + if (peek(i) != string[i]) + return false; + } + return true; + }; + + auto consume_while = [&](auto condition) { + StringBuilder builder; + while (index < input.length() && condition(peek())) { + builder.append(consume()); + } + return builder.to_string(); + }; + + consume_string("interface"); + consume_whitespace(); + interface->name = consume_while([](auto ch) { return !isspace(ch); }); + consume_whitespace(); + if (consume_if(':')) { + consume_whitespace(); + interface->parent_name = consume_while([](auto ch) { return !isspace(ch); }); + consume_whitespace(); + } + consume_specific('{'); + + auto parse_type = [&] { + auto name = consume_while([](auto ch) { return !isspace(ch) && ch != '?'; }); + auto nullable = peek() == '?'; + if (nullable) + consume_specific('?'); + return Type { name, nullable }; + }; + + auto parse_attribute = [&] { + bool readonly = false; + if (next_is("readonly")) { + consume_string("readonly"); + readonly = true; + consume_whitespace(); + } + if (next_is("attribute")) { + consume_string("attribute"); + consume_whitespace(); + } + auto type = parse_type(); + consume_whitespace(); + auto name = consume_while([](auto ch) { return !isspace(ch) && ch != ';'; }); + consume_specific(';'); + Attribute attribute; + attribute.readonly = readonly; + attribute.type = type; + attribute.name = name; + attribute.getter_callback_name = String::format("%s_getter", snake_name(attribute.name).characters()); + attribute.setter_callback_name = String::format("%s_setter", snake_name(attribute.name).characters()); + interface->attributes.append(move(attribute)); + }; + + auto parse_function = [&] { + auto return_type = parse_type(); + consume_whitespace(); + auto name = consume_while([](auto ch) { return !isspace(ch) && ch != '('; }); + consume_specific('('); + + Vector parameters; + + for (;;) { + auto type = parse_type(); + consume_whitespace(); + auto name = consume_while([](auto ch) { return !isspace(ch) && ch != ',' && ch != ')'; }); + parameters.append({ move(type), move(name) }); + if (consume_if(')')) + break; + consume_specific(','); + consume_whitespace(); + } + + consume_specific(';'); + + interface->functions.append(Function { return_type, name, move(parameters) }); + }; + + for (;;) { + + consume_whitespace(); + + if (consume_if('}')) + break; + + if (next_is("readonly") || next_is("attribute")) { + parse_attribute(); + continue; + } + + parse_function(); + } + + interface->wrapper_class = String::format("%sWrapper", interface->name.characters()); + interface->wrapper_base_class = String::format("%sWrapper", interface->parent_name.characters()); + + return interface; +} + +} + +static void generate_header(const IDL::Interface&); +static void generate_implementation(const IDL::Interface&); + +int main(int argc, char** argv) +{ + Core::ArgsParser args_parser; + const char* path = nullptr; + bool header_mode = false; + bool 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_positional_argument(path, "IDL file", "idl-file"); + args_parser.parse(argc, argv); + + auto file_or_error = Core::File::open(path, Core::IODevice::ReadOnly); + if (file_or_error.is_error()) { + fprintf(stderr, "Cannot open %s\n", path); + return 1; + } + + auto data = file_or_error.value()->read_all(); + auto interface = IDL::parse_interface(data); + + if (!interface) { + fprintf(stderr, "Cannot parse %s\n", path); + return 1; + } + +#if 0 + dbg() << "Attributes:"; + for (auto& attribute : interface->attributes) { + dbg() << " " << (attribute.readonly ? "Readonly " : "") + << attribute.type.name << (attribute.type.nullable ? "?" : "") + << " " << attribute.name; + } + + dbg() << "Functions:"; + for (auto& function : interface->functions) { + dbg() << " " << function.return_type.name << (function.return_type.nullable ? "?" : "") + << " " << function.name; + for (auto& parameter : function.parameters) { + dbg() << " " << parameter.type.name << (parameter.type.nullable ? "?" : "") << " " << parameter.name; + } + } +#endif + + if (header_mode) + generate_header(*interface); + + if (implementation_mode) + generate_implementation(*interface); + + return 0; +} + +static void generate_header(const IDL::Interface& interface) +{ + auto& wrapper_class = interface.wrapper_class; + auto& wrapper_base_class = interface.wrapper_base_class; + + out() << "#pragma once"; + out() << "#include "; + out() << "#include "; + + if (!wrapper_base_class.is_empty()) { + out() << "#include "; + } + + out() << "namespace Web {"; + out() << "namespace Bindings {"; + + out() << "class " << wrapper_class << " : public " << wrapper_base_class << " {"; + out() << "public:"; + out() << " " << wrapper_class << "(JS::GlobalObject&, " << interface.name << "&);"; + out() << " virtual void initialize(JS::Interpreter&, JS::GlobalObject&) override;"; + out() << " virtual ~" << wrapper_class << "() override;"; + + if (wrapper_base_class.is_empty()) { + out() << " " << interface.name << "& impl() { return *m_impl; }"; + out() << " const " << interface.name << "& impl() const { return *m_impl; }"; + } else { + out() << " " << interface.name << "& impl() { return static_cast<" << interface.name << "&>(" << wrapper_base_class << "::impl()); }"; + out() << " const " << interface.name << "& impl() const { return static_cast(" << wrapper_base_class << "::impl()); }"; + } + + out() << "private:"; + out() << " virtual const char* class_name() const override { return \"" << interface.name << "\"; }"; + + for (auto& function : interface.functions) { + out() << " JS_DECLARE_NATIVE_FUNCTION(" << snake_name(function.name) << ");"; + } + + for (auto& attribute : interface.attributes) { + out() << " JS_DECLARE_NATIVE_GETTER(" << snake_name(attribute.name) << "_getter);"; + if (!attribute.readonly) + out() << " JS_DECLARE_NATIVE_SETTER(" << snake_name(attribute.name) << "_setter);"; + } + + out() << "};"; + out() << "}"; + out() << "}"; +} + +void generate_implementation(const IDL::Interface& interface) +{ + auto& wrapper_class = interface.wrapper_class; + auto& wrapper_base_class = interface.wrapper_base_class; + + out() << "#include "; + out() << "#include "; + out() << "#include "; + out() << "#include "; + out() << "#include "; + out() << "#include "; + out() << "#include "; + out() << "#include "; + out() << "#include "; + + out() << "namespace Web {"; + out() << "namespace Bindings {"; + + // Implementation: Wrapper constructor + out() << wrapper_class << "::" << wrapper_class << "(JS::GlobalObject& global_object, " << interface.name << "& impl)"; + out() << " : " << wrapper_base_class << "(global_object, impl)"; + out() << "{"; + out() << "}"; + + // Implementation: Wrapper initialize() + out() << "void " << wrapper_class << "::initialize(JS::Interpreter& interpreter, JS::GlobalObject& global_object)"; + out() << "{"; + out() << " [[maybe_unused]] u8 default_attributes = JS::Attribute::Enumerable | JS::Attribute::Configurable;"; + out() << " " << wrapper_base_class << "::initialize(interpreter, global_object);"; + + for (auto& attribute : interface.attributes) { + out() << " define_native_property(\"" << attribute.name << "\", " << attribute.getter_callback_name << ", " << (attribute.readonly ? "nullptr" : attribute.setter_callback_name) << ", default_attributes);"; + } + + for (auto& function : interface.functions) { + out() << " define_native_function(\"" << function.name << "\", " << snake_name(function.name) << ", " << function.length() << ", default_attributes);"; + } + + out() << "}"; + + // Implementation: Wrapper destructor + out() << wrapper_class << "::~" << wrapper_class << "()"; + out() << "{"; + out() << "}"; + + // Implementation: impl_from() + out() << "static " << interface.name << "* impl_from(JS::Interpreter& interpreter, JS::GlobalObject& global_object)"; + out() << "{"; + out() << " auto* this_object = interpreter.this_value(global_object).to_object(interpreter, global_object);"; + out() << " if (!this_object)"; + out() << " return {};"; + out() << " if (StringView(\"" << interface.name << "\") != this_object->class_name()) {"; + out() << " interpreter.throw_exception(JS::ErrorType::NotA, \"" << interface.name << "\");"; + out() << " return nullptr;"; + out() << " }"; + out() << " return &static_cast<" << wrapper_class << "*>(this_object)->impl();"; + out() << "}"; + + auto generate_return_statement = [&](auto& return_type) { + if (return_type.nullable) { + out() << " if (!retval)"; + out() << " return JS::js_null();"; + } + + if (return_type.name == "DOMString") { + out() << " return JS::js_string(interpreter, retval);"; + } else if (return_type.name == "ArrayFromVector") { + // FIXME: Remove this fake type hack once it's no longer needed. + // Basically once we have NodeList we can throw this out. + out() << " auto* new_array = JS::Array::create(global_object);"; + out() << " for (auto& element : retval) {"; + out() << " new_array->indexed_properties().append(wrap(interpreter.heap(), element));"; + out() << " }"; + out() << " return new_array;"; + } else { + out() << " return wrap(interpreter.heap(), const_cast<" << return_type.name << "&>(*retval));"; + } + }; + + // Implementation: Attributes + for (auto& attribute : interface.attributes) { + out() << "JS_DEFINE_NATIVE_GETTER(" << wrapper_class << "::" << snake_name(attribute.name) << "_getter)"; + out() << "{"; + out() << " auto* impl = impl_from(interpreter, global_object);"; + out() << " if (!impl)"; + out() << " return {};"; + out() << " auto retval = impl->" << snake_name(attribute.name) << "();"; + generate_return_statement(attribute.type); + out() << "}"; + } + + // Implementation: Functions + for (auto& function : interface.functions) { + out() << "JS_DEFINE_NATIVE_FUNCTION(" << wrapper_class << "::" << snake_name(function.name) << ")"; + out() << "{"; + out() << " auto* impl = impl_from(interpreter, global_object);"; + out() << " if (!impl)"; + out() << " return {};"; + out() << " if (interpreter.argument_count() < " << function.length() << ")"; + out() << " return interpreter.throw_exception(JS::ErrorType::BadArgCountMany, \"" << function.name << "\", \"" << function.length() << "\");"; + + Vector parameter_names; + size_t argument_index = 0; + for (auto& parameter : function.parameters) { + parameter_names.append(parameter.name); + if (parameter.type.name == "DOMString") { + out() << " auto " << snake_name(parameter.name) << " = interpreter.argument(" << argument_index << ").to_string(interpreter);"; + out() << " if (interpreter.exception())"; + out() << " return {};"; + } + ++argument_index; + } + + StringBuilder arguments_builder; + arguments_builder.join(", ", parameter_names); + + if (function.return_type.name != "void") { + out() << " auto retval = impl->" << snake_name(function.name) << "(" << arguments_builder.to_string() << ");"; + } else { + out() << " impl->" << snake_name(function.name) << "(" << arguments_builder.to_string() << ");"; + } + + generate_return_statement(function.return_type); + out() << "}"; + } + + out() << "}"; + out() << "}"; +} diff --git a/Libraries/LibWeb/DOM/Document.idl b/Libraries/LibWeb/DOM/Document.idl new file mode 100644 index 0000000000..a2e89cc222 --- /dev/null +++ b/Libraries/LibWeb/DOM/Document.idl @@ -0,0 +1,7 @@ +interface Document : Node { + + Element? getElementById(DOMString id); + Element? querySelector(DOMString selectors); + ArrayFromVector querySelectorAll(DOMString selectors); + +} diff --git a/Libraries/LibWeb/DOM/Node.cpp b/Libraries/LibWeb/DOM/Node.cpp index 055bb353c5..2e6eababc7 100644 --- a/Libraries/LibWeb/DOM/Node.cpp +++ b/Libraries/LibWeb/DOM/Node.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include diff --git a/Libraries/LibWeb/DOM/Node.idl b/Libraries/LibWeb/DOM/Node.idl new file mode 100644 index 0000000000..97e036d721 --- /dev/null +++ b/Libraries/LibWeb/DOM/Node.idl @@ -0,0 +1,10 @@ +interface Node : EventTarget { + + readonly attribute DOMString nodeName; + readonly attribute Node? firstChild; + readonly attribute Node? lastChild; + readonly attribute Node? previousSibling; + readonly attribute Node? nextSibling; + +} +