From d062d7baa7f4ce223da2a73108d87bd2178088bf Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Wed, 1 Apr 2020 18:53:28 +0200 Subject: [PATCH] LibWeb+LibJS: Move DOM Window object to dedicated classes LibWeb now creates a WindowObject which inherits from GlobalObject. Allocation of the global object is moved out of the Interpreter ctor to allow for specialized construction. The existing Window interfaces are moved to WindowObject with their implementation code in the new Window class. --- Libraries/LibJS/Interpreter.cpp | 2 - Libraries/LibJS/Interpreter.h | 7 + Libraries/LibJS/Runtime/GlobalObject.h | 2 +- Libraries/LibWeb/Bindings/WindowObject.cpp | 144 +++++++++++++++++++++ Libraries/LibWeb/Bindings/WindowObject.h | 58 +++++++++ Libraries/LibWeb/DOM/Document.cpp | 63 +-------- Libraries/LibWeb/DOM/Document.h | 11 +- Libraries/LibWeb/DOM/Window.cpp | 84 ++++++++++++ Libraries/LibWeb/DOM/Window.h | 56 ++++++++ Libraries/LibWeb/Forward.h | 7 + Libraries/LibWeb/Makefile | 2 + Userland/js.cpp | 4 +- 12 files changed, 368 insertions(+), 72 deletions(-) create mode 100644 Libraries/LibWeb/Bindings/WindowObject.cpp create mode 100644 Libraries/LibWeb/Bindings/WindowObject.h create mode 100644 Libraries/LibWeb/DOM/Window.cpp create mode 100644 Libraries/LibWeb/DOM/Window.h diff --git a/Libraries/LibJS/Interpreter.cpp b/Libraries/LibJS/Interpreter.cpp index 83f6c9bd71..17236cf335 100644 --- a/Libraries/LibJS/Interpreter.cpp +++ b/Libraries/LibJS/Interpreter.cpp @@ -48,8 +48,6 @@ Interpreter::Interpreter() m_array_prototype = heap().allocate(); m_error_prototype = heap().allocate(); m_date_prototype = heap().allocate(); - - m_global_object = heap().allocate(); } Interpreter::~Interpreter() diff --git a/Libraries/LibJS/Interpreter.h b/Libraries/LibJS/Interpreter.h index 2902cabdb8..5145d6b103 100644 --- a/Libraries/LibJS/Interpreter.h +++ b/Libraries/LibJS/Interpreter.h @@ -71,6 +71,13 @@ public: Interpreter(); ~Interpreter(); + template + void initialize_global_object(Args&&... args) + { + ASSERT(!m_global_object); + m_global_object = heap().allocate(forward(args)...); + } + Value run(const Statement&, Vector = {}, ScopeType = ScopeType::Block); Object& global_object() { return *m_global_object; } diff --git a/Libraries/LibJS/Runtime/GlobalObject.h b/Libraries/LibJS/Runtime/GlobalObject.h index c5efc058b9..ab2898713d 100644 --- a/Libraries/LibJS/Runtime/GlobalObject.h +++ b/Libraries/LibJS/Runtime/GlobalObject.h @@ -4,7 +4,7 @@ namespace JS { -class GlobalObject final : public Object { +class GlobalObject : public Object { public: explicit GlobalObject(); virtual ~GlobalObject() override; diff --git a/Libraries/LibWeb/Bindings/WindowObject.cpp b/Libraries/LibWeb/Bindings/WindowObject.cpp new file mode 100644 index 0000000000..ca669ee772 --- /dev/null +++ b/Libraries/LibWeb/Bindings/WindowObject.cpp @@ -0,0 +1,144 @@ +/* + * 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 + +namespace Web { +namespace Bindings { + +WindowObject::WindowObject(Window& impl) + : m_impl(impl) +{ + put_native_property("document", document_getter, document_setter); + put_native_function("alert", alert); + put_native_function("setInterval", set_interval); + put_native_function("requestAnimationFrame", request_animation_frame); + put_native_function("cancelAnimationFrame", cancel_animation_frame); +} + +WindowObject::~WindowObject() +{ +} + +static Window* impl_from(JS::Interpreter& interpreter) +{ + auto* this_object = interpreter.this_value().to_object(interpreter.heap()); + if (!this_object) { + dbg() << "this_object is null"; + ASSERT_NOT_REACHED(); + return nullptr; + } + if (StringView("WindowObject") != this_object->class_name()) { + interpreter.throw_exception("TypeError", "That's not a WindowObject, bro."); + dbg() << "this_object class_name is '" << this_object->class_name() << "'"; + return nullptr; + } + return &static_cast(this_object)->impl(); +} + +JS::Value WindowObject::alert(JS::Interpreter& interpreter) +{ + dbg() << "alert entry"; + auto* impl = impl_from(interpreter); + if (!impl) + return {}; + dbg() << "alert2 entry"; + auto& arguments = interpreter.call_frame().arguments; + if (arguments.size() < 1) + return {}; + impl->alert(arguments[0].to_string()); + return {}; +} + +JS::Value WindowObject::set_interval(JS::Interpreter& interpreter) +{ + auto* impl = impl_from(interpreter); + if (!impl) + return {}; + auto& arguments = interpreter.call_frame().arguments; + if (arguments.size() < 2) + return {}; + auto* callback_object = arguments[0].to_object(interpreter.heap()); + if (!callback_object) + return {}; + if (!callback_object->is_function()) + return interpreter.throw_exception("TypeError", "Not a function"); + impl->set_interval(*static_cast(callback_object), arguments[1].to_i32()); + return {}; +} + +JS::Value WindowObject::request_animation_frame(JS::Interpreter& interpreter) +{ + auto* impl = impl_from(interpreter); + if (!impl) + return {}; + auto& arguments = interpreter.call_frame().arguments; + if (arguments.size() < 1) + return {}; + auto* callback_object = arguments[0].to_object(interpreter.heap()); + if (!callback_object) + return {}; + if (!callback_object->is_function()) + return interpreter.throw_exception("TypeError", "Not a function"); + return JS::Value(impl->request_animation_frame(*static_cast(callback_object))); +} + + +JS::Value WindowObject::cancel_animation_frame(JS::Interpreter& interpreter) +{ + auto* impl = impl_from(interpreter); + if (!impl) + return {}; + auto& arguments = interpreter.call_frame().arguments; + if (arguments.size() < 1) + return {}; + impl->cancel_animation_frame(arguments[0].to_i32()); + return {}; +} + +JS::Value WindowObject::document_getter(JS::Interpreter& interpreter) +{ + auto* impl = impl_from(interpreter); + if (!impl) + return {}; + return wrap(interpreter.heap(), impl->document()); +} + +void WindowObject::document_setter(JS::Interpreter&, JS::Value) +{ + // FIXME: Figure out what we should do here. Just ignore attempts to set window.document for now. +} + +} +} diff --git a/Libraries/LibWeb/Bindings/WindowObject.h b/Libraries/LibWeb/Bindings/WindowObject.h new file mode 100644 index 0000000000..f47c9e5ca1 --- /dev/null +++ b/Libraries/LibWeb/Bindings/WindowObject.h @@ -0,0 +1,58 @@ +/* + * 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 +#include + +namespace Web { +namespace Bindings { + +class WindowObject : public JS::GlobalObject { +public: + explicit WindowObject(Window&); + virtual ~WindowObject() override; + + Window& impl() { return *m_impl; } + const Window& impl() const { return *m_impl; } + +private: + virtual const char* class_name() const override { return "WindowObject"; } + + static JS::Value document_getter(JS::Interpreter&); + static void document_setter(JS::Interpreter&, JS::Value); + + static JS::Value alert(JS::Interpreter&); + static JS::Value set_interval(JS::Interpreter&); + static JS::Value request_animation_frame(JS::Interpreter&); + static JS::Value cancel_animation_frame(JS::Interpreter&); + + NonnullRefPtr m_impl; +}; + +} +} diff --git a/Libraries/LibWeb/DOM/Document.cpp b/Libraries/LibWeb/DOM/Document.cpp index 555124fb27..9dee2a6e02 100644 --- a/Libraries/LibWeb/DOM/Document.cpp +++ b/Libraries/LibWeb/DOM/Document.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -44,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -57,6 +59,7 @@ namespace Web { Document::Document() : ParentNode(*this, NodeType::DOCUMENT_NODE) , m_style_resolver(make(*this)) + , m_window(Window::create_with_document(*this)) { m_style_update_timer = Core::Timer::construct(); m_style_update_timer->set_single_shot(true); @@ -359,65 +362,7 @@ JS::Interpreter& Document::interpreter() { if (!m_interpreter) { m_interpreter = make(); - - m_interpreter->global_object().put_native_function("alert", [](JS::Interpreter& interpreter) -> JS::Value { - auto& arguments = interpreter.call_frame().arguments; - if (arguments.size() < 1) - return JS::js_undefined(); - GUI::MessageBox::show(arguments[0].to_string(), "Alert", GUI::MessageBox::Type::Information); - return JS::js_undefined(); - }); - - m_interpreter->global_object().put_native_function("setInterval", [this](JS::Interpreter& interpreter) -> JS::Value { - auto& arguments = interpreter.call_frame().arguments; - if (arguments.size() < 2) - return JS::js_undefined(); - ASSERT(arguments[0].is_object()); - ASSERT(arguments[0].as_object()->is_function()); - auto callback = make_handle(const_cast(arguments[0].as_object())); - - // FIXME: This timer should not be leaked! It should also be removable with clearInterval()! - (void)Core::Timer::construct( - arguments[1].to_i32(), [this, callback] { - auto* function = const_cast(static_cast(callback.cell())); - m_interpreter->call(function); - }) - .leak_ref(); - - return JS::js_undefined(); - }); - - m_interpreter->global_object().put_native_function("requestAnimationFrame", [this](JS::Interpreter& interpreter) -> JS::Value { - auto& arguments = interpreter.call_frame().arguments; - if (arguments.size() < 1) - return JS::js_undefined(); - ASSERT(arguments[0].is_object()); - ASSERT(arguments[0].as_object()->is_function()); - auto callback = make_handle(const_cast(arguments[0].as_object())); - // FIXME: Don't hand out raw DisplayLink ID's to JavaScript! - i32 link_id = GUI::DisplayLink::register_callback([this, callback](i32 link_id) { - auto* function = const_cast(static_cast(callback.cell())); - m_interpreter->call(function); - GUI::DisplayLink::unregister_callback(link_id); - }); - return JS::Value(link_id); - }); - - m_interpreter->global_object().put_native_function("cancelAnimationFrame", [](JS::Interpreter& interpreter) -> JS::Value { - auto& arguments = interpreter.call_frame().arguments; - if (arguments.size() < 1) - return JS::js_undefined(); - // FIXME: We should not be passing untrusted numbers to DisplayLink::unregistered_callback()! - GUI::DisplayLink::unregister_callback(arguments[0].to_i32()); - return JS::js_undefined(); - }); - - m_interpreter->global_object().put_native_property( - "document", - [this](JS::Interpreter&) { - return wrap(m_interpreter->heap(), *this); - }, - nullptr); + m_interpreter->initialize_global_object(*m_window); } return *m_interpreter; } diff --git a/Libraries/LibWeb/DOM/Document.h b/Libraries/LibWeb/DOM/Document.h index df183081a7..a578ff2152 100644 --- a/Libraries/LibWeb/DOM/Document.h +++ b/Libraries/LibWeb/DOM/Document.h @@ -42,15 +42,6 @@ namespace Web { -class Frame; -class HTMLBodyElement; -class HTMLHtmlElement; -class HTMLHeadElement; -class LayoutDocument; -class LayoutNode; -class StyleResolver; -class StyleSheet; - class Document : public ParentNode , public NonElementParentNode { @@ -140,6 +131,8 @@ private: WeakPtr m_frame; URL m_url; + RefPtr m_window; + RefPtr m_layout_root; Optional m_link_color; diff --git a/Libraries/LibWeb/DOM/Window.cpp b/Libraries/LibWeb/DOM/Window.cpp new file mode 100644 index 0000000000..fb806ce43c --- /dev/null +++ b/Libraries/LibWeb/DOM/Window.cpp @@ -0,0 +1,84 @@ +/* + * 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 + +namespace Web { + +NonnullRefPtr Window::create_with_document(Document& document) +{ + return adopt(*new Window(document)); +} + +Window::Window(Document& document) + : m_document(document) +{ +} + +Window::~Window() +{ +} + +void Window::alert(const String& message) +{ + GUI::MessageBox::show(message, "Alert", GUI::MessageBox::Type::Information); +} + +void Window::set_interval(JS::Function& callback, i32 interval) +{ + // FIXME: This leaks the interval timer and makes it unstoppable. + (void)Core::Timer::construct(interval, [handle = make_handle(&callback)] { + auto* function = const_cast(static_cast(handle.cell())); + auto& interpreter = function->interpreter(); + interpreter.call(function); + }).leak_ref(); +} + +i32 Window::request_animation_frame(JS::Function& callback) +{ + i32 link_id = GUI::DisplayLink::register_callback([handle = make_handle(&callback)](i32 link_id) { + auto* function = const_cast(static_cast(handle.cell())); + auto& interpreter = function->interpreter(); + interpreter.call(function); + GUI::DisplayLink::unregister_callback(link_id); + }); + + // FIXME: Don't hand out raw DisplayLink ID's to JavaScript! + return link_id; +} + +void Window::cancel_animation_frame(i32 id) +{ + // FIXME: We should not be passing untrusted numbers to DisplayLink::unregister_callback()! + GUI::DisplayLink::unregister_callback(id); +} + +} diff --git a/Libraries/LibWeb/DOM/Window.h b/Libraries/LibWeb/DOM/Window.h new file mode 100644 index 0000000000..a3ec299ad6 --- /dev/null +++ b/Libraries/LibWeb/DOM/Window.h @@ -0,0 +1,56 @@ +/* + * 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 +#include +#include +#include +#include + +namespace Web { + +class Window : public RefCounted { +public: + static NonnullRefPtr create_with_document(Document&); + ~Window(); + + const Document& document() const { return m_document; } + Document& document() { return m_document; } + + void alert(const String&); + i32 request_animation_frame(JS::Function&); + void cancel_animation_frame(i32); + void set_interval(JS::Function&, i32); + +private: + explicit Window(Document&); + + Document& m_document; +}; + +} diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index 0403db9b97..230c112208 100644 --- a/Libraries/LibWeb/Forward.h +++ b/Libraries/LibWeb/Forward.h @@ -35,15 +35,21 @@ class Event; class EventListener; class EventTarget; class Frame; +class HTMLBodyElement; class HTMLCanvasElement; class HTMLElement; +class HTMLHeadElement; +class HTMLHtmlElement; class HtmlView; +class LayoutDocument; class LayoutNode; class MouseEvent; class Node; class Selector; +class StyleResolver; class StyleRule; class StyleSheet; +class Window; namespace Bindings { @@ -56,6 +62,7 @@ class EventTargetWrapper; class HTMLCanvasElementWrapper; class MouseEventWrapper; class NodeWrapper; +class WindowObject; class Wrappable; class Wrapper; diff --git a/Libraries/LibWeb/Makefile b/Libraries/LibWeb/Makefile index 5de6b61cd2..f5a1818d5a 100644 --- a/Libraries/LibWeb/Makefile +++ b/Libraries/LibWeb/Makefile @@ -8,6 +8,7 @@ LIBWEB_OBJS = \ Bindings/HTMLCanvasElementWrapper.o \ Bindings/MouseEventWrapper.o \ Bindings/NodeWrapper.o \ + Bindings/WindowObject.o \ Bindings/Wrappable.o \ CSS/DefaultStyleSheetSource.o \ CSS/PropertyID.o \ @@ -51,6 +52,7 @@ LIBWEB_OBJS = \ DOM/Node.o \ DOM/ParentNode.o \ DOM/Text.o \ + DOM/Window.o \ StylePropertiesModel.o \ DOMTreeModel.o \ Dump.o \ diff --git a/Userland/js.cpp b/Userland/js.cpp index f8cfab8db1..4970a870d2 100644 --- a/Userland/js.cpp +++ b/Userland/js.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -133,7 +134,7 @@ void print_value(JS::Value value, HashTable& seen_objects) if (value.is_array()) return print_array(static_cast(value.as_object()), seen_objects); - + if (value.is_object()) { auto* object = value.as_object(); if (object->is_function()) @@ -198,6 +199,7 @@ int main(int argc, char** argv) args_parser.parse(argc, argv); JS::Interpreter interpreter; + interpreter.initialize_global_object(); interpreter.heap().set_should_collect_on_every_allocation(gc_on_every_allocation); interpreter.global_object().put("global", &interpreter.global_object());