// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/frame_sink/frame_sink_holder.h"
#include <algorithm>
#include <memory>
#include <vector>
#include "ash/frame_sink/frame_sink_holder_test_api.h"
#include "ash/frame_sink/frame_sink_host.h"
#include "ash/frame_sink/test/test_begin_frame_source.h"
#include "ash/frame_sink/test/test_layer_tree_frame_sink.h"
#include "ash/frame_sink/ui_resource_manager.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "components/viz/common/frame_sinks/begin_frame_args.h"
#include "components/viz/common/frame_sinks/begin_frame_source.h"
#include "components/viz/common/gpu/context_provider.h"
#include "components/viz/common/quads/compositor_frame.h"
#include "components/viz/common/resources/resource_id.h"
#include "components/viz/common/resources/returned_resource.h"
#include "components/viz/common/resources/transferable_resource.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
namespace ash {
namespace {
class StubBeginFrameSource : public viz::BeginFrameSource {
public:
StubBeginFrameSource() : viz::BeginFrameSource(0u) {}
void DidFinishFrame(viz::BeginFrameObserver* obs) override {}
void AddObserver(viz::BeginFrameObserver* obs) override {}
void RemoveObserver(viz::BeginFrameObserver* obs) override {}
void OnGpuNoLongerBusy() override {}
};
class TestFrameFactory {
public:
TestFrameFactory() = default;
TestFrameFactory(const TestFrameFactory&) = delete;
TestFrameFactory& operator=(const TestFrameFactory&) = delete;
~TestFrameFactory() = default;
std::unique_ptr<viz::CompositorFrame> CreateCompositorFrame(
const viz::BeginFrameAck& begin_frame_ack,
UiResourceManager& resource_manager,
bool auto_refresh,
const gfx::Size& last_submitted_frame_size,
float last_submitted_frame_dsf) {
auto frame = std::make_unique<viz::CompositorFrame>();
frame->metadata.begin_frame_ack = begin_frame_ack;
frame->metadata.device_scale_factor = latest_frame_dsf_;
const viz::CompositorRenderPassId kRenderPassId{1};
auto render_pass = viz::CompositorRenderPass::Create();
render_pass->SetNew(kRenderPassId, gfx::Rect(latest_frame_size_),
gfx::Rect(), gfx::Transform());
frame->render_pass_list.push_back(std::move(render_pass));
for (viz::ResourceId id : latest_frame_resources_) {
frame->resource_list.push_back(
resource_manager.PrepareResourceForExport(id));
}
return frame;
}
void OnFirstFrameRequested() {}
void SetFrameResources(std::vector<viz::ResourceId> frame_resource) {
latest_frame_resources_ = std::move(frame_resource);
}
void SetFrameMetaData(const gfx::Size frame_size, float dsf) {
latest_frame_size_ = frame_size;
latest_frame_dsf_ = dsf;
}
private:
std::vector<viz::ResourceId> latest_frame_resources_;
gfx::Size latest_frame_size_;
float latest_frame_dsf_ = 1.0;
};
MATCHER_P(IsBeginFrameAckEqual, value, "") {
return arg.frame_id == value.frame_id && arg.trace_id == value.trace_id &&
arg.has_damage == value.has_damage;
}
class FrameSinkHolderTest : public AshTestBase {
public:
FrameSinkHolderTest() = default;
FrameSinkHolderTest(const FrameSinkHolderTest&) = delete;
FrameSinkHolderTest& operator=(const FrameSinkHolderTest&) = delete;
// AshTestBase:
void SetUp() override {
AshTestBase::SetUp();
aura::Window* root_window =
Shell::Get()->GetRootWindowForDisplayId(GetPrimaryDisplay().id());
auto host_window = std::make_unique<aura::Window>(/*delegate=*/nullptr);
host_window_ = host_window.release();
host_window_->Init(ui::LayerType::LAYER_SOLID_COLOR);
root_window->AddChild(host_window_);
frame_factory_ = std::make_unique<TestFrameFactory>();
auto layer_tree_frame_sink = std::make_unique<TestLayerTreeFrameSink>();
layer_tree_frame_sink_ = layer_tree_frame_sink.get();
frame_sink_holder_ = std::make_unique<FrameSinkHolder>(
std::move(layer_tree_frame_sink),
base::BindRepeating(&TestFrameFactory::CreateCompositorFrame,
base::Unretained(frame_factory_.get())),
base::BindRepeating(&TestFrameFactory::OnFirstFrameRequested,
base::Unretained(frame_factory_.get())));
holder_weak_ptr_ = frame_sink_holder_->GetWeakPtr();
}
UiResourceManager& GetResourceManager() {
return frame_sink_holder_->resource_manager();
}
// If `frame_sink_holder_` lifetime has been extended in a unittest and the
// holder did not schedule a delete task, it will get destroyed once we
// delete the root_window of `host_window_`.
std::unique_ptr<FrameSinkHolder> frame_sink_holder_;
raw_ptr<aura::Window, DanglingUntriaged> host_window_;
// Will be used to access the frame_sink_holder once we pass the
// ownership of `frame_sink_holder_` to
// `DeleteWhenLastResourceHasBeenReclaimed()` in unittests.
base::WeakPtr<FrameSinkHolder> holder_weak_ptr_;
// Factory to create test compositor frames.
std::unique_ptr<TestFrameFactory> frame_factory_;
// Keeping a reference to be used in tests.
raw_ptr<TestLayerTreeFrameSink, DanglingUntriaged>
layer_tree_frame_sink_; // no owned
};
TEST_F(FrameSinkHolderTest, SubmitFrameSynchronouslyBeforeFirstFrameRequested) {
FrameSinkHolderTestApi test_api(frame_sink_holder_.get());
frame_factory_->SetFrameMetaData(gfx::Size(100, 100), 1.0);
ASSERT_FALSE(test_api.IsFirstFrameRequested());
frame_sink_holder_->SubmitCompositorFrame(/*synchronous_draw=*/true);
// Confirm that FrameSinkHolder did not submit any frame yet.
EXPECT_TRUE(test_api.LastSubmittedFrameSize().IsEmpty());
EXPECT_EQ(layer_tree_frame_sink_->num_of_frames_received(), 0);
// FrameSinkHolder has pending a frame that will be sent out asynchronously.
EXPECT_TRUE(test_api.IsPendingFrame());
// Asynchronous frame request.
frame_sink_holder_->OnBeginFrame(CreateValidBeginFrameArgsForTesting());
ASSERT_TRUE(test_api.IsFirstFrameRequested());
EXPECT_FALSE(test_api.IsPendingFrame());
EXPECT_TRUE(test_api.IsPendingFrameAck());
// LayerTreeFrameSink received the frame.
EXPECT_EQ(layer_tree_frame_sink_->num_of_frames_received(), 1);
// Manual BeginFrameAck is used for synchronous frames only.
EXPECT_THAT(
layer_tree_frame_sink_->GetLatestReceivedFrame().metadata.begin_frame_ack,
testing::Not(IsBeginFrameAckEqual(
viz::BeginFrameAck::CreateManualAckWithDamage())));
frame_sink_holder_->DidReceiveCompositorFrameAck();
EXPECT_FALSE(test_api.IsPendingFrameAck());
layer_tree_frame_sink_->ResetLatestFrameState();
// Now that FrameSinkHolder has received the requested for the first frame, it
// can now submit frames synchronously.
frame_sink_holder_->SubmitCompositorFrame(/*synchronous_draw=*/true);
EXPECT_EQ(layer_tree_frame_sink_->num_of_frames_received(), 2);
EXPECT_EQ(test_api.LastSubmittedFrameSize(),
layer_tree_frame_sink_->GetLatestReceivedFrame().size_in_pixels());
// Manual BeginFrameAck is used only for synchronously submitted frames.
EXPECT_THAT(
layer_tree_frame_sink_->GetLatestReceivedFrame().metadata.begin_frame_ack,
IsBeginFrameAckEqual(viz::BeginFrameAck::CreateManualAckWithDamage()));
}
TEST_F(FrameSinkHolderTest, ObserveBeginFrameSourceOnDemand) {
FrameSinkHolderTestApi test_api(frame_sink_holder_.get());
StubBeginFrameSource source;
frame_sink_holder_->SetBeginFrameSource(&source);
// FrameSinkHolder should be observing the source when it is set.
EXPECT_TRUE(test_api.IsObservingBeginFrameSource());
// After consecutively not producing frames for a certain number of
// BeginFrames, FrameSinkHolder should stop observing the source.
for (int i = 0; i < 4; i++) {
frame_sink_holder_->OnBeginFrame(CreateValidBeginFrameArgsForTesting());
}
frame_sink_holder_->SubmitCompositorFrame(/*synchronous_draw=*/true);
frame_sink_holder_->OnBeginFrame(CreateValidBeginFrameArgsForTesting());
EXPECT_TRUE(test_api.IsObservingBeginFrameSource());
for (int i = 0; i < 5; i++) {
frame_sink_holder_->OnBeginFrame(CreateValidBeginFrameArgsForTesting());
}
EXPECT_FALSE(test_api.IsObservingBeginFrameSource());
// However, if there is request to submit a new frame, FrameSinkHolder should
// start observing the source again.
frame_sink_holder_->SubmitCompositorFrame(/*synchronous_draw=*/true);
EXPECT_TRUE(test_api.IsObservingBeginFrameSource());
frame_sink_holder_->SetBeginFrameSource(nullptr);
}
TEST_F(FrameSinkHolderTest, ObserveBeginFrameSourceOnDemand_AutoUpdate) {
FrameSinkHolderTestApi test_api(frame_sink_holder_.get());
frame_sink_holder_->SetAutoUpdateMode(true);
StubBeginFrameSource source;
frame_sink_holder_->SetBeginFrameSource(&source);
// FrameSinkHolder should be observing BeginFrameSource when it is set.
EXPECT_TRUE(test_api.IsObservingBeginFrameSource());
// When auto update mode is on, we should not stop observation of
// BeginFrameSource.
for (int i = 0; i < 10; i++) {
frame_sink_holder_->OnBeginFrame(CreateValidBeginFrameArgsForTesting());
}
EXPECT_TRUE(test_api.IsObservingBeginFrameSource());
// However once the auto update mode is turned off, we should stop observing
// the BeginFrameSource as needed.
frame_sink_holder_->SetAutoUpdateMode(false);
for (int i = 0; i < 5; i++) {
frame_sink_holder_->OnBeginFrame(CreateValidBeginFrameArgsForTesting());
}
EXPECT_FALSE(test_api.IsObservingBeginFrameSource());
// Renablending the auto update mode should start the observation of
// BeginFrameSource.
frame_sink_holder_->SetAutoUpdateMode(true);
EXPECT_TRUE(test_api.IsObservingBeginFrameSource());
frame_sink_holder_->SetBeginFrameSource(nullptr);
}
TEST_F(FrameSinkHolderTest, SubmitFrameSynchronouslyWhilePendingFrameAck) {
FrameSinkHolderTestApi test_api(frame_sink_holder_.get());
frame_factory_->SetFrameMetaData(gfx::Size(100, 100), 1.0);
// Call OnBeginFrame so that FrameSinkHolder can know that it can submit
// frames synchronously.
frame_sink_holder_->OnBeginFrame(CreateValidBeginFrameArgsForTesting());
frame_sink_holder_->SubmitCompositorFrame(/*synchronous_draw=*/true);
EXPECT_TRUE(test_api.IsPendingFrameAck());
EXPECT_EQ(layer_tree_frame_sink_->num_of_frames_received(), 1);
frame_factory_->SetFrameMetaData(gfx::Size(200, 200), 1.0);
frame_sink_holder_->SubmitCompositorFrame(/*synchronous_draw=*/true);
// This confirms that FrameSinkHolder did not submit frame synchronously,
// since it has not received frame ack for the last frame.
EXPECT_EQ(layer_tree_frame_sink_->GetLatestReceivedFrame().size_in_pixels(),
gfx::Size(100, 100));
EXPECT_EQ(layer_tree_frame_sink_->num_of_frames_received(), 1);
// FrameSinkHolder fell to asynchronous frame submission.
EXPECT_TRUE(test_api.IsPendingFrame());
}
TEST_F(FrameSinkHolderTest, HandlingAsynchronousFrameRequests_NoAutoUpdate) {
FrameSinkHolderTestApi test_api(frame_sink_holder_.get());
frame_sink_holder_->OnBeginFrame(CreateValidBeginFrameArgsForTesting());
// FrameSinkHolder has no request to submit a frame.
auto skipped_reason = layer_tree_frame_sink_->GetLatestFrameSkippedReason();
ASSERT_TRUE(skipped_reason.has_value());
EXPECT_EQ(skipped_reason, cc::FrameSkippedReason::kNoDamage);
frame_factory_->SetFrameMetaData(gfx::Size(100, 100), 1.0);
frame_sink_holder_->SubmitCompositorFrame(/*synchronous_draw=*/false);
EXPECT_TRUE(test_api.IsPendingFrame());
// This time FrameSinkHolder has a request to submit frame asynchronously.
frame_sink_holder_->OnBeginFrame(CreateValidBeginFrameArgsForTesting());
EXPECT_EQ(layer_tree_frame_sink_->num_of_frames_received(), 1);
// Asynchronously submitted frames will not have manual BeginFrameAck.
EXPECT_THAT(
layer_tree_frame_sink_->GetLatestReceivedFrame().metadata.begin_frame_ack,
testing::Not(IsBeginFrameAckEqual(
viz::BeginFrameAck::CreateManualAckWithDamage())));
EXPECT_FALSE(test_api.IsPendingFrame());
EXPECT_TRUE(test_api.IsPendingFrameAck());
layer_tree_frame_sink_->ResetLatestFrameState();
// FrameSinkHolder did not submit a frame since it is still waiting for ack.
frame_sink_holder_->OnBeginFrame(CreateValidBeginFrameArgsForTesting());
skipped_reason = layer_tree_frame_sink_->GetLatestFrameSkippedReason();
ASSERT_TRUE(skipped_reason.has_value());
EXPECT_EQ(skipped_reason, cc::FrameSkippedReason::kWaitingOnMain);
EXPECT_EQ(layer_tree_frame_sink_->num_of_frames_received(), 1);
// Received ack.
frame_sink_holder_->DidReceiveCompositorFrameAck();
EXPECT_FALSE(test_api.IsPendingFrameAck());
layer_tree_frame_sink_->ResetLatestFrameState();
// FrameSinkHolder did not submit anything because it did not have any pending
// request.
frame_sink_holder_->OnBeginFrame(CreateValidBeginFrameArgsForTesting());
skipped_reason = layer_tree_frame_sink_->GetLatestFrameSkippedReason();
ASSERT_TRUE(skipped_reason.has_value());
EXPECT_EQ(skipped_reason, cc::FrameSkippedReason::kNoDamage);
EXPECT_EQ(layer_tree_frame_sink_->num_of_frames_received(), 1);
// FrameSinkHolder has an async request again.
frame_sink_holder_->SubmitCompositorFrame(/*synchronous_draw=*/false);
EXPECT_TRUE(test_api.IsPendingFrame());
EXPECT_FALSE(test_api.IsPendingFrameAck());
layer_tree_frame_sink_->ResetLatestFrameState();
// FrameSinkHolder should now submit a new frame.
frame_sink_holder_->OnBeginFrame(CreateValidBeginFrameArgsForTesting());
// Asynchronously submitted frames will not have manual begin_frame_ack.
EXPECT_THAT(
layer_tree_frame_sink_->GetLatestReceivedFrame().metadata.begin_frame_ack,
testing::Not(IsBeginFrameAckEqual(
viz::BeginFrameAck::CreateManualAckWithDamage())));
EXPECT_EQ(layer_tree_frame_sink_->num_of_frames_received(), 2);
EXPECT_FALSE(test_api.IsPendingFrame());
EXPECT_TRUE(test_api.IsPendingFrameAck());
}
TEST_F(FrameSinkHolderTest, DontSubmitNewFramesWhenWaitingToDeleteSinkHolder) {
FrameSinkHolderTestApi test_api(frame_sink_holder_.get());
base::RunLoop loop;
viz::ResourceId id_1 =
GetResourceManager().OfferResource(std::make_unique<UiResource>());
frame_factory_->SetFrameResources({id_1});
frame_factory_->SetFrameMetaData(gfx::Size(100, 100), 1.0);
// Call OnBeginFrame so that FrameSinkHolder can know that it can submit
// frames synchronously.
frame_sink_holder_->OnBeginFrame(CreateValidBeginFrameArgsForTesting());
frame_sink_holder_->SubmitCompositorFrame(/*synchronous_draw=*/true);
EXPECT_EQ(layer_tree_frame_sink_->num_of_frames_received(), 1);
// The lifetime of frame_sink_holder has been extended since there are still
// some exported resources.
EXPECT_FALSE(FrameSinkHolder::DeleteWhenLastResourceHasBeenReclaimed(
std::move(frame_sink_holder_), host_window_));
ASSERT_TRUE(holder_weak_ptr_);
// During deletion FrameSinkHolder submits a empty frame.
EXPECT_EQ(layer_tree_frame_sink_->num_of_frames_received(), 2);
layer_tree_frame_sink_->ResetLatestFrameState();
holder_weak_ptr_->OnBeginFrame(CreateValidBeginFrameArgsForTesting());
// Confirms that FrameSinkHolder did not submit a new frame on asynchronous
// request.
EXPECT_EQ(layer_tree_frame_sink_->num_of_frames_received(), 2);
}
TEST_F(FrameSinkHolderTest,
DeleteSinkHolderImmediatelyWhenNoFramesIsSubmitted) {
FrameSinkHolderTestApi test_api(frame_sink_holder_.get());
// Confirms that FrameSinkHolder has not submitted any frames yet.
EXPECT_TRUE(test_api.LastSubmittedFrameSize().IsEmpty());
EXPECT_EQ(layer_tree_frame_sink_->num_of_frames_received(), 0);
// FrameSinkHolder will get deleted straight away since it has not submitted
// any resources to the display compositor.
EXPECT_TRUE(FrameSinkHolder::DeleteWhenLastResourceHasBeenReclaimed(
std::move(frame_sink_holder_), host_window_));
// Since FrameSinkHolder is deleted immediately, we expect the weak_ptr to be
// not valid.
EXPECT_FALSE(holder_weak_ptr_);
}
TEST_F(FrameSinkHolderTest, ExtendLifeTimeOfHolderToRootWindow) {
FrameSinkHolderTestApi test_api(frame_sink_holder_.get());
viz::ResourceId id_1 =
GetResourceManager().OfferResource(std::make_unique<UiResource>());
viz::ResourceId id_2 =
GetResourceManager().OfferResource(std::make_unique<UiResource>());
viz::ResourceId id_3 =
GetResourceManager().OfferResource(std::make_unique<UiResource>());
frame_factory_->SetFrameResources({id_1, id_2, id_3});
frame_factory_->SetFrameMetaData(gfx::Size(100, 100), 1.0);
// Call OnBeginFrame so that FrameSinkHolder can know that it can submit
// frames synchronously.
frame_sink_holder_->OnBeginFrame(CreateValidBeginFrameArgsForTesting());
frame_sink_holder_->SubmitCompositorFrame(/*synchronous_draw=*/true);
// Confirms that FrameSinkHolder has not submitted any frames.
EXPECT_FALSE(test_api.LastSubmittedFrameSize().IsEmpty());
EXPECT_EQ(layer_tree_frame_sink_->num_of_frames_received(), 1);
// Since FrameSinkHolder has not received the resources back from display
// compositor, it extend its lifetime.
EXPECT_FALSE(FrameSinkHolder::DeleteWhenLastResourceHasBeenReclaimed(
std::move(frame_sink_holder_), host_window_));
// Since FrameSinkHolder lifetime is extend, we expect the weak_ptr to be
// valid.
EXPECT_TRUE(holder_weak_ptr_);
}
TEST_F(FrameSinkHolderTest, KeepSubmittingFrameWhenAutoUpdateIsOn) {
FrameSinkHolderTestApi test_api(frame_sink_holder_.get());
frame_factory_->SetFrameMetaData(gfx::Size(100, 100), 1.0);
// Request a frame.
frame_sink_holder_->OnBeginFrame(CreateValidBeginFrameArgsForTesting());
// Since auto_fresh_mode is off, FrameSinkHolder did not submit any frame as
// there was not request for a frame submission.
EXPECT_EQ(layer_tree_frame_sink_->num_of_frames_received(), 0);
// Request a frame again. FrameSinkHolder should not submit a frame.
frame_sink_holder_->OnBeginFrame(CreateValidBeginFrameArgsForTesting());
EXPECT_EQ(layer_tree_frame_sink_->num_of_frames_received(), 0);
frame_sink_holder_->SetAutoUpdateMode(/*mode=*/true);
// After auto_fresh_mode on, when compositor requests for a frame,
// FrameSinkHolder should submit a frame.
frame_sink_holder_->OnBeginFrame(CreateValidBeginFrameArgsForTesting());
EXPECT_EQ(layer_tree_frame_sink_->num_of_frames_received(), 1);
// FrameSinkHolder should not submit a new frame sas it has no received an
// ack from the compositor,
frame_sink_holder_->OnBeginFrame(CreateValidBeginFrameArgsForTesting());
EXPECT_EQ(layer_tree_frame_sink_->num_of_frames_received(), 1);
// Receive an ack.
frame_sink_holder_->DidReceiveCompositorFrameAck();
// In auto_fresh mode, FrameSinkHolder will keep on submitting frames
// asynchronously.
frame_sink_holder_->OnBeginFrame(CreateValidBeginFrameArgsForTesting());
EXPECT_EQ(layer_tree_frame_sink_->num_of_frames_received(), 2);
frame_sink_holder_->DidReceiveCompositorFrameAck();
frame_sink_holder_->OnBeginFrame(CreateValidBeginFrameArgsForTesting());
EXPECT_EQ(layer_tree_frame_sink_->num_of_frames_received(), 3);
}
TEST_F(FrameSinkHolderTest, DeleteHolderAfterReclaimingAllResources) {
FrameSinkHolderTestApi test_api(frame_sink_holder_.get());
base::RunLoop loop;
viz::ResourceId id_1 =
GetResourceManager().OfferResource(std::make_unique<UiResource>());
viz::ResourceId id_2 =
GetResourceManager().OfferResource(std::make_unique<UiResource>());
frame_factory_->SetFrameResources({id_1, id_2});
frame_factory_->SetFrameMetaData(gfx::Size(100, 100), 1.0);
// Call OnBeginFrame so that FrameSinkHolder can know that it can submit
// frames synchronously.
frame_sink_holder_->OnBeginFrame(CreateValidBeginFrameArgsForTesting());
frame_sink_holder_->SubmitCompositorFrame(/*synchronous_draw=*/true);
EXPECT_FALSE(FrameSinkHolder::DeleteWhenLastResourceHasBeenReclaimed(
std::move(frame_sink_holder_), host_window_));
// The lifetime of frame_sink_holder has been extended since there are still
// some exported resources.
ASSERT_TRUE(holder_weak_ptr_);
std::vector<viz::ReturnedResource> to_be_returned_resources;
layer_tree_frame_sink_->GetFrameResourcesToReturn(to_be_returned_resources);
// Reclaim the exported resources.
holder_weak_ptr_->ReclaimResources(std::move(to_be_returned_resources));
// Wait for the deletion task to be completed.
loop.RunUntilIdle();
ASSERT_FALSE(holder_weak_ptr_);
}
TEST_F(FrameSinkHolderTest, LayerTreeFrameSinkLost) {
FrameSinkHolderTestApi test_api(frame_sink_holder_.get());
viz::ResourceId id_1 =
GetResourceManager().OfferResource(std::make_unique<UiResource>());
frame_factory_->SetFrameResources({id_1});
frame_factory_->SetFrameMetaData(gfx::Size(100, 100), 1.0);
// Call OnBeginFrame so that FrameSinkHolder can know that it can submit
// frames synchronously.
frame_sink_holder_->OnBeginFrame(CreateValidBeginFrameArgsForTesting());
frame_sink_holder_->SubmitCompositorFrame(/*synchronous_draw=*/true);
EXPECT_EQ(GetResourceManager().exported_resources_count(), 1u);
frame_sink_holder_->DidLoseLayerTreeFrameSink();
// When FrameSinkHolder loses the LayerTreeFrameSink, it marks all the
// exported resources as lost.
EXPECT_EQ(GetResourceManager().exported_resources_count(), 0u);
}
TEST_F(FrameSinkHolderTest,
LayerTreeFrameSinkLostWhenWaitingToDeleteSinkHolder) {
FrameSinkHolderTestApi test_api(frame_sink_holder_.get());
base::RunLoop loop;
viz::ResourceId id_1 =
GetResourceManager().OfferResource(std::make_unique<UiResource>());
frame_factory_->SetFrameResources({id_1});
frame_factory_->SetFrameMetaData(gfx::Size(100, 100), 1.0);
// Call OnBeginFrame so that FrameSinkHolder can know that it can submit
// frames synchronously.
frame_sink_holder_->OnBeginFrame(CreateValidBeginFrameArgsForTesting());
frame_sink_holder_->SubmitCompositorFrame(/*synchronous_draw=*/true);
EXPECT_FALSE(FrameSinkHolder::DeleteWhenLastResourceHasBeenReclaimed(
std::move(frame_sink_holder_), host_window_));
// The lifetime of frame_sink_holder has been extended since there are still
// some exported resources.
ASSERT_TRUE(holder_weak_ptr_);
holder_weak_ptr_->DidLoseLayerTreeFrameSink();
// Since FrameSinkHolder cannot reclaim back exported resources, it schedules
// to delete itself.
// Wait for deletion task to complete.
loop.RunUntilIdle();
ASSERT_FALSE(holder_weak_ptr_);
}
TEST_F(FrameSinkHolderTest,
DeleteSinkHolderWithExportedResources_DuringShutdown) {
FrameSinkHolderTestApi test_api(frame_sink_holder_.get());
viz::ResourceId id_1 =
GetResourceManager().OfferResource(std::make_unique<UiResource>());
frame_factory_->SetFrameResources({id_1});
frame_factory_->SetFrameMetaData(gfx::Size(100, 100), 1.0);
// Call OnBeginFrame so that FrameSinkHolder can know that it can submit
// frames synchronously.
frame_sink_holder_->OnBeginFrame(CreateValidBeginFrameArgsForTesting());
frame_sink_holder_->SubmitCompositorFrame(/*synchronous_draw=*/true);
// Confirms we have an exported resource.
EXPECT_EQ(frame_sink_holder_->resource_manager().exported_resources_count(),
1u);
// During shutdown, root_window can be null. We can replicate it by
// removing the host window from the window hierarchy.
aura::Window* root_window =
Shell::Get()->GetRootWindowForDisplayId(GetPrimaryDisplay().id());
root_window->RemoveChild(host_window_);
// Since `host_window_` is removed from window tree hierarchy, wrapping it in
// a unique_ptr to delete this object as it goes out of scope and stop it
// from leaking memory.
auto host_window = base::WrapUnique<aura::Window>(host_window_);
// Since FrameSinkHolder cannot extend its lifetime, it marks the resources
// as lost and deletes itself immediately.
EXPECT_TRUE(FrameSinkHolder::DeleteWhenLastResourceHasBeenReclaimed(
std::move(frame_sink_holder_), host_window_));
// Since FrameSinkHolder is deleted immediately, we expect the weak_ptr to be
// not valid.
EXPECT_FALSE(holder_weak_ptr_);
}
TEST_F(FrameSinkHolderTest,
DeleteSinkHolderImmediatelyWhenNoExportedResources) {
FrameSinkHolderTestApi test_api(frame_sink_holder_.get());
viz::ResourceId id_1 =
GetResourceManager().OfferResource(std::make_unique<UiResource>());
frame_factory_->SetFrameResources({id_1});
frame_factory_->SetFrameMetaData(gfx::Size(100, 100), 1.0);
// Call OnBeginFrame so that FrameSinkHolder can know that it can submit
// frames synchronously.
frame_sink_holder_->OnBeginFrame(CreateValidBeginFrameArgsForTesting());
frame_sink_holder_->SubmitCompositorFrame(/*synchronous_draw=*/true);
// Confirms that FrameSinkHolder has submitted a frame.
EXPECT_FALSE(test_api.LastSubmittedFrameSize().IsEmpty());
EXPECT_EQ(layer_tree_frame_sink_->num_of_frames_received(), 1);
std::vector<viz::ReturnedResource> to_be_returned_resources;
layer_tree_frame_sink_->GetFrameResourcesToReturn(to_be_returned_resources);
frame_sink_holder_->ReclaimResources(std::move(to_be_returned_resources));
// We can delete the holder straight way since we have no exported resources.
ASSERT_EQ(GetResourceManager().exported_resources_count(), 0u);
EXPECT_TRUE(FrameSinkHolder::DeleteWhenLastResourceHasBeenReclaimed(
std::move(frame_sink_holder_), host_window_));
// Since FrameSinkHolder is deleted immediately, we expect the weak_ptr to be
// not valid.
EXPECT_FALSE(holder_weak_ptr_);
}
} // namespace
} // namespace ash