Index: third_party/WebKit/Source/core/paint/PaintPropertyTreeUpdateTests.cpp |
diff --git a/third_party/WebKit/Source/core/paint/PaintPropertyTreeUpdateTests.cpp b/third_party/WebKit/Source/core/paint/PaintPropertyTreeUpdateTests.cpp |
new file mode 100644 |
index 0000000000000000000000000000000000000000..e983490c89dc7f94ba7b167c155eb4c6399d4b10 |
--- /dev/null |
+++ b/third_party/WebKit/Source/core/paint/PaintPropertyTreeUpdateTests.cpp |
@@ -0,0 +1,444 @@ |
+// Copyright 2016 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "core/html/HTMLIFrameElement.h" |
+#include "core/paint/PaintPropertyTreeBuilderTest.h" |
+#include "core/paint/PaintPropertyTreePrinter.h" |
+ |
+namespace blink { |
+ |
+// Tests covering incremental updates of paint property trees. |
+class PaintPropertyTreeUpdateTest : public PaintPropertyTreeBuilderTest {}; |
+ |
+TEST_P(PaintPropertyTreeUpdateTest, |
+ ThreadedScrollingDisabledMainThreadScrollReason) { |
+ setBodyInnerHTML( |
+ "<style>" |
+ " #overflowA {" |
+ " position: absolute;" |
+ " overflow: scroll;" |
+ " width: 20px;" |
+ " height: 20px;" |
+ " }" |
+ " .forceScroll {" |
+ " height: 4000px;" |
+ " }" |
+ "</style>" |
+ "<div id='overflowA'>" |
+ " <div class='forceScroll'></div>" |
+ "</div>" |
+ "<div class='forceScroll'></div>"); |
+ Element* overflowA = document().getElementById("overflowA"); |
+ EXPECT_FALSE(frameScroll()->threadedScrollingDisabled()); |
+ EXPECT_FALSE(overflowA->layoutObject() |
+ ->paintProperties() |
+ ->scroll() |
+ ->threadedScrollingDisabled()); |
+ |
+ document().settings()->setThreadedScrollingEnabled(false); |
+ // TODO(pdr): The main thread scrolling setting should invalidate properties. |
+ document().view()->setNeedsPaintPropertyUpdate(); |
+ overflowA->layoutObject()->setNeedsPaintPropertyUpdate(); |
+ document().view()->updateAllLifecyclePhases(); |
+ |
+ EXPECT_TRUE(frameScroll()->threadedScrollingDisabled()); |
+ EXPECT_TRUE(overflowA->layoutObject() |
+ ->paintProperties() |
+ ->scroll() |
+ ->threadedScrollingDisabled()); |
+} |
+ |
+TEST_P(PaintPropertyTreeUpdateTest, |
+ BackgroundAttachmentFixedMainThreadScrollReasonsWithNestedScrollers) { |
+ setBodyInnerHTML( |
+ "<style>" |
+ " #overflowA {" |
+ " position: absolute;" |
+ " overflow: scroll;" |
+ " width: 20px;" |
+ " height: 20px;" |
+ " }" |
+ " #overflowB {" |
+ " position: absolute;" |
+ " overflow: scroll;" |
+ " width: 5px;" |
+ " height: 3px;" |
+ " }" |
+ " .backgroundAttachmentFixed {" |
+ " background-image: url('foo');" |
+ " background-attachment: fixed;" |
+ " }" |
+ " .forceScroll {" |
+ " height: 4000px;" |
+ " }" |
+ "</style>" |
+ "<div id='overflowA'>" |
+ " <div id='overflowB' class='backgroundAttachmentFixed'>" |
+ " <div class='forceScroll'></div>" |
+ " </div>" |
+ " <div class='forceScroll'></div>" |
+ "</div>" |
+ "<div class='forceScroll'></div>"); |
+ Element* overflowA = document().getElementById("overflowA"); |
+ Element* overflowB = document().getElementById("overflowB"); |
+ |
+ EXPECT_TRUE(frameScroll()->hasBackgroundAttachmentFixedDescendants()); |
+ EXPECT_TRUE(overflowA->layoutObject() |
+ ->paintProperties() |
+ ->scroll() |
+ ->hasBackgroundAttachmentFixedDescendants()); |
+ EXPECT_FALSE(overflowB->layoutObject() |
+ ->paintProperties() |
+ ->scroll() |
+ ->hasBackgroundAttachmentFixedDescendants()); |
+ |
+ // Removing a main thread scrolling reason should update the entire tree. |
+ overflowB->removeAttribute("class"); |
+ document().view()->updateAllLifecyclePhases(); |
+ EXPECT_FALSE(frameScroll()->hasBackgroundAttachmentFixedDescendants()); |
+ EXPECT_FALSE(overflowA->layoutObject() |
+ ->paintProperties() |
+ ->scroll() |
+ ->hasBackgroundAttachmentFixedDescendants()); |
+ EXPECT_FALSE(overflowB->layoutObject() |
+ ->paintProperties() |
+ ->scroll() |
+ ->hasBackgroundAttachmentFixedDescendants()); |
+ |
+ // Adding a main thread scrolling reason should update the entire tree. |
+ overflowB->setAttribute(HTMLNames::classAttr, "backgroundAttachmentFixed"); |
+ document().view()->updateAllLifecyclePhases(); |
+ EXPECT_TRUE(frameScroll()->hasBackgroundAttachmentFixedDescendants()); |
+ EXPECT_TRUE(overflowA->layoutObject() |
+ ->paintProperties() |
+ ->scroll() |
+ ->hasBackgroundAttachmentFixedDescendants()); |
+ EXPECT_FALSE(overflowB->layoutObject() |
+ ->paintProperties() |
+ ->scroll() |
+ ->hasBackgroundAttachmentFixedDescendants()); |
+} |
+ |
+TEST_P(PaintPropertyTreeUpdateTest, |
+ BackgroundAttachmentFixedMainThreadScrollReasonsWithFixedScroller) { |
+ setBodyInnerHTML( |
+ "<style>" |
+ " #overflowA {" |
+ " position: absolute;" |
+ " overflow: scroll;" |
+ " width: 20px;" |
+ " height: 20px;" |
+ " }" |
+ " #overflowB {" |
+ " position: fixed;" |
+ " overflow: scroll;" |
+ " width: 5px;" |
+ " height: 3px;" |
+ " }" |
+ " .backgroundAttachmentFixed {" |
+ " background-image: url('foo');" |
+ " background-attachment: fixed;" |
+ " }" |
+ " .forceScroll {" |
+ " height: 4000px;" |
+ " }" |
+ "</style>" |
+ "<div id='overflowA'>" |
+ " <div id='overflowB' class='backgroundAttachmentFixed'>" |
+ " <div class='forceScroll'></div>" |
+ " </div>" |
+ " <div class='forceScroll'></div>" |
+ "</div>" |
+ "<div class='forceScroll'></div>"); |
+ Element* overflowA = document().getElementById("overflowA"); |
+ Element* overflowB = document().getElementById("overflowB"); |
+ |
+ // This should be false. We are not as strict about main thread scrolling |
+ // reasons as we could be. |
+ EXPECT_TRUE(overflowA->layoutObject() |
+ ->paintProperties() |
+ ->scroll() |
+ ->hasBackgroundAttachmentFixedDescendants()); |
+ EXPECT_FALSE(overflowB->layoutObject() |
+ ->paintProperties() |
+ ->scroll() |
+ ->hasBackgroundAttachmentFixedDescendants()); |
+ EXPECT_TRUE(overflowB->layoutObject() |
+ ->paintProperties() |
+ ->scroll() |
+ ->parent() |
+ ->isRoot()); |
+ |
+ // Removing a main thread scrolling reason should update the entire tree. |
+ overflowB->removeAttribute("class"); |
+ document().view()->updateAllLifecyclePhases(); |
+ EXPECT_FALSE(overflowA->layoutObject() |
+ ->paintProperties() |
+ ->scroll() |
+ ->hasBackgroundAttachmentFixedDescendants()); |
+ EXPECT_FALSE(overflowB->layoutObject() |
+ ->paintProperties() |
+ ->scroll() |
+ ->hasBackgroundAttachmentFixedDescendants()); |
+ EXPECT_FALSE(overflowB->layoutObject() |
+ ->paintProperties() |
+ ->scroll() |
+ ->parent() |
+ ->hasBackgroundAttachmentFixedDescendants()); |
+} |
+ |
+TEST_P(PaintPropertyTreeUpdateTest, DescendantNeedsUpdateAcrossFrames) { |
+ setBodyInnerHTML( |
+ "<style>body { margin: 0; }</style>" |
+ "<div id='divWithTransform' style='transform: translate3d(1px,2px,3px);'>" |
+ " <iframe style='border: 7px solid black'></iframe>" |
+ "</div>"); |
+ setChildFrameHTML( |
+ "<style>body { margin: 0; }</style><div id='transform' style='transform: " |
+ "translate3d(4px, 5px, 6px); width: 100px; height: 200px'></div>"); |
+ |
+ FrameView* frameView = document().view(); |
+ frameView->updateAllLifecyclePhases(); |
+ |
+ LayoutObject* divWithTransform = |
+ document().getElementById("divWithTransform")->layoutObject(); |
+ LayoutObject* childLayoutView = childDocument().layoutView(); |
+ LayoutObject* innerDivWithTransform = |
+ childDocument().getElementById("transform")->layoutObject(); |
+ |
+ // Initially, no objects should need a descendant update. |
+ EXPECT_FALSE(document().layoutView()->descendantNeedsPaintPropertyUpdate()); |
+ EXPECT_FALSE(divWithTransform->descendantNeedsPaintPropertyUpdate()); |
+ EXPECT_FALSE(childLayoutView->descendantNeedsPaintPropertyUpdate()); |
+ EXPECT_FALSE(innerDivWithTransform->descendantNeedsPaintPropertyUpdate()); |
+ |
+ // Marking the child div as needing a paint property update should propagate |
+ // up the tree and across frames. |
+ innerDivWithTransform->setNeedsPaintPropertyUpdate(); |
+ EXPECT_TRUE(document().layoutView()->descendantNeedsPaintPropertyUpdate()); |
+ EXPECT_TRUE(divWithTransform->descendantNeedsPaintPropertyUpdate()); |
+ EXPECT_TRUE(childLayoutView->descendantNeedsPaintPropertyUpdate()); |
+ EXPECT_TRUE(innerDivWithTransform->needsPaintPropertyUpdate()); |
+ EXPECT_FALSE(innerDivWithTransform->descendantNeedsPaintPropertyUpdate()); |
+ |
+ // After a lifecycle update, no nodes should need a descendant update. |
+ frameView->updateAllLifecyclePhases(); |
+ EXPECT_FALSE(document().layoutView()->descendantNeedsPaintPropertyUpdate()); |
+ EXPECT_FALSE(divWithTransform->descendantNeedsPaintPropertyUpdate()); |
+ EXPECT_FALSE(childLayoutView->descendantNeedsPaintPropertyUpdate()); |
+ EXPECT_FALSE(innerDivWithTransform->descendantNeedsPaintPropertyUpdate()); |
+ |
+ // A child frame marked as needing a paint property update should not be |
+ // skipped if the owning layout tree does not need an update. |
+ FrameView* childFrameView = childDocument().view(); |
+ childFrameView->setNeedsPaintPropertyUpdate(); |
+ EXPECT_TRUE(document().layoutView()->descendantNeedsPaintPropertyUpdate()); |
+ frameView->updateAllLifecyclePhases(); |
+ EXPECT_FALSE(document().layoutView()->descendantNeedsPaintPropertyUpdate()); |
+ EXPECT_FALSE(frameView->needsPaintPropertyUpdate()); |
+ EXPECT_FALSE(childFrameView->needsPaintPropertyUpdate()); |
+} |
+ |
+TEST_P(PaintPropertyTreeUpdateTest, UpdatingFrameViewContentClip) { |
+ setBodyInnerHTML("hello world."); |
+ EXPECT_EQ(FloatRoundedRect(0, 0, 800, 600), frameContentClip()->clipRect()); |
+ document().view()->resize(800, 599); |
+ document().view()->updateAllLifecyclePhases(); |
+ EXPECT_EQ(FloatRoundedRect(0, 0, 800, 599), frameContentClip()->clipRect()); |
+ document().view()->resize(800, 600); |
+ document().view()->updateAllLifecyclePhases(); |
+ EXPECT_EQ(FloatRoundedRect(0, 0, 800, 600), frameContentClip()->clipRect()); |
+ document().view()->resize(5, 5); |
+ document().view()->updateAllLifecyclePhases(); |
+ EXPECT_EQ(FloatRoundedRect(0, 0, 5, 5), frameContentClip()->clipRect()); |
+} |
+ |
+TEST_P(PaintPropertyTreeUpdateTest, BuildingStopsAtThrottledFrames) { |
+ setBodyInnerHTML( |
+ "<style>body { margin: 0; }</style>" |
+ "<div id='transform' style='transform: translate3d(4px, 5px, 6px);'>" |
+ "</div>" |
+ "<iframe id='iframe' sandbox></iframe>"); |
+ setChildFrameHTML( |
+ "<style>body { margin: 0; }</style>" |
+ "<div id='iframeTransform'" |
+ " style='transform: translate3d(4px, 5px, 6px);'/>"); |
+ |
+ // Move the child frame offscreen so it becomes available for throttling. |
+ auto* iframe = toHTMLIFrameElement(document().getElementById("iframe")); |
+ iframe->setAttribute(HTMLNames::styleAttr, "transform: translateY(5555px)"); |
+ document().view()->updateAllLifecyclePhases(); |
+ // Ensure intersection observer notifications get delivered. |
+ testing::runPendingTasks(); |
+ EXPECT_FALSE(document().view()->isHiddenForThrottling()); |
+ EXPECT_TRUE(childDocument().view()->isHiddenForThrottling()); |
+ |
+ auto* transform = document().getElementById("transform")->layoutObject(); |
+ auto* iframeLayoutView = childDocument().layoutView(); |
+ auto* iframeTransform = |
+ childDocument().getElementById("iframeTransform")->layoutObject(); |
+ |
+ // Invalidate properties in the iframe and ensure ancestors are marked. |
+ iframeTransform->setNeedsPaintPropertyUpdate(); |
+ EXPECT_FALSE(document().layoutView()->needsPaintPropertyUpdate()); |
+ EXPECT_TRUE(document().layoutView()->descendantNeedsPaintPropertyUpdate()); |
+ EXPECT_FALSE(transform->needsPaintPropertyUpdate()); |
+ EXPECT_FALSE(transform->descendantNeedsPaintPropertyUpdate()); |
+ EXPECT_FALSE(iframeLayoutView->needsPaintPropertyUpdate()); |
+ EXPECT_TRUE(iframeLayoutView->descendantNeedsPaintPropertyUpdate()); |
+ EXPECT_TRUE(iframeTransform->needsPaintPropertyUpdate()); |
+ EXPECT_FALSE(iframeTransform->descendantNeedsPaintPropertyUpdate()); |
+ |
+ transform->setNeedsPaintPropertyUpdate(); |
+ EXPECT_TRUE(transform->needsPaintPropertyUpdate()); |
+ EXPECT_FALSE(transform->descendantNeedsPaintPropertyUpdate()); |
+ |
+ { |
+ DocumentLifecycle::AllowThrottlingScope throttlingScope( |
+ document().lifecycle()); |
+ EXPECT_FALSE(document().view()->shouldThrottleRendering()); |
+ EXPECT_TRUE(childDocument().view()->shouldThrottleRendering()); |
+ |
+ // A lifecycle update should update all properties except those with |
+ // actively throttled descendants. |
+ document().view()->updateAllLifecyclePhases(); |
+ EXPECT_FALSE(document().layoutView()->needsPaintPropertyUpdate()); |
+ EXPECT_TRUE(document().layoutView()->descendantNeedsPaintPropertyUpdate()); |
+ EXPECT_FALSE(transform->needsPaintPropertyUpdate()); |
+ EXPECT_FALSE(transform->descendantNeedsPaintPropertyUpdate()); |
+ EXPECT_FALSE(iframeLayoutView->needsPaintPropertyUpdate()); |
+ EXPECT_TRUE(iframeLayoutView->descendantNeedsPaintPropertyUpdate()); |
+ EXPECT_TRUE(iframeTransform->needsPaintPropertyUpdate()); |
+ EXPECT_FALSE(iframeTransform->descendantNeedsPaintPropertyUpdate()); |
+ } |
+ |
+ EXPECT_FALSE(document().view()->shouldThrottleRendering()); |
+ EXPECT_FALSE(childDocument().view()->shouldThrottleRendering()); |
+ // Once unthrottled, a lifecycel update should update all properties. |
+ document().view()->updateAllLifecyclePhases(); |
+ EXPECT_FALSE(document().layoutView()->needsPaintPropertyUpdate()); |
+ EXPECT_FALSE(document().layoutView()->descendantNeedsPaintPropertyUpdate()); |
+ EXPECT_FALSE(transform->needsPaintPropertyUpdate()); |
+ EXPECT_FALSE(transform->descendantNeedsPaintPropertyUpdate()); |
+ EXPECT_FALSE(iframeLayoutView->needsPaintPropertyUpdate()); |
+ EXPECT_FALSE(iframeLayoutView->descendantNeedsPaintPropertyUpdate()); |
+ EXPECT_FALSE(iframeTransform->needsPaintPropertyUpdate()); |
+ EXPECT_FALSE(iframeTransform->descendantNeedsPaintPropertyUpdate()); |
+} |
+ |
+TEST_P(PaintPropertyTreeUpdateTest, ClipChangesUpdateOverflowClip) { |
+ setBodyInnerHTML( |
+ "<style>" |
+ " body { margin:0 }" |
+ " #div { overflow:hidden; height:0px; }" |
+ "</style>" |
+ "<div id='div'></div>"); |
+ auto* div = document().getElementById("div"); |
+ div->setAttribute(HTMLNames::styleAttr, "display:inline-block; width:7px;"); |
+ document().view()->updateAllLifecyclePhases(); |
+ auto* clipProperties = div->layoutObject()->paintProperties()->overflowClip(); |
+ EXPECT_EQ(FloatRect(0, 0, 7, 0), clipProperties->clipRect().rect()); |
+ |
+ // Width changes should update the overflow clip. |
+ div->setAttribute(HTMLNames::styleAttr, "display:inline-block; width:7px;"); |
+ document().view()->updateAllLifecyclePhases(); |
+ clipProperties = div->layoutObject()->paintProperties()->overflowClip(); |
+ EXPECT_EQ(FloatRect(0, 0, 7, 0), clipProperties->clipRect().rect()); |
+ div->setAttribute(HTMLNames::styleAttr, "display:inline-block; width:9px;"); |
+ document().view()->updateAllLifecyclePhases(); |
+ EXPECT_EQ(FloatRect(0, 0, 9, 0), clipProperties->clipRect().rect()); |
+ |
+ // An inline block's overflow clip should be updated when padding changes, |
+ // even if the border box remains unchanged. |
+ div->setAttribute(HTMLNames::styleAttr, |
+ "display:inline-block; width:7px; padding-right:3px;"); |
+ document().view()->updateAllLifecyclePhases(); |
+ clipProperties = div->layoutObject()->paintProperties()->overflowClip(); |
+ EXPECT_EQ(FloatRect(0, 0, 10, 0), clipProperties->clipRect().rect()); |
+ div->setAttribute(HTMLNames::styleAttr, |
+ "display:inline-block; width:8px; padding-right:2px;"); |
+ document().view()->updateAllLifecyclePhases(); |
+ EXPECT_EQ(FloatRect(0, 0, 10, 0), clipProperties->clipRect().rect()); |
+ div->setAttribute(HTMLNames::styleAttr, |
+ "display:inline-block; width:8px;" |
+ "padding-right:1px; padding-left:1px;"); |
+ document().view()->updateAllLifecyclePhases(); |
+ EXPECT_EQ(FloatRect(0, 0, 10, 0), clipProperties->clipRect().rect()); |
+ |
+ // An block's overflow clip should be updated when borders change. |
+ div->setAttribute(HTMLNames::styleAttr, "border-right:3px solid red;"); |
+ document().view()->updateAllLifecyclePhases(); |
+ clipProperties = div->layoutObject()->paintProperties()->overflowClip(); |
+ EXPECT_EQ(FloatRect(0, 0, 797, 0), clipProperties->clipRect().rect()); |
+ div->setAttribute(HTMLNames::styleAttr, "border-right:5px solid red;"); |
+ document().view()->updateAllLifecyclePhases(); |
+ EXPECT_EQ(FloatRect(0, 0, 795, 0), clipProperties->clipRect().rect()); |
+ |
+ // Removing overflow clip should remove the property. |
+ div->setAttribute(HTMLNames::styleAttr, "overflow:hidden;"); |
+ document().view()->updateAllLifecyclePhases(); |
+ clipProperties = div->layoutObject()->paintProperties()->overflowClip(); |
+ EXPECT_EQ(FloatRect(0, 0, 800, 0), clipProperties->clipRect().rect()); |
+ div->setAttribute(HTMLNames::styleAttr, "overflow:visible;"); |
+ document().view()->updateAllLifecyclePhases(); |
+ EXPECT_TRUE(!div->layoutObject()->paintProperties() || |
+ !div->layoutObject()->paintProperties()->overflowClip()); |
+} |
+ |
+TEST_P(PaintPropertyTreeUpdateTest, ContainPaintChangesUpdateOverflowClip) { |
+ setBodyInnerHTML( |
+ "<style>" |
+ " body { margin:0 }" |
+ " #div { will-change:transform; width:7px; height:6px; }" |
+ "</style>" |
+ "<div id='div' style='contain:paint;'></div>"); |
+ document().view()->updateAllLifecyclePhases(); |
+ auto* div = document().getElementById("div"); |
+ auto* properties = div->layoutObject()->paintProperties()->overflowClip(); |
+ EXPECT_EQ(FloatRect(0, 0, 7, 6), properties->clipRect().rect()); |
+ |
+ div->setAttribute(HTMLNames::styleAttr, ""); |
+ document().view()->updateAllLifecyclePhases(); |
+ EXPECT_TRUE(!div->layoutObject()->paintProperties() || |
+ !div->layoutObject()->paintProperties()->overflowClip()); |
+} |
+ |
+// A basic sanity check for over-invalidation of paint properties. |
+TEST_P(PaintPropertyTreeUpdateTest, NoPaintPropertyUpdateOnBackgroundChange) { |
+ setBodyInnerHTML("<div id='div' style='background-color: blue'>DIV</div>"); |
+ auto* div = document().getElementById("div"); |
+ |
+ document().view()->updateAllLifecyclePhases(); |
+ div->setAttribute(HTMLNames::styleAttr, "background-color: green"); |
+ document().view()->updateLifecycleToLayoutClean(); |
+ EXPECT_FALSE(div->layoutObject()->needsPaintPropertyUpdate()); |
+} |
+ |
+// Disabled due to stale scrollsOverflow values, see: https://crbug.com/675296. |
+TEST_P(PaintPropertyTreeUpdateTest, |
+ DISABLED_FrameVisibilityChangeUpdatesProperties) { |
+ setBodyInnerHTML( |
+ "<style>body { margin: 0; }</style>" |
+ "<div id='iframeContainer'>" |
+ " <iframe id='iframe' style='width: 100px; height: 100px;'></iframe>" |
+ "</div>"); |
+ setChildFrameHTML( |
+ "<style>body { margin: 0; }</style>" |
+ "<div id='forceScroll' style='height: 3000px;'></div>"); |
+ |
+ FrameView* frameView = document().view(); |
+ frameView->updateAllLifecyclePhases(); |
+ EXPECT_EQ(nullptr, frameScroll(frameView)); |
+ FrameView* childFrameView = childDocument().view(); |
+ EXPECT_NE(nullptr, frameScroll(childFrameView)); |
+ |
+ auto* iframeContainer = document().getElementById("iframeContainer"); |
+ iframeContainer->setAttribute(HTMLNames::styleAttr, "visibility: hidden;"); |
+ frameView->updateAllLifecyclePhases(); |
+ |
+ EXPECT_EQ(nullptr, frameScroll(frameView)); |
+ EXPECT_EQ(nullptr, frameScroll(childFrameView)); |
+} |
+ |
+} // namespace blink |