| // Copyright 2012 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/printing/print_preview_dialog_controller.h" | 
 |  | 
 | #include <memory> | 
 |  | 
 | #include "base/command_line.h" | 
 | #include "base/files/file_path.h" | 
 | #include "base/functional/bind.h" | 
 | #include "base/functional/callback_helpers.h" | 
 | #include "base/location.h" | 
 | #include "base/memory/raw_ptr.h" | 
 | #include "base/run_loop.h" | 
 | #include "base/strings/string_util.h" | 
 | #include "base/strings/utf_string_conversions.h" | 
 | #include "base/task/single_thread_task_runner.h" | 
 | #include "base/test/bind.h" | 
 | #include "build/build_config.h" | 
 | #include "chrome/browser/preloading/scoped_prewarm_feature_list.h" | 
 | #include "chrome/browser/plugins/chrome_plugin_service_filter.h" | 
 | #include "chrome/browser/plugins/plugin_prefs.h" | 
 | #include "chrome/browser/printing/print_view_manager.h" | 
 | #include "chrome/browser/printing/test_print_preview_dialog_cloned_observer.h" | 
 | #include "chrome/browser/printing/test_print_view_manager_for_request_preview.h" | 
 | #include "chrome/browser/profiles/profile.h" | 
 | #include "chrome/browser/task_manager/mock_web_contents_task_manager.h" | 
 | #include "chrome/browser/ui/browser.h" | 
 | #include "chrome/browser/ui/browser_commands.h" | 
 | #include "chrome/browser/ui/tabs/tab_strip_model.h" | 
 | #include "chrome/common/chrome_content_client.h" | 
 | #include "chrome/common/url_constants.h" | 
 | #include "chrome/grit/generated_resources.h" | 
 | #include "chrome/test/base/in_process_browser_test.h" | 
 | #include "chrome/test/base/ui_test_utils.h" | 
 | #include "content/public/browser/plugin_service.h" | 
 | #include "content/public/browser/render_frame_host.h" | 
 | #include "content/public/browser/render_process_host.h" | 
 | #include "content/public/browser/web_contents_observer.h" | 
 | #include "content/public/common/webplugininfo.h" | 
 | #include "content/public/test/browser_test.h" | 
 | #include "content/public/test/browser_test_utils.h" | 
 | #include "content/public/test/scoped_accessibility_mode_override.h" | 
 | #include "content/public/test/test_utils.h" | 
 | #include "ui/accessibility/ax_mode.h" | 
 | #include "ui/base/l10n/l10n_util.h" | 
 | #include "ui/base/ui_base_switches.h" | 
 | #include "url/gurl.h" | 
 |  | 
 | using content::WebContents; | 
 | using content::WebContentsObserver; | 
 |  | 
 | namespace { | 
 |  | 
 | void PluginsLoadedCallback( | 
 |     base::OnceClosure quit_closure, | 
 |     const std::vector<content::WebPluginInfo>& /* info */) { | 
 |   std::move(quit_closure).Run(); | 
 | } | 
 |  | 
 | void CheckPdfPluginForRenderFrame(content::RenderFrameHost* frame) { | 
 |   static const base::FilePath kPdfInternalPluginPath( | 
 |       ChromeContentClient::kPDFInternalPluginPath); | 
 |  | 
 |   content::WebPluginInfo pdf_internal_plugin_info; | 
 |   ASSERT_TRUE(content::PluginService::GetInstance()->GetPluginInfoByPath( | 
 |       kPdfInternalPluginPath, &pdf_internal_plugin_info)); | 
 |  | 
 |   ChromePluginServiceFilter* filter = ChromePluginServiceFilter::GetInstance(); | 
 |   EXPECT_TRUE(filter->IsPluginAvailable(frame->GetBrowserContext(), | 
 |                                         pdf_internal_plugin_info)); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | class PrintPreviewDialogControllerBrowserTest : public InProcessBrowserTest { | 
 |  public: | 
 |   WebContents* initiator() { | 
 |     return initiator_; | 
 |   } | 
 |  | 
 |   void PrintPreview() { | 
 |     base::RunLoop run_loop; | 
 |     test_print_view_manager_->set_quit_closure(run_loop.QuitClosure()); | 
 |     chrome::Print(browser()); | 
 |     run_loop.Run(); | 
 |   } | 
 |  | 
 |   WebContents* GetPrintPreviewDialog() { | 
 |     printing::PrintPreviewDialogController* dialog_controller = | 
 |         printing::PrintPreviewDialogController::GetInstance(); | 
 |     return dialog_controller->GetPrintPreviewForContents(initiator_); | 
 |   } | 
 |  | 
 |   void PrintPreviewDone() { test_print_view_manager_->PrintPreviewDone(); } | 
 |  | 
 |   void SetAlwaysOpenPdfExternallyForTests() { | 
 |     PluginPrefs::GetForProfile(browser()->profile()) | 
 |         ->SetAlwaysOpenPdfExternallyForTests(true); | 
 |   } | 
 |  | 
 |  private: | 
 |   void SetUpOnMainThread() override { | 
 | #if BUILDFLAG(IS_MAC) | 
 |     base::CommandLine::ForCurrentProcess()->AppendSwitch( | 
 |         switches::kDisableModalAnimations); | 
 | #endif | 
 |  | 
 |     WebContents* first_tab = | 
 |         browser()->tab_strip_model()->GetActiveWebContents(); | 
 |     ASSERT_TRUE(first_tab); | 
 |  | 
 |     // Open a new tab so `cloned_tab_observer_` can see it and create a | 
 |     // TestPrintViewManagerForRequestPreview for it before the real | 
 |     // PrintViewManager gets created. | 
 |     // Since TestPrintViewManagerForRequestPreview is created with | 
 |     // PrintViewManager::UserDataKey(), the real PrintViewManager is not created | 
 |     // and TestPrintViewManagerForRequestPreview gets mojo messages for the | 
 |     // purposes of this test. | 
 |     cloned_tab_observer_ = | 
 |         std::make_unique<printing::TestPrintPreviewDialogClonedObserver>( | 
 |             first_tab); | 
 |     chrome::DuplicateTab(browser()); | 
 |  | 
 |     initiator_ = browser()->tab_strip_model()->GetActiveWebContents(); | 
 |     ASSERT_TRUE(initiator_); | 
 |     ASSERT_NE(first_tab, initiator_); | 
 |  | 
 |     test_print_view_manager_ = | 
 |         printing::TestPrintViewManagerForRequestPreview::FromWebContents( | 
 |             initiator_); | 
 |     content::PluginService::GetInstance()->Init(); | 
 |   } | 
 |  | 
 |   void TearDownOnMainThread() override { | 
 |     cloned_tab_observer_.reset(); | 
 |     initiator_ = nullptr; | 
 |   } | 
 |  | 
 |   // TODO(https://crbug.com/423465927): Explore a better approach to make the | 
 |   // existing tests run with the prewarm feature enabled. | 
 |   test::ScopedPrewarmFeatureList prewarm_feature_list_{ | 
 |       test::ScopedPrewarmFeatureList::PrewarmState::kDisabled}; | 
 |  | 
 |   std::unique_ptr<printing::TestPrintPreviewDialogClonedObserver> | 
 |       cloned_tab_observer_; | 
 |   raw_ptr<printing::TestPrintViewManagerForRequestPreview, | 
 |           AcrossTasksDanglingUntriaged> | 
 |       test_print_view_manager_; | 
 |   raw_ptr<WebContents, AcrossTasksDanglingUntriaged> initiator_ = nullptr; | 
 | }; | 
 |  | 
 | // Test to verify that when a initiator navigates, we can create a new preview | 
 | // dialog for the new tab contents. | 
 | // TODO(crbug.com/40251696): Test is flaky on Mac | 
 | #if BUILDFLAG(IS_MAC) | 
 | #define MAYBE_NavigateFromInitiatorTab DISABLED_NavigateFromInitiatorTab | 
 | #else | 
 | #define MAYBE_NavigateFromInitiatorTab NavigateFromInitiatorTab | 
 | #endif | 
 | IN_PROC_BROWSER_TEST_F(PrintPreviewDialogControllerBrowserTest, | 
 |                        MAYBE_NavigateFromInitiatorTab) { | 
 |   // Print for the first time. | 
 |   PrintPreview(); | 
 |  | 
 |   // Get the preview dialog for the initiator tab. | 
 |   WebContents* preview_dialog = GetPrintPreviewDialog(); | 
 |  | 
 |   // Check a new print preview dialog got created. | 
 |   ASSERT_TRUE(preview_dialog); | 
 |   ASSERT_NE(initiator(), preview_dialog); | 
 |  | 
 |   PrintPreviewDone(); | 
 |  | 
 |   // Navigate in the initiator tab. Make sure navigating destroys the print | 
 |   // preview dialog. | 
 |   content::WebContentsDestroyedWatcher watcher(preview_dialog); | 
 |   ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), | 
 |                                            GURL(chrome::kChromeUINewTabURL))); | 
 |   ASSERT_TRUE(watcher.IsDestroyed()); | 
 |  | 
 |   // Try printing again. | 
 |   PrintPreview(); | 
 |  | 
 |   // Get the print preview dialog for the initiator tab. | 
 |   WebContents* new_preview_dialog = GetPrintPreviewDialog(); | 
 |  | 
 |   // Check a new preview dialog got created. | 
 |   EXPECT_TRUE(new_preview_dialog); | 
 |  | 
 |   PrintPreviewDone(); | 
 | } | 
 |  | 
 | // Test to verify that after reloading the initiator, it creates a new print | 
 | // preview dialog. | 
 | // TODO(crbug.com/40251696): Test is flaky on Mac | 
 | #if BUILDFLAG(IS_MAC) | 
 | #define MAYBE_ReloadInitiatorTab DISABLED_ReloadInitiatorTab | 
 | #else | 
 | #define MAYBE_ReloadInitiatorTab ReloadInitiatorTab | 
 | #endif | 
 | IN_PROC_BROWSER_TEST_F(PrintPreviewDialogControllerBrowserTest, | 
 |                        MAYBE_ReloadInitiatorTab) { | 
 |   // Print for the first time. | 
 |   PrintPreview(); | 
 |  | 
 |   WebContents* preview_dialog = GetPrintPreviewDialog(); | 
 |  | 
 |   // Check a new print preview dialog got created. | 
 |   ASSERT_TRUE(preview_dialog); | 
 |   ASSERT_NE(initiator(), preview_dialog); | 
 |  | 
 |   PrintPreviewDone(); | 
 |  | 
 |   // Reload the initiator. Make sure reloading destroys the print preview | 
 |   // dialog. | 
 |   content::WebContentsDestroyedWatcher watcher(preview_dialog); | 
 |   chrome::Reload(browser(), WindowOpenDisposition::CURRENT_TAB); | 
 |   EXPECT_TRUE(content::WaitForLoadStop( | 
 |       browser()->tab_strip_model()->GetActiveWebContents())); | 
 |   // When Widget::Close is called, a task is posted that will destroy the | 
 |   // widget. Here the widget is closed when the navigation commits. Load stop | 
 |   // may occur right after the commit, before the widget is destroyed. | 
 |   // Execute pending tasks to account for this. | 
 |   base::RunLoop().RunUntilIdle(); | 
 |   ASSERT_TRUE(watcher.IsDestroyed()); | 
 |  | 
 |   // Try printing again. | 
 |   PrintPreview(); | 
 |  | 
 |   // Create a preview dialog for the initiator tab. | 
 |   WebContents* new_preview_dialog = GetPrintPreviewDialog(); | 
 |   EXPECT_TRUE(new_preview_dialog); | 
 |  | 
 |   PrintPreviewDone(); | 
 | } | 
 |  | 
 | // Test to verify that after print preview works even when the PDF plugin is | 
 | // disabled for webpages. | 
 | // TODO(crbug.com/40884297): Flaky on Mac12 Test. | 
 | #if BUILDFLAG(IS_MAC) | 
 | #define MAYBE_PdfPluginDisabled DISABLED_PdfPluginDisabled | 
 | #else | 
 | #define MAYBE_PdfPluginDisabled PdfPluginDisabled | 
 | #endif | 
 | IN_PROC_BROWSER_TEST_F(PrintPreviewDialogControllerBrowserTest, | 
 |                        MAYBE_PdfPluginDisabled) { | 
 |   // Make sure plugins are loaded. | 
 |   { | 
 |     base::RunLoop run_loop; | 
 |     content::PluginService::GetInstance()->GetPlugins( | 
 |         base::BindOnce(&PluginsLoadedCallback, run_loop.QuitClosure())); | 
 |     run_loop.Run(); | 
 |   } | 
 |   // Get the PDF plugin info. | 
 |   content::WebPluginInfo pdf_external_plugin_info; | 
 |   ASSERT_TRUE(content::PluginService::GetInstance()->GetPluginInfoByPath( | 
 |       base::FilePath(ChromeContentClient::kPDFExtensionPluginPath), | 
 |       &pdf_external_plugin_info)); | 
 |  | 
 |   // Disable the PDF plugin. | 
 |   SetAlwaysOpenPdfExternallyForTests(); | 
 |  | 
 |   // Make sure it is actually disabled for webpages. | 
 |   ChromePluginServiceFilter* filter = ChromePluginServiceFilter::GetInstance(); | 
 |   EXPECT_FALSE(filter->IsPluginAvailable(initiator()->GetBrowserContext(), | 
 |                                          pdf_external_plugin_info)); | 
 |  | 
 |   PrintPreview(); | 
 |  | 
 |   // Check a new print preview dialog got created. | 
 |   WebContents* preview_dialog = GetPrintPreviewDialog(); | 
 |   ASSERT_TRUE(preview_dialog); | 
 |   ASSERT_NE(initiator(), preview_dialog); | 
 |  | 
 |   // Wait until all the frames in the Print Preview renderer have loaded. | 
 |   // `frame_count` should be 3: the main frame, the viewer's <iframe>, and the | 
 |   // plugin frame. | 
 |   const int kExpectedFrameCount = 3; | 
 |   int frame_count; | 
 |   do { | 
 |     base::RunLoop run_loop; | 
 |     base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( | 
 |         FROM_HERE, run_loop.QuitClosure(), base::Seconds(1)); | 
 |     run_loop.Run(); | 
 |  | 
 |     frame_count = 0; | 
 |     preview_dialog->GetPrimaryMainFrame()->ForEachRenderFrameHost( | 
 |         [&frame_count](content::RenderFrameHost* /*frame*/) { ++frame_count; }); | 
 |   } while (frame_count < kExpectedFrameCount); | 
 |   ASSERT_EQ(kExpectedFrameCount, frame_count); | 
 |  | 
 |   // Make sure all the frames in the dialog has access to the PDF plugin. | 
 |   preview_dialog->GetPrimaryMainFrame()->ForEachRenderFrameHost( | 
 |       &CheckPdfPluginForRenderFrame); | 
 |  | 
 |   PrintPreviewDone(); | 
 | } | 
 |  | 
 | namespace { | 
 |  | 
 | std::u16string GetExpectedPrefix() { | 
 |   return l10n_util::GetStringFUTF16(IDS_TASK_MANAGER_PRINT_PREFIX, | 
 |                                     std::u16string()); | 
 | } | 
 |  | 
 | const std::vector<raw_ptr<task_manager::WebContentsTag, VectorExperimental>>& | 
 | GetTrackedTags() { | 
 |   return task_manager::WebContentsTagsManager::GetInstance()->tracked_tags(); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | // TODO(crbug.com/40879071): Flaky on macos12 builds. | 
 | #if BUILDFLAG(IS_MAC) | 
 | #define MAYBE_TaskManagementTest DISABLED_TaskManagementTest | 
 | #else | 
 | #define MAYBE_TaskManagementTest TaskManagementTest | 
 | #endif | 
 | IN_PROC_BROWSER_TEST_F(PrintPreviewDialogControllerBrowserTest, | 
 |                        MAYBE_TaskManagementTest) { | 
 |   // This test starts with two tabs open. | 
 |   EXPECT_EQ(2U, GetTrackedTags().size()); | 
 |  | 
 |   PrintPreview(); | 
 |   EXPECT_EQ(3U, GetTrackedTags().size()); | 
 |  | 
 |   // Create a task manager and expect the pre-existing print previews are | 
 |   // provided. | 
 |   task_manager::MockWebContentsTaskManager task_manager; | 
 |   EXPECT_TRUE(task_manager.tasks().empty()); | 
 |   task_manager.StartObserving(); | 
 |   ASSERT_EQ(3U, task_manager.tasks().size()); | 
 |   const task_manager::Task* pre_existing_task = task_manager.tasks().back(); | 
 |   EXPECT_EQ(task_manager::Task::RENDERER, pre_existing_task->GetType()); | 
 |   const std::u16string pre_existing_title = pre_existing_task->title(); | 
 |   const std::u16string expected_prefix = GetExpectedPrefix(); | 
 |   EXPECT_TRUE(base::StartsWith(pre_existing_title, | 
 |                                expected_prefix, | 
 |                                base::CompareCase::INSENSITIVE_ASCII)); | 
 |  | 
 |   PrintPreviewDone(); | 
 |  | 
 |   // Navigating away from the current page in the current tab for which a print | 
 |   // preview is displayed will cancel the print preview and hence the task | 
 |   // manger shouldn't show a printing task. | 
 |   ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank"))); | 
 |   EXPECT_EQ(2U, GetTrackedTags().size()); | 
 |   EXPECT_EQ(2U, task_manager.tasks().size()); | 
 |  | 
 |   // Now start another print preview after the had already been created and | 
 |   // validated that a corresponding task is reported. | 
 |   PrintPreview(); | 
 |   EXPECT_EQ(3U, GetTrackedTags().size()); | 
 |   ASSERT_EQ(3U, task_manager.tasks().size()); | 
 |   const task_manager::Task* task = task_manager.tasks().back(); | 
 |   EXPECT_EQ(task_manager::Task::RENDERER, task->GetType()); | 
 |   const std::u16string title = task->title(); | 
 |   EXPECT_TRUE(base::StartsWith(title, | 
 |                                expected_prefix, | 
 |                                base::CompareCase::INSENSITIVE_ASCII)); | 
 |   PrintPreviewDone(); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(PrintPreviewDialogControllerBrowserTest, | 
 |                        PrintPreviewPdfAccessibility) { | 
 |   content::ScopedAccessibilityModeOverride mode_override(ui::kAXModeComplete); | 
 |   // Put a DIV after the text we're going to search for. The last node in the | 
 |   // tree will not have a newline appended, but all the others will. Avoid | 
 |   // making assumptions about whether it's the last node or not. There may be | 
 |   // nodes for headers and footers following the document contents. | 
 |   ASSERT_TRUE(ui_test_utils::NavigateToURL( | 
 |       browser(), GURL("data:text/html,HelloWorld<div>next</div>"))); | 
 |   PrintPreview(); | 
 |   WebContents* preview_dialog = GetPrintPreviewDialog(); | 
 |   WaitForAccessibilityTreeToContainNodeWithName(preview_dialog, | 
 |                                                 "HelloWorld\r\n"); | 
 |  | 
 |   PrintPreviewDone(); | 
 | } |