| // Copyright 2014 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 <map> |
| #include <queue> |
| #include <utility> |
| |
| #include "android_webview/browser/browser_view_renderer.h" |
| #include "android_webview/browser/child_frame.h" |
| #include "android_webview/browser/compositor_frame_consumer.h" |
| #include "android_webview/browser/render_thread_manager.h" |
| #include "android_webview/browser/test/rendering_test.h" |
| #include "base/location.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "cc/output/compositor_frame.h" |
| #include "content/public/test/test_synchronous_compositor_android.h" |
| |
| namespace android_webview { |
| |
| class SmokeTest : public RenderingTest { |
| void StartTest() override { |
| browser_view_renderer_->PostInvalidate(ActiveCompositor()); |
| } |
| |
| void DidDrawOnRT() override { EndTest(); } |
| }; |
| |
| RENDERING_TEST_F(SmokeTest); |
| |
| // Test the case where SynchronousCompositor is constructed after the RVH that |
| // owns it is switched to be active. |
| class ActiveCompositorSwitchBeforeConstructionTest : public RenderingTest { |
| public: |
| ActiveCompositorSwitchBeforeConstructionTest() |
| : on_draw_count_(0), new_compositor_(nullptr) {} |
| void StartTest() override { |
| browser_view_renderer_->PostInvalidate(ActiveCompositor()); |
| } |
| |
| void DidOnDraw(bool success) override { |
| on_draw_count_++; |
| switch (on_draw_count_) { |
| case 1: |
| EXPECT_TRUE(success); |
| // Change compositor here. And do another ondraw. |
| // The previous active compositor id is 0, 0, now change it to 0, 1. |
| browser_view_renderer_->SetActiveCompositorID(CompositorID(0, 1)); |
| browser_view_renderer_->PostInvalidate(ActiveCompositor()); |
| break; |
| case 2: |
| // The 2nd ondraw is skipped because there is no active compositor at |
| // the moment. |
| EXPECT_FALSE(success); |
| new_compositor_.reset(new content::TestSynchronousCompositor(0, 1)); |
| new_compositor_->SetClient(browser_view_renderer_.get()); |
| EXPECT_EQ(ActiveCompositor(), new_compositor_.get()); |
| browser_view_renderer_->PostInvalidate(ActiveCompositor()); |
| break; |
| case 3: |
| EXPECT_TRUE(success); |
| compositor_ = std::move(new_compositor_); |
| |
| EXPECT_EQ(ActiveCompositor(), compositor_.get()); |
| browser_view_renderer_->PostInvalidate(ActiveCompositor()); |
| break; |
| case 4: |
| EXPECT_TRUE(success); |
| EndTest(); |
| } |
| } |
| |
| private: |
| int on_draw_count_; |
| std::unique_ptr<content::TestSynchronousCompositor> new_compositor_; |
| }; |
| |
| RENDERING_TEST_F(ActiveCompositorSwitchBeforeConstructionTest); |
| |
| // Test the case where SynchronousCompositor is constructed before the RVH that |
| // owns it is switched to be active. |
| class ActiveCompositorSwitchAfterConstructionTest : public RenderingTest { |
| public: |
| ActiveCompositorSwitchAfterConstructionTest() |
| : on_draw_count_(0), new_compositor_(nullptr) {} |
| void StartTest() override { |
| browser_view_renderer_->PostInvalidate(ActiveCompositor()); |
| } |
| |
| void DidOnDraw(bool success) override { |
| on_draw_count_++; |
| switch (on_draw_count_) { |
| case 1: |
| EXPECT_TRUE(success); |
| // Create a new compositor here. And switch it to be active. And then |
| // do another ondraw. |
| new_compositor_.reset(new content::TestSynchronousCompositor(0, 1)); |
| new_compositor_->SetClient(browser_view_renderer_.get()); |
| browser_view_renderer_->SetActiveCompositorID(CompositorID(0, 1)); |
| |
| EXPECT_EQ(ActiveCompositor(), new_compositor_.get()); |
| browser_view_renderer_->PostInvalidate(ActiveCompositor()); |
| break; |
| case 2: |
| EXPECT_TRUE(success); |
| compositor_ = std::move(new_compositor_); |
| |
| EXPECT_EQ(ActiveCompositor(), compositor_.get()); |
| browser_view_renderer_->PostInvalidate(ActiveCompositor()); |
| break; |
| case 3: |
| EXPECT_TRUE(success); |
| EndTest(); |
| } |
| } |
| |
| private: |
| int on_draw_count_; |
| std::unique_ptr<content::TestSynchronousCompositor> new_compositor_; |
| }; |
| |
| RENDERING_TEST_F(ActiveCompositorSwitchAfterConstructionTest); |
| |
| class ClearViewTest : public RenderingTest { |
| public: |
| ClearViewTest() : on_draw_count_(0) {} |
| |
| void StartTest() override { |
| browser_view_renderer_->PostInvalidate(ActiveCompositor()); |
| browser_view_renderer_->ClearView(); |
| } |
| |
| void DidOnDraw(bool success) override { |
| on_draw_count_++; |
| if (on_draw_count_ == 1) { |
| // First OnDraw should be skipped due to ClearView. |
| EXPECT_FALSE(success); |
| browser_view_renderer_->DidUpdateContent( |
| ActiveCompositor()); // Unset ClearView. |
| browser_view_renderer_->PostInvalidate(ActiveCompositor()); |
| } else { |
| // Following OnDraws should succeed. |
| EXPECT_TRUE(success); |
| } |
| } |
| |
| void DidDrawOnRT() override { EndTest(); } |
| |
| private: |
| int on_draw_count_; |
| }; |
| |
| RENDERING_TEST_F(ClearViewTest); |
| |
| class TestAnimateInAndOutOfScreen : public RenderingTest { |
| public: |
| TestAnimateInAndOutOfScreen() : on_draw_count_(0), draw_gl_count_on_rt_(0) {} |
| |
| void StartTest() override { |
| new_constraints_ = ParentCompositorDrawConstraints( |
| false, gfx::Transform(), window_->surface_size().IsEmpty()); |
| new_constraints_.transform.Scale(2.0, 2.0); |
| browser_view_renderer_->PostInvalidate(ActiveCompositor()); |
| } |
| |
| void WillOnDraw() override { |
| RenderingTest::WillOnDraw(); |
| // Step 0: A single onDraw on screen. The parent draw constraints |
| // of the BVR will updated to be the initial constraints. |
| // Step 1: A single onDrraw off screen. The parent draw constraints of the |
| // BVR will be updated to the new constraints. |
| // Step 2: This onDraw is to introduce the DrawGL that animates the |
| // webview onto the screen on render thread. End the test when the parent |
| // draw constraints of BVR is updated to initial constraints. |
| if (on_draw_count_ == 1 || on_draw_count_ == 2) |
| browser_view_renderer_->PrepareToDraw(gfx::Vector2d(), gfx::Rect()); |
| } |
| |
| void DidOnDraw(bool success) override { |
| EXPECT_TRUE(success); |
| on_draw_count_++; |
| } |
| |
| bool WillDrawOnRT(AwDrawGLInfo* draw_info) override { |
| if (draw_gl_count_on_rt_ == 1) { |
| draw_gl_count_on_rt_++; |
| ui_task_runner_->PostTask( |
| FROM_HERE, |
| base::Bind(&RenderingTest::PostInvalidate, base::Unretained(this))); |
| return false; |
| } |
| |
| draw_info->width = window_->surface_size().width(); |
| draw_info->height = window_->surface_size().height(); |
| draw_info->is_layer = false; |
| |
| gfx::Transform transform; |
| if (draw_gl_count_on_rt_ == 0) |
| transform = new_constraints_.transform; |
| |
| transform.matrix().asColMajorf(draw_info->transform); |
| return true; |
| } |
| |
| void DidDrawOnRT() override { draw_gl_count_on_rt_++; } |
| |
| bool DrawConstraintsEquals( |
| const ParentCompositorDrawConstraints& constraints1, |
| const ParentCompositorDrawConstraints& constraints2) { |
| if (constraints1.is_layer != constraints2.is_layer || |
| constraints1.transform != constraints2.transform) |
| return false; |
| |
| return !constraints1.is_layer || |
| constraints1.surface_rect_empty == constraints2.surface_rect_empty; |
| } |
| |
| void OnParentDrawConstraintsUpdated() override { |
| ParentCompositorDrawConstraints constraints = |
| GetCompositorFrameConsumer()->GetParentDrawConstraintsOnUI(); |
| switch (on_draw_count_) { |
| case 0u: |
| // This OnParentDrawConstraintsUpdated is generated by |
| // connecting the compositor frame consumer to the producer. |
| break; |
| case 1u: |
| EXPECT_TRUE(DrawConstraintsEquals(constraints, new_constraints_)); |
| break; |
| case 3u: |
| EXPECT_TRUE(DrawConstraintsEquals(constraints, initial_constraints_)); |
| EndTest(); |
| break; |
| // There will be a following 4th onDraw. But the hardware renderer won't |
| // post back the draw constraints in DrawGL because the constraints |
| // don't change. |
| default: |
| FAIL(); |
| } |
| } |
| |
| private: |
| int on_draw_count_; |
| int draw_gl_count_on_rt_; |
| ParentCompositorDrawConstraints initial_constraints_; |
| ParentCompositorDrawConstraints new_constraints_; |
| }; |
| |
| RENDERING_TEST_F(TestAnimateInAndOutOfScreen); |
| |
| class CompositorNoFrameTest : public RenderingTest { |
| public: |
| CompositorNoFrameTest() : on_draw_count_(0) {} |
| |
| void StartTest() override { |
| browser_view_renderer_->PostInvalidate(ActiveCompositor()); |
| } |
| |
| void WillOnDraw() override { |
| if (0 == on_draw_count_) { |
| // No frame from compositor. |
| } else if (1 == on_draw_count_) { |
| compositor_->SetHardwareFrame(0u, ConstructEmptyFrame()); |
| } else if (2 == on_draw_count_) { |
| // No frame from compositor. |
| } |
| // There may be trailing invalidates. |
| } |
| |
| void DidOnDraw(bool success) override { |
| if (0 == on_draw_count_) { |
| // Should fail as there has been no frames from compositor. |
| EXPECT_FALSE(success); |
| browser_view_renderer_->PostInvalidate(ActiveCompositor()); |
| } else if (1 == on_draw_count_) { |
| // Should succeed with frame from compositor. |
| EXPECT_TRUE(success); |
| browser_view_renderer_->PostInvalidate(ActiveCompositor()); |
| } else if (2 == on_draw_count_) { |
| // Should still succeed with last frame, even if no frame from compositor. |
| EXPECT_TRUE(success); |
| EndTest(); |
| } |
| on_draw_count_++; |
| } |
| |
| private: |
| int on_draw_count_; |
| }; |
| |
| RENDERING_TEST_F(CompositorNoFrameTest); |
| |
| class ResourceRenderingTest : public RenderingTest { |
| public: |
| using ResourceCountMap = std::map<cc::ResourceId, int>; |
| using OutputSurfaceResourceCountMap = std::map<uint32_t, ResourceCountMap>; |
| |
| virtual std::unique_ptr<content::SynchronousCompositor::Frame> GetFrame( |
| int frame_number) = 0; |
| |
| void StartTest() override { |
| frame_number_ = 0; |
| AdvanceFrame(); |
| } |
| |
| void WillOnDraw() override { |
| if (next_frame_) { |
| compositor_->SetHardwareFrame(next_frame_->output_surface_id, |
| std::move(next_frame_->frame)); |
| } |
| } |
| |
| void DidOnDraw(bool success) override { |
| EXPECT_EQ(next_frame_ != nullptr, success); |
| if (!AdvanceFrame()) { |
| ui_task_runner_->PostTask(FROM_HERE, |
| base::Bind(&ResourceRenderingTest::CheckResults, |
| base::Unretained(this))); |
| } |
| } |
| |
| OutputSurfaceResourceCountMap GetReturnedResourceCounts() { |
| OutputSurfaceResourceCountMap counts; |
| content::TestSynchronousCompositor::FrameAckArray returned_resources_array; |
| compositor_->SwapReturnedResources(&returned_resources_array); |
| for (const auto& resources : returned_resources_array) { |
| for (const auto& returned_resource : resources.resources) { |
| counts[resources.output_surface_id][returned_resource.id] += |
| returned_resource.count; |
| } |
| } |
| return counts; |
| } |
| |
| virtual void CheckResults() = 0; |
| |
| private: |
| bool AdvanceFrame() { |
| next_frame_ = GetFrame(frame_number_++); |
| if (next_frame_) { |
| browser_view_renderer_->PostInvalidate(ActiveCompositor()); |
| return true; |
| } |
| return false; |
| } |
| |
| std::unique_ptr<content::SynchronousCompositor::Frame> next_frame_; |
| int frame_number_; |
| }; |
| |
| class SwitchOutputSurfaceIdTest : public ResourceRenderingTest { |
| struct FrameInfo { |
| uint32_t output_surface_id; |
| cc::ResourceId resource_id; // Each frame contains a single resource. |
| }; |
| |
| std::unique_ptr<content::SynchronousCompositor::Frame> GetFrame( |
| int frame_number) override { |
| static const FrameInfo infos[] = { |
| // First output surface. |
| {0u, 1u}, {0u, 1u}, {0u, 2u}, {0u, 2u}, {0u, 3u}, {0u, 3u}, {0u, 4u}, |
| // Second output surface. |
| {1u, 1u}, {1u, 1u}, {1u, 2u}, {1u, 2u}, {1u, 3u}, {1u, 3u}, {1u, 4u}, |
| }; |
| if (frame_number >= static_cast<int>(arraysize(infos))) { |
| return nullptr; |
| } |
| |
| std::unique_ptr<content::SynchronousCompositor::Frame> frame( |
| new content::SynchronousCompositor::Frame); |
| frame->output_surface_id = infos[frame_number].output_surface_id; |
| frame->frame = ConstructFrame(infos[frame_number].resource_id); |
| |
| if (last_output_surface_id_ != infos[frame_number].output_surface_id) { |
| expected_return_count_.clear(); |
| last_output_surface_id_ = infos[frame_number].output_surface_id; |
| } |
| ++expected_return_count_[infos[frame_number].resource_id]; |
| return frame; |
| } |
| |
| void StartTest() override { |
| last_output_surface_id_ = -1U; |
| ResourceRenderingTest::StartTest(); |
| } |
| |
| void CheckResults() override { |
| GetCompositorFrameConsumer()->DeleteHardwareRendererOnUI(); |
| window_->Detach(); |
| window_.reset(); |
| |
| // Make sure resources for the last output surface are returned. |
| EXPECT_EQ(expected_return_count_, |
| GetReturnedResourceCounts()[last_output_surface_id_]); |
| EndTest(); |
| } |
| |
| private: |
| uint32_t last_output_surface_id_; |
| ResourceCountMap expected_return_count_; |
| }; |
| |
| RENDERING_TEST_F(SwitchOutputSurfaceIdTest); |
| |
| class RenderThreadManagerDeletionTest : public ResourceRenderingTest { |
| std::unique_ptr<content::SynchronousCompositor::Frame> GetFrame( |
| int frame_number) override { |
| if (frame_number > 0) { |
| return nullptr; |
| } |
| |
| const uint32_t output_surface_id = 0u; |
| const cc::ResourceId resource_id = |
| static_cast<cc::ResourceId>(frame_number); |
| |
| std::unique_ptr<content::SynchronousCompositor::Frame> frame( |
| new content::SynchronousCompositor::Frame); |
| frame->output_surface_id = output_surface_id; |
| frame->frame = ConstructFrame(resource_id); |
| ++expected_return_count_[output_surface_id][resource_id]; |
| return frame; |
| } |
| |
| void CheckResults() override { |
| OutputSurfaceResourceCountMap resource_counts; |
| functor_.reset(); |
| // Make sure resources for the last frame are returned. |
| EXPECT_EQ(expected_return_count_, GetReturnedResourceCounts()); |
| EndTest(); |
| } |
| |
| private: |
| OutputSurfaceResourceCountMap expected_return_count_; |
| }; |
| |
| RENDERING_TEST_F(RenderThreadManagerDeletionTest); |
| |
| class RenderThreadManagerSwitchTest : public ResourceRenderingTest { |
| std::unique_ptr<content::SynchronousCompositor::Frame> GetFrame( |
| int frame_number) override { |
| switch (frame_number) { |
| case 0: { |
| // Draw a frame with initial RTM. |
| break; |
| } |
| case 1: { |
| // Switch to new RTM. |
| std::unique_ptr<FakeFunctor> functor(new FakeFunctor); |
| functor->Init(window_.get(), |
| base::WrapUnique(new RenderThreadManager( |
| functor.get(), base::ThreadTaskRunnerHandle::Get()))); |
| browser_view_renderer_->SetCurrentCompositorFrameConsumer( |
| functor->GetCompositorFrameConsumer()); |
| saved_functor_ = std::move(functor_); |
| functor_ = std::move(functor); |
| break; |
| } |
| case 2: { |
| // Draw a frame with the new RTM, but also redraw the initial RTM. |
| window_->RequestDrawGL(saved_functor_.get()); |
| break; |
| } |
| case 3: { |
| // Switch back to the initial RTM, allowing the new RTM to be destroyed. |
| functor_ = std::move(saved_functor_); |
| browser_view_renderer_->SetCurrentCompositorFrameConsumer( |
| functor_->GetCompositorFrameConsumer()); |
| break; |
| } |
| default: |
| return nullptr; |
| } |
| |
| const uint32_t output_surface_id = 0u; |
| const cc::ResourceId resource_id = |
| static_cast<cc::ResourceId>(frame_number); |
| |
| std::unique_ptr<content::SynchronousCompositor::Frame> frame( |
| new content::SynchronousCompositor::Frame); |
| frame->output_surface_id = output_surface_id; |
| frame->frame = ConstructFrame(resource_id); |
| ++expected_return_count_[output_surface_id][resource_id]; |
| return frame; |
| } |
| |
| void CheckResults() override { |
| OutputSurfaceResourceCountMap resource_counts; |
| functor_.reset(); |
| // Make sure resources for all frames are returned. |
| EXPECT_EQ(expected_return_count_, GetReturnedResourceCounts()); |
| EndTest(); |
| } |
| |
| private: |
| std::unique_ptr<FakeFunctor> saved_functor_; |
| OutputSurfaceResourceCountMap expected_return_count_; |
| }; |
| |
| RENDERING_TEST_F(RenderThreadManagerSwitchTest); |
| |
| } // namespace android_webview |