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);