blob: d878e97a3f5a2718e554ab84d0924f40acdaa077 [file] [log] [blame]
// 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 "third_party/blink/renderer/core/html/forms/html_select_element.h"
#include "third_party/blink/renderer/core/html/html_iframe_element.h"
#include "third_party/blink/renderer/core/page/focus_controller.h"
#include "third_party/blink/renderer/core/paint/paint_property_tree_builder_test.h"
#include "third_party/blink/renderer/core/paint/paint_property_tree_printer.h"
namespace blink {
// Tests covering incremental updates of paint property trees.
class PaintPropertyTreeUpdateTest : public PaintPropertyTreeBuilderTest {};
INSTANTIATE_PAINT_TEST_CASE_P(PaintPropertyTreeUpdateTest);
TEST_P(PaintPropertyTreeUpdateTest,
ThreadedScrollingDisabledMainThreadScrollReason) {
SetBodyInnerHTML(R"HTML(
<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>
)HTML");
Element* overflow_a = GetDocument().getElementById("overflowA");
EXPECT_FALSE(DocScroll()->ThreadedScrollingDisabled());
EXPECT_FALSE(overflow_a->GetLayoutObject()
->FirstFragment()
.PaintProperties()
->ScrollTranslation()
->ScrollNode()
->ThreadedScrollingDisabled());
GetDocument().GetSettings()->SetThreadedScrollingEnabled(false);
// TODO(pdr): The main thread scrolling setting should invalidate properties.
GetDocument().View()->SetNeedsPaintPropertyUpdate();
overflow_a->GetLayoutObject()->SetNeedsPaintPropertyUpdate();
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_TRUE(DocScroll()->ThreadedScrollingDisabled());
EXPECT_TRUE(overflow_a->GetLayoutObject()
->FirstFragment()
.PaintProperties()
->ScrollTranslation()
->ScrollNode()
->ThreadedScrollingDisabled());
}
TEST_P(PaintPropertyTreeUpdateTest,
BackgroundAttachmentFixedMainThreadScrollReasonsWithNestedScrollers) {
SetBodyInnerHTML(R"HTML(
<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>
)HTML");
Element* overflow_a = GetDocument().getElementById("overflowA");
Element* overflow_b = GetDocument().getElementById("overflowB");
EXPECT_TRUE(DocScroll()->HasBackgroundAttachmentFixedDescendants());
EXPECT_TRUE(overflow_a->GetLayoutObject()
->FirstFragment()
.PaintProperties()
->ScrollTranslation()
->ScrollNode()
->HasBackgroundAttachmentFixedDescendants());
EXPECT_TRUE(overflow_b->GetLayoutObject()
->FirstFragment()
.PaintProperties()
->ScrollTranslation()
->ScrollNode()
->HasBackgroundAttachmentFixedDescendants());
// Removing a main thread scrolling reason should update the entire tree.
overflow_b->removeAttribute("class");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_FALSE(DocScroll()->HasBackgroundAttachmentFixedDescendants());
EXPECT_FALSE(overflow_a->GetLayoutObject()
->FirstFragment()
.PaintProperties()
->ScrollTranslation()
->ScrollNode()
->HasBackgroundAttachmentFixedDescendants());
EXPECT_FALSE(overflow_b->GetLayoutObject()
->FirstFragment()
.PaintProperties()
->ScrollTranslation()
->ScrollNode()
->HasBackgroundAttachmentFixedDescendants());
// Adding a main thread scrolling reason should update the entire tree.
overflow_b->setAttribute(HTMLNames::classAttr, "backgroundAttachmentFixed");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_TRUE(DocScroll()->HasBackgroundAttachmentFixedDescendants());
EXPECT_TRUE(overflow_a->GetLayoutObject()
->FirstFragment()
.PaintProperties()
->ScrollTranslation()
->ScrollNode()
->HasBackgroundAttachmentFixedDescendants());
EXPECT_TRUE(overflow_b->GetLayoutObject()
->FirstFragment()
.PaintProperties()
->ScrollTranslation()
->ScrollNode()
->HasBackgroundAttachmentFixedDescendants());
}
TEST_P(PaintPropertyTreeUpdateTest, ParentFrameMainThreadScrollReasons) {
SetBodyInnerHTML(R"HTML(
<style>
body { margin: 0; }
.fixedBackground {
background-image: url('foo');
background-attachment: fixed;
}
</style>
<iframe></iframe>
<div id='fixedBackground' class='fixedBackground'></div>
<div id='forceScroll' style='height: 8888px;'></div>
)HTML");
SetChildFrameHTML(
"<style>body { margin: 0; }</style>"
"<div id='forceScroll' style='height: 8888px;'></div>");
GetDocument().View()->UpdateAllLifecyclePhases();
Document* parent = &GetDocument();
EXPECT_TRUE(DocScroll(parent)->HasBackgroundAttachmentFixedDescendants());
Document* child = &ChildDocument();
EXPECT_TRUE(DocScroll(child)->HasBackgroundAttachmentFixedDescendants());
// Removing a main thread scrolling reason should update the entire tree.
auto* fixed_background = GetDocument().getElementById("fixedBackground");
fixed_background->removeAttribute(HTMLNames::classAttr);
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_FALSE(DocScroll(parent)->HasBackgroundAttachmentFixedDescendants());
EXPECT_FALSE(DocScroll(child)->HasBackgroundAttachmentFixedDescendants());
// Adding a main thread scrolling reason should update the entire tree.
fixed_background->setAttribute(HTMLNames::classAttr, "fixedBackground");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_TRUE(DocScroll(parent)->HasBackgroundAttachmentFixedDescendants());
EXPECT_TRUE(DocScroll(child)->HasBackgroundAttachmentFixedDescendants());
}
TEST_P(PaintPropertyTreeUpdateTest, ChildFrameMainThreadScrollReasons) {
SetBodyInnerHTML(R"HTML(
<style>body { margin: 0; }</style>
<iframe></iframe>
<div id='forceScroll' style='height: 8888px;'></div>
)HTML");
SetChildFrameHTML(R"HTML(
<style>
body { margin: 0; }
.fixedBackground {
background-image: url('foo');
background-attachment: fixed;
}
</style>
<div id='fixedBackground' class='fixedBackground'></div>
<div id='forceScroll' style='height: 8888px;'></div>
)HTML");
GetDocument().View()->UpdateAllLifecyclePhases();
Document* parent = &GetDocument();
EXPECT_FALSE(DocScroll(parent)->HasBackgroundAttachmentFixedDescendants());
Document* child = &ChildDocument();
EXPECT_TRUE(DocScroll(child)->HasBackgroundAttachmentFixedDescendants());
// Removing a main thread scrolling reason should update the entire tree.
auto* fixed_background = ChildDocument().getElementById("fixedBackground");
fixed_background->removeAttribute(HTMLNames::classAttr);
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_FALSE(DocScroll(parent)->HasBackgroundAttachmentFixedDescendants());
EXPECT_FALSE(DocScroll(child)->HasBackgroundAttachmentFixedDescendants());
// Adding a main thread scrolling reason should update the entire tree.
fixed_background->setAttribute(HTMLNames::classAttr, "fixedBackground");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_FALSE(DocScroll(parent)->HasBackgroundAttachmentFixedDescendants());
EXPECT_TRUE(DocScroll(child)->HasBackgroundAttachmentFixedDescendants());
}
TEST_P(PaintPropertyTreeUpdateTest,
BackgroundAttachmentFixedMainThreadScrollReasonsWithFixedScroller) {
SetBodyInnerHTML(R"HTML(
<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>
)HTML");
Element* overflow_a = GetDocument().getElementById("overflowA");
Element* overflow_b = GetDocument().getElementById("overflowB");
// This should be false. We are not as strict about main thread scrolling
// reasons as we could be.
EXPECT_TRUE(overflow_a->GetLayoutObject()
->FirstFragment()
.PaintProperties()
->ScrollTranslation()
->ScrollNode()
->HasBackgroundAttachmentFixedDescendants());
EXPECT_FALSE(overflow_b->GetLayoutObject()
->FirstFragment()
.PaintProperties()
->ScrollTranslation()
->ScrollNode()
->HasBackgroundAttachmentFixedDescendants());
EXPECT_TRUE(overflow_b->GetLayoutObject()
->FirstFragment()
.PaintProperties()
->ScrollTranslation()
->ScrollNode()
->Parent()
->IsRoot());
// Removing a main thread scrolling reason should update the entire tree.
overflow_b->removeAttribute("class");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_FALSE(overflow_a->GetLayoutObject()
->FirstFragment()
.PaintProperties()
->ScrollTranslation()
->ScrollNode()
->HasBackgroundAttachmentFixedDescendants());
EXPECT_FALSE(overflow_b->GetLayoutObject()
->FirstFragment()
.PaintProperties()
->ScrollTranslation()
->ScrollNode()
->HasBackgroundAttachmentFixedDescendants());
EXPECT_FALSE(overflow_b->GetLayoutObject()
->FirstFragment()
.PaintProperties()
->ScrollTranslation()
->ScrollNode()
->Parent()
->HasBackgroundAttachmentFixedDescendants());
}
TEST_P(PaintPropertyTreeUpdateTest, DescendantNeedsUpdateAcrossFrames) {
SetBodyInnerHTML(R"HTML(
<style>body { margin: 0; }</style>
<div id='divWithTransform' style='transform: translate3d(1px,2px,3px);'>
<iframe style='border: 7px solid black'></iframe>
</div>
)HTML");
SetChildFrameHTML(
"<style>body { margin: 0; }</style><div id='transform' style='transform: "
"translate3d(4px, 5px, 6px); width: 100px; height: 200px'></div>");
LocalFrameView* frame_view = GetDocument().View();
frame_view->UpdateAllLifecyclePhases();
LayoutObject* div_with_transform =
GetLayoutObjectByElementId("divWithTransform");
LayoutObject* child_layout_view = ChildDocument().GetLayoutView();
LayoutObject* inner_div_with_transform =
ChildDocument().getElementById("transform")->GetLayoutObject();
// Initially, no objects should need a descendant update.
EXPECT_FALSE(
GetDocument().GetLayoutView()->DescendantNeedsPaintPropertyUpdate());
EXPECT_FALSE(div_with_transform->DescendantNeedsPaintPropertyUpdate());
EXPECT_FALSE(child_layout_view->DescendantNeedsPaintPropertyUpdate());
EXPECT_FALSE(inner_div_with_transform->DescendantNeedsPaintPropertyUpdate());
// Marking the child div as needing a paint property update should propagate
// up the tree and across frames.
inner_div_with_transform->SetNeedsPaintPropertyUpdate();
EXPECT_TRUE(
GetDocument().GetLayoutView()->DescendantNeedsPaintPropertyUpdate());
EXPECT_TRUE(div_with_transform->DescendantNeedsPaintPropertyUpdate());
EXPECT_TRUE(child_layout_view->DescendantNeedsPaintPropertyUpdate());
EXPECT_TRUE(inner_div_with_transform->NeedsPaintPropertyUpdate());
EXPECT_FALSE(inner_div_with_transform->DescendantNeedsPaintPropertyUpdate());
// After a lifecycle update, no nodes should need a descendant update.
frame_view->UpdateAllLifecyclePhases();
EXPECT_FALSE(
GetDocument().GetLayoutView()->DescendantNeedsPaintPropertyUpdate());
EXPECT_FALSE(div_with_transform->DescendantNeedsPaintPropertyUpdate());
EXPECT_FALSE(child_layout_view->DescendantNeedsPaintPropertyUpdate());
EXPECT_FALSE(inner_div_with_transform->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.
LocalFrameView* child_frame_view = ChildDocument().View();
child_frame_view->SetNeedsPaintPropertyUpdate();
EXPECT_TRUE(
GetDocument().GetLayoutView()->DescendantNeedsPaintPropertyUpdate());
frame_view->UpdateAllLifecyclePhases();
EXPECT_FALSE(
GetDocument().GetLayoutView()->DescendantNeedsPaintPropertyUpdate());
EXPECT_FALSE(GetDocument().GetLayoutView()->NeedsPaintPropertyUpdate());
EXPECT_FALSE(
child_frame_view->GetLayoutView()->DescendantNeedsPaintPropertyUpdate());
EXPECT_FALSE(child_frame_view->GetLayoutView()->NeedsPaintPropertyUpdate());
}
TEST_P(PaintPropertyTreeUpdateTest, UpdatingFrameViewContentClip) {
SetBodyInnerHTML("hello world.");
EXPECT_EQ(FloatRoundedRect(0, 0, 800, 600), DocContentClip()->ClipRect());
GetDocument().View()->Resize(800, 599);
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(FloatRoundedRect(0, 0, 800, 599), DocContentClip()->ClipRect());
GetDocument().View()->Resize(800, 600);
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(FloatRoundedRect(0, 0, 800, 600), DocContentClip()->ClipRect());
GetDocument().View()->Resize(5, 5);
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(FloatRoundedRect(0, 0, 5, 5), DocContentClip()->ClipRect());
}
// There is also FrameThrottlingTest.UpdatePaintPropertiesOnUnthrottling
// testing with real frame viewport intersection observer. This one tests
// paint property update with or without AllowThrottlingScope.
TEST_P(PaintPropertyTreeUpdateTest, BuildingStopsAtThrottledFrames) {
SetBodyInnerHTML(R"HTML(
<style>body { margin: 0; }</style>
<div id='transform' style='transform: translate3d(4px, 5px, 6px);'>
</div>
<iframe id='iframe' sandbox></iframe>
)HTML");
SetChildFrameHTML(R"HTML(
<style>body { margin: 0; }</style>
<div id='iframeTransform'
style='transform: translate3d(4px, 5px, 6px);'/>
)HTML");
// Move the child frame offscreen so it becomes available for throttling.
auto* iframe = ToHTMLIFrameElement(GetDocument().getElementById("iframe"));
iframe->setAttribute(HTMLNames::styleAttr, "transform: translateY(5555px)");
GetDocument().View()->UpdateAllLifecyclePhases();
// Ensure intersection observer notifications get delivered.
test::RunPendingTasks();
EXPECT_FALSE(GetDocument().View()->IsHiddenForThrottling());
EXPECT_TRUE(ChildDocument().View()->IsHiddenForThrottling());
auto* transform = GetLayoutObjectByElementId("transform");
auto* iframe_layout_view = ChildDocument().GetLayoutView();
auto* iframe_transform =
ChildDocument().getElementById("iframeTransform")->GetLayoutObject();
// Invalidate properties in the iframe and ensure ancestors are marked.
iframe_transform->SetNeedsPaintPropertyUpdate();
EXPECT_FALSE(GetDocument().GetLayoutView()->NeedsPaintPropertyUpdate());
EXPECT_TRUE(
GetDocument().GetLayoutView()->DescendantNeedsPaintPropertyUpdate());
EXPECT_FALSE(transform->NeedsPaintPropertyUpdate());
EXPECT_FALSE(transform->DescendantNeedsPaintPropertyUpdate());
EXPECT_FALSE(iframe_layout_view->NeedsPaintPropertyUpdate());
EXPECT_TRUE(iframe_layout_view->DescendantNeedsPaintPropertyUpdate());
EXPECT_TRUE(iframe_transform->NeedsPaintPropertyUpdate());
EXPECT_FALSE(iframe_transform->DescendantNeedsPaintPropertyUpdate());
transform->SetNeedsPaintPropertyUpdate();
EXPECT_TRUE(transform->NeedsPaintPropertyUpdate());
EXPECT_FALSE(transform->DescendantNeedsPaintPropertyUpdate());
{
DocumentLifecycle::AllowThrottlingScope throttling_scope(
GetDocument().Lifecycle());
EXPECT_FALSE(GetDocument().View()->ShouldThrottleRendering());
EXPECT_TRUE(ChildDocument().View()->ShouldThrottleRendering());
// A lifecycle update should update all properties except those with
// actively throttled descendants.
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_FALSE(GetDocument().GetLayoutView()->NeedsPaintPropertyUpdate());
EXPECT_FALSE(
GetDocument().GetLayoutView()->DescendantNeedsPaintPropertyUpdate());
EXPECT_FALSE(transform->NeedsPaintPropertyUpdate());
EXPECT_FALSE(transform->DescendantNeedsPaintPropertyUpdate());
EXPECT_FALSE(iframe_layout_view->NeedsPaintPropertyUpdate());
EXPECT_TRUE(iframe_layout_view->DescendantNeedsPaintPropertyUpdate());
EXPECT_TRUE(iframe_transform->NeedsPaintPropertyUpdate());
EXPECT_FALSE(iframe_transform->DescendantNeedsPaintPropertyUpdate());
}
EXPECT_FALSE(GetDocument().View()->ShouldThrottleRendering());
EXPECT_FALSE(ChildDocument().View()->ShouldThrottleRendering());
// Once unthrottled, a lifecycel update should update all properties.
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_FALSE(GetDocument().GetLayoutView()->NeedsPaintPropertyUpdate());
EXPECT_FALSE(
GetDocument().GetLayoutView()->DescendantNeedsPaintPropertyUpdate());
EXPECT_FALSE(transform->NeedsPaintPropertyUpdate());
EXPECT_FALSE(transform->DescendantNeedsPaintPropertyUpdate());
EXPECT_FALSE(iframe_layout_view->NeedsPaintPropertyUpdate());
EXPECT_FALSE(iframe_layout_view->DescendantNeedsPaintPropertyUpdate());
EXPECT_FALSE(iframe_transform->NeedsPaintPropertyUpdate());
EXPECT_FALSE(iframe_transform->DescendantNeedsPaintPropertyUpdate());
}
TEST_P(PaintPropertyTreeUpdateTest, ClipChangesUpdateOverflowClip) {
GetDocument().SetCompatibilityMode(Document::kQuirksMode);
SetBodyInnerHTML(R"HTML(
<style>
body { margin:0 }
#div { overflow:hidden; height:0px; }
</style>
<div id='div'>
<div style='width: 100px; height: 100px'></div>
</div>
)HTML");
auto* div = GetDocument().getElementById("div");
div->setAttribute(HTMLNames::styleAttr, "display:inline-block; width:7px;");
GetDocument().View()->UpdateAllLifecyclePhases();
auto* clip_properties =
div->GetLayoutObject()->FirstFragment().PaintProperties()->OverflowClip();
EXPECT_EQ(FloatRect(0, 0, 7, 0), clip_properties->ClipRect().Rect());
// Width changes should update the overflow clip.
div->setAttribute(HTMLNames::styleAttr, "display:inline-block; width:7px;");
GetDocument().View()->UpdateAllLifecyclePhases();
clip_properties =
div->GetLayoutObject()->FirstFragment().PaintProperties()->OverflowClip();
EXPECT_EQ(FloatRect(0, 0, 7, 0), clip_properties->ClipRect().Rect());
div->setAttribute(HTMLNames::styleAttr, "display:inline-block; width:9px;");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(FloatRect(0, 0, 9, 0), clip_properties->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;");
GetDocument().View()->UpdateAllLifecyclePhases();
clip_properties =
div->GetLayoutObject()->FirstFragment().PaintProperties()->OverflowClip();
EXPECT_EQ(FloatRect(0, 0, 10, 0), clip_properties->ClipRect().Rect());
div->setAttribute(HTMLNames::styleAttr,
"display:inline-block; width:8px; padding-right:2px;");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(FloatRect(0, 0, 10, 0), clip_properties->ClipRect().Rect());
div->setAttribute(HTMLNames::styleAttr,
"display:inline-block; width:8px;"
"padding-right:1px; padding-left:1px;");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(FloatRect(0, 0, 10, 0), clip_properties->ClipRect().Rect());
// An block's overflow clip should be updated when borders change.
div->setAttribute(HTMLNames::styleAttr, "border-right:3px solid red;");
GetDocument().View()->UpdateAllLifecyclePhases();
clip_properties =
div->GetLayoutObject()->FirstFragment().PaintProperties()->OverflowClip();
EXPECT_EQ(FloatRect(0, 0, 797, 0), clip_properties->ClipRect().Rect());
div->setAttribute(HTMLNames::styleAttr, "border-right:5px solid red;");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(FloatRect(0, 0, 795, 0), clip_properties->ClipRect().Rect());
// Removing overflow clip should remove the property.
div->setAttribute(HTMLNames::styleAttr, "overflow:hidden;");
GetDocument().View()->UpdateAllLifecyclePhases();
clip_properties =
div->GetLayoutObject()->FirstFragment().PaintProperties()->OverflowClip();
EXPECT_EQ(FloatRect(0, 0, 800, 0), clip_properties->ClipRect().Rect());
div->setAttribute(HTMLNames::styleAttr, "overflow:visible;");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_TRUE(!div->GetLayoutObject()->FirstFragment().PaintProperties() ||
!div->GetLayoutObject()
->FirstFragment()
.PaintProperties()
->OverflowClip());
}
TEST_P(PaintPropertyTreeUpdateTest, ContainPaintChangesUpdateOverflowClip) {
SetBodyInnerHTML(R"HTML(
<style>
body { margin:0 }
#div { will-change:transform; width:7px; height:6px; }
</style>
<div id='div' style='contain:paint;'>
<div style='width: 100px; height: 100px'></div>
</div>
)HTML");
GetDocument().View()->UpdateAllLifecyclePhases();
auto* div = GetDocument().getElementById("div");
auto* properties =
div->GetLayoutObject()->FirstFragment().PaintProperties()->OverflowClip();
EXPECT_EQ(FloatRect(0, 0, 7, 6), properties->ClipRect().Rect());
div->setAttribute(HTMLNames::styleAttr, "");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_TRUE(!div->GetLayoutObject()->FirstFragment().PaintProperties() ||
!div->GetLayoutObject()
->FirstFragment()
.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 = GetDocument().getElementById("div");
GetDocument().View()->UpdateAllLifecyclePhases();
div->setAttribute(HTMLNames::styleAttr, "background-color: green");
GetDocument().View()->UpdateLifecycleToLayoutClean();
EXPECT_FALSE(div->GetLayoutObject()->NeedsPaintPropertyUpdate());
}
// Disabled due to stale scrollsOverflow values, see: https://crbug.com/675296.
TEST_P(PaintPropertyTreeUpdateTest,
DISABLED_FrameVisibilityChangeUpdatesProperties) {
SetBodyInnerHTML(R"HTML(
<style>body { margin: 0; }</style>
<div id='iframeContainer'>
<iframe id='iframe' style='width: 100px; height: 100px;'></iframe>
</div>
)HTML");
SetChildFrameHTML(
"<style>body { margin: 0; }</style>"
"<div id='forceScroll' style='height: 3000px;'></div>");
LocalFrameView* frame_view = GetDocument().View();
frame_view->UpdateAllLifecyclePhases();
EXPECT_EQ(nullptr, DocScroll());
Document* child_doc = &ChildDocument();
EXPECT_NE(nullptr, DocScroll(child_doc));
auto* iframe_container = GetDocument().getElementById("iframeContainer");
iframe_container->setAttribute(HTMLNames::styleAttr, "visibility: hidden;");
frame_view->UpdateAllLifecyclePhases();
EXPECT_EQ(nullptr, DocScroll());
EXPECT_EQ(nullptr, DocScroll(child_doc));
}
TEST_P(PaintPropertyTreeUpdateTest,
TransformNodeWithAnimationLosesNodeWhenAnimationRemoved) {
LoadTestData("transform-animation.html");
Element* target = GetDocument().getElementById("target");
const ObjectPaintProperties* properties =
target->GetLayoutObject()->FirstFragment().PaintProperties();
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled())
EXPECT_TRUE(properties->Transform()->HasDirectCompositingReasons());
// Removing the animation should remove the transform node.
target->removeAttribute(HTMLNames::classAttr);
GetDocument().View()->UpdateAllLifecyclePhases();
// Ensure the paint properties object was cleared as it is no longer needed.
EXPECT_EQ(nullptr,
target->GetLayoutObject()->FirstFragment().PaintProperties());
}
TEST_P(PaintPropertyTreeUpdateTest,
EffectNodeWithAnimationLosesNodeWhenAnimationRemoved) {
LoadTestData("opacity-animation.html");
Element* target = GetDocument().getElementById("target");
const ObjectPaintProperties* properties =
target->GetLayoutObject()->FirstFragment().PaintProperties();
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled())
EXPECT_TRUE(properties->Effect()->HasDirectCompositingReasons());
// Removing the animation should remove the effect node.
target->removeAttribute(HTMLNames::classAttr);
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(nullptr,
target->GetLayoutObject()->FirstFragment().PaintProperties());
}
TEST_P(PaintPropertyTreeUpdateTest,
TransformNodeDoesNotLoseCompositorElementIdWhenAnimationRemoved) {
if (!RuntimeEnabledFeatures::SlimmingPaintV2Enabled())
return;
LoadTestData("transform-animation.html");
Element* target = GetDocument().getElementById("target");
target->setAttribute(HTMLNames::styleAttr, "transform: translateX(2em)");
GetDocument().View()->UpdateAllLifecyclePhases();
const ObjectPaintProperties* properties =
target->GetLayoutObject()->FirstFragment().PaintProperties();
EXPECT_NE(CompositorElementId(),
properties->Transform()->GetCompositorElementId());
// Remove the animation but keep the transform on the element.
target->removeAttribute(HTMLNames::classAttr);
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_NE(CompositorElementId(),
properties->Transform()->GetCompositorElementId());
}
TEST_P(PaintPropertyTreeUpdateTest,
EffectNodeDoesNotLoseCompositorElementIdWhenAnimationRemoved) {
if (!RuntimeEnabledFeatures::SlimmingPaintV2Enabled())
return;
LoadTestData("opacity-animation.html");
Element* target = GetDocument().getElementById("target");
target->setAttribute(HTMLNames::styleAttr, "opacity: 0.2");
GetDocument().View()->UpdateAllLifecyclePhases();
const ObjectPaintProperties* properties =
target->GetLayoutObject()->FirstFragment().PaintProperties();
EXPECT_NE(CompositorElementId(),
properties->Effect()->GetCompositorElementId());
target->removeAttribute(HTMLNames::classAttr);
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_NE(CompositorElementId(),
properties->Effect()->GetCompositorElementId());
}
TEST_P(PaintPropertyTreeUpdateTest, PerspectiveOriginUpdatesOnSizeChanges) {
SetBodyInnerHTML(R"HTML(
<style>
body { margin: 0 }
#perspective {
position: absolute;
perspective: 100px;
width: 100px;
perspective-origin: 50% 50% 0;
}
</style>
<div id='perspective'>
<div id='contents'></div>
</div>
)HTML");
auto* perspective = GetLayoutObjectByElementId("perspective");
EXPECT_EQ(
TransformationMatrix().ApplyPerspective(100),
perspective->FirstFragment().PaintProperties()->Perspective()->Matrix());
EXPECT_EQ(
FloatPoint3D(50, 0, 0),
perspective->FirstFragment().PaintProperties()->Perspective()->Origin());
auto* contents = GetDocument().getElementById("contents");
contents->setAttribute(HTMLNames::styleAttr, "height: 200px;");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(
TransformationMatrix().ApplyPerspective(100),
perspective->FirstFragment().PaintProperties()->Perspective()->Matrix());
EXPECT_EQ(
FloatPoint3D(50, 100, 0),
perspective->FirstFragment().PaintProperties()->Perspective()->Origin());
}
TEST_P(PaintPropertyTreeUpdateTest, TransformUpdatesOnRelativeLengthChanges) {
SetBodyInnerHTML(R"HTML(
<style>
body { margin: 0 }
#transform {
transform: translate3d(50%, 50%, 0);
width: 100px;
height: 200px;
}
</style>
<div id='transform'></div>
)HTML");
auto* transform = GetDocument().getElementById("transform");
auto* transform_object = transform->GetLayoutObject();
EXPECT_EQ(TransformationMatrix().Translate3d(50, 100, 0),
transform_object->FirstFragment()
.PaintProperties()
->Transform()
->Matrix());
transform->setAttribute(HTMLNames::styleAttr, "width: 200px; height: 300px;");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(TransformationMatrix().Translate3d(100, 150, 0),
transform_object->FirstFragment()
.PaintProperties()
->Transform()
->Matrix());
}
TEST_P(PaintPropertyTreeUpdateTest, CSSClipDependingOnSize) {
SetBodyInnerHTML(R"HTML(
<style>
body { margin: 0 }
#outer {
position: absolute;
width: 100px; height: 100px; top: 50px; left: 50px;
}
#clip {
position: absolute;
clip: rect(auto auto auto -5px);
top: 0; left: 0; right: 0; bottom: 0;
}
</style>
<div id='outer'>
<div id='clip'></div>
</div>
)HTML");
auto* outer = GetDocument().getElementById("outer");
auto* clip = GetLayoutObjectByElementId("clip");
EXPECT_EQ(
FloatRect(45, 50, 105, 100),
clip->FirstFragment().PaintProperties()->CssClip()->ClipRect().Rect());
outer->setAttribute(HTMLNames::styleAttr, "height: 200px");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(
FloatRect(45, 50, 105, 200),
clip->FirstFragment().PaintProperties()->CssClip()->ClipRect().Rect());
}
TEST_P(PaintPropertyTreeUpdateTest, ScrollBoundsChange) {
SetBodyInnerHTML(R"HTML(
<div id='container'
style='width: 100px; height: 100px; overflow: scroll'>
<div id='content' style='width: 200px; height: 200px'></div>
</div>
)HTML");
auto* container = GetLayoutObjectByElementId("container");
auto* scroll_node = container->FirstFragment()
.PaintProperties()
->ScrollTranslation()
->ScrollNode();
EXPECT_EQ(IntRect(0, 0, 100, 100), scroll_node->ContainerRect());
EXPECT_EQ(IntRect(0, 0, 200, 200), scroll_node->ContentsRect());
GetDocument().getElementById("content")->setAttribute(
HTMLNames::styleAttr, "width: 200px; height: 300px");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(scroll_node, container->FirstFragment()
.PaintProperties()
->ScrollTranslation()
->ScrollNode());
EXPECT_EQ(IntRect(0, 0, 100, 100), scroll_node->ContainerRect());
EXPECT_EQ(IntRect(0, 0, 200, 300), scroll_node->ContentsRect());
}
TEST_P(PaintPropertyTreeUpdateTest, ScrollbarWidthChange) {
SetBodyInnerHTML(R"HTML(
<style>::-webkit-scrollbar {width: 20px; height: 20px}</style>
<div id='container'
style='width: 100px; height: 100px; overflow: scroll'>
<div id='content' style='width: 200px; height: 200px'></div>
</div>
)HTML");
auto* container = GetLayoutObjectByElementId("container");
auto* overflow_clip =
container->FirstFragment().PaintProperties()->OverflowClip();
EXPECT_EQ(FloatSize(80, 80), overflow_clip->ClipRect().Rect().Size());
auto* new_style = GetDocument().CreateRawElement(HTMLNames::styleTag);
new_style->setTextContent("::-webkit-scrollbar {width: 40px; height: 40px}");
GetDocument().body()->AppendChild(new_style);
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(overflow_clip,
container->FirstFragment().PaintProperties()->OverflowClip());
EXPECT_EQ(FloatSize(60, 60), overflow_clip->ClipRect().Rect().Size());
}
TEST_P(PaintPropertyTreeUpdateTest, Preserve3DChange) {
SetBodyInnerHTML(R"HTML(
<div id='parent'>
<div id='child' style='transform: translate3D(1px, 2px, 3px)'></div>
</div>
)HTML");
auto* child = GetLayoutObjectByElementId("child");
auto* transform = child->FirstFragment().PaintProperties()->Transform();
EXPECT_TRUE(transform->FlattensInheritedTransform());
GetDocument().getElementById("parent")->setAttribute(
HTMLNames::styleAttr, "transform-style: preserve-3d");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(transform, child->FirstFragment().PaintProperties()->Transform());
EXPECT_FALSE(transform->FlattensInheritedTransform());
}
TEST_P(PaintPropertyTreeUpdateTest, MenuListControlClipChange) {
SetBodyInnerHTML(R"HTML(
<select id='select' style='white-space: normal'>
<option></option>
<option>bar</option>
</select>
)HTML");
auto* select = GetLayoutObjectByElementId("select");
EXPECT_NE(nullptr, select->FirstFragment().PaintProperties()->OverflowClip());
// Should not assert in FindPropertiesNeedingUpdate.
ToHTMLSelectElement(select->GetNode())->setSelectedIndex(1);
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_NE(nullptr, select->FirstFragment().PaintProperties()->OverflowClip());
}
TEST_P(PaintPropertyTreeUpdateTest, BoxAddRemoveMask) {
if (!RuntimeEnabledFeatures::SlimmingPaintV175Enabled())
return;
SetBodyInnerHTML(R"HTML(
<style>#target {width: 100px; height: 100px}</style>
<div id='target'>
<div style='width:500px; height:500px; background:green;'></div>
</div>
)HTML");
EXPECT_EQ(nullptr, PaintPropertiesForElement("target"));
auto* target = GetDocument().getElementById("target");
target->setAttribute(HTMLNames::styleAttr,
"-webkit-mask: linear-gradient(red, blue)");
GetDocument().View()->UpdateAllLifecyclePhases();
const auto* properties = PaintPropertiesForElement("target");
ASSERT_NE(nullptr, properties);
EXPECT_NE(nullptr, properties->Effect());
EXPECT_NE(nullptr, properties->Mask());
const auto* mask_clip = properties->MaskClip();
ASSERT_NE(nullptr, mask_clip);
EXPECT_EQ(FloatRoundedRect(8, 8, 100, 100), mask_clip->ClipRect());
target->setAttribute(HTMLNames::styleAttr, "");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(nullptr, PaintPropertiesForElement("target"));
}
TEST_P(PaintPropertyTreeUpdateTest, MaskClipNodeBoxSizeChange) {
if (!RuntimeEnabledFeatures::SlimmingPaintV175Enabled())
return;
SetBodyInnerHTML(R"HTML(
<style>
#target {
width: 100px;
height: 100px;
-webkit-mask: linear-gradient(red, blue);
}
</style>
<div id='target'>
<div style='width:500px; height:500px; background:green;'></div>
</div>
)HTML");
const auto* properties = PaintPropertiesForElement("target");
ASSERT_NE(nullptr, properties);
const auto* mask_clip = properties->MaskClip();
ASSERT_NE(nullptr, mask_clip);
EXPECT_EQ(FloatRoundedRect(8, 8, 100, 100), mask_clip->ClipRect());
GetDocument().getElementById("target")->setAttribute(HTMLNames::styleAttr,
"height: 200px");
GetDocument().View()->UpdateAllLifecyclePhases();
ASSERT_EQ(mask_clip, properties->MaskClip());
EXPECT_EQ(FloatRoundedRect(8, 8, 100, 200), mask_clip->ClipRect());
}
TEST_P(PaintPropertyTreeUpdateTest, InlineAddRemoveMask) {
if (!RuntimeEnabledFeatures::SlimmingPaintV175Enabled())
return;
SetBodyInnerHTML(
"<span id='target'><img id='img' style='width: 50px'></span>");
EXPECT_EQ(nullptr, PaintPropertiesForElement("target"));
auto* target = GetDocument().getElementById("target");
target->setAttribute(HTMLNames::styleAttr,
"-webkit-mask: linear-gradient(red, blue)");
GetDocument().View()->UpdateAllLifecyclePhases();
const auto* properties = PaintPropertiesForElement("target");
ASSERT_NE(nullptr, properties);
EXPECT_NE(nullptr, properties->Effect());
EXPECT_NE(nullptr, properties->Mask());
const auto* mask_clip = properties->MaskClip();
ASSERT_NE(nullptr, mask_clip);
EXPECT_EQ(50, mask_clip->ClipRect().Rect().Width());
target->setAttribute(HTMLNames::styleAttr, "");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(nullptr, PaintPropertiesForElement("target"));
}
TEST_P(PaintPropertyTreeUpdateTest, MaskClipNodeInlineBoundsChange) {
if (!RuntimeEnabledFeatures::SlimmingPaintV175Enabled())
return;
SetBodyInnerHTML(R"HTML(
<span id='target' style='-webkit-mask: linear-gradient(red, blue)'>
<img id='img' style='width: 50px'>
</span>
)HTML");
const auto* properties = PaintPropertiesForElement("target");
ASSERT_NE(nullptr, properties);
const auto* mask_clip = properties->MaskClip();
ASSERT_NE(nullptr, mask_clip);
EXPECT_EQ(50, mask_clip->ClipRect().Rect().Width());
GetDocument().getElementById("img")->setAttribute(HTMLNames::styleAttr,
"width: 100px");
GetDocument().View()->UpdateAllLifecyclePhases();
ASSERT_EQ(mask_clip, properties->MaskClip());
EXPECT_EQ(100, mask_clip->ClipRect().Rect().Width());
}
TEST_P(PaintPropertyTreeUpdateTest, AddRemoveSVGMask) {
if (!RuntimeEnabledFeatures::SlimmingPaintV175Enabled())
return;
SetBodyInnerHTML(R"HTML(
<svg width='200' height='200'>
<rect id='rect' x='0' y='100' width='100' height='100' fill='blue'/>
<defs>
<mask id='mask' x='0' y='0' width='100' height='200'>
<rect width='100' height='200' fill='red'/>
</mask>
</defs>
</svg>
)HTML");
EXPECT_EQ(nullptr, PaintPropertiesForElement("rect"));
GetDocument().getElementById("rect")->setAttribute("mask", "url(#mask)");
GetDocument().View()->UpdateAllLifecyclePhases();
const auto* properties = PaintPropertiesForElement("rect");
ASSERT_NE(nullptr, properties);
EXPECT_NE(nullptr, properties->Effect());
EXPECT_NE(nullptr, properties->Mask());
const auto* mask_clip = properties->MaskClip();
ASSERT_NE(nullptr, mask_clip);
EXPECT_EQ(FloatRoundedRect(0, 100, 100, 100), mask_clip->ClipRect());
GetDocument().getElementById("rect")->removeAttribute("mask");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(nullptr, PaintPropertiesForElement("rect"));
}
TEST_P(PaintPropertyTreeUpdateTest, SVGMaskTargetBoundsChange) {
if (!RuntimeEnabledFeatures::SlimmingPaintV175Enabled())
return;
SetBodyInnerHTML(R"HTML(
<svg width='500' height='500'>
<g id='target' mask='url(#mask)'>
<rect id='rect' x='0' y='50' width='50' height='100' fill='blue'/>
</g>
<defs>
<mask id='mask' x='0' y='0' width='100' height='200'>
<rect width='100' height='200' fill='red'/>
</mask>
</defs>
</svg>
)HTML");
const auto* properties = PaintPropertiesForElement("target");
ASSERT_NE(nullptr, properties);
EXPECT_NE(nullptr, properties->Effect());
EXPECT_NE(nullptr, properties->Mask());
const auto* mask_clip = properties->MaskClip();
ASSERT_NE(nullptr, mask_clip);
EXPECT_EQ(FloatRoundedRect(0, 50, 100, 150), mask_clip->ClipRect());
GetDocument().getElementById("rect")->setAttribute("width", "200");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_NE(nullptr, properties->Effect());
EXPECT_NE(nullptr, properties->Mask());
EXPECT_EQ(FloatRoundedRect(0, 50, 100, 150), mask_clip->ClipRect());
}
TEST_P(PaintPropertyTreeUpdateTest, WillTransformChangeAboveFixed) {
SetBodyInnerHTML(R"HTML(
<style>
#container { position: absolute; top: 100px; left: 100px }
</style>
<div id='container' style='will-change: transform'>
<div id='fixed' style='position: fixed; top: 50px; left: 50px'></div>
</div>
)HTML");
const auto* container = GetLayoutObjectByElementId("container");
const auto* fixed = GetLayoutObjectByElementId("fixed");
EXPECT_EQ(container->FirstFragment().PaintProperties()->Transform(),
fixed->FirstFragment().LocalBorderBoxProperties().Transform());
ToElement(container->GetNode())
->setAttribute(HTMLNames::styleAttr, "will-change: top");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(
GetLayoutView().FirstFragment().LocalBorderBoxProperties().Transform(),
fixed->FirstFragment().LocalBorderBoxProperties().Transform());
ToElement(container->GetNode())
->setAttribute(HTMLNames::styleAttr, "will-change: transform");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(container->FirstFragment().PaintProperties()->Transform(),
fixed->FirstFragment().LocalBorderBoxProperties().Transform());
}
TEST_P(PaintPropertyTreeUpdateTest, CompositingReasonForAnimation) {
if (!RuntimeEnabledFeatures::SlimmingPaintV2Enabled())
return;
SetBodyInnerHTML(R"HTML(
<style>
#target {
transition: 100s;
filter: opacity(30%);
transform: translateX(10px);
position: relative;
}
</style>
<div id='target'>TARGET</div>
)HTML");
auto* target = GetDocument().getElementById("target");
auto* transform =
target->GetLayoutObject()->FirstFragment().PaintProperties()->Transform();
ASSERT_TRUE(transform);
EXPECT_FALSE(transform->HasDirectCompositingReasons());
auto* filter =
target->GetLayoutObject()->FirstFragment().PaintProperties()->Filter();
ASSERT_TRUE(filter);
EXPECT_FALSE(filter->HasDirectCompositingReasons());
target->setAttribute(HTMLNames::styleAttr, "transform: translateX(11px)");
GetDocument().View()->UpdateAllLifecyclePhases();
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
EXPECT_TRUE(transform->HasDirectCompositingReasons());
EXPECT_TRUE(transform->RequiresCompositingForAnimation());
}
EXPECT_FALSE(filter->HasDirectCompositingReasons());
target->setAttribute(HTMLNames::styleAttr,
"transform: translateX(11px); filter: opacity(40%)");
GetDocument().View()->UpdateAllLifecyclePhases();
// The transform animation still continues.
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
EXPECT_TRUE(transform->HasDirectCompositingReasons());
EXPECT_TRUE(transform->RequiresCompositingForAnimation());
// The filter node should have correct direct compositing reasons, not
// shadowed by the transform animation.
EXPECT_TRUE(filter->HasDirectCompositingReasons());
EXPECT_TRUE(filter->RequiresCompositingForAnimation());
}
}
TEST_P(PaintPropertyTreeUpdateTest, SVGViewportContainerOverflowChange) {
if (!RuntimeEnabledFeatures::SlimmingPaintV175Enabled())
return;
SetBodyInnerHTML(R"HTML(
<svg>
<svg id='target' width='30' height='40'></svg>
</svg>
)HTML");
const auto* properties = PaintPropertiesForElement("target");
ASSERT_NE(nullptr, properties);
EXPECT_EQ(FloatRect(0, 0, 30, 40),
properties->OverflowClip()->ClipRect().Rect());
GetDocument().getElementById("target")->setAttribute("overflow", "visible");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(nullptr, PaintPropertiesForElement("target"));
GetDocument().getElementById("target")->setAttribute("overflow", "hidden");
GetDocument().View()->UpdateAllLifecyclePhases();
properties = PaintPropertiesForElement("target");
ASSERT_NE(nullptr, properties);
EXPECT_EQ(FloatRect(0, 0, 30, 40),
properties->OverflowClip()->ClipRect().Rect());
}
TEST_P(PaintPropertyTreeUpdateTest, SVGForeignObjectOverflowChange) {
if (!RuntimeEnabledFeatures::SlimmingPaintV175Enabled())
return;
SetBodyInnerHTML(R"HTML(
<svg>
<foreignObject id='target' x='10' y='20' width='30' height='40'
overflow='hidden'>
</foreignObject>
</svg>
)HTML");
const auto* properties = PaintPropertiesForElement("target");
ASSERT_NE(nullptr, properties);
EXPECT_EQ(FloatRect(10, 20, 30, 40),
properties->OverflowClip()->ClipRect().Rect());
GetDocument().getElementById("target")->setAttribute("overflow", "visible");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(nullptr, PaintPropertiesForElement("target"));
GetDocument().getElementById("target")->setAttribute("overflow", "hidden");
GetDocument().View()->UpdateAllLifecyclePhases();
properties = PaintPropertiesForElement("target");
ASSERT_NE(nullptr, properties);
EXPECT_EQ(FloatRect(10, 20, 30, 40),
properties->OverflowClip()->ClipRect().Rect());
}
TEST_P(PaintPropertyTreeBuilderTest, OmitOverflowClipOnSelectionChange) {
SetBodyInnerHTML(R"HTML(
<div id="target" style="overflow: hidden">
<img style="width: 50px; height: 50px">
</div>
)HTML");
EXPECT_FALSE(PaintPropertiesForElement("target")->OverflowClip());
GetDocument().GetFrame()->Selection().SelectAll();
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_TRUE(PaintPropertiesForElement("target")->OverflowClip());
GetDocument().GetFrame()->Selection().Clear();
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_FALSE(PaintPropertiesForElement("target")->OverflowClip());
}
TEST_P(PaintPropertyTreeBuilderTest, OmitOverflowClipOnCaretChange) {
SetBodyInnerHTML(R"HTML(
<div id="target" contentEditable="true" style="overflow: hidden">
<img style="width: 50px; height: 50px">
</div>
)HTML");
GetDocument().GetPage()->GetFocusController().SetActive(true);
GetDocument().GetPage()->GetFocusController().SetFocused(true);
auto* target = GetDocument().getElementById("target");
EXPECT_FALSE(PaintPropertiesForElement("target")->OverflowClip());
target->focus();
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_TRUE(PaintPropertiesForElement("target")->OverflowClip());
target->blur();
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_FALSE(PaintPropertiesForElement("target")->OverflowClip());
}
} // namespace blink