Title: [202324] trunk
Revision
202324
Author
[email protected]
Date
2016-06-21 22:52:05 -0700 (Tue, 21 Jun 2016)

Log Message

:hover CSS pseudo-class sometimes keeps matching ever after mouse has left the element
https://bugs.webkit.org/show_bug.cgi?id=158340

Patch by Benjamin Poulain <[email protected]> on 2016-06-21
Reviewed by Simon Fraser.

Source/WebCore:

When removing a hovered subtree from the document, we were getting
into an inconsistent state where m_hoveredElement is in the detached
subtree and we have no way of clearing the existing IsHovered flags.

What happens is:
-The root "a" has an child "b" that is hovered.
-"a" starts being removed from the tree, its renderer is destroyed.
-RenderTreeUpdater::tearDownRenderers() pushes "a" on the teardownStack
 and calls hoveredElementDidDetach().
-hoveredElementDidDetach() is called with "a". "a" is not the hovered
 element, the function does nothing.
-RenderTreeUpdater::tearDownRenderers() pushes "b" on the teardownStack
 and calls hoveredElementDidDetach().
-hoveredElementDidDetach() is called with "b". The next parent with a renderer
 is "a", m_hoveredElement is set to "a".
-"a"'s parent is set to nullptr.

-> We have a m_hoveredElement on the root of a detached tree, making
   it impossible to clear the real dirty tree.

This patch changes the order in which we clear the flags.
It is done in the order in which we clear the renderers to ensure
the last element with a dead renderer is the last to update m_hoveredElement.

Tests: fast/css/ancestor-of-hovered-element-detached.html
       fast/css/ancestor-of-hovered-element-removed.html

* Source/WebCore/style/RenderTreeUpdater.cpp:

LayoutTests:

* fast/css/ancestor-of-hovered-element-detached-expected.txt: Added.
* fast/css/ancestor-of-hovered-element-detached.html: Added.
* fast/css/ancestor-of-hovered-element-removed-expected.txt: Added.
* fast/css/ancestor-of-hovered-element-removed.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (202323 => 202324)


--- trunk/LayoutTests/ChangeLog	2016-06-22 05:47:58 UTC (rev 202323)
+++ trunk/LayoutTests/ChangeLog	2016-06-22 05:52:05 UTC (rev 202324)
@@ -1,3 +1,15 @@
+2016-06-21  Benjamin Poulain  <[email protected]>
+
+        :hover CSS pseudo-class sometimes keeps matching ever after mouse has left the element
+        https://bugs.webkit.org/show_bug.cgi?id=158340
+
+        Reviewed by Simon Fraser.
+
+        * fast/css/ancestor-of-hovered-element-detached-expected.txt: Added.
+        * fast/css/ancestor-of-hovered-element-detached.html: Added.
+        * fast/css/ancestor-of-hovered-element-removed-expected.txt: Added.
+        * fast/css/ancestor-of-hovered-element-removed.html: Added.
+
 2016-06-21  Alexey Proskuryakov  <[email protected]>
 
         Test expectations gardening.

Added: trunk/LayoutTests/fast/css/ancestor-of-hovered-element-detached-expected.txt (0 => 202324)


--- trunk/LayoutTests/fast/css/ancestor-of-hovered-element-detached-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/fast/css/ancestor-of-hovered-element-detached-expected.txt	2016-06-22 05:52:05 UTC (rev 202324)
@@ -0,0 +1,16 @@
+Verify the hovered state is updated correctly when an ancestor of the hovered element loses its renderer
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+    Initial state
+PASS elementsWithHoverStyle() is ["html", "body", "prime-ancestor"]
+PASS elementsMatchingHoverSelector() is ["html", "body", "prime-ancestor"]
+Moving over #target
+PASS elementsWithHoverStyle() is ["html", "body", "prime-ancestor", "group", "element-to-remove", "target"]
+PASS elementsMatchingHoverSelector() is ["html", "body", "prime-ancestor", "group", "element-to-remove", "target"]
+Removing the renderer of #element-to-remove
+PASS elementsWithHoverStyle() is ["html", "body", "prime-ancestor", "interceptor"]
+PASS elementsMatchingHoverSelector() is ["html", "body", "prime-ancestor", "interceptor"]
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/fast/css/ancestor-of-hovered-element-detached.html (0 => 202324)


--- trunk/LayoutTests/fast/css/ancestor-of-hovered-element-detached.html	                        (rev 0)
+++ trunk/LayoutTests/fast/css/ancestor-of-hovered-element-detached.html	2016-06-22 05:52:05 UTC (rev 202324)
@@ -0,0 +1,98 @@
+<!DOCTYPE html>
+<html id="html">
+<head>
+<style>
+    * {
+        background-color: white;
+        margin: 0;
+        padding: 0;
+    }
+    :hover {
+        background-color: rgb(50, 100, 150) !important;
+    }
+    #prime-ancestor >> div {
+        width: 100px;
+        height: 100px;
+    }
+    #target {
+        width: 100px;
+        height: 100px;
+        background-color: green;
+        position: absolute;
+        left: 100px;
+    }
+    #interceptor {
+        position: absolute;
+        left: 100px;
+    }
+</style>
+</head>
+<script src=""
+<body id="body">
+    <div id="prime-ancestor">
+        <div id="interceptor">
+        </div>
+        <div id="group">
+            <div id="element-to-remove">
+                <div id="target">
+                </div>
+            </div>
+        </div>
+    </div>
+    <div id="console">
+    </div>
+    <script>
+    description("Verify the hovered state is updated correctly when an ancestor of the hovered element loses its renderer");
+    window.jsTestIsAsync = true;
+
+    function elementsWithHoverStyle() {
+        let elements = [];
+        for (let element of document.querySelectorAll("*")) {
+            if (getComputedStyle(element).backgroundColor === "rgb(50, 100, 150)")
+                elements.push(element.id);
+        }
+        return elements;
+    }
+    function elementsMatchingHoverSelector() {
+        let elements = [];
+        for (let element of document.querySelectorAll(":hover")) {
+            elements.push(element.id);
+        }
+        return elements;
+    }
+
+    if (!window.eventSender) {
+        debug("This test requires eventSender");
+    }
+    eventSender.mouseMoveTo(300, 50);
+    {
+        // See https://bugs.webkit.org/show_bug.cgi?id=74264
+        eventSender.mouseDown()
+        eventSender.mouseUp()
+    }
+
+    debug("Initial state");
+    shouldBe('elementsWithHoverStyle()', '["html", "body", "prime-ancestor"]');
+    shouldBe('elementsMatchingHoverSelector()', '["html", "body", "prime-ancestor"]');
+
+    debug("Moving over #target")
+    eventSender.mouseMoveTo(150, 50);
+    shouldBe('elementsWithHoverStyle()', '["html", "body", "prime-ancestor", "group", "element-to-remove", "target"]');
+    shouldBe('elementsMatchingHoverSelector()', '["html", "body", "prime-ancestor", "group", "element-to-remove", "target"]');
+
+    debug("Removing the renderer of #element-to-remove");
+    var elementToRemove = document.getElementById("element-to-remove");
+    elementToRemove.style.display = "none";
+    // Force layout.
+    offsetTop = elementToRemove.offsetTop;
+
+    // hover updates happen on timer.
+    setTimeout(function() {
+        shouldBe('elementsWithHoverStyle()', '["html", "body", "prime-ancestor", "interceptor"]');
+        shouldBe('elementsMatchingHoverSelector()', '["html", "body", "prime-ancestor", "interceptor"]');
+        finishJSTest();
+    }, 125);
+    </script>
+    <script src=""
+</body>
+</html>

Added: trunk/LayoutTests/fast/css/ancestor-of-hovered-element-removed-expected.txt (0 => 202324)


--- trunk/LayoutTests/fast/css/ancestor-of-hovered-element-removed-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/fast/css/ancestor-of-hovered-element-removed-expected.txt	2016-06-22 05:52:05 UTC (rev 202324)
@@ -0,0 +1,17 @@
+Verify the hovered state is updated correctly when an ancestor of the hovered element is detached from the document's tree
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+    Initial state
+PASS elementsWithHoverStyle() is ["html", "body", "prime-ancestor"]
+PASS elementsMatchingHoverSelector() is ["html", "body", "prime-ancestor"]
+Moving over #target
+PASS elementsWithHoverStyle() is ["html", "body", "prime-ancestor", "group", "element-to-remove", "target"]
+PASS elementsMatchingHoverSelector() is ["html", "body", "prime-ancestor", "group", "element-to-remove", "target"]
+Removing the renderer of #element-to-remove
+PASS elementsMatchingHoverSelector(elementToRemove) is []
+PASS elementsWithHoverStyle() is ["html", "body", "prime-ancestor", "interceptor"]
+PASS elementsMatchingHoverSelector() is ["html", "body", "prime-ancestor", "interceptor"]
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/fast/css/ancestor-of-hovered-element-removed.html (0 => 202324)


--- trunk/LayoutTests/fast/css/ancestor-of-hovered-element-removed.html	                        (rev 0)
+++ trunk/LayoutTests/fast/css/ancestor-of-hovered-element-removed.html	2016-06-22 05:52:05 UTC (rev 202324)
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<html id="html">
+<head>
+<style>
+    * {
+        background-color: white;
+        margin: 0;
+        padding: 0;
+    }
+    :hover {
+        background-color: rgb(50, 100, 150) !important;
+    }
+    #prime-ancestor >> div {
+        width: 100px;
+        height: 100px;
+    }
+    #target {
+        width: 100px;
+        height: 100px;
+        background-color: green;
+        position: absolute;
+        left: 100px;
+    }
+    #interceptor {
+        position: absolute;
+        left: 100px;
+    }
+</style>
+</head>
+<script src=""
+<body id="body">
+    <div id="prime-ancestor">
+        <div id="interceptor">
+        </div>
+        <div id="group">
+            <div id="element-to-remove">
+                <div id="target">
+                </div>
+            </div>
+        </div>
+    </div>
+    <div id="console">
+    </div>
+    <script>
+    description("Verify the hovered state is updated correctly when an ancestor of the hovered element is detached from the document's tree");
+    window.jsTestIsAsync = true;
+
+    function elementsWithHoverStyle() {
+        let elements = [];
+        for (let element of document.querySelectorAll("*")) {
+            if (getComputedStyle(element).backgroundColor === "rgb(50, 100, 150)")
+                elements.push(element.id);
+        }
+        return elements;
+    }
+    function elementsMatchingHoverSelector(root = document) {
+        let elements = [];
+        for (let element of root.querySelectorAll(":hover")) {
+            elements.push(element.id);
+        }
+        return elements;
+    }
+
+    if (!window.eventSender) {
+        debug("This test requires eventSender");
+    }
+    eventSender.mouseMoveTo(300, 50);
+    {
+        // See https://bugs.webkit.org/show_bug.cgi?id=74264
+        eventSender.mouseDown()
+        eventSender.mouseUp()
+    }
+
+    debug("Initial state");
+    shouldBe('elementsWithHoverStyle()', '["html", "body", "prime-ancestor"]');
+    shouldBe('elementsMatchingHoverSelector()', '["html", "body", "prime-ancestor"]');
+
+    debug("Moving over #target")
+    eventSender.mouseMoveTo(150, 50);
+    shouldBe('elementsWithHoverStyle()', '["html", "body", "prime-ancestor", "group", "element-to-remove", "target"]');
+    shouldBe('elementsMatchingHoverSelector()', '["html", "body", "prime-ancestor", "group", "element-to-remove", "target"]');
+
+    debug("Removing the renderer of #element-to-remove");
+    var elementToRemove = document.getElementById("element-to-remove");
+    elementToRemove.parentElement.removeChild(elementToRemove);
+    shouldBe('elementsMatchingHoverSelector(elementToRemove)', '[]');
+
+    // Force layout.
+    offsetTop = elementToRemove.offsetTop;
+
+    // hover updates happen on timer.
+    setTimeout(function() {
+        shouldBe('elementsWithHoverStyle()', '["html", "body", "prime-ancestor", "interceptor"]');
+        shouldBe('elementsMatchingHoverSelector()', '["html", "body", "prime-ancestor", "interceptor"]');
+        finishJSTest();
+    }, 125);
+    </script>
+    <script src=""
+</body>
+</html>

Modified: trunk/LayoutTests/platform/ios-simulator/TestExpectations (202323 => 202324)


--- trunk/LayoutTests/platform/ios-simulator/TestExpectations	2016-06-22 05:47:58 UTC (rev 202323)
+++ trunk/LayoutTests/platform/ios-simulator/TestExpectations	2016-06-22 05:52:05 UTC (rev 202324)
@@ -1389,6 +1389,8 @@
 fast/clip/clip-when-rect-has-fractional-pixel-value.html [ ImageOnlyFailure ]
 fast/css-generated-content/table-parts-before-and-after.html [ Failure ]
 fast/css/absolute-child-with-percent-height-inside-relative-parent.html [ Failure ]
+fast/css/ancestor-of-hovered-element-detached.html [ Failure ]
+fast/css/ancestor-of-hovered-element-removed.html [ Failure ]
 fast/css/background-image-with-baseurl.html [ Failure ]
 fast/css/button-height.html [ Failure ]
 fast/css/caption-width-absolute-position-offset-top.htm [ Failure ]

Modified: trunk/Source/WebCore/ChangeLog (202323 => 202324)


--- trunk/Source/WebCore/ChangeLog	2016-06-22 05:47:58 UTC (rev 202323)
+++ trunk/Source/WebCore/ChangeLog	2016-06-22 05:52:05 UTC (rev 202324)
@@ -1,3 +1,39 @@
+2016-06-21  Benjamin Poulain  <[email protected]>
+
+        :hover CSS pseudo-class sometimes keeps matching ever after mouse has left the element
+        https://bugs.webkit.org/show_bug.cgi?id=158340
+
+        Reviewed by Simon Fraser.
+
+        When removing a hovered subtree from the document, we were getting
+        into an inconsistent state where m_hoveredElement is in the detached
+        subtree and we have no way of clearing the existing IsHovered flags.
+
+        What happens is:
+        -The root "a" has an child "b" that is hovered.
+        -"a" starts being removed from the tree, its renderer is destroyed.
+        -RenderTreeUpdater::tearDownRenderers() pushes "a" on the teardownStack
+         and calls hoveredElementDidDetach().
+        -hoveredElementDidDetach() is called with "a". "a" is not the hovered
+         element, the function does nothing.
+        -RenderTreeUpdater::tearDownRenderers() pushes "b" on the teardownStack
+         and calls hoveredElementDidDetach().
+        -hoveredElementDidDetach() is called with "b". The next parent with a renderer
+         is "a", m_hoveredElement is set to "a".
+        -"a"'s parent is set to nullptr.
+
+        -> We have a m_hoveredElement on the root of a detached tree, making
+           it impossible to clear the real dirty tree.
+
+        This patch changes the order in which we clear the flags.
+        It is done in the order in which we clear the renderers to ensure
+        the last element with a dead renderer is the last to update m_hoveredElement.
+
+        Tests: fast/css/ancestor-of-hovered-element-detached.html
+               fast/css/ancestor-of-hovered-element-removed.html
+
+        * Source/WebCore/style/RenderTreeUpdater.cpp:
+
 2016-06-21  Youenn Fablet  <[email protected]>
 
         [Fetch API] Rename 'origin-only' referrer policy to 'origin'

Modified: trunk/Source/WebCore/style/RenderTreeUpdater.cpp (202323 => 202324)


--- trunk/Source/WebCore/style/RenderTreeUpdater.cpp	2016-06-22 05:47:58 UTC (rev 202323)
+++ trunk/Source/WebCore/style/RenderTreeUpdater.cpp	2016-06-22 05:52:05 UTC (rev 202324)
@@ -518,10 +518,6 @@
     auto push = [&] (Element& element) {
         if (element.hasCustomStyleResolveCallbacks())
             element.willDetachRenderers();
-        if (teardownType != TeardownType::KeepHoverAndActive)
-            element.clearHoverAndActiveStatusBeforeDetachingRenderer();
-        element.clearStyleDerivedDataBeforeDetachingRenderer();
-
         teardownStack.append(&element);
     };
 
@@ -529,6 +525,10 @@
         while (teardownStack.size() > depth) {
             auto& element = *teardownStack.takeLast();
 
+            if (teardownType != TeardownType::KeepHoverAndActive)
+                element.clearHoverAndActiveStatusBeforeDetachingRenderer();
+            element.clearStyleDerivedDataBeforeDetachingRenderer();
+
             if (auto* renderer = element.renderer()) {
                 renderer->destroyAndCleanupAnonymousWrappers();
                 element.setRenderer(nullptr);
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to