LibWeb/HTML: Implement CommandEvent class

This implements the `CommandEvent` class.
This commit is contained in:
Glenn Skrzypczak 2025-04-04 12:35:20 +02:00
parent 22b4ff1dfc
commit 9e31ee3778
8 changed files with 297 additions and 0 deletions

View file

@ -333,6 +333,7 @@ set(SOURCES
HTML/CloseEvent.cpp
HTML/CloseWatcher.cpp
HTML/CloseWatcherManager.cpp
HTML/CommandEvent.cpp
HTML/CORSSettingAttribute.cpp
HTML/CrossOrigin/AbstractOperations.cpp
HTML/CrossOrigin/Reporting.cpp

View file

@ -0,0 +1,44 @@
/*
* Copyright (c) 2025, Glenn Skrzypczak <glenn.skrzypczak@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/Bindings/CommandEventPrototype.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/HTML/CommandEvent.h>
namespace Web::HTML {
GC_DEFINE_ALLOCATOR(CommandEvent);
GC::Ref<CommandEvent> CommandEvent::create(JS::Realm& realm, FlyString const& event_name, CommandEventInit event_init)
{
return realm.create<CommandEvent>(realm, event_name, move(event_init));
}
WebIDL::ExceptionOr<GC::Ref<CommandEvent>> CommandEvent::construct_impl(JS::Realm& realm, FlyString const& event_name, CommandEventInit event_init)
{
return create(realm, event_name, move(event_init));
}
CommandEvent::CommandEvent(JS::Realm& realm, FlyString const& event_name, CommandEventInit event_init)
: DOM::Event(realm, event_name, event_init)
, m_source(event_init.source)
, m_command(move(event_init.command))
{
}
void CommandEvent::visit_edges(Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_source);
}
void CommandEvent::initialize(JS::Realm& realm)
{
Base::initialize(realm);
WEB_SET_PROTOTYPE_FOR_INTERFACE(CommandEvent);
}
}

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2025, Glenn Skrzypczak <glenn.skrzypczak@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/String.h>
#include <LibWeb/DOM/Element.h>
#include <LibWeb/DOM/Event.h>
#include <LibWeb/DOM/Utils.h>
namespace Web::HTML {
struct CommandEventInit : public DOM::EventInit {
GC::Ptr<DOM::Element> source;
String command;
};
class CommandEvent : public DOM::Event {
WEB_PLATFORM_OBJECT(CommandEvent, DOM::Event);
GC_DECLARE_ALLOCATOR(CommandEvent);
public:
[[nodiscard]] static GC::Ref<CommandEvent> create(JS::Realm&, FlyString const& event_name, CommandEventInit = {});
static WebIDL::ExceptionOr<GC::Ref<CommandEvent>> construct_impl(JS::Realm&, FlyString const& event_name, CommandEventInit);
// https://html.spec.whatwg.org/multipage/interaction.html#dom-commandevent-command
String const& command() const { return m_command; }
// https://html.spec.whatwg.org/multipage/interaction.html#dom-commandevent-source
GC::Ptr<DOM::Element> source() const { return as<DOM::Element>(retarget(m_source, current_target())); }
private:
void visit_edges(Visitor&) override;
CommandEvent(JS::Realm&, FlyString const& event_name, CommandEventInit event_init);
void initialize(JS::Realm&) override;
GC::Ptr<DOM::Element> m_source;
String m_command;
};
}

View file

@ -0,0 +1,14 @@
#import <DOM/Event.idl>
// https://html.spec.whatwg.org/multipage/interaction.html#commandevent
[Exposed=Window]
interface CommandEvent : Event {
constructor(DOMString type, optional CommandEventInit eventInitDict = {});
readonly attribute Element? source;
readonly attribute DOMString command;
};
dictionary CommandEventInit : EventInit {
Element? source = null;
DOMString command = "";
};

View file

@ -119,6 +119,7 @@ libweb_js_bindings(HTML/CanvasPattern)
libweb_js_bindings(HTML/CanvasRenderingContext2D)
libweb_js_bindings(HTML/CloseEvent)
libweb_js_bindings(HTML/CloseWatcher)
libweb_js_bindings(HTML/CommandEvent)
libweb_js_bindings(HTML/CustomElements/CustomElementRegistry)
libweb_js_bindings(HTML/DataTransfer)
libweb_js_bindings(HTML/DataTransferItem)

View file

@ -69,6 +69,7 @@ ClipboardEvent
ClipboardItem
CloseEvent
CloseWatcher
CommandEvent
Comment
CompositionEvent
CompressionStream

View file

@ -0,0 +1,27 @@
Harness status: OK
Found 22 tests
22 Pass
Pass command is a readonly defaulting to ''
Pass source is readonly defaulting to null
Pass command reflects initialized attribute
Pass command set to undefined
Pass command set to null
Pass command set to false
Pass command explicitly set to empty string
Pass command set to true
Pass command set to a number
Pass command set to []
Pass command set to [1, 2, 3]
Pass command set to an object
Pass command set to an object with a toString function
Pass CommandEventInit properties set value
Pass CommandEventInit properties set value 2
Pass CommandEventInit properties set value 3
Pass source set to undefined
Pass source set to null
Pass source set to false
Pass source set to true
Pass source set to {}
Pass source set to non-Element EventTarget

View file

@ -0,0 +1,163 @@
<!doctype html>
<meta charset="utf-8" />
<meta name="author" title="Keith Cirkel" href="mailto:wpt@keithcirkel.co.uk" />
<link rel="help" href="https://open-ui.org/components/invokers.explainer/" />
<script src="../../../../resources/testharness.js"></script>
<script src="../../../../resources/testharnessreport.js"></script>
<script src="resources/invoker-utils.js"></script>
<div id="div"></div>
<button id="button"></button>
<script>
test(function () {
const event = new CommandEvent("test");
assert_equals(event.command, "");
assert_readonly(event, "command", "readonly attribute value");
}, "command is a readonly defaulting to ''");
test(function () {
const event = new CommandEvent("test");
assert_equals(event.source, null);
assert_readonly(event, "source", "readonly attribute value");
}, "source is readonly defaulting to null");
test(function () {
const event = new CommandEvent("test", { command: "sAmPle" });
assert_equals(event.command, "sAmPle");
}, "command reflects initialized attribute");
test(function () {
const event = new CommandEvent("test", { command: undefined });
assert_equals(event.command, "");
}, "command set to undefined");
test(function () {
const event = new CommandEvent("test", { command: null });
assert_equals(event.command, "null");
}, "command set to null");
test(function () {
const event = new CommandEvent("test", { command: false });
assert_equals(event.command, "false");
}, "command set to false");
test(function () {
const event = new CommandEvent("test", { command: "" });
assert_equals(event.command, "");
}, "command explicitly set to empty string");
test(function () {
const event = new CommandEvent("test", { command: true });
assert_equals(event.command, "true");
}, "command set to true");
test(function () {
const event = new CommandEvent("test", { command: 0.5 });
assert_equals(event.command, "0.5");
}, "command set to a number");
test(function () {
const event = new CommandEvent("test", { command: [] });
assert_equals(event.command, "");
}, "command set to []");
test(function () {
const event = new CommandEvent("test", { command: [1, 2, 3] });
assert_equals(event.command, "1,2,3");
}, "command set to [1, 2, 3]");
test(function () {
const event = new CommandEvent("test", { command: { sample: 0.5 } });
assert_equals(event.command, "[object Object]");
}, "command set to an object");
test(function () {
const event = new CommandEvent("test", {
command: {
toString() {
return "sample";
},
},
});
assert_equals(event.command, "sample");
}, "command set to an object with a toString function");
test(function () {
const eventInit = { command: "sample", source: document.body };
const event = new CommandEvent("test", eventInit);
assert_equals(event.command, "sample");
assert_equals(event.source, document.body);
}, "CommandEventInit properties set value");
test(function () {
const eventInit = {
command: "open",
source: document.getElementById("div"),
};
const event = new CommandEvent("beforetoggle", eventInit);
assert_equals(event.command, "open");
assert_equals(event.source, document.getElementById("div"));
}, "CommandEventInit properties set value 2");
test(function () {
const eventInit = {
command: "closed",
source: document.getElementById("button"),
};
const event = new CommandEvent("toggle", eventInit);
assert_equals(event.command, "closed");
assert_equals(event.source, document.getElementById("button"));
}, "CommandEventInit properties set value 3");
test(function () {
const event = new CommandEvent("test", { source: undefined });
assert_equals(event.source, null);
}, "source set to undefined");
test(function () {
const event = new CommandEvent("test", { source: null });
assert_equals(event.source, null);
}, "source set to null");
test(function () {
assert_throws_js(
TypeError,
function () {
new CommandEvent("test", { source: false });
},
"source is not an object",
);
}, "source set to false");
test(function () {
assert_throws_js(
TypeError,
function () {
const event = new CommandEvent("test", { source: true });
},
"source is not an object",
);
}, "source set to true");
test(function () {
assert_throws_js(
TypeError,
function () {
const event = new CommandEvent("test", { source: {} });
},
"source is not an object",
);
}, "source set to {}");
test(function () {
assert_throws_js(
TypeError,
function () {
const eventInit = { command: "closed", source: new XMLHttpRequest() };
const event = new CommandEvent("toggle", eventInit);
},
"source is not an Element",
);
}, "source set to non-Element EventTarget");
</script>