<input type="range"> changing to disabled while active breaks all pointer events
authorcdumez@apple.com <cdumez@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 5 Apr 2017 18:47:39 +0000 (18:47 +0000)
committercdumez@apple.com <cdumez@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 5 Apr 2017 18:47:39 +0000 (18:47 +0000)
https://bugs.webkit.org/show_bug.cgi?id=170447
<rdar://problem/31442875>

Reviewed by Geoffrey Garen.

Source/WebCore:

When a range's slider is being moved, we set SliderThumbElement's m_inDragMode flag
to true and mark the range elements as the CapturingMouseEventsElement. When we get
the mouseUp event, we are supposed to exit drag mode. However, when the range element
gets disabled while dragging, we do not get the mouseUp event and we need to make
sure we exit dragging mode anyway. r112547 tried to fix this by calling stopDragging()
in SliderThumbElement::defaultEventHandler() when the input element is disabled.
While this often works, this is fragile and we sometimes fail to exit dragging mode
when we should.

This patch addressed the issue by calling stopDragging() in
SliderThumbElement::disabledAttributeChanged() instead. This is much safer as we
guarantee will exit dragging mode whenever the range element gets disabled, even
if SliderThumbElement::defaultEventHandler() does not get called after that.

Test: fast/forms/range/disabled-while-dragging.html

* html/RangeInputType.cpp:
(WebCore::RangeInputType::disabledAttributeChanged):
* html/RangeInputType.h:
* html/shadow/SliderThumbElement.cpp:
(WebCore::SliderThumbElement::defaultEventHandler):
(WebCore::SliderThumbElement::disabledAttributeChanged):
* html/shadow/SliderThumbElement.h:

LayoutTests:

Add layout test coverage.

* fast/forms/range/disabled-while-dragging-expected.txt: Added.
* fast/forms/range/disabled-while-dragging.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@214955 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/fast/forms/range/disabled-while-dragging-expected.txt [new file with mode: 0644]
LayoutTests/fast/forms/range/disabled-while-dragging.html [new file with mode: 0644]
LayoutTests/platform/ios/TestExpectations
Source/WebCore/ChangeLog
Source/WebCore/html/RangeInputType.cpp
Source/WebCore/html/RangeInputType.h
Source/WebCore/html/shadow/SliderThumbElement.cpp
Source/WebCore/html/shadow/SliderThumbElement.h

index 28763d7..493db30 100644 (file)
@@ -1,3 +1,16 @@
+2017-04-05  Chris Dumez  <cdumez@apple.com>
+
+        <input type="range"> changing to disabled while active breaks all pointer events
+        https://bugs.webkit.org/show_bug.cgi?id=170447
+        <rdar://problem/31442875>
+
+        Reviewed by Geoffrey Garen.
+
+        Add layout test coverage.
+
+        * fast/forms/range/disabled-while-dragging-expected.txt: Added.
+        * fast/forms/range/disabled-while-dragging.html: Added.
+
 2017-04-05  Jiewen Tan  <jiewen_tan@apple.com>
 
         Unreviewed, rebasing crypto/subtle/rsa-import-key-malformed-parameters.html
diff --git a/LayoutTests/fast/forms/range/disabled-while-dragging-expected.txt b/LayoutTests/fast/forms/range/disabled-while-dragging-expected.txt
new file mode 100644 (file)
index 0000000..22875d3
--- /dev/null
@@ -0,0 +1,14 @@
+Tests that click events still work after a range is disabled while dragging.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS range.disabled is false
+Drag range slider.
+PASS range.disabled is true
+Click button
+PASS buttonClicked is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+ Click
diff --git a/LayoutTests/fast/forms/range/disabled-while-dragging.html b/LayoutTests/fast/forms/range/disabled-while-dragging.html
new file mode 100644 (file)
index 0000000..d967cb5
--- /dev/null
@@ -0,0 +1,59 @@
+<!doctype html>
+<html>
+<body>
+<script src="../../../resources/js-test-pre.js"></script>
+<script>
+description("Tests that click events still work after a range is disabled while dragging.");
+jsTestIsAsync = true;
+
+const input = (that) => {
+    if (Math.abs(that.value - 50) === 50)
+        that.disabled = true;
+};
+
+let buttonClicked = false;
+
+function buttonClick()
+{
+    buttonClicked = true;
+}
+
+onload = function() {
+    range = document.querySelector("input");
+
+    shouldBeFalse("range.disabled");
+
+    debug("Drag range slider.");
+    var centerY = range.offsetTop + range.offsetHeight / 2;
+    var centerX = range.offsetLeft + range.offsetWidth / 2;
+    var rightEdgeX = range.offsetLeft + range.offsetWidth - 1;
+
+    eventSender.mouseMoveTo(centerX, centerY);
+    eventSender.mouseDown();
+    eventSender.mouseMoveTo(rightEdgeX, centerY);
+    eventSender.mouseUp();
+
+    setTimeout(function() {
+        shouldBeTrue("range.disabled");
+
+        debug("Click button");
+        button = document.querySelector("button");
+        var centerY = button.offsetTop + button.offsetHeight / 2;
+        var centerX = button.offsetLeft + button.offsetWidth / 2;
+        eventSender.mouseMoveTo(centerX, centerY);
+        eventSender.mouseDown();
+        eventSender.mouseUp();
+
+        setTimeout(function() {
+            shouldBeTrue("buttonClicked");
+            finishJSTest();
+        }, 0);
+    }, 0);
+}
+</script>
+
+<input type="range" oninput="input(this)">
+<button onclick="buttonClick()">Click</button>
+<script src="../../../resources/js-test-post.js"></script>
+</body>
+</html>
index 7861ebc..ebd39b6 100644 (file)
@@ -380,6 +380,8 @@ http/tests/navigation/keyboard-events-during-provisional-navigation.html [ Skip
 media/audio-dealloc-crash.html [ Skip ]
 
 # This test uses EventSender's mouseMoveTo, mouseUp and mouseDown
+fast/forms/range/disabled-while-dragging.html [ Skip ]
+fast/forms/range/range-drag-when-toggled-disabled.html [ Skip ]
 fast/media/video-element-in-details-collapse.html [ Skip ]
 
 # The file-wrapper part of <attachment> is not yet working on iOS
@@ -1421,7 +1423,6 @@ fast/forms/password-doubleclick-selection.html [ Failure ]
 fast/forms/password-placeholder-text-security.html [ Pass ImageOnlyFailure ]
 fast/forms/placeholder-position.html [ Failure ]
 fast/forms/radio/indeterminate-radio.html [ Pass Failure ]
-fast/forms/range/range-drag-when-toggled-disabled.html [ Failure ]
 fast/forms/range/range-drag.html [ Failure ]
 fast/forms/range/range-hit-test-with-padding.html [ Failure ]
 fast/forms/range/range-slow-drag-to-edge.html [ Failure ]
index a70ff37..91358e9 100644 (file)
@@ -1,3 +1,35 @@
+2017-04-05  Chris Dumez  <cdumez@apple.com>
+
+        <input type="range"> changing to disabled while active breaks all pointer events
+        https://bugs.webkit.org/show_bug.cgi?id=170447
+        <rdar://problem/31442875>
+
+        Reviewed by Geoffrey Garen.
+
+        When a range's slider is being moved, we set SliderThumbElement's m_inDragMode flag
+        to true and mark the range elements as the CapturingMouseEventsElement. When we get
+        the mouseUp event, we are supposed to exit drag mode. However, when the range element
+        gets disabled while dragging, we do not get the mouseUp event and we need to make
+        sure we exit dragging mode anyway. r112547 tried to fix this by calling stopDragging()
+        in SliderThumbElement::defaultEventHandler() when the input element is disabled.
+        While this often works, this is fragile and we sometimes fail to exit dragging mode
+        when we should.
+
+        This patch addressed the issue by calling stopDragging() in
+        SliderThumbElement::disabledAttributeChanged() instead. This is much safer as we
+        guarantee will exit dragging mode whenever the range element gets disabled, even
+        if SliderThumbElement::defaultEventHandler() does not get called after that.
+
+        Test: fast/forms/range/disabled-while-dragging.html
+
+        * html/RangeInputType.cpp:
+        (WebCore::RangeInputType::disabledAttributeChanged):
+        * html/RangeInputType.h:
+        * html/shadow/SliderThumbElement.cpp:
+        (WebCore::SliderThumbElement::defaultEventHandler):
+        (WebCore::SliderThumbElement::disabledAttributeChanged):
+        * html/shadow/SliderThumbElement.h:
+
 2017-04-05  Eric Carlson  <eric.carlson@apple.com>
 
         [MediaStream] Video doesn't render in fullscreen on iOS
index 1554ffa..9649118 100644 (file)
@@ -182,14 +182,12 @@ bool RangeInputType::hasTouchEventHandler() const
     return true;
 }
 #endif
+#endif // ENABLE(TOUCH_EVENTS)
 
-#if PLATFORM(IOS)
 void RangeInputType::disabledAttributeChanged()
 {
     typedSliderThumbElement().disabledAttributeChanged();
 }
-#endif
-#endif // ENABLE(TOUCH_EVENTS)
 
 void RangeInputType::handleKeydownEvent(KeyboardEvent& event)
 {
index c6f62d8..c839f13 100644 (file)
@@ -81,9 +81,7 @@ private:
     void handleTouchEvent(TouchEvent&) final;
 #endif
 
-#if ENABLE(TOUCH_EVENTS) && PLATFORM(IOS)
     void disabledAttributeChanged() final;
-#endif
 
 #if ENABLE(TOUCH_EVENTS) && !PLATFORM(IOS) && ENABLE(TOUCH_SLIDER)
     bool hasTouchEventHandler() const final;
index 27f138b..6f8865f 100644 (file)
@@ -349,7 +349,6 @@ void SliderThumbElement::defaultEventHandler(Event& event)
     // Missing this kind of check is likely to occur elsewhere if adding it in each shadow element.
     HTMLInputElement* input = hostInput();
     if (!input || input->isDisabledFormControl()) {
-        stopDragging();
         HTMLDivElement::defaultEventHandler(event);
         return;
     }
@@ -564,15 +563,20 @@ void SliderThumbElement::unregisterForTouchEvents()
     document().removeTouchEventHandler(*this);
     m_isRegisteredAsTouchEventListener = false;
 }
+#endif // ENABLE(IOS_TOUCH_EVENTS)
 
 void SliderThumbElement::disabledAttributeChanged()
 {
+    if (isDisabledFormControl())
+        stopDragging();
+
+#if ENABLE(IOS_TOUCH_EVENTS)
     if (shouldAcceptTouchEvents())
         registerForTouchEvents();
     else
         unregisterForTouchEvents();
+#endif
 }
-#endif // ENABLE(IOS_TOUCH_EVENTS)
 
 HTMLInputElement* SliderThumbElement::hostInput() const
 {
index f8a99b3..944165f 100644 (file)
@@ -52,9 +52,9 @@ public:
 
 #if ENABLE(IOS_TOUCH_EVENTS)
     void handleTouchEvent(TouchEvent&);
+#endif
 
     void disabledAttributeChanged();
-#endif
 
 private:
     SliderThumbElement(Document&);