Title: [202470] trunk
Revision
202470
Author
[email protected]
Date
2016-06-25 21:34:23 -0700 (Sat, 25 Jun 2016)

Log Message

The active state of elements can break when focus changes
https://bugs.webkit.org/show_bug.cgi?id=159112

Patch by Benjamin Poulain <[email protected]> on 2016-06-25
Reviewed by Antti Koivisto.

Source/WebCore:

The pseudo class :active was behaving weirdly when used
with label elements with an associated form element.
The form element would get the :active state on the first click
then no longer get the state until the focus changes.

What was happenning is setFocusedElement() was clearing active
for some unknown reason. When you really do that on an active element,
you end up in an inconsistent state where no invalidation works.

The two tests illustrates 2 ways this breaks.

The test "pseudo-active-on-labeled-element-not-canceled-by-focus" clicks
several time on a lable element. The first time, the input element gets
the focus. The second time, it already has the focus, setFocusedElement()
clears :active before finding the focusable element and end up clearing
the active state on a target in the active chain.

The test "pseudo-active-with-programmatic-focus.html" shows how to invalidate
arbitrary elements using _javascript_. This can cause severely broken active
chains where invalidation never cleans some ancestors.

Tests: fast/css/pseudo-active-on-labeled-element-not-canceled-by-focus.html
       fast/css/pseudo-active-with-programmatic-focus.html

* dom/Document.cpp:
(WebCore::Document::setFocusedElement): Deleted.

* page/EventHandler.cpp:
(WebCore::EventHandler::handleMouseDoubleClickEvent):
This is WebKit1 specific. The double click event was dispatching
the mouseUp and Click with after doing an Active hit test.
This causes us to have :active state in and after mouseUp in WebKit1.

LayoutTests:

* fast/css/pseudo-active-on-labeled-element-not-canceled-by-focus-expected.txt: Added.
* fast/css/pseudo-active-on-labeled-element-not-canceled-by-focus.html: Added.
* fast/css/pseudo-active-with-programmatic-focus-expected.txt: Added.
* fast/css/pseudo-active-with-programmatic-focus.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (202469 => 202470)


--- trunk/LayoutTests/ChangeLog	2016-06-25 19:07:15 UTC (rev 202469)
+++ trunk/LayoutTests/ChangeLog	2016-06-26 04:34:23 UTC (rev 202470)
@@ -1,3 +1,15 @@
+2016-06-25  Benjamin Poulain  <[email protected]>
+
+        The active state of elements can break when focus changes
+        https://bugs.webkit.org/show_bug.cgi?id=159112
+
+        Reviewed by Antti Koivisto.
+
+        * fast/css/pseudo-active-on-labeled-element-not-canceled-by-focus-expected.txt: Added.
+        * fast/css/pseudo-active-on-labeled-element-not-canceled-by-focus.html: Added.
+        * fast/css/pseudo-active-with-programmatic-focus-expected.txt: Added.
+        * fast/css/pseudo-active-with-programmatic-focus.html: Added.
+
 2016-06-24  Jer Noble  <[email protected]>
 
         Consider exposing or hiding knowledge of a redirect from clients of WebCoreNSURLSession

Added: trunk/LayoutTests/fast/css/pseudo-active-on-labeled-element-not-canceled-by-focus-expected.txt (0 => 202470)


--- trunk/LayoutTests/fast/css/pseudo-active-on-labeled-element-not-canceled-by-focus-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/fast/css/pseudo-active-on-labeled-element-not-canceled-by-focus-expected.txt	2016-06-26 04:34:23 UTC (rev 202470)
@@ -0,0 +1,49 @@
+Verify that indirect :active on a labeled element is not affected when the element gets focus from the click.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+
+    Initial state
+PASS elementsWithActiveStyle() is []
+PASS elementsMatchingActiveSelector() is []
+On Mouse Down
+PASS elementsWithActiveStyle() is ["html", "body", "prime-ancestor", "labelable-ancestor", "labelable-parent", "target", "labelable", "sibling5"]
+PASS elementsMatchingActiveSelector() is ["html", "body", "prime-ancestor", "labelable-ancestor", "labelable-parent", "target", "labelable"]
+After Mouse Down
+PASS elementsWithActiveStyle() is ["html", "body", "prime-ancestor", "labelable-ancestor", "labelable-parent", "target", "labelable", "sibling5"]
+PASS elementsMatchingActiveSelector() is ["html", "body", "prime-ancestor", "labelable-ancestor", "labelable-parent", "target", "labelable"]
+On Mouse Up
+PASS elementsWithActiveStyle() is []
+PASS elementsMatchingActiveSelector() is []
+After Mouse Up
+PASS elementsWithActiveStyle() is []
+PASS elementsMatchingActiveSelector() is []
+On Mouse Down
+PASS elementsWithActiveStyle() is ["html", "body", "prime-ancestor", "labelable-ancestor", "labelable-parent", "target", "labelable", "sibling5"]
+PASS elementsMatchingActiveSelector() is ["html", "body", "prime-ancestor", "labelable-ancestor", "labelable-parent", "target", "labelable"]
+After Mouse Down
+PASS elementsWithActiveStyle() is ["html", "body", "prime-ancestor", "labelable-ancestor", "labelable-parent", "target", "labelable", "sibling5"]
+PASS elementsMatchingActiveSelector() is ["html", "body", "prime-ancestor", "labelable-ancestor", "labelable-parent", "target", "labelable"]
+On Mouse Up
+PASS elementsWithActiveStyle() is []
+PASS elementsMatchingActiveSelector() is []
+After Mouse Up
+PASS elementsWithActiveStyle() is []
+PASS elementsMatchingActiveSelector() is []
+On Mouse Down
+PASS elementsWithActiveStyle() is ["html", "body", "prime-ancestor", "labelable-ancestor", "labelable-parent", "target", "labelable", "sibling5"]
+PASS elementsMatchingActiveSelector() is ["html", "body", "prime-ancestor", "labelable-ancestor", "labelable-parent", "target", "labelable"]
+After Mouse Down
+PASS elementsWithActiveStyle() is ["html", "body", "prime-ancestor", "labelable-ancestor", "labelable-parent", "target", "labelable", "sibling5"]
+PASS elementsMatchingActiveSelector() is ["html", "body", "prime-ancestor", "labelable-ancestor", "labelable-parent", "target", "labelable"]
+On Mouse Up
+PASS elementsWithActiveStyle() is []
+PASS elementsMatchingActiveSelector() is []
+After Mouse Up
+PASS elementsWithActiveStyle() is []
+PASS elementsMatchingActiveSelector() is []
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/fast/css/pseudo-active-on-labeled-element-not-canceled-by-focus.html (0 => 202470)


--- trunk/LayoutTests/fast/css/pseudo-active-on-labeled-element-not-canceled-by-focus.html	                        (rev 0)
+++ trunk/LayoutTests/fast/css/pseudo-active-on-labeled-element-not-canceled-by-focus.html	2016-06-26 04:34:23 UTC (rev 202470)
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<html id="html">
+<head>
+<style id="custom-style">
+    * {
+        background-color: white;
+        margin: 0;
+        padding: 0;
+    }
+    :active, input:active + * + * + * {
+        background-color: rgb(50, 100, 150) !important;
+    }
+    #target {
+        display: block;
+        width: 100px;
+        height: 100px;
+        background-color: green;
+    }
+</style>
+</head>
+<script src=""
+<body id="body">
+    <div id="prime-ancestor">
+        <div id="labelable-ancestor">
+            <div id="labelable-parent">
+                <label for="" id="target">Label</label>
+            </div>
+        </div>
+        <div id="next-group" style="display:block">
+            <div id="sibling1">Sibling1</div>
+            <div id="sibling2">Sibling2</div>
+            <input id="labelable" value="Labelable Input">
+            <div id="sibling3">Sibling3</div>
+            <div id="sibling4">Sibling4</div>
+            <div id="sibling5">Sibling5</div>
+        </div>
+    </div>
+    <div id="console">
+    </div>
+    <script>
+    description("Verify that indirect :active on a labeled element is not affected when the element gets focus from the click.");
+    window.jsTestIsAsync = true;
+
+    function elementsWithActiveStyle() {
+        let elements = [];
+        for (let element of document.querySelectorAll("*")) {
+            if (getComputedStyle(element).backgroundColor === "rgb(50, 100, 150)")
+                elements.push(element.id);
+        }
+        return elements;
+    }
+    function elementsMatchingActiveSelector() {
+        let elements = [];
+        for (let element of document.querySelectorAll(":active")) {
+            elements.push(element.id);
+        }
+        return elements;
+    }
+
+    if (window.eventSender) {
+        eventSender.mouseMoveTo(50, 50);
+    } else {
+        debug("");
+        debug("To run Manually, click-hold-release 3 times on the green rect. The rect and the input element should change color. All the results below should say PASS.")
+        debug("");
+    }
+
+    function sendMouseDown() {
+        if (window.eventSender) {
+            eventSender.mouseDown();
+        }
+    }
+
+    function sendMouseUp() {
+        if (window.eventSender) {
+            eventSender.mouseUp();
+        }
+    }
+
+    function mouseDownHandler(event, delayed = false) {
+        debug(delayed ? "After Mouse Down" : "On Mouse Down");
+        shouldBe('elementsWithActiveStyle()', '["html", "body", "prime-ancestor", "labelable-ancestor", "labelable-parent", "target", "labelable", "sibling5"]');
+        shouldBe('elementsMatchingActiveSelector()', '["html", "body", "prime-ancestor", "labelable-ancestor", "labelable-parent", "target", "labelable"]');
+
+        // The current spec does not defined the order in which elements are activated.
+        // It is reasonable to have the activation after the mouseDown dispatch. That's what Firefox does at this time.
+        // This delayed handler ensure we cover both possibilities. The first handler fails on Firefox but that's not
+        // necessarily wrong, just undefined. The result is fine as long as one of the two handler succeed.
+        if (!delayed) {
+            setTimeout(function() { mouseDownHandler(event, true); }, 0);
+        } else {
+            sendMouseUp();
+        }
+    }
+    var target = document.getElementById('target');
+    target.addEventListener('mousedown', mouseDownHandler);
+
+    let mouseUpCount = 0;
+    function mouseUpHandler(event, delayed = false) {
+        debug(delayed ? "After Mouse Up" : "On Mouse Up");
+        shouldBe('elementsWithActiveStyle()', '[]');
+        shouldBe('elementsMatchingActiveSelector()', '[]');
+        if (++mouseUpCount === 6) {
+            document.getElementById("prime-ancestor").style.display = "none";
+            document.getElementById("custom-style").innerText = "";
+            finishJSTest();
+        } else {
+            if (!delayed) {
+                setTimeout(function() { mouseUpHandler(event, true); }, 0);
+            } else {
+                sendMouseDown();
+            }
+        }
+    }
+    target.addEventListener('mouseup', mouseUpHandler);
+
+
+    debug("Initial state");
+    shouldBe('elementsWithActiveStyle()', '[]');
+    shouldBe('elementsMatchingActiveSelector()', '[]');
+
+    sendMouseDown();
+    </script>
+    <script src=""
+</body>
+</html>

Added: trunk/LayoutTests/fast/css/pseudo-active-with-programmatic-focus-expected.txt (0 => 202470)


--- trunk/LayoutTests/fast/css/pseudo-active-with-programmatic-focus-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/fast/css/pseudo-active-with-programmatic-focus-expected.txt	2016-06-26 04:34:23 UTC (rev 202470)
@@ -0,0 +1,35 @@
+
+    
+Verify that :active remain consistent when the focus is changing.
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+    Initial state
+PASS elementsWithActiveStyle() is []
+PASS elementsMatchingSelector(":active") is []
+PASS elementsMatchingSelector(":focus") is []
+On Mouse Down
+PASS elementsWithActiveStyle() is ["html", "body", "webkit-test", "labelable-ancestor", "labelable-parent", "target"]
+PASS elementsMatchingSelector(":active") is ["html", "body", "webkit-test", "labelable-ancestor", "labelable-parent", "target"]
+After Mouse Down
+PASS elementsWithActiveStyle() is ["html", "body", "webkit-test", "labelable-ancestor", "labelable-parent", "target"]
+PASS elementsMatchingSelector(":active") is ["html", "body", "webkit-test", "labelable-ancestor", "labelable-parent", "target"]
+Focus the target
+PASS elementsWithActiveStyle() is ["html", "body", "webkit-test", "labelable-ancestor", "labelable-parent", "target"]
+PASS elementsMatchingSelector(":active") is ["html", "body", "webkit-test", "labelable-ancestor", "labelable-parent", "target"]
+PASS elementsMatchingSelector(":focus") is ["target"]
+Focus input2
+PASS elementsWithActiveStyle() is ["html", "body", "webkit-test", "labelable-ancestor", "labelable-parent", "target"]
+PASS elementsMatchingSelector(":active") is ["html", "body", "webkit-test", "labelable-ancestor", "labelable-parent", "target"]
+PASS elementsMatchingSelector(":focus") is ["input2"]
+Focus the target
+PASS elementsWithActiveStyle() is ["html", "body", "webkit-test", "labelable-ancestor", "labelable-parent", "target"]
+PASS elementsMatchingSelector(":active") is ["html", "body", "webkit-test", "labelable-ancestor", "labelable-parent", "target"]
+PASS elementsMatchingSelector(":focus") is ["target"]
+Focus input1
+PASS elementsWithActiveStyle() is ["html", "body", "webkit-test", "labelable-ancestor", "labelable-parent", "target"]
+PASS elementsMatchingSelector(":active") is ["html", "body", "webkit-test", "labelable-ancestor", "labelable-parent", "target"]
+PASS elementsMatchingSelector(":focus") is ["input1"]
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/fast/css/pseudo-active-with-programmatic-focus.html (0 => 202470)


--- trunk/LayoutTests/fast/css/pseudo-active-with-programmatic-focus.html	                        (rev 0)
+++ trunk/LayoutTests/fast/css/pseudo-active-with-programmatic-focus.html	2016-06-26 04:34:23 UTC (rev 202470)
@@ -0,0 +1,127 @@
+<!DOCTYPE html>
+<html id="html">
+<head>
+<style id="custom-style">
+    * {
+        background-color: white;
+        margin: 0;
+        padding: 0;
+    }
+    :active {
+        background-color: rgb(50, 100, 150) !important;
+    }
+    #target {
+        display: block;
+        width: 100px;
+        height: 100px;
+        background-color: green;
+    }
+</style>
+</head>
+<script src=""
+<body id="body">
+    <div id="webkit-test">
+        <div id="labelable-ancestor">
+            <div id="labelable-parent">
+                <textarea id="target">Label</textarea>
+            </div>
+        </div>
+        <div id="next-group" style="display:block">
+            <input id="input1" value="Input1">
+            <input id="input2" value="Input2">
+            <input id="input3" value="Input3">
+        </div>
+    </div>
+    <div id="console">
+    </div>
+    <script>
+    description("Verify that :active remain consistent when the focus is changing.");
+    window.jsTestIsAsync = true;
+
+    function elementsWithActiveStyle() {
+        let elements = [];
+        for (let element of document.querySelectorAll("*")) {
+            if (getComputedStyle(element).backgroundColor === "rgb(50, 100, 150)")
+                elements.push(element.id);
+        }
+        return elements;
+    }
+    function elementsMatchingSelector(selector) {
+        let elements = [];
+        for (let element of document.querySelectorAll(selector)) {
+            elements.push(element.id);
+        }
+        return elements;
+    }
+
+    if (window.eventSender) {
+        eventSender.mouseMoveTo(50, 50);
+    } else {
+        debug("");
+        debug("To run Manually, click-hold-release on the green rect. All the results below should say PASS.")
+        debug("");
+    }
+
+    function sendMouseDown() {
+        if (window.eventSender) {
+            eventSender.mouseDown();
+        }
+    }
+
+    function sendMouseUp() {
+        if (window.eventSender) {
+            eventSender.mouseUp();
+        }
+    }
+
+    var target = document.getElementById('target');
+    function afterMouseDown(event) {
+        debug("After Mouse Down");
+        shouldBe('elementsWithActiveStyle()', '["html", "body", "webkit-test", "labelable-ancestor", "labelable-parent", "target"]');
+        shouldBe('elementsMatchingSelector(":active")', '["html", "body", "webkit-test", "labelable-ancestor", "labelable-parent", "target"]');
+        debug("Focus the target");
+        target.focus();
+        shouldBe('elementsWithActiveStyle()', '["html", "body", "webkit-test", "labelable-ancestor", "labelable-parent", "target"]');
+        shouldBe('elementsMatchingSelector(":active")', '["html", "body", "webkit-test", "labelable-ancestor", "labelable-parent", "target"]');
+        shouldBe('elementsMatchingSelector(":focus")', '["target"]');
+        debug("Focus input2");
+        let input2 = document.getElementById("input2");
+        input2.focus();
+        shouldBe('elementsWithActiveStyle()', '["html", "body", "webkit-test", "labelable-ancestor", "labelable-parent", "target"]');
+        shouldBe('elementsMatchingSelector(":active")', '["html", "body", "webkit-test", "labelable-ancestor", "labelable-parent", "target"]');
+        shouldBe('elementsMatchingSelector(":focus")', '["input2"]');
+        debug("Focus the target");
+        target.focus();
+        shouldBe('elementsWithActiveStyle()', '["html", "body", "webkit-test", "labelable-ancestor", "labelable-parent", "target"]');
+        shouldBe('elementsMatchingSelector(":active")', '["html", "body", "webkit-test", "labelable-ancestor", "labelable-parent", "target"]');
+        shouldBe('elementsMatchingSelector(":focus")', '["target"]');
+        debug("Focus input1");
+        let input1 = document.getElementById("input1");
+        input1.focus();
+        shouldBe('elementsWithActiveStyle()', '["html", "body", "webkit-test", "labelable-ancestor", "labelable-parent", "target"]');
+        shouldBe('elementsMatchingSelector(":active")', '["html", "body", "webkit-test", "labelable-ancestor", "labelable-parent", "target"]');
+        shouldBe('elementsMatchingSelector(":focus")', '["input1"]');
+
+        finishJSTest();
+    }
+    function mouseDownHandler(event) {
+        debug("On Mouse Down");
+        shouldBe('elementsWithActiveStyle()', '["html", "body", "webkit-test", "labelable-ancestor", "labelable-parent", "target"]');
+        shouldBe('elementsMatchingSelector(":active")', '["html", "body", "webkit-test", "labelable-ancestor", "labelable-parent", "target"]');
+
+        setTimeout(function() { afterMouseDown(event); }, 0);
+    }
+
+    target.addEventListener('mousedown', mouseDownHandler);
+
+
+    debug("Initial state");
+    shouldBe('elementsWithActiveStyle()', '[]');
+    shouldBe('elementsMatchingSelector(":active")', '[]');
+    shouldBe('elementsMatchingSelector(":focus")', '[]');
+
+    sendMouseDown();
+    </script>
+    <script src=""
+</body>
+</html>

Modified: trunk/LayoutTests/platform/ios-simulator/TestExpectations (202469 => 202470)


--- trunk/LayoutTests/platform/ios-simulator/TestExpectations	2016-06-25 19:07:15 UTC (rev 202469)
+++ trunk/LayoutTests/platform/ios-simulator/TestExpectations	2016-06-26 04:34:23 UTC (rev 202470)
@@ -1435,6 +1435,8 @@
 fast/css/negative-text-indent-in-inline-block.html [ ImageOnlyFailure ]
 fast/css/object-fit/object-fit-input-image.html [ ImageOnlyFailure ]
 fast/css/outline-auto-empty-rects.html [ Failure ]
+fast/css/pseudo-active-on-labeled-element-not-canceled-by-focus.html [ Failure ]
+fast/css/pseudo-active-with-programmatic-focus.html [ Failure ]
 fast/css/pseudo-first-line-border-width.html [ Failure ]
 fast/css/read-only-read-write-input-basics.html [ ImageOnlyFailure ]
 fast/css/replaced-element-implicit-size.html [ Failure ]

Modified: trunk/Source/WebCore/ChangeLog (202469 => 202470)


--- trunk/Source/WebCore/ChangeLog	2016-06-25 19:07:15 UTC (rev 202469)
+++ trunk/Source/WebCore/ChangeLog	2016-06-26 04:34:23 UTC (rev 202470)
@@ -1,3 +1,43 @@
+2016-06-25  Benjamin Poulain  <[email protected]>
+
+        The active state of elements can break when focus changes
+        https://bugs.webkit.org/show_bug.cgi?id=159112
+
+        Reviewed by Antti Koivisto.
+
+        The pseudo class :active was behaving weirdly when used
+        with label elements with an associated form element.
+        The form element would get the :active state on the first click
+        then no longer get the state until the focus changes.
+
+        What was happenning is setFocusedElement() was clearing active
+        for some unknown reason. When you really do that on an active element,
+        you end up in an inconsistent state where no invalidation works.
+
+        The two tests illustrates 2 ways this breaks.
+
+        The test "pseudo-active-on-labeled-element-not-canceled-by-focus" clicks
+        several time on a lable element. The first time, the input element gets
+        the focus. The second time, it already has the focus, setFocusedElement()
+        clears :active before finding the focusable element and end up clearing
+        the active state on a target in the active chain.
+
+        The test "pseudo-active-with-programmatic-focus.html" shows how to invalidate
+        arbitrary elements using _javascript_. This can cause severely broken active
+        chains where invalidation never cleans some ancestors.
+
+        Tests: fast/css/pseudo-active-on-labeled-element-not-canceled-by-focus.html
+               fast/css/pseudo-active-with-programmatic-focus.html
+
+        * dom/Document.cpp:
+        (WebCore::Document::setFocusedElement): Deleted.
+
+        * page/EventHandler.cpp:
+        (WebCore::EventHandler::handleMouseDoubleClickEvent):
+        This is WebKit1 specific. The double click event was dispatching
+        the mouseUp and Click with after doing an Active hit test.
+        This causes us to have :active state in and after mouseUp in WebKit1.
+
 2016-06-24  Jer Noble  <[email protected]>
 
         Consider exposing or hiding knowledge of a redirect from clients of WebCoreNSURLSession

Modified: trunk/Source/WebCore/dom/Document.cpp (202469 => 202470)


--- trunk/Source/WebCore/dom/Document.cpp	2016-06-25 19:07:15 UTC (rev 202469)
+++ trunk/Source/WebCore/dom/Document.cpp	2016-06-26 04:34:23 UTC (rev 202470)
@@ -3777,9 +3777,6 @@
 
     // Remove focus from the existing focus node (if any)
     if (oldFocusedElement) {
-        if (oldFocusedElement->active())
-            oldFocusedElement->setActive(false);
-
         oldFocusedElement->setFocus(false);
         setFocusNavigationStartingNode(nullptr);
 

Modified: trunk/Source/WebCore/page/EventHandler.cpp (202469 => 202470)


--- trunk/Source/WebCore/page/EventHandler.cpp	2016-06-25 19:07:15 UTC (rev 202469)
+++ trunk/Source/WebCore/page/EventHandler.cpp	2016-06-26 04:34:23 UTC (rev 202470)
@@ -1752,7 +1752,7 @@
     m_mousePressed = false;
     setLastKnownMousePosition(platformMouseEvent);
 
-    HitTestRequest request(HitTestRequest::Active | HitTestRequest::DisallowUserAgentShadowContent);
+    HitTestRequest request(HitTestRequest::Release | HitTestRequest::DisallowUserAgentShadowContent);
     MouseEventWithHitTestResults mouseEvent = prepareMouseEvent(request, platformMouseEvent);
     Frame* subframe = subframeForHitTestResult(mouseEvent);
     if (m_eventHandlerWillResetCapturingMouseEventsElement)
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to