mirror of
https://github.com/RGBCube/serenity
synced 2025-05-14 09:34:59 +00:00
LibWeb: Support committing changes to <input> elements
We currently fire the change event on <input> elements when they lose focus. The spec allows for us to also fire the event when changes are "committed", so long as such an action makes sense for the input type. This patch detects when the return key is entered in an <input> element and uses that as the commit action for text-related types. If no change has occurred since the last commit, no change event is fired.
This commit is contained in:
parent
97665fa92f
commit
7edfeb7056
8 changed files with 88 additions and 11 deletions
1
Tests/LibWeb/Text/expected/input-commit.txt
Normal file
1
Tests/LibWeb/Text/expected/input-commit.txt
Normal file
|
@ -0,0 +1 @@
|
|||
wfh :^) wfh :^)
|
15
Tests/LibWeb/Text/input/input-commit.html
Normal file
15
Tests/LibWeb/Text/input/input-commit.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
<input id=input type=text>
|
||||
<script src="include.js"></script>
|
||||
<script>
|
||||
asyncTest((done) => {
|
||||
let input = document.getElementById("input");
|
||||
|
||||
input.addEventListener("change", () => {
|
||||
println(input.value);
|
||||
done();
|
||||
});
|
||||
|
||||
internals.sendText(input, "wfh :^)");
|
||||
internals.commitText();
|
||||
})
|
||||
</script>
|
|
@ -276,8 +276,6 @@ WebIDL::ExceptionOr<void> HTMLInputElement::run_input_activation_behavior()
|
|||
TRY(form->submit_form(*this));
|
||||
} else if (type_state() == TypeAttributeState::FileUpload || type_state() == TypeAttributeState::Color) {
|
||||
show_the_picker_if_applicable(*this);
|
||||
} else {
|
||||
dispatch_event(DOM::Event::create(realm(), EventNames::change));
|
||||
}
|
||||
|
||||
return {};
|
||||
|
@ -289,6 +287,8 @@ void HTMLInputElement::did_edit_text_node(Badge<BrowsingContext>)
|
|||
m_value = value_sanitization_algorithm(m_text_node->data());
|
||||
m_dirty_value = true;
|
||||
|
||||
m_has_uncommitted_changes = true;
|
||||
|
||||
update_placeholder_visibility();
|
||||
|
||||
// NOTE: This is a bit ad-hoc, but basically implements part of "4.10.5.5 Common event behaviors"
|
||||
|
@ -406,6 +406,33 @@ WebIDL::ExceptionOr<void> HTMLInputElement::set_value(String const& value)
|
|||
return {};
|
||||
}
|
||||
|
||||
void HTMLInputElement::commit_pending_changes()
|
||||
{
|
||||
// The change event fires when the value is committed, if that makes sense for the control,
|
||||
// or else when the control loses focus
|
||||
switch (type_state()) {
|
||||
case TypeAttributeState::Email:
|
||||
case TypeAttributeState::Password:
|
||||
case TypeAttributeState::Search:
|
||||
case TypeAttributeState::Telephone:
|
||||
case TypeAttributeState::Text:
|
||||
case TypeAttributeState::URL:
|
||||
if (!m_has_uncommitted_changes)
|
||||
return;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
m_has_uncommitted_changes = false;
|
||||
|
||||
queue_an_element_task(HTML::Task::Source::UserInteraction, [this] {
|
||||
auto change_event = DOM::Event::create(realm(), HTML::EventNames::change, { .bubbles = true });
|
||||
dispatch_event(change_event);
|
||||
});
|
||||
}
|
||||
|
||||
void HTMLInputElement::update_placeholder_visibility()
|
||||
{
|
||||
if (!m_placeholder_element)
|
||||
|
@ -615,13 +642,7 @@ void HTMLInputElement::did_receive_focus()
|
|||
|
||||
void HTMLInputElement::did_lose_focus()
|
||||
{
|
||||
// The change event fires when the value is committed, if that makes sense for the control,
|
||||
// or else when the control loses focus
|
||||
queue_an_element_task(HTML::Task::Source::UserInteraction, [this] {
|
||||
auto change_event = DOM::Event::create(realm(), HTML::EventNames::change);
|
||||
change_event->set_bubbles(true);
|
||||
dispatch_event(change_event);
|
||||
});
|
||||
commit_pending_changes();
|
||||
}
|
||||
|
||||
void HTMLInputElement::attribute_changed(FlyString const& name, Optional<String> const& value)
|
||||
|
|
|
@ -69,6 +69,8 @@ public:
|
|||
virtual String value() const override;
|
||||
WebIDL::ExceptionOr<void> set_value(String const&);
|
||||
|
||||
void commit_pending_changes();
|
||||
|
||||
Optional<DeprecatedString> placeholder_value() const;
|
||||
|
||||
bool checked() const { return m_checked; }
|
||||
|
@ -215,6 +217,8 @@ private:
|
|||
|
||||
TypeAttributeState m_type { TypeAttributeState::Text };
|
||||
String m_value;
|
||||
|
||||
bool m_has_uncommitted_changes { false };
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <LibWeb/DOM/Event.h>
|
||||
#include <LibWeb/DOM/EventTarget.h>
|
||||
#include <LibWeb/HTML/BrowsingContext.h>
|
||||
#include <LibWeb/HTML/HTMLElement.h>
|
||||
#include <LibWeb/HTML/Window.h>
|
||||
#include <LibWeb/Internals/Internals.h>
|
||||
#include <LibWeb/Page/Page.h>
|
||||
|
@ -62,6 +63,27 @@ JS::Object* Internals::hit_test(double x, double y)
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
void Internals::send_text(HTML::HTMLElement& target, String const& text)
|
||||
{
|
||||
auto* page = global_object().browsing_context()->page();
|
||||
if (!page)
|
||||
return;
|
||||
|
||||
target.focus();
|
||||
|
||||
for (auto code_point : text.code_points())
|
||||
page->handle_keydown(code_point_to_key_code(code_point), 0, code_point);
|
||||
}
|
||||
|
||||
void Internals::commit_text()
|
||||
{
|
||||
auto* page = global_object().browsing_context()->page();
|
||||
if (!page)
|
||||
return;
|
||||
|
||||
page->handle_keydown(Key_Return, 0, 0);
|
||||
}
|
||||
|
||||
WebIDL::ExceptionOr<bool> Internals::dispatch_user_activated_event(DOM::EventTarget& target, DOM::Event& event)
|
||||
{
|
||||
event.set_is_trusted(true);
|
||||
|
|
|
@ -22,6 +22,9 @@ public:
|
|||
void gc();
|
||||
JS::Object* hit_test(double x, double y);
|
||||
|
||||
void send_text(HTML::HTMLElement&, String const&);
|
||||
void commit_text();
|
||||
|
||||
WebIDL::ExceptionOr<bool> dispatch_user_activated_event(DOM::EventTarget&, DOM::Event& event);
|
||||
|
||||
private:
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
#import <DOM/EventTarget.idl>
|
||||
#import <HTML/HTMLElement.idl>
|
||||
|
||||
[Exposed=Nobody] interface Internals {
|
||||
|
||||
undefined signalTextTestIsDone();
|
||||
undefined gc();
|
||||
object hitTest(double x, double y);
|
||||
|
||||
undefined sendText(HTMLElement target, DOMString text);
|
||||
undefined commitText();
|
||||
|
||||
boolean dispatchUserActivatedEvent(EventTarget target, Event event);
|
||||
|
||||
};
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <LibWeb/HTML/HTMLAnchorElement.h>
|
||||
#include <LibWeb/HTML/HTMLIFrameElement.h>
|
||||
#include <LibWeb/HTML/HTMLImageElement.h>
|
||||
#include <LibWeb/HTML/HTMLInputElement.h>
|
||||
#include <LibWeb/HTML/HTMLMediaElement.h>
|
||||
#include <LibWeb/HTML/HTMLVideoElement.h>
|
||||
#include <LibWeb/Layout/Viewport.h>
|
||||
|
@ -769,6 +770,8 @@ bool EventHandler::handle_keydown(KeyCode key, unsigned modifiers, u32 code_poin
|
|||
}
|
||||
|
||||
if (m_browsing_context->cursor_position() && m_browsing_context->cursor_position()->node()->is_editable()) {
|
||||
auto& node = verify_cast<DOM::Text>(*m_browsing_context->cursor_position()->node());
|
||||
|
||||
if (key == KeyCode::Key_Backspace) {
|
||||
if (!m_browsing_context->decrement_cursor_position_offset()) {
|
||||
// FIXME: Move to the previous node and delete the last character there.
|
||||
|
@ -799,15 +802,18 @@ bool EventHandler::handle_keydown(KeyCode key, unsigned modifiers, u32 code_poin
|
|||
return true;
|
||||
}
|
||||
if (key == KeyCode::Key_Home) {
|
||||
auto& node = verify_cast<DOM::Text>(*m_browsing_context->cursor_position()->node());
|
||||
m_browsing_context->set_cursor_position(DOM::Position::create(realm, node, 0));
|
||||
return true;
|
||||
}
|
||||
if (key == KeyCode::Key_End) {
|
||||
auto& node = verify_cast<DOM::Text>(*m_browsing_context->cursor_position()->node());
|
||||
m_browsing_context->set_cursor_position(DOM::Position::create(realm, node, (unsigned)node.data().bytes().size()));
|
||||
return true;
|
||||
}
|
||||
if (key == KeyCode::Key_Return && is<HTML::HTMLInputElement>(node.editable_text_node_owner())) {
|
||||
auto& input_element = static_cast<HTML::HTMLInputElement&>(*node.editable_text_node_owner());
|
||||
input_element.commit_pending_changes();
|
||||
return true;
|
||||
}
|
||||
if (!should_ignore_keydown_event(code_point)) {
|
||||
m_edit_event_handler->handle_insert(JS::NonnullGCPtr { *m_browsing_context->cursor_position() }, code_point);
|
||||
m_browsing_context->increment_cursor_position_offset();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue