blob: 9938a031839700735fcba66c48de01abf4fb4df8 [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/frame/visual_viewport.h"
#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"
#include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper.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();
UpdateAllLifecyclePhasesForTest();
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");
UpdateAllLifecyclePhasesForTest();
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(html_names::kClassAttr, "backgroundAttachmentFixed");
UpdateAllLifecyclePhasesForTest();
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>");
UpdateAllLifecyclePhasesForTest();
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(html_names::kClassAttr);
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(DocScroll(parent)->HasBackgroundAttachmentFixedDescendants());
EXPECT_FALSE(DocScroll(child)->HasBackgroundAttachmentFixedDescendants());
// Adding a main thread scrolling reason should update the entire tree.
fixed_background->setAttribute(html_names::kClassAttr, "fixedBackground");
UpdateAllLifecyclePhasesForTest();
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");
UpdateAllLifecyclePhasesForTest();
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(html_names::kClassAttr);
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(DocScroll(parent)->HasBackgroundAttachmentFixedDescendants());
EXPECT_FALSE(DocScroll(child)->HasBackgroundAttachmentFixedDescendants());
// Adding a main thread scrolling reason should update the entire tree.
fixed_background->setAttribute(html_names::kClassAttr, "fixedBackground");
UpdateAllLifecyclePhasesForTest();
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");
VisualViewport& visual_viewport =
GetDocument().GetPage()->GetVisualViewport();
// 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_EQ(visual_viewport.GetScrollNode(), overflow_b->GetLayoutObject()
->FirstFragment()
.PaintProperties()
->ScrollTranslation()
->ScrollNode()
->Parent());
// Removing a main thread scrolling reason should update the entire tree.
overflow_b->removeAttribute("class");
UpdateAllLifecyclePhasesForTest();
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(
DocumentLifecycle::LifecycleUpdateReason::kTest);
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(
DocumentLifecycle::LifecycleUpdateReason::kTest);
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(
DocumentLifecycle::LifecycleUpdateReason::kTest);
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);
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(FloatRoundedRect(0, 0, 800, 599), DocContentClip()->ClipRect());
GetDocument().View()->Resize(800, 600);
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(FloatRoundedRect(0, 0, 800, 600), DocContentClip()->ClipRect());
GetDocument().View()->Resize(5, 5);
UpdateAllLifecyclePhasesForTest();
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(html_names::kStyleAttr, "transform: translateY(5555px)");
UpdateAllLifecyclePhasesForTest();
// 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.
UpdateAllLifecyclePhasesForTest();
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.
UpdateAllLifecyclePhasesForTest();
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(html_names::kStyleAttr, "display:inline-block; width:7px;");
UpdateAllLifecyclePhasesForTest();
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(html_names::kStyleAttr, "display:inline-block; width:7px;");
UpdateAllLifecyclePhasesForTest();
clip_properties =
div->GetLayoutObject()->FirstFragment().PaintProperties()->OverflowClip();
EXPECT_EQ(FloatRect(0, 0, 7, 0), clip_properties->ClipRect().Rect());
div->setAttribute(html_names::kStyleAttr, "display:inline-block; width:9px;");
UpdateAllLifecyclePhasesForTest();
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(html_names::kStyleAttr,
"display:inline-block; width:7px; padding-right:3px;");
UpdateAllLifecyclePhasesForTest();
clip_properties =
div->GetLayoutObject()->FirstFragment().PaintProperties()->OverflowClip();
EXPECT_EQ(FloatRect(0, 0, 10, 0), clip_properties->ClipRect().Rect());
div->setAttribute(html_names::kStyleAttr,
"display:inline-block; width:8px; padding-right:2px;");
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(FloatRect(0, 0, 10, 0), clip_properties->ClipRect().Rect());
div->setAttribute(html_names::kStyleAttr,
"display:inline-block; width:8px;"
"padding-right:1px; padding-left:1px;");
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(FloatRect(0, 0, 10, 0), clip_properties->ClipRect().Rect());
// An block's overflow clip should be updated when borders change.
div->setAttribute(html_names::kStyleAttr, "border-right:3px solid red;");
UpdateAllLifecyclePhasesForTest();
clip_properties =
div->GetLayoutObject()->FirstFragment().PaintProperties()->OverflowClip();
EXPECT_EQ(FloatRect(0, 0, 797, 0), clip_properties->ClipRect().Rect());
div->setAttribute(html_names::kStyleAttr, "border-right:5px solid red;");
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(FloatRect(0, 0, 795, 0), clip_properties->ClipRect().Rect());
// Removing overflow clip should remove the property.
div->setAttribute(html_names::kStyleAttr, "overflow:hidden;");
UpdateAllLifecyclePhasesForTest();
clip_properties =
div->GetLayoutObject()->FirstFragment().PaintProperties()->OverflowClip();
EXPECT_EQ(FloatRect(0, 0, 800, 0), clip_properties->ClipRect().Rect());
div->setAttribute(html_names::kStyleAttr, "overflow:visible;");
UpdateAllLifecyclePhasesForTest();
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");
UpdateAllLifecyclePhasesForTest();
auto* div = GetDocument().getElementById("div");
auto* properties =
div->GetLayoutObject()->FirstFragment().PaintProperties()->OverflowClip();
EXPECT_EQ(FloatRect(0, 0, 7, 6), properties->ClipRect().Rect());
div->setAttribute(html_names::kStyleAttr, "");
UpdateAllLifecyclePhasesForTest();
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");
UpdateAllLifecyclePhasesForTest();
div->setAttribute(html_names::kStyleAttr, "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(
DocumentLifecycle::LifecycleUpdateReason::kTest);
EXPECT_EQ(nullptr, DocScroll());
Document* child_doc = &ChildDocument();
EXPECT_NE(nullptr, DocScroll(child_doc));
auto* iframe_container = GetDocument().getElementById("iframeContainer");
iframe_container->setAttribute(html_names::kStyleAttr, "visibility: hidden;");
frame_view->UpdateAllLifecyclePhases(
DocumentLifecycle::LifecycleUpdateReason::kTest);
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(html_names::kClassAttr);
UpdateAllLifecyclePhasesForTest();
// 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(html_names::kClassAttr);
UpdateAllLifecyclePhasesForTest();
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(html_names::kStyleAttr, "transform: translateX(2em)");
UpdateAllLifecyclePhasesForTest();
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(html_names::kClassAttr);
UpdateAllLifecyclePhasesForTest();
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(html_names::kStyleAttr, "opacity: 0.2");
UpdateAllLifecyclePhasesForTest();
const ObjectPaintProperties* properties =
target->GetLayoutObject()->FirstFragment().PaintProperties();
EXPECT_NE(CompositorElementId(),
properties->Effect()->GetCompositorElementId());
target->removeAttribute(html_names::kClassAttr);
UpdateAllLifecyclePhasesForTest();
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(html_names::kStyleAttr, "height: 200px;");
UpdateAllLifecyclePhasesForTest();
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(html_names::kStyleAttr,
"width: 200px; height: 300px;");
UpdateAllLifecyclePhasesForTest();
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(html_names::kStyleAttr, "height: 200px");
UpdateAllLifecyclePhasesForTest();
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(IntSize(200, 200), scroll_node->ContentsSize());
GetDocument().getElementById("content")->setAttribute(
html_names::kStyleAttr, "width: 200px; height: 300px");
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(scroll_node, container->FirstFragment()
.PaintProperties()
->ScrollTranslation()
->ScrollNode());
EXPECT_EQ(IntRect(0, 0, 100, 100), scroll_node->ContainerRect());
EXPECT_EQ(IntSize(200, 300), scroll_node->ContentsSize());
}
// The scrollbars are attached to the visual viewport but created by (and have
// space saved by) the frame view. Conceptually, the scrollbars are part of the
// scrollable content so they must be included in the contents rect. They must
// also not be excluded from the container rect since they don't take away space
// from the viewport's viewable area.
TEST_P(PaintPropertyTreeUpdateTest,
ViewportContentsAndContainerRectsIncludeScrollbar) {
SetBodyInnerHTML(R"HTML(
<style>
::-webkit-scrollbar {width: 20px; height: 20px}
body {height: 10000px; width: 10000px; margin: 0;}
</style>
)HTML");
VisualViewport& visual_viewport =
GetDocument().GetPage()->GetVisualViewport();
EXPECT_EQ(IntRect(0, 0, 800, 600),
visual_viewport.GetScrollNode()->ContainerRect());
EXPECT_EQ(IntSize(800, 600), visual_viewport.GetScrollNode()->ContentsSize());
}
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(html_names::kStyleTag);
new_style->setTextContent("::-webkit-scrollbar {width: 40px; height: 40px}");
GetDocument().body()->AppendChild(new_style);
UpdateAllLifecyclePhasesForTest();
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(
html_names::kStyleAttr, "transform-style: preserve-3d");
UpdateAllLifecyclePhasesForTest();
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);
UpdateAllLifecyclePhasesForTest();
EXPECT_NE(nullptr, select->FirstFragment().PaintProperties()->OverflowClip());
}
TEST_P(PaintPropertyTreeUpdateTest, BoxAddRemoveMask) {
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(html_names::kStyleAttr,
"-webkit-mask: linear-gradient(red, blue)");
UpdateAllLifecyclePhasesForTest();
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(html_names::kStyleAttr, "");
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(nullptr, PaintPropertiesForElement("target"));
}
TEST_P(PaintPropertyTreeUpdateTest, MaskClipNodeBoxSizeChange) {
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(html_names::kStyleAttr,
"height: 200px");
UpdateAllLifecyclePhasesForTest();
ASSERT_EQ(mask_clip, properties->MaskClip());
EXPECT_EQ(FloatRoundedRect(8, 8, 100, 200), mask_clip->ClipRect());
}
TEST_P(PaintPropertyTreeUpdateTest, InlineAddRemoveMask) {
SetBodyInnerHTML(
"<span id='target'><img id='img' style='width: 50px'></span>");
EXPECT_EQ(nullptr, PaintPropertiesForElement("target"));
auto* target = GetDocument().getElementById("target");
target->setAttribute(html_names::kStyleAttr,
"-webkit-mask: linear-gradient(red, blue)");
UpdateAllLifecyclePhasesForTest();
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(html_names::kStyleAttr, "");
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(nullptr, PaintPropertiesForElement("target"));
}
TEST_P(PaintPropertyTreeUpdateTest, MaskClipNodeInlineBoundsChange) {
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(html_names::kStyleAttr,
"width: 100px");
UpdateAllLifecyclePhasesForTest();
ASSERT_EQ(mask_clip, properties->MaskClip());
EXPECT_EQ(100, mask_clip->ClipRect().Rect().Width());
}
TEST_P(PaintPropertyTreeUpdateTest, AddRemoveSVGMask) {
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)");
UpdateAllLifecyclePhasesForTest();
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");
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(nullptr, PaintPropertiesForElement("rect"));
}
TEST_P(PaintPropertyTreeUpdateTest, SVGMaskTargetBoundsChange) {
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");
UpdateAllLifecyclePhasesForTest();
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(html_names::kStyleAttr, "will-change: top");
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(
GetLayoutView().FirstFragment().LocalBorderBoxProperties().Transform(),
fixed->FirstFragment().LocalBorderBoxProperties().Transform());
ToElement(container->GetNode())
->setAttribute(html_names::kStyleAttr, "will-change: transform");
UpdateAllLifecyclePhasesForTest();
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(html_names::kStyleAttr, "transform: translateX(11px)");
UpdateAllLifecyclePhasesForTest();
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
EXPECT_TRUE(transform->HasDirectCompositingReasons());
EXPECT_TRUE(transform->RequiresCompositingForAnimation());
}
// TODO(flackr): After https://crbug.com/900241 is fixed the filter effect
// should no longer have direct compositing reasons due to the animation.
EXPECT_TRUE(filter->HasDirectCompositingReasons());
target->setAttribute(html_names::kStyleAttr,
"transform: translateX(11px); filter: opacity(40%)");
UpdateAllLifecyclePhasesForTest();
// 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) {
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");
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(nullptr, PaintPropertiesForElement("target"));
GetDocument().getElementById("target")->setAttribute("overflow", "hidden");
UpdateAllLifecyclePhasesForTest();
properties = PaintPropertiesForElement("target");
ASSERT_NE(nullptr, properties);
EXPECT_EQ(FloatRect(0, 0, 30, 40),
properties->OverflowClip()->ClipRect().Rect());
}
TEST_P(PaintPropertyTreeUpdateTest, SVGForeignObjectOverflowChange) {
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");
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(nullptr, PaintPropertiesForElement("target"));
GetDocument().getElementById("target")->setAttribute("overflow", "hidden");
UpdateAllLifecyclePhasesForTest();
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();
UpdateAllLifecyclePhasesForTest();
EXPECT_TRUE(PaintPropertiesForElement("target")->OverflowClip());
GetDocument().GetFrame()->Selection().Clear();
UpdateAllLifecyclePhasesForTest();
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();
UpdateAllLifecyclePhasesForTest();
EXPECT_TRUE(PaintPropertiesForElement("target")->OverflowClip());
target->blur();
UpdateAllLifecyclePhasesForTest();
EXPECT_FALSE(PaintPropertiesForElement("target")->OverflowClip());
}
TEST_P(PaintPropertyTreeUpdateTest,
FragmentClipUpdateOnMulticolContainerWidthChange) {
SetBodyInnerHTML(R"HTML(
<style>body {margin: 0}</style>
<div id="container" style="width: 100px">
<div id="multicol" style="columns: 2; column-gap: 0; line-height: 500px">
<div><br></div>
<div><br></div>
</div>
</div>
)HTML");
auto* flow_thread = GetLayoutObjectByElementId("multicol")->SlowFirstChild();
ASSERT_EQ(2u, NumFragments(flow_thread));
EXPECT_EQ(1000000, FragmentAt(flow_thread, 0)
.PaintProperties()
->FragmentClip()
->ClipRect()
.Rect()
.MaxX());
EXPECT_EQ(-999950, FragmentAt(flow_thread, 1)
.PaintProperties()
->FragmentClip()
->ClipRect()
.Rect()
.X());
GetDocument()
.getElementById("container")
->setAttribute(html_names::kStyleAttr, "width: 500px");
UpdateAllLifecyclePhasesForTest();
ASSERT_EQ(2u, NumFragments(flow_thread));
EXPECT_EQ(1000000, FragmentAt(flow_thread, 0)
.PaintProperties()
->FragmentClip()
->ClipRect()
.Rect()
.MaxX());
EXPECT_EQ(-999750, FragmentAt(flow_thread, 1)
.PaintProperties()
->FragmentClip()
->ClipRect()
.Rect()
.X());
}
TEST_P(PaintPropertyTreeUpdateTest,
PropertyTreesRebuiltAfterSVGBlendModeChange) {
SetBodyInnerHTML(R"HTML(
<style>
#blended {
mix-blend-mode: darken;
fill: red;
}
</style>
<svg width="100" height="100">
<rect id="blended" x="0" y="0" width="100" height="100"></rect>
</svg>
)HTML");
auto* blended_element = GetDocument().getElementById("blended");
ASSERT_TRUE(blended_element);
const auto* props =
blended_element->GetLayoutObject()->FirstFragment().PaintProperties();
ASSERT_TRUE(props->Effect());
EXPECT_EQ(props->Effect()->BlendMode(), SkBlendMode::kDarken);
blended_element->setAttribute(html_names::kStyleAttr,
"mix-blend-mode: lighten;");
UpdateAllLifecyclePhasesForTest();
props = blended_element->GetLayoutObject()->FirstFragment().PaintProperties();
ASSERT_TRUE(props->Effect());
EXPECT_EQ(props->Effect()->BlendMode(), SkBlendMode::kLighten);
}
TEST_P(PaintPropertyTreeUpdateTest, EnsureSnapContainerData) {
SetBodyInnerHTML(R"HTML(
<!DOCTYPE html>
<style>
body {
overflow: scroll;
scroll-snap-type: both proximity;
height: 300px;
width: 300px;
margin: 0px;
padding: 0px;
}
#container {
margin: 0px;
padding: 0px;
width: 600px;
height: 2000px;
}
#area {
position: relative;
left: 100px;
top: 700px;
width: 200px;
height: 200px;
scroll-snap-align: start;
}
</style>
<div id="container">
<div id="area"></div>
</div>
)HTML");
GetDocument().View()->Resize(300, 300);
UpdateAllLifecyclePhasesForTest();
auto doc_snap_container_data = DocScroll()->GetSnapContainerData();
ASSERT_TRUE(doc_snap_container_data);
EXPECT_EQ(doc_snap_container_data->scroll_snap_type().axis, SnapAxis::kBoth);
EXPECT_EQ(doc_snap_container_data->scroll_snap_type().strictness,
SnapStrictness::kProximity);
EXPECT_EQ(doc_snap_container_data->rect(), gfx::RectF(0, 0, 300, 300));
EXPECT_EQ(doc_snap_container_data->size(), 1u);
EXPECT_EQ(doc_snap_container_data->at(0).rect,
gfx::RectF(100, 700, 200, 200));
}
TEST_P(PaintPropertyTreeUpdateTest,
EffectAndClipWithNonContainedOutOfFlowDescendant) {
SetBodyInnerHTML(R"HTML(
<div id="clip" style="overflow: hidden; width: 100px; height: 100px">
<div id="effect" style="opacity: 0.5">
<div id="descendant" style="position: fixed">Fixed</div>
</div>
</div>
)HTML");
const auto* clip_properties = PaintPropertiesForElement("clip");
EXPECT_NE(nullptr, clip_properties->OverflowClip());
const auto* effect_properties = PaintPropertiesForElement("effect");
ASSERT_NE(nullptr, effect_properties->Effect());
// The effect's OutputClip is nullptr because of the fixed descendant.
EXPECT_EQ(nullptr, effect_properties->Effect()->OutputClip());
auto* descendant = GetDocument().getElementById("descendant");
descendant->setAttribute(html_names::kStyleAttr, "position: relative");
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(clip_properties->OverflowClip(),
effect_properties->Effect()->OutputClip());
descendant->setAttribute(html_names::kStyleAttr, "position: absolute");
UpdateAllLifecyclePhasesForTest();
// The effect's OutputClip is nullptr because of the absolute descendant.
EXPECT_EQ(nullptr, effect_properties->Effect()->OutputClip());
}
TEST_P(PaintPropertyTreeUpdateTest, ForwardReferencedSVGElementUpdate) {
SetBodyInnerHTML(R"HTML(
<svg id="svg1" filter="url(#filter)">
<filter id="filter">
<feImage id="image" href="#rect"/>
</filter>
</svg>
<svg id="svg2" style="perspective: 10px">
<rect id="rect" width="100" height="100" transform="translate(1)"/>
</svg>
)HTML");
const auto* svg2_properties = PaintPropertiesForElement("svg2");
EXPECT_NE(nullptr, svg2_properties->PaintOffsetTranslation());
EXPECT_EQ(nullptr, svg2_properties->Transform());
EXPECT_NE(nullptr, svg2_properties->Perspective());
EXPECT_EQ(svg2_properties->PaintOffsetTranslation(),
svg2_properties->Perspective()->Parent());
const auto* rect_properties = PaintPropertiesForElement("rect");
ASSERT_NE(nullptr, rect_properties->Transform());
EXPECT_EQ(svg2_properties->Perspective(),
rect_properties->Transform()->Parent());
EXPECT_EQ(TransformationMatrix().Translate(1, 0),
GeometryMapper::SourceToDestinationProjection(
rect_properties->Transform(),
svg2_properties->PaintOffsetTranslation()));
// Change filter which forward references rect, and insert a transform
// node above rect's transform.
GetDocument().getElementById("filter")->setAttribute("width", "20");
GetDocument().getElementById("svg2")->setAttribute("transform",
"translate(2)");
UpdateAllLifecyclePhasesForTest();
EXPECT_NE(nullptr, svg2_properties->Transform());
EXPECT_EQ(svg2_properties->PaintOffsetTranslation(),
svg2_properties->Transform()->Parent());
EXPECT_EQ(svg2_properties->Transform(),
svg2_properties->Perspective()->Parent());
EXPECT_EQ(svg2_properties->Perspective(),
rect_properties->Transform()->Parent());
// Ensure that GeometryMapper's cache is properly invalidated and updated.
EXPECT_EQ(TransformationMatrix().Translate(3, 0),
GeometryMapper::SourceToDestinationProjection(
rect_properties->Transform(),
svg2_properties->PaintOffsetTranslation()));
}
} // namespace blink