blob: 2669e3008bd7efbcb9d28bafc8c1a16de0ea3edc [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/viz/service/layers/layer_context_impl.h"
#include <algorithm>
#include <cmath>
#include <limits>
#include <memory>
#include <string>
#include <tuple>
#include <utility>
#include "base/memory/raw_ptr.h"
#include "base/types/expected.h"
#include "cc/trees/layer_tree_host_impl.h"
#include "cc/trees/layer_tree_impl.h"
#include "cc/trees/property_tree.h"
#include "components/viz/service/frame_sinks/compositor_frame_sink_support.h"
#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
#include "components/viz/test/fake_compositor_frame_sink_client.h"
#include "gpu/GLES2/gl2extchromium.h"
#include "services/viz/public/mojom/compositing/layer_context.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/rect.h"
namespace viz {
namespace {
const float kDefaultPageScaleFactor = 1.0f;
const float kDefaultMinPageScaleFactor = 0.5f;
const float kDefaultMaxPageScaleFactor = 2.0f;
class LayerContextImplTest : public testing::Test {
public:
LayerContextImplTest()
: frame_sink_manager_(FrameSinkManagerImpl::InitParams()),
default_local_surface_id_(
LocalSurfaceId(1,
base::UnguessableToken::CreateForTesting(2u, 3u))) {}
void SetUp() override {
compositor_frame_sink_support_ =
std::make_unique<CompositorFrameSinkSupport>(
&dummy_client_, &frame_sink_manager_, kDefaultFrameSinkId,
/*is_root=*/true);
layer_context_impl_ = LayerContextImpl::CreateForTesting(
compositor_frame_sink_support_.get(), /*draw_mode_is_gpu=*/true);
}
mojom::LayerTreeUpdatePtr CreateDefaultUpdate() {
auto update = mojom::LayerTreeUpdate::New();
if (first_update_) {
AddFirstTimeDefaultProperties(update.get());
first_update_ = false;
}
AddDefaultPropertyUpdates(update.get());
return update;
}
void AddDefaultPropertyUpdates(mojom::LayerTreeUpdate* update) {
update->source_frame_number = 1;
update->trace_id = 1;
update->primary_main_frame_item_sequence_number = 1;
update->device_viewport = gfx::Rect(100, 100);
update->background_color = SkColors::kTransparent;
// Valid scale factors by default
update->page_scale_factor = kDefaultPageScaleFactor;
update->min_page_scale_factor = kDefaultMinPageScaleFactor;
update->max_page_scale_factor = kDefaultMaxPageScaleFactor;
update->external_page_scale_factor = 1.0f;
update->device_scale_factor = 1.0f;
update->painted_device_scale_factor = 1.0f;
update->num_transform_nodes = next_transform_id_;
update->num_clip_nodes = next_clip_id_;
update->num_effect_nodes = next_effect_id_;
update->num_scroll_nodes = next_scroll_id_;
// Viewport property IDs
update->overscroll_elasticity_transform =
viewport_property_ids.overscroll_elasticity_transform;
update->page_scale_transform = viewport_property_ids.page_scale_transform;
update->inner_scroll = viewport_property_ids.inner_scroll;
update->outer_clip = viewport_property_ids.outer_clip;
update->outer_scroll = viewport_property_ids.outer_scroll;
// Other defaults
update->display_color_spaces = gfx::DisplayColorSpaces();
update->local_surface_id_from_parent = default_local_surface_id_;
base::TimeTicks now = base::TimeTicks::Now();
base::TimeDelta interval = base::Milliseconds(16);
update->begin_frame_args = BeginFrameArgs::Create(
BEGINFRAME_FROM_HERE, BeginFrameArgs::kStartingSourceId,
BeginFrameArgs::kStartingFrameNumber, now, now + interval, interval,
BeginFrameArgs::NORMAL);
}
void AddFirstTimeDefaultProperties(mojom::LayerTreeUpdate* update) {
// Root & Secondary transform nodes are always expected 1st
AddTransformNode(update, cc::kInvalidPropertyNodeId);
AddTransformNode(update, cc::kRootPropertyNodeId);
// Updating the page scale requires that a page_scale_transform node is
// set up.
viewport_property_ids.overscroll_elasticity_transform =
AddTransformNode(update, cc::kSecondaryRootPropertyNodeId);
viewport_property_ids.page_scale_transform = AddTransformNode(
update, viewport_property_ids.overscroll_elasticity_transform);
update->transform_nodes.back()->in_subtree_of_page_scale_layer = true;
// Root & Secondary clip nodes are always expected
AddClipNode(update, cc::kInvalidPropertyNodeId);
AddClipNode(update, cc::kRootPropertyNodeId);
// Root & Secondary effect nodes are always expected
AddEffectNode(update, cc::kInvalidPropertyNodeId);
AddEffectNode(update, cc::kRootPropertyNodeId);
update->effect_nodes.back()->render_surface_reason =
cc::RenderSurfaceReason::kRoot;
update->effect_nodes.back()->element_id = cc::ElementId(1ULL);
// Root & Secondary scroll nodes are always expected
AddScrollNode(update, cc::kInvalidPropertyNodeId);
AddScrollNode(update, cc::kRootPropertyNodeId);
// Root layer
AddDefaultLayerToUpdate(update);
}
int AddTransformNode(mojom::LayerTreeUpdate* update, int parent) {
auto node = mojom::TransformNode::New();
int id = next_transform_id_++;
node->id = id;
node->parent_id = parent;
update->transform_nodes.push_back(std::move(node));
update->num_transform_nodes = next_transform_id_;
return id;
}
int AddClipNode(mojom::LayerTreeUpdate* update, int parent) {
auto node = mojom::ClipNode::New();
int id = next_clip_id_++;
node->id = id;
node->parent_id = parent;
node->transform_id = viewport_property_ids.page_scale_transform;
update->clip_nodes.push_back(std::move(node));
update->num_clip_nodes = next_clip_id_;
return id;
}
int AddEffectNode(mojom::LayerTreeUpdate* update, int parent) {
auto node = mojom::EffectNode::New();
int id = next_effect_id_++;
node->id = id;
node->parent_id = parent;
node->transform_id = viewport_property_ids.page_scale_transform;
node->clip_id = cc::kRootPropertyNodeId;
node->target_id = cc::kRootPropertyNodeId;
update->effect_nodes.push_back(std::move(node));
update->num_effect_nodes = next_effect_id_;
return id;
}
int AddScrollNode(mojom::LayerTreeUpdate* update, int parent) {
auto node = mojom::ScrollNode::New();
int id = next_scroll_id_++;
node->id = id;
node->parent_id = parent;
node->transform_id = viewport_property_ids.page_scale_transform;
update->scroll_nodes.push_back(std::move(node));
update->num_scroll_nodes = next_scroll_id_;
return id;
}
// Helper to add a default layer to the update.
// Returns the ID of the added layer.
int AddDefaultLayerToUpdate(
mojom::LayerTreeUpdate* update,
cc::mojom::LayerType type = cc::mojom::LayerType::kLayer,
int id = -1) {
auto layer = mojom::Layer::New();
if (id == -1) {
id = next_layer_id_++;
}
layer->id = id;
layer->type = type;
layer->transform_tree_index = cc::kSecondaryRootPropertyNodeId;
layer->clip_tree_index = cc::kRootPropertyNodeId;
layer->effect_tree_index = cc::kSecondaryRootPropertyNodeId;
update->layers.push_back(std::move(layer));
// Update the local layer order, and in the LayerTreeUpdate.
layer_order_.push_back(id);
update->layer_order = layer_order_;
return id;
}
void RemoveLayerInUpdate(mojom::LayerTreeUpdate* update, int id) {
// Remove the ID from the local list of layers if it exists.
auto it = std::find(layer_order_.begin(), layer_order_.end(), id);
if (it != layer_order_.end()) {
layer_order_.erase(it);
}
// Update the layer order in the LayerTreeUpdate.
update->layer_order = layer_order_;
}
protected:
static constexpr FrameSinkId kDefaultFrameSinkId = FrameSinkId(1, 1);
FakeCompositorFrameSinkClient dummy_client_;
FrameSinkManagerImpl frame_sink_manager_;
LocalSurfaceId default_local_surface_id_;
std::unique_ptr<CompositorFrameSinkSupport> compositor_frame_sink_support_;
std::unique_ptr<LayerContextImpl> layer_context_impl_;
bool first_update_ = true;
// Layer IDs start at 1, as 0 is reserved for cc::kInvalidLayerId.
int next_layer_id_ = 1;
// Property tree IDs start at 0.
int next_transform_id_ = 0;
int next_clip_id_ = 0;
int next_effect_id_ = 0;
int next_scroll_id_ = 0;
cc::ViewportPropertyIds viewport_property_ids;
std::vector<int> layer_order_;
};
namespace {
mojom::TransferableUIResourceRequestPtr CreateUIResourceRequest(
int uid,
mojom::TransferableUIResourceRequest::Type type) {
auto request = mojom::TransferableUIResourceRequest::New();
request->uid = uid;
request->type = type;
if (type == mojom::TransferableUIResourceRequest::Type::kCreate) {
// Add a minimal valid resource.
request->transferable_resource = TransferableResource::MakeGpu(
gpu::Mailbox::Generate(), GL_TEXTURE_2D,
gpu::SyncToken(gpu::CommandBufferNamespace::GPU_IO,
gpu::CommandBufferId::FromUnsafeValue(0x234), 0x456),
gfx::Size(1, 1), SinglePlaneFormat::kRGBA_8888,
false /* is_overlay_candidate */);
}
return request;
}
} // namespace
TEST_F(LayerContextImplTest, EmptyScrollingContentsCullRectsByDefault) {
EXPECT_TRUE(layer_context_impl_->host_impl()
->active_tree()
->property_trees()
->scroll_tree()
.scrolling_contents_cull_rects()
.empty());
auto result = layer_context_impl_->DoUpdateDisplayTree(CreateDefaultUpdate());
ASSERT_TRUE(result.has_value());
EXPECT_TRUE(layer_context_impl_->host_impl()
->active_tree()
->property_trees()
->scroll_tree()
.scrolling_contents_cull_rects()
.empty());
}
TEST_F(LayerContextImplTest, ScrollingContentsCullRectsAreSynchronized) {
constexpr cc::ElementId kElementId = cc::ElementId(42);
constexpr gfx::Rect kCullRect = gfx::Rect{100, 100};
base::flat_map<cc::ElementId, gfx::Rect> scrolling_contents_cull_rects;
scrolling_contents_cull_rects[kElementId] = kCullRect;
auto scroll_tree_update = mojom::ScrollTreeUpdate::New();
scroll_tree_update->scrolling_contents_cull_rects =
scrolling_contents_cull_rects;
auto update = CreateDefaultUpdate();
update->scroll_tree_update = std::move(scroll_tree_update);
auto result = layer_context_impl_->DoUpdateDisplayTree(std::move(update));
EXPECT_TRUE(result.has_value());
auto synchronized_scrolling_contents_cull_rects =
layer_context_impl_->host_impl()
->active_tree()
->property_trees()
->scroll_tree()
.scrolling_contents_cull_rects();
EXPECT_EQ(scrolling_contents_cull_rects,
synchronized_scrolling_contents_cull_rects);
}
class LayerContextImplUpdateDisplayTreePageScaleFactorTest
: public LayerContextImplTest,
public ::testing::WithParamInterface<std::tuple<float, bool>> {};
TEST_P(LayerContextImplUpdateDisplayTreePageScaleFactorTest, PageScaleFactor) {
const float scale_factor = std::get<0>(GetParam());
const bool is_valid = std::get<1>(GetParam());
auto update = CreateDefaultUpdate();
update->page_scale_factor = scale_factor;
auto result = layer_context_impl_->DoUpdateDisplayTree(std::move(update));
if (is_valid) {
EXPECT_TRUE(result.has_value());
float expected_factor =
std::min(std::max(scale_factor, kDefaultMinPageScaleFactor),
kDefaultMaxPageScaleFactor);
EXPECT_EQ(layer_context_impl_->host_impl()
->active_tree()
->current_page_scale_factor(),
expected_factor);
} else {
EXPECT_FALSE(result.has_value());
EXPECT_EQ(result.error(), "Invalid page scale factors");
}
}
INSTANTIATE_TEST_SUITE_P(
PageScaleFactor,
LayerContextImplUpdateDisplayTreePageScaleFactorTest,
::testing::Values(
// Test value below min_page_scale_factor.
std::make_tuple(0.25f, true),
// Test value inside min/max_page_scale_factor.
std::make_tuple(1.23f, true),
// Test value outside min/max_page_scale_factor.
std::make_tuple(2.5, true),
// Test invalid values.
std::make_tuple(0.0f, false),
std::make_tuple(-1.0f, false),
std::make_tuple(std::numeric_limits<float>::infinity(), false),
std::make_tuple(std::numeric_limits<float>::quiet_NaN(), false)),
[](const testing::TestParamInfo<
LayerContextImplUpdateDisplayTreePageScaleFactorTest::ParamType>&
info) {
std::stringstream name;
name << (std::get<1>(info.param) ? "Valid" : "Invalid") << "_"
<< info.index;
return name.str();
});
class LayerContextImplUpdateDisplayTreeMinPageScaleFactorTest
: public LayerContextImplTest,
public ::testing::WithParamInterface<std::tuple<float, bool>> {};
TEST_P(LayerContextImplUpdateDisplayTreeMinPageScaleFactorTest,
MinPageScaleFactor) {
const float scale_factor = std::get<0>(GetParam());
const bool is_valid = std::get<1>(GetParam());
auto update = CreateDefaultUpdate();
update->min_page_scale_factor = scale_factor;
auto result = layer_context_impl_->DoUpdateDisplayTree(std::move(update));
if (is_valid) {
EXPECT_TRUE(result.has_value());
EXPECT_EQ(layer_context_impl_->host_impl()
->active_tree()
->min_page_scale_factor(),
scale_factor);
} else {
EXPECT_FALSE(result.has_value());
EXPECT_EQ(result.error(), "Invalid page scale factors");
}
}
INSTANTIATE_TEST_SUITE_P(
MinPageScaleFactor,
LayerContextImplUpdateDisplayTreeMinPageScaleFactorTest,
::testing::Values(
// Test value below max_page_scale_factor.
std::make_tuple(kDefaultMaxPageScaleFactor - 0.1f, true),
// Test value equal to max_page_scale_factor.
std::make_tuple(kDefaultMaxPageScaleFactor, true),
// Test value greater than max_page_scale_factor.
std::make_tuple(kDefaultMaxPageScaleFactor + 0.1f, false),
// Test invalid values.
std::make_tuple(0.0f, false),
std::make_tuple(-1.0f, false),
std::make_tuple(std::numeric_limits<float>::infinity(), false),
std::make_tuple(std::numeric_limits<float>::quiet_NaN(), false)),
[](const testing::TestParamInfo<
LayerContextImplUpdateDisplayTreeMinPageScaleFactorTest::ParamType>&
info) {
std::stringstream name;
name << (std::get<1>(info.param) ? "Valid" : "Invalid") << "_"
<< info.index;
return name.str();
});
class LayerContextImplUpdateDisplayTreeMaxPageScaleFactorTest
: public LayerContextImplTest,
public ::testing::WithParamInterface<std::tuple<float, bool>> {};
TEST_P(LayerContextImplUpdateDisplayTreeMaxPageScaleFactorTest,
MaxPageScaleFactor) {
const float scale_factor = std::get<0>(GetParam());
const bool is_valid = std::get<1>(GetParam());
auto update = CreateDefaultUpdate();
update->max_page_scale_factor = scale_factor;
auto result = layer_context_impl_->DoUpdateDisplayTree(std::move(update));
if (is_valid) {
EXPECT_TRUE(result.has_value());
EXPECT_EQ(layer_context_impl_->host_impl()
->active_tree()
->max_page_scale_factor(),
scale_factor);
} else {
EXPECT_FALSE(result.has_value());
EXPECT_EQ(result.error(), "Invalid page scale factors");
}
}
INSTANTIATE_TEST_SUITE_P(
MaxPageScaleFactor,
LayerContextImplUpdateDisplayTreeMaxPageScaleFactorTest,
::testing::Values(
// Test value equal to min_page_scale_factor.
std::make_tuple(kDefaultMinPageScaleFactor, true),
// Test value above min_page_scale_factor.
std::make_tuple(kDefaultMinPageScaleFactor + 0.1f, true),
// Test value below min_page_scale_factor.
std::make_tuple(kDefaultMinPageScaleFactor - 0.1f, false),
// Test invalid values.
std::make_tuple(0.0f, false),
std::make_tuple(-1.0f, false),
std::make_tuple(std::numeric_limits<float>::infinity(), false),
std::make_tuple(std::numeric_limits<float>::quiet_NaN(), false)),
[](const testing::TestParamInfo<
LayerContextImplUpdateDisplayTreeMaxPageScaleFactorTest::ParamType>&
info) {
std::stringstream name;
name << (std::get<1>(info.param) ? "Valid" : "Invalid") << "_"
<< info.index;
return name.str();
});
class LayerContextImplUpdateDisplayTreeScaleFactorTest
: public LayerContextImplTest,
public ::testing::WithParamInterface<std::tuple<float, bool>> {};
TEST_P(LayerContextImplUpdateDisplayTreeScaleFactorTest,
ExternalPageScaleFactor) {
const float scale_factor = std::get<0>(GetParam());
const bool is_valid = std::get<1>(GetParam());
auto update = CreateDefaultUpdate();
update->external_page_scale_factor = scale_factor;
auto result = layer_context_impl_->DoUpdateDisplayTree(std::move(update));
if (is_valid) {
EXPECT_TRUE(result.has_value());
EXPECT_EQ(layer_context_impl_->host_impl()
->active_tree()
->external_page_scale_factor(),
scale_factor);
} else {
ASSERT_FALSE(result.has_value());
EXPECT_EQ(result.error(), "Invalid external page scale factor");
}
}
TEST_P(LayerContextImplUpdateDisplayTreeScaleFactorTest, DeviceScaleFactor) {
const float scale_factor = std::get<0>(GetParam());
const bool is_valid = std::get<1>(GetParam());
auto update = CreateDefaultUpdate();
update->device_scale_factor = scale_factor;
auto result = layer_context_impl_->DoUpdateDisplayTree(std::move(update));
if (is_valid) {
EXPECT_TRUE(result.has_value());
EXPECT_EQ(
layer_context_impl_->host_impl()->active_tree()->device_scale_factor(),
scale_factor);
} else {
ASSERT_FALSE(result.has_value());
EXPECT_EQ(result.error(), "Invalid device scale factor");
}
}
TEST_P(LayerContextImplUpdateDisplayTreeScaleFactorTest,
PaintedDeviceScaleFactor) {
const float scale_factor = std::get<0>(GetParam());
const bool is_valid = std::get<1>(GetParam());
auto update = CreateDefaultUpdate();
update->painted_device_scale_factor = scale_factor;
auto result = layer_context_impl_->DoUpdateDisplayTree(std::move(update));
if (is_valid) {
EXPECT_TRUE(result.has_value());
EXPECT_EQ(layer_context_impl_->host_impl()
->active_tree()
->painted_device_scale_factor(),
scale_factor);
} else {
ASSERT_FALSE(result.has_value());
EXPECT_EQ(result.error(), "Invalid painted device scale factor");
}
}
INSTANTIATE_TEST_SUITE_P(
TreeScaleFactors,
LayerContextImplUpdateDisplayTreeScaleFactorTest,
::testing::Values(
// Test value below min_page_scale_factor.
std::make_tuple(0.25f, true),
// Test value inside min/max_page_scale_factor.
std::make_tuple(1.23f, true),
// Test value outside min/max_page_scale_factor.
std::make_tuple(2.5, true),
// Test invalid values.
std::make_tuple(0.0f, false),
std::make_tuple(-1.0f, false),
std::make_tuple(std::numeric_limits<float>::infinity(), false),
std::make_tuple(std::numeric_limits<float>::quiet_NaN(), false)),
[](const testing::TestParamInfo<
LayerContextImplUpdateDisplayTreeScaleFactorTest::ParamType>& info) {
std::stringstream name;
name << (std::get<1>(info.param) ? "Valid" : "Invalid") << "_"
<< info.index;
return name.str();
});
class LayerContextImplUpdateDisplayTreeUIResourceRequestTest
: public LayerContextImplTest,
public ::testing::WithParamInterface<std::tuple<gfx::Size, bool>> {};
TEST_P(LayerContextImplUpdateDisplayTreeUIResourceRequestTest, ResourceSize) {
const gfx::Size resource_size = std::get<0>(GetParam());
const bool is_valid = std::get<1>(GetParam());
auto update = CreateDefaultUpdate();
auto request = mojom::TransferableUIResourceRequest::New();
request->type = mojom::TransferableUIResourceRequest::Type::kCreate;
request->uid = 42;
request->transferable_resource = TransferableResource::MakeGpu(
gpu::Mailbox::Generate(), GL_TEXTURE_2D,
gpu::SyncToken(gpu::CommandBufferNamespace::GPU_IO,
gpu::CommandBufferId::FromUnsafeValue(0x234), 0x456),
resource_size, SinglePlaneFormat::kRGBA_8888,
false /* is_overlay_candidate */);
update->ui_resource_requests.push_back(std::move(request));
auto result = layer_context_impl_->DoUpdateDisplayTree(std::move(update));
if (is_valid) {
EXPECT_TRUE(result.has_value());
EXPECT_NE(layer_context_impl_->host_impl()->ResourceIdForUIResource(
/*uid=*/42),
kInvalidResourceId);
} else {
ASSERT_FALSE(result.has_value());
EXPECT_EQ(result.error(),
"Invalid dimensions for transferable UI resource.");
}
}
INSTANTIATE_TEST_SUITE_P(
UIResourceRequestDimensions,
LayerContextImplUpdateDisplayTreeUIResourceRequestTest,
::testing::Values(std::make_tuple(gfx::Size(10, 10), true),
std::make_tuple(gfx::Size(0, 10), false),
std::make_tuple(gfx::Size(10, 0), false)),
[](const testing::TestParamInfo<
LayerContextImplUpdateDisplayTreeUIResourceRequestTest::ParamType>&
info) {
std::stringstream name;
name << (std::get<1>(info.param) ? "Valid" : "Invalid") << "_"
<< info.index;
return name.str();
});
class LayerContextImplUpdateDisplayTreeTilingTest
: public LayerContextImplTest,
public ::testing::WithParamInterface<std::tuple<gfx::Size, bool>> {};
TEST_P(LayerContextImplUpdateDisplayTreeTilingTest, TileSize) {
const gfx::Size tile_size = std::get<0>(GetParam());
const bool is_valid = std::get<1>(GetParam());
auto update = CreateDefaultUpdate();
int layer_id =
AddDefaultLayerToUpdate(update.get(), cc::mojom::LayerType::kTileDisplay);
auto tiling = mojom::Tiling::New();
tiling->layer_id = layer_id;
tiling->scale_key = 1.0f;
tiling->raster_scale = gfx::Vector2dF(1.0f, 1.0f);
tiling->tile_size = tile_size;
tiling->tiling_rect = gfx::Rect(100, 100);
auto tile = mojom::Tile::New();
tile->column_index = 0;
tile->row_index = 0;
tile->contents = mojom::TileContents::NewSolidColor(SkColors::kRed);
tiling->tiles.push_back(std::move(tile));
update->tilings.push_back(std::move(tiling));
auto result = layer_context_impl_->DoUpdateDisplayTree(std::move(update));
if (is_valid) {
EXPECT_TRUE(result.has_value());
cc::LayerTreeImpl* active_tree =
layer_context_impl_->host_impl()->active_tree();
ASSERT_TRUE(active_tree);
cc::LayerImpl* layer_impl = active_tree->LayerById(layer_id);
ASSERT_TRUE(layer_impl);
ASSERT_EQ(layer_impl->GetLayerType(), cc::mojom::LayerType::kTileDisplay);
const auto* tile_display_layer =
static_cast<const cc::TileDisplayLayerImpl*>(layer_impl);
EXPECT_NE(nullptr,
tile_display_layer->GetTilingForTesting(/*scale_key=*/1.0));
} else {
ASSERT_FALSE(result.has_value());
EXPECT_EQ(result.error(), "Invalid tile_size dimensions in Tiling");
}
}
INSTANTIATE_TEST_SUITE_P(
TileSize,
LayerContextImplUpdateDisplayTreeTilingTest,
::testing::Values(std::make_tuple(gfx::Size(10, 10), true),
std::make_tuple(gfx::Size(0, 10), false),
std::make_tuple(gfx::Size(10, 0), false)),
[](const testing::TestParamInfo<
LayerContextImplUpdateDisplayTreeTilingTest::ParamType>& info) {
std::stringstream name;
name << (std::get<1>(info.param) ? "Valid" : "Invalid") << "_"
<< info.index;
return name.str();
});
} // namespace
TEST_F(LayerContextImplTest, TransferableUIResourceLifecycleAndEdgeCases) {
cc::LayerTreeHostImpl* host_impl = layer_context_impl_->host_impl();
// Initial state: No resources.
EXPECT_EQ(host_impl->ResourceIdForUIResource(1), kInvalidResourceId);
EXPECT_EQ(host_impl->ResourceIdForUIResource(2), kInvalidResourceId);
EXPECT_EQ(host_impl->ResourceIdForUIResource(3), kInvalidResourceId);
// Test Case 1: Create UIResource 1. Verify it exists.
auto update1 = CreateDefaultUpdate();
update1->ui_resource_requests.push_back(CreateUIResourceRequest(
1, mojom::TransferableUIResourceRequest::Type::kCreate));
EXPECT_TRUE(
layer_context_impl_->DoUpdateDisplayTree(std::move(update1)).has_value());
EXPECT_NE(host_impl->ResourceIdForUIResource(1), kInvalidResourceId);
EXPECT_EQ(host_impl->ResourceIdForUIResource(2), kInvalidResourceId);
// Test Case 2: Create UIResource 2. Verify both 1 and 2 exist.
auto update2 = CreateDefaultUpdate();
update2->ui_resource_requests.push_back(CreateUIResourceRequest(
2, mojom::TransferableUIResourceRequest::Type::kCreate));
EXPECT_TRUE(
layer_context_impl_->DoUpdateDisplayTree(std::move(update2)).has_value());
EXPECT_NE(host_impl->ResourceIdForUIResource(1), kInvalidResourceId);
EXPECT_NE(host_impl->ResourceIdForUIResource(2), kInvalidResourceId);
// Test Case 3: Remove UIResource 1. Verify 1 is gone, 2 exists.
auto update3 = CreateDefaultUpdate();
update3->ui_resource_requests.push_back(CreateUIResourceRequest(
1, mojom::TransferableUIResourceRequest::Type::kDelete));
EXPECT_TRUE(
layer_context_impl_->DoUpdateDisplayTree(std::move(update3)).has_value());
EXPECT_EQ(host_impl->ResourceIdForUIResource(1), kInvalidResourceId);
EXPECT_NE(host_impl->ResourceIdForUIResource(2), kInvalidResourceId);
// Test Case 4: Edge Case - Try to remove UIResource 1 again (already
// removed). Verify no crash and 2 still exists.
auto update4 = CreateDefaultUpdate();
update4->ui_resource_requests.push_back(CreateUIResourceRequest(
1, mojom::TransferableUIResourceRequest::Type::kDelete));
EXPECT_TRUE(
layer_context_impl_->DoUpdateDisplayTree(std::move(update4)).has_value());
EXPECT_EQ(host_impl->ResourceIdForUIResource(1), kInvalidResourceId);
EXPECT_NE(host_impl->ResourceIdForUIResource(2), kInvalidResourceId);
// Test Case 5: Edge Case - Try to remove UIResource 3 (never created).
// Verify no crash and 2 still exists.
auto update5 = CreateDefaultUpdate();
update5->ui_resource_requests.push_back(CreateUIResourceRequest(
3, mojom::TransferableUIResourceRequest::Type::kDelete));
EXPECT_TRUE(
layer_context_impl_->DoUpdateDisplayTree(std::move(update5)).has_value());
EXPECT_EQ(host_impl->ResourceIdForUIResource(3), kInvalidResourceId);
EXPECT_NE(host_impl->ResourceIdForUIResource(2), kInvalidResourceId);
// Test Case 6: Edge Case - Try to create UIResource 2 again (duplicate
// create). Verify 2 still exists (it's replaced).
ResourceId old_resource_2_id = host_impl->ResourceIdForUIResource(2);
auto update6 = CreateDefaultUpdate();
update6->ui_resource_requests.push_back(CreateUIResourceRequest(
2, mojom::TransferableUIResourceRequest::Type::kCreate));
EXPECT_TRUE(
layer_context_impl_->DoUpdateDisplayTree(std::move(update6)).has_value());
EXPECT_NE(host_impl->ResourceIdForUIResource(2), kInvalidResourceId);
// The resource ID might change upon re-creation.
EXPECT_NE(host_impl->ResourceIdForUIResource(2), old_resource_2_id);
// Test Case 7: Remove UIResource 2. Verify it's gone.
auto update7 = CreateDefaultUpdate();
update7->ui_resource_requests.push_back(CreateUIResourceRequest(
2, mojom::TransferableUIResourceRequest::Type::kDelete));
EXPECT_TRUE(
layer_context_impl_->DoUpdateDisplayTree(std::move(update7)).has_value());
EXPECT_EQ(host_impl->ResourceIdForUIResource(2), kInvalidResourceId);
// Test Case 8: Operations within the same update.
// Create UID 10, then Delete UID 10. Should result in no resource 10.
// Delete UID 11 (non-existent), then Create UID 11. Should result in
// resource 11. Create UID 12, then Create UID 12 again. Should result in
// resource 12.
auto update8 = CreateDefaultUpdate();
update8->ui_resource_requests.push_back(CreateUIResourceRequest(
10, mojom::TransferableUIResourceRequest::Type::kCreate));
update8->ui_resource_requests.push_back(CreateUIResourceRequest(
10, mojom::TransferableUIResourceRequest::Type::kDelete));
update8->ui_resource_requests.push_back(CreateUIResourceRequest(
11, mojom::TransferableUIResourceRequest::Type::kDelete));
update8->ui_resource_requests.push_back(CreateUIResourceRequest(
11, mojom::TransferableUIResourceRequest::Type::kCreate));
update8->ui_resource_requests.push_back(CreateUIResourceRequest(
12, mojom::TransferableUIResourceRequest::Type::kCreate));
update8->ui_resource_requests.push_back(CreateUIResourceRequest(
12, mojom::TransferableUIResourceRequest::Type::kCreate));
EXPECT_TRUE(
layer_context_impl_->DoUpdateDisplayTree(std::move(update8)).has_value());
EXPECT_EQ(host_impl->ResourceIdForUIResource(10), kInvalidResourceId);
EXPECT_NE(host_impl->ResourceIdForUIResource(11), kInvalidResourceId);
EXPECT_NE(host_impl->ResourceIdForUIResource(12), kInvalidResourceId);
// Cleanup remaining resources
auto update_cleanup = CreateDefaultUpdate();
update_cleanup->ui_resource_requests.push_back(CreateUIResourceRequest(
11, mojom::TransferableUIResourceRequest::Type::kDelete));
update_cleanup->ui_resource_requests.push_back(CreateUIResourceRequest(
12, mojom::TransferableUIResourceRequest::Type::kDelete));
EXPECT_TRUE(
layer_context_impl_->DoUpdateDisplayTree(std::move(update_cleanup))
.has_value());
EXPECT_EQ(host_impl->ResourceIdForUIResource(11), kInvalidResourceId);
EXPECT_EQ(host_impl->ResourceIdForUIResource(12), kInvalidResourceId);
}
class LayerContextImplLayerLifecycleTest : public LayerContextImplTest {
protected:
cc::LayerImpl* GetLayerFromActiveTree(int layer_id) {
return layer_context_impl_->host_impl()->active_tree()->LayerById(layer_id);
}
void VerifyLayerExists(int layer_id, bool should_exist) {
if (should_exist) {
EXPECT_NE(nullptr, GetLayerFromActiveTree(layer_id))
<< "Layer " << layer_id << " should exist.";
} else {
EXPECT_EQ(nullptr, GetLayerFromActiveTree(layer_id))
<< "Layer " << layer_id << " should not exist.";
}
}
void VerifyLayerBounds(int layer_id, const gfx::Size& expected_bounds) {
cc::LayerImpl* layer = GetLayerFromActiveTree(layer_id);
ASSERT_NE(nullptr, layer) << "Layer " << layer_id << " not found.";
EXPECT_EQ(expected_bounds, layer->bounds());
}
void VerifyLayerOrder(const std::vector<int>& expected_order) {
cc::LayerTreeImpl* active_tree =
layer_context_impl_->host_impl()->active_tree();
ASSERT_EQ(expected_order.size(), active_tree->NumLayers());
size_t i = 0;
for (cc::LayerImpl* layer : *active_tree) {
ASSERT_LT(i, expected_order.size());
EXPECT_EQ(expected_order[i], layer->id()) << "Mismatch at index " << i;
i++;
}
}
// Helper to manually add a layer to an update, bypassing AddDefaultLayer.
// This is useful for testing specific ID scenarios or invalid properties.
mojom::LayerPtr CreateManualLayer(
int id,
cc::mojom::LayerType type = cc::mojom::LayerType::kLayer,
const gfx::Size& bounds = gfx::Size(10, 10),
int transform_idx = cc::kSecondaryRootPropertyNodeId,
int clip_idx = cc::kRootPropertyNodeId,
int effect_idx = cc::kSecondaryRootPropertyNodeId) {
auto layer = mojom::Layer::New();
layer->id = id;
layer->type = type;
layer->bounds = bounds;
layer->transform_tree_index = transform_idx;
layer->clip_tree_index = clip_idx;
layer->effect_tree_index = effect_idx;
return layer;
}
};
TEST_F(LayerContextImplLayerLifecycleTest, LayerLifecycleAndEdgeCases) {
constexpr int kLayerId1 = 2; // Start after default root layer (ID 1).
constexpr int kLayerId2 = 3;
constexpr int kLayerId3 = 4;
constexpr int kNonExistentLayerId = 99;
(void)kLayerId2;
(void)kLayerId3;
(void)kNonExistentLayerId;
// Test Case 1: Basic Layer Lifecycle (Create, Update Bounds, Remove)
// Update 1: Create Layer ID kLayerId1.
auto update1 = CreateDefaultUpdate();
AddDefaultLayerToUpdate(update1.get(), cc::mojom::LayerType::kLayer,
kLayerId1);
EXPECT_TRUE(
layer_context_impl_->DoUpdateDisplayTree(std::move(update1)).has_value());
VerifyLayerExists(kLayerId1, true);
VerifyLayerBounds(kLayerId1, gfx::Size(0, 0)); // Default bounds
// Update 2: Update bounds of Layer ID kLayerId1.
auto update2 = CreateDefaultUpdate();
update2->layers.push_back(CreateManualLayer(
kLayerId1, cc::mojom::LayerType::kLayer, gfx::Size(20, 20)));
EXPECT_TRUE(
layer_context_impl_->DoUpdateDisplayTree(std::move(update2)).has_value());
VerifyLayerExists(kLayerId1, true);
VerifyLayerBounds(kLayerId1, gfx::Size(20, 20));
// Update 3: Remove Layer ID kLayerId1.
auto update3 = CreateDefaultUpdate();
RemoveLayerInUpdate(update3.get(), kLayerId1);
EXPECT_TRUE(
layer_context_impl_->DoUpdateDisplayTree(std::move(update3)).has_value());
VerifyLayerExists(kLayerId1, false);
// Test Case 2: Multiple Layers and Interleaved Operations
// Update 4: Re-Create Layer ID kLayerId1.
auto update4 = CreateDefaultUpdate();
AddDefaultLayerToUpdate(update4.get(), cc::mojom::LayerType::kLayer,
kLayerId1);
EXPECT_TRUE(
layer_context_impl_->DoUpdateDisplayTree(std::move(update4)).has_value());
VerifyLayerExists(kLayerId1, true);
VerifyLayerOrder({1, kLayerId1});
// Update 5: Create Layer ID kLayerId2.
auto update5 = CreateDefaultUpdate();
AddDefaultLayerToUpdate(update5.get(), cc::mojom::LayerType::kLayer,
kLayerId2);
EXPECT_TRUE(
layer_context_impl_->DoUpdateDisplayTree(std::move(update5)).has_value());
VerifyLayerExists(kLayerId1, true);
VerifyLayerExists(kLayerId2, true);
VerifyLayerOrder({1, kLayerId1, kLayerId2});
// Update 6: Update Layer ID kLayerId1.
auto update6 = CreateDefaultUpdate();
update6->layers.push_back(CreateManualLayer(
kLayerId1, cc::mojom::LayerType::kLayer, gfx::Size(30, 30)));
EXPECT_TRUE(
layer_context_impl_->DoUpdateDisplayTree(std::move(update6)).has_value());
VerifyLayerBounds(kLayerId1, gfx::Size(30, 30));
VerifyLayerBounds(kLayerId2, gfx::Size(0, 0)); // Unaffected
// Update 7: Remove Layer ID kLayerId1.
auto update7 = CreateDefaultUpdate();
RemoveLayerInUpdate(update7.get(), kLayerId1);
EXPECT_TRUE(
layer_context_impl_->DoUpdateDisplayTree(std::move(update7)).has_value());
VerifyLayerExists(kLayerId1, false);
VerifyLayerExists(kLayerId2, true);
VerifyLayerOrder({1, kLayerId2});
// Update 8: Remove Layer ID kLayerId2.
auto update8 = CreateDefaultUpdate();
RemoveLayerInUpdate(update8.get(), kLayerId2);
EXPECT_TRUE(
layer_context_impl_->DoUpdateDisplayTree(std::move(update8)).has_value());
VerifyLayerExists(kLayerId2, false);
VerifyLayerOrder({1});
// Test Case 3: Updating a Never Existing Layer should fail
// Update 9: Create kLayerId1 again.
auto update9 = CreateDefaultUpdate();
AddDefaultLayerToUpdate(update9.get(), cc::mojom::LayerType::kLayer,
kLayerId1);
EXPECT_TRUE(
layer_context_impl_->DoUpdateDisplayTree(std::move(update9)).has_value());
VerifyLayerExists(kLayerId1, true);
// Update 10: Attempt to update kNonExistentLayerId.
auto update10 = CreateDefaultUpdate();
update10->layers.push_back(
CreateManualLayer(kNonExistentLayerId, cc::mojom::LayerType::kLayer,
gfx::Size(5, 5))); // Update non-existent
auto result10 = layer_context_impl_->DoUpdateDisplayTree(std::move(update10));
ASSERT_FALSE(result10.has_value());
EXPECT_EQ(result10.error(), "Invalid layer ID");
VerifyLayerExists(kLayerId1, true); // Unaffected
VerifyLayerExists(kNonExistentLayerId, false);
VerifyLayerOrder({1, kLayerId1});
// Test Case 4: Updating on Previously Removed Layer shoulf fail
// Update 11: Remove kLayerId1.
auto update11 = CreateDefaultUpdate();
RemoveLayerInUpdate(update11.get(), kLayerId1);
EXPECT_TRUE(layer_context_impl_->DoUpdateDisplayTree(std::move(update11))
.has_value());
VerifyLayerExists(kLayerId1, false);
// Update 12: Attempt to update kLayerId1 (removed).
auto update12 = CreateDefaultUpdate();
update12->layers.push_back(CreateManualLayer(
kLayerId1, cc::mojom::LayerType::kLayer, gfx::Size(40, 40)));
auto result12 = layer_context_impl_->DoUpdateDisplayTree(std::move(update12));
ASSERT_FALSE(result12.has_value());
EXPECT_EQ(result12.error(), "Invalid layer ID");
VerifyLayerExists(kLayerId1, false); // Should not be re-created
// Test Case 5: Duplicate or non existent layer IDs in the Layer Order should
// fail. Update 13: Create kLayerId1 again.
auto update13 = CreateDefaultUpdate();
AddDefaultLayerToUpdate(update13.get(), cc::mojom::LayerType::kLayer,
kLayerId1);
EXPECT_TRUE(layer_context_impl_->DoUpdateDisplayTree(std::move(update13))
.has_value());
VerifyLayerExists(kLayerId1, true);
VerifyLayerBounds(kLayerId1, gfx::Size(0, 0));
VerifyLayerOrder({1, kLayerId1});
// Update 14: Try to add another instance of kLayerId1 with different bounds.
auto update14 = CreateDefaultUpdate();
update14->layers.push_back(CreateManualLayer(
kLayerId1, cc::mojom::LayerType::kLayer, gfx::Size(50, 50)));
update14->layer_order = layer_order_;
update14->layer_order->push_back(kLayerId1);
auto result14 = layer_context_impl_->DoUpdateDisplayTree(std::move(update14));
ASSERT_FALSE(result14.has_value());
EXPECT_EQ(result14.error(), "Invalid or duplicate layer ID");
VerifyLayerExists(kLayerId1, true);
VerifyLayerBounds(kLayerId1, gfx::Size(50, 50)); // Layer should be updated
VerifyLayerOrder({1, kLayerId1}); // Layer Order should not update
// Update 15: Try to add a Non Existent layer to Layer Order
auto update15 = CreateDefaultUpdate();
update15->layer_order = layer_order_;
update15->layer_order->push_back(kNonExistentLayerId);
auto result15 = layer_context_impl_->DoUpdateDisplayTree(std::move(update15));
ASSERT_FALSE(result15.has_value());
EXPECT_EQ(result15.error(), "Invalid or duplicate layer ID");
// Test Case 7: Invalid Property Tree Indices on Creation
// Update 16: Try to send a layer update with an invalid transform node index
auto update16 = CreateDefaultUpdate();
update16->layers.push_back(
CreateManualLayer(kLayerId2, cc::mojom::LayerType::kLayer,
gfx::Size(10, 10), /*transform_idx=*/999));
update16->layer_order = layer_order_;
update16->layer_order->push_back(kLayerId2);
EXPECT_FALSE(layer_context_impl_->DoUpdateDisplayTree(std::move(update16))
.has_value());
VerifyLayerExists(kLayerId2, false); // Should not have been created
// Test Case 8: Layer Order Manipulation
// Update 17: Re-Create 1, kLayerId1, kLayerId2. Order [1, kLayerId1,
// kLayerId2]
auto update17 = CreateDefaultUpdate();
layer_order_.clear();
AddDefaultLayerToUpdate(update17.get(), cc::mojom::LayerType::kLayer, 1);
AddDefaultLayerToUpdate(update17.get(), cc::mojom::LayerType::kLayer,
kLayerId1);
AddDefaultLayerToUpdate(update17.get(), cc::mojom::LayerType::kLayer,
kLayerId2);
EXPECT_TRUE(layer_context_impl_->DoUpdateDisplayTree(std::move(update17))
.has_value());
VerifyLayerOrder({1, kLayerId1, kLayerId2});
// Update 18: Change order to [1, kLayerId2, kLayerId1]
auto update18 = CreateDefaultUpdate();
layer_order_.clear();
layer_order_.push_back(1);
layer_order_.push_back(kLayerId2);
layer_order_.push_back(kLayerId1);
update18->layer_order = layer_order_;
EXPECT_TRUE(layer_context_impl_->DoUpdateDisplayTree(std::move(update18))
.has_value());
VerifyLayerOrder({1, kLayerId2, kLayerId1});
// Update 19: Create kLayerId3. Order [1, kLayerId2, kLayerId3, kLayerId1]
auto update19 = CreateDefaultUpdate();
AddDefaultLayerToUpdate(update19.get(), cc::mojom::LayerType::kLayer,
kLayerId3); // kLayerId3
layer_order_.clear();
layer_order_.push_back(1);
layer_order_.push_back(kLayerId2);
layer_order_.push_back(kLayerId3);
layer_order_.push_back(kLayerId1);
update19->layer_order = layer_order_;
EXPECT_TRUE(layer_context_impl_->DoUpdateDisplayTree(std::move(update19))
.has_value());
VerifyLayerOrder({1, kLayerId2, kLayerId3, kLayerId1});
// Update 20: Remove kLayerId2. Order [1, kLayerId3, kLayerId1]
auto update20 = CreateDefaultUpdate();
layer_order_.clear();
layer_order_.push_back(1);
layer_order_.push_back(kLayerId3);
layer_order_.push_back(kLayerId1);
update20->layer_order = layer_order_;
EXPECT_TRUE(layer_context_impl_->DoUpdateDisplayTree(std::move(update20))
.has_value());
VerifyLayerExists(kLayerId2, false);
VerifyLayerOrder({1, kLayerId3, kLayerId1});
}
} // namespace viz