blob: 061aed42cf58fb3a73151b9da36647180d135480 [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 <vector>
#include "base/auto_reset.h"
#include "base/check_op.h"
#include "base/containers/contains.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/path_service.h"
#include "base/ranges/algorithm.h"
#include "base/run_loop.h"
#include "base/strings/string_piece.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/enterprise/connectors/analysis/content_analysis_dialog.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/printing/print_error_dialog.h"
#include "chrome/browser/printing/print_job.h"
#include "chrome/browser/printing/print_job_manager.h"
#include "chrome/browser/printing/print_view_manager.h"
#include "chrome/browser/printing/print_view_manager_common.h"
#include "chrome/browser/printing/printer_query.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/chrome_switches.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 "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/global_routing_id.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 "content/public/test/fenced_frame_test_util.h"
#include "content/public/test/prerender_test_util.h"
#include "extensions/common/extension.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "printing/backend/test_print_backend.h"
#include "printing/buildflags/buildflags.h"
#include "printing/mojom/print.mojom.h"
#include "printing/page_setup.h"
#include "printing/print_settings.h"
#include "printing/printing_context.h"
#include "printing/printing_context_factory_for_test.h"
#include "printing/printing_features.h"
#include "printing/printing_utils.h"
#include "printing/test_printing_context.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/scheduler/web_scheduler_tracked_feature.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.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"
#include "chrome/browser/printing/print_job_worker_oop.h"
#include "chrome/browser/printing/printer_query_oop.h"
#include "chrome/services/printing/public/mojom/print_backend_service.mojom.h"
#endif
#if BUILDFLAG(ENABLE_PRINT_CONTENT_ANALYSIS)
#include "chrome/browser/enterprise/connectors/analysis/fake_content_analysis_delegate.h"
#include "chrome/browser/enterprise/connectors/common.h"
#include "chrome/browser/policy/dm_token_utils.h"
#include "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_test_utils.h"
#endif // BUILDFLAG(ENABLE_PRINT_CONTENT_ANALYSIS)
namespace printing {
using testing::_;
using OnDidCreatePrintJobCallback =
base::RepeatingCallback<void(PrintJob* print_job)>;
#if BUILDFLAG(ENABLE_OOP_PRINTING)
using OnUseDefaultSettingsCallback = base::RepeatingClosure;
using OnGetSettingsWithUICallback = base::RepeatingClosure;
using ErrorCheckCallback =
base::RepeatingCallback<void(mojom::ResultCode result)>;
using OnDidUseDefaultSettingsCallback =
base::RepeatingCallback<void(mojom::ResultCode result)>;
#if BUILDFLAG(IS_WIN)
using OnDidAskUserForSettingsCallback =
base::RepeatingCallback<void(mojom::ResultCode result)>;
#endif
using OnDidStartPrintingCallback =
base::RepeatingCallback<void(mojom::ResultCode result,
PrintJob* print_job)>;
#if BUILDFLAG(IS_WIN)
using OnDidRenderPrintedPageCallback =
base::RepeatingCallback<void(uint32_t page_number,
mojom::ResultCode result)>;
#endif
using OnDidRenderPrintedDocumentCallback =
base::RepeatingCallback<void(mojom::ResultCode result)>;
using OnDidDocumentDoneCallback =
base::RepeatingCallback<void(mojom::ResultCode result)>;
using OnDidCancelCallback = base::RepeatingClosure;
using OnDidShowErrorDialog = base::RepeatingClosure;
#endif // BUILDFLAG(ENABLE_OOP_PRINTING)
namespace {
constexpr int kTestPrintingDpi = 72;
constexpr int kTestPrinterCapabilitiesMaxCopies = 99;
constexpr gfx::Size kTestPrinterCapabilitiesDpi(kTestPrintingDpi,
kTestPrintingDpi);
constexpr int kTestPrintSettingsCopies = 42;
const std::vector<gfx::Size> kTestPrinterCapabilitiesDefaultDpis{
kTestPrinterCapabilitiesDpi};
const PrinterBasicInfoOptions kTestDummyPrintInfoOptions{{"opt1", "123"},
{"opt2", "456"}};
constexpr int kDefaultDocumentCookie = 1234;
#if BUILDFLAG(ENABLE_PRINT_CONTENT_ANALYSIS)
constexpr char kFakeDmToken[] = "fake-dm-token";
#endif // BUILDFLAG(ENABLE_PRINT_CONTENT_ANALYSIS)
std::unique_ptr<TestPrintingContext> MakeDefaultTestPrintingContext(
PrintingContext::Delegate* delegate,
bool skip_system_calls,
const std::string& printer_name) {
auto context =
std::make_unique<TestPrintingContext>(delegate, skip_system_calls);
// Setup a sample page setup, which is needed to pass checks in
// `PrintRenderFrameHelper` that the print params are valid.
constexpr gfx::Size kPhysicalSize = gfx::Size(200, 200);
constexpr gfx::Rect kPrintableArea = gfx::Rect(0, 0, 200, 200);
const PageMargins kRequestedMargins(0, 0, 5, 5, 5, 5);
const PageSetup kPageSetup(kPhysicalSize, kPrintableArea, kRequestedMargins,
/*forced_margins=*/false,
/*text_height=*/0);
auto settings = std::make_unique<PrintSettings>();
settings->set_copies(kTestPrintSettingsCopies);
settings->set_dpi(kTestPrintingDpi);
settings->set_page_setup_device_units(kPageSetup);
settings->set_device_name(base::ASCIIToUTF16(printer_name));
context->SetDeviceSettings(printer_name, std::move(settings));
return context;
}
void OnDidUpdatePrintSettings(
std::unique_ptr<PrintSettings>& snooped_settings,
scoped_refptr<PrintQueriesQueue> queue,
std::unique_ptr<PrinterQuery> printer_query,
mojom::PrintManagerHost::UpdatePrintSettingsCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(printer_query);
auto params = mojom::PrintPagesParams::New();
params->params = mojom::PrintParams::New();
if (printer_query->last_status() == mojom::ResultCode::kSuccess) {
RenderParamsFromPrintSettings(printer_query->settings(),
params->params.get());
params->params->document_cookie = printer_query->cookie();
params->pages = printer_query->settings().ranges();
snooped_settings =
std::make_unique<PrintSettings>(printer_query->settings());
}
bool canceled = printer_query->last_status() == mojom::ResultCode::kCanceled;
std::move(callback).Run(std::move(params), canceled);
if (printer_query->cookie() && printer_query->settings().dpi()) {
queue->QueuePrinterQuery(std::move(printer_query));
}
}
class BrowserPrintingContextFactoryForTest
: public PrintingContextFactoryForTest {
public:
std::unique_ptr<PrintingContext> CreatePrintingContext(
PrintingContext::Delegate* delegate,
bool skip_system_calls) override {
auto context = MakeDefaultTestPrintingContext(delegate, skip_system_calls,
printer_name_);
if (failed_error_for_new_document_)
context->SetNewDocumentFails();
if (access_denied_errors_for_new_document_)
context->SetNewDocumentBlockedByPermissions();
#if BUILDFLAG(IS_WIN)
if (access_denied_errors_for_render_page_)
context->SetOnRenderPageBlockedByPermissions();
if (failed_error_for_render_page_number_) {
context->SetOnRenderPageFailsForPage(
failed_error_for_render_page_number_);
}
#endif
if (access_denied_errors_for_render_document_)
context->SetOnRenderDocumentBlockedByPermissions();
if (access_denied_errors_for_document_done_)
context->SetDocumentDoneBlockedByPermissions();
if (fail_on_use_default_settings_)
context->SetUseDefaultSettingsFails();
#if BUILDFLAG(IS_WIN)
if (cancel_on_ask_user_for_settings_)
context->SetAskUserForSettingsCanceled();
#endif
context->SetNewDocumentCalledClosure(base::BindRepeating(
&BrowserPrintingContextFactoryForTest::NewDocumentCalled,
base::Unretained(this)));
return std::move(context);
}
void SetPrinterNameForSubsequentContexts(const std::string& printer_name) {
printer_name_ = printer_name;
}
void SetFailedErrorOnNewDocument(bool cause_errors) {
failed_error_for_new_document_ = cause_errors;
}
void SetAccessDeniedErrorOnNewDocument(bool cause_errors) {
access_denied_errors_for_new_document_ = cause_errors;
}
#if BUILDFLAG(IS_WIN)
void SetAccessDeniedErrorOnRenderPage(bool cause_errors) {
access_denied_errors_for_render_page_ = cause_errors;
}
void SetFailedErrorForRenderPage(uint32_t page_number) {
failed_error_for_render_page_number_ = page_number;
}
#endif
void SetAccessDeniedErrorOnRenderDocument(bool cause_errors) {
access_denied_errors_for_render_document_ = cause_errors;
}
void SetAccessDeniedErrorOnDocumentDone(bool cause_errors) {
access_denied_errors_for_document_done_ = cause_errors;
}
void SetFailErrorOnUseDefaultSettings() {
fail_on_use_default_settings_ = true;
}
#if BUILDFLAG(ENABLE_BASIC_PRINT_DIALOG)
void SetCancelErrorOnAskUserForSettings() {
cancel_on_ask_user_for_settings_ = true;
}
#endif
void NewDocumentCalled() { ++new_document_called_count_; }
int new_document_called_count() { return new_document_called_count_; }
private:
std::string printer_name_;
bool failed_error_for_new_document_ = false;
bool access_denied_errors_for_new_document_ = false;
#if BUILDFLAG(IS_WIN)
bool access_denied_errors_for_render_page_ = false;
uint32_t failed_error_for_render_page_number_ = 0;
#endif
bool access_denied_errors_for_render_document_ = false;
bool access_denied_errors_for_document_done_ = false;
bool fail_on_use_default_settings_ = false;
#if BUILDFLAG(ENABLE_BASIC_PRINT_DIALOG)
bool cancel_on_ask_user_for_settings_ = false;
#endif
int new_document_called_count_ = 0;
};
class PrintPreviewObserver : PrintPreviewUI::TestDelegate {
public:
explicit PrintPreviewObserver(bool wait_for_loaded)
: PrintPreviewObserver(wait_for_loaded, /*pages_per_sheet=*/1) {}
PrintPreviewObserver(bool wait_for_loaded, int pages_per_sheet)
: pages_per_sheet_(pages_per_sheet), wait_for_loaded_(wait_for_loaded) {
PrintPreviewUI::SetDelegateForTesting(this);
}
PrintPreviewObserver(const PrintPreviewObserver&) = delete;
PrintPreviewObserver& operator=(const PrintPreviewObserver&) = delete;
~PrintPreviewObserver() override {
PrintPreviewUI::SetDelegateForTesting(nullptr);
}
void WaitUntilPreviewIsReady() {
if (rendered_page_count_ >= expected_rendered_page_count_)
return;
base::RunLoop run_loop;
base::AutoReset<base::RunLoop*> 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_; }
uint32_t rendered_page_count() const { return rendered_page_count_; }
private:
// PrintPreviewUI::TestDelegate:
void DidGetPreviewPageCount(uint32_t page_count) override {
// `page_count` is the number of pages to be generated but doesn't take
// N-up into consideration. Since `DidRenderPreviewPage()` is called after
// any N-up processing is performed, determine the number of times that
// function is expected to be called.
expected_rendered_page_count_ =
(page_count + pages_per_sheet_ - 1) / pages_per_sheet_;
}
// PrintPreviewUI::TestDelegate:
void DidRenderPreviewPage(content::WebContents* preview_dialog) override {
++rendered_page_count_;
DVLOG(2) << "Rendered preview page " << rendered_page_count_
<< " of a total expected " << expected_rendered_page_count_;
CHECK_LE(rendered_page_count_, expected_rendered_page_count_);
if (rendered_page_count_ == expected_rendered_page_count_ && run_loop_) {
run_loop_->Quit();
preview_dialog_ = preview_dialog;
if (wait_for_loaded_) {
// Instantiate `queue_` to listen for messages in `preview_dialog_`.
queue_.emplace(preview_dialog_);
content::ExecuteScriptAsync(
preview_dialog_.get(),
"window.addEventListener('message', event => {"
" if (event.data.type === 'documentLoaded') {"
" domAutomationController.send(event.data.load_state);"
" }"
"});");
}
}
}
absl::optional<content::DOMMessageQueue> queue_;
// Rendered pages are provided after N-up processing, which will be different
// from the count provided to `DidGetPreviewPageCount()` when
// `pages_per_sheet_` is larger than one.
const int pages_per_sheet_;
uint32_t expected_rendered_page_count_ = 1;
uint32_t rendered_page_count_ = 0;
const bool wait_for_loaded_;
raw_ptr<content::WebContents, DanglingUntriaged> preview_dialog_ = nullptr;
// This field is not a raw_ptr<> because it was filtered by the rewriter for:
// #addr-of
RAW_PTR_EXCLUSION base::RunLoop* run_loop_ = nullptr;
};
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::SequencedTaskRunner::GetCurrentDefault()),
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);
std::move(callback).Run(document_cookie, std::move(param));
task_runner_->PostTask(FROM_HERE, msg_callback_);
}
void Bind(mojo::ScopedInterfaceEndpointHandle handle) {
receiver_.Bind(mojo::PendingAssociatedReceiver<mojom::PrintRenderFrame>(
std::move(handle)));
}
static mojom::DidPrintContentParamsPtr GetDefaultDidPrintContentParams() {
auto 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);
return printed_frame_params;
}
// mojom::PrintRenderFrameInterceptorForTesting
mojom::PrintRenderFrame* GetForwardingInterface() override {
NOTREACHED();
return nullptr;
}
void PrintFrameContent(mojom::PrintFrameContentParamsPtr params,
PrintFrameContentCallback callback) override {
// Sends the printed result back.
OnDidPrintFrameContent(params->document_cookie,
GetDefaultDidPrintContentParams(),
std::move(callback));
auto* client = PrintCompositeClient::FromWebContents(web_contents_);
if (!client)
return;
// Prints its children.
content::RenderFrameHost* child = ChildFrameAt(frame_host_.get(), 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_.get(), i);
}
}
private:
raw_ptr<content::RenderFrameHost> frame_host_;
raw_ptr<content::WebContents> web_contents_;
const int document_cookie_;
scoped_refptr<base::SequencedTaskRunner> task_runner_;
base::RepeatingClosure msg_callback_;
mojo::AssociatedReceiver<mojom::PrintRenderFrame> 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<mojom::PrintRenderFrame>(
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:
const raw_ptr<content::RenderProcessHost> rph_;
mojo::AssociatedReceiver<mojom::PrintRenderFrame> receiver_{this};
};
} // namespace
class TestPrintViewManager : public PrintViewManager {
public:
explicit TestPrintViewManager(content::WebContents* web_contents)
: PrintViewManager(web_contents) {}
TestPrintViewManager(content::WebContents* web_contents,
OnDidCreatePrintJobCallback callback)
: PrintViewManager(web_contents),
on_did_create_print_job_(std::move(callback)) {}
TestPrintViewManager(const TestPrintViewManager&) = delete;
TestPrintViewManager& operator=(const TestPrintViewManager&) = delete;
~TestPrintViewManager() override = default;
bool StartPrinting(content::WebContents* contents) {
auto* print_view_manager = TestPrintViewManager::FromWebContents(contents);
if (!print_view_manager)
return false;
content::RenderFrameHost* rfh_to_use = GetFrameToPrint(contents);
if (!rfh_to_use)
return false;
return print_view_manager->PrintNow(rfh_to_use);
}
void WaitUntilPreviewIsShownOrCancelled() {
base::RunLoop run_loop;
base::AutoReset<base::RunLoop*> auto_reset(&run_loop_, &run_loop);
run_loop.Run();
}
PrintSettings* snooped_settings() { return snooped_settings_.get(); }
const absl::optional<bool>& print_now_result() const {
return print_now_result_;
}
static TestPrintViewManager* CreateForWebContents(
content::WebContents* web_contents) {
auto manager = std::make_unique<TestPrintViewManager>(web_contents);
auto* manager_ptr = manager.get();
web_contents->SetUserData(PrintViewManager::UserDataKey(),
std::move(manager));
return manager_ptr;
}
// `PrintViewManagerBase` overrides.
bool PrintNow(content::RenderFrameHost* rfh) override {
print_now_result_ = PrintViewManager::PrintNow(rfh);
return *print_now_result_;
}
void ShowInvalidPrinterSettingsError() override { ShowPrintErrorDialog(); }
bool CreateNewPrintJob(std::unique_ptr<PrinterQuery> query) override {
if (!PrintViewManager::CreateNewPrintJob(std::move(query)))
return false;
if (on_did_create_print_job_)
on_did_create_print_job_.Run(print_job_.get());
return true;
}
protected:
// This field is not a raw_ptr<> because it was filtered by the rewriter for:
// #addr-of
RAW_PTR_EXCLUSION base::RunLoop* run_loop_ = nullptr;
private:
void PrintPreviewAllowedForTesting() override {
if (run_loop_) {
run_loop_->Quit();
}
}
// printing::mojom::PrintManagerHost:
void UpdatePrintSettings(int32_t cookie,
base::Value::Dict job_settings,
UpdatePrintSettingsCallback callback) override {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
std::unique_ptr<PrinterQuery> printer_query =
queue_->PopPrinterQuery(cookie);
if (!printer_query) {
printer_query =
queue_->CreatePrinterQuery(content::GlobalRenderFrameHostId());
}
auto* printer_query_ptr = printer_query.get();
printer_query_ptr->SetSettings(
std::move(job_settings),
base::BindOnce(&OnDidUpdatePrintSettings, std::ref(snooped_settings_),
queue_, std::move(printer_query), std::move(callback)));
}
std::unique_ptr<PrintSettings> snooped_settings_;
absl::optional<bool> print_now_result_;
OnDidCreatePrintJobCallback on_did_create_print_job_;
};
class TestPrintViewManagerForDLP : public TestPrintViewManager {
public:
// Used to simulate Data Leak Prevention polices and possible user actions.
enum class RestrictionLevel {
// No DLP restrictions set - printing is allowed.
kNotSet,
// The user is warned and selects "continue" - printing is allowed.
kWarnAllow,
// The user is warned and selects "cancel" - printing is not allowed.
kWarnCancel,
// Printing is blocked, no print preview is shown.
kBlock,
};
static TestPrintViewManagerForDLP* CreateForWebContents(
content::WebContents* web_contents,
RestrictionLevel restriction_level) {
auto manager = std::make_unique<TestPrintViewManagerForDLP>(
web_contents, restriction_level);
auto* manager_ptr = manager.get();
web_contents->SetUserData(PrintViewManager::UserDataKey(),
std::move(manager));
return manager_ptr;
}
// Used by the TestPrintViewManagerForDLP to check that the correct action is
// taken based on the restriction level.
enum class PrintAllowance {
// No checks done yet to determine whether printing is allowed or not.
kUnknown,
// There are no restrictions/user allowed printing.
kAllowed,
// There are BLOCK restrictions or user canceled the printing.
kDisallowed,
};
TestPrintViewManagerForDLP(content::WebContents* web_contents,
RestrictionLevel restriction_level)
: TestPrintViewManager(web_contents),
restriction_level_(restriction_level) {
PrintViewManager::SetReceiverImplForTesting(this);
}
TestPrintViewManagerForDLP(const TestPrintViewManagerForDLP&) = delete;
TestPrintViewManagerForDLP& operator=(const TestPrintViewManagerForDLP&) =
delete;
~TestPrintViewManagerForDLP() override {
PrintViewManager::SetReceiverImplForTesting(nullptr);
}
PrintAllowance GetPrintAllowance() const { return allowance_; }
private:
void RejectPrintPreviewRequestIfRestricted(
content::GlobalRenderFrameHostId rfh_id,
base::OnceCallback<void(bool)> callback) override {
switch (restriction_level_) {
case RestrictionLevel::kNotSet:
case RestrictionLevel::kWarnAllow:
std::move(callback).Run(true);
break;
case RestrictionLevel::kBlock:
case RestrictionLevel::kWarnCancel:
std::move(callback).Run(false);
break;
}
}
void PrintPreviewRejectedForTesting() override {
run_loop_->Quit();
allowance_ = PrintAllowance::kDisallowed;
}
void PrintPreviewAllowedForTesting() override {
run_loop_->Quit();
allowance_ = PrintAllowance::kAllowed;
}
RestrictionLevel restriction_level_ = RestrictionLevel::kNotSet;
PrintAllowance allowance_ = PrintAllowance::kUnknown;
};
#if BUILDFLAG(ENABLE_PRINT_CONTENT_ANALYSIS)
class TestPrintViewManagerForContentAnalysis : public TestPrintViewManager {
public:
class Observer : public PrintViewManagerBase::Observer {
public:
void OnPrintNow(const content::RenderFrameHost* rfh) override {
print_now_called_ = true;
}
void OnScriptedPrint() override { scripted_print_called_ = true; }
bool print_now_called() const { return print_now_called_; }
bool scripted_print_called() const { return scripted_print_called_; }
private:
bool print_now_called_ = false;
bool scripted_print_called_ = false;
};
static TestPrintViewManagerForContentAnalysis* CreateForWebContents(
content::WebContents* web_contents) {
auto manager =
std::make_unique<TestPrintViewManagerForContentAnalysis>(web_contents);
auto* manager_ptr = manager.get();
web_contents->SetUserData(PrintViewManager::UserDataKey(),
std::move(manager));
return manager_ptr;
}
explicit TestPrintViewManagerForContentAnalysis(
content::WebContents* web_contents)
: TestPrintViewManager(web_contents) {
AddObserver(observer_);
PrintViewManager::SetReceiverImplForTesting(this);
}
~TestPrintViewManagerForContentAnalysis() override {
PrintViewManager::SetReceiverImplForTesting(nullptr);
}
void WaitOnScanning() { scanning_run_loop_.Run(); }
void WaitOnPreview() { preview_run_loop_.Run(); }
bool print_now_called() const { return observer_.print_now_called(); }
bool scripted_print_called() const {
return observer_.scripted_print_called();
}
const absl::optional<bool>& preview_allowed() const {
return preview_allowed_;
}
#if BUILDFLAG(IS_CHROMEOS)
void set_allowed_by_dlp(bool allowed) { allowed_by_dlp_ = allowed; }
#endif // BUILDFLAG(IS_CHROMEOS)
protected:
void OnGotSnapshotCallback(
base::OnceCallback<void(bool should_proceed)> callback,
enterprise_connectors::ContentAnalysisDelegate::Data data,
content::GlobalRenderFrameHostId rfh_id,
mojom::DidPrintDocumentParamsPtr params) override {
ASSERT_TRUE(web_contents());
ASSERT_TRUE(params);
EXPECT_TRUE(params->content->metafile_data_region.IsValid());
PrintViewManager::OnGotSnapshotCallback(
std::move(callback), std::move(data), rfh_id, std::move(params));
}
void OnCompositedForContentAnalysis(
base::OnceCallback<void(bool should_proceed)> callback,
enterprise_connectors::ContentAnalysisDelegate::Data data,
content::GlobalRenderFrameHostId rfh_id,
mojom::PrintCompositor::Status status,
base::ReadOnlySharedMemoryRegion page_region) override {
EXPECT_TRUE(content::RenderFrameHost::FromID(rfh_id));
EXPECT_EQ(status, mojom::PrintCompositor::Status::kSuccess);
// The settings passed to this function should match the content of the
// print Connector policy.
EXPECT_EQ(data.settings.tags.size(), 1u);
EXPECT_TRUE(base::Contains(data.settings.tags, "dlp"));
EXPECT_TRUE(data.settings.cloud_or_local_settings.is_cloud_analysis());
EXPECT_EQ(data.settings.cloud_or_local_settings.dm_token(), kFakeDmToken);
EXPECT_EQ(data.settings.block_until_verdict,
enterprise_connectors::BlockUntilVerdict::kBlock);
EXPECT_TRUE(data.settings.block_large_files);
// The snapshot should be valid and populated.
EXPECT_TRUE(LooksLikePdf(page_region.Map().GetMemoryAsSpan<char>()));
PrintViewManager::OnCompositedForContentAnalysis(
base::BindOnce(
[](base::OnceCallback<void(bool should_proceed)> callback,
base::RunLoop* scanning_run_loop, bool allowed) {
std::move(callback).Run(allowed);
scanning_run_loop->Quit();
},
std::move(callback), &scanning_run_loop_),
std::move(data), rfh_id, status, std::move(page_region));
}
#if BUILDFLAG(IS_CHROMEOS)
void OnDlpPrintingRestrictionsChecked(
content::GlobalRenderFrameHostId rfh_id,
base::OnceCallback<void(bool should_proceed)> callback,
bool should_proceed) override {
PrintViewManager::OnDlpPrintingRestrictionsChecked(
rfh_id, std::move(callback), allowed_by_dlp_);
}
#endif // BUILDFLAG(IS_CHROMEOS)
void CompleteScriptedPrint(content::RenderFrameHost* rfh,
mojom::ScriptedPrintParamsPtr params,
ScriptedPrintCallback callback) override {
auto print_params = mojom::PrintPagesParams::New();
print_params->params = mojom::PrintParams::New();
std::move(callback).Run(std::move(print_params));
for (auto& observer : GetObservers())
observer.OnScriptedPrint();
}
private:
void PrintPreviewRejectedForTesting() override {
preview_allowed_ = false;
preview_run_loop_.Quit();
}
void PrintPreviewAllowedForTesting() override {
preview_allowed_ = true;
preview_run_loop_.Quit();
}
#if BUILDFLAG(IS_CHROMEOS)
bool allowed_by_dlp_ = true;
#endif // BUILDFLAG(IS_CHROMEOS)
// Indicates whether the preview was allowed after checking against content
// analysis and DLP (if on CrOS). This is unpopulated until then.
absl::optional<bool> preview_allowed_;
base::RunLoop preview_run_loop_;
base::RunLoop scanning_run_loop_;
Observer observer_;
};
#endif // BUILDFLAG(ENABLE_PRINT_CONTENT_ANALYSIS)
class PrintBrowserTest : public InProcessBrowserTest {
public:
struct PrintParams {
bool print_only_selection = false;
int pages_per_sheet = 1;
};
PrintBrowserTest() = default;
~PrintBrowserTest() override = default;
void SetUp() override {
test_print_backend_ = base::MakeRefCounted<TestPrintBackend>();
PrintBackend::SetPrintBackendForTesting(test_print_backend_.get());
PrintingContext::SetPrintingContextFactoryForTest(
&test_printing_context_factory_);
num_expected_messages_ = 1; // By default, only wait on one message.
num_received_messages_ = 0;
InProcessBrowserTest::SetUp();
}
void SetUpOnMainThread() override {
// Safe to use `base::Unretained(this)` since this testing class
// necessarily must outlive all interactions from the tests which will
// run through the printing stack using derivatives of
// `PrintViewManagerBase` and `PrintPreviewHandler`, which can trigger
// this callback.
SetShowPrintErrorDialogForTest(base::BindRepeating(
&PrintBrowserTest::ShowPrintErrorDialog, base::Unretained(this)));
host_resolver()->AddRule("*", "127.0.0.1");
content::SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
}
void TearDownOnMainThread() override {
// Remove map of objects pointing to //content objects before they go away.
frame_content_.clear();
SetShowPrintErrorDialogForTest(base::NullCallback());
InProcessBrowserTest::TearDownOnMainThread();
}
void TearDown() override {
InProcessBrowserTest::TearDown();
PrintingContext::SetPrintingContextFactoryForTest(/*factory=*/nullptr);
PrintBackend::SetPrintBackendForTesting(/*print_backend=*/nullptr);
}
void AddPrinter(const std::string& printer_name) {
PrinterBasicInfo printer_info(
printer_name,
/*display_name=*/"test printer",
/*printer_description=*/"A printer for testing.",
/*printer_status=*/0,
/*is_default=*/true, kTestDummyPrintInfoOptions);
auto default_caps = std::make_unique<PrinterSemanticCapsAndDefaults>();
default_caps->copies_max = kTestPrinterCapabilitiesMaxCopies;
default_caps->dpis = kTestPrinterCapabilitiesDefaultDpis;
default_caps->default_dpi = kTestPrinterCapabilitiesDpi;
test_print_backend_->AddValidPrinter(
printer_name, std::move(default_caps),
std::make_unique<PrinterBasicInfo>(printer_info));
}
void SetPrinterNameForSubsequentContexts(const std::string& printer_name) {
test_printing_context_factory_.SetPrinterNameForSubsequentContexts(
printer_name);
}
void PrintAndWaitUntilPreviewIsReady() {
const PrintParams kParams;
PrintAndWaitUntilPreviewIsReady(kParams);
}
void PrintAndWaitUntilPreviewIsReady(const PrintParams& params) {
PrintPreviewObserver print_preview_observer(/*wait_for_loaded=*/false,
params.pages_per_sheet);
StartPrint(browser()->tab_strip_model()->GetActiveWebContents(),
/*print_renderer=*/mojo::NullAssociatedRemote(),
/*print_preview_disabled=*/false, params.print_only_selection);
print_preview_observer.WaitUntilPreviewIsReady();
set_rendered_page_count(print_preview_observer.rendered_page_count());
}
void PrintAndWaitUntilPreviewIsReadyAndLoaded() {
const PrintParams kParams;
PrintAndWaitUntilPreviewIsReadyAndLoaded(kParams);
}
void PrintAndWaitUntilPreviewIsReadyAndLoaded(const PrintParams& params) {
PrintPreviewObserver print_preview_observer(/*wait_for_loaded=*/true,
params.pages_per_sheet);
StartPrint(browser()->tab_strip_model()->GetActiveWebContents(),
/*print_renderer=*/mojo::NullAssociatedRemote(),
/*print_preview_disabled=*/false, params.print_only_selection);
print_preview_observer.WaitUntilPreviewIsReady();
set_rendered_page_count(print_preview_observer.rendered_page_count());
}
// 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<TestPrintRenderFrame>(
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<mojom::PrintRenderFrame>& GetPrintRenderFrame(
content::RenderFrameHost* rfh) {
if (!remote_)
rfh->GetRemoteAssociatedInterfaces()->GetInterface(&remote_);
return remote_;
}
uint32_t rendered_page_count() const { return rendered_page_count_; }
uint32_t error_dialog_shown_count() const {
return error_dialog_shown_count_;
}
protected:
TestPrintBackend* test_print_backend() { return test_print_backend_.get(); }
BrowserPrintingContextFactoryForTest* test_printing_context_factory() {
return &test_printing_context_factory_;
}
void set_rendered_page_count(uint32_t page_count) {
rendered_page_count_ = page_count;
}
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))));
}
void ShowPrintErrorDialog() {
++error_dialog_shown_count_;
CheckForQuit();
}
uint32_t error_dialog_shown_count_ = 0;
uint32_t rendered_page_count_ = 0;
unsigned int num_expected_messages_;
unsigned int num_received_messages_;
base::OnceClosure quit_callback_;
mojo::AssociatedRemote<mojom::PrintRenderFrame> remote_;
std::map<content::RenderFrameHost*, std::unique_ptr<TestPrintRenderFrame>>
frame_content_;
scoped_refptr<TestPrintBackend> test_print_backend_;
BrowserPrintingContextFactoryForTest test_printing_context_factory_;
};
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_.InitWithFeaturesAndParameters(
{{::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"}}}},
// Allow BackForwardCache for all devices regardless of their memory.
{::features::kBackForwardCacheMemoryControls});
PrintBrowserTest::SetUpCommandLine(command_line);
}
content::WebContents* web_contents() const {
return browser()->tab_strip_model()->GetActiveWebContents();
}
content::RenderFrameHost* current_frame_host() {
return web_contents()->GetPrimaryMainFrame();
}
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<base::Bucket>* buckets,
base::HistogramBase::Sample sample) {
auto it = base::ranges::find(*buckets, sample, &base::Bucket::min);
if (it == buckets->end()) {
buckets->push_back(base::Bucket(sample, 1));
} else {
it->count++;
}
}
base::HistogramTester histogram_tester_;
std::vector<base::Bucket> 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() {
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);
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);
ASSERT_TRUE(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"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
const PrintParams kParams{.print_only_selection = true};
PrintAndWaitUntilPreviewIsReady(kParams);
}
// 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"));
ASSERT_TRUE(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();
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"));
ASSERT_TRUE(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();
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"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
PrintAndWaitUntilPreviewIsReady();
// Test that entering print preview didn't mess up the scroll position.
EXPECT_EQ(
0, content::EvalJs(browser()->tab_strip_model()->GetActiveWebContents(),
"window.scrollX"));
}
// https://crbug.com/1285208
IN_PROC_BROWSER_TEST_F(PrintBrowserTest, LegacyLayoutEngineFallback) {
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL(
"/printing/legacy-layout-engine-known-bug.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
auto* contents = browser()->tab_strip_model()->GetActiveWebContents();
const char kExpression[] = "target.offsetHeight";
// The non-printed document should be laid out with LayoutNG. We're testing
// this by looking for a known margin-collapsing / clearance bug in the legacy
// engine, not present in LayoutNG. The height should be 0 if the bug isn't
// present.
double old_height = content::EvalJs(contents, kExpression).ExtractDouble();
if (old_height != 0) {
// LayoutNG seems to be disabled. There's nothing useful to test here then.
return;
}
// Entering print preview may trigger legacy engine fallback, but this should
// only be temporary.
PrintAndWaitUntilPreviewIsReady();
// The non-printed document should still be laid out with LayoutNG.
double new_height = content::EvalJs(contents, kExpression).ExtractDouble();
EXPECT_EQ(new_height, 0);
}
IN_PROC_BROWSER_TEST_F(PrintBrowserTest, LazyLoadedImagesFetched) {
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL(
"/printing/lazy-loaded-image-offscreen.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
auto* contents = browser()->tab_strip_model()->GetActiveWebContents();
const char kExpression[] = "target.offsetHeight";
double old_height = content::EvalJs(contents, kExpression).ExtractDouble();
PrintAndWaitUntilPreviewIsReady();
// The non-printed document should have loaded the image, which will have
// a different height.
double new_height = content::EvalJs(contents, kExpression).ExtractDouble();
EXPECT_NE(old_height, new_height);
}
IN_PROC_BROWSER_TEST_F(PrintBrowserTest, LazyLoadedIframeFetched) {
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL(
"/printing/lazy-loaded-iframe-offscreen.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
auto* contents = browser()->tab_strip_model()->GetActiveWebContents();
const char kExpression[] =
"target.contentWindow.document.documentElement.clientHeight";
double old_height = content::EvalJs(contents, kExpression).ExtractDouble();
PrintAndWaitUntilPreviewIsReady();
double new_height = content::EvalJs(contents, kExpression).ExtractDouble();
EXPECT_NE(old_height, new_height);
}
// TODO(crbug.com/1305193) Reenable after flakes have been resolved.
IN_PROC_BROWSER_TEST_F(PrintBrowserTest,
DISABLED_LazyLoadedIframeFetchedCrossOrigin) {
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL(
"/printing/lazy-loaded-iframe-offscreen-cross-origin.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
auto* contents = browser()->tab_strip_model()->GetActiveWebContents();
const char kExpression[] = "document.documentElement.clientHeight";
double old_height =
content::EvalJs(content::ChildFrameAt(contents, 0), kExpression)
.ExtractDouble();
PrintAndWaitUntilPreviewIsReady();
double new_height =
content::EvalJs(content::ChildFrameAt(contents, 0), kExpression)
.ExtractDouble();
EXPECT_NE(old_height, new_height);
}
IN_PROC_BROWSER_TEST_F(PrintBrowserTest, LazyLoadedImagesFetchedScriptedPrint) {
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL(
"/printing/lazy-loaded-image-offscreen.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
auto* contents = browser()->tab_strip_model()->GetActiveWebContents();
const char kExpression[] = "target.offsetHeight";
double old_height = content::EvalJs(contents, kExpression).ExtractDouble();
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
TestPrintViewManager* print_view_manager =
TestPrintViewManager::CreateForWebContents(web_contents);
content::ExecuteScriptAsync(web_contents->GetPrimaryMainFrame(),
"window.print();");
print_view_manager->WaitUntilPreviewIsShownOrCancelled();
// The non-printed document should have loaded the image, which will have
// a different height.
double new_height = content::EvalJs(contents, kExpression).ExtractDouble();
EXPECT_NE(old_height, new_height);
}
// 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"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
auto* contents = browser()->tab_strip_model()->GetActiveWebContents();
contents->SetPageScale(1.5);
PrintAndWaitUntilPreviewIsReady();
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"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* original_contents =
browser()->tab_strip_model()->GetActiveWebContents();
content::RenderFrameHost* rfh = original_contents->GetPrimaryMainFrame();
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"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* original_contents =
browser()->tab_strip_model()->GetActiveWebContents();
content::RenderFrameHost* test_frame = ChildFrameAt(original_contents, 0);
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"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* original_contents =
browser()->tab_strip_model()->GetActiveWebContents();
// Create composite client so subframe print message can be forwarded.
PrintCompositeClient::CreateForWebContents(original_contents);
content::RenderFrameHost* main_frame =
original_contents->GetPrimaryMainFrame();
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"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* original_contents =
browser()->tab_strip_model()->GetActiveWebContents();
// Create composite client so subframe print message can be forwarded.
PrintCompositeClient::CreateForWebContents(original_contents);
content::RenderFrameHost* main_frame =
original_contents->GetPrimaryMainFrame();
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 frame content with a cross-site iframe before creating
// PrintCompositor by the main frame.
// This test passes if PrintCompositeClient queues subframes when
// it doesn't have PrintCompositor and clears them after PrintCompositor is
// created.
IN_PROC_BROWSER_TEST_F(PrintBrowserTest,
PrintSubframeContentBeforeCompositeClientCreation) {
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(
embedded_test_server()->GetURL("/printing/content_with_iframe.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
// When OOPIF is not enabled, CompositorClient is not used.
if (!IsOopifEnabled())
return;
content::WebContents* original_contents =
browser()->tab_strip_model()->GetActiveWebContents();
content::RenderFrameHost* main_frame =
original_contents->GetPrimaryMainFrame();
ASSERT_TRUE(main_frame);
content::RenderFrameHost* test_frame = ChildFrameAt(main_frame, 0);
ASSERT_TRUE(test_frame);
ASSERT_NE(main_frame->GetProcess(), test_frame->GetProcess());
CreateTestPrintRenderFrame(main_frame, original_contents);
CreateTestPrintRenderFrame(test_frame, original_contents);
SetNumExpectedMessages(2);
// Print on the main frame.
GetPrintRenderFrame(main_frame)
->PrintFrameContent(GetDefaultPrintFrameParams(), base::DoNothing());
// The printed result will be received and checked in TestPrintRenderFrame.
WaitUntilCallbackReceived();
// As PrintFrameContent() with the main frame doesn't call
// PrintCompositeClient::DoCompositeDocumentToPdf() on this test, when
// PrintCompositeClient::OnDidPrintFrameContent() is called with the sub
// frame, it doesn't have mojom::PrintCompositor.
auto* client = PrintCompositeClient::FromWebContents(original_contents);
ASSERT_FALSE(client->compositor_);
// When there is no mojom::PrintCompositor, PrintCompositeClient queues
// subframes and handles them when mojom::PrintCompositor is created.
// `requested_subframes_` should have the requested subframes.
ASSERT_EQ(1u, client->requested_subframes_.size());
PrintCompositeClient::RequestedSubFrame* subframe_in_queue =
client->requested_subframes_.begin()->get();
ASSERT_EQ(kDefaultDocumentCookie, subframe_in_queue->document_cookie_);
ASSERT_EQ(test_frame->GetGlobalId(), subframe_in_queue->rfh_id_);
// Creates mojom::PrintCompositor.
client->DoCompositeDocumentToPdf(
kDefaultDocumentCookie, main_frame,
*TestPrintRenderFrame::GetDefaultDidPrintContentParams(),
base::DoNothing());
ASSERT_TRUE(client->GetCompositeRequest(kDefaultDocumentCookie));
// `requested_subframes_` should be empty.
ASSERT_TRUE(client->requested_subframes_.empty());
}
// 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"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
PrintAndWaitUntilPreviewIsReady();
}
// 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"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* original_contents =
browser()->tab_strip_model()->GetActiveWebContents();
content::RenderFrameHost* test_frame = ChildFrameAt(original_contents, 0);
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();
}
// 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"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* original_contents =
browser()->tab_strip_model()->GetActiveWebContents();
content::RenderFrameHost* subframe = ChildFrameAt(original_contents, 0);
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, 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"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* original_contents =
browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_TRUE(NavigateIframeToURL(original_contents, "iframe", isolated_url));
auto* main_frame = original_contents->GetPrimaryMainFrame();
auto* subframe = ChildFrameAt(main_frame, 0);
ASSERT_NE(main_frame->GetProcess(), subframe->GetProcess());
PrintAndWaitUntilPreviewIsReady();
}
// Printing preview a webpage.
// Test that we use oopif printing by default when full site isolation is
// enabled.
IN_PROC_BROWSER_TEST_F(PrintBrowserTest, RegularPrinting) {
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL("/printing/test1.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
EXPECT_EQ(content::AreAllSitesIsolatedForTesting(), IsOopifEnabled());
}
#if BUILDFLAG(IS_CHROMEOS)
// Test that if user allows printing after being shown a warning due to DLP
// restrictions, the print preview is rendered.
IN_PROC_BROWSER_TEST_F(PrintBrowserTest, DLPWarnAllowed) {
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL("/printing/test1.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
// Set up the print view manager and DLP restrictions.
TestPrintViewManagerForDLP* print_view_manager =
TestPrintViewManagerForDLP::CreateForWebContents(
web_contents,
TestPrintViewManagerForDLP::RestrictionLevel::kWarnAllow);
ASSERT_EQ(print_view_manager->GetPrintAllowance(),
TestPrintViewManagerForDLP::PrintAllowance::kUnknown);
StartPrint(browser()->tab_strip_model()->GetActiveWebContents(),
/*print_renderer=*/mojo::NullAssociatedRemote(),
/*print_preview_disabled=*/false,
/*has_selection=*/false);
print_view_manager->WaitUntilPreviewIsShownOrCancelled();
ASSERT_EQ(print_view_manager->GetPrintAllowance(),
TestPrintViewManagerForDLP::PrintAllowance::kAllowed);
}
// Test that if user cancels printing after being shown a warning due to DLP
// restrictions, the print preview is not rendered.
IN_PROC_BROWSER_TEST_F(PrintBrowserTest, DLPWarnCanceled) {
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL("/printing/test1.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
// Set up the print view manager and DLP restrictions.
TestPrintViewManagerForDLP* print_view_manager =
TestPrintViewManagerForDLP::CreateForWebContents(
web_contents,
TestPrintViewManagerForDLP::RestrictionLevel::kWarnCancel);
ASSERT_EQ(print_view_manager->GetPrintAllowance(),
TestPrintViewManagerForDLP::PrintAllowance::kUnknown);
StartPrint(browser()->tab_strip_model()->GetActiveWebContents(),
/*print_renderer=*/mojo::NullAssociatedRemote(),
/*print_preview_disabled=*/false,
/*has_selection=*/false);
print_view_manager->WaitUntilPreviewIsShownOrCancelled();
ASSERT_EQ(print_view_manager->GetPrintAllowance(),
TestPrintViewManagerForDLP::PrintAllowance::kDisallowed);
}
// Test that if printing is blocked due to DLP restrictions, the print preview
// is not rendered.
IN_PROC_BROWSER_TEST_F(PrintBrowserTest, DLPBlocked) {
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL("/printing/test1.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
// Set up the print view manager and DLP restrictions.
TestPrintViewManagerForDLP* print_view_manager =
TestPrintViewManagerForDLP::CreateForWebContents(
web_contents, TestPrintViewManagerForDLP::RestrictionLevel::kBlock);
ASSERT_EQ(print_view_manager->GetPrintAllowance(),
TestPrintViewManagerForDLP::PrintAllowance::kUnknown);
StartPrint(browser()->tab_strip_model()->GetActiveWebContents(),
/*print_renderer=*/mojo::NullAssociatedRemote(),
/*print_preview_disabled=*/false,
/*has_selection=*/false);
print_view_manager->WaitUntilPreviewIsShownOrCancelled();
ASSERT_EQ(print_view_manager->GetPrintAllowance(),
TestPrintViewManagerForDLP::PrintAllowance::kDisallowed);
}
// Test that if user allows printing after being shown a warning due to DLP
// restrictions, the print preview is rendered when initiated by window.print().
IN_PROC_BROWSER_TEST_F(PrintBrowserTest, DLPWarnAllowedWithWindowDotPrint) {
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL("/printing/test1.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
// Set up the print view manager and DLP restrictions.
TestPrintViewManagerForDLP* print_view_manager =
TestPrintViewManagerForDLP::CreateForWebContents(
web_contents,
TestPrintViewManagerForDLP::RestrictionLevel::kWarnAllow);
ASSERT_EQ(print_view_manager->GetPrintAllowance(),
TestPrintViewManagerForDLP::PrintAllowance::kUnknown);
content::ExecuteScriptAsync(web_contents->GetPrimaryMainFrame(),
"window.print();");
print_view_manager->WaitUntilPreviewIsShownOrCancelled();
ASSERT_EQ(print_view_manager->GetPrintAllowance(),
TestPrintViewManagerForDLP::PrintAllowance::kAllowed);
}
// Test that if user cancels printing after being shown a warning due to DLP
// restrictions, the print preview is not rendered when initiated by
// window.print().
IN_PROC_BROWSER_TEST_F(PrintBrowserTest, DLPWarnCanceledWithWindowDotPrint) {
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL("/printing/test1.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
// Set up the print view manager and DLP restrictions.
TestPrintViewManagerForDLP* print_view_manager =
TestPrintViewManagerForDLP::CreateForWebContents(
web_contents,
TestPrintViewManagerForDLP::RestrictionLevel::kWarnCancel);
ASSERT_EQ(print_view_manager->GetPrintAllowance(),
TestPrintViewManagerForDLP::PrintAllowance::kUnknown);
content::ExecuteScriptAsync(web_contents->GetPrimaryMainFrame(),
"window.print();");
print_view_manager->WaitUntilPreviewIsShownOrCancelled();
ASSERT_EQ(print_view_manager->GetPrintAllowance(),
TestPrintViewManagerForDLP::PrintAllowance::kDisallowed);
}
// Test that if printing is blocked due to DLP restrictions, the print preview
// is not rendered when initiated by window.print().
IN_PROC_BROWSER_TEST_F(PrintBrowserTest, DLPBlockedWithWindowDotPrint) {
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL("/printing/test1.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
// Set up the print view manager and DLP restrictions.
TestPrintViewManagerForDLP* print_view_manager =
TestPrintViewManagerForDLP::CreateForWebContents(
web_contents, TestPrintViewManagerForDLP::RestrictionLevel::kBlock);
ASSERT_EQ(print_view_manager->GetPrintAllowance(),
TestPrintViewManagerForDLP::PrintAllowance::kUnknown);
content::ExecuteScriptAsync(web_contents->GetPrimaryMainFrame(),
"window.print();");
print_view_manager->WaitUntilPreviewIsShownOrCancelled();
ASSERT_EQ(print_view_manager->GetPrintAllowance(),
TestPrintViewManagerForDLP::PrintAllowance::kDisallowed);
}
#endif // BUILDFLAG(IS_CHROMEOS)
// 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"));
ASSERT_TRUE(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"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::RenderFrameHost* rfh_a = current_frame_host();
content::RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
PrintAndWaitUntilPreviewIsReady();
// 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"));
ASSERT_TRUE(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();
}
// 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();
}
// Printing frame content for the main frame of a generic webpage with N-up
// printing. 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/multipagenup.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
TestPrintViewManager print_view_manager(web_contents);
PrintViewManager::SetReceiverImplForTesting(&print_view_manager);
// Override print parameters to do N-up, specify 4 pages per sheet.
const PrintParams kParams{.pages_per_sheet = 4};
PrintAndWaitUntilPreviewIsReady(kParams);
PrintViewManager::SetReceiverImplForTesting(nullptr);
// With 4 pages per sheet requested by `GetPrintParams()`, a 7 page input
// will result in 2 pages in the print preview.
EXPECT_EQ(rendered_page_count(), 2u);
}
// 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/multipagenup.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
TestPrintViewManager print_view_manager(web_contents);
PrintViewManager::SetReceiverImplForTesting(&print_view_manager);
// Override print parameters to do N-up, specify 4 pages per sheet.
const PrintParams kParams{.pages_per_sheet = 4};
PrintAndWaitUntilPreviewIsReady(kParams);
PrintViewManager::SetReceiverImplForTesting(nullptr);
// With 4 pages per sheet requested by `GetPrintParams()`, a 7 page input
// will result in 2 pages in the print preview.
EXPECT_EQ(rendered_page_count(), 2u);
}
IN_PROC_BROWSER_TEST_F(PrintBrowserTest, MultipagePrint) {
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL("/printing/multipage.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
PrintAndWaitUntilPreviewIsReadyAndLoaded();
EXPECT_EQ(rendered_page_count(), 3u);
}
IN_PROC_BROWSER_TEST_F(SitePerProcessPrintBrowserTest, MultipagePrint) {
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL("/printing/multipage.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
PrintAndWaitUntilPreviewIsReadyAndLoaded();
EXPECT_EQ(rendered_page_count(), 3u);
}
// Disabled due to flakiness: crbug.com/1311998
IN_PROC_BROWSER_TEST_F(PrintBrowserTest,
DISABLED_PDFPluginNotKeyboardFocusable) {
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL("/printing/multipage.html"));
ASSERT_TRUE(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, /*has_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 <zoom-out-button> when
// focused. Focus the element after PDF plugin in tab order.
const char kScript[] = R"(
const button = document.getElementsByTagName('print-preview-app')[0]
.$['previewArea']
.shadowRoot.querySelector('iframe')
.contentDocument.querySelector('pdf-viewer-pp')
.shadowRoot.querySelector('#zoomToolbar')
.$['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 <shift-tab> press and wait for a focus message.
content::DOMMessageQueue msg_queue(preview_dialog);
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 <shift-tab> should focus the last toolbar element
// (zoom-out-button) instead of PDF plugin.
EXPECT_EQ("\"zoom-out-button\"", reply);
}
IN_PROC_BROWSER_TEST_F(PrintBrowserTest, WindowDotPrint) {
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
PrintPreviewObserver print_preview_observer(/*wait_for_loaded=*/false);
content::ExecuteScriptAsync(web_contents->GetPrimaryMainFrame(),
"window.print();");
print_preview_observer.WaitUntilPreviewIsReady();
}
class PrintPrerenderBrowserTest : public PrintBrowserTest {
public:
PrintPrerenderBrowserTest()
: prerender_helper_(
base::BindRepeating(&PrintPrerenderBrowserTest::web_contents,
base::Unretained(this))) {}
void SetUpCommandLine(base::CommandLine* cmd_line) override {
cmd_line->AppendSwitch(switches::kDisablePrintPreview);
PrintBrowserTest::SetUpCommandLine(cmd_line);
}
void SetUp() override {
prerender_helper_.SetUp(embedded_test_server());
PrintBrowserTest::SetUp();
}
void SetUpOnMainThread() override {
ASSERT_TRUE(embedded_test_server()->Start());
}
content::WebContents* web_contents() {
return browser()->tab_strip_model()->GetActiveWebContents();
}
protected:
content::test::PrerenderTestHelper prerender_helper_;
};
// Test that print() is silently ignored.
// https://wicg.github.io/nav-speculation/prerendering.html#patch-modals
IN_PROC_BROWSER_TEST_F(PrintPrerenderBrowserTest, QuietBlockWithWindowPrint) {
// Navigate to an initial page.
const GURL kUrl(embedded_test_server()->GetURL("/empty.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), kUrl));
// Start a prerender.
GURL prerender_url =
embedded_test_server()->GetURL("/printing/prerendering.html");
content::WebContentsConsoleObserver console_observer(web_contents());
int prerender_id = prerender_helper_.AddPrerender(prerender_url);
content::RenderFrameHost* prerender_host =
prerender_helper_.GetPrerenderedMainFrameHost(prerender_id);
EXPECT_EQ(0u, console_observer.messages().size());
// Try to print by JS during prerendering.
EXPECT_EQ(true, content::ExecJs(prerender_host, "window.print();",
content::EXECUTE_SCRIPT_NO_USER_GESTURE));
EXPECT_EQ(false, content::EvalJs(prerender_host, "firedBeforePrint"));
EXPECT_EQ(false, content::EvalJs(prerender_host, "firedAfterPrint"));
EXPECT_EQ(1u, console_observer.messages().size());
}
// Test that execCommand('print') is silently ignored.
// execCommand() is not specced, but
// https://wicg.github.io/nav-speculation/prerendering.html#patch-modals
// indicates the intent to silently ignore print APIs.
IN_PROC_BROWSER_TEST_F(PrintPrerenderBrowserTest,
QuietBlockWithDocumentExecCommand) {
// Navigate to an initial page.
const GURL kUrl(embedded_test_server()->GetURL("/empty.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), kUrl));
// Start a prerender.
GURL prerender_url =
embedded_test_server()->GetURL("/printing/prerendering.html");
content::WebContentsConsoleObserver console_observer(web_contents());
int prerender_id = prerender_helper_.AddPrerender(prerender_url);
content::RenderFrameHost* prerender_host =
prerender_helper_.GetPrerenderedMainFrameHost(prerender_id);
EXPECT_EQ(0u, console_observer.messages().size());
// Try to print by JS during prerendering.
EXPECT_EQ(false,
content::EvalJs(prerender_host, "document.execCommand('print');"));
EXPECT_EQ(false, content::EvalJs(prerender_host, "firedBeforePrint"));
EXPECT_EQ(false, content::EvalJs(prerender_host, "firedAfterPrint"));
EXPECT_EQ(1u, console_observer.messages().size());
}
class PrintFencedFrameBrowserTest : public PrintBrowserTest {
public:
PrintFencedFrameBrowserTest() {
fenced_frame_helper_ =
std::make_unique<content::test::FencedFrameTestHelper>();
}
~PrintFencedFrameBrowserTest() override = default;
void SetUpOnMainThread() override {
PrintBrowserTest::SetUpOnMainThread();
https_server_.ServeFilesFromSourceDirectory(GetChromeTestDataDir());
ASSERT_TRUE(https_server_.Start());
}
PrintFencedFrameBrowserTest(const PrintFencedFrameBrowserTest&) = delete;
PrintFencedFrameBrowserTest& operator=(const PrintFencedFrameBrowserTest&) =
delete;
content::WebContents* web_contents() {
return browser()->tab_strip_model()->GetActiveWebContents();
}
content::test::FencedFrameTestHelper* fenced_frame_test_helper() {
return fenced_frame_helper_.get();
}
protected:
content::RenderFrameHost* CreateFencedFrame(
content::RenderFrameHost* fenced_frame_parent,
const GURL& url) {
if (fenced_frame_helper_)
return fenced_frame_helper_->CreateFencedFrame(fenced_frame_parent, url);
// FencedFrameTestHelper only supports the MPArch version of fenced frames.
// So need to maually create a fenced frame for the ShadowDOM version.
content::TestNavigationManager navigation(web_contents(), url);
constexpr char kAddFencedFrameScript[] = R"({
const fenced_frame = document.createElement('fencedframe');
fenced_frame.src = $1;
document.body.appendChild(fenced_frame);
})";
EXPECT_TRUE(ExecJs(fenced_frame_parent,
content::JsReplace(kAddFencedFrameScript, url)));
EXPECT_TRUE(navigation.WaitForNavigationFinished());
content::RenderFrameHost* new_frame = ChildFrameAt(fenced_frame_parent, 0);
return new_frame;
}
void RunPrintTest(const std::string& print_command) {
// Navigate to an initial page.
const GURL url(https_server_.GetURL("/empty.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
// Load a fenced frame.
GURL fenced_frame_url = https_server_.GetURL("/fenced_frames/title1.html");
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
content::RenderFrameHost* fenced_frame_host = CreateFencedFrame(
web_contents->GetPrimaryMainFrame(), fenced_frame_url);
ASSERT_TRUE(fenced_frame_host);
content::WebContentsConsoleObserver console_observer(web_contents);
EXPECT_EQ(0u, console_observer.messages().size());
constexpr char kAddListenersScript[] = R"(
(async () => {
let firedBeforePrint = false;
let firedAfterPrint = false;
window.addEventListener('beforeprint', () => {
firedBeforePrint = true;
});
window.addEventListener('afterprint', () => {
firedAfterPrint = true;
});
%s
return 'beforeprint: ' + firedBeforePrint +
', afterprint: ' + firedAfterPrint;
})();
)";
const std::string test_script =
base::StringPrintf(kAddListenersScript, print_command.c_str());
EXPECT_EQ("beforeprint: false, afterprint: false",
content::EvalJs(fenced_frame_host, test_script));
ASSERT_TRUE(console_observer.Wait());
ASSERT_EQ(1u, console_observer.messages().size());
EXPECT_EQ(
"Ignored call to 'print()'. The document is in a fenced frame tree.",
console_observer.GetMessageAt(0));
}
private:
base::test::ScopedFeatureList feature_list_;
std::unique_ptr<content::test::FencedFrameTestHelper> fenced_frame_helper_;
net::EmbeddedTestServer https_server_{net::EmbeddedTestServer::TYPE_HTTPS};
};
IN_PROC_BROWSER_TEST_F(PrintFencedFrameBrowserTest, ScriptedPrint) {
RunPrintTest("window.print();");
}
IN_PROC_BROWSER_TEST_F(PrintFencedFrameBrowserTest, DocumentExecCommand) {
RunPrintTest("document.execCommand('print');");
}
// TODO(crbug.com/822505) ChromeOS uses different testing setup that isn't
// hooked up to make use of `TestPrintingContext` yet.
#if !BUILDFLAG(IS_CHROMEOS)
#if BUILDFLAG(ENABLE_OOP_PRINTING)
class TestPrinterQuery : public PrinterQuery {
public:
// Callbacks to run for overrides.
struct PrintCallbacks {
OnUseDefaultSettingsCallback did_use_default_settings_callback;
OnGetSettingsWithUICallback did_get_settings_with_ui_callback;
};
TestPrinterQuery(content::GlobalRenderFrameHostId rfh_id,
PrintCallbacks* callbacks)
: PrinterQuery(rfh_id), callbacks_(callbacks) {}
void UseDefaultSettings(SettingsCallback callback) override {
DVLOG(1) << "Observed: invoke use default settings";
PrinterQuery::UseDefaultSettings(std::move(callback));
callbacks_->did_use_default_settings_callback.Run();
}
void GetSettingsWithUI(uint32_t document_page_count,
bool has_selection,
bool is_scripted,
SettingsCallback callback) override {
DVLOG(1) << "Observed: invoke get settings with UI";
PrinterQuery::GetSettingsWithUI(document_page_count, has_selection,
is_scripted, std::move(callback));
callbacks_->did_get_settings_with_ui_callback.Run();
}
raw_ptr<PrintCallbacks> callbacks_;
};
class TestPrintJobWorkerOop : public PrintJobWorkerOop {
public:
// Callbacks to run for overrides are broken into the following steps:
// 1. Error case processing. Call `error_check_callback` to reset any
// triggers that were primed to cause errors in the testing context.
// 2. Run the base class callback for normal handling. If there was an
// access-denied error then this can lead to a retry. The retry has a
// chance to succeed since error triggers were removed.
// 3. Exercise the associated test callback (e.g.,
// `did_start_printing_callback` when in `OnDidStartPrinting()`) to note
// the callback was observed and completed. This ensures all base class
// processing was done before possibly quitting the test run loop.
struct PrintCallbacks {
ErrorCheckCallback error_check_callback;
OnDidUseDefaultSettingsCallback did_use_default_settings_callback;
#if BUILDFLAG(IS_WIN)
OnDidAskUserForSettingsCallback did_ask_user_for_settings_callback;
#endif
OnDidStartPrintingCallback did_start_printing_callback;
#if BUILDFLAG(IS_WIN)
OnDidRenderPrintedPageCallback did_render_printed_page_callback;
#endif
OnDidRenderPrintedDocumentCallback did_render_printed_document_callback;
OnDidDocumentDoneCallback did_document_done_callback;
OnDidCancelCallback did_cancel_callback;
};
TestPrintJobWorkerOop(
std::unique_ptr<PrintingContext::Delegate> printing_context_delegate,
std::unique_ptr<PrintingContext> printing_context,
PrintJob* print_job,
bool simulate_spooling_memory_errors,
TestPrintJobWorkerOop::PrintCallbacks* callbacks)
: PrintJobWorkerOop(std::move(printing_context_delegate),
std::move(printing_context),
print_job,
simulate_spooling_memory_errors),
callbacks_(callbacks) {}
TestPrintJobWorkerOop(const TestPrintJobWorkerOop&) = delete;
TestPrintJobWorkerOop& operator=(const TestPrintJobWorkerOop&) = delete;
~TestPrintJobWorkerOop() override = default;
private:
void OnDidStartPrinting(mojom::ResultCode result) override {
DVLOG(1) << "Observed: start printing of document";
callbacks_->error_check_callback.Run(result);
PrintJobWorkerOop::OnDidStartPrinting(result);
callbacks_->did_start_printing_callback.Run(result, print_job());
}
#if BUILDFLAG(IS_WIN)
void OnDidRenderPrintedPage(uint32_t page_number,
mojom::ResultCode result) override {
DVLOG(1) << "Observed render for printed page " << page_number;
callbacks_->error_check_callback.Run(result);
PrintJobWorkerOop::OnDidRenderPrintedPage(page_number, result);
callbacks_->did_render_printed_page_callback.Run(page_number, result);
}
#endif // BUILDFLAG(IS_WIN)
void OnDidRenderPrintedDocument(mojom::ResultCode result) override {
DVLOG(1) << "Observed render for printed document";
callbacks_->error_check_callback.Run(result);
PrintJobWorkerOop::OnDidRenderPrintedDocument(result);
callbacks_->did_render_printed_document_callback.Run(result);
}
void OnDidDocumentDone(int job_id, mojom::ResultCode result) override {
DVLOG(1) << "Observed: document done";
callbacks_->error_check_callback.Run(result);
PrintJobWorkerOop::OnDidDocumentDone(job_id, result);
callbacks_->did_document_done_callback.Run(result);
}
void OnDidCancel(scoped_refptr<PrintJob> job) override {
DVLOG(1) << "Observed: cancel";
// Must not use `std::move(job)`, as that could potentially cause the `job`
// (and consequentially `this`) to be destroyed before
// `did_cancel_callback` is run.
PrintJobWorkerOop::OnDidCancel(job);
callbacks_->did_cancel_callback.Run();
}
raw_ptr<PrintCallbacks> callbacks_;
};
class TestPrinterQueryOop : public PrinterQueryOop {
public:
TestPrinterQueryOop(content::GlobalRenderFrameHostId rfh_id,
bool simulate_spooling_memory_errors,
TestPrintJobWorkerOop::PrintCallbacks* callbacks)
: PrinterQueryOop(rfh_id),
simulate_spooling_memory_errors_(simulate_spooling_memory_errors),
callbacks_(callbacks) {}
void OnDidUseDefaultSettings(
SettingsCallback callback,
mojom::PrintSettingsResultPtr print_settings) override {
DVLOG(1) << "Observed: use default settings";
mojom::ResultCode result = print_settings->is_result_code()
? print_settings->get_result_code()
: mojom::ResultCode::kSuccess;
callbacks_->error_check_callback.Run(result);
PrinterQueryOop::OnDidUseDefaultSettings(std::move(callback),
std::move(print_settings));
callbacks_->did_use_default_settings_callback.Run(result);
}
#if BUILDFLAG(IS_WIN)
void OnDidAskUserForSettings(
SettingsCallback callback,
mojom::PrintSettingsResultPtr print_settings) override {
DVLOG(1) << "Observed: ask user for settings";
mojom::ResultCode result = print_settings->is_result_code()
? print_settings->get_result_code()
: mojom::ResultCode::kSuccess;
callbacks_->error_check_callback.Run(result);
PrinterQueryOop::OnDidAskUserForSettings(std::move(callback),
std::move(print_settings));
callbacks_->did_ask_user_for_settings_callback.Run(result);
}
#endif // BUILDFLAG(IS_WIN)
std::unique_ptr<PrintJobWorker> TransferContextToNewWorker(
PrintJob* print_job) override {
return std::make_unique<TestPrintJobWorkerOop>(
std::move(printing_context_delegate_), std::move(printing_context_),
print_job, simulate_spooling_memory_errors_, callbacks_);
}
bool simulate_spooling_memory_errors_;
raw_ptr<TestPrintJobWorkerOop::PrintCallbacks> callbacks_;
};
#endif // BUILDFLAG(ENABLE_OOP_PRINTING)
class SystemAccessProcessPrintBrowserTestBase
: public PrintBrowserTest,
public PrintJob::Observer,
public PrintViewManagerBase::Observer {
public:
SystemAccessProcessPrintBrowserTestBase() = default;
~SystemAccessProcessPrintBrowserTestBase() override = default;
virtual bool UseService() = 0;
// Only of interest when `UseService()` returns true.
virtual bool SandboxService() = 0;
void SetUp() override {
#if BUILDFLAG(ENABLE_OOP_PRINTING)
if (UseService()) {
feature_list_.InitAndEnableFeatureWithParameters(
features::kEnableOopPrintDrivers,
{{features::kEnableOopPrintDriversJobPrint.name, "true"},
{features::kEnableOopPrintDriversSandbox.name,
SandboxService() ? "true" : "false"}});
// Safe to use `base::Unretained(this)` since this testing class
// necessarily must outlive all interactions from the tests which will
// run through `TestPrintJobWorkerOop`, the user of these callbacks.
test_print_job_worker_oop_callbacks_.error_check_callback =
base::BindRepeating(
&SystemAccessProcessPrintBrowserTestBase::ErrorCheck,
base::Unretained(this));
test_print_job_worker_oop_callbacks_.did_use_default_settings_callback =
base::BindRepeating(
&SystemAccessProcessPrintBrowserTestBase::OnDidUseDefaultSettings,
base::Unretained(this));
#if BUILDFLAG(IS_WIN)
test_print_job_worker_oop_callbacks_.did_ask_user_for_settings_callback =
base::BindRepeating(
&SystemAccessProcessPrintBrowserTestBase::OnDidAskUserForSettings,
base::Unretained(this));
#endif
test_print_job_worker_oop_callbacks_.did_start_printing_callback =
base::BindRepeating(
&SystemAccessProcessPrintBrowserTestBase::OnDidStartPrinting,
base::Unretained(this));
#if BUILDFLAG(IS_WIN)
test_print_job_worker_oop_callbacks_.did_render_printed_page_callback =
base::BindRepeating(
&SystemAccessProcessPrintBrowserTestBase::OnDidRenderPrintedPage,
base::Unretained(this));
#endif
test_print_job_worker_oop_callbacks_
.did_render_printed_document_callback = base::BindRepeating(
&SystemAccessProcessPrintBrowserTestBase::OnDidRenderPrintedDocument,
base::Unretained(this));
test_print_job_worker_oop_callbacks_.did_document_done_callback =
base::BindRepeating(
&SystemAccessProcessPrintBrowserTestBase::OnDidDocumentDone,
base::Unretained(this));
test_print_job_worker_oop_callbacks_.did_cancel_callback =
base::BindRepeating(
&SystemAccessProcessPrintBrowserTestBase::OnDidCancel,
base::Unretained(this));
} else {
test_print_job_worker_callbacks_.did_use_default_settings_callback =
base::BindRepeating(
&SystemAccessProcessPrintBrowserTestBase::OnUseDefaultSettings,
base::Unretained(this));
test_print_job_worker_callbacks_.did_get_settings_with_ui_callback =
base::BindRepeating(
&SystemAccessProcessPrintBrowserTestBase::OnGetSettingsWithUI,
base::Unretained(this));
}
test_create_printer_query_callback_ = base::BindRepeating(
&SystemAccessProcessPrintBrowserTestBase::CreatePrinterQuery,
base::Unretained(this), UseService());
PrinterQuery::SetCreatePrinterQueryCallbackForTest(
&test_create_printer_query_callback_);
#endif // BUILDFLAG(ENABLE_OOP_PRINTING)
PrintBrowserTest::SetUp();
}
void SetUpOnMainThread() override {
#if BUILDFLAG(ENABLE_OOP_PRINTING)
if (UseService()) {
print_backend_service_ = PrintBackendServiceTestImpl::LaunchForTesting(
test_remote_, test_print_backend(), /*sandboxed=*/true);
}
#endif
PrintBrowserTest::SetUpOnMainThread();
}
void TearDown() override {
PrintBrowserTest::TearDown();
#if BUILDFLAG(ENABLE_OOP_PRINTING)
PrinterQuery::SetCreatePrinterQueryCallbackForTest(/*callback=*/nullptr);
if (UseService()) {
// Check that there is never a straggler client registration.
EXPECT_EQ(
PrintBackendServiceManager::GetInstance().GetClientsRegisteredCount(),
0u);
}
PrintBackendServiceManager::ResetForTesting();
#endif
ASSERT_EQ(print_job_construction_count(), print_job_destruction_count());
}
// PrintViewManagerBase::Observer:
void OnDidPrintDocument() override {
++did_print_document_count_;
CheckForQuit();
}
// PrintJob::Observer:
void OnDestruction() override {
++print_job_destruction_count_;
CheckForQuit();
}
void OnCreatedPrintJob(PrintJob* print_job) {
++print_job_construction_count_;
print_job->AddObserver(*this);
}
void SetUpPrintViewManager(content::WebContents* web_contents) {
auto manager = std::make_unique<TestPrintViewManager>(
web_contents,
base::BindRepeating(
&SystemAccessProcessPrintBrowserTestBase::OnCreatedPrintJob,
base::Unretained(this)));
manager->AddObserver(*this);
web_contents->SetUserData(PrintViewManager::UserDataKey(),
std::move(manager));
}
void PrintAfterPreviewIsReadyAndLoaded() {
// First invoke the Print Preview dialog with `StartPrint()`.
PrintPreviewObserver print_preview_observer(/*wait_for_loaded=*/true);
StartPrint(browser()->tab_strip_model()->GetActiveWebContents(),
/*print_renderer=*/mojo::NullAssociatedRemote(),
/*print_preview_disabled=*/false,
/*has_selection=*/false);
print_preview_observer.WaitUntilPreviewIsReady();
set_rendered_page_count(print_preview_observer.rendered_page_count());
content::WebContents* preview_dialog =
print_preview_observer.GetPrintPreviewDialog();
ASSERT_TRUE(preview_dialog);
// Print Preview is completely ready, can now initiate printing.
// This script locates and clicks the Print button.
const char kScript[] = R"(
const button = document.getElementsByTagName('print-preview-app')[0]
.$['sidebar']
.shadowRoot.querySelector('print-preview-button-strip')
.shadowRoot.querySelector('.action-button');
button.click();)";
ASSERT_TRUE(content::ExecuteScript(preview_dialog, kScript));
WaitUntilCallbackReceived();
}
#if BUILDFLAG(ENABLE_BASIC_PRINT_DIALOG)
void SystemPrintFromPreviewOnceReadyAndLoaded() {
// First invoke the Print Preview dialog with `StartPrint()`.
PrintPreviewObserver print_preview_observer(/*wait_for_loaded=*/true);
StartPrint(browser()->tab_strip_model()->GetActiveWebContents(),
/*print_renderer=*/mojo::NullAssociatedRemote(),
/*print_preview_disabled=*/false,
/*has_selection=*/false);
print_preview_observer.WaitUntilPreviewIsReady();
set_rendered_page_count(print_preview_observer.rendered_page_count());
content::WebContents* preview_dialog =
print_preview_observer.GetPrintPreviewDialog();
ASSERT_TRUE(preview_dialog);
// Print Preview is completely ready, can now initiate printing.
// This script locates and clicks the "Print using system dialog",
// which is still enabled even if it is hidden.
const char kPrintWithSystemDialogScript[] = R"(
const printSystemDialog
= document.getElementsByTagName('print-preview-app')[0]
.$['sidebar']
.shadowRoot.querySelector('print-preview-link-container')
.$['systemDialogLink'];
printSystemDialog.click();)";
// It is possible for sufficient processing for the system print to
// complete such that the renderer naturally terminates before ExecJs()
// returns here. This causes ExecJs() to return false, with a JavaScript
// error of "Renderer terminated". Since the termination can actually be
// a result of successful print processing, do not assert on this return
// result, just ignore the error instead. Rely upon tests catching any
// failure through the use of other expectation checks.
std::ignore = content::ExecJs(preview_dialog, kPrintWithSystemDialogScript);
WaitUntilCallbackReceived();
}
#endif // BUILDFLAG(ENABLE_BASIC_PRINT_DIALOG)
void PrimeAsRepeatingErrorGenerator() { reset_errors_after_check_ = false; }
#if BUILDFLAG(ENABLE_OOP_PRINTING)
void PrimeForSpoolingSharedMemoryErrors() {
simulate_spooling_memory_errors_ = true;
}
void PrimeForFailInUseDefaultSettings() {
test_printing_context_factory()->SetFailErrorOnUseDefaultSettings();
}
#if BUILDFLAG(ENABLE_BASIC_PRINT_DIALOG)
void PrimeForCancelInAskUserForSettings() {
test_printing_context_factory()->SetCancelErrorOnAskUserForSettings();
}
#endif
void PrimeForErrorsInNewDocument() {
test_printing_context_factory()->SetFailedErrorOnNewDocument(
/*cause_errors=*/true);
}
void PrimeForAccessDeniedErrorsInNewDocument() {
test_printing_context_factory()->SetAccessDeniedErrorOnNewDocument(
/*cause_errors=*/true);
}
#if BUILDFLAG(IS_WIN)
void PrimeForAccessDeniedErrorsInRenderPrintedPage() {
test_printing_context_factory()->SetAccessDeniedErrorOnRenderPage(
/*cause_errors=*/true);
}
void PrimeForDelayedRenderingUntilPage(uint32_t page_number) {
print_backend_service_->set_rendering_delayed_until_page(page_number);
}
void PrimeForRenderingErrorOnPage(uint32_t page_number) {
test_printing_context_factory()->SetFailedErrorForRenderPage(page_number);
}
#endif
void PrimeForAccessDeniedErrorsInRenderPrintedDocument() {
test_printing_context_factory()->SetAccessDeniedErrorOnRenderDocument(
/*cause_errors=*/true);
}
void PrimeForAccessDeniedErrorsInDocumentDone() {
test_printing_context_factory()->SetAccessDeniedErrorOnDocumentDone(
/*cause_errors=*/true);
}
bool did_use_default_settings() const { return did_use_default_settings_; }
bool did_get_settings_with_ui() const { return did_get_settings_with_ui_; }
bool print_backend_service_use_detected() const {
return print_backend_service_use_detected_;
}
#endif // BUILDFLAG(ENABLE_OOP_PRINTING)
mojom::ResultCode use_default_settings_result() const {
return use_default_settings_result_;
}
#if BUILDFLAG(ENABLE_BASIC_PRINT_DIALOG)
mojom::ResultCode ask_user_for_settings_result() const {
return ask_user_for_settings_result_;
}
#endif
mojom::ResultCode start_printing_result() const {
return start_printing_result_;
}
#if BUILDFLAG(IS_WIN)
mojom::ResultCode render_printed_page_result() const {
return render_printed_page_result_;
}
int render_printed_page_count() const { return render_printed_pages_count_; }
#endif // BUILDFLAG(IS_WIN)
mojom::ResultCode render_printed_document_result() {
return render_printed_document_result_;
}
mojom::ResultCode document_done_result() const {
return document_done_result_;
}
int cancel_count() const { return cancel_count_; }
int print_job_construction_count() const {
return print_job_construction_count_;
}
int print_job_destruction_count() const {
return print_job_destruction_count_;
}
int did_print_document_count() const { return did_print_document_count_; }
private:
#if BUILDFLAG(ENABLE_OOP_PRINTING)
std::unique_ptr<PrinterQuery> CreatePrinterQuery(
bool use_service,
content::GlobalRenderFrameHostId rfh_id) {
if (use_service) {
return std::make_unique<TestPrinterQueryOop>(
rfh_id, simulate_spooling_memory_errors_,
&test_print_job_worker_oop_callbacks_);
}
return std::make_unique<TestPrinterQuery>(
rfh_id, &test_print_job_worker_callbacks_);
}
void OnUseDefaultSettings() {
did_use_default_settings_ = true;
PrintBackendServiceDetectionCheck();
CheckForQuit();
}
void OnGetSettingsWithUI() {
did_get_settings_with_ui_ = true;
PrintBackendServiceDetectionCheck();
CheckForQuit();
}
void PrintBackendServiceDetectionCheck() {
// Want to know if `PrintBackendService` clients are ever detected, since
// registrations could have gone away by the time checks are made at the
// end of tests.
if (PrintBackendServiceManager::GetInstance().GetClientsRegisteredCount() >
0) {
print_backend_service_use_detected_ = true;
}
}
#endif // BUILDFLAG(ENABLE_OOP_PRINTING)
void ErrorCheck(mojom::ResultCode result) {
// Interested to reset any trigger for causing access-denied errors, so
// that retry logic has a chance to be exercised and succeed.
if (result == mojom::ResultCode::kAccessDenied)
ResetForNoAccessDeniedErrors();
}
void OnDidUseDefaultSettings(mojom::ResultCode result) {
use_default_settings_result_ = result;
CheckForQuit();
}
#if BUILDFLAG(ENABLE_BASIC_PRINT_DIALOG)
void OnDidAskUserForSettings(mojom::ResultCode result) {
ask_user_for_settings_result_ = result;
CheckForQuit();
}
#endif
void OnDidStartPrinting(mojom::ResultCode result, PrintJob* print_job) {
start_printing_result_ = result;
print_job_ = print_job;
CheckForQuit();
}
#if BUILDFLAG(IS_WIN)
void OnDidRenderPrintedPage(uint32_t page_number, mojom::ResultCode result) {
render_printed_page_result_ = result;
if (result == mojom::ResultCode::kSuccess)
render_printed_pages_count_++;
CheckForQuit();
}
#endif
void OnDidRenderPrintedDocument(mojom::ResultCode result) {
render_printed_document_result_ = result;
CheckForQuit();
}
void OnDidDocumentDone(mojom::ResultCode result) {
document_done_result_ = result;
CheckForQuit();
}
void OnDidCancel() {
++cancel_count_;
CheckForQuit();
}
void OnDidDestroyPrintJob() {
++print_job_destruction_count_;
CheckForQuit();
}
void ResetForNoAccessDeniedErrors() {
// Don't do the reset if test scenario is repeatedly return errors.
if (!reset_errors_after_check_)
return;
test_printing_context_factory()->SetAccessDeniedErrorOnNewDocument(
/*cause_errors=*/false);
#if BUILDFLAG(IS_WIN)
test_printing_context_factory()->SetAccessDeniedErrorOnRenderPage(
/*cause_errors=*/false);
#endif
test_printing_context_factory()->SetAccessDeniedErrorOnRenderDocument(
/*cause_errors=*/false);
test_printing_context_factory()->SetAccessDeniedErrorOnDocumentDone(
/*cause_errors=*/false);
}
base::test::ScopedFeatureList feature_list_;
#if BUILDFLAG(ENABLE_OOP_PRINTING)
TestPrinterQuery::PrintCallbacks test_print_job_worker_callbacks_;
TestPrintJobWorkerOop::PrintCallbacks test_print_job_worker_oop_callbacks_;
CreatePrinterQueryCallback test_create_printer_query_callback_;
bool did_use_default_settings_ = false;
bool did_get_settings_with_ui_ = false;
bool print_backend_service_use_detected_ = false;
bool simulate_spooling_memory_errors_ = false;
mojo::Remote<mojom::PrintBackendService> test_remote_;
std::unique_ptr<PrintBackendServiceTestImpl> print_backend_service_;
#endif // BUILDFLAG(ENABLE_OOP_PRINTING)
raw_ptr<PrintJob, DanglingUntriaged> print_job_ = nullptr;
bool reset_errors_after_check_ = true;
int did_print_document_count_ = 0;
mojom::ResultCode use_default_settings_result_ = mojom::ResultCode::kFailed;
#if BUILDFLAG(ENABLE_BASIC_PRINT_DIALOG)
mojom::ResultCode ask_user_for_settings_result_ = mojom::ResultCode::kFailed;
#endif
mojom::ResultCode start_printing_result_ = mojom::ResultCode::kFailed;
#if BUILDFLAG(IS_WIN)
mojom::ResultCode render_printed_page_result_ = mojom::ResultCode::kFailed;
int render_printed_pages_count_ = 0;
#endif
mojom::ResultCode render_printed_document_result_ =
mojom::ResultCode::kFailed;
mojom::ResultCode document_done_result_ = mojom::ResultCode::kFailed;
int cancel_count_ = 0;
int print_job_construction_count_ = 0;
int print_job_destruction_count_ = 0;
};
class SystemAccessProcessSandboxedServicePrintBrowserTest
: public SystemAccessProcessPrintBrowserTestBase {
public:
SystemAccessProcessSandboxedServicePrintBrowserTest() = default;
~SystemAccessProcessSandboxedServicePrintBrowserTest() override = default;
bool UseService() override { return true; }
bool SandboxService() override { return true; }
};
#if BUILDFLAG(ENABLE_OOP_PRINTING)
class SystemAccessProcessServicePrintBrowserTest
: public SystemAccessProcessPrintBrowserTestBase,
public testing::WithParamInterface<bool> {
public:
SystemAccessProcessServicePrintBrowserTest() = default;
~SystemAccessProcessServicePrintBrowserTest() override = default;
bool UseService() override { return true; }
bool SandboxService() override { return GetParam(); }
};
INSTANTIATE_TEST_SUITE_P(All,
SystemAccessProcessServicePrintBrowserTest,
testing::Bool());
#endif
class SystemAccessProcessInBrowserPrintBrowserTest
: public SystemAccessProcessPrintBrowserTestBase {
public:
SystemAccessProcessInBrowserPrintBrowserTest() = default;
~SystemAccessProcessInBrowserPrintBrowserTest() override = default;
bool UseService() override { return false; }
bool SandboxService() override { return false; }
};
enum class PrintBackendFeatureVariation {
// `PrintBackend` calls occur from browser process.
kInBrowserProcess,
// Use OOP `PrintBackend`. Attempt to have `PrintBackendService` be
// sandboxed.
kOopSandboxedService,
// Use OOP `PrintBackend`. Always use `PrintBackendService` unsandboxed.
kOopUnsandboxedService,
};
class SystemAccessProcessPrintBrowserTest
: public SystemAccessProcessPrintBrowserTestBase,
public testing::WithParamInterface<PrintBackendFeatureVariation> {
public:
SystemAccessProcessPrintBrowserTest() = default;
~SystemAccessProcessPrintBrowserTest() override = default;
bool UseService() override {
return GetParam() != PrintBackendFeatureVariation::kInBrowserProcess;
}
bool SandboxService() override {
return GetParam() == PrintBackendFeatureVariation::kOopSandboxedService;
}
};
INSTANTIATE_TEST_SUITE_P(
All,
SystemAccessProcessPrintBrowserTest,
testing::Values(PrintBackendFeatureVariation::kInBrowserProcess,
PrintBackendFeatureVariation::kOopSandboxedService,
PrintBackendFeatureVariation::kOopUnsandboxedService));
IN_PROC_BROWSER_TEST_P(SystemAccessProcessPrintBrowserTest,
UpdatePrintSettings) {
AddPrinter("printer1");
SetPrinterNameForSubsequentContexts("printer1");
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL("/printing/multipage.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
TestPrintViewManager print_view_manager(web_contents);
PrintViewManager::SetReceiverImplForTesting(&print_view_manager);
PrintAndWaitUntilPreviewIsReady();
EXPECT_EQ(rendered_page_count(), 3u);
ASSERT_TRUE(print_view_manager.snooped_settings());
EXPECT_EQ(print_view_manager.snooped_settings()->copies(),
kTestPrintSettingsCopies);
#if BUILDFLAG(IS_LINUX) && BUILDFLAG(USE_CUPS)
// Collect just the keys to compare the info options vs. advanced settings.
std::vector<std::string> advanced_setting_keys;
std::vector<std::string> print_info_options_keys;
const PrintSettings::AdvancedSettings& advanced_settings =
print_view_manager.snooped_settings()->advanced_settings();
for (const auto& advanced_setting : advanced_settings) {
advanced_setting_keys.push_back(advanced_setting.first);
}
for (const auto& option : kTestDummyPrintInfoOptions) {
print_info_options_keys.push_back(option.first);
}
EXPECT_THAT(advanced_setting_keys,
testing::UnorderedElementsAreArray(print_info_options_keys));
#endif // BUILDFLAG(IS_LINUX) && BUILDFLAG(USE_CUPS)
}
#if BUILDFLAG(ENABLE_OOP_PRINTING)
IN_PROC_BROWSER_TEST_P(SystemAccessProcessServicePrintBrowserTest,
StartPrinting) {
AddPrinter("printer1");
SetPrinterNameForSubsequentContexts("printer1");
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL("/printing/test3.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
SetUpPrintViewManager(web_contents);
// The expected events for this are:
// 1. A print job is started.
// 2. Rendering for 1 page of document of content.
// 3. Completes with document done.
// 4. Wait for the one print job to be destroyed, to ensure printing
// finished cleanly before completing the test.
SetNumExpectedMessages(/*num=*/4);
PrintAfterPreviewIsReadyAndLoaded();
EXPECT_EQ(start_printing_result(), mojom::ResultCode::kSuccess);
#if BUILDFLAG(IS_WIN)
// TODO(crbug.com/1008222) Include Windows coverage of
// RenderPrintedDocument() once XPS print pipeline is added.
EXPECT_EQ(render_printed_page_result(), mojom::ResultCode::kSuccess);
EXPECT_EQ(render_printed_page_count(), 1);
#else
EXPECT_EQ(render_printed_document_result(), mojom::ResultCode::kSuccess);
#endif
EXPECT_EQ(document_done_result(), mojom::ResultCode::kSuccess);
EXPECT_EQ(error_dialog_shown_count(), 0u);
EXPECT_EQ(print_job_destruction_count(), 1);
}
IN_PROC_BROWSER_TEST_P(SystemAccessProcessServicePrintBrowserTest,
StartPrintingMultipage) {
AddPrinter("printer1");
SetPrinterNameForSubsequentContexts("printer1");
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL("/printing/multipage.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
SetUpPrintViewManager(web_contents);
#if BUILDFLAG(IS_WIN)
// Windows GDI results in a callback for each rendered page.
// The expected events for this are:
// 1. A print job is started.
// 2. First page is rendered.
// 3. Second page is rendered.
// 4. Third page is rendered.
// 5. Completes with document done.
// 6. Wait for the one print job to be destroyed, to ensure printing
// finished cleanly before completing the test.
// TODO(crbug.com/1008222) Include Windows coverage of
// RenderPrintedDocument() once XPS print pipeline is added.
SetNumExpectedMessages(/*num=*/6);
#else
// The expected events for this are:
// 1. A print job is started.
// 2. Document is rendered.
// 3. Completes with document done.
// 4. Wait for the one print job to be destroyed, to ensure printing
// finished cleanly before completing the test.
SetNumExpectedMessages(/*num=*/4);
#endif
PrintAfterPreviewIsReadyAndLoaded();
EXPECT_EQ(start_printing_result(), mojom::ResultCode::kSuccess);
#if BUILDFLAG(IS_WIN)
// TODO(crbug.com/1008222) Include Windows coverage of
// RenderPrintedDocument() once XPS print pipeline is added.
EXPECT_EQ(render_printed_page_result(), mojom::ResultCode::kSuccess);
EXPECT_EQ(render_printed_page_count(), 3);
#else
EXPECT_EQ(render_printed_document_result(), mojom::ResultCode::kSuccess);
#endif
EXPECT_EQ(document_done_result(), mojom::ResultCode::kSuccess);
EXPECT_EQ(error_dialog_shown_count(), 0u);
EXPECT_EQ(print_job_destruction_count(), 1);
}
IN_PROC_BROWSER_TEST_P(SystemAccessProcessServicePrintBrowserTest,
StartPrintingSpoolingSharedMemoryError) {
AddPrinter("printer1");
SetPrinterNameForSubsequentContexts("printer1");
PrimeForSpoolingSharedMemoryErrors();
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL("/printing/test3.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
SetUpPrintViewManager(web_contents);
// No attempt to retry is made if a job has a shared memory error when trying
// to spool a page/document fails on a shared memory error. The test
// sequence for this is:
// 1. A print job is started.
// 2. Spooling to send the render data will fail. An error dialog is shown.
// 3. The print job is canceled. The callback from the service could occur
// after the print job has been destroyed.
// 4. Wait for the one print job to be destroyed, to ensure printing
// finished cleanly before completing the test.
SetNumExpectedMessages(/*num=*/4);
PrintAfterPreviewIsReadyAndLoaded();
EXPECT_EQ(start_printing_result(), mojom::ResultCode::kSuccess);
EXPECT_EQ(error_dialog_shown_count(), 1u);
EXPECT_EQ(cancel_count(), 1);
EXPECT_EQ(print_job_destruction_count(), 1);
}
// TODO(crbug.com/1384459): Flaky on MSan builds.
#if defined(MEMORY_SANITIZER)
#define MAYBE_StartPrintingFails DISABLED_StartPrintingFails
#else
#define MAYBE_StartPrintingFails StartPrintingFails
#endif
IN_PROC_BROWSER_TEST_P(SystemAccessProcessPrintBrowserTest,
MAYBE_StartPrintingFails) {
AddPrinter("printer1");
SetPrinterNameForSubsequentContexts("printer1");
PrimeForErrorsInNewDocument();
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL("/printing/test3.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
SetUpPrintViewManager(web_contents);
if (GetParam() == PrintBackendFeatureVariation::kInBrowserProcess) {
// There are no callbacks for print stages with in-browser printing. So
// the print job is started, but that fails, and there is no capturing of
// that result.
// The expected events for this are:
// 1. An error dialog is shown.
// 2. Wait for the one print job to be destroyed, to ensure printing
// finished cleanly before completing the test.
SetNumExpectedMessages(/*num=*/2);
} else {
// The expected events for this are:
// 1. A print job is started, but that fails.
// 2. An error dialog is shown.
// 3. The print job is canceled. The callback from the service could occur
// after the print job has been destroyed.
// 4. Wait for the one print job to be destroyed, to ensure printing
// finished cleanly before completing the test.
SetNumExpectedMessages(/*num=*/4);
}
PrintAfterPreviewIsReadyAndLoaded();
EXPECT_EQ(start_printing_result(), mojom::ResultCode::kFailed);
EXPECT_EQ(error_dialog_shown_count(), 1u);
// No tracking of cancel for in-browser tests, only for OOP.
if (GetParam() != PrintBackendFeatureVariation::kInBrowserProcess)
EXPECT_EQ(cancel_count(), 1);
EXPECT_EQ(print_job_destruction_count(), 1);
}
IN_PROC_BROWSER_TEST_F(SystemAccessProcessSandboxedServicePrintBrowserTest,
StartPrintingAccessDenied) {
AddPrinter("printer1");
SetPrinterNameForSubsequentContexts("printer1");
PrimeForAccessDeniedErrorsInNewDocument();
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL("/printing/test3.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
SetUpPrintViewManager(web_contents);
// The expected events for this are:
// 1. A print job is started, but has an access-denied error.
// 2. A retry to start the print job with adjusted access will succeed.
// 3. Rendering for 1 page of document of content.
// 4. Completes with document done.
// 5. Wait for the one print job to be destroyed, to ensure printing
// finished cleanly before completing the test.
SetNumExpectedMessages(/*num=*/5);
PrintAfterPreviewIsReadyAndLoaded();
EXPECT_EQ(start_printing_result(), mojom::ResultCode::kSuccess);
#if BUILDFLAG(IS_WIN)
// TODO(crbug.com/1008222) Include Windows coverage of
// RenderPrintedDocument() once XPS print pipeline is added.
EXPECT_EQ(render_printed_page_result(), mojom::ResultCode::kSuccess);
EXPECT_EQ(render_printed_page_count(), 1);
#else
EXPECT_EQ(render_printed_document_result(), mojom::ResultCode::kSuccess);
#endif
EXPECT_EQ(document_done_result(), mojom::ResultCode::kSuccess);
EXPECT_EQ(error_dialog_shown_count(), 0u);
EXPECT_EQ(print_job_destruction_count(), 1);
}
IN_PROC_BROWSER_TEST_F(SystemAccessProcessSandboxedServicePrintBrowserTest,
StartPrintingRepeatedAccessDenied) {
AddPrinter("printer1");
SetPrinterNameForSubsequentContexts("printer1");
PrimeAsRepeatingErrorGenerator();
PrimeForAccessDeniedErrorsInNewDocument();
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL("/printing/test3.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
SetUpPrintViewManager(web_contents);
// Test of a misbehaving printer driver which only returns access-denied
// errors. The expected events for this are:
// 1. A print job is started, but has an access-denied error.
// 2. A retry to start the print job with adjusted access will still fail.
// 3. An error dialog is shown.
// 4. The print job is canceled. The callback from the service could occur
// after the print job has been destroyed.
// 5. Wait for the one print job to be destroyed, to ensure printing
// finished cleanly before completing the test.
SetNumExpectedMessages(/*num=*/5);
PrintAfterPreviewIsReadyAndLoaded();
EXPECT_EQ(start_printing_result(), mojom::ResultCode::kAccessDenied);
EXPECT_EQ(error_dialog_shown_count(), 1u);
EXPECT_EQ(cancel_count(), 1);
EXPECT_EQ(print_job_destruction_count(), 1);
}
#if BUILDFLAG(IS_WIN)
IN_PROC_BROWSER_TEST_F(SystemAccessProcessSandboxedServicePrintBrowserTest,
StartPrintingRenderPageAccessDenied) {
AddPrinter("printer1");
SetPrinterNameForSubsequentContexts("printer1");
PrimeForAccessDeniedErrorsInRenderPrintedPage();
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL("/printing/test3.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
SetUpPrintViewManager(web_contents);
// No attempt to retry is made if an access-denied error occurs when trying
// to render a page. The expected events for this are:
// 1. A print job is started.
// 2. Rendering for 1 page of document of content fails with access denied.
// 3. An error dialog is shown.
// 4. The print job is canceled. The callback from the service could occur
// after the print job has been destroyed.
// 5. Wait for the one print job to be destroyed, to ensure printing
// finished cleanly before completing the test.
SetNumExpectedMessages(/*num=*/5);
PrintAfterPreviewIsReadyAndLoaded();
EXPECT_EQ(start_printing_result(), mojom::ResultCode::kSuccess);
EXPECT_EQ(render_printed_page_result(), mojom::ResultCode::kAccessDenied);
EXPECT_EQ(render_printed_page_count(), 0);
EXPECT_EQ(error_dialog_shown_count(), 1u);
EXPECT_EQ(cancel_count(), 1);
EXPECT_EQ(print_job_destruction_count(), 1);
}
IN_PROC_BROWSER_TEST_F(SystemAccessProcessSandboxedServicePrintBrowserTest,
StartPrintingMultipageMidJobError) {
AddPrinter("printer1");
SetPrinterNameForSubsequentContexts("printer1");
// Delay rendering until all pages have been sent, to avoid any race
// conditions related to error handling. This is to ensure that page 3 is in
// the service queued for processing, before we let page 2 be processed and
// have it trigger an error that could affect page 3 processing.
PrimeForDelayedRenderingUntilPage(/*page_number=*/3);
PrimeForRenderingErrorOnPage(/*page_number=*/2);
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL("/printing/multipage.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
SetUpPrintViewManager(web_contents);
// The expected events for this are:
// 1. Start the print job.
// 2. First page render callback shows success.
// 3. Second page render callback shows failure. Will start failure
// processing to cancel the print job.
// 4. A printing error dialog is displayed.
// 5. Third page render callback will show it was canceled (due to prior
// failure). This is disregarded by the browser, since the job has
// already been canceled.
// 6. The print job is canceled. The callback from the service could occur
// after the print job has been destroyed.
// 7. Wait for the one print job to be destroyed, to ensure printing
// finished cleanly before completing the test.
SetNumExpectedMessages(/*num=*/7);
PrintAfterPreviewIsReadyAndLoaded();
EXPECT_EQ(start_printing_result(), mojom::ResultCode::kSuccess);
// First failure page is `kFailed`, but is followed by another page with
// status `kCanceled`.
EXPECT_EQ(render_printed_page_result(), mojom::ResultCode::kCanceled);
EXPECT_EQ(render_printed_page_count(), 1);
EXPECT_EQ(error_dialog_shown_count(), 1u);
EXPECT_EQ(cancel_count(), 1);
EXPECT_EQ(print_job_destruction_count(), 1);
}
#endif // BUILDFLAG(IS_WIN)
// TODO(crbug.com/1008222) Include Windows once XPS print pipeline is added.
#if !BUILDFLAG(IS_WIN)
IN_PROC_BROWSER_TEST_F(SystemAccessProcessSandboxedServicePrintBrowserTest,
StartPrintingRenderDocumentAccessDenied) {
AddPrinter("printer1");
SetPrinterNameForSubsequentContexts("printer1");
PrimeForAccessDeniedErrorsInRenderPrintedDocument();
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL("/printing/test3.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
SetUpPrintViewManager(web_contents);
// No attempt to retry is made if an access-denied error occurs when trying
// to render a document. The expected events for this are:
// 1. A print job is started.
// 2. Rendering for 1 page of document of content fails with access denied.
// 3. An error dialog is shown.
// 4. The print job is canceled. The callback from the service could occur
// after the print job has been destroyed.
// 5. Wait for the one print job to be destroyed, to ensure printing
// finished cleanly before completing the test.
SetNumExpectedMessages(/*num=*/5);
PrintAfterPreviewIsReadyAndLoaded();
EXPECT_EQ(start_printing_result(), mojom::ResultCode::kSuccess);
EXPECT_EQ(render_printed_document_result(), mojom::ResultCode::kAccessDenied);
EXPECT_EQ(error_dialog_shown_count(), 1u);
EXPECT_EQ(cancel_count(), 1);
EXPECT_EQ(print_job_destruction_count(), 1);
}
#endif // !BUILDFLAG(IS_WIN)
IN_PROC_BROWSER_TEST_F(SystemAccessProcessSandboxedServicePrintBrowserTest,
StartPrintingDocumentDoneAccessDenied) {
AddPrinter("printer1");
SetPrinterNameForSubsequentContexts("printer1");
PrimeForAccessDeniedErrorsInDocumentDone();
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL("/printing/test3.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
SetUpPrintViewManager(web_contents);
// No attempt to retry is made if an access-denied error occurs when trying
// do wrap-up a rendered document. The expected events are:
// 1. A print job is started.
// 2. Rendering for 1 page of document of content.
// 3. Document done results in an access-denied error.
// 4. An error dialog is shown.
// 5. The print job is canceled. The callback from the service could occur
// after the print job has been destroyed.
// 6. Wait for the one print job to be destroyed, to ensure printing
// finished cleanly before completing the test.
SetNumExpectedMessages(/*num=*/6);
PrintAfterPreviewIsReadyAndLoaded();
EXPECT_EQ(start_printing_result(), mojom::ResultCode::kSuccess);
#if BUILDFLAG(IS_WIN)
// TODO(crbug.com/1008222) Include Windows coverage of
// RenderPrintedDocument() once XPS print pipeline is added.
EXPECT_EQ(render_printed_page_result(), mojom::ResultCode::kSuccess);
EXPECT_EQ(render_printed_page_count(), 1);
#else
EXPECT_EQ(render_printed_document_result(), mojom::ResultCode::kSuccess);
#endif
EXPECT_EQ(document_done_result(), mojom::ResultCode::kAccessDenied);
EXPECT_EQ(error_dialog_shown_count(), 1u);
EXPECT_EQ(cancel_count(), 1);
EXPECT_EQ(print_job_destruction_count(), 1);
}
#if BUILDFLAG(ENABLE_BASIC_PRINT_DIALOG)
IN_PROC_BROWSER_TEST_P(SystemAccessProcessPrintBrowserTest,
SystemPrintFromPrintPreview) {
// TODO(crbug.com/1393505) Enable OOP test coverage once underlying
// printing stack updates to support this scenario with out-of-process are
// in place.
if (GetParam() != PrintBackendFeatureVariation::kInBrowserProcess) {
GTEST_SKIP() << "Skipping test for out-of-process, which is known to crash "
"when transitioning to system print from print preview";
}
AddPrinter("printer1");
SetPrinterNameForSubsequentContexts("printer1");
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL("/printing/test3.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
SetUpPrintViewManager(web_contents);
if (GetParam() == PrintBackendFeatureVariation::kInBrowserProcess) {
#if BUILDFLAG(IS_WIN)
// There are no callbacks that trigger for print stages with in-browser
// printing for the Windows case. The only expected event for this is to
// wait for the one print job to be destroyed, to ensure printing finished
// cleanly before completing the test.
SetNumExpectedMessages(/*num=*/1);
#else
// Once the transition to system print is initiated, the expected events
// are:
// 1. Use default settings.
// 2. Ask the user for settings.
// 3. Wait until all processing for DidPrintDocument is known to have
// completed, to ensure printing finished cleanly before completing the
// test.
// 4. Wait for the one print job to be destroyed, to ensure printing
// finished cleanly before completing the test.
SetNumExpectedMessages(/*num=*/4);
#endif // BUILDFLAG(IS_WIN)
} else {
// TODO(crbug.com/1393505) Fill in expected events once the printing stack
// updates to support this scenario with out-of-process are in place.
}
SystemPrintFromPreviewOnceReadyAndLoaded();
#if !BUILDFLAG(IS_WIN)
if (GetParam() == PrintBackendFeatureVariation::kInBrowserProcess) {
EXPECT_TRUE(did_get_settings_with_ui());
EXPECT_EQ(did_print_document_count(), 1);
} else {
// TODO(crbug.com/1393505) Fill in expectations once the printing stack
// updates to support this scenario with out-of-process are in place.
}
#endif
EXPECT_EQ(error_dialog_shown_count(), 0u);
EXPECT_EQ(print_job_destruction_count(), 1);
}
IN_PROC_BROWSER_TEST_P(SystemAccessProcessServicePrintBrowserTest,
StartBasicPrint) {
AddPrinter("printer1");
SetPrinterNameForSubsequentContexts("printer1");
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL("/printing/test3.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
SetUpPrintViewManager(web_contents);
#if BUILDFLAG(IS_WIN)
// The expected events for this are:
// 1. Get the default settings.
// 2. Ask the user for settings.
// 3. A print job is started.
// 4. The print compositor will complete generating the document.
// 5. The document is rendered.
// 6. Receive document done notification.
// 8. Wait for the one print job to be destroyed, to ensure printing
// finished cleanly before completing the test.
SetNumExpectedMessages(/*num=*/7);
#else
// The expected events for this are:
// 1. Get the default settings. Ask the user for settings; due to issues
// with displaying a system dialog from the utility process, there is no
// callback to capture the request for user supplied settings.
// 2. A print job is started.
// 3. The print compositor will complete generating the document.
// 4. The document is rendered.
// 5. Receive document done notification.
// 6. Wait for the one print job to be destroyed, to ensure printing
// finished cleanly before completing the test.
// TODO(crbug.com/1374188) Update this expectation once
// `AskUserForSettings()` is able to be pushed OOP for Linux.
SetNumExpectedMessages(/*num=*/6);
#endif
StartBasicPrint(web_contents);
WaitUntilCallbackReceived();
EXPECT_EQ(use_default_settings_result(), mojom::ResultCode::kSuccess);
// macOS and Linux currently have to invoke a system dialog from within the
// browser process. There is not a callback to capture the result in these
// cases.
// TODO(crbug.com/1374188) Re-enable this check against
// `ask_user_for_settings_result()` once `AskForUserSettings()` is able to be
// pushed OOP for Linux.
#if BUILDFLAG(IS_WIN)
EXPECT_EQ(ask_user_for_settings_result(), mojom::ResultCode::kSuccess);
#endif
EXPECT_EQ(start_printing_result(), mojom::ResultCode::kSuccess);
#if BUILDFLAG(IS_WIN)
// TODO(crbug.com/1008222) Include Windows coverage of
// RenderPrintedDocument() once XPS print pipeline is added.
EXPECT_EQ(render_printed_page_result(), mojom::ResultCode::kSuccess);
EXPECT_EQ(render_printed_page_count(), 1);
#else
EXPECT_EQ(render_printed_document_result(), mojom::ResultCode::kSuccess);
#endif
EXPECT_EQ(document_done_result(), mojom::ResultCode::kSuccess);
EXPECT_EQ(error_dialog_shown_count(), 0u);
EXPECT_EQ(did_print_document_count(), 1);
EXPECT_EQ(print_job_destruction_count(), 1);
}
// TODO(crbug.com/1375007): Very flaky on Mac and slightly on Linux.
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
#define MAYBE_StartBasicPrintCancel DISABLED_StartBasicPrintCancel
#else
#define MAYBE_StartBasicPrintCancel StartBasicPrintCancel
#endif
IN_PROC_BROWSER_TEST_F(SystemAccessProcessInBrowserPrintBrowserTest,
MAYBE_StartBasicPrintCancel) {
AddPrinter("printer1");
SetPrinterNameForSubsequentContexts("printer1");
PrimeForCancelInAskUserForSettings();
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL("/printing/test3.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
SetUpPrintViewManager(web_contents);
// The expected events for this are:
// 1. Get the default settings.
// 2. Ask the user for settings, which indicates to cancel the print
// request. No further printing calls are made.
// No print job is created because of such an early cancel.
SetNumExpectedMessages(/*num=*/2);
StartBasicPrint(web_contents);
WaitUntilCallbackReceived();
EXPECT_TRUE(did_use_default_settings());
EXPECT_TRUE(did_get_settings_with_ui());
EXPECT_EQ(error_dialog_shown_count(), 0u);
EXPECT_EQ(did_print_document_count(), 0);
EXPECT_EQ(print_job_destruction_count(), 0);
// `PrintBackendService` should never be used when printing in-browser.
EXPECT_FALSE(print_backend_service_use_detected());
}
IN_PROC_BROWSER_TEST_P(SystemAccessProcessPrintBrowserTest,
StartBasicPrintFails) {
AddPrinter("printer1");
SetPrinterNameForSubsequentContexts("printer1");
PrimeForErrorsInNewDocument();
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL("/printing/test3.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
SetUpPrintViewManager(web_contents);
if (GetParam() == PrintBackendFeatureVariation::kInBrowserProcess) {
// There are only partial overrides to track most steps in the printing
// pipeline, so the expected events for this are:
// 1. Gets default settings.
// 2. Asks user for settings.
// 3. A print job is started, but that fails. There is no override to
// this notice directly. This does cause an error dialog to be shown.
// 4. Wait for the one print job to be destroyed, to ensure printing
// finished cleanly before completing the test.
// 5. The renderer will have initiated printing of document, which could
// invoke the print compositor. Wait until all processing for
// DidPrintDocument is known to have completed, to ensure printing
// finished cleanly before completing the test.
SetNumExpectedMessages(/*num=*/5);
} else {
#if BUILDFLAG(IS_WIN)
// The expected events for this are:
// 1. Gets default settings.
// 2. Asks user for settings.
// 3. A print job is started, which fails.
// 4. An error dialog is shown.
// 5. The print job is canceled. The callback from the service could occur
// after the print job has been destroyed.
// 6. Wait for the one print job to be destroyed, to ensure printing
// finished cleanly before completing the test.
// 7. The renderer will have initiated printing of document, which could
// invoke the print compositor. Wait until all processing for
// DidPrintDocument is known to have completed, to ensure printing
// finished cleanly before completing the test.
SetNumExpectedMessages(/*num=*/7);
#else
// The expected events for this are:
// 1. Gets default settings.
// 2. Asks user for settings. This is invoked from the browser process,
// so there is no override to observe this. Then a print job is
// started, which fails.
// 3. An error dialog is shown.
// 4. The print job is canceled. The callback from the service could occur
// after the print job has been destroyed.
// 5. Wait for the one print job to be destroyed, to ensure printing
// finished cleanly before completing the test.
// 6. The print compositor will have started to generate the document.
// Wait until that is known to have completed, to ensure printing
// finished cleanly before completing the test.
SetNumExpectedMessages(/*num=*/6);
#endif // BUILDFLAG(IS_WIN)
}
StartBasicPrint(web_contents);
WaitUntilCallbackReceived();
EXPECT_EQ(start_printing_result(), mojom::ResultCode::kFailed);
EXPECT_EQ(error_dialog_shown_count(), 1u);
EXPECT_EQ(
cancel_count(),
GetParam() == PrintBackendFeatureVariation::kInBrowserProcess ? 0 : 1);
EXPECT_EQ(did_print_document_count(), 1);
EXPECT_EQ(print_job_destruction_count(), 1);
}
// macOS and Linux currently have to invoke a system dialog from within the
// browser process. There is not a callback to capture the result in these
// cases.
// TODO(crbug.com/1374188) Re-enable for Linux once `AskForUserSettings()` is
// able to be pushed OOP for Linux.
#undef MAYBE_StartBasicPrintCancel
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
#define MAYBE_StartBasicPrintCancel DISABLED_StartBasicPrintCancel
#else
#define MAYBE_StartBasicPrintCancel StartBasicPrintCancel
#endif
IN_PROC_BROWSER_TEST_P(SystemAccessProcessServicePrintBrowserTest,
MAYBE_StartBasicPrintCancel) {
AddPrinter("printer1");
SetPrinterNameForSubsequentContexts("printer1");
PrimeForCancelInAskUserForSettings();
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL("/printing/test3.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
SetUpPrintViewManager(web_contents);
// The expected events for this are:
// 1. Get the default settings.
// 2. Ask the user for settings, which indicates to cancel the print
// request. No further printing calls are made.
// No print job is created because of such an early cancel.
SetNumExpectedMessages(/*num=*/2);
StartBasicPrint(web_contents);
WaitUntilCallbackReceived();
EXPECT_EQ(use_default_settings_result(), mojom::ResultCode::kSuccess);
EXPECT_EQ(ask_user_for_settings_result(), mojom::ResultCode::kCanceled);
EXPECT_EQ(error_dialog_shown_count(), 0u);
EXPECT_EQ(did_print_document_count(), 0);
EXPECT_EQ(print_job_construction_count(), 0);
}
IN_PROC_BROWSER_TEST_P(SystemAccessProcessServicePrintBrowserTest,
StartBasicPrintConcurrent) {
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL("/printing/test3.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
TestPrintViewManager* print_view_manager =
TestPrintViewManager::CreateForWebContents(web_contents);
// Pretend that a window has started a system print.
absl::optional<uint32_t> client_id =
PrintBackendServiceManager::GetInstance().RegisterQueryWithUiClient();
ASSERT_TRUE(client_id.has_value());
// Now initiate a system print that would exist concurrently with that.
StartBasicPrint(web_contents);
const absl::optional<bool>& result = print_view_manager->print_now_result();
ASSERT_TRUE(result.has_value());
// With the exception of Linux, concurrent system print is not allowed.
#if BUILDFLAG(IS_LINUX)
EXPECT_TRUE(*result);
#else
// The denied concurrent print is silent without an error.
EXPECT_EQ(error_dialog_shown_count(), 0u);
EXPECT_FALSE(*result);
#endif
// Cleanup before test shutdown.
PrintBackendServiceManager::GetInstance().UnregisterClient(*client_id);
}
IN_PROC_BROWSER_TEST_P(SystemAccessProcessServicePrintBrowserTest,
StartBasicPrintUseDefaultFails) {
PrimeForFailInUseDefaultSettings();
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL("/printing/test3.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
SetUpPrintViewManager(web_contents);
// The expected events for this are:
// 1. Get the default settings, which fails.
// 2. The print error dialog is shown.
// No print job is created from such an early failure.
SetNumExpectedMessages(/*num=*/2);
StartBasicPrint(web_contents);
WaitUntilCallbackReceived();
EXPECT_EQ(use_default_settings_result(), mojom::ResultCode::kFailed);
EXPECT_EQ(error_dialog_shown_count(), 1u);
EXPECT_EQ(did_print_document_count(), 0);
EXPECT_EQ(print_job_construction_count(), 0);
}
#endif // BUILDFLAG(ENABLE_BASIC_PRINT_DIALOG)
#endif // BUILDFLAG(ENABLE_OOP_PRINTING)
#endif // !BUILDFLAG(IS_CHROMEOS)
#if BUILDFLAG(ENABLE_PRINT_CONTENT_ANALYSIS)
struct ContentAnalysisTestCase {
bool content_analysis_allows_print = false;
bool oop_enabled = false;
};
class ContentAnalysisPrintBrowserTest
: public PrintBrowserTest,
public testing::WithParamInterface<ContentAnalysisTestCase> {
public:
ContentAnalysisPrintBrowserTest() {
policy::SetDMTokenForTesting(
policy::DMToken::CreateValidTokenForTesting(kFakeDmToken));
enterprise_connectors::ContentAnalysisDelegate::SetFactoryForTesting(
base::BindRepeating(
&enterprise_connectors::FakeContentAnalysisDelegate::Create,
base::DoNothing(),
base::BindRepeating(
&ContentAnalysisPrintBrowserTest::ScanningResponse,
base::Unretained(this)),
kFakeDmToken));
enterprise_connectors::ContentAnalysisDialog::SetShowDialogDelayForTesting(
base::Milliseconds(0));
}
void SetUp() override {
if (oop_enabled()) {
feature_list_.InitWithFeaturesAndParameters(
{
{features::kEnableOopPrintDrivers,
{{features::kEnableOopPrintDriversJobPrint.name, "true"}}},
{features::kEnablePrintContentAnalysis, {}},
},
{});
} else {
feature_list_.InitAndEnableFeature(features::kEnablePrintContentAnalysis);
}
test_printing_context_factory()->SetPrinterNameForSubsequentContexts(
"printer_name");
PrintBrowserTest::SetUp();
}
void SetUpOnMainThread() override {
safe_browsing::SetAnalysisConnector(
browser()->profile()->GetPrefs(),
enterprise_connectors::AnalysisConnector::PRINT,
R"({
"service_provider": "google",
"enable": [ {"url_list": ["*"], "tags": ["dlp"]} ],
"block_until_verdict": 1,
"block_large_files": true
})");
PrintBrowserTest::SetUpOnMainThread();
}
bool content_analysis_allows_print() const {
return GetParam().content_analysis_allows_print;
}
bool oop_enabled() { return GetParam().oop_enabled; }
enterprise_connectors::ContentAnalysisResponse ScanningResponse(
const std::string& contents,
const base::FilePath& path) {
enterprise_connectors::ContentAnalysisResponse response;
auto* result = response.add_results();
result->set_tag("dlp");
result->set_status(
enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS);
if (!content_analysis_allows_print()) {
auto* rule = result->add_triggered_rules();
rule->set_rule_name("blocking_rule_name");
rule->set_action(enterprise_connectors::TriggeredRule::BLOCK);
}
return response;
}
int new_document_called_count() {
return test_printing_context_factory()->new_document_called_count();
}
private:
base::test::ScopedFeatureList feature_list_;
};
class ContentAnalysisScriptedPreviewlessPrintBrowserTest
: public ContentAnalysisPrintBrowserTest {
public:
void SetUpCommandLine(base::CommandLine* cmd_line) override {
cmd_line->AppendSwitch(switches::kDisablePrintPreview);
ContentAnalysisPrintBrowserTest::SetUpCommandLine(cmd_line);
}
void RunScriptedPrintTest(const std::string& script) {
AddPrinter("printer_name");
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL("/printing/test1.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
auto* print_view_manager =
TestPrintViewManagerForContentAnalysis::CreateForWebContents(
web_contents);
content::ExecuteScriptAsync(web_contents->GetPrimaryMainFrame(), script);
print_view_manager->WaitOnScanning();
ASSERT_EQ(print_view_manager->scripted_print_called(),
content_analysis_allows_print());
// Validate that `NewDocument` was never call as that can needlessly
// prompt the user.
ASSERT_EQ(new_document_called_count(), 0);
}
};
#if !BUILDFLAG(IS_CHROMEOS)
IN_PROC_BROWSER_TEST_P(ContentAnalysisPrintBrowserTest, PrintNow) {
#if BUILDFLAG(IS_WIN)
// TODO(crbug.com/1396386): Remove this when tests are fixed.
if (oop_enabled())
return;
#endif
AddPrinter("printer_name");
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL("/printing/test1.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
auto* print_view_manager =
TestPrintViewManagerForContentAnalysis::CreateForWebContents(
web_contents);
StartPrint(browser()->tab_strip_model()->GetActiveWebContents(),
/*print_renderer=*/mojo::NullAssociatedRemote(),
/*print_preview_disabled=*/true,
/*has_selection=*/false);
print_view_manager->WaitOnScanning();
// PrintNow uses the same code path as scripted prints to scan printed pages,
// so print_now_called() should always happen and scripted_print_called()
// should be called with the same result that is expected from scanning.
ASSERT_TRUE(print_view_manager->print_now_called());
ASSERT_EQ(print_view_manager->scripted_print_called(),
content_analysis_allows_print());
// Validate that `NewDocument` was never call as that can needlessly
// prompt the user.
ASSERT_EQ(new_document_called_count(), 0);
}
IN_PROC_BROWSER_TEST_P(ContentAnalysisPrintBrowserTest, PrintWithPreview) {
AddPrinter("printer_name");
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL("/printing/test1.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
auto* print_view_manager =
TestPrintViewManagerForContentAnalysis::CreateForWebContents(
web_contents);
StartPrint(browser()->tab_strip_model()->GetActiveWebContents(),
/*print_renderer=*/mojo::NullAssociatedRemote(),
/*print_preview_disabled=*/false,
/*has_selection=*/false);
print_view_manager->WaitOnScanning();
ASSERT_EQ(print_view_manager->preview_allowed(),
content_analysis_allows_print());
// Validate that `NewDocument` was never call as that can needlessly
// prompt the user.
ASSERT_EQ(new_document_called_count(), 0);
}
IN_PROC_BROWSER_TEST_P(ContentAnalysisScriptedPreviewlessPrintBrowserTest,
DocumentExecPrint) {
RunScriptedPrintTest("document.execCommand('print');");
}
IN_PROC_BROWSER_TEST_P(ContentAnalysisScriptedPreviewlessPrintBrowserTest,
WindowPrint) {
RunScriptedPrintTest("window.print()");
}
#endif // !BUILDFLAG(IS_CHROMEOS)
#if BUILDFLAG(IS_CHROMEOS)
IN_PROC_BROWSER_TEST_P(ContentAnalysisPrintBrowserTest,
BlockedByDLPThenNoContentAnalysis) {
AddPrinter("printer_name");
ASSERT_TRUE(embedded_test_server()->Started());
GURL url(embedded_test_server()->GetURL("/printing/test1.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
auto* print_view_manager =
TestPrintViewManagerForContentAnalysis::CreateForWebContents(
web_contents);
print_view_manager->set_allowed_by_dlp(false);
StartPrint(browser()->tab_strip_model()->GetActiveWebContents(),
/*print_renderer=*/mojo::NullAssociatedRemote(),
/*print_preview_disabled=*/false,
/*has_selection=*/false);
print_view_manager->WaitOnPreview();
ASSERT_TRUE(print_view_manager->preview_allowed().has_value());
ASSERT_FALSE(print_view_manager->preview_allowed().value());
// This is always 0 because printing is always blocked by the DLP policy.
ASSERT_EQ(new_document_called_count(), 0);
}
#endif // BUILDFLAG(IS_CHROMEOS)
INSTANTIATE_TEST_SUITE_P(
All,
ContentAnalysisPrintBrowserTest,
testing::Values(
ContentAnalysisTestCase{/*content_analysis_allows_print=*/true,
/*oop_enabled=*/true},
ContentAnalysisTestCase{/*content_analysis_allows_print=*/true,
/*oop_enabled=*/false},
ContentAnalysisTestCase{/*content_analysis_allows_print=*/false,
/*oop_enabled=*/true},
ContentAnalysisTestCase{/*content_analysis_allows_print=*/false,
/*oop_enabled=*/false}));
#if BUILDFLAG(ENABLE_BASIC_PRINT_DIALOG)
INSTANTIATE_TEST_SUITE_P(
All,
ContentAnalysisScriptedPreviewlessPrintBrowserTest,
// TODO(crbug.com/1396386): Add back oop_enabled=true values when tests are
// fixed.
testing::Values(
ContentAnalysisTestCase{/*content_analysis_allows_print=*/true,
/*oop_enabled=*/false},
ContentAnalysisTestCase{/*content_analysis_allows_print=*/false,
/*oop_enabled=*/false}));
#endif // BUILDFLAG(ENABLE_BASIC_PRINT_DIALOG)
#endif // BUILDFLAG(ENABLE_PRINT_SCANNING)
} // namespace printing