blob: 11c2358538f10b5b3567c0485b8ca2489c25b0aa [file] [log] [blame]
// 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 <memory>
#include <tuple>
#include <utility>
#include <variant>
#include <vector>
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/pdf/pdf_extension_test_base.h"
#include "chrome/browser/pdf/pdf_extension_test_util.h"
#include "chrome/browser/printing/browser_printing_context_factory_for_test.h"
#include "chrome/browser/printing/print_error_dialog.h"
#include "chrome/browser/printing/print_job.h"
#include "chrome/browser/printing/print_test_utils.h"
#include "chrome/browser/printing/print_view_manager_base.h"
#include "chrome/browser/printing/test_print_preview_observer.h"
#include "chrome/browser/printing/test_print_view_manager.h"
#include "chrome/browser/renderer_context_menu/render_view_context_menu_browsertest_util.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/common/chrome_switches.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/context_menu_interceptor.h"
#include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "pdf/pdf_features.h"
#include "printing/backend/print_backend.h"
#include "printing/backend/test_print_backend.h"
#include "printing/buildflags/buildflags.h"
#include "printing/printing_features.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/input/web_input_event.h"
#include "third_party/blink/public/common/input/web_mouse_event.h"
#include "ui/base/mojom/menu_source_type.mojom.h"
#if BUILDFLAG(ENABLE_OOP_PRINTING)
#include "chrome/browser/printing/print_backend_service_manager.h"
#include "chrome/browser/printing/print_backend_service_test_impl.h"
#endif
#if BUILDFLAG(ENABLE_PRINT_PREVIEW)
#include "ui/base/ui_base_types.h"
#endif
#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/ash/printing/cups_print_job_manager_factory.h"
#include "chrome/browser/ash/printing/cups_printers_manager_factory.h"
#include "chrome/browser/ash/printing/fake_cups_printers_manager.h"
#include "chrome/browser/ash/printing/test_cups_print_job_manager.h"
#endif
namespace {
struct PDFExtensionPrintingTestPassToString {
std::string operator()(
const ::testing::TestParamInfo<std::tuple<bool, bool>>& i) const {
return std::string(std::get<1>(i.param) ? "OOPIF_" : "GUESTVIEW_") +
std::string(std::get<0>(i.param) ? "SERVICE" : "BROWSER");
}
};
#if BUILDFLAG(IS_CHROMEOS)
std::unique_ptr<KeyedService> BuildTestCupsPrintJobManager(
content::BrowserContext* context) {
return std::make_unique<ash::TestCupsPrintJobManager>(
Profile::FromBrowserContext(context));
}
std::unique_ptr<KeyedService> BuildFakeCupsPrintersManager(
content::BrowserContext* context) {
return std::make_unique<ash::FakeCupsPrintersManager>();
}
void OnWillCreateBrowserContextServices(content::BrowserContext* context) {
ash::CupsPrintJobManagerFactory::GetInstance()->SetTestingFactory(
context, base::BindRepeating(&BuildTestCupsPrintJobManager));
ash::CupsPrintersManagerFactory::GetInstance()->SetTestingFactory(
context, base::BindRepeating(&BuildFakeCupsPrintersManager));
}
#endif
} // namespace
using ::content::WebContents;
using ::extensions::MimeHandlerViewGuest;
using ::pdf_extension_test_util::SetInputFocusOnPlugin;
class PDFExtensionPrintingTest
: public PDFExtensionTestBase,
public printing::PrintJob::Observer,
public testing::WithParamInterface<std::tuple<bool, bool>> {
public:
PDFExtensionPrintingTest() = default;
~PDFExtensionPrintingTest() override = default;
// PDFExtensionTestBase:
void SetUp() override {
// Avoid using a real PrintBackend / PrintingContext, as they can show modal
// print dialogs.
// Called here in SetUp() because it must be reset in TearDown(), as
// resetting in TearDownOnMainThread() is too early. The MessagePump may
// still process messages after TearDownOnMainThread(), which can trigger
// PrintingContext calls.
printing::PrintBackend::SetPrintBackendForTesting(
test_print_backend_.get());
printing::PrintingContext::SetPrintingContextFactoryForTest(
&test_printing_context_factory_);
// Tests assume that printing can be done to the print backend, so there
// must be at least one printer available for that to make sense.
constexpr char kTestPrinter[] = "printer1";
AddPrinter(kTestPrinter);
test_printing_context_factory_.SetPrinterNameForSubsequentContexts(
kTestPrinter);
PDFExtensionTestBase::SetUp();
}
void SetUpOnMainThread() override {
// Avoid getting blocked by modal print error dialogs. Must be called after
// the UI thread is up and running.
SetShowPrintErrorDialogForTest(base::DoNothing());
#if BUILDFLAG(ENABLE_OOP_PRINTING)
if (UseService()) {
print_backend_service_ =
printing::PrintBackendServiceTestImpl::LaunchForTesting(
test_remote_, test_print_backend_.get(), /*sandboxed=*/true);
}
#endif
PDFExtensionTestBase::SetUpOnMainThread();
}
#if BUILDFLAG(IS_CHROMEOS)
void SetUpInProcessBrowserTestFixture() override {
create_services_subscription_ =
BrowserContextDependencyManager::GetInstance()
->RegisterCreateServicesCallbackForTesting(
base::BindRepeating(&OnWillCreateBrowserContextServices));
}
#endif
void TearDownOnMainThread() override {
PDFExtensionTestBase::TearDownOnMainThread();
#if BUILDFLAG(ENABLE_OOP_PRINTING)
printing::PrintBackendServiceManager::ResetForTesting();
#endif
SetShowPrintErrorDialogForTest(base::NullCallback());
}
void TearDown() override {
PDFExtensionTestBase::TearDown();
printing::PrintingContext::SetPrintingContextFactoryForTest(nullptr);
printing::PrintBackend::SetPrintBackendForTesting(nullptr);
}
bool UseOopif() const override { return std::get<1>(GetParam()); }
std::vector<base::test::FeatureRefAndParams> GetEnabledFeatures()
const override {
std::vector<base::test::FeatureRefAndParams> enabled =
PDFExtensionTestBase::GetEnabledFeatures();
#if BUILDFLAG(ENABLE_OOP_PRINTING)
if (UseService()) {
enabled.push_back({printing::features::kEnableOopPrintDrivers, {}});
}
#endif
return enabled;
}
std::vector<base::test::FeatureRef> GetDisabledFeatures() const override {
std::vector<base::test::FeatureRef> disabled =
PDFExtensionTestBase::GetDisabledFeatures();
#if BUILDFLAG(ENABLE_OOP_PRINTING)
if (!UseService()) {
disabled.push_back(printing::features::kEnableOopPrintDrivers);
}
#endif
return disabled;
}
void AddPrinter(const std::string& printer_name) {
printing::PrinterBasicInfo printer_info(
printer_name,
/*display_name=*/"test printer",
/*printer_description=*/"A printer for testing.",
printing::test::kPrintInfoOptions);
auto default_caps =
std::make_unique<printing::PrinterSemanticCapsAndDefaults>();
default_caps->copies_max = 1;
default_caps->dpis = printing::test::kPrinterCapabilitiesDefaultDpis;
default_caps->default_dpi = printing::test::kPrinterCapabilitiesDpi;
default_caps->papers.push_back(printing::test::kPaperLetter);
default_caps->papers.push_back(printing::test::kPaperLegal);
test_print_backend_->AddValidPrinter(
printer_name, std::move(default_caps),
std::make_unique<printing::PrinterBasicInfo>(printer_info));
test_print_backend_->SetDefaultPrinterName(printer_name);
}
void SetupPrintViewManagerForJobMonitoring(content::RenderFrameHost* frame) {
auto* web_contents = content::WebContents::FromRenderFrameHost(frame);
auto manager = std::make_unique<printing::TestPrintViewManager>(
web_contents,
base::BindRepeating(&PDFExtensionPrintingTest::OnCreatedPrintJob,
weak_factory_.GetWeakPtr()));
web_contents->SetUserData(printing::PrintViewManager::UserDataKey(),
std::move(manager));
}
void WaitForPrintJobDestruction() {
if (!print_job_destroyed_) {
base::RunLoop run_loop;
base::AutoReset<raw_ptr<base::RunLoop>> auto_reset(&run_loop_, &run_loop);
run_loop.Run();
EXPECT_TRUE(print_job_destroyed_);
}
}
private:
bool UseService() const { return std::get<0>(GetParam()); }
void OnCreatedPrintJob(printing::PrintJob* print_job) {
EXPECT_FALSE(observing_print_job_);
print_job->AddObserver(*this);
observing_print_job_ = true;
}
// PrintJob::Observer:
void OnDestruction() override {
EXPECT_FALSE(print_job_destroyed_);
if (run_loop_) {
run_loop_->Quit();
}
print_job_destroyed_ = true;
}
#if BUILDFLAG(IS_CHROMEOS)
base::CallbackListSubscription create_services_subscription_;
#endif
scoped_refptr<printing::TestPrintBackend> test_print_backend_ =
base::MakeRefCounted<printing::TestPrintBackend>();
printing::BrowserPrintingContextFactoryForTest test_printing_context_factory_;
#if BUILDFLAG(ENABLE_OOP_PRINTING)
mojo::Remote<printing::mojom::PrintBackendService> test_remote_;
std::unique_ptr<printing::PrintBackendServiceTestImpl> print_backend_service_;
#endif
bool observing_print_job_ = false;
bool print_job_destroyed_ = false;
raw_ptr<base::RunLoop> run_loop_ = nullptr;
base::WeakPtrFactory<PDFExtensionPrintingTest> weak_factory_{this};
};
IN_PROC_BROWSER_TEST_P(PDFExtensionPrintingTest, BasicPrintCommand) {
ASSERT_TRUE(LoadPdf(embedded_test_server()->GetURL("/pdf/test.pdf")));
content::RenderFrameHost* plugin_frame =
pdf_extension_test_util::GetOnlyPdfPluginFrame(GetActiveWebContents());
ASSERT_TRUE(plugin_frame);
SetupPrintViewManagerForJobMonitoring(plugin_frame);
chrome::BasicPrint(browser());
WaitForPrintJobDestruction();
}
#if BUILDFLAG(ENABLE_PRINT_PREVIEW)
IN_PROC_BROWSER_TEST_P(PDFExtensionPrintingTest, PrintCommand) {
ASSERT_TRUE(LoadPdf(embedded_test_server()->GetURL("/pdf/test.pdf")));
printing::TestPrintPreviewObserver print_observer(/*wait_for_loaded=*/false);
chrome::Print(browser());
print_observer.WaitUntilPreviewIsReady();
}
IN_PROC_BROWSER_TEST_P(PDFExtensionPrintingTest,
ContextMenuPrintCommandExtensionMainFrame) {
content::RenderFrameHost* extension_host =
LoadPdfGetExtensionHost(embedded_test_server()->GetURL("/pdf/test.pdf"));
ASSERT_TRUE(extension_host);
content::WebContents* embedder_web_contents = GetEmbedderWebContents();
ASSERT_TRUE(embedder_web_contents);
// Makes sure that the correct frame invoked the context menu.
content::ContextMenuInterceptor menu_interceptor(extension_host);
// Executes the print command as soon as the context menu is shown.
ContextMenuNotificationObserver context_menu_observer(IDC_PRINT);
printing::TestPrintPreviewObserver print_observer(/*wait_for_loaded=*/false);
SimulateMouseClickAt(extension_host, embedder_web_contents,
blink::WebInputEvent::kNoModifiers,
blink::WebMouseEvent::Button::kRight, {1, 1});
print_observer.WaitUntilPreviewIsReady();
menu_interceptor.Wait();
}
IN_PROC_BROWSER_TEST_P(PDFExtensionPrintingTest,
ContextMenuPrintCommandEmbeddedExtensionMainFrame) {
content::RenderFrameHost* extension_host =
LoadPdfInFirstChildGetExtensionHost(
embedded_test_server()->GetURL("/pdf/pdf_embed.html"));
ASSERT_TRUE(extension_host);
content::WebContents* embedder_web_contents = GetEmbedderWebContents();
ASSERT_TRUE(embedder_web_contents);
// Makes sure that the correct frame invoked the context menu.
content::ContextMenuInterceptor menu_interceptor(extension_host);
// Executes the print command as soon as the context menu is shown.
ContextMenuNotificationObserver context_menu_observer(IDC_PRINT);
printing::TestPrintPreviewObserver print_observer(/*wait_for_loaded=*/false);
SimulateMouseClickAt(extension_host, embedder_web_contents,
blink::WebInputEvent::kNoModifiers,
blink::WebMouseEvent::Button::kRight, {1, 1});
print_observer.WaitUntilPreviewIsReady();
menu_interceptor.Wait();
}
IN_PROC_BROWSER_TEST_P(PDFExtensionPrintingTest,
ContextMenuPrintCommandPluginFrame) {
content::RenderFrameHost* extension_host =
LoadPdfGetExtensionHost(embedded_test_server()->GetURL("/pdf/test.pdf"));
ASSERT_TRUE(extension_host);
content::RenderFrameHost* plugin_frame =
pdf_extension_test_util::GetOnlyPdfPluginFrame(GetActiveWebContents());
ASSERT_TRUE(plugin_frame);
content::WebContents* embedder_web_contents = GetEmbedderWebContents();
ASSERT_TRUE(embedder_web_contents);
// Makes sure that the correct frame invoked the context menu.
content::ContextMenuInterceptor menu_interceptor(plugin_frame);
// Executes the print command as soon as the context menu is shown.
ContextMenuNotificationObserver context_menu_observer(IDC_PRINT);
printing::TestPrintPreviewObserver print_observer(/*wait_for_loaded=*/false);
SetInputFocusOnPlugin(extension_host, embedder_web_contents);
plugin_frame->GetRenderWidgetHost()->ShowContextMenuAtPoint(
{1, 1}, ui::mojom::MenuSourceType::kMouse);
print_observer.WaitUntilPreviewIsReady();
menu_interceptor.Wait();
}
// TODO(crbug.com/40842943): Fix flakiness.
IN_PROC_BROWSER_TEST_P(PDFExtensionPrintingTest,
DISABLED_ContextMenuPrintCommandEmbeddedPluginFrame) {
content::RenderFrameHost* extension_host =
LoadPdfInFirstChildGetExtensionHost(
embedded_test_server()->GetURL("/pdf/pdf_embed.html"));
ASSERT_TRUE(extension_host);
content::RenderFrameHost* plugin_frame =
pdf_extension_test_util::GetOnlyPdfPluginFrame(GetActiveWebContents());
ASSERT_TRUE(plugin_frame);
content::WebContents* embedder_web_contents = GetEmbedderWebContents();
ASSERT_TRUE(embedder_web_contents);
// Makes sure that the correct frame invoked the context menu.
content::ContextMenuInterceptor menu_interceptor(plugin_frame);
// Executes the print command as soon as the context menu is shown.
ContextMenuNotificationObserver context_menu_observer(IDC_PRINT);
printing::TestPrintPreviewObserver print_observer(/*wait_for_loaded=*/false);
SetInputFocusOnPlugin(extension_host, embedder_web_contents);
plugin_frame->GetRenderWidgetHost()->ShowContextMenuAtPoint(
{1, 1}, ui::mojom::MenuSourceType::kMouse);
print_observer.WaitUntilPreviewIsReady();
menu_interceptor.Wait();
}
IN_PROC_BROWSER_TEST_P(PDFExtensionPrintingTest, PrintButton) {
content::RenderFrameHost* extension_host =
LoadPdfGetExtensionHost(embedded_test_server()->GetURL("/pdf/test.pdf"));
ASSERT_TRUE(extension_host);
printing::TestPrintPreviewObserver print_observer(/*wait_for_loaded=*/false);
constexpr char kClickPrintButtonScript[] = R"(
viewer.shadowRoot.querySelector('#toolbar')
.shadowRoot.querySelector('#print')
.click();
)";
EXPECT_TRUE(ExecJs(extension_host, kClickPrintButtonScript));
print_observer.WaitUntilPreviewIsReady();
}
#endif // BUILDFLAG(ENABLE_PRINT_PREVIEW)
INSTANTIATE_TEST_SUITE_P(All,
PDFExtensionPrintingTest,
testing::Combine(
#if BUILDFLAG(ENABLE_OOP_PRINTING)
testing::Bool(),
#else
testing::Values(false),
#endif
testing::Bool()),
PDFExtensionPrintingTestPassToString());
class PDFExtensionBasicPrintingTest : public PDFExtensionPrintingTest {
public:
PDFExtensionBasicPrintingTest() = default;
~PDFExtensionBasicPrintingTest() override = default;
// PDFExtensionPrintingTest:
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitch(switches::kDisablePrintPreview);
PDFExtensionPrintingTest::SetUpCommandLine(command_line);
}
};
// TODO(crbug.com/40283511): Test is flaky.
#if BUILDFLAG(IS_MAC)
#define MAYBE_ContextMenuPrintCommandExtensionMainFrame \
DISABLED_ContextMenuPrintCommandExtensionMainFrame
#else
#define MAYBE_ContextMenuPrintCommandExtensionMainFrame \
ContextMenuPrintCommandExtensionMainFrame
#endif
IN_PROC_BROWSER_TEST_P(PDFExtensionBasicPrintingTest,
MAYBE_ContextMenuPrintCommandExtensionMainFrame) {
content::RenderFrameHost* extension_host =
LoadPdfGetExtensionHost(embedded_test_server()->GetURL("/pdf/test.pdf"));
ASSERT_TRUE(extension_host);
content::RenderFrameHost* plugin_frame =
pdf_extension_test_util::GetOnlyPdfPluginFrame(GetActiveWebContents());
ASSERT_TRUE(plugin_frame);
content::WebContents* embedder_web_contents = GetEmbedderWebContents();
ASSERT_TRUE(embedder_web_contents);
// Makes sure that the correct frame invoked the context menu.
content::ContextMenuInterceptor menu_interceptor(plugin_frame);
// Executes the print command as soon as the context menu is shown.
ContextMenuNotificationObserver context_menu_observer(IDC_PRINT);
SetupPrintViewManagerForJobMonitoring(plugin_frame);
SetInputFocusOnPlugin(extension_host, embedder_web_contents);
SimulateMouseClickAt(plugin_frame, embedder_web_contents,
blink::WebInputEvent::kNoModifiers,
blink::WebMouseEvent::Button::kRight, {1, 1});
menu_interceptor.Wait();
WaitForPrintJobDestruction();
}
INSTANTIATE_TEST_SUITE_P(All,
PDFExtensionBasicPrintingTest,
testing::Combine(testing::Bool(), testing::Bool()),
PDFExtensionPrintingTestPassToString());