blob: 83eef884811c8c33aedcbc7146093530476d4fc0 [file] [log] [blame]
// Copyright 2018 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 "build/build_config.h"
#include "cc/layers/picture_layer.h"
#include "cc/trees/effect_node.h"
#include "cc/trees/transform_node.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/platform/web_url_loader_mock_factory.h"
#include "third_party/blink/public/web/web_script_source.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
#include "third_party/blink/renderer/core/html/html_element.h"
#include "third_party/blink/renderer/core/layout/layout_box.h"
#include "third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
#include "third_party/blink/renderer/core/testing/sim/sim_request.h"
#include "third_party/blink/renderer/core/testing/sim/sim_test.h"
#include "third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h"
#include "third_party/blink/renderer/platform/testing/paint_test_configurations.h"
namespace blink {
// Tests the integration between blink and cc where a layer list is sent to cc.
class WebLayerListTest : public PaintTestConfigurations, public testing::Test {
public:
static void ConfigureCompositingWebView(WebSettings* settings) {
settings->SetPreferCompositingToLCDTextEnabled(true);
}
~WebLayerListTest() override {
Platform::Current()
->GetURLLoaderMockFactory()
->UnregisterAllURLsAndClearMemoryCache();
}
void SetUp() override {
web_view_helper_ = std::make_unique<frame_test_helpers::WebViewHelper>();
web_view_helper_->Initialize(nullptr, nullptr, &web_widget_client_,
&ConfigureCompositingWebView);
web_view_helper_->Resize(WebSize(200, 200));
// The paint artifact compositor should have been created as part of the
// web view helper setup.
DCHECK(paint_artifact_compositor());
paint_artifact_compositor()->EnableExtraDataForTesting();
}
void TearDown() override { web_view_helper_.reset(); }
// Both sets the inner html and runs the document lifecycle.
void InitializeWithHTML(LocalFrame& frame, const String& html_content) {
frame.GetDocument()->body()->SetInnerHTMLFromString(html_content);
frame.GetDocument()->View()->UpdateAllLifecyclePhases(
DocumentLifecycle::LifecycleUpdateReason::kTest);
}
WebLocalFrame* LocalMainFrame() { return web_view_helper_->LocalMainFrame(); }
LocalFrameView* GetLocalFrameView() {
return web_view_helper_->LocalMainFrame()->GetFrameView();
}
WebViewImpl* WebView() { return web_view_helper_->GetWebView(); }
size_t ContentLayerCount() {
return paint_artifact_compositor()
->GetExtraDataForTesting()
->content_layers.size();
}
cc::Layer* ContentLayerAt(size_t index) {
return paint_artifact_compositor()
->GetExtraDataForTesting()
->content_layers[index]
.get();
}
size_t ScrollHitTestLayerCount() {
return paint_artifact_compositor()
->GetExtraDataForTesting()
->scroll_hit_test_layers.size();
}
cc::Layer* ScrollHitTestLayerAt(unsigned index) {
return paint_artifact_compositor()
->GetExtraDataForTesting()
->ScrollHitTestWebLayerAt(index);
}
cc::LayerTreeHost* LayerTreeHost() {
return web_widget_client_.layer_tree_view()->layer_tree_host();
}
Element* GetElementById(const AtomicString& id) {
WebLocalFrameImpl* frame = web_view_helper_->LocalMainFrame();
return frame->GetFrame()->GetDocument()->getElementById(id);
}
void UpdateAllLifecyclePhases() {
WebView()->MainFrameWidget()->UpdateAllLifecyclePhases(
WebWidget::LifecycleUpdateReason::kTest);
}
private:
PaintArtifactCompositor* paint_artifact_compositor() {
return GetLocalFrameView()->GetPaintArtifactCompositorForTesting();
}
frame_test_helpers::TestWebWidgetClient web_widget_client_;
std::unique_ptr<frame_test_helpers::WebViewHelper> web_view_helper_;
};
INSTANTIATE_LAYER_LIST_TEST_SUITE_P(WebLayerListTest);
TEST_P(WebLayerListTest, DidScrollCallbackAfterScrollableAreaChanges) {
InitializeWithHTML(*WebView()->MainFrameImpl()->GetFrame(),
"<style>"
" #scrollable {"
" height: 100px;"
" width: 100px;"
" overflow: scroll;"
" will-change: transform;"
" }"
" #forceScroll { height: 120px; width: 50px; }"
"</style>"
"<div id='scrollable'>"
" <div id='forceScroll'></div>"
"</div>");
UpdateAllLifecyclePhases();
Document* document = WebView()->MainFrameImpl()->GetFrame()->GetDocument();
Element* scrollable = document->getElementById("scrollable");
auto* scrollable_area =
ToLayoutBox(scrollable->GetLayoutObject())->GetScrollableArea();
EXPECT_NE(nullptr, scrollable_area);
auto initial_content_layer_count = ContentLayerCount();
auto initial_scroll_hit_test_layer_count = ScrollHitTestLayerCount();
cc::Layer* overflow_scroll_layer = nullptr;
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
overflow_scroll_layer = ScrollHitTestLayerAt(ScrollHitTestLayerCount() - 1);
} else {
overflow_scroll_layer = ContentLayerAt(ContentLayerCount() - 2);
}
EXPECT_TRUE(overflow_scroll_layer->scrollable());
EXPECT_EQ(overflow_scroll_layer->scroll_container_bounds(),
gfx::Size(100, 100));
// Ensure a synthetic impl-side scroll offset propagates to the scrollable
// area using the DidScroll callback.
EXPECT_EQ(ScrollOffset(), scrollable_area->GetScrollOffset());
overflow_scroll_layer->SetScrollOffsetFromImplSide(gfx::ScrollOffset(0, 1));
UpdateAllLifecyclePhases();
EXPECT_EQ(ScrollOffset(0, 1), scrollable_area->GetScrollOffset());
// Make the scrollable area non-scrollable.
scrollable->setAttribute(html_names::kStyleAttr, "overflow: visible");
// Update layout without updating compositing state.
LocalMainFrame()->ExecuteScript(
WebScriptSource("var forceLayoutFromScript = scrollable.offsetTop;"));
EXPECT_EQ(document->Lifecycle().GetState(), DocumentLifecycle::kLayoutClean);
EXPECT_EQ(nullptr,
ToLayoutBox(scrollable->GetLayoutObject())->GetScrollableArea());
// The web scroll layer has not been deleted yet and we should be able to
// apply impl-side offsets without crashing.
EXPECT_EQ(ContentLayerCount(), initial_content_layer_count);
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
EXPECT_EQ(ScrollHitTestLayerCount(), initial_scroll_hit_test_layer_count);
overflow_scroll_layer->SetScrollOffsetFromImplSide(gfx::ScrollOffset(0, 3));
UpdateAllLifecyclePhases();
EXPECT_LT(ContentLayerCount(), initial_content_layer_count);
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
EXPECT_LT(ScrollHitTestLayerCount(), initial_scroll_hit_test_layer_count);
}
TEST_P(WebLayerListTest, FrameViewScroll) {
InitializeWithHTML(*WebView()->MainFrameImpl()->GetFrame(),
"<style>"
" #forceScroll {"
" height: 2000px;"
" width: 100px;"
" }"
"</style>"
"<div id='forceScroll'></div>");
UpdateAllLifecyclePhases();
auto* scrollable_area = GetLocalFrameView()->LayoutViewport();
EXPECT_NE(nullptr, scrollable_area);
cc::Layer* scroll_layer = nullptr;
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
EXPECT_EQ(ScrollHitTestLayerCount(), 1u);
scroll_layer = ScrollHitTestLayerAt(0);
} else {
// Find the last scroll layer.
for (size_t index = ContentLayerCount() - 1; index >= 0; index--) {
if (ContentLayerAt(index)->scrollable()) {
scroll_layer = ContentLayerAt(index);
break;
}
}
}
EXPECT_TRUE(scroll_layer->scrollable());
// Ensure a synthetic impl-side scroll offset propagates to the scrollable
// area using the DidScroll callback.
EXPECT_EQ(ScrollOffset(), scrollable_area->GetScrollOffset());
scroll_layer->SetScrollOffsetFromImplSide(gfx::ScrollOffset(0, 1));
UpdateAllLifecyclePhases();
EXPECT_EQ(ScrollOffset(0, 1), scrollable_area->GetScrollOffset());
}
class WebLayerListSimTest : public PaintTestConfigurations, public SimTest {
public:
void InitializeWithHTML(const String& html) {
WebView().MainFrameWidget()->Resize(WebSize(800, 600));
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(html);
// Enable the paint artifact compositor extra testing data.
UpdateAllLifecyclePhases();
DCHECK(paint_artifact_compositor());
paint_artifact_compositor()->EnableExtraDataForTesting();
UpdateAllLifecyclePhases();
DCHECK(paint_artifact_compositor()->GetExtraDataForTesting());
}
size_t ContentLayerCount() {
return paint_artifact_compositor()
->GetExtraDataForTesting()
->content_layers.size();
}
cc::Layer* ContentLayerAt(size_t index) {
return paint_artifact_compositor()
->GetExtraDataForTesting()
->content_layers[index]
.get();
}
Element* GetElementById(const AtomicString& id) {
return MainFrame().GetFrame()->GetDocument()->getElementById(id);
}
void UpdateAllLifecyclePhases() {
WebView().MainFrameWidget()->UpdateAllLifecyclePhases(
WebWidget::LifecycleUpdateReason::kTest);
}
void UpdateAllLifecyclePhasesExceptPaint() {
WebView().MainFrameWidget()->UpdateLifecycle(
WebWidget::LifecycleUpdate::kPrePaint,
WebWidget::LifecycleUpdateReason::kTest);
}
cc::PropertyTrees* GetPropertyTrees() {
return Compositor().layer_tree_view().layer_tree_host()->property_trees();
}
PaintArtifactCompositor* paint_artifact_compositor() {
return MainFrame().GetFrameView()->GetPaintArtifactCompositorForTesting();
}
};
INSTANTIATE_LAYER_LIST_TEST_SUITE_P(WebLayerListSimTest);
TEST_P(WebLayerListSimTest, LayerUpdatesDoNotInvalidateEarlierLayers) {
// TODO(crbug.com/765003): CAP may make different layerization decisions and
// we cannot guarantee that both divs will be composited in this test. When
// CAP gets closer to launch, this test should be updated to pass.
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
return;
InitializeWithHTML(R"HTML(
<!DOCTYPE html>
<style>
html { overflow: hidden; }
div {
width: 100px;
height: 100px;
will-change: transform;
}
</style>
<div id='a'></div>
<div id='b'></div>
)HTML");
Compositor().BeginFrame();
auto* a_element = GetElementById("a");
auto* a_layer = ContentLayerAt(ContentLayerCount() - 2);
DCHECK_EQ(a_layer->element_id(), CompositorElementIdFromUniqueObjectId(
a_element->GetLayoutObject()->UniqueId(),
CompositorElementIdNamespace::kPrimary));
auto* b_element = GetElementById("b");
auto* b_layer = ContentLayerAt(ContentLayerCount() - 1);
DCHECK_EQ(b_layer->element_id(), CompositorElementIdFromUniqueObjectId(
b_element->GetLayoutObject()->UniqueId(),
CompositorElementIdNamespace::kPrimary));
// Initially, neither a nor b should have a layer that should push properties.
auto* host = Compositor().layer_tree_view().layer_tree_host();
EXPECT_FALSE(host->LayersThatShouldPushProperties().count(a_layer));
EXPECT_FALSE(host->LayersThatShouldPushProperties().count(b_layer));
// Modifying b should only cause the b layer to need to push properties.
b_element->setAttribute(html_names::kStyleAttr, "opacity: 0.2");
UpdateAllLifecyclePhases();
EXPECT_FALSE(host->LayersThatShouldPushProperties().count(a_layer));
EXPECT_TRUE(host->LayersThatShouldPushProperties().count(b_layer));
// After a frame, no layers should need to push properties again.
Compositor().BeginFrame();
EXPECT_FALSE(host->LayersThatShouldPushProperties().count(a_layer));
EXPECT_FALSE(host->LayersThatShouldPushProperties().count(b_layer));
}
TEST_P(WebLayerListSimTest, LayerUpdatesDoNotInvalidateLaterLayers) {
// TODO(crbug.com/765003): CAP may make different layerization decisions and
// we cannot guarantee that both divs will be composited in this test. When
// CAP gets closer to launch, this test should be updated to pass.
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
return;
InitializeWithHTML(R"HTML(
<!DOCTYPE html>
<style>
html { overflow: hidden; }
div {
width: 100px;
height: 100px;
will-change: transform;
}
</style>
<div id='a'></div>
<div id='b' style='opacity: 0.2;'></div>
<div id='c'></div>
)HTML");
Compositor().BeginFrame();
auto* a_element = GetElementById("a");
auto* a_layer = ContentLayerAt(ContentLayerCount() - 3);
DCHECK_EQ(a_layer->element_id(), CompositorElementIdFromUniqueObjectId(
a_element->GetLayoutObject()->UniqueId(),
CompositorElementIdNamespace::kPrimary));
auto* b_element = GetElementById("b");
auto* b_layer = ContentLayerAt(ContentLayerCount() - 2);
DCHECK_EQ(b_layer->element_id(), CompositorElementIdFromUniqueObjectId(
b_element->GetLayoutObject()->UniqueId(),
CompositorElementIdNamespace::kPrimary));
auto* c_element = GetElementById("c");
auto* c_layer = ContentLayerAt(ContentLayerCount() - 1);
DCHECK_EQ(c_layer->element_id(), CompositorElementIdFromUniqueObjectId(
c_element->GetLayoutObject()->UniqueId(),
CompositorElementIdNamespace::kPrimary));
// Initially, no layer should need to push properties.
auto* host = Compositor().layer_tree_view().layer_tree_host();
EXPECT_FALSE(host->LayersThatShouldPushProperties().count(a_layer));
EXPECT_FALSE(host->LayersThatShouldPushProperties().count(b_layer));
EXPECT_FALSE(host->LayersThatShouldPushProperties().count(c_layer));
// Modifying a and b (adding opacity to a and removing opacity from b) should
// not cause the c layer to push properties.
a_element->setAttribute(html_names::kStyleAttr, "opacity: 0.3");
b_element->setAttribute(html_names::kStyleAttr, "");
UpdateAllLifecyclePhases();
EXPECT_TRUE(host->LayersThatShouldPushProperties().count(a_layer));
EXPECT_TRUE(host->LayersThatShouldPushProperties().count(b_layer));
EXPECT_FALSE(host->LayersThatShouldPushProperties().count(c_layer));
// After a frame, no layers should need to push properties again.
Compositor().BeginFrame();
EXPECT_FALSE(host->LayersThatShouldPushProperties().count(a_layer));
EXPECT_FALSE(host->LayersThatShouldPushProperties().count(b_layer));
EXPECT_FALSE(host->LayersThatShouldPushProperties().count(c_layer));
}
TEST_P(WebLayerListSimTest,
NoopChangeDoesNotCauseFullTreeSyncOrPropertyTreeUpdate) {
InitializeWithHTML(R"HTML(
<!DOCTYPE html>
<style>
div {
width: 100px;
height: 100px;
will-change: transform;
}
</style>
<div></div>
)HTML");
Compositor().BeginFrame();
// Initially the host should not need to sync.
auto* layer_tree_host = Compositor().layer_tree_view().layer_tree_host();
EXPECT_FALSE(layer_tree_host->needs_full_tree_sync());
int sequence_number = GetPropertyTrees()->sequence_number;
EXPECT_GT(sequence_number, 0);
// A no-op update should not cause the host to need a full tree sync.
UpdateAllLifecyclePhases();
EXPECT_FALSE(layer_tree_host->needs_full_tree_sync());
// It should also not cause a property tree update - the sequence number
// should not change.
EXPECT_EQ(sequence_number, GetPropertyTrees()->sequence_number);
}
// When a property tree change occurs that affects layer transform in the
// general case, all layers associated with the changed property tree node, and
// all layers associated with a descendant of the changed property tree node
// need to have |subtree_property_changed| set for damage tracking. In
// non-layer-list mode, this occurs in BuildPropertyTreesInternal (see:
// SetLayerPropertyChangedForChild).
TEST_P(WebLayerListSimTest, LayerSubtreeTransformPropertyChanged) {
// TODO(crbug.com/765003): CAP may make different layerization decisions and
// we cannot guarantee that both divs will be composited in this test. When
// CAP gets closer to launch, this test should be updated to pass.
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
return;
InitializeWithHTML(R"HTML(
<!DOCTYPE html>
<style>
html { overflow: hidden; }
#outer {
width: 100px;
height: 100px;
will-change: transform;
transform: translate(10px, 10px);
}
#inner {
width: 100px;
height: 100px;
will-change: transform;
background: lightblue;
}
</style>
<div id='outer'>
<div id='inner'></div>
</div>
)HTML");
Compositor().BeginFrame();
auto* outer_element = GetElementById("outer");
auto* outer_element_layer = ContentLayerAt(ContentLayerCount() - 2);
DCHECK_EQ(outer_element_layer->element_id(),
CompositorElementIdFromUniqueObjectId(
outer_element->GetLayoutObject()->UniqueId(),
CompositorElementIdNamespace::kPrimary));
auto* inner_element = GetElementById("inner");
auto* inner_element_layer = ContentLayerAt(ContentLayerCount() - 1);
DCHECK_EQ(inner_element_layer->element_id(),
CompositorElementIdFromUniqueObjectId(
inner_element->GetLayoutObject()->UniqueId(),
CompositorElementIdNamespace::kPrimary));
// Initially, no layer should have |subtree_property_changed| set.
EXPECT_FALSE(outer_element_layer->subtree_property_changed());
EXPECT_FALSE(inner_element_layer->subtree_property_changed());
// Modifying the transform style should set |subtree_property_changed| on
// both layers.
outer_element->setAttribute(html_names::kStyleAttr,
"transform: rotate(10deg)");
UpdateAllLifecyclePhases();
EXPECT_TRUE(outer_element_layer->subtree_property_changed());
EXPECT_TRUE(inner_element_layer->subtree_property_changed());
// After a frame the |subtree_property_changed| value should be reset.
Compositor().BeginFrame();
EXPECT_FALSE(outer_element_layer->subtree_property_changed());
EXPECT_FALSE(inner_element_layer->subtree_property_changed());
}
// When a property tree change occurs that affects layer transform in a simple
// case (ie before and after transforms both preserve axis alignment), the
// transforms can be directly updated without explicitly marking layers as
// damaged. The ensure damage occurs, the transform node should have
// |transform_changed| set. In non-layer-list mode, this occurs in
// cc::TransformTree::OnTransformAnimated and cc::Layer::SetTransform.
TEST_P(WebLayerListSimTest, DirectTransformPropertyUpdate) {
// TODO(crbug.com/765003): CAP may make different layerization decisions and
// we cannot guarantee that both divs will be composited in this test. When
// CAP gets closer to launch, this test should be updated to pass.
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
return;
InitializeWithHTML(R"HTML(
<!DOCTYPE html>
<style>
html { overflow: hidden; }
#outer {
width: 100px;
height: 100px;
will-change: transform;
transform: translate(10px, 10px) scale(1, 2);
}
#inner {
width: 100px;
height: 100px;
will-change: transform;
background: lightblue;
}
</style>
<div id='outer'>
<div id='inner'></div>
</div>
)HTML");
Compositor().BeginFrame();
auto* outer_element = GetElementById("outer");
auto* outer_element_layer = ContentLayerAt(ContentLayerCount() - 2);
DCHECK_EQ(outer_element_layer->element_id(),
CompositorElementIdFromUniqueObjectId(
outer_element->GetLayoutObject()->UniqueId(),
CompositorElementIdNamespace::kPrimary));
auto transform_tree_index = outer_element_layer->transform_tree_index();
auto* transform_node =
GetPropertyTrees()->transform_tree.Node(transform_tree_index);
// Initially, transform should be unchanged.
EXPECT_FALSE(transform_node->transform_changed);
EXPECT_FALSE(paint_artifact_compositor()->NeedsUpdate());
// Modifying the transform in a simple way allowed for a direct update.
outer_element->setAttribute(html_names::kStyleAttr,
"transform: translate(30px, 20px) scale(5, 5)");
UpdateAllLifecyclePhasesExceptPaint();
EXPECT_TRUE(transform_node->transform_changed);
EXPECT_FALSE(paint_artifact_compositor()->NeedsUpdate());
// After a frame the |transform_changed| value should be reset.
Compositor().BeginFrame();
EXPECT_FALSE(transform_node->transform_changed);
}
// This test is similar to |LayerSubtreeTransformPropertyChanged| but for
// effect property node changes.
TEST_P(WebLayerListSimTest, LayerSubtreeEffectPropertyChanged) {
// TODO(crbug.com/765003): CAP may make different layerization decisions and
// we cannot guarantee that both divs will be composited in this test. When
// CAP gets closer to launch, this test should be updated to pass.
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
return;
InitializeWithHTML(R"HTML(
<!DOCTYPE html>
<style>
html { overflow: hidden; }
#outer {
width: 100px;
height: 100px;
will-change: transform;
filter: blur(10px);
}
#inner {
width: 100px;
height: 100px;
will-change: transform;
background: lightblue;
}
</style>
<div id='outer'>
<div id='inner'></div>
</div>
)HTML");
Compositor().BeginFrame();
auto* outer_element = GetElementById("outer");
auto* outer_element_layer = ContentLayerAt(ContentLayerCount() - 2);
DCHECK_EQ(outer_element_layer->element_id(),
CompositorElementIdFromUniqueObjectId(
outer_element->GetLayoutObject()->UniqueId(),
CompositorElementIdNamespace::kPrimary));
auto* inner_element = GetElementById("inner");
auto* inner_element_layer = ContentLayerAt(ContentLayerCount() - 1);
DCHECK_EQ(inner_element_layer->element_id(),
CompositorElementIdFromUniqueObjectId(
inner_element->GetLayoutObject()->UniqueId(),
CompositorElementIdNamespace::kPrimary));
// Initially, no layer should have |subtree_property_changed| set.
EXPECT_FALSE(outer_element_layer->subtree_property_changed());
EXPECT_FALSE(inner_element_layer->subtree_property_changed());
// Modifying the filter style should set |subtree_property_changed| on
// both layers.
outer_element->setAttribute(html_names::kStyleAttr, "filter: blur(20px)");
UpdateAllLifecyclePhases();
EXPECT_TRUE(outer_element_layer->subtree_property_changed());
EXPECT_TRUE(inner_element_layer->subtree_property_changed());
// After a frame the |subtree_property_changed| value should be reset.
Compositor().BeginFrame();
EXPECT_FALSE(outer_element_layer->subtree_property_changed());
EXPECT_FALSE(inner_element_layer->subtree_property_changed());
}
// This test is similar to |LayerSubtreeTransformPropertyChanged| but for
// clip property node changes.
TEST_P(WebLayerListSimTest, LayerSubtreeClipPropertyChanged) {
// TODO(crbug.com/765003): CAP may make different layerization decisions and
// we cannot guarantee that both divs will be composited in this test. When
// CAP gets closer to launch, this test should be updated to pass.
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
return;
InitializeWithHTML(R"HTML(
<!DOCTYPE html>
<style>
html { overflow: hidden; }
#outer {
width: 100px;
height: 100px;
will-change: transform;
position: absolute;
clip: rect(10px, 80px, 70px, 40px);
}
#inner {
width: 100px;
height: 100px;
will-change: transform;
background: lightblue;
}
</style>
<div id='outer'>
<div id='inner'></div>
</div>
)HTML");
Compositor().BeginFrame();
auto* outer_element = GetElementById("outer");
auto* outer_element_layer = ContentLayerAt(ContentLayerCount() - 2);
auto* inner_element = GetElementById("inner");
auto* inner_element_layer = ContentLayerAt(ContentLayerCount() - 1);
DCHECK_EQ(inner_element_layer->element_id(),
CompositorElementIdFromUniqueObjectId(
inner_element->GetLayoutObject()->UniqueId(),
CompositorElementIdNamespace::kPrimary));
// Initially, no layer should have |subtree_property_changed| set.
EXPECT_FALSE(outer_element_layer->subtree_property_changed());
EXPECT_FALSE(inner_element_layer->subtree_property_changed());
// Modifying the clip style should set |subtree_property_changed| on
// both layers.
outer_element->setAttribute(html_names::kStyleAttr,
"clip: rect(1px, 8px, 7px, 4px);");
UpdateAllLifecyclePhases();
EXPECT_TRUE(outer_element_layer->subtree_property_changed());
EXPECT_TRUE(inner_element_layer->subtree_property_changed());
// After a frame the |subtree_property_changed| value should be reset.
Compositor().BeginFrame();
EXPECT_FALSE(outer_element_layer->subtree_property_changed());
EXPECT_FALSE(inner_element_layer->subtree_property_changed());
}
TEST_P(WebLayerListSimTest, LayerSubtreeOverflowClipPropertyChanged) {
// TODO(crbug.com/765003): CAP may make different layerization decisions and
// we cannot guarantee that both divs will be composited in this test. When
// CAP gets closer to launch, this test should be updated to pass.
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
return;
InitializeWithHTML(R"HTML(
<!DOCTYPE html>
<style>
html { overflow: hidden; }
#outer {
width: 100px;
height: 100px;
will-change: transform;
position: absolute;
overflow: hidden;
}
#inner {
width: 200px;
height: 100px;
will-change: transform;
background: lightblue;
}
</style>
<div id='outer'>
<div id='inner'></div>
</div>
)HTML");
Compositor().BeginFrame();
auto* outer_element = GetElementById("outer");
auto* outer_element_layer = ContentLayerAt(ContentLayerCount() - 2);
auto* inner_element = GetElementById("inner");
auto* inner_element_layer = ContentLayerAt(ContentLayerCount() - 1);
DCHECK_EQ(inner_element_layer->element_id(),
CompositorElementIdFromUniqueObjectId(
inner_element->GetLayoutObject()->UniqueId(),
CompositorElementIdNamespace::kPrimary));
// Initially, no layer should have |subtree_property_changed| set.
EXPECT_FALSE(outer_element_layer->subtree_property_changed());
EXPECT_FALSE(inner_element_layer->subtree_property_changed());
// Modifying the clip width should set |subtree_property_changed| on
// both layers.
outer_element->setAttribute(html_names::kStyleAttr, "width: 200px;");
UpdateAllLifecyclePhases();
EXPECT_TRUE(outer_element_layer->subtree_property_changed());
EXPECT_TRUE(inner_element_layer->subtree_property_changed());
// After a frame the |subtree_property_changed| value should be reset.
Compositor().BeginFrame();
EXPECT_FALSE(outer_element_layer->subtree_property_changed());
EXPECT_FALSE(inner_element_layer->subtree_property_changed());
}
TEST_P(WebLayerListSimTest, SafeOpaqueBackgroundColorGetsSet) {
// TODO(crbug.com/765003): CAP may make different layerization decisions and
// we cannot guarantee that both divs will be composited in this test. When
// CAP gets closer to launch, this test should be updated to pass.
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
return;
InitializeWithHTML(R"HTML(
<!DOCTYPE html>
<style>
div {
position: absolute;
z-index: 1;
width: 20px;
height: 20px;
}
#behind {
top: 12px;
left: 12px;
background: blue;
will-change: transform; /* Composited */
}
#topleft {
top: 0px;
left: 0px;
background: lime;
}
#bottomright {
top: 24px;
left: 24px;
background: cyan;
}
</style>
<div id="behind"></div>
<div id="topleft"></div>
<div id="bottomright"></div>
)HTML");
Compositor().BeginFrame();
auto* behind_element = GetElementById("behind");
auto* behind_layer = ContentLayerAt(ContentLayerCount() - 2);
EXPECT_EQ(behind_layer->element_id(),
CompositorElementIdFromUniqueObjectId(
behind_element->GetLayoutObject()->UniqueId(),
CompositorElementIdNamespace::kPrimary));
EXPECT_EQ(behind_layer->SafeOpaqueBackgroundColor(), SK_ColorBLUE);
auto* grouped_mapping =
GetElementById("topleft")->GetLayoutBox()->Layer()->GroupedMapping();
auto* squashed_layer = grouped_mapping->SquashingLayer()->CcLayer();
ASSERT_NE(nullptr, squashed_layer);
// Top left and bottom right are squashed.
// This squashed layer should not be opaque, as it is squashing two squares
// with some gaps between them.
EXPECT_FALSE(squashed_layer->contents_opaque());
// This shouldn't DCHECK.
squashed_layer->SafeOpaqueBackgroundColor();
// Because contents_opaque is false, the SafeOpaqueBackgroundColor() getter
// will return SK_ColorTRANSPARENT. So we need to grab the actual color,
// to make sure it's right.
SkColor squashed_bg_color =
squashed_layer->ActualSafeOpaqueBackgroundColorForTesting();
// The squashed layer should have a non-transparent safe opaque background
// color, that isn't blue. Exactly which color it is depends on heuristics,
// but it should be one of the two colors of the elements that created it.
EXPECT_NE(squashed_bg_color, SK_ColorBLUE);
EXPECT_EQ(SkColorGetA(squashed_bg_color), SK_AlphaOPAQUE);
// #behind is blue, which is SK_ColorBLUE
// #topleft is lime, which is SK_ColorGREEN
// #bottomright is cyan, which is SK_ColorCYAN
EXPECT_TRUE((squashed_bg_color == SK_ColorGREEN) ||
(squashed_bg_color == SK_ColorCYAN));
}
TEST_P(WebLayerListSimTest, NonDrawableLayersIgnoredForRenderSurfaces) {
// TODO(crbug.com/765003): CAP may make different layerization decisions. When
// CAP gets closer to launch, this test should be updated to pass.
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
return;
InitializeWithHTML(R"HTML(
<!DOCTYPE html>
<style>
#outer {
width: 100px;
height: 100px;
opacity: 0.5;
background: blue;
}
#inner {
width: 10px;
height: 10px;
will-change: transform;
}
</style>
<div id='outer'>
<div id='inner'></div>
</div>
)HTML");
Compositor().BeginFrame();
ASSERT_GE(ContentLayerCount(), 2u);
auto* inner_element_layer = ContentLayerAt(ContentLayerCount() - 1);
EXPECT_FALSE(inner_element_layer->DrawsContent());
auto* outer_element_layer = ContentLayerAt(ContentLayerCount() - 2);
EXPECT_TRUE(outer_element_layer->DrawsContent());
// The inner element layer is only needed for hit testing and does not draw
// content, so it should not cause a render surface.
auto effect_tree_index = outer_element_layer->effect_tree_index();
auto* effect_node = GetPropertyTrees()->effect_tree.Node(effect_tree_index);
EXPECT_EQ(effect_node->opacity, 0.5f);
EXPECT_FALSE(effect_node->has_render_surface);
}
} // namespace blink