| // 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 "chrome/browser/pdf/pdf_viewer_stream_manager.h" |
| |
| #include <memory> |
| |
| #include "base/memory/weak_ptr.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "chrome/browser/pdf/pdf_test_util.h" |
| #include "chrome/test/base/chrome_render_view_host_test_harness.h" |
| #include "content/public/browser/global_routing_id.h" |
| #include "content/public/browser/navigation_handle.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/mock_navigation_handle.h" |
| #include "content/public/test/navigation_simulator.h" |
| #include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h" |
| #include "pdf/pdf_features.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/mojom/loader/transferrable_url_loader.mojom.h" |
| #include "url/gurl.h" |
| |
| namespace pdf { |
| |
| namespace { |
| |
| using ::testing::NiceMock; |
| using ::testing::Return; |
| |
| constexpr char kOriginalUrl1[] = "https://original_url1"; |
| constexpr char kOriginalUrl2[] = "https://original_url2"; |
| |
| } // namespace |
| |
| class PdfViewerStreamManagerTest : public ChromeRenderViewHostTestHarness { |
| protected: |
| void SetUp() override { |
| ChromeRenderViewHostTestHarness::SetUp(); |
| |
| feature_list_.InitAndEnableFeature(chrome_pdf::features::kPdfOopif); |
| } |
| |
| void TearDown() override { |
| ChromeRenderViewHostTestHarness::web_contents()->RemoveUserData( |
| PdfViewerStreamManager::UserDataKey()); |
| ChromeRenderViewHostTestHarness::TearDown(); |
| } |
| |
| PdfViewerStreamManager* pdf_viewer_stream_manager() { |
| return PdfViewerStreamManager::FromWebContents( |
| ChromeRenderViewHostTestHarness::web_contents()); |
| } |
| |
| // Simulate a navigation and commit on `host`. The last committed URL will be |
| // `original_url`. |
| content::RenderFrameHost* NavigateAndCommit(content::RenderFrameHost* host, |
| const GURL& original_url) { |
| content::RenderFrameHost* new_host = |
| content::NavigationSimulator::NavigateAndCommitFromDocument( |
| original_url, host); |
| |
| // Create `PdfViewerStreamManager` if it doesn't exist already. If `host` is |
| // the primary main frame, then the previous `PdfViewerStreamManager` may |
| // have been deleted as part of the above navigation. |
| PdfViewerStreamManager::Create( |
| ChromeRenderViewHostTestHarness::web_contents()); |
| return new_host; |
| } |
| |
| content::RenderFrameHost* CreateChildRenderFrameHost( |
| content::RenderFrameHost* parent_host, |
| const std::string& frame_name) { |
| auto* parent_host_tester = content::RenderFrameHostTester::For(parent_host); |
| parent_host_tester->InitializeRenderFrameIfNeeded(); |
| return parent_host_tester->AppendChild(frame_name); |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| // Verify adding and getting an `extensions::StreamContainer`. |
| TEST_F(PdfViewerStreamManagerTest, AddAndGetStreamContainer) { |
| content::RenderFrameHost* embedder_host = |
| NavigateAndCommit(main_rfh(), GURL(kOriginalUrl1)); |
| content::FrameTreeNodeId frame_tree_node_id = |
| embedder_host->GetFrameTreeNodeId(); |
| |
| PdfViewerStreamManager* manager = pdf_viewer_stream_manager(); |
| manager->AddStreamContainer(frame_tree_node_id, "internal_id", |
| pdf_test_util::GenerateSampleStreamContainer(1)); |
| EXPECT_TRUE(manager->ContainsUnclaimedStreamInfo(frame_tree_node_id)); |
| manager->ClaimStreamInfoForTesting(embedder_host); |
| |
| base::WeakPtr<extensions::StreamContainer> result = |
| manager->GetStreamContainer(embedder_host); |
| |
| ASSERT_TRUE(result); |
| blink::mojom::TransferrableURLLoaderPtr transferrable_loader = |
| result->TakeTransferrableURLLoader(); |
| EXPECT_EQ(result->tab_id(), 1); |
| EXPECT_EQ(result->embedded(), true); |
| EXPECT_EQ(result->handler_url(), GURL("https://handler_url1")); |
| EXPECT_EQ(result->extension_id(), "extension_id1"); |
| EXPECT_EQ(transferrable_loader->url, GURL("stream://url1")); |
| EXPECT_EQ(transferrable_loader->head->mime_type, "application/pdf"); |
| EXPECT_EQ(result->original_url(), GURL("https://original_url1")); |
| EXPECT_TRUE(pdf_viewer_stream_manager()); |
| } |
| |
| // Verify adding an `extensions::StreamContainer` under the same frame tree node |
| // ID replaces the original unclaimed `extensions::StreamContainer`. |
| TEST_F(PdfViewerStreamManagerTest, |
| AddStreamContainerSameFrameTreeNodeIdUnclaimed) { |
| content::RenderFrameHost* embedder_host = |
| NavigateAndCommit(main_rfh(), GURL(kOriginalUrl2)); |
| content::FrameTreeNodeId frame_tree_node_id = |
| embedder_host->GetFrameTreeNodeId(); |
| |
| PdfViewerStreamManager* manager = pdf_viewer_stream_manager(); |
| manager->AddStreamContainer(frame_tree_node_id, "internal_id1", |
| pdf_test_util::GenerateSampleStreamContainer(1)); |
| manager->AddStreamContainer(frame_tree_node_id, "internal_id2", |
| pdf_test_util::GenerateSampleStreamContainer(2)); |
| manager->ClaimStreamInfoForTesting(embedder_host); |
| |
| base::WeakPtr<extensions::StreamContainer> result = |
| manager->GetStreamContainer(main_rfh()); |
| |
| ASSERT_TRUE(result); |
| blink::mojom::TransferrableURLLoaderPtr transferrable_loader = |
| result->TakeTransferrableURLLoader(); |
| EXPECT_EQ(result->tab_id(), 2); |
| EXPECT_EQ(result->embedded(), true); |
| EXPECT_EQ(result->handler_url(), GURL("https://handler_url2")); |
| EXPECT_EQ(result->extension_id(), "extension_id2"); |
| EXPECT_EQ(transferrable_loader->url, GURL("stream://url2")); |
| EXPECT_EQ(transferrable_loader->head->mime_type, "application/pdf"); |
| EXPECT_EQ(result->original_url(), GURL("https://original_url2")); |
| EXPECT_TRUE(pdf_viewer_stream_manager()); |
| } |
| |
| // Verify getting a `StreamContainer` with a non-matching URL returns nullptr; |
| TEST_F(PdfViewerStreamManagerTest, AddAndGetStreamInvalidURL) { |
| content::RenderFrameHost* embedder_host = |
| NavigateAndCommit(main_rfh(), GURL("https://nonmatching_url")); |
| |
| PdfViewerStreamManager* manager = pdf_viewer_stream_manager(); |
| manager->AddStreamContainer(embedder_host->GetFrameTreeNodeId(), |
| "internal_id", |
| pdf_test_util::GenerateSampleStreamContainer(1)); |
| manager->ClaimStreamInfoForTesting(embedder_host); |
| |
| EXPECT_FALSE(manager->GetStreamContainer(embedder_host)); |
| EXPECT_TRUE(pdf_viewer_stream_manager()); |
| } |
| |
| // Verify adding multiple `extensions::StreamContainer`s for different |
| // FrameTreeNodes at once. |
| TEST_F(PdfViewerStreamManagerTest, AddMultipleStreamContainers) { |
| auto* embedder_host = NavigateAndCommit(main_rfh(), GURL(kOriginalUrl1)); |
| auto* child_host = CreateChildRenderFrameHost(embedder_host, "child host"); |
| child_host = NavigateAndCommit(child_host, GURL(kOriginalUrl2)); |
| |
| PdfViewerStreamManager* manager = pdf_viewer_stream_manager(); |
| manager->AddStreamContainer(embedder_host->GetFrameTreeNodeId(), |
| "internal_id1", |
| pdf_test_util::GenerateSampleStreamContainer(1)); |
| manager->AddStreamContainer(child_host->GetFrameTreeNodeId(), "internal_id2", |
| pdf_test_util::GenerateSampleStreamContainer(2)); |
| manager->ClaimStreamInfoForTesting(embedder_host); |
| manager->ClaimStreamInfoForTesting(child_host); |
| |
| base::WeakPtr<extensions::StreamContainer> result = |
| manager->GetStreamContainer(embedder_host); |
| |
| ASSERT_TRUE(result); |
| blink::mojom::TransferrableURLLoaderPtr transferrable_loader = |
| result->TakeTransferrableURLLoader(); |
| EXPECT_EQ(result->tab_id(), 1); |
| EXPECT_EQ(result->embedded(), true); |
| EXPECT_EQ(result->handler_url(), GURL("https://handler_url1")); |
| EXPECT_EQ(result->extension_id(), "extension_id1"); |
| EXPECT_EQ(transferrable_loader->url, GURL("stream://url1")); |
| EXPECT_EQ(transferrable_loader->head->mime_type, "application/pdf"); |
| EXPECT_EQ(result->original_url(), GURL("https://original_url1")); |
| |
| result = manager->GetStreamContainer(child_host); |
| |
| ASSERT_TRUE(result); |
| transferrable_loader = result->TakeTransferrableURLLoader(); |
| EXPECT_EQ(result->tab_id(), 2); |
| EXPECT_EQ(result->embedded(), true); |
| EXPECT_EQ(result->handler_url(), GURL("https://handler_url2")); |
| EXPECT_EQ(result->extension_id(), "extension_id2"); |
| EXPECT_EQ(transferrable_loader->url, GURL("stream://url2")); |
| EXPECT_EQ(transferrable_loader->head->mime_type, "application/pdf"); |
| EXPECT_EQ(result->original_url(), GURL("https://original_url2")); |
| EXPECT_TRUE(pdf_viewer_stream_manager()); |
| } |
| |
| // `PdfViewerStreamManager::IsPdfExtensionHost()` should correctly identify the |
| // PDF extension hosts. |
| TEST_F(PdfViewerStreamManagerTest, IsPdfExtensionHost) { |
| auto* embedder_host = CreateChildRenderFrameHost(main_rfh(), "embedder host"); |
| embedder_host = NavigateAndCommit(embedder_host, GURL(kOriginalUrl1)); |
| |
| // In a PDF load, there's an RFH for the extension frame for the initial |
| // about:blank navigation. This RFH will always be replaced by |
| // `extension_host`. |
| auto* about_blank_host = |
| CreateChildRenderFrameHost(embedder_host, "extension host"); |
| auto* extension_host = |
| CreateChildRenderFrameHost(embedder_host, "extension host"); |
| auto* other_host = CreateChildRenderFrameHost(embedder_host, "other host"); |
| |
| PdfViewerStreamManager* manager = pdf_viewer_stream_manager(); |
| manager->AddStreamContainer(embedder_host->GetFrameTreeNodeId(), |
| "internal_id", |
| pdf_test_util::GenerateSampleStreamContainer(1)); |
| manager->ClaimStreamInfoForTesting(embedder_host); |
| ASSERT_TRUE(manager->GetStreamContainer(embedder_host)); |
| |
| // `about_blank_host` and `extension_host` should have the same frame tree |
| // node ID, but this isn't possible with the current test infrastructure. For |
| // testing purposes, it's okay to set the extension frame tree node ID to the |
| // initial RFH. |
| manager->SetExtensionFrameTreeNodeIdForTesting( |
| embedder_host, about_blank_host->GetFrameTreeNodeId()); |
| |
| // `about_blank_host` should be considered a PDF content host, even if it |
| // isn't navigating to the original PDF URL. |
| EXPECT_TRUE(manager->IsPdfExtensionHost(about_blank_host)); |
| |
| // Now, set the extension frame tree node ID to the actual extension frame |
| // tree node ID, which will be the same ID as `about_blank_host` in real |
| // situations. |
| manager->SetExtensionFrameTreeNodeIdForTesting( |
| embedder_host, extension_host->GetFrameTreeNodeId()); |
| |
| EXPECT_TRUE(manager->IsPdfExtensionHost(extension_host)); |
| |
| // Unrelated hosts shouldn't be considered PDF content hosts. |
| EXPECT_FALSE(manager->IsPdfExtensionHost(other_host)); |
| } |
| |
| // `PdfViewerStreamManager::IsPdfContentHost()` should correctly identify the |
| // PDF content hosts. |
| TEST_F(PdfViewerStreamManagerTest, IsPdfContentHost) { |
| const GURL pdf_url = GURL(kOriginalUrl1); |
| |
| auto* embedder_host = CreateChildRenderFrameHost(main_rfh(), "embedder host"); |
| embedder_host = NavigateAndCommit(embedder_host, pdf_url); |
| auto* extension_host = |
| CreateChildRenderFrameHost(embedder_host, "extension host"); |
| |
| // In a PDF load, there's an RFH for the content frame for the initial |
| // stream URL navigation. This RFH will always be replaced by |
| // `content_host`. |
| auto* stream_url_host = |
| CreateChildRenderFrameHost(extension_host, "content host"); |
| auto* content_host = |
| CreateChildRenderFrameHost(extension_host, "content host"); |
| content_host = NavigateAndCommit(content_host, pdf_url); |
| auto* other_host = CreateChildRenderFrameHost(extension_host, "other host"); |
| |
| PdfViewerStreamManager* manager = pdf_viewer_stream_manager(); |
| manager->AddStreamContainer(embedder_host->GetFrameTreeNodeId(), |
| "internal_id", |
| pdf_test_util::GenerateSampleStreamContainer(1)); |
| manager->ClaimStreamInfoForTesting(embedder_host); |
| manager->SetExtensionFrameTreeNodeIdForTesting( |
| embedder_host, extension_host->GetFrameTreeNodeId()); |
| ASSERT_TRUE(manager->GetStreamContainer(embedder_host)); |
| |
| // `stream_url_host` and `content_host` should have the same frame tree node |
| // ID, but this isn't possible with the current test infrastructure. For |
| // testing purposes, it's okay to set the content frame tree node ID to the |
| // initial RFH. |
| manager->SetContentFrameTreeNodeIdForTesting( |
| embedder_host, stream_url_host->GetFrameTreeNodeId()); |
| |
| // `stream_url_host` should be considered a PDF content host, even if it isn't |
| // navigating to the original PDF URL. |
| EXPECT_TRUE(manager->IsPdfContentHost(stream_url_host)); |
| |
| // Now, set the content frame tree node ID to the actual content frame tree |
| // node ID, which will be the same as `stream_url_host` in real situations. |
| manager->SetContentFrameTreeNodeIdForTesting( |
| embedder_host, content_host->GetFrameTreeNodeId()); |
| |
| EXPECT_TRUE(manager->IsPdfContentHost(content_host)); |
| |
| // Unrelated hosts shouldn't be considered PDF content hosts. |
| EXPECT_FALSE(manager->IsPdfContentHost(other_host)); |
| } |
| |
| // If multiple `extensions::StreamContainer`s exist, then deleting one stream |
| // shouldn't delete the other stream. |
| TEST_F(PdfViewerStreamManagerTest, DeleteWithMultipleStreamContainers) { |
| content::RenderFrameHost* embedder_host = |
| NavigateAndCommit(main_rfh(), GURL(kOriginalUrl1)); |
| auto* child_host = CreateChildRenderFrameHost(embedder_host, "child host"); |
| child_host = NavigateAndCommit(child_host, GURL(kOriginalUrl2)); |
| |
| PdfViewerStreamManager* manager = pdf_viewer_stream_manager(); |
| manager->AddStreamContainer(embedder_host->GetFrameTreeNodeId(), |
| "internal_id1", |
| pdf_test_util::GenerateSampleStreamContainer(1)); |
| manager->AddStreamContainer(child_host->GetFrameTreeNodeId(), "internal_id2", |
| pdf_test_util::GenerateSampleStreamContainer(2)); |
| manager->ClaimStreamInfoForTesting(embedder_host); |
| manager->ClaimStreamInfoForTesting(child_host); |
| ASSERT_TRUE(manager->GetStreamContainer(embedder_host)); |
| ASSERT_TRUE(manager->GetStreamContainer(child_host)); |
| |
| // `PdfViewerStreamManager::RenderFrameDeleted()` should cause the stream |
| // associated with `child_host` to be deleted. |
| manager->RenderFrameDeleted(child_host); |
| |
| EXPECT_TRUE(manager->GetStreamContainer(embedder_host)); |
| EXPECT_FALSE(manager->GetStreamContainer(child_host)); |
| EXPECT_TRUE(pdf_viewer_stream_manager()); |
| } |
| |
| // Verify that unclaimed stream infos can be deleted. |
| TEST_F(PdfViewerStreamManagerTest, DeleteUnclaimedStreamInfo) { |
| content::RenderFrameHost* unclaimed_embedder_host = |
| NavigateAndCommit(main_rfh(), GURL(kOriginalUrl1)); |
| content::FrameTreeNodeId frame_tree_node_id = |
| unclaimed_embedder_host->GetFrameTreeNodeId(); |
| |
| PdfViewerStreamManager* manager = pdf_viewer_stream_manager(); |
| manager->AddStreamContainer(frame_tree_node_id, "internal_id", |
| pdf_test_util::GenerateSampleStreamContainer(1)); |
| EXPECT_FALSE(manager->GetStreamContainer(unclaimed_embedder_host)); |
| |
| manager->DeleteUnclaimedStreamInfo(frame_tree_node_id); |
| |
| // There are no remaining streams, so `PdfViewerStreamManager` should delete |
| // itself. |
| EXPECT_FALSE(pdf_viewer_stream_manager()); |
| } |
| |
| // If the embedder render frame is deleted, the stream should be deleted. |
| TEST_F(PdfViewerStreamManagerTest, RenderFrameDeletedWithClaimedStream) { |
| auto* actual_host = CreateChildRenderFrameHost(main_rfh(), "actual host"); |
| actual_host = NavigateAndCommit(actual_host, GURL(kOriginalUrl1)); |
| |
| PdfViewerStreamManager* manager = pdf_viewer_stream_manager(); |
| manager->AddStreamContainer(actual_host->GetFrameTreeNodeId(), "internal_id", |
| pdf_test_util::GenerateSampleStreamContainer(1)); |
| manager->ClaimStreamInfoForTesting(actual_host); |
| ASSERT_TRUE(manager->GetStreamContainer(actual_host)); |
| |
| // Unrelated hosts should be ignored. |
| manager->RenderFrameDeleted(main_rfh()); |
| ASSERT_EQ(manager, pdf_viewer_stream_manager()); |
| |
| // `PdfViewerStreamManager::RenderFrameDeleted()` should cause the stream |
| // associated with `actual_host` to be deleted. |
| manager->RenderFrameDeleted(actual_host); |
| |
| // There are no remaining streams, so `PdfViewerStreamManager` should delete |
| // itself. |
| EXPECT_FALSE(pdf_viewer_stream_manager()); |
| } |
| |
| TEST_F(PdfViewerStreamManagerTest, RenderFrameDeletedWithUnclaimedStream) { |
| auto* actual_host = CreateChildRenderFrameHost(main_rfh(), "actual host"); |
| actual_host = NavigateAndCommit(actual_host, GURL(kOriginalUrl1)); |
| |
| PdfViewerStreamManager* manager = pdf_viewer_stream_manager(); |
| manager->AddStreamContainer(actual_host->GetFrameTreeNodeId(), "internal_id", |
| pdf_test_util::GenerateSampleStreamContainer(1)); |
| |
| // The stream hasn't been claimed, so the stream container can't be retrieved. |
| ASSERT_FALSE(manager->GetStreamContainer(actual_host)); |
| |
| // Unrelated hosts should be ignored. |
| manager->RenderFrameDeleted(main_rfh()); |
| ASSERT_EQ(manager, pdf_viewer_stream_manager()); |
| |
| // `PdfViewerStreamManager::RenderFrameDeleted()` should cause the stream |
| // associated with `actual_host` to be deleted. |
| manager->RenderFrameDeleted(actual_host); |
| |
| // There are no remaining streams, so `PdfViewerStreamManager` should delete |
| // itself. |
| EXPECT_FALSE(pdf_viewer_stream_manager()); |
| } |
| |
| // If the `content::RenderFrameHost` for the stream changes, then the stream |
| // should be deleted. |
| TEST_F(PdfViewerStreamManagerTest, EmbedderRenderFrameHostChanged) { |
| content::RenderFrameHost* old_host = |
| NavigateAndCommit(main_rfh(), GURL(kOriginalUrl1)); |
| auto* new_host = CreateChildRenderFrameHost(old_host, "new host"); |
| |
| PdfViewerStreamManager* manager = pdf_viewer_stream_manager(); |
| manager->AddStreamContainer(old_host->GetFrameTreeNodeId(), "internal_id", |
| pdf_test_util::GenerateSampleStreamContainer(1)); |
| manager->ClaimStreamInfoForTesting(old_host); |
| ASSERT_TRUE(manager->GetStreamContainer(old_host)); |
| |
| // If the first parameter to RenderFrameHostChanged() is null, then it means a |
| // subframe is being created and should be ignored. |
| manager->RenderFrameHostChanged(nullptr, old_host); |
| EXPECT_TRUE(manager->GetStreamContainer(old_host)); |
| |
| // Unrelated hosts should be ignored. |
| manager->RenderFrameHostChanged(new_host, new_host); |
| EXPECT_TRUE(manager->GetStreamContainer(old_host)); |
| |
| // There are no remaining streams, so `PdfViewerStreamManager` should delete |
| // itself. |
| manager->RenderFrameHostChanged(old_host, new_host); |
| EXPECT_FALSE(pdf_viewer_stream_manager()); |
| } |
| |
| // If the PDF extension host changes to a different host, the stream should be |
| // deleted. |
| TEST_F(PdfViewerStreamManagerTest, ExtensionRenderFrameHostChanged) { |
| auto* embedder_host = CreateChildRenderFrameHost(main_rfh(), "embedder host"); |
| embedder_host = NavigateAndCommit(embedder_host, GURL(kOriginalUrl1)); |
| |
| // In a PDF load, there's an RFH for the extension frame for the initial |
| // about:blank navigation. This RFH will always be replaced by |
| // `extension_host` and shouldn't trigger stream deletion. Both hosts should |
| // share the same frame name. |
| auto* about_blank_host = |
| CreateChildRenderFrameHost(embedder_host, "extension host"); |
| about_blank_host = NavigateAndCommit(about_blank_host, GURL("about:blank")); |
| auto* extension_host = |
| CreateChildRenderFrameHost(embedder_host, "extension host"); |
| auto* new_host = CreateChildRenderFrameHost(embedder_host, "new host"); |
| |
| PdfViewerStreamManager* manager = pdf_viewer_stream_manager(); |
| manager->AddStreamContainer(embedder_host->GetFrameTreeNodeId(), |
| "internal_id", |
| pdf_test_util::GenerateSampleStreamContainer(1)); |
| manager->ClaimStreamInfoForTesting(embedder_host); |
| ASSERT_TRUE(manager->GetStreamContainer(embedder_host)); |
| |
| // `about_blank_host` and `extension_host` should have the same frame tree |
| // node ID, but this isn't possible with the current test infrastructure. For |
| // testing purposes, it's okay to set the extension frame tree node ID to the |
| // initial RFH. |
| manager->SetExtensionFrameTreeNodeIdForTesting( |
| embedder_host, about_blank_host->GetFrameTreeNodeId()); |
| |
| // Changing `about_blank_host` to `extension_host` shouldn't delete the |
| // stream. |
| manager->RenderFrameHostChanged(about_blank_host, extension_host); |
| |
| ASSERT_TRUE(pdf_viewer_stream_manager()); |
| EXPECT_TRUE(manager->GetStreamContainer(embedder_host)); |
| |
| // Now, set the extension frame tree node ID to the actual extension frame |
| // tree node ID, which will be the same ID as `about_blank_host` in real |
| // situations. |
| manager->SetExtensionFrameTreeNodeIdForTesting( |
| embedder_host, extension_host->GetFrameTreeNodeId()); |
| |
| // Changing the extension host should delete the stream. |
| manager->RenderFrameHostChanged(extension_host, new_host); |
| |
| // There are no remaining streams, so `PdfViewerStreamManager` should delete |
| // itself. |
| EXPECT_FALSE(pdf_viewer_stream_manager()); |
| } |
| |
| // If the PDF content host changes to a different host, the stream should be |
| // deleted. |
| TEST_F(PdfViewerStreamManagerTest, ContentRenderFrameHostChanged) { |
| const GURL pdf_url = GURL(kOriginalUrl1); |
| |
| auto* embedder_host = CreateChildRenderFrameHost(main_rfh(), "embedder host"); |
| embedder_host = NavigateAndCommit(embedder_host, pdf_url); |
| auto* extension_host = |
| CreateChildRenderFrameHost(embedder_host, "extension host"); |
| |
| // In a PDF load, there's an RFH for the content frame for the initial |
| // stream URL navigation. This RFH will always be replaced by |
| // `content_host` and shouldn't trigger stream deletion. |
| auto* stream_url_host = |
| CreateChildRenderFrameHost(extension_host, "content host"); |
| auto* content_host = |
| CreateChildRenderFrameHost(extension_host, "content host"); |
| content_host = NavigateAndCommit(content_host, pdf_url); |
| auto* new_host = CreateChildRenderFrameHost(extension_host, "new host"); |
| |
| PdfViewerStreamManager* manager = pdf_viewer_stream_manager(); |
| manager->AddStreamContainer(embedder_host->GetFrameTreeNodeId(), |
| "internal_id", |
| pdf_test_util::GenerateSampleStreamContainer(1)); |
| manager->ClaimStreamInfoForTesting(embedder_host); |
| manager->SetExtensionFrameTreeNodeIdForTesting( |
| embedder_host, extension_host->GetFrameTreeNodeId()); |
| ASSERT_TRUE(manager->GetStreamContainer(embedder_host)); |
| |
| // The extension host needs to have the PDF extension origin so that |
| // `pdf_frame_util::GetEmbedderHost()` can identify and get the embedder host |
| // in `PdfViewerStreamManager::MaybeDeleteStreamOnPdfContentHostChanged()`. |
| content::OverrideLastCommittedOrigin( |
| extension_host, |
| url::Origin::Create(GURL( |
| "chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/index.html"))); |
| |
| // `stream_url_host` and `content_host` should have the same frame tree node |
| // ID, but this isn't possible with the current test infrastructure. For |
| // testing purposes, it's okay to set the content frame tree node ID to the |
| // initial RFH. |
| manager->SetContentFrameTreeNodeIdForTesting( |
| embedder_host, stream_url_host->GetFrameTreeNodeId()); |
| |
| // Changing `stream_url_host` to `content_host` shouldn't delete the stream. |
| manager->RenderFrameHostChanged(stream_url_host, content_host); |
| |
| ASSERT_TRUE(pdf_viewer_stream_manager()); |
| EXPECT_TRUE(manager->GetStreamContainer(embedder_host)); |
| |
| // Now, set the content frame tree node ID to the actual content frame tree |
| // node ID, which will be the same as `stream_url_host` in real situations. |
| manager->SetContentFrameTreeNodeIdForTesting( |
| embedder_host, content_host->GetFrameTreeNodeId()); |
| |
| // Changing the content host should delete the stream. |
| manager->RenderFrameHostChanged(content_host, new_host); |
| |
| // There are no remaining streams, so `PdfViewerStreamManager` should delete |
| // itself. |
| EXPECT_FALSE(pdf_viewer_stream_manager()); |
| } |
| |
| // If the `content::RenderFrameHost` for the stream is deleted, then the stream |
| // should be deleted. |
| TEST_F(PdfViewerStreamManagerTest, EmbedderFrameDeleted) { |
| content::RenderFrameHost* embedder_host = |
| NavigateAndCommit(main_rfh(), GURL(kOriginalUrl1)); |
| content::FrameTreeNodeId frame_tree_node_id = |
| embedder_host->GetFrameTreeNodeId(); |
| |
| PdfViewerStreamManager* manager = pdf_viewer_stream_manager(); |
| manager->AddStreamContainer(frame_tree_node_id, "internal_id", |
| pdf_test_util::GenerateSampleStreamContainer(1)); |
| manager->ClaimStreamInfoForTesting(embedder_host); |
| ASSERT_TRUE(manager->GetStreamContainer(embedder_host)); |
| |
| // There are no remaining streams, so `PdfViewerStreamManager` should delete |
| // itself. |
| manager->FrameDeleted(frame_tree_node_id); |
| EXPECT_FALSE(pdf_viewer_stream_manager()); |
| } |
| |
| // If the PDF extension frame is deleted, the stream should be deleted. |
| TEST_F(PdfViewerStreamManagerTest, ExtensionFrameDeleted) { |
| auto* embedder_host = CreateChildRenderFrameHost(main_rfh(), "actual host"); |
| embedder_host = NavigateAndCommit(embedder_host, GURL(kOriginalUrl1)); |
| auto* extension_host = |
| CreateChildRenderFrameHost(embedder_host, "extension host"); |
| content::FrameTreeNodeId frame_tree_node_id = |
| extension_host->GetFrameTreeNodeId(); |
| |
| PdfViewerStreamManager* manager = pdf_viewer_stream_manager(); |
| manager->AddStreamContainer(embedder_host->GetFrameTreeNodeId(), |
| "internal_id", |
| pdf_test_util::GenerateSampleStreamContainer(1)); |
| manager->ClaimStreamInfoForTesting(embedder_host); |
| ASSERT_TRUE(manager->GetStreamContainer(embedder_host)); |
| |
| // Set the extension frame tree node ID so the stream can be deleted when the |
| // extension host is deleted. |
| manager->SetExtensionFrameTreeNodeIdForTesting(embedder_host, |
| frame_tree_node_id); |
| |
| // Deleting the extension host should cause the stream to be deleted. |
| manager->FrameDeleted(frame_tree_node_id); |
| |
| // There are no remaining streams, so `PdfViewerStreamManager` should delete |
| // itself. |
| EXPECT_FALSE(pdf_viewer_stream_manager()); |
| } |
| |
| // If the PDF content frame is deleted, the stream should be deleted. |
| TEST_F(PdfViewerStreamManagerTest, ContentFrameDeleted) { |
| auto* embedder_host = CreateChildRenderFrameHost(main_rfh(), "embedder host"); |
| embedder_host = NavigateAndCommit(embedder_host, GURL(kOriginalUrl1)); |
| auto* extension_host = |
| CreateChildRenderFrameHost(embedder_host, "extension host"); |
| |
| auto* content_host = |
| CreateChildRenderFrameHost(extension_host, "content host"); |
| content::FrameTreeNodeId frame_tree_node_id = |
| content_host->GetFrameTreeNodeId(); |
| |
| PdfViewerStreamManager* manager = pdf_viewer_stream_manager(); |
| manager->AddStreamContainer(embedder_host->GetFrameTreeNodeId(), |
| "internal_id", |
| pdf_test_util::GenerateSampleStreamContainer(1)); |
| manager->ClaimStreamInfoForTesting(embedder_host); |
| ASSERT_TRUE(manager->GetStreamContainer(embedder_host)); |
| |
| // Set the content frame tree node ID so the stream can be deleted when the |
| // content host is deleted. |
| manager->SetContentFrameTreeNodeIdForTesting(embedder_host, |
| frame_tree_node_id); |
| |
| // Deleting the content host should cause the stream to be deleted. |
| manager->FrameDeleted(frame_tree_node_id); |
| |
| // There are no remaining streams, so `PdfViewerStreamManager` should delete |
| // itself. |
| EXPECT_FALSE(pdf_viewer_stream_manager()); |
| } |
| |
| // Starting the navigation for the content host should set the content host |
| // frame tree node ID. |
| TEST_F(PdfViewerStreamManagerTest, |
| DidStartNavigationSetContentHostFrameTreeNodeId) { |
| content::RenderFrameHost* embedder_host = |
| NavigateAndCommit(main_rfh(), GURL(kOriginalUrl1)); |
| auto* extension_host = |
| CreateChildRenderFrameHost(embedder_host, "extension host"); |
| auto* pdf_host = CreateChildRenderFrameHost(extension_host, "pdf host"); |
| content::FrameTreeNodeId content_frame_tree_node_id = |
| pdf_host->GetFrameTreeNodeId(); |
| |
| PdfViewerStreamManager* manager = pdf_viewer_stream_manager(); |
| manager->AddStreamContainer(embedder_host->GetFrameTreeNodeId(), |
| "internal_id", |
| pdf_test_util::GenerateSampleStreamContainer(1)); |
| manager->ClaimStreamInfoForTesting(embedder_host); |
| ASSERT_TRUE(manager->GetStreamContainer(embedder_host)); |
| |
| // Deleting the frame using frame tree node ID shouldn't trigger the stream |
| // deletion, since the content host frame tree node ID hasn't been set. |
| manager->FrameDeleted(pdf_host->GetFrameTreeNodeId()); |
| |
| ASSERT_TRUE(pdf_viewer_stream_manager()); |
| |
| NiceMock<content::MockNavigationHandle> navigation_handle(GURL(kOriginalUrl1), |
| pdf_host); |
| |
| // Set `navigation_handle`'s frame host to a grandchild frame host. This acts |
| // as the PDF frame host. |
| ON_CALL(navigation_handle, IsPdf).WillByDefault(Return(true)); |
| navigation_handle.set_render_frame_host(pdf_host); |
| |
| // Start the navigation. The content host frame tree node ID should now be |
| // set. |
| manager->DidStartNavigation(&navigation_handle); |
| |
| ASSERT_TRUE(pdf_viewer_stream_manager()); |
| |
| // Deleting the frame should now trigger stream deletion, as the content host |
| // frame tree node ID has been set. |
| manager->FrameDeleted(content_frame_tree_node_id); |
| |
| // There are no remaining streams, so `PdfViewerStreamManager` should delete |
| // itself. |
| EXPECT_FALSE(pdf_viewer_stream_manager()); |
| } |
| |
| // `PdfViewerStreamManager` should register a subresource |
| // override if the navigation handle is for a PDF content frame. |
| TEST_F(PdfViewerStreamManagerTest, ReadyToCommitNavigationSubresourceOverride) { |
| content::RenderFrameHost* embedder_host = |
| NavigateAndCommit(main_rfh(), GURL(kOriginalUrl1)); |
| auto* extension_host = |
| CreateChildRenderFrameHost(embedder_host, "extension host"); |
| auto* pdf_host = CreateChildRenderFrameHost(extension_host, "pdf host"); |
| |
| PdfViewerStreamManager* manager = pdf_viewer_stream_manager(); |
| manager->AddStreamContainer(embedder_host->GetFrameTreeNodeId(), |
| "internal_id", |
| pdf_test_util::GenerateSampleStreamContainer(1)); |
| manager->ClaimStreamInfoForTesting(embedder_host); |
| ASSERT_TRUE(manager->GetStreamContainer(embedder_host)); |
| |
| NiceMock<content::MockNavigationHandle> navigation_handle; |
| |
| // Set `navigation_handle`'s frame host to a grandchild frame host. This acts |
| // as the PDF frame host. |
| ON_CALL(navigation_handle, IsPdf).WillByDefault(Return(true)); |
| navigation_handle.set_render_frame_host(pdf_host); |
| navigation_handle.set_url(GURL("navigation_handle_url")); |
| |
| EXPECT_CALL(navigation_handle, RegisterSubresourceOverride); |
| manager->ReadyToCommitNavigation(&navigation_handle); |
| |
| // The stream should persist after the PDF load to provide postMessage support |
| // and PDF saving. |
| ASSERT_EQ(manager, pdf_viewer_stream_manager()); |
| EXPECT_TRUE(manager->GetStreamContainer(embedder_host)); |
| } |
| |
| // `PdfViewerStreamManager` should be able to handle registering multiple |
| // subresource override for multiple PDF streams. |
| TEST_F(PdfViewerStreamManagerTest, |
| ReadyToCommitNavigationSubresourceOverrideMultipleStreams) { |
| content::RenderFrameHost* embedder_host1 = |
| NavigateAndCommit(main_rfh(), GURL(kOriginalUrl1)); |
| auto* extension_host1 = |
| CreateChildRenderFrameHost(embedder_host1, "extension host1"); |
| auto* pdf_host1 = CreateChildRenderFrameHost(extension_host1, "pdf host"); |
| |
| auto* embedder_host2 = |
| CreateChildRenderFrameHost(embedder_host1, "embedder host2"); |
| embedder_host2 = NavigateAndCommit(embedder_host2, GURL(kOriginalUrl2)); |
| auto* extension_host2 = |
| CreateChildRenderFrameHost(embedder_host2, "extension host2"); |
| auto* pdf_host2 = CreateChildRenderFrameHost(extension_host2, "pdf host2"); |
| |
| PdfViewerStreamManager* manager = pdf_viewer_stream_manager(); |
| manager->AddStreamContainer(embedder_host1->GetFrameTreeNodeId(), |
| "internal_id1", |
| pdf_test_util::GenerateSampleStreamContainer(1)); |
| manager->AddStreamContainer(embedder_host2->GetFrameTreeNodeId(), |
| "internal_id2", |
| pdf_test_util::GenerateSampleStreamContainer(2)); |
| manager->ClaimStreamInfoForTesting(main_rfh()); |
| manager->ClaimStreamInfoForTesting(embedder_host2); |
| ASSERT_TRUE(manager->GetStreamContainer(main_rfh())); |
| ASSERT_TRUE(manager->GetStreamContainer(embedder_host2)); |
| |
| NiceMock<content::MockNavigationHandle> navigation_handle1; |
| |
| // Set `navigation_handle1`'s frame host to a grandchild frame host. This acts |
| // as the PDF frame host. |
| ON_CALL(navigation_handle1, IsPdf).WillByDefault(Return(true)); |
| navigation_handle1.set_render_frame_host(pdf_host1); |
| navigation_handle1.set_url(GURL("navigation_handle_url")); |
| |
| EXPECT_CALL(navigation_handle1, RegisterSubresourceOverride); |
| manager->ReadyToCommitNavigation(&navigation_handle1); |
| |
| NiceMock<content::MockNavigationHandle> navigation_handle2; |
| |
| // Set `navigation_handle2`'s frame host to a grandchild frame host. This acts |
| // as the PDF frame host. |
| ON_CALL(navigation_handle2, IsPdf).WillByDefault(Return(true)); |
| navigation_handle2.set_render_frame_host(pdf_host2); |
| navigation_handle2.set_url(GURL("navigation_handle_url")); |
| |
| EXPECT_CALL(navigation_handle2, RegisterSubresourceOverride); |
| manager->ReadyToCommitNavigation(&navigation_handle2); |
| |
| // The streams should persist after the PDF load to provide postMessage |
| // support and PDF saving. |
| ASSERT_EQ(manager, pdf_viewer_stream_manager()); |
| EXPECT_TRUE(manager->GetStreamContainer(embedder_host1)); |
| EXPECT_TRUE(manager->GetStreamContainer(embedder_host2)); |
| } |
| |
| // The initial load should claim the stream. |
| TEST_F(PdfViewerStreamManagerTest, ReadyToCommitNavigationClaimAndReplace) { |
| content::RenderFrameHost* embedder_host = |
| NavigateAndCommit(main_rfh(), GURL(kOriginalUrl1)); |
| PdfViewerStreamManager* manager = pdf_viewer_stream_manager(); |
| manager->AddStreamContainer(embedder_host->GetFrameTreeNodeId(), |
| "internal_id", |
| pdf_test_util::GenerateSampleStreamContainer(1)); |
| EXPECT_FALSE(manager->GetStreamContainer(embedder_host)); |
| |
| NiceMock<content::MockNavigationHandle> navigation_handle1; |
| navigation_handle1.set_render_frame_host(embedder_host); |
| |
| // The initial load should cause the embedder host to claim the stream. |
| manager->ReadyToCommitNavigation(&navigation_handle1); |
| base::WeakPtr<extensions::StreamContainer> original_stream = |
| manager->GetStreamContainer(embedder_host); |
| EXPECT_TRUE(original_stream); |
| EXPECT_TRUE(pdf_viewer_stream_manager()); |
| |
| NiceMock<content::MockNavigationHandle> navigation_handle2; |
| navigation_handle2.set_render_frame_host(embedder_host); |
| |
| // Committing a navigation again shouldn't try to claim a stream again if |
| // there isn't a new stream. The stream should remain the same. This can occur |
| // if a page contains an embed to a PDF, and the embed later navigates to |
| // another URL. |
| manager->ReadyToCommitNavigation(&navigation_handle2); |
| base::WeakPtr<extensions::StreamContainer> same_stream = |
| manager->GetStreamContainer(embedder_host); |
| ASSERT_TRUE(original_stream); |
| ASSERT_TRUE(same_stream); |
| EXPECT_EQ(original_stream.get(), same_stream.get()); |
| EXPECT_TRUE(pdf_viewer_stream_manager()); |
| |
| // Re-add a duplicate stream. |
| manager->AddStreamContainer(embedder_host->GetFrameTreeNodeId(), |
| "internal_id", |
| pdf_test_util::GenerateSampleStreamContainer(1)); |
| |
| NiceMock<content::MockNavigationHandle> navigation_handle3; |
| navigation_handle3.set_render_frame_host(embedder_host); |
| |
| // If a new stream exists for the same frame tree node ID, allow claiming the |
| // new stream. This can occur if a full page PDF viewer refreshes. |
| manager->ReadyToCommitNavigation(&navigation_handle3); |
| EXPECT_TRUE(manager->GetStreamContainer(embedder_host)); |
| EXPECT_FALSE(original_stream); |
| EXPECT_TRUE(pdf_viewer_stream_manager()); |
| } |
| |
| // If the PDF URL is reloaded during a PDF load, `PdfViewerStreamManager` should |
| // ignore the initial PDF content frame navigation. |
| TEST_F(PdfViewerStreamManagerTest, |
| DidFinishNavigationReloadOverlappingNavigations) { |
| const GURL pdf_url(kOriginalUrl1); |
| |
| // Set up the first PDF load and initial PDF content navigation handle. |
| content::RenderFrameHost* embedder_host = |
| NavigateAndCommit(main_rfh(), pdf_url); |
| auto* extension_host1 = |
| CreateChildRenderFrameHost(embedder_host, "extension host1"); |
| auto* pdf_host1 = CreateChildRenderFrameHost(extension_host1, "pdf host1"); |
| |
| PdfViewerStreamManager* manager = pdf_viewer_stream_manager(); |
| manager->AddStreamContainer(embedder_host->GetFrameTreeNodeId(), |
| "internal_id1", |
| pdf_test_util::GenerateSampleStreamContainer(1)); |
| manager->ClaimStreamInfoForTesting(embedder_host); |
| ASSERT_TRUE(manager->GetStreamContainer(embedder_host)); |
| |
| manager->SetContentFrameTreeNodeIdForTesting(embedder_host, |
| pdf_host1->GetFrameTreeNodeId()); |
| |
| NiceMock<content::MockNavigationHandle> navigation_handle1(pdf_url, |
| pdf_host1); |
| ON_CALL(navigation_handle1, IsPdf).WillByDefault(Return(true)); |
| |
| // Before processing the initial PDF content navigation handle, "reload" the |
| // URL. The embedder host will stay the same, but there will be a new PDF |
| // content frame and navigation handle. The stream info will be overridden. |
| // Do not use `NavigateAndCommit()` here, as it would delete `manager` (which |
| // does not happen in production code). |
| auto* extension_host2 = |
| CreateChildRenderFrameHost(embedder_host, "extension host2"); |
| auto* pdf_host2 = CreateChildRenderFrameHost(extension_host2, "pdf host2"); |
| manager->AddStreamContainer(embedder_host->GetFrameTreeNodeId(), |
| "internal_id1", |
| pdf_test_util::GenerateSampleStreamContainer(1)); |
| manager->ClaimStreamInfoForTesting(embedder_host); |
| ASSERT_TRUE(manager->GetStreamContainer(embedder_host)); |
| |
| manager->SetContentFrameTreeNodeIdForTesting(embedder_host, |
| pdf_host2->GetFrameTreeNodeId()); |
| |
| NiceMock<content::MockNavigationHandle> navigation_handle2(pdf_url, |
| pdf_host2); |
| ON_CALL(navigation_handle2, IsPdf).WillByDefault(Return(true)); |
| |
| EXPECT_FALSE(manager->DidPdfContentNavigate(embedder_host)); |
| |
| // The initial PDF content navigation should not update any stream state. |
| manager->DidFinishNavigation(&navigation_handle1); |
| EXPECT_FALSE(manager->DidPdfContentNavigate(embedder_host)); |
| |
| // The new, correct PDF content navigation should update the stream state. |
| manager->DidFinishNavigation(&navigation_handle2); |
| EXPECT_TRUE(manager->DidPdfContentNavigate(embedder_host)); |
| } |
| |
| } // namespace pdf |