mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-25 09:00:22 +00:00
Ladybird+LibWeb: Add basic select element support
This commit is contained in:
parent
b439431488
commit
466153e680
Notes:
sideshowbarker
2024-07-16 22:14:49 +09:00
Author: https://github.com/bplaat Commit: https://github.com/SerenityOS/serenity/commit/466153e680 Pull-request: https://github.com/SerenityOS/serenity/pull/22187
28 changed files with 641 additions and 4 deletions
61
Base/res/html/misc/select.html
Normal file
61
Base/res/html/misc/select.html
Normal file
|
@ -0,0 +1,61 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Select showcase</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Basic select:</p>
|
||||
<p>
|
||||
<select onchange="document.getElementById('a-value').textContent = this.value">
|
||||
<option value="one">One</option>
|
||||
<option value="two">Two</option>
|
||||
<option value="three">Three</option>
|
||||
<option value="four">Four</option>
|
||||
<option value="five">Five</option>
|
||||
</select>
|
||||
Value: <span id="a-value">?</span>
|
||||
</p>
|
||||
|
||||
<p>Basic select with separators:</p>
|
||||
<p>
|
||||
<select onchange="document.getElementById('b-value').textContent = this.value" style="width: 50%;">
|
||||
<option value="one">One</option>
|
||||
<option value="two">Two</option>
|
||||
<hr>
|
||||
<option value="three">Three</option>
|
||||
<option value="four">Four</option>
|
||||
<hr>
|
||||
<option value="five">Five</option>
|
||||
<option value="six">Six</option>
|
||||
</select>
|
||||
Value: <span id="b-value">?</span>
|
||||
</p>
|
||||
|
||||
<p>Basic select with option groups and separators:</p>
|
||||
<p>
|
||||
<select onchange="document.getElementById('c-value').textContent = this.value">
|
||||
<optgroup label="8.01 Physics I: Classical Mechanics">
|
||||
<option value="8.01.1">Lecture 01: Powers of Ten
|
||||
<option value="8.01.2">Lecture 02: 1D Kinematics
|
||||
<option value="8.01.3">Lecture 03: Vectors
|
||||
<hr>
|
||||
<option value="8.01.4">Lecture 04: Random
|
||||
<optgroup label="8.02 Electricity and Magnetism">
|
||||
<option value="8.02.1">Lecture 01: What holds our world together?
|
||||
<option value="8.02.2">Lecture 02: Electric Field
|
||||
<option value="8.02.3">Lecture 03: Electric Flux
|
||||
<hr>
|
||||
<option value="8.02.4">Lecture 04: Random
|
||||
<optgroup label="8.03 Physics III: Vibrations and Waves">
|
||||
<option value="8.03.1">Lecture 01: Periodic Phenomenon
|
||||
<option value="8.03.2">Lecture 02: Beats
|
||||
<option value="8.03.3">Lecture 03: Forced Oscillations with Damping
|
||||
<hr>
|
||||
<option value="8.03.4">Lecture 04: Random
|
||||
</select>
|
||||
Value: <span id="c-value">?</span>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
|
@ -36,7 +36,7 @@
|
|||
|
||||
@end
|
||||
|
||||
@interface LadybirdWebView : NSClipView
|
||||
@interface LadybirdWebView : NSClipView <NSMenuDelegate>
|
||||
|
||||
- (instancetype)init:(id<LadybirdWebViewObserver>)observer;
|
||||
|
||||
|
|
|
@ -62,6 +62,7 @@ struct HideCursor {
|
|||
@property (nonatomic, strong) NSMenu* image_context_menu;
|
||||
@property (nonatomic, strong) NSMenu* audio_context_menu;
|
||||
@property (nonatomic, strong) NSMenu* video_context_menu;
|
||||
@property (nonatomic, strong) NSMenu* select_dropdown;
|
||||
@property (nonatomic, strong) NSTextField* status_label;
|
||||
@property (nonatomic, strong) NSAlert* dialog;
|
||||
|
||||
|
@ -608,6 +609,21 @@ static void copy_data_to_clipboard(StringView data, NSPasteboardType pasteboard_
|
|||
[panel makeKeyAndOrderFront:nil];
|
||||
};
|
||||
|
||||
self.select_dropdown = [[NSMenu alloc] initWithTitle:@"Select Dropdown"];
|
||||
[self.select_dropdown setDelegate:self];
|
||||
|
||||
m_web_view_bridge->on_request_select_dropdown = [self](Gfx::IntPoint content_position, i32 minimum_width, Vector<Web::HTML::SelectItem> items) {
|
||||
[self.select_dropdown removeAllItems];
|
||||
self.select_dropdown.minimumWidth = minimum_width;
|
||||
for (auto const& item : items) {
|
||||
[self selectDropdownAdd:self.select_dropdown
|
||||
item:item];
|
||||
}
|
||||
|
||||
auto* event = Ladybird::create_context_menu_mouse_event(self, content_position);
|
||||
[NSMenu popUpContextMenu:self.select_dropdown withEvent:event forView:self];
|
||||
};
|
||||
|
||||
m_web_view_bridge->on_get_all_cookies = [](auto const& url) {
|
||||
auto* delegate = (ApplicationDelegate*)[NSApp delegate];
|
||||
return [delegate cookieJar].get_all_cookies(url);
|
||||
|
@ -703,6 +719,48 @@ static void copy_data_to_clipboard(StringView data, NSPasteboardType pasteboard_
|
|||
};
|
||||
}
|
||||
|
||||
- (void)selectDropdownAdd:(NSMenu*)menu item:(Web::HTML::SelectItem const&)item
|
||||
{
|
||||
if (item.type == Web::HTML::SelectItem::Type::OptionGroup) {
|
||||
NSMenuItem* subtitle = [[NSMenuItem alloc]
|
||||
initWithTitle:Ladybird::string_to_ns_string(item.label.value_or(""_string))
|
||||
action:nil
|
||||
keyEquivalent:@""];
|
||||
subtitle.enabled = false;
|
||||
[menu addItem:subtitle];
|
||||
|
||||
for (auto const& item : *item.items) {
|
||||
[self selectDropdownAdd:menu
|
||||
item:item];
|
||||
}
|
||||
}
|
||||
if (item.type == Web::HTML::SelectItem::Type::Option) {
|
||||
NSMenuItem* menuItem = [[NSMenuItem alloc]
|
||||
initWithTitle:Ladybird::string_to_ns_string(item.label.value_or(""_string))
|
||||
action:@selector(selectDropdownAction:)
|
||||
keyEquivalent:@""];
|
||||
[menuItem setRepresentedObject:Ladybird::string_to_ns_string(item.value.value_or(""_string))];
|
||||
[menuItem setEnabled:YES];
|
||||
[menuItem setState:item.selected ? NSControlStateValueOn : NSControlStateValueOff];
|
||||
[menu addItem:menuItem];
|
||||
}
|
||||
if (item.type == Web::HTML::SelectItem::Type::Separator) {
|
||||
[menu addItem:[NSMenuItem separatorItem]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)selectDropdownAction:(NSMenuItem*)menuItem
|
||||
{
|
||||
auto value = Ladybird::ns_string_to_string([menuItem representedObject]);
|
||||
m_web_view_bridge->select_dropdown_closed(value);
|
||||
}
|
||||
|
||||
- (void)menuDidClose:(NSMenu*)menu
|
||||
{
|
||||
if (!menu.highlightedItem)
|
||||
m_web_view_bridge->select_dropdown_closed({});
|
||||
}
|
||||
|
||||
- (void)colorPickerClosed:(NSNotification*)notification
|
||||
{
|
||||
m_web_view_bridge->color_picker_closed(Ladybird::ns_color_to_gfx_color([[NSColorPanel sharedColorPanel] color]));
|
||||
|
|
|
@ -226,6 +226,22 @@ Tab::Tab(BrowserWindow* window, WebContentOptions const& web_content_options, St
|
|||
m_dialog = nullptr;
|
||||
};
|
||||
|
||||
m_select_dropdown = new QMenu("Select Dropdown", this);
|
||||
QObject::connect(m_select_dropdown, &QMenu::aboutToHide, this, [this]() {
|
||||
if (!m_select_dropdown->activeAction())
|
||||
view().select_dropdown_closed({});
|
||||
});
|
||||
|
||||
view().on_request_select_dropdown = [this](Gfx::IntPoint content_position, i32 minimum_width, Vector<Web::HTML::SelectItem> items) {
|
||||
m_select_dropdown->clear();
|
||||
m_select_dropdown->setMinimumWidth(minimum_width);
|
||||
for (auto const& item : items) {
|
||||
select_dropdown_add_item(m_select_dropdown, item);
|
||||
}
|
||||
|
||||
m_select_dropdown->exec(mapToGlobal(QPoint(content_position.x(), content_position.y())));
|
||||
};
|
||||
|
||||
QObject::connect(focus_location_editor_action, &QAction::triggered, this, &Tab::focus_location_editor);
|
||||
|
||||
view().on_received_source = [this](auto const& url, auto const& source) {
|
||||
|
@ -569,6 +585,37 @@ Tab::~Tab()
|
|||
close_sub_widgets();
|
||||
}
|
||||
|
||||
void Tab::select_dropdown_add_item(QMenu* menu, Web::HTML::SelectItem const& item)
|
||||
{
|
||||
if (item.type == Web::HTML::SelectItem::Type::OptionGroup) {
|
||||
QAction* subtitle = new QAction(qstring_from_ak_string(item.label.value_or(""_string)), this);
|
||||
subtitle->setDisabled(true);
|
||||
menu->addAction(subtitle);
|
||||
|
||||
for (auto const& item : *item.items) {
|
||||
select_dropdown_add_item(menu, item);
|
||||
}
|
||||
}
|
||||
if (item.type == Web::HTML::SelectItem::Type::Option) {
|
||||
QAction* action = new QAction(qstring_from_ak_string(item.label.value_or(""_string)), this);
|
||||
action->setCheckable(true);
|
||||
action->setChecked(item.selected);
|
||||
action->setData(QVariant(qstring_from_ak_string(item.value.value_or(""_string))));
|
||||
QObject::connect(action, &QAction::triggered, this, &Tab::select_dropdown_action);
|
||||
menu->addAction(action);
|
||||
}
|
||||
if (item.type == Web::HTML::SelectItem::Type::Separator) {
|
||||
menu->addSeparator();
|
||||
}
|
||||
}
|
||||
|
||||
void Tab::select_dropdown_action()
|
||||
{
|
||||
QAction* action = qobject_cast<QAction*>(sender());
|
||||
auto value = action->data().value<QString>();
|
||||
view().select_dropdown_closed(ak_string_from_qstring(value));
|
||||
}
|
||||
|
||||
void Tab::update_reset_zoom_button()
|
||||
{
|
||||
auto zoom_level = view().zoom_level();
|
||||
|
|
|
@ -54,12 +54,15 @@ public:
|
|||
public slots:
|
||||
void focus_location_editor();
|
||||
void location_edit_return_pressed();
|
||||
void select_dropdown_action();
|
||||
|
||||
signals:
|
||||
void title_changed(int id, QString);
|
||||
void favicon_changed(int id, QIcon);
|
||||
|
||||
private:
|
||||
void select_dropdown_add_item(QMenu* menu, Web::HTML::SelectItem const& item);
|
||||
|
||||
virtual void resizeEvent(QResizeEvent*) override;
|
||||
virtual bool event(QEvent*) override;
|
||||
|
||||
|
@ -106,6 +109,8 @@ private:
|
|||
QAction* m_media_context_menu_loop_action { nullptr };
|
||||
URL m_media_context_menu_url;
|
||||
|
||||
QMenu* m_select_dropdown { nullptr };
|
||||
|
||||
int tab_index();
|
||||
|
||||
bool m_is_history_navigation { false };
|
||||
|
|
|
@ -140,6 +140,7 @@ source_set("HTML") {
|
|||
"PotentialCORSRequest.cpp",
|
||||
"PromiseRejectionEvent.cpp",
|
||||
"RemoteBrowsingContext.cpp",
|
||||
"SelectItem.cpp",
|
||||
"SessionHistoryEntry.cpp",
|
||||
"SharedImageRequest.cpp",
|
||||
"SourceSet.cpp",
|
||||
|
|
5
Tests/LibWeb/Text/expected/select.txt
Normal file
5
Tests/LibWeb/Text/expected/select.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
1. "one"
|
||||
2. 0
|
||||
3. "two"
|
||||
4. 3
|
||||
5. "three"
|
67
Tests/LibWeb/Text/input/select.html
Normal file
67
Tests/LibWeb/Text/input/select.html
Normal file
|
@ -0,0 +1,67 @@
|
|||
<script src="include.js"></script>
|
||||
<script>
|
||||
test(() => {
|
||||
let testCounter = 1;
|
||||
function testPart(part) {
|
||||
println(`${testCounter++}. ${JSON.stringify(part())}`);
|
||||
}
|
||||
|
||||
// 1. Get select value of unedited
|
||||
testPart(() => {
|
||||
const select = document.createElement('select');
|
||||
// FIXME: Remove selected attribute (currently this is needed because the DIN children are not loaded to select first item in test run)
|
||||
select.innerHTML = `
|
||||
<option value="one" selected>One</option>
|
||||
<option value="two">Two</option>
|
||||
<option value="three">Three</option>
|
||||
`;
|
||||
return select.value;
|
||||
});
|
||||
|
||||
// 2. Get select selectedIndex
|
||||
testPart(() => {
|
||||
const select = document.createElement('select');
|
||||
// FIXME: Remove selected attribute (currently this is needed because the DIN children are not loaded to select first item in test run)
|
||||
select.innerHTML = `
|
||||
<option value="one" selected>One</option>
|
||||
<option value="two">Two</option>
|
||||
<option value="three">Three</option>
|
||||
`;
|
||||
return select.selectedIndex;
|
||||
});
|
||||
|
||||
// 3. Get select value of selectedIndex
|
||||
testPart(() => {
|
||||
const select = document.createElement('select');
|
||||
select.innerHTML = `
|
||||
<option value="one">One</option>
|
||||
<option value="two">Two</option>
|
||||
<option value="three">Three</option>
|
||||
`;
|
||||
select.selectedIndex = 1;
|
||||
return select.value;
|
||||
});
|
||||
|
||||
// 4. Get select length
|
||||
testPart(() => {
|
||||
const select = document.createElement('select');
|
||||
select.innerHTML = `
|
||||
<option value="one">One</option>
|
||||
<option value="two">Two</option>
|
||||
<option value="three">Three</option>
|
||||
`;
|
||||
return select.length;
|
||||
});
|
||||
|
||||
// 5. Get select value of init selected
|
||||
testPart(() => {
|
||||
const select = document.createElement('select');
|
||||
select.innerHTML = `
|
||||
<option value="one">One</option>
|
||||
<option value="two">Two</option>
|
||||
<option value="three" selected>Three</option>
|
||||
`;
|
||||
return select.value;
|
||||
});
|
||||
});
|
||||
</script>
|
|
@ -578,6 +578,23 @@ Tab::Tab(BrowserWindow& window)
|
|||
m_dialog = nullptr;
|
||||
};
|
||||
|
||||
m_select_dropdown = GUI::Menu::construct();
|
||||
m_select_dropdown->on_visibility_change = [this](bool visible) {
|
||||
if (!visible && !m_select_dropdown_closed_by_action)
|
||||
view().select_dropdown_closed({});
|
||||
};
|
||||
|
||||
view().on_request_select_dropdown = [this](Gfx::IntPoint content_position, i32, Vector<Web::HTML::SelectItem> items) {
|
||||
m_select_dropdown_closed_by_action = false;
|
||||
m_select_dropdown->remove_all_actions();
|
||||
// FIXME: Set menu minimum width
|
||||
for (auto const& item : items) {
|
||||
select_dropdown_add_item(*m_select_dropdown, item);
|
||||
}
|
||||
|
||||
m_select_dropdown->popup(view().screen_relative_rect().location().translated(content_position));
|
||||
};
|
||||
|
||||
view().on_received_source = [this](auto& url, auto& source) {
|
||||
view_source(url, source);
|
||||
};
|
||||
|
@ -699,6 +716,31 @@ Tab::Tab(BrowserWindow& window)
|
|||
};
|
||||
}
|
||||
|
||||
void Tab::select_dropdown_add_item(GUI::Menu& menu, Web::HTML::SelectItem const& item)
|
||||
{
|
||||
if (item.type == Web::HTML::SelectItem::Type::OptionGroup) {
|
||||
auto subtitle = GUI::Action::create(MUST(DeprecatedString::from_utf8(item.label.value_or(""_string))), nullptr);
|
||||
subtitle->set_enabled(false);
|
||||
menu.add_action(subtitle);
|
||||
|
||||
for (auto const& item : *item.items) {
|
||||
select_dropdown_add_item(menu, item);
|
||||
}
|
||||
}
|
||||
if (item.type == Web::HTML::SelectItem::Type::Option) {
|
||||
auto action = GUI::Action::create(MUST(DeprecatedString::from_utf8(item.label.value_or(""_string))), [this, item](GUI::Action&) {
|
||||
m_select_dropdown_closed_by_action = true;
|
||||
view().select_dropdown_closed(item.value.value_or(""_string));
|
||||
});
|
||||
action->set_checkable(true);
|
||||
action->set_checked(item.selected);
|
||||
menu.add_action(action);
|
||||
}
|
||||
if (item.type == Web::HTML::SelectItem::Type::Separator) {
|
||||
menu.add_separator();
|
||||
}
|
||||
}
|
||||
|
||||
void Tab::update_reset_zoom_button()
|
||||
{
|
||||
auto zoom_level = view().zoom_level();
|
||||
|
|
|
@ -115,6 +115,8 @@ private:
|
|||
void update_status(Optional<String> text_override = {}, i32 count_waiting = 0);
|
||||
void close_sub_widgets();
|
||||
|
||||
void select_dropdown_add_item(GUI::Menu& menu, Web::HTML::SelectItem const& item);
|
||||
|
||||
WebView::History m_history;
|
||||
|
||||
RefPtr<WebView::OutOfProcessWebView> m_web_content_view;
|
||||
|
@ -155,6 +157,9 @@ private:
|
|||
RefPtr<GUI::Menu> m_go_back_context_menu;
|
||||
RefPtr<GUI::Menu> m_go_forward_context_menu;
|
||||
|
||||
RefPtr<GUI::Menu> m_select_dropdown;
|
||||
bool m_select_dropdown_closed_by_action { false };
|
||||
|
||||
DeprecatedString m_title;
|
||||
RefPtr<Gfx::Bitmap const> m_icon;
|
||||
|
||||
|
|
|
@ -384,6 +384,7 @@ set(SOURCES
|
|||
HTML/Scripting/TemporaryExecutionContext.cpp
|
||||
HTML/Scripting/WindowEnvironmentSettingsObject.cpp
|
||||
HTML/Scripting/WorkerEnvironmentSettingsObject.cpp
|
||||
HTML/SelectItem.cpp
|
||||
HTML/SessionHistoryEntry.cpp
|
||||
HTML/SharedImageRequest.cpp
|
||||
HTML/SourceSet.cpp
|
||||
|
|
|
@ -1,15 +1,27 @@
|
|||
/*
|
||||
* Copyright (c) 2020, the SerenityOS developers.
|
||||
* Copyright (c) 2021-2022, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2023, Bastiaan van der Plaat <bastiaan.v.d.plaat@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/DOM/ElementFactory.h>
|
||||
#include <LibWeb/DOM/Event.h>
|
||||
#include <LibWeb/DOM/ShadowRoot.h>
|
||||
#include <LibWeb/HTML/EventNames.h>
|
||||
#include <LibWeb/HTML/HTMLFormElement.h>
|
||||
#include <LibWeb/HTML/HTMLHRElement.h>
|
||||
#include <LibWeb/HTML/HTMLOptGroupElement.h>
|
||||
#include <LibWeb/HTML/HTMLOptionElement.h>
|
||||
#include <LibWeb/HTML/HTMLSelectElement.h>
|
||||
#include <LibWeb/Infra/Strings.h>
|
||||
#include <LibWeb/Layout/Node.h>
|
||||
#include <LibWeb/Namespace.h>
|
||||
#include <LibWeb/Page/Page.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
|
@ -32,6 +44,17 @@ void HTMLSelectElement::visit_edges(Cell::Visitor& visitor)
|
|||
{
|
||||
Base::visit_edges(visitor);
|
||||
visitor.visit(m_options);
|
||||
visitor.visit(m_inner_text_element);
|
||||
}
|
||||
|
||||
JS::GCPtr<Layout::Node> HTMLSelectElement::create_layout_node(NonnullRefPtr<CSS::StyleProperties> style)
|
||||
{
|
||||
// AD-HOC: We rewrite `display: inline` to `display: inline-block`.
|
||||
// This is required for the internal shadow tree to work correctly in layout.
|
||||
if (style->display().is_inline_outside() && style->display().is_flow_inside())
|
||||
style->set_property(CSS::PropertyID::Display, CSS::DisplayStyleValue::create(CSS::Display::from_short(CSS::Display::Short::InlineBlock)));
|
||||
|
||||
return Element::create_layout_node_for_display_type(document(), style->display(), style, this);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/form-elements.html#dom-select-options
|
||||
|
@ -177,6 +200,39 @@ Optional<ARIA::Role> HTMLSelectElement::default_role() const
|
|||
return ARIA::Role::combobox;
|
||||
}
|
||||
|
||||
String HTMLSelectElement::value() const
|
||||
{
|
||||
for (auto const& option_element : list_of_options())
|
||||
if (option_element->selected())
|
||||
return option_element->value();
|
||||
return ""_string;
|
||||
}
|
||||
|
||||
WebIDL::ExceptionOr<void> HTMLSelectElement::set_value(String const& value)
|
||||
{
|
||||
for (auto const& option_element : list_of_options())
|
||||
option_element->set_selected(option_element->value() == value);
|
||||
update_inner_text_element();
|
||||
document().invalidate_layout();
|
||||
|
||||
// When the user agent is to send select update notifications, queue an element task on the user interaction task source given the select element to run these steps:
|
||||
queue_an_element_task(HTML::Task::Source::UserInteraction, [this] {
|
||||
// FIXME: 1. Set the select element's user interacted to true.
|
||||
|
||||
// 2. Fire an event named input at the select element, with the bubbles and composed attributes initialized to true.
|
||||
auto input_event = DOM::Event::create(realm(), HTML::EventNames::input);
|
||||
input_event->set_bubbles(true);
|
||||
input_event->set_composed(true);
|
||||
dispatch_event(input_event);
|
||||
|
||||
// 3. Fire an event named change at the select element, with the bubbles attribute initialized to true.
|
||||
auto change_event = DOM::Event::create(realm(), HTML::EventNames::change);
|
||||
change_event->set_bubbles(true);
|
||||
dispatch_event(*change_event);
|
||||
});
|
||||
return {};
|
||||
}
|
||||
|
||||
void HTMLSelectElement::set_is_open(bool open)
|
||||
{
|
||||
if (open == m_is_open)
|
||||
|
@ -186,4 +242,139 @@ void HTMLSelectElement::set_is_open(bool open)
|
|||
invalidate_style();
|
||||
}
|
||||
|
||||
bool HTMLSelectElement::has_activation_behavior() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static Optional<String> strip_newlines(Optional<String> string)
|
||||
{
|
||||
// FIXME: Move this to a more general function
|
||||
if (!string.has_value())
|
||||
return {};
|
||||
|
||||
StringBuilder builder;
|
||||
for (auto c : string.value().bytes_as_string_view()) {
|
||||
if (c == '\r' || c == '\n') {
|
||||
builder.append(' ');
|
||||
} else {
|
||||
builder.append(c);
|
||||
}
|
||||
}
|
||||
return MUST(Infra::strip_and_collapse_whitespace(MUST(builder.to_string())));
|
||||
}
|
||||
|
||||
void HTMLSelectElement::activation_behavior(DOM::Event const&)
|
||||
{
|
||||
// Populate select items
|
||||
Vector<SelectItem> items;
|
||||
for (auto const& child : children_as_vector()) {
|
||||
if (is<HTMLOptGroupElement>(*child)) {
|
||||
auto& opt_group_element = verify_cast<HTMLOptGroupElement>(*child);
|
||||
Vector<SelectItem> opt_group_items;
|
||||
for (auto const& child : opt_group_element.children_as_vector()) {
|
||||
if (is<HTMLOptionElement>(*child)) {
|
||||
auto& option_element = verify_cast<HTMLOptionElement>(*child);
|
||||
auto option_value = option_element.value();
|
||||
opt_group_items.append(SelectItem { SelectItem::Type::Option, strip_newlines(option_element.text_content()), option_value, {}, option_element.selected() });
|
||||
}
|
||||
if (is<HTMLHRElement>(*child)) {
|
||||
opt_group_items.append(SelectItem { SelectItem::Type::Separator });
|
||||
}
|
||||
}
|
||||
items.append(SelectItem { SelectItem::Type::OptionGroup, opt_group_element.get_attribute(AttributeNames::label), {}, opt_group_items });
|
||||
}
|
||||
|
||||
if (is<HTMLOptionElement>(*child)) {
|
||||
auto& option_element = verify_cast<HTMLOptionElement>(*child);
|
||||
auto option_value = option_element.value();
|
||||
items.append(SelectItem { SelectItem::Type::Option, strip_newlines(option_element.text_content()), option_value, {}, option_element.selected() });
|
||||
}
|
||||
if (is<HTMLHRElement>(*child)) {
|
||||
items.append(SelectItem { SelectItem::Type::Separator });
|
||||
}
|
||||
}
|
||||
|
||||
// Request select dropdown
|
||||
auto weak_element = make_weak_ptr<HTMLSelectElement>();
|
||||
auto rect = get_bounding_client_rect();
|
||||
document().browsing_context()->top_level_browsing_context()->page().did_request_select_dropdown(weak_element, Gfx::IntPoint { rect->x(), rect->y() }, rect->width(), items);
|
||||
set_is_open(true);
|
||||
}
|
||||
|
||||
void HTMLSelectElement::did_select_value(Optional<String> value)
|
||||
{
|
||||
set_is_open(false);
|
||||
if (value.has_value()) {
|
||||
MUST(set_value(*value));
|
||||
}
|
||||
}
|
||||
|
||||
void HTMLSelectElement::form_associated_element_was_inserted()
|
||||
{
|
||||
create_shadow_tree_if_needed();
|
||||
|
||||
// Wait until children are ready
|
||||
queue_an_element_task(HTML::Task::Source::Microtask, [this] {
|
||||
// Select first option when no other option is selected
|
||||
if (selected_index() == -1) {
|
||||
auto options = list_of_options();
|
||||
if (options.size() > 0) {
|
||||
options.at(0)->set_selected(true);
|
||||
update_inner_text_element();
|
||||
document().invalidate_layout();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void HTMLSelectElement::form_associated_element_was_removed(DOM::Node*)
|
||||
{
|
||||
set_shadow_root(nullptr);
|
||||
}
|
||||
|
||||
void HTMLSelectElement::create_shadow_tree_if_needed()
|
||||
{
|
||||
if (shadow_root_internal())
|
||||
return;
|
||||
|
||||
auto shadow_root = heap().allocate<DOM::ShadowRoot>(realm(), document(), *this, Bindings::ShadowRootMode::Closed);
|
||||
set_shadow_root(shadow_root);
|
||||
|
||||
auto border = DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML).release_value_but_fixme_should_propagate_errors();
|
||||
MUST(border->set_attribute(HTML::AttributeNames::style, R"~~~(
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
padding: 4px;
|
||||
border: 1px solid ButtonBorder;
|
||||
background-color: ButtonFace;
|
||||
)~~~"_string));
|
||||
MUST(shadow_root->append_child(border));
|
||||
|
||||
m_inner_text_element = DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML).release_value_but_fixme_should_propagate_errors();
|
||||
MUST(m_inner_text_element->set_attribute(HTML::AttributeNames::style, R"~~~(
|
||||
flex: 1;
|
||||
)~~~"_string));
|
||||
MUST(border->append_child(*m_inner_text_element));
|
||||
|
||||
// FIXME: Find better way to add chevron icon
|
||||
auto chevron_icon_element = DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML).release_value_but_fixme_should_propagate_errors();
|
||||
MUST(chevron_icon_element->set_inner_html("<svg style=\"width: 16px; height: 16px;\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8.58Z\" /></svg>"sv));
|
||||
MUST(border->append_child(*chevron_icon_element));
|
||||
|
||||
update_inner_text_element();
|
||||
}
|
||||
|
||||
void HTMLSelectElement::update_inner_text_element()
|
||||
{
|
||||
// Update inner text element to text content of selected option
|
||||
for (auto const& option_element : list_of_options()) {
|
||||
if (option_element->selected()) {
|
||||
m_inner_text_element->set_text_content(strip_newlines(option_element->text_content()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
* Copyright (c) 2020, the SerenityOS developers.
|
||||
* Copyright (c) 2021-2022, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2022, Luke Wilde <lukew@serenityos.org>
|
||||
* Copyright (c) 2023, Bastiaan van der Plaat <bastiaan.v.d.plaat@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -24,6 +25,8 @@ class HTMLSelectElement final
|
|||
public:
|
||||
virtual ~HTMLSelectElement() override;
|
||||
|
||||
virtual JS::GCPtr<Layout::Node> create_layout_node(NonnullRefPtr<CSS::StyleProperties>) override;
|
||||
|
||||
JS::GCPtr<HTMLOptionsCollection> const& options();
|
||||
|
||||
size_t length();
|
||||
|
@ -34,6 +37,9 @@ public:
|
|||
int selected_index() const;
|
||||
void set_selected_index(int);
|
||||
|
||||
virtual String value() const override;
|
||||
WebIDL::ExceptionOr<void> set_value(String const&);
|
||||
|
||||
bool is_open() const { return m_is_open; }
|
||||
void set_is_open(bool);
|
||||
|
||||
|
@ -66,6 +72,14 @@ public:
|
|||
|
||||
virtual Optional<ARIA::Role> default_role() const override;
|
||||
|
||||
virtual bool has_activation_behavior() const override;
|
||||
virtual void activation_behavior(DOM::Event const&) override;
|
||||
|
||||
virtual void form_associated_element_was_inserted() override;
|
||||
virtual void form_associated_element_was_removed(DOM::Node*) override;
|
||||
|
||||
void did_select_value(Optional<String> value);
|
||||
|
||||
private:
|
||||
HTMLSelectElement(DOM::Document&, DOM::QualifiedName);
|
||||
|
||||
|
@ -75,8 +89,12 @@ private:
|
|||
// ^DOM::Element
|
||||
virtual i32 default_tab_index_value() const override;
|
||||
|
||||
void create_shadow_tree_if_needed();
|
||||
void update_inner_text_element();
|
||||
|
||||
JS::GCPtr<HTMLOptionsCollection> m_options;
|
||||
bool m_is_open { false };
|
||||
JS::GCPtr<DOM::Element> m_inner_text_element;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ interface HTMLSelectElement : HTMLElement {
|
|||
|
||||
// FIXME: [SameObject] readonly attribute HTMLCollection selectedOptions;
|
||||
attribute long selectedIndex;
|
||||
// FIXME: attribute DOMString value;
|
||||
attribute DOMString value;
|
||||
|
||||
// FIXME: readonly attribute boolean willValidate;
|
||||
// FIXME: readonly attribute ValidityState validity;
|
||||
|
|
31
Userland/Libraries/LibWeb/HTML/SelectItem.cpp
Normal file
31
Userland/Libraries/LibWeb/HTML/SelectItem.cpp
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Bastiaan van der Plaat <bastiaan.v.d.plaat@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "SelectItem.h"
|
||||
#include <LibIPC/Decoder.h>
|
||||
#include <LibIPC/Encoder.h>
|
||||
|
||||
template<>
|
||||
ErrorOr<void> IPC::encode(Encoder& encoder, Web::HTML::SelectItem const& select_item)
|
||||
{
|
||||
TRY(encoder.encode(select_item.type));
|
||||
TRY(encoder.encode(select_item.label));
|
||||
TRY(encoder.encode(select_item.value));
|
||||
TRY(encoder.encode(select_item.items));
|
||||
TRY(encoder.encode(select_item.selected));
|
||||
return {};
|
||||
}
|
||||
|
||||
template<>
|
||||
ErrorOr<Web::HTML::SelectItem> IPC::decode(Decoder& decoder)
|
||||
{
|
||||
auto type = TRY(decoder.decode<Web::HTML::SelectItem::Type>());
|
||||
auto label = TRY(decoder.decode<Optional<String>>());
|
||||
auto value = TRY(decoder.decode<Optional<String>>());
|
||||
auto items = TRY(decoder.decode<Optional<Vector<Web::HTML::SelectItem>>>());
|
||||
auto selected = TRY(decoder.decode<bool>());
|
||||
return Web::HTML::SelectItem { type, move(label), move(value), move(items), selected };
|
||||
}
|
38
Userland/Libraries/LibWeb/HTML/SelectItem.h
Normal file
38
Userland/Libraries/LibWeb/HTML/SelectItem.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Bastiaan van der Plaat <bastiaan.v.d.plaat@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/String.h>
|
||||
#include <LibIPC/Forward.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
struct SelectItem {
|
||||
enum class Type {
|
||||
OptionGroup,
|
||||
Option,
|
||||
Separator,
|
||||
};
|
||||
|
||||
Type type;
|
||||
Optional<String> label = {};
|
||||
Optional<String> value = {};
|
||||
Optional<Vector<SelectItem>> items = {};
|
||||
bool selected = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace IPC {
|
||||
|
||||
template<>
|
||||
ErrorOr<void> encode(Encoder&, Web::HTML::SelectItem const&);
|
||||
|
||||
template<>
|
||||
ErrorOr<Web::HTML::SelectItem> decode(Decoder&);
|
||||
|
||||
}
|
|
@ -15,6 +15,7 @@
|
|||
#include <LibWeb/HTML/EventLoop/EventLoop.h>
|
||||
#include <LibWeb/HTML/HTMLInputElement.h>
|
||||
#include <LibWeb/HTML/HTMLMediaElement.h>
|
||||
#include <LibWeb/HTML/HTMLSelectElement.h>
|
||||
#include <LibWeb/HTML/Scripting/Environments.h>
|
||||
#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
|
||||
#include <LibWeb/HTML/TraversableNavigable.h>
|
||||
|
@ -323,7 +324,30 @@ void Page::color_picker_closed(Optional<Color> picked_color)
|
|||
m_pending_non_blocking_dialog = PendingNonBlockingDialog::None;
|
||||
|
||||
if (m_pending_non_blocking_dialog_target) {
|
||||
m_pending_non_blocking_dialog_target->did_pick_color(picked_color);
|
||||
auto& input_element = verify_cast<HTML::HTMLInputElement>(*m_pending_non_blocking_dialog_target);
|
||||
input_element.did_pick_color(move(picked_color));
|
||||
m_pending_non_blocking_dialog_target.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Page::did_request_select_dropdown(WeakPtr<HTML::HTMLSelectElement> target, Gfx::IntPoint content_position, i32 minimum_width, Vector<Web::HTML::SelectItem> items)
|
||||
{
|
||||
if (m_pending_non_blocking_dialog == PendingNonBlockingDialog::None) {
|
||||
m_pending_non_blocking_dialog = PendingNonBlockingDialog::Select;
|
||||
m_pending_non_blocking_dialog_target = move(target);
|
||||
m_client->page_did_request_select_dropdown(content_position, minimum_width, move(items));
|
||||
}
|
||||
}
|
||||
|
||||
void Page::select_dropdown_closed(Optional<String> value)
|
||||
{
|
||||
if (m_pending_non_blocking_dialog == PendingNonBlockingDialog::Select) {
|
||||
m_pending_non_blocking_dialog = PendingNonBlockingDialog::None;
|
||||
|
||||
if (m_pending_non_blocking_dialog_target) {
|
||||
auto& select_element = verify_cast<HTML::HTMLSelectElement>(*m_pending_non_blocking_dialog_target);
|
||||
select_element.did_select_value(move(value));
|
||||
m_pending_non_blocking_dialog_target.clear();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include <LibWeb/Cookie/Cookie.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
#include <LibWeb/HTML/ActivateTab.h>
|
||||
#include <LibWeb/HTML/SelectItem.h>
|
||||
#include <LibWeb/Loader/FileRequest.h>
|
||||
#include <LibWeb/PixelUnits.h>
|
||||
|
||||
|
@ -128,9 +129,13 @@ public:
|
|||
void did_request_color_picker(WeakPtr<HTML::HTMLInputElement> target, Color current_color);
|
||||
void color_picker_closed(Optional<Color> picked_color);
|
||||
|
||||
void did_request_select_dropdown(WeakPtr<HTML::HTMLSelectElement> target, Gfx::IntPoint content_position, i32 minimum_width, Vector<Web::HTML::SelectItem> items);
|
||||
void select_dropdown_closed(Optional<String> value);
|
||||
|
||||
enum class PendingNonBlockingDialog {
|
||||
None,
|
||||
ColorPicker,
|
||||
Select,
|
||||
};
|
||||
|
||||
struct MediaContextMenu {
|
||||
|
@ -185,7 +190,7 @@ private:
|
|||
Optional<Optional<String>> m_pending_prompt_response;
|
||||
|
||||
PendingNonBlockingDialog m_pending_non_blocking_dialog { PendingNonBlockingDialog::None };
|
||||
WeakPtr<HTML::HTMLInputElement> m_pending_non_blocking_dialog_target;
|
||||
WeakPtr<HTML::HTMLElement> m_pending_non_blocking_dialog_target;
|
||||
|
||||
Optional<int> m_media_context_menu_element_id;
|
||||
|
||||
|
@ -272,6 +277,7 @@ public:
|
|||
// https://html.spec.whatwg.org/multipage/input.html#show-the-picker,-if-applicable
|
||||
virtual void page_did_request_file_picker(WeakPtr<DOM::EventTarget>, [[maybe_unused]] bool multiple) {};
|
||||
virtual void page_did_request_color_picker([[maybe_unused]] Color current_color) {};
|
||||
virtual void page_did_request_select_dropdown([[maybe_unused]] Gfx::IntPoint content_position, [[maybe_unused]] i32 minimum_width, [[maybe_unused]] Vector<Web::HTML::SelectItem> items) {};
|
||||
|
||||
virtual void page_did_finish_text_test() {};
|
||||
|
||||
|
|
|
@ -266,6 +266,11 @@ void ViewImplementation::color_picker_closed(Optional<Color> picked_color)
|
|||
client().async_color_picker_closed(picked_color);
|
||||
}
|
||||
|
||||
void ViewImplementation::select_dropdown_closed(Optional<String> value)
|
||||
{
|
||||
client().async_select_dropdown_closed(value);
|
||||
}
|
||||
|
||||
void ViewImplementation::toggle_media_play_state()
|
||||
{
|
||||
client().async_toggle_media_play_state();
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <LibGfx/StandardCursor.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
#include <LibWeb/HTML/ActivateTab.h>
|
||||
#include <LibWeb/HTML/SelectItem.h>
|
||||
#include <LibWebView/Forward.h>
|
||||
#include <LibWebView/WebContentClient.h>
|
||||
|
||||
|
@ -83,6 +84,7 @@ public:
|
|||
void confirm_closed(bool accepted);
|
||||
void prompt_closed(Optional<String> response);
|
||||
void color_picker_closed(Optional<Color> picked_color);
|
||||
void select_dropdown_closed(Optional<String> value);
|
||||
|
||||
void toggle_media_play_state();
|
||||
void toggle_media_mute_state();
|
||||
|
@ -154,6 +156,7 @@ public:
|
|||
Function<Gfx::IntRect()> on_minimize_window;
|
||||
Function<Gfx::IntRect()> on_fullscreen_window;
|
||||
Function<void(Color current_color)> on_request_color_picker;
|
||||
Function<void(Gfx::IntPoint content_position, i32 minimum_width, Vector<Web::HTML::SelectItem> items)> on_request_select_dropdown;
|
||||
Function<void(bool)> on_finish_handling_input_event;
|
||||
Function<void()> on_text_test_finish;
|
||||
Function<void(Gfx::Color)> on_theme_color_change;
|
||||
|
|
|
@ -384,6 +384,12 @@ void WebContentClient::did_request_color_picker(Color const& current_color)
|
|||
m_view.on_request_color_picker(current_color);
|
||||
}
|
||||
|
||||
void WebContentClient::did_request_select_dropdown(Gfx::IntPoint content_position, i32 minimum_width, Vector<Web::HTML::SelectItem> const& items)
|
||||
{
|
||||
if (m_view.on_request_select_dropdown)
|
||||
m_view.on_request_select_dropdown(content_position, minimum_width, items);
|
||||
}
|
||||
|
||||
void WebContentClient::did_finish_handling_input_event(bool event_was_accepted)
|
||||
{
|
||||
if (m_view.on_finish_handling_input_event)
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <AK/HashMap.h>
|
||||
#include <LibIPC/ConnectionToServer.h>
|
||||
#include <LibWeb/HTML/ActivateTab.h>
|
||||
#include <LibWeb/HTML/SelectItem.h>
|
||||
#include <WebContent/WebContentClientEndpoint.h>
|
||||
#include <WebContent/WebContentServerEndpoint.h>
|
||||
|
||||
|
@ -82,6 +83,7 @@ private:
|
|||
virtual Messages::WebContentClient::DidRequestFullscreenWindowResponse did_request_fullscreen_window() override;
|
||||
virtual void did_request_file(DeprecatedString const& path, i32) override;
|
||||
virtual void did_request_color_picker(Color const& current_color) override;
|
||||
virtual void did_request_select_dropdown(Gfx::IntPoint content_position, i32 minimum_width, Vector<Web::HTML::SelectItem> const& items) override;
|
||||
virtual void did_finish_handling_input_event(bool event_was_accepted) override;
|
||||
virtual void did_finish_text_test() override;
|
||||
virtual void did_change_theme_color(Gfx::Color color) override;
|
||||
|
|
|
@ -1052,6 +1052,11 @@ void ConnectionFromClient::color_picker_closed(Optional<Color> const& picked_col
|
|||
page().page().color_picker_closed(picked_color);
|
||||
}
|
||||
|
||||
void ConnectionFromClient::select_dropdown_closed(Optional<String> const& value)
|
||||
{
|
||||
page().page().select_dropdown_closed(value);
|
||||
}
|
||||
|
||||
void ConnectionFromClient::toggle_media_play_state()
|
||||
{
|
||||
page().page().toggle_media_play_state().release_value_but_fixme_should_propagate_errors();
|
||||
|
|
|
@ -110,6 +110,7 @@ private:
|
|||
virtual void confirm_closed(bool accepted) override;
|
||||
virtual void prompt_closed(Optional<String> const& response) override;
|
||||
virtual void color_picker_closed(Optional<Color> const& picked_color) override;
|
||||
virtual void select_dropdown_closed(Optional<String> const& value) override;
|
||||
|
||||
virtual void toggle_media_play_state() override;
|
||||
virtual void toggle_media_mute_state() override;
|
||||
|
|
|
@ -404,6 +404,11 @@ void PageClient::color_picker_closed(Optional<Color> picked_color)
|
|||
page().color_picker_closed(picked_color);
|
||||
}
|
||||
|
||||
void PageClient::select_dropdown_closed(Optional<String> value)
|
||||
{
|
||||
page().select_dropdown_closed(value);
|
||||
}
|
||||
|
||||
Web::WebIDL::ExceptionOr<void> PageClient::toggle_media_play_state()
|
||||
{
|
||||
return page().toggle_media_play_state();
|
||||
|
@ -504,6 +509,11 @@ void PageClient::page_did_request_color_picker(Color current_color)
|
|||
client().async_did_request_color_picker(current_color);
|
||||
}
|
||||
|
||||
void PageClient::page_did_request_select_dropdown(Gfx::IntPoint content_position, i32 minimum_width, Vector<Web::HTML::SelectItem> items)
|
||||
{
|
||||
client().async_did_request_select_dropdown(content_position, minimum_width, items);
|
||||
}
|
||||
|
||||
void PageClient::page_did_change_theme_color(Gfx::Color color)
|
||||
{
|
||||
client().async_did_change_theme_color(color);
|
||||
|
|
|
@ -53,6 +53,7 @@ public:
|
|||
void confirm_closed(bool accepted);
|
||||
void prompt_closed(Optional<String> response);
|
||||
void color_picker_closed(Optional<Color> picked_color);
|
||||
void select_dropdown_closed(Optional<String> value);
|
||||
|
||||
[[nodiscard]] Gfx::Color background_color() const;
|
||||
|
||||
|
@ -118,6 +119,7 @@ private:
|
|||
virtual void page_did_close_browsing_context(Web::HTML::BrowsingContext const&) override;
|
||||
virtual void request_file(Web::FileRequest) override;
|
||||
virtual void page_did_request_color_picker(Color current_color) override;
|
||||
virtual void page_did_request_select_dropdown(Gfx::IntPoint content_position, i32 minimum_width, Vector<Web::HTML::SelectItem> items) override;
|
||||
virtual void page_did_finish_text_test() override;
|
||||
virtual void page_did_change_theme_color(Gfx::Color color) override;
|
||||
virtual void page_did_insert_clipboard_entry(String data, String presentation_style, String mime_type) override;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <LibWeb/Cookie/ParsedCookie.h>
|
||||
#include <LibWeb/CSS/Selector.h>
|
||||
#include <LibWeb/HTML/ActivateTab.h>
|
||||
#include <LibWeb/HTML/SelectItem.h>
|
||||
#include <LibWeb/Page/Page.h>
|
||||
#include <LibWebView/Attribute.h>
|
||||
|
||||
|
@ -62,6 +63,7 @@ endpoint WebContentClient
|
|||
did_request_fullscreen_window() => (Gfx::IntRect window_rect)
|
||||
did_request_file(DeprecatedString path, i32 request_id) =|
|
||||
did_request_color_picker(Color current_color) =|
|
||||
did_request_select_dropdown(Gfx::IntPoint content_position, i32 minimum_width, Vector<Web::HTML::SelectItem> items) =|
|
||||
did_finish_handling_input_event(bool event_was_accepted) =|
|
||||
did_change_theme_color(Gfx::Color color) =|
|
||||
did_insert_clipboard_entry(String data, String presentation_style, String mime_type) =|
|
||||
|
|
|
@ -91,6 +91,7 @@ endpoint WebContentServer
|
|||
confirm_closed(bool accepted) =|
|
||||
prompt_closed(Optional<String> response) =|
|
||||
color_picker_closed(Optional<Color> picked_color) =|
|
||||
select_dropdown_closed(Optional<String> value) =|
|
||||
|
||||
toggle_media_play_state() =|
|
||||
toggle_media_mute_state() =|
|
||||
|
|
Loading…
Reference in a new issue