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.
This commit is contained in:
Andreas Kling 2020-04-01 18:53:28 +02:00
parent cd9379dca9
commit d062d7baa7
12 changed files with 368 additions and 72 deletions

View file

@ -48,8 +48,6 @@ Interpreter::Interpreter()
m_array_prototype = heap().allocate<ArrayPrototype>();
m_error_prototype = heap().allocate<ErrorPrototype>();
m_date_prototype = heap().allocate<DatePrototype>();
m_global_object = heap().allocate<GlobalObject>();
}
Interpreter::~Interpreter()

View file

@ -71,6 +71,13 @@ public:
Interpreter();
~Interpreter();
template<typename T, typename... Args>
void initialize_global_object(Args&&... args)
{
ASSERT(!m_global_object);
m_global_object = heap().allocate<T>(forward<Args>(args)...);
}
Value run(const Statement&, Vector<Argument> = {}, ScopeType = ScopeType::Block);
Object& global_object() { return *m_global_object; }

View file

@ -4,7 +4,7 @@
namespace JS {
class GlobalObject final : public Object {
class GlobalObject : public Object {
public:
explicit GlobalObject();
virtual ~GlobalObject() override;

View file

@ -0,0 +1,144 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* 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 <AK/FlyString.h>
#include <AK/Function.h>
#include <LibJS/Interpreter.h>
#include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/Function.h>
#include <LibWeb/Bindings/DocumentWrapper.h>
#include <LibWeb/Bindings/WindowObject.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/Window.h>
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<JS::Error>("TypeError", "That's not a WindowObject, bro.");
dbg() << "this_object class_name is '" << this_object->class_name() << "'";
return nullptr;
}
return &static_cast<WindowObject*>(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<JS::Error>("TypeError", "Not a function");
impl->set_interval(*static_cast<JS::Function*>(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<JS::Error>("TypeError", "Not a function");
return JS::Value(impl->request_animation_frame(*static_cast<JS::Function*>(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.
}
}
}

View file

@ -0,0 +1,58 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* 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 <LibJS/Runtime/GlobalObject.h>
#include <LibWeb/Forward.h>
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<Window> m_impl;
};
}
}

View file

@ -34,6 +34,7 @@
#include <LibJS/Runtime/Function.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibWeb/Bindings/DocumentWrapper.h>
#include <LibWeb/Bindings/WindowObject.h>
#include <LibWeb/CSS/SelectorEngine.h>
#include <LibWeb/CSS/StyleResolver.h>
#include <LibWeb/DOM/Document.h>
@ -44,6 +45,7 @@
#include <LibWeb/DOM/HTMLHeadElement.h>
#include <LibWeb/DOM/HTMLHtmlElement.h>
#include <LibWeb/DOM/HTMLTitleElement.h>
#include <LibWeb/DOM/Window.h>
#include <LibWeb/Dump.h>
#include <LibWeb/Frame.h>
#include <LibWeb/HtmlView.h>
@ -57,6 +59,7 @@ namespace Web {
Document::Document()
: ParentNode(*this, NodeType::DOCUMENT_NODE)
, m_style_resolver(make<StyleResolver>(*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<JS::Interpreter>();
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<JS::Object*>(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<JS::Function*>(static_cast<const JS::Function*>(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<JS::Object*>(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<JS::Function*>(static_cast<const JS::Function*>(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<Bindings::WindowObject>(*m_window);
}
return *m_interpreter;
}

View file

@ -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<Document> {
@ -140,6 +131,8 @@ private:
WeakPtr<Frame> m_frame;
URL m_url;
RefPtr<Window> m_window;
RefPtr<LayoutDocument> m_layout_root;
Optional<Color> m_link_color;

View file

@ -0,0 +1,84 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* 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 <LibCore/Timer.h>
#include <LibGUI/DisplayLink.h>
#include <LibGUI/MessageBox.h>
#include <LibJS/Interpreter.h>
#include <LibJS/Runtime/Function.h>
#include <LibWeb/DOM/Window.h>
namespace Web {
NonnullRefPtr<Window> 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<JS::Function*>(static_cast<const JS::Function*>(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<JS::Function*>(static_cast<const JS::Function*>(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);
}
}

View file

@ -0,0 +1,56 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* 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 <AK/Badge.h>
#include <AK/RefCounted.h>
#include <AK/RefPtr.h>
#include <LibWeb/Bindings/WindowObject.h>
#include <LibWeb/Bindings/Wrappable.h>
namespace Web {
class Window : public RefCounted<Window> {
public:
static NonnullRefPtr<Window> 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;
};
}

View file

@ -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;

View file

@ -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 \

View file

@ -35,6 +35,7 @@
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/Date.h>
#include <LibJS/Runtime/Function.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Object.h>
#include <LibJS/Runtime/PrimitiveString.h>
#include <LibJS/Runtime/Value.h>
@ -133,7 +134,7 @@ void print_value(JS::Value value, HashTable<JS::Object*>& seen_objects)
if (value.is_array())
return print_array(static_cast<const JS::Array*>(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<JS::GlobalObject>();
interpreter.heap().set_should_collect_on_every_allocation(gc_on_every_allocation);
interpreter.global_object().put("global", &interpreter.global_object());