1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 01:17:35 +00:00

LibWeb: Basic implementation of global event handlers :^)

Document and HTMLElement now inherit from HTML::GlobalEventHandlers
which allows them to support "onfoo" event handler attributes.

These are assignable both via IDL attributes and content attributes.

Event listeners constructed this way get a special "attribute" flag
on them so we know which one to replace if you reassign them.
This also allows them to coexist with EventTarget.addEventListener().

This is all a bit sloppy, but it works decently for a first cut.
The Window object should also inherit GlobalEventHandlers, but since
we don't generate it from IDL, I haven't taken that step here.

Also this would be a lot nicer if we supported IDL mixins.
This commit is contained in:
Andreas Kling 2021-02-03 22:47:50 +01:00
parent b43db4cc50
commit a59b1825ce
15 changed files with 391 additions and 2 deletions

View file

@ -0,0 +1,54 @@
/*
* Copyright (c) 2021, 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/String.h>
#include <LibJS/Heap/Handle.h>
#include <LibJS/Runtime/Function.h>
namespace Web::HTML {
struct EventHandler {
EventHandler()
{
}
EventHandler(String s)
: string(move(s))
{
}
EventHandler(JS::Handle<JS::Function> c)
: callback(move(c))
{
}
String string;
JS::Handle<JS::Function> callback;
};
}

View file

@ -0,0 +1,99 @@
/*
* Copyright (c) 2021, 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 <LibJS/Interpreter.h>
#include <LibJS/Parser.h>
#include <LibJS/Runtime/ScriptFunction.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/EventListener.h>
#include <LibWeb/HTML/EventHandler.h>
#include <LibWeb/HTML/EventNames.h>
#include <LibWeb/HTML/GlobalEventHandlers.h>
#include <LibWeb/UIEvents/EventNames.h>
namespace Web::HTML {
#undef __ENUMERATE
#define __ENUMERATE(attribute_name, event_name) \
void GlobalEventHandlers::set_##attribute_name(HTML::EventHandler value) \
{ \
set_event_handler_attribute(event_name, move(value)); \
} \
HTML::EventHandler GlobalEventHandlers::attribute_name() \
{ \
return get_event_handler_attribute(event_name); \
}
ENUMERATE_GLOBAL_EVENT_HANDLERS(__ENUMERATE)
#undef __ENUMERATE
GlobalEventHandlers::~GlobalEventHandlers()
{
}
void GlobalEventHandlers::set_event_handler_attribute(const FlyString& name, HTML::EventHandler value)
{
auto& self = global_event_handlers_to_event_target();
RefPtr<DOM::EventListener> listener;
if (!value.callback.is_null()) {
listener = adopt(*new DOM::EventListener(move(value.callback)));
} else {
StringBuilder builder;
builder.appendff("function {}(event) {{\n{}\n}}", name, value.string);
auto parser = JS::Parser(JS::Lexer(builder.string_view()));
auto program = parser.parse_function_node<JS::FunctionExpression>();
if (parser.has_errors()) {
dbgln("Failed to parse script in event handler attribute '{}'", name);
return;
}
auto* function = JS::ScriptFunction::create(self.script_execution_context()->interpreter().global_object(), name, program->body(), program->parameters(), program->function_length(), nullptr, false, false);
ASSERT(function);
listener = adopt(*new DOM::EventListener(JS::make_handle(static_cast<JS::Function*>(function))));
}
if (listener) {
for (auto& registered_listener : self.listeners()) {
if (registered_listener.event_name == name && registered_listener.listener->is_attribute()) {
self.remove_event_listener(name, registered_listener.listener);
break;
}
}
self.add_event_listener(name, listener.release_nonnull());
}
}
HTML::EventHandler GlobalEventHandlers::get_event_handler_attribute(const FlyString& name)
{
auto& self = global_event_handlers_to_event_target();
for (auto& listener : self.listeners()) {
if (listener.event_name == name && listener.listener->is_attribute()) {
return HTML::EventHandler { JS::make_handle(&listener.listener->function()) };
}
}
return {};
}
}

View file

@ -26,6 +26,9 @@
#pragma once
#include <AK/Forward.h>
#include <LibWeb/Forward.h>
#define ENUMERATE_GLOBAL_EVENT_HANDLERS(E) \
E(onabort, HTML::EventNames::abort) \
E(onauxclick, "auxclick") \
@ -93,3 +96,25 @@
E(onwebkitanimationstart, "webkitanimationstart") \
E(onwebkittransitionend, "webkittransitionend") \
E(onwheel, "wheel")
namespace Web::HTML {
class GlobalEventHandlers {
public:
virtual ~GlobalEventHandlers();
#undef __ENUMERATE
#define __ENUMERATE(attribute_name, event_name) \
void set_##attribute_name(HTML::EventHandler); \
HTML::EventHandler attribute_name();
ENUMERATE_GLOBAL_EVENT_HANDLERS(__ENUMERATE)
#undef __ENUMERATE
void set_event_handler_attribute(const FlyString& name, HTML::EventHandler);
HTML::EventHandler get_event_handler_attribute(const FlyString& name);
protected:
virtual DOM::EventTarget& global_event_handlers_to_event_target() = 0;
};
}

View file

@ -27,6 +27,7 @@
#include <LibWeb/CSS/StyleProperties.h>
#include <LibWeb/CSS/StyleValue.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/Window.h>
#include <LibWeb/HTML/HTMLBodyElement.h>
namespace Web::HTML {
@ -78,4 +79,10 @@ void HTMLBodyElement::parse_attribute(const FlyString& name, const String& value
}
}
DOM::EventTarget& HTMLBodyElement::global_event_handlers_to_event_target()
{
// NOTE: This is a little weird, but IIUC document.body.onload actually refers to window.onload
return document().window();
}
}

View file

@ -41,6 +41,9 @@ public:
virtual void apply_presentational_hints(CSS::StyleProperties&) const override;
private:
// ^HTML::GlobalEventHandlers
virtual EventTarget& global_event_handlers_to_event_target() override;
RefPtr<CSS::ImageStyleValue> m_background_style_value;
};

View file

@ -25,11 +25,17 @@
*/
#include <AK/StringBuilder.h>
#include <LibJS/Interpreter.h>
#include <LibJS/Parser.h>
#include <LibJS/Runtime/ScriptFunction.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/EventListener.h>
#include <LibWeb/HTML/EventHandler.h>
#include <LibWeb/HTML/HTMLAnchorElement.h>
#include <LibWeb/HTML/HTMLElement.h>
#include <LibWeb/Layout/BreakNode.h>
#include <LibWeb/Layout/TextNode.h>
#include <LibWeb/UIEvents/EventNames.h>
namespace Web::HTML {
@ -138,4 +144,17 @@ bool HTMLElement::cannot_navigate() const
return !is<HTML::HTMLAnchorElement>(this) && !is_connected();
}
void HTMLElement::parse_attribute(const FlyString& name, const String& value)
{
Element::parse_attribute(name, value);
#undef __ENUMERATE
#define __ENUMERATE(attribute_name, event_name) \
if (name == HTML::AttributeNames::attribute_name) { \
set_event_handler_attribute(event_name, EventHandler { value }); \
}
ENUMERATE_GLOBAL_EVENT_HANDLERS(__ENUMERATE)
#undef __ENUMERATE
}
}

View file

@ -27,10 +27,14 @@
#pragma once
#include <LibWeb/DOM/Element.h>
#include <LibWeb/HTML/EventNames.h>
#include <LibWeb/HTML/GlobalEventHandlers.h>
namespace Web::HTML {
class HTMLElement : public DOM::Element {
class HTMLElement
: public DOM::Element
, public HTML::GlobalEventHandlers {
public:
using WrapperType = Bindings::HTMLElementWrapper;
@ -48,7 +52,13 @@ public:
bool cannot_navigate() const;
protected:
virtual void parse_attribute(const FlyString& name, const String& value) override;
private:
// ^HTML::GlobalEventHandlers
virtual EventTarget& global_event_handlers_to_event_target() override { return *this; }
enum class ContentEditableState {
True,
False,

View file

@ -8,4 +8,76 @@ interface HTMLElement : Element {
attribute DOMString contentEditable;
[LegacyNullToEmptyString] attribute DOMString innerText;
// FIXME: These should all come from a GlobalEventHandlers mixin
attribute EventHandler onabort;
attribute EventHandler onauxclick;
attribute EventHandler onblur;
attribute EventHandler oncancel;
attribute EventHandler oncanplay;
attribute EventHandler oncanplaythrough;
attribute EventHandler onchange;
attribute EventHandler onclick;
attribute EventHandler onclose;
attribute EventHandler oncontextmenu;
attribute EventHandler oncuechange;
attribute EventHandler ondblclick;
attribute EventHandler ondrag;
attribute EventHandler ondragend;
attribute EventHandler ondragenter;
attribute EventHandler ondragleave;
attribute EventHandler ondragover;
attribute EventHandler ondragstart;
attribute EventHandler ondrop;
attribute EventHandler ondurationchange;
attribute EventHandler onemptied;
attribute EventHandler onended;
// FIXME: Should be an OnErrorEventHandler
attribute EventHandler onerror;
attribute EventHandler onfocus;
attribute EventHandler onformdata;
attribute EventHandler oninput;
attribute EventHandler oninvalid;
attribute EventHandler onkeydown;
attribute EventHandler onkeypress;
attribute EventHandler onkeyup;
attribute EventHandler onload;
attribute EventHandler onloadeddata;
attribute EventHandler onloadedmetadata;
attribute EventHandler onloadstart;
attribute EventHandler onmousedown;
[LegacyLenientThis] attribute EventHandler onmouseenter;
[LegacyLenientThis] attribute EventHandler onmouseleave;
attribute EventHandler onmousemove;
attribute EventHandler onmouseout;
attribute EventHandler onmouseover;
attribute EventHandler onmouseup;
attribute EventHandler onpause;
attribute EventHandler onplay;
attribute EventHandler onplaying;
attribute EventHandler onprogress;
attribute EventHandler onratechange;
attribute EventHandler onreset;
attribute EventHandler onresize;
attribute EventHandler onscroll;
attribute EventHandler onsecuritypolicyviolation;
attribute EventHandler onseeked;
attribute EventHandler onseeking;
attribute EventHandler onselect;
attribute EventHandler onslotchange;
attribute EventHandler onstalled;
attribute EventHandler onsubmit;
attribute EventHandler onsuspend;
attribute EventHandler ontimeupdate;
attribute EventHandler ontoggle;
attribute EventHandler onvolumechange;
attribute EventHandler onwaiting;
attribute EventHandler onwebkitanimationend;
attribute EventHandler onwebkitanimationiteration;
attribute EventHandler onwebkitanimationstart;
attribute EventHandler onwebkittransitionend;
attribute EventHandler onwheel;
};