Просмотр исходного кода

LibWeb: Fire a change event on input elements in the focus update steps

This ensures the change event is received before the blur event.
Timothy Flynn 1 год назад
Родитель
Сommit
08ee48606d

+ 2 - 0
Tests/LibWeb/Text/expected/input-commit-on-unfocus.txt

@@ -0,0 +1,2 @@
+wfh :^)   wfh :^)
+blur

+ 18 - 0
Tests/LibWeb/Text/input/input-commit-on-unfocus.html

@@ -0,0 +1,18 @@
+<input id=input type=text>
+<script src="include.js"></script>
+<script>
+    test(() => {
+        let input = document.getElementById("input");
+
+        input.addEventListener("change", () => {
+            println(input.value);
+        });
+
+        input.addEventListener("blur", () => {
+            println("blur");
+        });
+
+        internals.sendText(input, "wfh :^)");
+        input.blur();
+    })
+</script>

+ 16 - 7
Userland/Libraries/LibWeb/HTML/Focus.cpp

@@ -13,6 +13,7 @@
 #include <LibWeb/DOM/Element.h>
 #include <LibWeb/DOM/Element.h>
 #include <LibWeb/DOM/ShadowRoot.h>
 #include <LibWeb/DOM/ShadowRoot.h>
 #include <LibWeb/HTML/Focus.h>
 #include <LibWeb/HTML/Focus.h>
+#include <LibWeb/HTML/HTMLInputElement.h>
 #include <LibWeb/HTML/TraversableNavigable.h>
 #include <LibWeb/HTML/TraversableNavigable.h>
 #include <LibWeb/UIEvents/FocusEvent.h>
 #include <LibWeb/UIEvents/FocusEvent.h>
 
 
@@ -32,13 +33,21 @@ static void run_focus_update_steps(Vector<JS::Handle<DOM::Node>> old_chain, Vect
 
 
     // 2. For each entry entry in old chain, in order, run these substeps:
     // 2. For each entry entry in old chain, in order, run these substeps:
     for (auto& entry : old_chain) {
     for (auto& entry : old_chain) {
-        // FIXME: 1. If entry is an input element, and the change event applies to the element,
-        //           and the element does not have a defined activation behavior,
-        //           and the user has changed the element's value or its list of selected files
-        //           while the control was focused without committing that change
-        //           (such that it is different to what it was when the control was first focused),
-        //           then fire an event named change at the element,
-        //           with the bubbles attribute initialized to true.
+        // 1. If entry is an input element, and the change event applies to the element, and the element does not have
+        //    a defined activation behavior, and the user has changed the element's value or its list of selected files
+        //    while the control was focused without committing that change (such that it is different to what it was
+        //    when the control was first focused), then fire an event named change at the element, with the bubbles
+        //    attribute initialized to true.
+        if (is<HTMLInputElement>(*entry)) {
+            auto& input_element = static_cast<HTMLInputElement&>(*entry);
+
+            // FIXME: Spec issue: It doesn't make sense to check if the element has a defined activation behavior, as
+            //        that is always true. Instead, we check if it has an *input* activation behavior.
+            //        https://github.com/whatwg/html/issues/9973
+            if (input_element.change_event_applies() && !input_element.has_input_activation_behavior()) {
+                input_element.commit_pending_changes();
+            }
+        }
 
 
         JS::GCPtr<DOM::EventTarget> blur_event_target;
         JS::GCPtr<DOM::EventTarget> blur_event_target;
         if (is<DOM::Element>(*entry)) {
         if (is<DOM::Element>(*entry)) {

+ 43 - 0
Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp

@@ -1340,4 +1340,47 @@ void HTMLInputElement::activation_behavior(DOM::Event const&)
     run_input_activation_behavior().release_value_but_fixme_should_propagate_errors();
     run_input_activation_behavior().release_value_but_fixme_should_propagate_errors();
 }
 }
 
 
+bool HTMLInputElement::has_input_activation_behavior() const
+{
+    switch (type_state()) {
+    case TypeAttributeState::Checkbox:
+    case TypeAttributeState::Color:
+    case TypeAttributeState::FileUpload:
+    case TypeAttributeState::ImageButton:
+    case TypeAttributeState::RadioButton:
+    case TypeAttributeState::ResetButton:
+    case TypeAttributeState::SubmitButton:
+        return true;
+    default:
+        return false;
+    }
+}
+
+// https://html.spec.whatwg.org/multipage/input.html#the-input-element:event-change-2
+bool HTMLInputElement::change_event_applies() const
+{
+    switch (type_state()) {
+    case TypeAttributeState::Checkbox:
+    case TypeAttributeState::Color:
+    case TypeAttributeState::Date:
+    case TypeAttributeState::Email:
+    case TypeAttributeState::FileUpload:
+    case TypeAttributeState::LocalDateAndTime:
+    case TypeAttributeState::Month:
+    case TypeAttributeState::Number:
+    case TypeAttributeState::Password:
+    case TypeAttributeState::RadioButton:
+    case TypeAttributeState::Range:
+    case TypeAttributeState::Search:
+    case TypeAttributeState::Telephone:
+    case TypeAttributeState::Text:
+    case TypeAttributeState::Time:
+    case TypeAttributeState::URL:
+    case TypeAttributeState::Week:
+        return true;
+    default:
+        return false;
+    }
+}
+
 }
 }

+ 3 - 0
Userland/Libraries/LibWeb/HTML/HTMLInputElement.h

@@ -158,6 +158,9 @@ public:
     virtual bool has_activation_behavior() const override;
     virtual bool has_activation_behavior() const override;
     virtual void activation_behavior(DOM::Event const&) override;
     virtual void activation_behavior(DOM::Event const&) override;
 
 
+    bool has_input_activation_behavior() const;
+    bool change_event_applies() const;
+
 private:
 private:
     HTMLInputElement(DOM::Document&, DOM::QualifiedName);
     HTMLInputElement(DOM::Document&, DOM::QualifiedName);