| // Copyright 2017 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 <memory> |
| #include <utility> |
| |
| #include "base/callback.h" |
| #include "base/run_loop.h" |
| #include "base/test/scoped_task_environment.h" |
| #include "components/crash/core/common/crash_key.h" |
| #include "components/services/pdf_compositor/pdf_compositor_impl.h" |
| #include "components/services/pdf_compositor/public/cpp/pdf_service_mojo_types.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace printing { |
| |
| struct TestRequestData { |
| uint64_t frame_guid; |
| int page_num; |
| }; |
| |
| class MockPdfCompositorImpl : public PdfCompositorImpl { |
| public: |
| MockPdfCompositorImpl() : PdfCompositorImpl("unittest", nullptr) {} |
| ~MockPdfCompositorImpl() override {} |
| |
| MOCK_METHOD2(OnFulfillRequest, void(uint64_t, int)); |
| |
| protected: |
| void FulfillRequest(base::ReadOnlySharedMemoryMapping serialized_content, |
| const ContentToFrameMap& subframe_content_map, |
| CompositeToPdfCallback callback) override { |
| const auto* data = serialized_content.GetMemoryAs<const TestRequestData>(); |
| OnFulfillRequest(data->frame_guid, data->page_num); |
| } |
| }; |
| |
| class PdfCompositorImplTest : public testing::Test { |
| public: |
| PdfCompositorImplTest() |
| : task_environment_( |
| base::test::ScopedTaskEnvironment::MainThreadType::IO), |
| run_loop_(std::make_unique<base::RunLoop>()), |
| is_ready_(false) {} |
| |
| void OnIsReadyToCompositeCallback(bool is_ready) { |
| is_ready_ = is_ready; |
| run_loop_->Quit(); |
| } |
| |
| bool ResultFromCallback() { |
| run_loop_->Run(); |
| run_loop_ = std::make_unique<base::RunLoop>(); |
| return is_ready_; |
| } |
| |
| static void OnCompositeToPdfCallback( |
| mojom::PdfCompositor::Status status, |
| base::ReadOnlySharedMemoryRegion region) { |
| // A stub for testing, no implementation. |
| } |
| |
| static base::ReadOnlySharedMemoryRegion CreateTestData(uint64_t frame_guid, |
| int page_num) { |
| static constexpr size_t kSize = sizeof(TestRequestData); |
| auto region = base::ReadOnlySharedMemoryRegion::Create(kSize); |
| auto* data = region.mapping.GetMemoryAs<TestRequestData>(); |
| data->frame_guid = frame_guid; |
| data->page_num = page_num; |
| return std::move(region.region); |
| } |
| |
| private: |
| base::test::ScopedTaskEnvironment task_environment_; |
| std::unique_ptr<base::RunLoop> run_loop_; |
| bool is_ready_; |
| }; |
| |
| class PdfCompositorImplCrashKeyTest : public PdfCompositorImplTest { |
| public: |
| PdfCompositorImplCrashKeyTest() {} |
| ~PdfCompositorImplCrashKeyTest() override {} |
| |
| void SetUp() override { |
| crash_reporter::ResetCrashKeysForTesting(); |
| crash_reporter::InitializeCrashKeys(); |
| } |
| |
| void TearDown() override { crash_reporter::ResetCrashKeysForTesting(); } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(PdfCompositorImplCrashKeyTest); |
| }; |
| |
| TEST_F(PdfCompositorImplTest, IsReadyToComposite) { |
| PdfCompositorImpl impl("unittest", nullptr); |
| // Frame 2 and 3 are painted. |
| impl.AddSubframeContent(2, CreateTestData(2, -1), ContentToFrameMap()); |
| impl.AddSubframeContent(3, CreateTestData(3, -1), ContentToFrameMap()); |
| |
| // Frame 1 contains content 3 which corresponds to frame 2. |
| // Frame 1 should be ready as frame 2 is ready. |
| ContentToFrameMap subframe_content_map = {{3, 2}}; |
| base::flat_set<uint64_t> pending_subframes; |
| EXPECT_TRUE( |
| impl.IsReadyToComposite(1, subframe_content_map, &pending_subframes)); |
| EXPECT_TRUE(pending_subframes.empty()); |
| |
| // If another page of frame 1 needs content 2 which corresponds to frame 3. |
| // This page is ready since frame 3 was painted also. |
| subframe_content_map = {{2, 3}}; |
| EXPECT_TRUE( |
| impl.IsReadyToComposite(1, subframe_content_map, &pending_subframes)); |
| EXPECT_TRUE(pending_subframes.empty()); |
| |
| // Frame 1 with content 1, 2 and 3 should not be ready since content 1's |
| // content in frame 4 is not painted yet. |
| subframe_content_map = {{1, 4}, {2, 3}, {3, 2}}; |
| EXPECT_FALSE( |
| impl.IsReadyToComposite(1, subframe_content_map, &pending_subframes)); |
| ASSERT_EQ(pending_subframes.size(), 1u); |
| EXPECT_EQ(*pending_subframes.begin(), 4u); |
| |
| // Add content of frame 4. Now it is ready for composition. |
| impl.AddSubframeContent(4, CreateTestData(4, -1), ContentToFrameMap()); |
| EXPECT_TRUE( |
| impl.IsReadyToComposite(1, subframe_content_map, &pending_subframes)); |
| EXPECT_TRUE(pending_subframes.empty()); |
| } |
| |
| TEST_F(PdfCompositorImplTest, MultiLayerDependency) { |
| PdfCompositorImpl impl("unittest", nullptr); |
| // Frame 3 has content 1 which refers to subframe 1. |
| ContentToFrameMap subframe_content_map = {{1, 1}}; |
| impl.AddSubframeContent(3, CreateTestData(3, -1), subframe_content_map); |
| |
| // Frame 5 has content 3 which refers to subframe 3. |
| // Although frame 3's content is added, its subframe 1's content is not added. |
| // So frame 5 is not ready. |
| subframe_content_map = {{3, 3}}; |
| base::flat_set<uint64_t> pending_subframes; |
| EXPECT_FALSE( |
| impl.IsReadyToComposite(5, subframe_content_map, &pending_subframes)); |
| ASSERT_EQ(pending_subframes.size(), 1u); |
| EXPECT_EQ(*pending_subframes.begin(), 1u); |
| |
| // Frame 6 is not ready either since it needs frame 5 to be ready. |
| subframe_content_map = {{1, 5}}; |
| EXPECT_FALSE( |
| impl.IsReadyToComposite(6, subframe_content_map, &pending_subframes)); |
| ASSERT_EQ(pending_subframes.size(), 1u); |
| EXPECT_EQ(*pending_subframes.begin(), 5u); |
| |
| // When frame 1's content is added, frame 5 is ready. |
| impl.AddSubframeContent(1, CreateTestData(1, -1), ContentToFrameMap()); |
| subframe_content_map = {{3, 3}}; |
| EXPECT_TRUE( |
| impl.IsReadyToComposite(5, subframe_content_map, &pending_subframes)); |
| EXPECT_TRUE(pending_subframes.empty()); |
| |
| // Add frame 5's content. |
| impl.AddSubframeContent(5, CreateTestData(5, -1), subframe_content_map); |
| |
| // Frame 6 is ready too. |
| subframe_content_map = {{1, 5}}; |
| EXPECT_TRUE( |
| impl.IsReadyToComposite(6, subframe_content_map, &pending_subframes)); |
| EXPECT_TRUE(pending_subframes.empty()); |
| } |
| |
| TEST_F(PdfCompositorImplTest, DependencyLoop) { |
| PdfCompositorImpl impl("unittest", nullptr); |
| // Frame 3 has content 1, which refers to frame 1. |
| // Frame 1 has content 3, which refers to frame 3. |
| ContentToFrameMap subframe_content_map = {{3, 3}}; |
| impl.AddSubframeContent(1, CreateTestData(1, -1), subframe_content_map); |
| |
| subframe_content_map = {{1, 1}}; |
| impl.AddSubframeContent(3, CreateTestData(3, -1), subframe_content_map); |
| |
| // Both frame 1 and 3 are painted, frame 5 should be ready. |
| base::flat_set<uint64_t> pending_subframes; |
| subframe_content_map = {{1, 3}}; |
| EXPECT_TRUE( |
| impl.IsReadyToComposite(5, subframe_content_map, &pending_subframes)); |
| EXPECT_TRUE(pending_subframes.empty()); |
| |
| // Frame 6 has content 7, which refers to frame 7. |
| subframe_content_map = {{7, 7}}; |
| impl.AddSubframeContent(6, CreateTestData(6, -1), subframe_content_map); |
| // Frame 7 should be ready since frame 6's own content is added and it only |
| // depends on frame 7. |
| subframe_content_map = {{6u, 6u}}; |
| EXPECT_TRUE( |
| impl.IsReadyToComposite(7, subframe_content_map, &pending_subframes)); |
| EXPECT_TRUE(pending_subframes.empty()); |
| } |
| |
| TEST_F(PdfCompositorImplTest, MultiRequestsBasic) { |
| MockPdfCompositorImpl impl; |
| // Page 0 with frame 3 has content 1, which refers to frame 8. |
| // When the content is not available, the request is not fulfilled. |
| const ContentToFrameMap subframe_content_map = {{1, 8}}; |
| EXPECT_CALL(impl, OnFulfillRequest(testing::_, testing::_)).Times(0); |
| impl.CompositePageToPdf( |
| 3, CreateTestData(3, 0), subframe_content_map, |
| base::BindOnce(&PdfCompositorImplTest::OnCompositeToPdfCallback)); |
| testing::Mock::VerifyAndClearExpectations(&impl); |
| |
| // When frame 8's content is ready, the previous request should be fulfilled. |
| EXPECT_CALL(impl, OnFulfillRequest(3, 0)).Times(1); |
| impl.AddSubframeContent(8, CreateTestData(8, -1), ContentToFrameMap()); |
| testing::Mock::VerifyAndClearExpectations(&impl); |
| |
| // The following requests which only depends on frame 8 should be |
| // immediately fulfilled. |
| EXPECT_CALL(impl, OnFulfillRequest(3, 1)).Times(1); |
| EXPECT_CALL(impl, OnFulfillRequest(3, -1)).Times(1); |
| impl.CompositePageToPdf( |
| 3, CreateTestData(3, 1), subframe_content_map, |
| base::BindOnce(&PdfCompositorImplTest::OnCompositeToPdfCallback)); |
| |
| impl.CompositeDocumentToPdf( |
| 3, CreateTestData(3, -1), subframe_content_map, |
| base::BindOnce(&PdfCompositorImplTest::OnCompositeToPdfCallback)); |
| } |
| |
| TEST_F(PdfCompositorImplTest, MultiRequestsOrder) { |
| MockPdfCompositorImpl impl; |
| // Page 0 with frame 3 has content 1, which refers to frame 8. |
| // When the content is not available, the request is not fulfilled. |
| const ContentToFrameMap subframe_content_map = {{1, 8}}; |
| EXPECT_CALL(impl, OnFulfillRequest(testing::_, testing::_)).Times(0); |
| impl.CompositePageToPdf( |
| 3, CreateTestData(3, 0), subframe_content_map, |
| base::BindOnce(&PdfCompositorImplTest::OnCompositeToPdfCallback)); |
| |
| // The following requests which only depends on frame 8 should be |
| // immediately fulfilled. |
| impl.CompositePageToPdf( |
| 3, CreateTestData(3, 1), subframe_content_map, |
| base::BindOnce(&PdfCompositorImplTest::OnCompositeToPdfCallback)); |
| |
| impl.CompositeDocumentToPdf( |
| 3, CreateTestData(3, -1), subframe_content_map, |
| base::BindOnce(&PdfCompositorImplTest::OnCompositeToPdfCallback)); |
| testing::Mock::VerifyAndClearExpectations(&impl); |
| |
| // When frame 8's content is ready, the previous request should be |
| // fulfilled. |
| EXPECT_CALL(impl, OnFulfillRequest(3, 0)).Times(1); |
| EXPECT_CALL(impl, OnFulfillRequest(3, 1)).Times(1); |
| EXPECT_CALL(impl, OnFulfillRequest(3, -1)).Times(1); |
| impl.AddSubframeContent(8, CreateTestData(8, -1), ContentToFrameMap()); |
| } |
| |
| TEST_F(PdfCompositorImplTest, MultiRequestsDepOrder) { |
| MockPdfCompositorImpl impl; |
| // Page 0 with frame 1 has content 1, which refers to frame |
| // 2. When the content is not available, the request is not |
| // fulfilled. |
| EXPECT_CALL(impl, OnFulfillRequest(testing::_, testing::_)).Times(0); |
| ContentToFrameMap subframe_content_map = {{1, 2}}; |
| impl.CompositePageToPdf( |
| 1, CreateTestData(1, 0), subframe_content_map, |
| base::BindOnce(&PdfCompositorImplTest::OnCompositeToPdfCallback)); |
| |
| // Page 1 with frame 1 has content 1, which refers to frame |
| // 3. When the content is not available, the request is not |
| // fulfilled either. |
| subframe_content_map = {{1, 3}}; |
| impl.CompositePageToPdf( |
| 1, CreateTestData(1, 1), subframe_content_map, |
| base::BindOnce(&PdfCompositorImplTest::OnCompositeToPdfCallback)); |
| testing::Mock::VerifyAndClearExpectations(&impl); |
| |
| // When frame 3 and 2 become available, the pending requests should be |
| // satisfied, thus be fulfilled in order. |
| testing::Sequence order; |
| EXPECT_CALL(impl, OnFulfillRequest(1, 1)).Times(1).InSequence(order); |
| EXPECT_CALL(impl, OnFulfillRequest(1, 0)).Times(1).InSequence(order); |
| impl.AddSubframeContent(3, CreateTestData(3, -1), ContentToFrameMap()); |
| impl.AddSubframeContent(2, CreateTestData(2, -1), ContentToFrameMap()); |
| } |
| |
| TEST_F(PdfCompositorImplTest, NotifyUnavailableSubframe) { |
| MockPdfCompositorImpl impl; |
| // Page 0 with frame 3 has content 1, which refers to frame 8. |
| // When the content is not available, the request is not fulfilled. |
| const ContentToFrameMap subframe_content_map = {{1, 8}}; |
| EXPECT_CALL(impl, OnFulfillRequest(testing::_, testing::_)).Times(0); |
| impl.CompositePageToPdf( |
| 3, CreateTestData(3, 0), subframe_content_map, |
| base::BindOnce(&PdfCompositorImplTest::OnCompositeToPdfCallback)); |
| testing::Mock::VerifyAndClearExpectations(&impl); |
| |
| // Notifies that frame 8's unavailable, the previous request should be |
| // fulfilled. |
| EXPECT_CALL(impl, OnFulfillRequest(3, 0)).Times(1); |
| impl.NotifyUnavailableSubframe(8); |
| testing::Mock::VerifyAndClearExpectations(&impl); |
| } |
| |
| TEST_F(PdfCompositorImplCrashKeyTest, SetCrashKey) { |
| PdfCompositorImpl impl("unittest", nullptr); |
| std::string url_str("https://www.example.com/"); |
| GURL url(url_str); |
| impl.SetWebContentsURL(url); |
| |
| EXPECT_EQ(crash_reporter::GetCrashKeyValue("main-frame-url"), url_str); |
| } |
| |
| } // namespace printing |