blob: dcfe64fa5456da0938d786a5b044686683180f94 [file] [log] [blame]
// Copyright 2017 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 <utility>
#include "base/auto_reset.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/notreached.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/values.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/printing/print_job_manager.h"
#include "chrome/browser/printing/print_job_worker.h"
#include "chrome/browser/printing/print_test_utils.h"
#include "chrome/browser/printing/print_view_manager.h"
#include "chrome/browser/printing/print_view_manager_base.h"
#include "chrome/browser/printing/printer_query.h"
#include "chrome/browser/printing/test_print_job.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/test/base/browser_with_test_window_test.h"
#include "components/printing/common/print.mojom.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_renderer_host.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "printing/print_settings.h"
#include "printing/print_settings_conversion.h"
#include "printing/units.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#if BUILDFLAG(IS_WIN)
#include "printing/mojom/print.mojom.h"
#endif
namespace printing {
using PrintViewManagerTest = BrowserWithTestWindowTest;
namespace {
class TestPrintQueriesQueue : public PrintQueriesQueue {
public:
TestPrintQueriesQueue() = default;
TestPrintQueriesQueue(const TestPrintQueriesQueue&) = delete;
TestPrintQueriesQueue& operator=(const TestPrintQueriesQueue&) = delete;
// Creates a `TestPrinterQuery`. Sets up the printer query with the printer
// settings indicated by `printable_offset_x_`, `printable_offset_y_`, and
// `print_driver_type_`.
std::unique_ptr<PrinterQuery> CreatePrinterQuery(
content::GlobalRenderFrameHostId rfh_id) override;
// Sets the printer's printable area offsets to `offset_x` and `offset_y`,
// which should be in pixels. Used to fill in printer settings that would
// normally be filled in by the backend `PrintingContext`.
void SetupPrinterOffsets(int offset_x, int offset_y);
#if BUILDFLAG(IS_WIN)
// Sets the printer type to `type`. Used to fill in printer settings that
// would normally be filled in by the backend `PrintingContext`.
void SetupPrinterLanguageType(mojom::PrinterLanguageType type);
#endif
private:
~TestPrintQueriesQueue() override = default;
#if BUILDFLAG(IS_WIN)
mojom::PrinterLanguageType printer_language_type_;
#endif
int printable_offset_x_;
int printable_offset_y_;
};
class TestPrinterQuery : public PrinterQuery {
public:
// Can only be called on the IO thread, since this inherits from
// `PrinterQuery`.
explicit TestPrinterQuery(content::GlobalRenderFrameHostId rfh_id);
TestPrinterQuery(const TestPrinterQuery&) = delete;
TestPrinterQuery& operator=(const TestPrinterQuery&) = delete;
~TestPrinterQuery() override;
// Updates the current settings with `new_settings` dictionary values. Also
// fills in the settings with values from `offsets_` and `printer_type_` that
// would normally be filled in by the `PrintingContext`.
void SetSettings(base::Value::Dict new_settings,
base::OnceClosure callback) override;
#if BUILDFLAG(IS_WIN)
// Sets `printer_language_type_` to `type`. Should be called before
// `SetSettings()`.
void SetPrinterLanguageType(mojom::PrinterLanguageType type);
#endif
// Sets printer offsets to `offset_x` and `offset_y`, which should be in DPI.
// Should be called before `SetSettings()`.
void SetPrintableAreaOffsets(int offset_x, int offset_y);
private:
absl::optional<gfx::Point> offsets_;
#if BUILDFLAG(IS_WIN)
absl::optional<mojom::PrinterLanguageType> printer_language_type_;
#endif
};
std::unique_ptr<PrinterQuery> TestPrintQueriesQueue::CreatePrinterQuery(
content::GlobalRenderFrameHostId rfh_id) {
auto test_query = std::make_unique<TestPrinterQuery>(rfh_id);
#if BUILDFLAG(IS_WIN)
test_query->SetPrinterLanguageType(printer_language_type_);
#endif
test_query->SetPrintableAreaOffsets(printable_offset_x_, printable_offset_y_);
return test_query;
}
void TestPrintQueriesQueue::SetupPrinterOffsets(int offset_x, int offset_y) {
printable_offset_x_ = offset_x;
printable_offset_y_ = offset_y;
}
#if BUILDFLAG(IS_WIN)
void TestPrintQueriesQueue::SetupPrinterLanguageType(
mojom::PrinterLanguageType type) {
printer_language_type_ = type;
}
#endif
TestPrinterQuery::TestPrinterQuery(content::GlobalRenderFrameHostId rfh_id)
: PrinterQuery(rfh_id) {}
TestPrinterQuery::~TestPrinterQuery() = default;
void TestPrinterQuery::SetSettings(base::Value::Dict new_settings,
base::OnceClosure callback) {
DCHECK(offsets_);
#if BUILDFLAG(IS_WIN)
DCHECK(printer_language_type_);
#endif
std::unique_ptr<PrintSettings> settings =
PrintSettingsFromJobSettings(new_settings);
mojom::ResultCode result = mojom::ResultCode::kSuccess;
if (!settings) {
settings = std::make_unique<PrintSettings>();
result = mojom::ResultCode::kFailed;
}
float device_microns_per_device_unit =
static_cast<float>(kMicronsPerInch) / settings->device_units_per_inch();
gfx::Size paper_size =
gfx::Size(settings->requested_media().size_microns.width() /
device_microns_per_device_unit,
settings->requested_media().size_microns.height() /
device_microns_per_device_unit);
gfx::Rect paper_rect(0, 0, paper_size.width(), paper_size.height());
paper_rect.Inset(gfx::Insets::VH(offsets_->y(), offsets_->x()));
settings->SetPrinterPrintableArea(paper_size, paper_rect, true);
#if BUILDFLAG(IS_WIN)
settings->set_printer_language_type(*printer_language_type_);
#endif
GetSettingsDone(std::move(callback), /*maybe_is_modifiable=*/absl::nullopt,
std::move(settings), result);
}
#if BUILDFLAG(IS_WIN)
void TestPrinterQuery::SetPrinterLanguageType(mojom::PrinterLanguageType type) {
printer_language_type_ = type;
}
#endif
void TestPrinterQuery::SetPrintableAreaOffsets(int offset_x, int offset_y) {
offsets_ = gfx::Point(offset_x, offset_y);
}
class TestPrintViewManagerForSystemDialogPrint : public PrintViewManager {
public:
explicit TestPrintViewManagerForSystemDialogPrint(
content::WebContents* web_contents)
: PrintViewManager(web_contents) {}
~TestPrintViewManagerForSystemDialogPrint() override = default;
// PrintViewManager:
void PrintForSystemDialogImpl() override {
// There has to be a target frame so DidShowPrintDialog() does not crash.
// Manually set it, as there is no IPC in progress.
print_manager_host_receivers_for_testing().SetCurrentTargetFrameForTesting(
web_contents()->GetPrimaryMainFrame());
DidShowPrintDialog();
print_manager_host_receivers_for_testing().SetCurrentTargetFrameForTesting(
nullptr);
}
};
} // 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 {
// Set this null here. Otherwise, the `PrintViewManagerBase` destructor
// will try to de-register for notifications that were not registered for
// in `CreateNewPrintJob()`.
print_job_ = nullptr;
}
// Mostly copied from `PrintViewManager::PrintPreviewNow()`. We can't
// override `PrintViewManager` since it is a user data class.
bool PrintPreviewNow(content::RenderFrameHost* rfh, bool has_selection) {
// Don't print / print preview crashed tabs.
if (IsCrashed())
return false;
mojo::AssociatedRemote<mojom::PrintRenderFrame> print_render_frame;
rfh->GetRemoteAssociatedInterfaces()->GetInterface(&print_render_frame);
print_render_frame->InitiatePrintPreview(mojo::NullAssociatedRemote(),
has_selection);
return true;
}
// Getters for validating arguments to StartPdf...Conversion functions
const gfx::Size& page_size() { return test_job()->page_size(); }
const gfx::Rect& content_area() { return test_job()->content_area(); }
const gfx::Point& physical_offsets() {
return test_job()->physical_offsets();
}
#if BUILDFLAG(IS_WIN)
mojom::PrinterLanguageType type() { return test_job()->type(); }
#endif
// Ends the run loop.
void FakePrintCallback(const base::Value& error) {
DCHECK(run_loop_);
run_loop_->Quit();
}
// Starts a run loop that quits when the print callback is called to indicate
// printing is complete.
void WaitForCallback() {
base::RunLoop run_loop;
base::AutoReset<base::RunLoop*> auto_reset(&run_loop_, &run_loop);
run_loop.Run();
}
protected:
// Override to create a `TestPrintJob` instead of a real one.
bool CreateNewPrintJob(std::unique_ptr<PrinterQuery> query) override {
print_job_ = base::MakeRefCounted<TestPrintJob>();
print_job_->Initialize(std::move(query), RenderSourceName(),
number_pages());
#if BUILDFLAG(IS_CHROMEOS)
print_job_->SetSource(PrintJob::Source::PRINT_PREVIEW, /*source_id=*/"");
#endif // BUILDFLAG(IS_CHROMEOS)
return true;
}
void SetupScriptedPrintPreview(
SetupScriptedPrintPreviewCallback callback) override {
NOTREACHED();
}
void ShowScriptedPrintPreview(bool is_modifiable) override { NOTREACHED(); }
void RequestPrintPreview(
mojom::RequestPrintPreviewParamsPtr params) override {
NOTREACHED();
}
void CheckForCancel(int32_t preview_ui_id,
int32_t request_id,
CheckForCancelCallback callback) override {
NOTREACHED();
}
private:
TestPrintJob* test_job() {
return static_cast<TestPrintJob*>(print_job_.get());
}
base::RunLoop* run_loop_ = nullptr;
};
TEST_F(PrintViewManagerTest, PrintSubFrameAndDestroy) {
chrome::NewTab(browser());
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
content::RenderFrameHost* sub_frame =
content::RenderFrameHostTester::For(web_contents->GetPrimaryMainFrame())
->AppendChild("child");
PrintViewManager* print_view_manager =
PrintViewManager::FromWebContents(web_contents);
ASSERT_TRUE(print_view_manager);
EXPECT_FALSE(print_view_manager->print_preview_rfh());
print_view_manager->PrintPreviewNow(sub_frame, false);
EXPECT_TRUE(print_view_manager->print_preview_rfh());
content::RenderFrameHostTester::For(sub_frame)->Detach();
EXPECT_FALSE(print_view_manager->print_preview_rfh());
}
TEST_F(PrintViewManagerTest, PrintForSystemDialog) {
chrome::NewTab(browser());
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
auto print_view_manager =
std::make_unique<TestPrintViewManagerForSystemDialogPrint>(web_contents);
ASSERT_TRUE(print_view_manager->PrintPreviewNow(
web_contents->GetPrimaryMainFrame(), /*has_selection=*/false));
base::RunLoop run_loop;
bool dialog_shown = false;
EXPECT_TRUE(print_view_manager->PrintForSystemDialogNow(
base::BindLambdaForTesting([&]() {
dialog_shown = true;
run_loop.Quit();
})));
run_loop.Run();
EXPECT_TRUE(dialog_shown);
print_view_manager->PrintPreviewDone();
}
#if BUILDFLAG(IS_WIN)
// Verifies that `StartPdfToPostScriptConversion` is called with the correct
// printable area offsets. See crbug.com/821485.
TEST_F(PrintViewManagerTest, PostScriptHasCorrectOffsets) {
scoped_refptr<TestPrintQueriesQueue> queue =
base::MakeRefCounted<TestPrintQueriesQueue>();
// Setup PostScript printer with printable area offsets of 0.1in.
queue->SetupPrinterLanguageType(
mojom::PrinterLanguageType::kPostscriptLevel2);
int offset_in_pixels = static_cast<int>(kTestPrinterDpi * 0.1f);
queue->SetupPrinterOffsets(offset_in_pixels, offset_in_pixels);
g_browser_process->print_job_manager()->SetQueueForTest(queue);
chrome::NewTab(browser());
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
std::unique_ptr<TestPrintViewManager> print_view_manager =
std::make_unique<TestPrintViewManager>(web_contents);
PrintViewManager::SetReceiverImplForTesting(print_view_manager.get());
print_view_manager->PrintPreviewNow(web_contents->GetPrimaryMainFrame(),
false);
base::Value::Dict print_ticket = GetPrintTicket(mojom::PrinterType::kLocal);
const char kTestData[] = "abc";
auto print_data = base::MakeRefCounted<base::RefCountedStaticMemory>(
kTestData, sizeof(kTestData));
PrinterHandler::PrintCallback callback =
base::BindOnce(&TestPrintViewManager::FakePrintCallback,
base::Unretained(print_view_manager.get()));
print_view_manager->PrintForPrintPreview(std::move(print_ticket), print_data,
web_contents->GetPrimaryMainFrame(),
std::move(callback));
print_view_manager->WaitForCallback();
EXPECT_EQ(gfx::Point(60, 60), print_view_manager->physical_offsets());
EXPECT_EQ(gfx::Rect(0, 0, 5100, 6600), print_view_manager->content_area());
EXPECT_EQ(mojom::PrinterLanguageType::kPostscriptLevel2,
print_view_manager->type());
PrintViewManager::SetReceiverImplForTesting(nullptr);
}
#endif
} // namespace printing