Absolute positioned element is not placed properly when parent becomes the containing block.
https://bugs.webkit.org/show_bug.cgi?id=157455
<rdar://problem/26212568>

Reviewed by Simon Fraser.

When a container becomes a containing block, we need to check if there are any positioned boxes in its subtree
in order to "re-parent" them. It basically means that we remove them from RenderBlock::positionedDescendants map
and they'll get re-inserted during the next layout correctly.
This patch fixes the case when a container becomes the containing block by setting the transform property and its positioned
child gets misplaced.

Source/WebCore:

Test: fast/block/containing-block-changes.html

* rendering/RenderBlock.cpp:
(WebCore::RenderBlock::removePositionedObjectsIfNeeded):
(WebCore::RenderBlock::styleWillChange):
* rendering/RenderBlock.h:

LayoutTests:

* fast/block/containing-block-changes-expected.html: Added.
* fast/block/containing-block-changes.html: Added.

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@200736 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog
index 917ed8f..5808437 100644
--- a/LayoutTests/ChangeLog
+++ b/LayoutTests/ChangeLog
@@ -1,3 +1,20 @@
+2016-05-11  Zalan Bujtas  <zalan@apple.com>
+
+        Absolute positioned element is not placed properly when parent becomes the containing block.
+        https://bugs.webkit.org/show_bug.cgi?id=157455
+        <rdar://problem/26212568>
+
+        Reviewed by Simon Fraser.
+
+        When a container becomes a containing block, we need to check if there are any positioned boxes in its subtree
+        in order to "re-parent" them. It basically means that we remove them from RenderBlock::positionedDescendants map
+        and they'll get re-inserted during the next layout correctly.
+        This patch fixes the case when a container becomes the containing block by setting the transform property and its positioned
+        child gets misplaced.  
+
+        * fast/block/containing-block-changes-expected.html: Added.
+        * fast/block/containing-block-changes.html: Added.
+
 2016-05-11  Ryosuke Niwa  <rniwa@webkit.org>
 
         Add a failing expectation on iOS for the test added in r200712
diff --git a/LayoutTests/fast/block/containing-block-changes-expected.html b/LayoutTests/fast/block/containing-block-changes-expected.html
new file mode 100644
index 0000000..99026dd
--- /dev/null
+++ b/LayoutTests/fast/block/containing-block-changes-expected.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>This tests that we properly reparent containing block descendants.</title>
+<style>
+.container {
+    position: relative;
+    height: 20px;
+    width: 20px;
+    border: 10px solid gray;
+}
+
+.indented {
+  left: 10px;
+}
+
+.box {
+    left: 0px;
+    top: 0px;
+    height: 10px;
+    width: 10px;
+    background-color: blue;
+}
+</style>
+</head>
+<body>
+<div class=box style="position: absolute"></div>
+<div class="container indented"><div class=box></div></div>
+<div class=container><div class=box></div></div>
+<div class="container indented"><div class=box></div></div>
+<div class="container indented"><div class=box></div></div>
+<div class="container indented"><div class=box></div></div>
+<div class=container><div class=box></div></div>
+<div class=container></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/LayoutTests/fast/block/containing-block-changes.html b/LayoutTests/fast/block/containing-block-changes.html
new file mode 100644
index 0000000..0d63281
--- /dev/null
+++ b/LayoutTests/fast/block/containing-block-changes.html
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>This tests that we properly reparent containing block descendants.</title>
+<style>
+    .translate {
+        transform: translateX(10px);
+    }
+
+    .positioned {
+        position: relative;
+    }
+    
+    .container {
+      height: 20px;
+      width: 20px;
+      border: 10px solid gray;
+    }
+    
+    .box {
+      position: absolute;
+      left: 0px;
+      top: 0px;
+      height: 10px;
+      width: 10px;
+      background-color: blue;
+    }
+</style>
+</head>
+<body>
+<div class="container"><div class="box"></div></div>
+<div class="container"><div class="box"></div></div>
+<div class="container"><div class="box"></div></div>
+<div class="container"><div class="box"></div></div>
+<div class="container"><div class="box"></div></div>
+<div class="container"><div class="box"></div></div>
+<div class="container"><div class="box"></div></div>
+<script>
+if (window.testRunner)
+    testRunner.waitUntilDone();
+
+setTimeout(function() {
+  document.getElementsByClassName("container")[0].classList.toggle("translate");
+  document.getElementsByClassName("container")[1].classList.toggle("positioned");
+  document.getElementsByClassName("container")[2].classList.toggle("translate");
+  document.getElementsByClassName("container")[3].classList.toggle("positioned");
+  document.getElementsByClassName("container")[4].classList.toggle("positioned");
+  document.getElementsByClassName("container")[5].classList.toggle("translate");
+  document.getElementsByClassName("container")[6].classList.toggle("translate");
+  document.body.offsetHeight;
+  setTimeout(function() {
+    document.getElementsByClassName("container")[2].classList.toggle("positioned"); 
+    document.getElementsByClassName("container")[3].classList.toggle("translate");
+    document.getElementsByClassName("container")[4].classList.toggle("translate"); 
+    document.getElementsByClassName("container")[5].classList.toggle("positioned"); 
+    document.getElementsByClassName("container")[6].classList.toggle("positioned"); 
+    document.body.offsetHeight;
+    setTimeout(function() {
+      document.getElementsByClassName("container")[4].classList.toggle("positioned"); 
+      document.getElementsByClassName("container")[5].classList.toggle("translate"); 
+      document.getElementsByClassName("container")[6].classList.toggle("translate"); 
+      document.body.offsetHeight;
+      setTimeout(function() {
+        document.getElementsByClassName("container")[6].classList.toggle("positioned"); 
+        document.body.offsetHeight;
+        if (window.testRunner)
+          testRunner.notifyDone();
+      }, 0);
+    }, 0);
+  }, 0);
+}, 0);
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index 674aa29..0367586 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,24 @@
+2016-05-11  Zalan Bujtas  <zalan@apple.com>
+
+        Absolute positioned element is not placed properly when parent becomes the containing block.
+        https://bugs.webkit.org/show_bug.cgi?id=157455
+        <rdar://problem/26212568>
+
+        Reviewed by Simon Fraser.
+
+        When a container becomes a containing block, we need to check if there are any positioned boxes in its subtree
+        in order to "re-parent" them. It basically means that we remove them from RenderBlock::positionedDescendants map
+        and they'll get re-inserted during the next layout correctly.
+        This patch fixes the case when a container becomes the containing block by setting the transform property and its positioned
+        child gets misplaced.  
+
+        Test: fast/block/containing-block-changes.html
+
+        * rendering/RenderBlock.cpp:
+        (WebCore::RenderBlock::removePositionedObjectsIfNeeded):
+        (WebCore::RenderBlock::styleWillChange):
+        * rendering/RenderBlock.h:
+
 2016-05-11  Commit Queue  <commit-queue@webkit.org>
 
         Unreviewed, rolling out r200700, r200703, and r200713.
diff --git a/Source/WebCore/rendering/RenderBlock.cpp b/Source/WebCore/rendering/RenderBlock.cpp
index a90b5e8..cac5b02 100644
--- a/Source/WebCore/rendering/RenderBlock.cpp
+++ b/Source/WebCore/rendering/RenderBlock.cpp
@@ -236,38 +236,45 @@
     return gRareDataMap ? gRareDataMap->contains(this) : false;
 }
 
+void RenderBlock::removePositionedObjectsIfNeeded(const RenderStyle& oldStyle, const RenderStyle& newStyle)
+{
+    bool hadTransform = oldStyle.hasTransformRelatedProperty();
+    bool willHaveTransform = newStyle.hasTransformRelatedProperty();
+    if (oldStyle.position() == newStyle.position() && hadTransform == willHaveTransform)
+        return;
+
+    // We are no longer a containing block.
+    if (newStyle.position() == StaticPosition && !willHaveTransform) {
+        // Clear our positioned objects list. Our absolutely positioned descendants will be
+        // inserted into our containing block's positioned objects list during layout.
+        removePositionedObjects(nullptr, NewContainingBlock);
+        return;
+    }
+    
+    // We are a new containing block.
+    if (oldStyle.position() == StaticPosition && !hadTransform) {
+        // Remove our absolutely positioned descendants from their current containing block.
+        // They will be inserted into our positioned objects list during layout.
+        auto* containingBlock = parent();
+        while (containingBlock && !is<RenderView>(*containingBlock)
+            && (containingBlock->style().position() == StaticPosition || (containingBlock->isInline() && !containingBlock->isReplaced()))) {
+            if (containingBlock->style().position() == RelativePosition && containingBlock->isInline() && !containingBlock->isReplaced()) {
+                containingBlock = containingBlock->containingBlock();
+                break;
+            }
+            containingBlock = containingBlock->parent();
+        }
+        if (containingBlock && is<RenderBlock>(*containingBlock))
+            downcast<RenderBlock>(*containingBlock).removePositionedObjects(this, NewContainingBlock);
+    }
+}
+
 void RenderBlock::styleWillChange(StyleDifference diff, const RenderStyle& newStyle)
 {
     const RenderStyle* oldStyle = hasInitializedStyle() ? &style() : nullptr;
-
     setReplaced(newStyle.isDisplayInlineType());
-
-    if (oldStyle && oldStyle->hasTransformRelatedProperty() && !newStyle.hasTransformRelatedProperty())
-        removePositionedObjects(nullptr, NewContainingBlock);
-
-    if (oldStyle && parent() && diff == StyleDifferenceLayout && oldStyle->position() != newStyle.position()) {
-        if (newStyle.position() == StaticPosition)
-            // Clear our positioned objects list. Our absolutely positioned descendants will be
-            // inserted into our containing block's positioned objects list during layout.
-            removePositionedObjects(nullptr, NewContainingBlock);
-        else if (oldStyle->position() == StaticPosition) {
-            // Remove our absolutely positioned descendants from their current containing block.
-            // They will be inserted into our positioned objects list during layout.
-            auto containingBlock = parent();
-            while (containingBlock && !is<RenderView>(*containingBlock)
-                && (containingBlock->style().position() == StaticPosition || (containingBlock->isInline() && !containingBlock->isReplaced()))) {
-                if (containingBlock->style().position() == RelativePosition && containingBlock->isInline() && !containingBlock->isReplaced()) {
-                    containingBlock = containingBlock->containingBlock();
-                    break;
-                }
-                containingBlock = containingBlock->parent();
-            }
-
-            if (is<RenderBlock>(*containingBlock))
-                downcast<RenderBlock>(*containingBlock).removePositionedObjects(this, NewContainingBlock);
-        }
-    }
-
+    if (oldStyle)
+        removePositionedObjectsIfNeeded(*oldStyle, newStyle);
     RenderBox::styleWillChange(diff, newStyle);
 }
 
diff --git a/Source/WebCore/rendering/RenderBlock.h b/Source/WebCore/rendering/RenderBlock.h
index e3a49d1..d8aa29a 100644
--- a/Source/WebCore/rendering/RenderBlock.h
+++ b/Source/WebCore/rendering/RenderBlock.h
@@ -489,6 +489,8 @@
 
     RenderFlowThread* updateCachedFlowThreadContainingBlock(RenderFlowThread*) const;
 
+    void removePositionedObjectsIfNeeded(const RenderStyle& oldStyle, const RenderStyle& newStyle);
+
 private:
     bool hasRareData() const;