// 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 #include #include "base/auto_reset.h" #include "base/bind.h" #include "base/files/file_path.h" #include "base/optional.h" #include "base/path_service.h" #include "base/run_loop.h" #include "base/strings/stringprintf.h" #include "base/threading/thread_restrictions.h" #include "build/build_config.h" #include "chrome/browser/extensions/extension_browsertest.h" #include "chrome/browser/printing/print_job_manager.h" #include "chrome/browser/printing/print_view_manager_base.h" #include "chrome/browser/printing/print_view_manager_common.h" #include "chrome/browser/printing/printer_query.h" #include "chrome/browser/printing/printing_message_filter.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/browser/ui/webui/print_preview/print_preview_ui.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/pref_names.h" #include "chrome/common/webui_url_constants.h" #include "chrome/test/base/in_process_browser_test.h" #include "chrome/test/base/ui_test_utils.h" #include "components/prefs/pref_service.h" #include "components/printing/browser/print_composite_client.h" #include "components/printing/browser/print_manager_utils.h" #include "components/printing/common/print.mojom-test-utils.h" #include "components/printing/common/print.mojom.h" #include "components/printing/common/print_messages.h" #include "content/public/browser/browser_message_filter.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" #include "content/public/common/content_features.h" #include "content/public/common/content_switches.h" #include "content/public/test/browser_test.h" #include "content/public/test/browser_test_utils.h" #include "extensions/common/extension.h" #include "mojo/public/cpp/bindings/associated_remote.h" #include "net/dns/mock_host_resolver.h" #include "net/test/embedded_test_server/embedded_test_server.h" #include "printing/mojom/print.mojom.h" #include "testing/gmock/include/gmock/gmock.h" #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h" #include "third_party/blink/public/common/scheduler/web_scheduler_tracked_feature.h" namespace printing { namespace { constexpr int kDefaultDocumentCookie = 1234; mojom::PrintParamsPtr GetPrintParams() { auto params = mojom::PrintParams::New(); params->page_size = gfx::Size(612, 792); params->content_size = gfx::Size(540, 720); params->printable_area = gfx::Rect(612, 792); params->dpi = gfx::Size(72, 72); params->document_cookie = kDefaultDocumentCookie; params->pages_per_sheet = 4; params->printed_doc_type = IsOopifEnabled() ? mojom::SkiaDocumentType::kMSKP : mojom::SkiaDocumentType::kPDF; return params; } void UpdatePrintSettingsReplyOnIO( scoped_refptr queue, std::unique_ptr printer_query, mojom::PrintManagerHost::UpdatePrintSettingsCallback callback) { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); DCHECK(printer_query); auto params = mojom::PrintPagesParams::New(); params->params = mojom::PrintParams::New(); if (printer_query->last_status() == PrintingContext::OK) { RenderParamsFromPrintSettings(printer_query->settings(), params->params.get()); params->params->document_cookie = printer_query->cookie(); params->pages = PageRange::GetPages(printer_query->settings().ranges()); } bool canceled = printer_query->last_status() == PrintingContext::CANCEL; params->params = GetPrintParams(); content::GetUIThreadTaskRunner({})->PostTask( FROM_HERE, base::BindOnce( [](mojom::PrintManagerHost::UpdatePrintSettingsCallback callback, mojom::PrintPagesParamsPtr params, bool canceled) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); std::move(callback).Run(std::move(params), canceled); }, std::move(callback), std::move(params), canceled)); if (printer_query->cookie() && printer_query->settings().dpi()) { queue->QueuePrinterQuery(std::move(printer_query)); } else { printer_query->StopWorker(); } } void UpdatePrintSettingsOnIO( int32_t cookie, mojom::PrintManagerHost::UpdatePrintSettingsCallback callback, scoped_refptr queue, base::Value job_settings) { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); std::unique_ptr printer_query = queue->PopPrinterQuery(cookie); if (!printer_query) { printer_query = queue->CreatePrinterQuery( content::ChildProcessHost::kInvalidUniqueID, MSG_ROUTING_NONE); } auto* printer_query_ptr = printer_query.get(); printer_query_ptr->SetSettings( std::move(job_settings), base::BindOnce(&UpdatePrintSettingsReplyOnIO, queue, std::move(printer_query), std::move(callback))); } class PrintPreviewObserver : PrintPreviewUI::TestDelegate { public: explicit PrintPreviewObserver(bool wait_for_loaded) { if (wait_for_loaded) queue_.emplace(); // DOMMessageQueue doesn't allow assignment PrintPreviewUI::SetDelegateForTesting(this); } ~PrintPreviewObserver() override { PrintPreviewUI::SetDelegateForTesting(nullptr); } void WaitUntilPreviewIsReady() { if (rendered_page_count_ >= total_page_count_) return; base::RunLoop run_loop; base::AutoReset auto_reset(&run_loop_, &run_loop); run_loop.Run(); if (queue_.has_value()) { std::string message; EXPECT_TRUE(queue_->WaitForMessage(&message)); EXPECT_EQ("\"success\"", message); } } content::WebContents* GetPrintPreviewDialog() { return preview_dialog_; } private: // PrintPreviewUI::TestDelegate: void DidGetPreviewPageCount(uint32_t page_count) override { total_page_count_ = page_count; } // PrintPreviewUI::TestDelegate: void DidRenderPreviewPage(content::WebContents* preview_dialog) override { ++rendered_page_count_; CHECK(rendered_page_count_ <= total_page_count_); if (rendered_page_count_ == total_page_count_ && run_loop_) { run_loop_->Quit(); preview_dialog_ = preview_dialog; if (queue_.has_value()) { content::ExecuteScriptAsync( preview_dialog, "window.addEventListener('message', event => {" " if (event.data.type === 'documentLoaded') {" " domAutomationController.send(event.data.load_state);" " }" "});"); } } } base::Optional queue_; uint32_t total_page_count_ = 1; uint32_t rendered_page_count_ = 0; content::WebContents* preview_dialog_ = nullptr; base::RunLoop* run_loop_ = nullptr; DISALLOW_COPY_AND_ASSIGN(PrintPreviewObserver); }; class TestPrintRenderFrame : public mojom::PrintRenderFrameInterceptorForTesting { public: TestPrintRenderFrame(content::RenderFrameHost* frame_host, content::WebContents* web_contents, int document_cookie, base::RepeatingClosure msg_callback) : frame_host_(frame_host), web_contents_(web_contents), document_cookie_(document_cookie), task_runner_(base::SequencedTaskRunnerHandle::Get()), msg_callback_(msg_callback) {} ~TestPrintRenderFrame() override = default; void OnDidPrintFrameContent(int document_cookie, mojom::DidPrintContentParamsPtr param, PrintFrameContentCallback callback) const { EXPECT_EQ(document_cookie, document_cookie_); ASSERT_TRUE(param->metafile_data_region.IsValid()); EXPECT_GT(param->metafile_data_region.GetSize(), 0U); task_runner_->PostTask(FROM_HERE, msg_callback_); std::move(callback).Run(document_cookie, std::move(param)); } void Bind(mojo::ScopedInterfaceEndpointHandle handle) { receiver_.Bind(mojo::PendingAssociatedReceiver( std::move(handle))); } // mojom::PrintRenderFrameInterceptorForTesting mojom::PrintRenderFrame* GetForwardingInterface() override { NOTREACHED(); return nullptr; } void PrintFrameContent(mojom::PrintFrameContentParamsPtr params, PrintFrameContentCallback callback) override { // Sends the printed result back. mojom::DidPrintContentParamsPtr printed_frame_params = mojom::DidPrintContentParams::New(); // Creates a small amount of region to avoid passing empty data to mojo. constexpr size_t kSize = 10; base::MappedReadOnlyRegion region_mapping = base::ReadOnlySharedMemoryRegion::Create(kSize); printed_frame_params->metafile_data_region = std::move(region_mapping.region); OnDidPrintFrameContent(params->document_cookie, std::move(printed_frame_params), std::move(callback)); auto* client = PrintCompositeClient::FromWebContents(web_contents_); if (!client) return; // Prints its children. content::RenderFrameHost* child = ChildFrameAt(frame_host_, 0); for (size_t i = 1; child; i++) { if (child->GetSiteInstance() != frame_host_->GetSiteInstance()) { client->PrintCrossProcessSubframe(gfx::Rect(), params->document_cookie, child); } child = ChildFrameAt(frame_host_, i); } } private: content::RenderFrameHost* frame_host_; content::WebContents* web_contents_; const int document_cookie_; scoped_refptr task_runner_; base::RepeatingClosure msg_callback_; mojo::AssociatedReceiver receiver_{this}; }; class KillPrintRenderFrame : public mojom::PrintRenderFrameInterceptorForTesting { public: explicit KillPrintRenderFrame(content::RenderProcessHost* rph) : rph_(rph) {} ~KillPrintRenderFrame() override = default; void OverrideBinderForTesting(content::RenderFrameHost* render_frame_host) { render_frame_host->GetRemoteAssociatedInterfaces() ->OverrideBinderForTesting( mojom::PrintRenderFrame::Name_, base::BindRepeating(&KillPrintRenderFrame::Bind, base::Unretained(this))); } void KillRenderProcess(int document_cookie, mojom::DidPrintContentParamsPtr param, PrintFrameContentCallback callback) const { std::move(callback).Run(document_cookie, std::move(param)); rph_->Shutdown(0); } void Bind(mojo::ScopedInterfaceEndpointHandle handle) { receiver_.Bind(mojo::PendingAssociatedReceiver( std::move(handle))); } // mojom::PrintRenderFrameInterceptorForTesting mojom::PrintRenderFrame* GetForwardingInterface() override { NOTREACHED(); return nullptr; } void PrintFrameContent(mojom::PrintFrameContentParamsPtr params, PrintFrameContentCallback callback) override { // Sends the printed result back. const size_t kSize = 10; mojom::DidPrintContentParamsPtr printed_frame_params = mojom::DidPrintContentParams::New(); base::MappedReadOnlyRegion region_mapping = base::ReadOnlySharedMemoryRegion::Create(kSize); printed_frame_params->metafile_data_region = std::move(region_mapping.region); KillRenderProcess(params->document_cookie, std::move(printed_frame_params), std::move(callback)); } private: content::RenderProcessHost* const rph_; mojo::AssociatedReceiver receiver_{this}; }; } // namespace class TestPrintViewManager : public PrintViewManagerBase { public: explicit TestPrintViewManager(content::WebContents* web_contents) : PrintViewManagerBase(web_contents) {} TestPrintViewManager(const TestPrintViewManager&) = delete; TestPrintViewManager& operator=(const TestPrintViewManager&) = delete; ~TestPrintViewManager() override = default; private: // printing::mojom::PrintManagerHost: void UpdatePrintSettings(int32_t cookie, base::Value job_settings, UpdatePrintSettingsCallback callback) override { content::GetIOThreadTaskRunner({})->PostTask( FROM_HERE, base::BindOnce(&UpdatePrintSettingsOnIO, cookie, std::move(callback), queue_, std::move(job_settings))); } }; class PrintBrowserTest : public InProcessBrowserTest { public: PrintBrowserTest() = default; ~PrintBrowserTest() override = default; void SetUp() override { num_expected_messages_ = 1; // By default, only wait on one message. num_received_messages_ = 0; InProcessBrowserTest::SetUp(); } void SetUpOnMainThread() override { host_resolver()->AddRule("*", "127.0.0.1"); content::SetupCrossSiteRedirector(embedded_test_server()); ASSERT_TRUE(embedded_test_server()->Start()); } void PrintAndWaitUntilPreviewIsReady(bool print_only_selection) { PrintPreviewObserver print_preview_observer(/*wait_for_loaded=*/false); StartPrint(browser()->tab_strip_model()->GetActiveWebContents(), /*print_renderer=*/mojo::NullAssociatedRemote(), /*print_preview_disabled=*/false, print_only_selection); print_preview_observer.WaitUntilPreviewIsReady(); } void PrintAndWaitUntilPreviewIsReadyAndLoaded(bool print_only_selection) { PrintPreviewObserver print_preview_observer(/*wait_for_loaded=*/true); StartPrint(browser()->tab_strip_model()->GetActiveWebContents(), /*print_renderer=*/mojo::NullAssociatedRemote(), /*print_preview_disabled=*/false, print_only_selection); print_preview_observer.WaitUntilPreviewIsReady(); } // The following are helper functions for having a wait loop in the test and // exit when all expected messages are received. void SetNumExpectedMessages(unsigned int num) { num_expected_messages_ = num; } void WaitUntilCallbackReceived() { base::RunLoop run_loop; quit_callback_ = run_loop.QuitClosure(); run_loop.Run(); } void CheckForQuit() { if (++num_received_messages_ != num_expected_messages_) return; if (quit_callback_) std::move(quit_callback_).Run(); } void CreateTestPrintRenderFrame(content::RenderFrameHost* frame_host, content::WebContents* web_contents) { frame_content_.emplace( frame_host, std::make_unique( frame_host, web_contents, kDefaultDocumentCookie, base::BindRepeating(&PrintBrowserTest::CheckForQuit, base::Unretained(this)))); OverrideBinderForTesting(frame_host); } static mojom::PrintFrameContentParamsPtr GetDefaultPrintFrameParams() { return mojom::PrintFrameContentParams::New(gfx::Rect(800, 600), kDefaultDocumentCookie); } const mojo::AssociatedRemote& GetPrintRenderFrame( content::RenderFrameHost* rfh) { if (!remote_) rfh->GetRemoteAssociatedInterfaces()->GetInterface(&remote_); return remote_; } private: TestPrintRenderFrame* GetFrameContent(content::RenderFrameHost* host) const { auto iter = frame_content_.find(host); return iter != frame_content_.end() ? iter->second.get() : nullptr; } void OverrideBinderForTesting(content::RenderFrameHost* render_frame_host) { render_frame_host->GetRemoteAssociatedInterfaces() ->OverrideBinderForTesting( mojom::PrintRenderFrame::Name_, base::BindRepeating( &TestPrintRenderFrame::Bind, base::Unretained(GetFrameContent(render_frame_host)))); } unsigned int num_expected_messages_; unsigned int num_received_messages_; base::OnceClosure quit_callback_; mojo::AssociatedRemote remote_; std::map> frame_content_; }; class SitePerProcessPrintBrowserTest : public PrintBrowserTest { public: SitePerProcessPrintBrowserTest() = default; ~SitePerProcessPrintBrowserTest() override = default; // content::BrowserTestBase void SetUpCommandLine(base::CommandLine* command_line) override { content::IsolateAllSitesForTesting(command_line); } }; class IsolateOriginsPrintBrowserTest : public PrintBrowserTest { public: static constexpr char kIsolatedSite[] = "b.com"; IsolateOriginsPrintBrowserTest() = default; ~IsolateOriginsPrintBrowserTest() override = default; // content::BrowserTestBase void SetUpCommandLine(base::CommandLine* command_line) override { ASSERT_TRUE(embedded_test_server()->Start()); std::string origin_list = embedded_test_server()->GetURL(kIsolatedSite, "/").spec(); command_line->AppendSwitchASCII(switches::kIsolateOrigins, origin_list); } void SetUpOnMainThread() override { host_resolver()->AddRule("*", "127.0.0.1"); } }; class BackForwardCachePrintBrowserTest : public PrintBrowserTest { public: BackForwardCachePrintBrowserTest() = default; BackForwardCachePrintBrowserTest(const BackForwardCachePrintBrowserTest&) = delete; BackForwardCachePrintBrowserTest& operator=( const BackForwardCachePrintBrowserTest&) = delete; ~BackForwardCachePrintBrowserTest() override = default; void SetUpCommandLine(base::CommandLine* command_line) override { scoped_feature_list_.InitAndEnableFeatureWithParameters( ::features::kBackForwardCache, { // Set a very long TTL before expiration (longer than the test // timeout) so tests that are expecting deletion don't pass when // they shouldn't. {"TimeToLiveInBackForwardCacheInSeconds", "3600"}, }); PrintBrowserTest::SetUpCommandLine(command_line); } content::WebContents* web_contents() const { return browser()->tab_strip_model()->GetActiveWebContents(); } content::RenderFrameHost* current_frame_host() { return web_contents()->GetMainFrame(); } void ExpectBlocklistedFeature( blink::scheduler::WebSchedulerTrackedFeature feature, base::Location location) { base::HistogramBase::Sample sample = base::HistogramBase::Sample(feature); AddSampleToBuckets(&expected_blocklisted_features_, sample); EXPECT_THAT( histogram_tester_.GetAllSamples( "BackForwardCache.HistoryNavigationOutcome." "BlocklistedFeature"), testing::UnorderedElementsAreArray(expected_blocklisted_features_)) << location.ToString(); EXPECT_THAT( histogram_tester_.GetAllSamples( "BackForwardCache.AllSites.HistoryNavigationOutcome." "BlocklistedFeature"), testing::UnorderedElementsAreArray(expected_blocklisted_features_)) << location.ToString(); } private: void AddSampleToBuckets(std::vector* buckets, base::HistogramBase::Sample sample) { auto it = std::find_if( buckets->begin(), buckets->end(), [sample](const base::Bucket& bucket) { return bucket.min == sample; }); if (it == buckets->end()) { buckets->push_back(base::Bucket(sample, 1)); } else { it->count++; } } base::HistogramTester histogram_tester_; std::vector expected_blocklisted_features_; base::test::ScopedFeatureList scoped_feature_list_; }; constexpr char IsolateOriginsPrintBrowserTest::kIsolatedSite[]; class PrintExtensionBrowserTest : public extensions::ExtensionBrowserTest { public: PrintExtensionBrowserTest() = default; ~PrintExtensionBrowserTest() override = default; void PrintAndWaitUntilPreviewIsReady(bool print_only_selection) { PrintPreviewObserver print_preview_observer(/*wait_for_loaded=*/false); StartPrint(browser()->tab_strip_model()->GetActiveWebContents(), /*print_renderer=*/mojo::NullAssociatedRemote(), /*print_preview_disabled=*/false, print_only_selection); print_preview_observer.WaitUntilPreviewIsReady(); } void LoadExtensionAndNavigateToOptionPage() { const extensions::Extension* extension = nullptr; { base::ScopedAllowBlockingForTesting allow_blocking; base::FilePath test_data_dir; base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir); extension = LoadExtension( test_data_dir.AppendASCII("printing").AppendASCII("test_extension")); ASSERT_TRUE(extension); } GURL url(chrome::kChromeUIExtensionsURL); std::string query = base::StringPrintf("options=%s", extension->id().c_str()); GURL::Replacements replacements; replacements.SetQueryStr(query); url = url.ReplaceComponents(replacements); ui_test_utils::NavigateToURL(browser(), url); } }; class SitePerProcessPrintExtensionBrowserTest : public PrintExtensionBrowserTest { public: // content::BrowserTestBase void SetUpCommandLine(base::CommandLine* command_line) override { content::IsolateAllSitesForTesting(command_line); } }; // Printing only a selection containing iframes is partially supported. // Iframes aren't currently displayed. This test passes whenever the print // preview is rendered (i.e. no timeout in the test). // This test shouldn't crash. See https://crbug.com/732780. IN_PROC_BROWSER_TEST_F(PrintBrowserTest, SelectionContainsIframe) { ASSERT_TRUE(embedded_test_server()->Started()); GURL url(embedded_test_server()->GetURL("/printing/selection_iframe.html")); ui_test_utils::NavigateToURL(browser(), url); PrintAndWaitUntilPreviewIsReady(/*print_only_selection=*/true); } // https://crbug.com/1125972 // https://crbug.com/1131598 IN_PROC_BROWSER_TEST_F(PrintBrowserTest, NoScrolling) { ASSERT_TRUE(embedded_test_server()->Started()); GURL url(embedded_test_server()->GetURL("/printing/with-scrollable.html")); ui_test_utils::NavigateToURL(browser(), url); auto* contents = browser()->tab_strip_model()->GetActiveWebContents(); const char kExpression1[] = "iframe.contentWindow.scrollY"; const char kExpression2[] = "scrollable.scrollTop"; const char kExpression3[] = "shapeshifter.scrollTop"; double old_scroll1 = content::EvalJs(contents, kExpression1).ExtractDouble(); double old_scroll2 = content::EvalJs(contents, kExpression2).ExtractDouble(); double old_scroll3 = content::EvalJs(contents, kExpression3).ExtractDouble(); PrintAndWaitUntilPreviewIsReady(/*print_only_selection=*/false); double new_scroll1 = content::EvalJs(contents, kExpression1).ExtractDouble(); // TODO(crbug.com/1131598): Perform the corresponding EvalJs() calls here and // assign to new_scroll2 and new_scroll3, once the printing code has been // fixed to handle these cases. Right now, the scroll offset jumps. double new_scroll2 = old_scroll2; double new_scroll3 = old_scroll3; EXPECT_EQ(old_scroll1, new_scroll1); EXPECT_EQ(old_scroll2, new_scroll2); EXPECT_EQ(old_scroll3, new_scroll3); } // https://crbug.com/1131598 IN_PROC_BROWSER_TEST_F(PrintBrowserTest, DISABLED_NoScrollingFrameset) { ASSERT_TRUE(embedded_test_server()->Started()); GURL url(embedded_test_server()->GetURL("/printing/frameset.html")); ui_test_utils::NavigateToURL(browser(), url); auto* contents = browser()->tab_strip_model()->GetActiveWebContents(); const char kExpression[] = "document.getElementById('frame').contentWindow.scrollY"; double old_scroll = content::EvalJs(contents, kExpression).ExtractDouble(); PrintAndWaitUntilPreviewIsReady(/*print_only_selection=*/false); double new_scroll = content::EvalJs(contents, kExpression).ExtractDouble(); EXPECT_EQ(old_scroll, new_scroll); } // https://crbug.com/1125972 IN_PROC_BROWSER_TEST_F(PrintBrowserTest, NoScrollingVerticalRl) { ASSERT_TRUE(embedded_test_server()->Started()); GURL url(embedded_test_server()->GetURL("/printing/vertical-rl.html")); ui_test_utils::NavigateToURL(browser(), url); PrintAndWaitUntilPreviewIsReady(/*print_only_selection=*/false); // Test that entering print preview didn't mess up the scroll position. EXPECT_EQ( 0, content::EvalJs(browser()->tab_strip_model()->GetActiveWebContents(), "window.scrollX")); } // Before invoking print preview, page scale is changed to a different value. // Test that when print preview is ready, in other words when printing is // finished, the page scale factor gets reset to initial scale. IN_PROC_BROWSER_TEST_F(PrintBrowserTest, ResetPageScaleAfterPrintPreview) { ASSERT_TRUE(embedded_test_server()->Started()); GURL url(embedded_test_server()->GetURL("/printing/test1.html")); ui_test_utils::NavigateToURL(browser(), url); auto* contents = browser()->tab_strip_model()->GetActiveWebContents(); contents->SetPageScale(1.5); PrintAndWaitUntilPreviewIsReady(/*print_only_selection=*/false); double contents_page_scale_after_print = content::EvalJs(contents, "window.visualViewport.scale").ExtractDouble(); constexpr double kContentsInitialScale = 1.0; EXPECT_EQ(kContentsInitialScale, contents_page_scale_after_print); } // Printing frame content for the main frame of a generic webpage. // This test passes when the printed result is sent back and checked in // TestPrintRenderFrame::OnDidPrintFrameContent(). IN_PROC_BROWSER_TEST_F(PrintBrowserTest, PrintFrameContent) { ASSERT_TRUE(embedded_test_server()->Started()); GURL url(embedded_test_server()->GetURL("/printing/test1.html")); ui_test_utils::NavigateToURL(browser(), url); content::WebContents* original_contents = browser()->tab_strip_model()->GetActiveWebContents(); content::RenderFrameHost* rfh = original_contents->GetMainFrame(); CreateTestPrintRenderFrame(rfh, original_contents); GetPrintRenderFrame(rfh)->PrintFrameContent(GetDefaultPrintFrameParams(), base::DoNothing()); // The printed result will be received and checked in // TestPrintRenderFrame. WaitUntilCallbackReceived(); } // Printing frame content for a cross-site iframe. // This test passes when the iframe responds to the print message. // The response is checked in TestPrintRenderFrame::OnDidPrintFrameContent(). IN_PROC_BROWSER_TEST_F(PrintBrowserTest, PrintSubframeContent) { ASSERT_TRUE(embedded_test_server()->Started()); GURL url( embedded_test_server()->GetURL("/printing/content_with_iframe.html")); ui_test_utils::NavigateToURL(browser(), url); content::WebContents* original_contents = browser()->tab_strip_model()->GetActiveWebContents(); ASSERT_EQ(2u, original_contents->GetAllFrames().size()); content::RenderFrameHost* test_frame = original_contents->GetAllFrames()[1]; ASSERT_TRUE(test_frame); CreateTestPrintRenderFrame(test_frame, original_contents); GetPrintRenderFrame(test_frame) ->PrintFrameContent(GetDefaultPrintFrameParams(), base::DoNothing()); // The printed result will be received and checked in // TestPrintRenderFrame. WaitUntilCallbackReceived(); } // Printing frame content with a cross-site iframe which also has a cross-site // iframe. The site reference chain is a.com --> b.com --> c.com. // This test passes when both cross-site frames are printed and their // responses which are checked in // TestPrintRenderFrame::OnDidPrintFrameContent(). IN_PROC_BROWSER_TEST_F(PrintBrowserTest, PrintSubframeChain) { ASSERT_TRUE(embedded_test_server()->Started()); GURL url(embedded_test_server()->GetURL( "/printing/content_with_iframe_chain.html")); ui_test_utils::NavigateToURL(browser(), url); content::WebContents* original_contents = browser()->tab_strip_model()->GetActiveWebContents(); ASSERT_EQ(3u, original_contents->GetAllFrames().size()); // Create composite client so subframe print message can be forwarded. PrintCompositeClient::CreateForWebContents(original_contents); content::RenderFrameHost* main_frame = original_contents->GetMainFrame(); content::RenderFrameHost* child_frame = content::ChildFrameAt(main_frame, 0); ASSERT_TRUE(child_frame); ASSERT_NE(child_frame, main_frame); bool oopif_enabled = child_frame->GetProcess() != main_frame->GetProcess(); content::RenderFrameHost* grandchild_frame = content::ChildFrameAt(child_frame, 0); ASSERT_TRUE(grandchild_frame); ASSERT_NE(grandchild_frame, child_frame); if (oopif_enabled) { ASSERT_NE(grandchild_frame->GetProcess(), child_frame->GetProcess()); ASSERT_NE(grandchild_frame->GetProcess(), main_frame->GetProcess()); } CreateTestPrintRenderFrame(main_frame, original_contents); if (oopif_enabled) { CreateTestPrintRenderFrame(child_frame, original_contents); CreateTestPrintRenderFrame(grandchild_frame, original_contents); } GetPrintRenderFrame(main_frame) ->PrintFrameContent(GetDefaultPrintFrameParams(), base::DoNothing()); // The printed result will be received and checked in // TestPrintRenderFrame. SetNumExpectedMessages(oopif_enabled ? 3 : 1); WaitUntilCallbackReceived(); } // Printing frame content with a cross-site iframe who also has a cross site // iframe, but this iframe resides in the same site as the main frame. // The site reference loop is a.com --> b.com --> a.com. // This test passes when both cross-site frames are printed and send back // responses which are checked in // TestPrintRenderFrame::OnDidPrintFrameContent(). IN_PROC_BROWSER_TEST_F(PrintBrowserTest, PrintSubframeABA) { ASSERT_TRUE(embedded_test_server()->Started()); GURL url(embedded_test_server()->GetURL( "a.com", "/printing/content_with_iframe_loop.html")); ui_test_utils::NavigateToURL(browser(), url); content::WebContents* original_contents = browser()->tab_strip_model()->GetActiveWebContents(); ASSERT_EQ(3u, original_contents->GetAllFrames().size()); // Create composite client so subframe print message can be forwarded. PrintCompositeClient::CreateForWebContents(original_contents); content::RenderFrameHost* main_frame = original_contents->GetMainFrame(); content::RenderFrameHost* child_frame = content::ChildFrameAt(main_frame, 0); ASSERT_TRUE(child_frame); ASSERT_NE(child_frame, main_frame); bool oopif_enabled = main_frame->GetProcess() != child_frame->GetProcess(); content::RenderFrameHost* grandchild_frame = content::ChildFrameAt(child_frame, 0); ASSERT_TRUE(grandchild_frame); ASSERT_NE(grandchild_frame, child_frame); // |grandchild_frame| is in the same site as |frame|, so whether OOPIF is // enabled, they will be in the same process. ASSERT_EQ(grandchild_frame->GetProcess(), main_frame->GetProcess()); CreateTestPrintRenderFrame(main_frame, original_contents); if (oopif_enabled) { CreateTestPrintRenderFrame(child_frame, original_contents); CreateTestPrintRenderFrame(grandchild_frame, original_contents); } GetPrintRenderFrame(main_frame) ->PrintFrameContent(GetDefaultPrintFrameParams(), base::DoNothing()); // The printed result will be received and checked in // TestPrintRenderFrame. SetNumExpectedMessages(oopif_enabled ? 3 : 1); WaitUntilCallbackReceived(); } // Printing preview a simple webpage when site per process is enabled. // Test that the basic oopif printing should succeed. The test should not crash // or timed out. There could be other reasons that cause the test fail, but the // most obvious ones would be font access outage or web sandbox support being // absent because we explicitly check these when pdf compositor service starts. IN_PROC_BROWSER_TEST_F(SitePerProcessPrintBrowserTest, BasicPrint) { ASSERT_TRUE(embedded_test_server()->Started()); GURL url(embedded_test_server()->GetURL("/printing/test1.html")); ui_test_utils::NavigateToURL(browser(), url); PrintAndWaitUntilPreviewIsReady(/*print_only_selection=*/false); } // Printing a web page with a dead subframe for site per process should succeed. // This test passes whenever the print preview is rendered. This should not be // a timed out test which indicates the print preview hung. IN_PROC_BROWSER_TEST_F(SitePerProcessPrintBrowserTest, SubframeUnavailableBeforePrint) { ASSERT_TRUE(embedded_test_server()->Started()); GURL url( embedded_test_server()->GetURL("/printing/content_with_iframe.html")); ui_test_utils::NavigateToURL(browser(), url); content::WebContents* original_contents = browser()->tab_strip_model()->GetActiveWebContents(); ASSERT_EQ(2u, original_contents->GetAllFrames().size()); content::RenderFrameHost* test_frame = original_contents->GetAllFrames()[1]; ASSERT_TRUE(test_frame); ASSERT_TRUE(test_frame->IsRenderFrameLive()); // Wait for the renderer to be down. content::RenderProcessHostWatcher render_process_watcher( test_frame->GetProcess(), content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); // Shutdown the subframe. ASSERT_TRUE(test_frame->GetProcess()->Shutdown(0)); render_process_watcher.Wait(); ASSERT_FALSE(test_frame->IsRenderFrameLive()); PrintAndWaitUntilPreviewIsReady(/*print_only_selection=*/false); } // If a subframe dies during printing, the page printing should still succeed. // This test passes whenever the print preview is rendered. This should not be // a timed out test which indicates the print preview hung. IN_PROC_BROWSER_TEST_F(SitePerProcessPrintBrowserTest, SubframeUnavailableDuringPrint) { ASSERT_TRUE(embedded_test_server()->Started()); GURL url( embedded_test_server()->GetURL("/printing/content_with_iframe.html")); ui_test_utils::NavigateToURL(browser(), url); content::WebContents* original_contents = browser()->tab_strip_model()->GetActiveWebContents(); ASSERT_EQ(2u, original_contents->GetAllFrames().size()); content::RenderFrameHost* subframe = original_contents->GetAllFrames()[1]; ASSERT_TRUE(subframe); auto* subframe_rph = subframe->GetProcess(); KillPrintRenderFrame frame_content(subframe_rph); frame_content.OverrideBinderForTesting(subframe); // Waits for the renderer to be down. content::RenderProcessHostWatcher process_watcher( subframe_rph, content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); // Adds the observer to get the status for the preview. PrintPreviewObserver print_preview_observer(/*wait_for_loaded=*/false); StartPrint(browser()->tab_strip_model()->GetActiveWebContents(), /*print_renderer=*/mojo::NullAssociatedRemote(), /*print_preview_disabled=*/false, /*has_selection*/ false); // Makes sure that |subframe_rph| is terminated. process_watcher.Wait(); // Confirms that the preview pages are rendered. print_preview_observer.WaitUntilPreviewIsReady(); } // Printing preview a web page with an iframe from an isolated origin. // This test passes whenever the print preview is rendered. This should not be // a timed out test which indicates the print preview hung or crash. IN_PROC_BROWSER_TEST_F(IsolateOriginsPrintBrowserTest, DISABLED_PrintIsolatedSubframe) { ASSERT_TRUE(embedded_test_server()->Started()); GURL url(embedded_test_server()->GetURL( "/printing/content_with_same_site_iframe.html")); GURL isolated_url( embedded_test_server()->GetURL(kIsolatedSite, "/printing/test1.html")); ui_test_utils::NavigateToURL(browser(), url); content::WebContents* original_contents = browser()->tab_strip_model()->GetActiveWebContents(); EXPECT_TRUE(NavigateIframeToURL(original_contents, "iframe", isolated_url)); ASSERT_EQ(2u, original_contents->GetAllFrames().size()); auto* main_frame = original_contents->GetMainFrame(); auto* subframe = original_contents->GetAllFrames()[1]; ASSERT_NE(main_frame->GetProcess(), subframe->GetProcess()); PrintAndWaitUntilPreviewIsReady(/*print_only_selection=*/false); } // Printing preview a webpage. // Test that we use oopif printing by default. IN_PROC_BROWSER_TEST_F(PrintBrowserTest, RegularPrinting) { ASSERT_TRUE(embedded_test_server()->Started()); GURL url(embedded_test_server()->GetURL("/printing/test1.html")); ui_test_utils::NavigateToURL(browser(), url); EXPECT_TRUE(IsOopifEnabled()); } // Printing preview a webpage with isolate-origins enabled. // Test that we will use oopif printing for this case. IN_PROC_BROWSER_TEST_F(IsolateOriginsPrintBrowserTest, OopifPrinting) { ASSERT_TRUE(embedded_test_server()->Started()); GURL url(embedded_test_server()->GetURL("/printing/test1.html")); ui_test_utils::NavigateToURL(browser(), url); EXPECT_TRUE(IsOopifEnabled()); } IN_PROC_BROWSER_TEST_F(BackForwardCachePrintBrowserTest, DisableCaching) { ASSERT_TRUE(embedded_test_server()->Started()); // 1) Navigate to A and trigger printing. GURL url(embedded_test_server()->GetURL( "a.com", "/back_forward_cache/no-favicon.html")); ui_test_utils::NavigateToURL(browser(), url); content::RenderFrameHost* rfh_a = current_frame_host(); content::RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); PrintAndWaitUntilPreviewIsReady(/*print_only_selection=*/false); // 2) Navigate to B. // The first page is not cached because printing preview was open. GURL url_2(embedded_test_server()->GetURL( "b.com", "/back_forward_cache/no-favicon.html")); ui_test_utils::NavigateToURL(browser(), url_2); delete_observer_rfh_a.WaitUntilDeleted(); // 3) Navigate back and checks the blocklisted feature is recorded in UMA. web_contents()->GetController().GoBack(); EXPECT_TRUE(content::WaitForLoadStop(web_contents())); ExpectBlocklistedFeature( blink::scheduler::WebSchedulerTrackedFeature::kPrinting, FROM_HERE); } // Printing an extension option page. // The test should not crash or timeout. IN_PROC_BROWSER_TEST_F(PrintExtensionBrowserTest, PrintOptionPage) { LoadExtensionAndNavigateToOptionPage(); PrintAndWaitUntilPreviewIsReady(/*print_only_selection=*/false); } // Printing an extension option page with site per process is enabled. // The test should not crash or timeout. IN_PROC_BROWSER_TEST_F(SitePerProcessPrintExtensionBrowserTest, PrintOptionPage) { LoadExtensionAndNavigateToOptionPage(); PrintAndWaitUntilPreviewIsReady(/*print_only_selection=*/false); } // Printing frame content for the main frame of a generic webpage with N-up // priting. This is a regression test for https://crbug.com/937247 IN_PROC_BROWSER_TEST_F(PrintBrowserTest, PrintNup) { ASSERT_TRUE(embedded_test_server()->Started()); GURL url(embedded_test_server()->GetURL("/printing/test1.html")); ui_test_utils::NavigateToURL(browser(), url); content::WebContents* web_contents = browser()->tab_strip_model()->GetActiveWebContents(); ASSERT_TRUE(web_contents); content::RemoveWebContentsReceiverSet(web_contents, mojom::PrintManagerHost::Name_); TestPrintViewManager print_view_manager(web_contents); PrintAndWaitUntilPreviewIsReady(/*print_only_selection=*/false); } // Site per process version of PrintBrowserTest.PrintNup. IN_PROC_BROWSER_TEST_F(SitePerProcessPrintBrowserTest, PrintNup) { ASSERT_TRUE(embedded_test_server()->Started()); GURL url(embedded_test_server()->GetURL("/printing/test1.html")); ui_test_utils::NavigateToURL(browser(), url); content::WebContents* web_contents = browser()->tab_strip_model()->GetActiveWebContents(); ASSERT_TRUE(web_contents); content::RemoveWebContentsReceiverSet(web_contents, mojom::PrintManagerHost::Name_); TestPrintViewManager print_view_manager(web_contents); PrintAndWaitUntilPreviewIsReady(/*print_only_selection=*/false); } IN_PROC_BROWSER_TEST_F(PrintBrowserTest, MultipagePrint) { ASSERT_TRUE(embedded_test_server()->Started()); GURL url(embedded_test_server()->GetURL("/printing/multipage.html")); ui_test_utils::NavigateToURL(browser(), url); PrintAndWaitUntilPreviewIsReadyAndLoaded(/*print_only_selection=*/false); } IN_PROC_BROWSER_TEST_F(SitePerProcessPrintBrowserTest, MultipagePrint) { ASSERT_TRUE(embedded_test_server()->Started()); GURL url(embedded_test_server()->GetURL("/printing/multipage.html")); ui_test_utils::NavigateToURL(browser(), url); PrintAndWaitUntilPreviewIsReadyAndLoaded(/*print_only_selection=*/false); } IN_PROC_BROWSER_TEST_F(PrintBrowserTest, PDFPluginNotKeyboardFocusable) { ASSERT_TRUE(embedded_test_server()->Started()); GURL url(embedded_test_server()->GetURL("/printing/multipage.html")); ui_test_utils::NavigateToURL(browser(), url); PrintPreviewObserver print_preview_observer(/*wait_for_loaded=*/true); StartPrint(browser()->tab_strip_model()->GetActiveWebContents(), /*print_renderer=*/mojo::NullAssociatedRemote(), /*print_preview_disabled=*/false, /*print_only_selection=*/false); print_preview_observer.WaitUntilPreviewIsReady(); content::WebContents* preview_dialog = print_preview_observer.GetPrintPreviewDialog(); ASSERT_TRUE(preview_dialog); // The script will ensure we return the id of when // focused. Focus the element after PDF plugin in tab order. const char kScript[] = R"( const button = document.getElementsByTagName('print-preview-app')[0] .$['previewArea'] .$$('iframe') .contentDocument.querySelector('pdf-viewer-pp') .shadowRoot.querySelector('#zoom-toolbar') .$['zoom-out-button']; button.addEventListener('focus', (e) => { window.domAutomationController.send(e.target.id); }); const select_tag = document.getElementsByTagName('print-preview-app')[0] .$['sidebar'] .$['destinationSettings'] .$['destinationSelect']; select_tag.addEventListener('focus', () => { window.domAutomationController.send(true); }); select_tag.focus();)"; bool success = false; ASSERT_TRUE( content::ExecuteScriptAndExtractBool(preview_dialog, kScript, &success)); ASSERT_TRUE(success); // Simulate a press and wait for a focus message. content::DOMMessageQueue msg_queue; SimulateKeyPress(preview_dialog, ui::DomKey::TAB, ui::DomCode::TAB, ui::VKEY_TAB, false, true, false, false); std::string reply; ASSERT_TRUE(msg_queue.WaitForMessage(&reply)); // Pressing should focus the last toolbar element // (zoom-out-button) instead of PDF plugin. EXPECT_EQ("\"zoom-out-button\"", reply); } } // namespace printing