chromium/chrome/browser/printing/system_access_process_print_browsertest.cc

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <memory>
#include <optional>
#include <tuple>
#include <utility>
#include <vector>

#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/browser/printing/print_browsertest.h"
#include "chrome/browser/printing/print_job.h"
#include "chrome/browser/printing/print_test_utils.h"
#include "chrome/browser/printing/print_view_manager_base.h"
#include "chrome/browser/printing/print_view_manager_common.h"
#include "chrome/browser/printing/printer_query.h"
#include "chrome/browser/printing/test_print_preview_observer.h"
#include "chrome/browser/printing/test_print_view_manager.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/enterprise/buildflags/buildflags.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/no_renderer_crashes_assertion.h"
#include "mojo/public/cpp/bindings/pending_associated_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "printing/buildflags/buildflags.h"
#include "printing/mojom/print.mojom.h"
#include "printing/printing_context.h"
#include "printing/printing_features.h"
#include "printing/printing_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/size_f.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/print_preview_dialog_controller.h"
#include "chrome/browser/printing/printer_query_oop.h"
#include "chrome/browser/task_manager/task_manager_browsertest_util.h"
#include "chrome/browser/task_manager/task_manager_interface.h"
#include "chrome/browser/ui/browser_dialogs.h"
#include "chrome/services/printing/public/mojom/print_backend_service.mojom.h"
#endif

#if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS)
#include "chrome/browser/enterprise/connectors/analysis/content_analysis_dialog.h"
#include "chrome/browser/enterprise/connectors/common.h"
#include "chrome/browser/enterprise/connectors/test/deep_scanning_test_utils.h"  // nogncheck
#include "chrome/browser/enterprise/connectors/test/fake_content_analysis_delegate.h"  // nogncheck
#include "chrome/browser/policy/dm_token_utils.h"
#include "components/enterprise/common/proto/connectors.pb.h"

#if BUILDFLAG(ENTERPRISE_LOCAL_CONTENT_ANALYSIS)
#include "chrome/browser/enterprise/connectors/test/fake_content_analysis_sdk_manager.h"  // nogncheck
#endif  // BUILDFLAG(ENTERPRISE_LOCAL_CONTENT_ANALYSIS)
#endif  // BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS)

#if BUILDFLAG(IS_CHROMEOS)
// TODO(crbug.com/40567307)  ChromeOS uses different testing setup that isn't
// hooked up to make use of `TestPrintingContext` yet.
#error "ChromeOS not supported here yet"
#endif

TaskManagerInterface;
MatchAnyTab;
MatchUtility;
WaitForTaskManagerRows;

namespace printing {

namespace {

#if BUILDFLAG(ENABLE_OOP_PRINTING) && !BUILDFLAG(IS_CHROMEOS)
constexpr gfx::SizeF kLetterPhysicalSize =;
constexpr gfx::RectF kLetterPrintableArea =;
constexpr gfx::SizeF kLegalPhysicalSize =;
constexpr gfx::RectF kLegalPrintableArea =;

// The default margins are set to 1.0cm in //printing/print_settings.cc, which
// is about 28 printer units. The resulting content size is 556 x 736 for
// Letter, and similarly is 556 x 952 for Legal.
constexpr gfx::SizeF kLetterExpectedContentSize =;
constexpr gfx::SizeF kLegalExpectedContentSize =;
#endif  // BUILDFLAG(ENABLE_OOP_PRINTING) && !BUILDFLAG(IS_CHROMEOS)

#if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS)
constexpr char kFakeDmToken[] =;

// The policy values below correspond to the schema described in
// https://chromeenterprise.google/policies/#OnPrintEnterpriseConnector
constexpr char kCloudAnalysisBlockingPolicy[] =;

constexpr char kCloudAnalysisNonBlockingPolicy[] =;

#if BUILDFLAG(ENTERPRISE_LOCAL_CONTENT_ANALYSIS)
constexpr char kLocalAnalysisPolicy[] =;
#endif  // BUILDFLAG(ENTERPRISE_LOCAL_CONTENT_ANALYSIS)

OnDidCompositeForContentAnalysis;
#endif  // BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS)

#if BUILDFLAG(ENABLE_OOP_PRINTING)
void CancelPrintPreview(content::WebContents* preview_dialog) {}

// Values for parameterized testing.
enum class PrintBackendFeatureVariation {};

const char* GetPrintBackendString(PrintBackendFeatureVariation variation) {}

enum class PlatformPrintApiVariation {};

const char* GetPlatformPrintApiString(PlatformPrintApiVariation variation) {}

// Caution must be taken with platform API variations, as `kXps` should not
// be generated with `kInBrowserProcess`.  Use of `testing::Combine()` between
// `PrintBackendFeatureVariation` and `PlatformPrintApiVariation` could
// inadvertently cause this illegal combination.  This can be avoided by using
// a local helper method to generate the allowed combinations.
//
// `SystemAccessProcessPrintBrowserTestBase` will check this constraint at
// runtime.
struct PrintBackendAndPlatformPrintApiVariation {};

// Tests using these variations are concerned with all the different language
// types on Windows.
constexpr PrintBackendAndPlatformPrintApiVariation
    kSandboxedServicePlatformPrintLanguageApiVariations[] =;

std::string GetPrintBackendAndPlatformPrintApiString(
    const PrintBackendAndPlatformPrintApiVariation& variation) {}

std::string GetPrintBackendAndPlatformPrintApiTestSuffix(
    const testing::TestParamInfo<PrintBackendAndPlatformPrintApiVariation>&
        info) {}

std::vector<PrintBackendAndPlatformPrintApiVariation>
GeneratePrintBackendAndPlatformPrintApiVariations(
    std::vector<PrintBackendFeatureVariation> print_backend_variations) {}

std::string GetServiceLaunchTimingTestSuffix(
    const testing::TestParamInfo<bool>& info) {}
#endif  // BUILDFLAG(ENABLE_OOP_PRINTING)

}  // namespace

#if BUILDFLAG(ENABLE_OOP_PRINTING)
OnUseDefaultSettingsCallback;
OnGetSettingsWithUICallback;

ErrorCheckCallback;
OnDidUseDefaultSettingsCallback;
#if BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG)
using OnDidAskUserForSettingsCallback =
    base::RepeatingCallback<void(mojom::ResultCode result)>;
#endif
OnDidUpdatePrintSettingsCallback;
OnFinishDocumentDoneCallback;
OnDidStartPrintingCallback;
#if BUILDFLAG(IS_WIN)
using OnDidRenderPrintedPageCallback =
    base::RepeatingCallback<void(uint32_t page_number,
                                 mojom::ResultCode result)>;
#endif
OnDidRenderPrintedDocumentCallback;
OnDidDocumentDoneCallback;
OnDidCancelCallback;
OnDidShowErrorDialog;

class TestPrintJobWorker : public PrintJobWorker {};

class TestPrinterQuery : public PrinterQuery {};

class TestPrintJobWorkerOop : public PrintJobWorkerOop {};

class TestPrinterQueryOop : public PrinterQueryOop {};
#endif  // BUILDFLAG(ENABLE_OOP_PRINTING)

class SystemAccessProcessPrintBrowserTestBase
    : public PrintBrowserTest,
      public PrintJob::Observer,
      public PrintViewManagerBase::TestObserver {};

#if BUILDFLAG(ENABLE_OOP_PRINTING)

class SystemAccessProcessUnsandboxedEarlyStartServicePrintBrowserTest
    : public SystemAccessProcessPrintBrowserTestBase,
      public testing::WithParamInterface<bool> {};

INSTANTIATE_TEST_SUITE_P();

class SystemAccessProcessPrintBrowserTest
    : public SystemAccessProcessPrintBrowserTestBase,
      public testing::WithParamInterface<
          PrintBackendAndPlatformPrintApiVariation> {};

INSTANTIATE_TEST_SUITE_P();

SystemAccessProcessServicePrintBrowserTest;

INSTANTIATE_TEST_SUITE_P();

SystemAccessProcessSandboxedServicePrintBrowserTest;

INSTANTIATE_TEST_SUITE_P();

SystemAccessProcessSandboxedServiceLanguagePrintBrowserTest;

INSTANTIATE_TEST_SUITE_P();

IN_PROC_BROWSER_TEST_P(
    SystemAccessProcessUnsandboxedEarlyStartServicePrintBrowserTest,
    ServiceLaunched) {}

IN_PROC_BROWSER_TEST_P(SystemAccessProcessPrintBrowserTest,
                       UpdatePrintSettings) {}

IN_PROC_BROWSER_TEST_P(SystemAccessProcessPrintBrowserTest,
                       UpdatePrintSettingsPrintableArea) {}

IN_PROC_BROWSER_TEST_P(SystemAccessProcessPrintBrowserTest,
                       UpdatePrintSettingsFails) {}

IN_PROC_BROWSER_TEST_P(SystemAccessProcessPrintBrowserTest,
                       PrintWithPreviewBeforeLoadedUpdatePrintSettingsFails) {}

IN_PROC_BROWSER_TEST_P(
    SystemAccessProcessSandboxedServiceLanguagePrintBrowserTest,
    StartPrinting) {}

IN_PROC_BROWSER_TEST_P(SystemAccessProcessSandboxedServicePrintBrowserTest,
                       StartPrintingMultipage) {}

IN_PROC_BROWSER_TEST_P(SystemAccessProcessServicePrintBrowserTest,
                       StartPrintingSpoolingSharedMemoryError) {}

#if BUILDFLAG(IS_WIN)
IN_PROC_BROWSER_TEST_P(SystemAccessProcessPrintBrowserTest,
                       StartPrintingPdfConversionFails) {
  AddPrinter("printer1");
  SetPrinterNameForSubsequentContexts("printer1");
  PrimeForPdfConversionErrorOnPageIndex(/*page_index=*/1);

  ASSERT_TRUE(embedded_test_server()->Started());
  GURL url(embedded_test_server()->GetURL("/printing/3_pages.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 (UseService()) {
    // The expected events for this are:
    // 1.  Update print settings.
    // 2.  A print job is started.
    // 3.  PDF conversion fails, which results in the print job being
    //     canceled.
    // 4.  Wait for the print job to be destroyed, to ensure printing finished
    //     cleanly before completing the test.
    // No error dialog is shown.
    SetNumExpectedMessages(/*num=*/4);
  } else {
    // The expected events for this are:
    // 1.  Update print settings.
    // 2.  Print job is started, but is canceled and destroyed due to failure
    //     during PDF conversion failure.
    // No error dialog is shown.
    SetNumExpectedMessages(/*num=*/2);
  }
  PrintAfterPreviewIsReadyAndLoaded();

  // No tracking of start printing or cancel callbacks for in-browser tests,
  // only for OOP.
  if (UseService()) {
    EXPECT_EQ(start_printing_result(), mojom::ResultCode::kSuccess);
  } else {
    EXPECT_THAT(in_process_last_error_result_code(),
                testing::Optional(mojom::ResultCode::kCanceled));
  }
  // TODO(crbug.com/40288222):  Update expectation once an error is shown for
  // this failure.
  EXPECT_EQ(error_dialog_shown_count(), 0u);
  EXPECT_EQ(print_job_destruction_count(), 1);
}
#endif  // BUILDFLAG(IS_WIN)

IN_PROC_BROWSER_TEST_P(SystemAccessProcessPrintBrowserTest,
                       StartPrintingFails) {}

IN_PROC_BROWSER_TEST_P(SystemAccessProcessPrintBrowserTest,
                       StartPrintingCanceled) {}

IN_PROC_BROWSER_TEST_P(SystemAccessProcessSandboxedServicePrintBrowserTest,
                       StartPrintingAccessDenied) {}

IN_PROC_BROWSER_TEST_P(SystemAccessProcessSandboxedServicePrintBrowserTest,
                       StartPrintingRepeatedAccessDenied) {}

#if BUILDFLAG(IS_WIN)
IN_PROC_BROWSER_TEST_P(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.  Update print settings.
  // 2.  A print job is started.
  // 3.  Rendering for 1 page of document of content fails with access denied.
  // 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);
  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_P(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/3_pages.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.  Update print settings.
  // 2.  Start the print job.
  // 3.  First page render callback shows success.
  // 4.  Second page render callback shows failure.  Will start failure
  //     processing to cancel the print job.
  // 5.  A printing error dialog is displayed.
  // 6.  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.
  // 7.  The print job is canceled.  The callback from the service could occur
  //     after the print job has been destroyed.
  // 8.  Wait for the one print job to be destroyed, to ensure printing
  //     finished cleanly before completing the test.
  SetNumExpectedMessages(/*num=*/8);

  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/40100562)  Include Windows once XPS print pipeline is added.
#if !BUILDFLAG(IS_WIN)
IN_PROC_BROWSER_TEST_P(SystemAccessProcessSandboxedServicePrintBrowserTest,
                       StartPrintingRenderDocumentAccessDenied) {}
#endif  // !BUILDFLAG(IS_WIN)

IN_PROC_BROWSER_TEST_P(SystemAccessProcessSandboxedServicePrintBrowserTest,
                       StartPrintingDocumentDoneAccessDenied) {}

#if BUILDFLAG(ENABLE_BASIC_PRINT_DIALOG)

IN_PROC_BROWSER_TEST_P(SystemAccessProcessPrintBrowserTest,
                       SystemPrintFromPrintPreview) {}

#if BUILDFLAG(IS_WIN)
// This test is Windows-only because of Print Preview behavior in
// `onPrintWithSystemDialog_()`.  For Windows this call ends up going through
// `PrintViewManagerBase::PrintForPrintPreview()`, and thus invokes
// `UpdatePrintSettings()` before displaying the system dialog.  Other
// platforms end up going through `PrintViewManager::PrintForSystemDialogNow()`
// and thus do not update print settings before the system dialog is displayed.
IN_PROC_BROWSER_TEST_P(SystemAccessProcessPrintBrowserTest,
                       SystemPrintFromPrintPreviewUpdatePrintSettingsFails) {
  AddPrinter("printer1");
  SetPrinterNameForSubsequentContexts("printer1");
  PrimeForFailInUpdatePrinterSettings();

  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);

  // Once the transition to system print is initiated, the expected events
  // are:
  // 1.  Update the print settings, which fails.  No further printing calls
  //     are made.  No print job is created because of such an early failure.
  // 2.  An error dialog is displayed.
  SetNumExpectedMessages(/*num=*/2);

  SystemPrintFromPreviewOnceReadyAndLoaded(/*wait_for_callback=*/true);

  EXPECT_EQ(update_print_settings_result(), mojom::ResultCode::kFailed);
  EXPECT_EQ(error_dialog_shown_count(), 1u);
}

// This test is Windows-only because of Print Preview behavior in
// `onPrintWithSystemDialog_()`.  For Windows this call ends up going through
// `PrintViewManagerBase::PrintForPrintPreview()`, and thus invokes
// `UpdatePrintSettings()` before displaying the system dialog.  Other
// platforms end up going through `PrintViewManager::PrintForSystemDialogNow()`
// and thus do not update print settings before the system dialog is displayed.
IN_PROC_BROWSER_TEST_P(
    SystemAccessProcessSandboxedServicePrintBrowserTest,
    PrintPreviewAfterSystemPrintFromPrintPreviewUpdatePrintSettingsFails) {
  AddPrinter("printer1");
  SetPrinterNameForSubsequentContexts("printer1");
  PrimeForFailInUpdatePrinterSettings();

  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);

  // First invoke system print from Print Preview.  Must wait until the
  // PrintPreviewUI is completely done before proceeding to the second part
  // of this test to ensure that the client is unregistered from the
  // `PrintBackendServiceManager`.
  SetCheckForPrintPreviewDone(/*check=*/true);

  // Once the transition to system print is initiated, the expected events
  // are:
  // 1.  Update the print settings, which fails.  No further printing calls
  //     are made.  No print job is created because of such an early failure.
  // 2.  An error dialog is displayed.
  // 3.  Print Preview UI is done.
  SetNumExpectedMessages(/*num=*/3);
  SystemPrintFromPreviewOnceReadyAndLoaded(/*wait_for_callback=*/true);

  EXPECT_EQ(update_print_settings_result(), mojom::ResultCode::kFailed);
  EXPECT_EQ(error_dialog_shown_count(), 1u);

  // Reset before initiating another Print Preview.
  PrepareRunloop();
  ResetNumReceivedMessages();

  // No longer expect the `PrintPreviewUI` to issue a done callback as part of
  // the test expectations, since the Print Preview will stay open displaying
  // an error message.  There will still be a preview done callback during
  // test shutdown though, so disable doing an expectation check for that.
  SetCheckForPrintPreviewDone(/*check=*/false);

  // The expected events for this are:
  // 1.  Update the print settings, which fails.  No further printing calls
  //     are made.  No print job is created because of such an early failure.
  // 2.  An error dialog is displayed.
  SetNumExpectedMessages(/*num=*/2);
  PrintAfterPreviewIsReadyAndLoaded();

  EXPECT_EQ(update_print_settings_result(), mojom::ResultCode::kFailed);
  EXPECT_EQ(error_dialog_shown_count(), 2u);
}

// This test is Windows-only, since it is the only platform which can invoke
// the system print dialog from within `PrintingContext::UpdatePrintSettings()`.
// From that system dialog we can cause a cancel to occur.
// TODO(crbug.com/40561724):  Expand this to also cover in-browser, once an
// appropriate signal is available to use for tracking expected events.
IN_PROC_BROWSER_TEST_P(SystemAccessProcessSandboxedServicePrintBrowserTest,
                       SystemPrintFromPrintPreviewCancelRetry) {
  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);

  // First invoke system print from Print Preview.  Must wait until the
  // PrintPreviewUI is completely done before proceeding to the second part
  // of this test to ensure that the client is unregistered from the
  // `PrintBackendServiceManager`.
  SetCheckForPrintPreviewDone(/*check=*/true);

  // The expected events for this are:
  // 1.  Update the print 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.
  // 2.  Print Preview UI is done.
  SetNumExpectedMessages(/*num=*/2);

  SystemPrintFromPreviewOnceReadyAndLoaded(/*wait_for_callback=*/true);

  EXPECT_EQ(update_print_settings_result(), mojom::ResultCode::kCanceled);
  EXPECT_EQ(error_dialog_shown_count(), 0u);
  EXPECT_EQ(print_job_destruction_count(), 0);

  // Now try to initiate the system print from a Print Preview again.
  // Same number of expected events.
  PrepareRunloop();
  ResetNumReceivedMessages();

  SystemPrintFromPreviewOnceReadyAndLoaded(/*wait_for_callback=*/true);

  EXPECT_EQ(update_print_settings_result(), mojom::ResultCode::kCanceled);
  EXPECT_EQ(error_dialog_shown_count(), 0u);
  EXPECT_EQ(print_job_destruction_count(), 0);
}

// TODO(crbug.com/40942272):  Enable test for Linux and macOS once renderer
// RunLoop behavior can be made to work with test expectations.
IN_PROC_BROWSER_TEST_P(SystemAccessProcessPrintBrowserTest,
                       SystemPrintAfterSystemPrintFromPrintPreview) {
  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);

  // First invoke system print from Print Preview.  Wait until the
  // PrintPreviewUI is done before proceeding to the second part of the
  // test.
  SetCheckForPrintPreviewDone(/*check=*/true);

  if (UseService()) {
    // Once the transition to system print is initiated, the expected events
    // are:
    // 1.  Update the print settings.  This internally invokes the system
    //     print dialog which cancels.
    // 2.  Print Preview is done.
    // No print job is created because of such an early cancel.
    SetNumExpectedMessages(/*num=*/2);
  } else {
    // Once the transition to system print is initiated, the expected events
    // are:
    // 1.  Update the print settings.
    // 2.  Print Preview is done.
    // No print job is created because of such an early cancel.
    SetNumExpectedMessages(/*num=*/2);
  }
  SystemPrintFromPreviewOnceReadyAndLoaded(/*wait_for_callback=*/true);

  if (UseService()) {
    // Windows invokes system print dialog from UpdatePrintSettings().
    EXPECT_EQ(update_print_settings_result(), mojom::ResultCode::kCanceled);
  } else {
    // User settings are invoked from within UpdatePrintSettings().
    EXPECT_FALSE(did_use_default_settings());
    EXPECT_FALSE(did_get_settings_with_ui());

    // `PrintBackendService` should never be used when printing in-browser.
    EXPECT_FALSE(print_backend_service_use_detected());
  }

  // Reset before initiating system print.
  PrepareRunloop();
  ResetNumReceivedMessages();

  // The expected events for this are:
  // 1.  Get the default settings.
  // 2.  Ask the user for settings, which cancels out.  No further printing
  // calls are made.
  SetNumExpectedMessages(/*num=*/2);

  StartBasicPrint(web_contents);

  WaitUntilCallbackReceived();

  if (UseService()) {
    EXPECT_EQ(use_default_settings_result(), mojom::ResultCode::kSuccess);
    EXPECT_EQ(ask_user_for_settings_result(), mojom::ResultCode::kCanceled);
  } else {
    EXPECT_TRUE(did_use_default_settings());
    EXPECT_TRUE(did_get_settings_with_ui());
  }
  EXPECT_EQ(error_dialog_shown_count(), 0u);
  EXPECT_EQ(print_job_destruction_count(), 0);
}
#endif  // BUILDFLAG(IS_WIN)

IN_PROC_BROWSER_TEST_P(SystemAccessProcessSandboxedServicePrintBrowserTest,
                       PrintPreviewPrintAfterSystemPrintRendererCrash) {}

IN_PROC_BROWSER_TEST_P(SystemAccessProcessSandboxedServicePrintBrowserTest,
                       StartBasicPrint) {}

IN_PROC_BROWSER_TEST_P(SystemAccessProcessSandboxedServicePrintBrowserTest,
                       StartBasicPrintPageRanges) {}

IN_PROC_BROWSER_TEST_P(SystemAccessProcessPrintBrowserTest,
                       StartBasicPrintCancel) {}

IN_PROC_BROWSER_TEST_P(SystemAccessProcessPrintBrowserTest,
                       StartBasicPrintAskUserForSettingsFails) {}

IN_PROC_BROWSER_TEST_P(SystemAccessProcessPrintBrowserTest,
                       StartBasicPrintFails) {}

#if BUILDFLAG(IS_WIN)
IN_PROC_BROWSER_TEST_P(SystemAccessProcessPrintBrowserTest,
                       StartBasicPrintPdfConversionFails) {
  AddPrinter("printer1");
  SetPrinterNameForSubsequentContexts("printer1");
  PrimeForPdfConversionErrorOnPageIndex(/*page_index=*/1);

  ASSERT_TRUE(embedded_test_server()->Started());
  GURL url(embedded_test_server()->GetURL("/printing/3_pages.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 (UseService()) {
    // The expected events for this are:
    // 1.  Gets default settings.
    // 2.  Asks user for settings.
    // 3.  A print job is started.
    // 4.  Notified of DidPrintDocument(), that composition of the print
    //     document has completed.
    // 5.  The PDF conversion fails, resulting in canceling the print job.
    // 6.  The print job is destroyed.
    // No error dialog is shown.
    SetNumExpectedMessages(/*num=*/6);
  } else {
    // 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 is canceled due to failure during PDF
    //     conversion.
    // 4.  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.
    // No error dialog is shown.
    SetNumExpectedMessages(/*num=*/4);
  }

  StartBasicPrint(web_contents);

  WaitUntilCallbackReceived();

  if (UseService()) {
    EXPECT_EQ(start_printing_result(), mojom::ResultCode::kSuccess);
  } else {
    EXPECT_THAT(in_process_last_error_result_code(),
                testing::Optional(mojom::ResultCode::kCanceled));
  }
  // TODO(crbug.com/40288222):  Update expectation once an error is shown for
  // this failure.
  EXPECT_EQ(error_dialog_shown_count(), 0u);
  EXPECT_EQ(print_job_destruction_count(), 1);
}
#endif  // BUILDFLAG(IS_WIN)

#if BUILDFLAG(ENABLE_CONCURRENT_BASIC_PRINT_DIALOGS)

IN_PROC_BROWSER_TEST_P(SystemAccessProcessSandboxedServicePrintBrowserTest,
                       StartBasicPrintConcurrentAllowed) {}

#if BUILDFLAG(ENABLE_PRINT_PREVIEW)
IN_PROC_BROWSER_TEST_P(SystemAccessProcessSandboxedServicePrintBrowserTest,
                       SystemPrintFromPrintPreviewConcurrentAllowed) {}
#endif  // BUILDFLAG(ENABLE_PRINT_PREVIEW)

#else  // BUILDFLAG(ENABLE_CONCURRENT_BASIC_PRINT_DIALOGS)

IN_PROC_BROWSER_TEST_P(SystemAccessProcessSandboxedServicePrintBrowserTest,
                       StartBasicPrintConcurrentNotAllowed) {
  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 =
      SetUpAndReturnPrintViewManager(web_contents);

  // Pretend that a window has started a system print.
  std::optional<PrintBackendServiceManager::ClientId> 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);

  // Concurrent system print is not allowed.
  EXPECT_THAT(print_view_manager->print_now_result(), testing::Optional(false));
  // The denied concurrent print is silent without an error.
  EXPECT_EQ(error_dialog_shown_count(), 0u);

  // Cleanup before test shutdown.
  PrintBackendServiceManager::GetInstance().UnregisterClient(*client_id);
}

#if BUILDFLAG(ENABLE_PRINT_PREVIEW)
IN_PROC_BROWSER_TEST_P(SystemAccessProcessSandboxedServicePrintBrowserTest,
                       SystemPrintFromPrintPreviewConcurrentNotAllowed) {
  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);

  // Pretend that another tab has started a system print.
  // TODO(crbug.com/40561724)  Improve on this test by using a persistent fake
  // system print dialog.
  std::optional<PrintBackendServiceManager::ClientId> client_id =
      PrintBackendServiceManager::GetInstance().RegisterQueryWithUiClient();
  ASSERT_TRUE(client_id.has_value());

  // Now do a print preview which will try to switch to doing system print.
  // Inability to support this should be detected immediately without needing
  // to wait for callback.
  SystemPrintFromPreviewOnceReadyAndLoaded(/*wait_for_callback=*/false);

  // Concurrent system print is not allowed.
  EXPECT_THAT(system_print_registration_succeeded(), testing::Optional(false));
  // The denied concurrent print is silent without an error.
  EXPECT_EQ(error_dialog_shown_count(), 0u);

  // Cleanup before test shutdown.
  PrintBackendServiceManager::GetInstance().UnregisterClient(*client_id);
}
#endif  // BUILDFLAG(ENABLE_PRINT_PREVIEW)

#endif  // BUILDFLAG(ENABLE_CONCURRENT_BASIC_PRINT_DIALOGS)

IN_PROC_BROWSER_TEST_P(SystemAccessProcessServicePrintBrowserTest,
                       StartBasicPrintUseDefaultFails) {}

IN_PROC_BROWSER_TEST_P(SystemAccessProcessServicePrintBrowserTest,
                       StartBasicPrintServiceDisappearsAfterGetSettings) {}

#if BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG) && BUILDFLAG(IS_WIN)
IN_PROC_BROWSER_TEST_P(
    SystemAccessProcessServicePrintBrowserTest,
    SystemPrintFromPrintPreviewUpdatePrintSettingsServiceDisappearsAfterGetSettings) {
  AddPrinter("printer1");
  SetPrinterNameForSubsequentContexts("printer1");
  PrimeForServiceTerminatesAfterUpdatePrintSettings();

  // Pretending the service terminated will result in a stranded context left
  // in the test Print Backend service which actually does still exist.
  SkipPersistentContextsCheckOnShutdown();

  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);

  // Once the transition to system print is initiated, the expected events
  // are:
  // 1.  Ask the user for settings.  This succeeds; however, because the
  //     service is detected to have terminated, the print request is aborted.
  // 2.  An error dialog is shown.
  // No print job is created because of such an early failure.
  SetNumExpectedMessages(/*num=*/2);

  SystemPrintFromPreviewOnceReadyAndLoaded(/*wait_for_callback=*/true);

  EXPECT_EQ(update_print_settings_result(), mojom::ResultCode::kSuccess);
  EXPECT_EQ(error_dialog_shown_count(), 1u);
}
#endif  // BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG) && BUILDFLAG(IS_WIN)
#endif  // BUILDFLAG(ENABLE_BASIC_PRINT_DIALOG)

#if BUILDFLAG(IS_MAC)
IN_PROC_BROWSER_TEST_P(SystemAccessProcessPrintBrowserTest, OpenPdfInPreview) {
  AddPrinter("printer1");
  SetPrinterNameForSubsequentContexts("printer1");
  constexpr int kJobId = 1;
  SetNewDocumentJobId(kJobId);

  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 (UseService()) {
    // The expected events for this are:
    // 1.  Update printer settings.
    // 2.  A print job is started.
    // 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);
  } else {
    // The expected events for this are:
    // 1.  Update printer settings.
    // 2.  Wait for the one print job to be destroyed, to ensure printing
    //     finished cleanly before completing the test.
    SetNumExpectedMessages(/*num=*/2);
  }
  OpenPdfInPreviewOnceReadyAndLoaded();

  if (UseService()) {
    EXPECT_EQ(start_printing_result(), mojom::ResultCode::kSuccess);
    EXPECT_EQ(render_printed_document_result(), mojom::ResultCode::kSuccess);
    EXPECT_EQ(document_done_result(), mojom::ResultCode::kSuccess);
  } else {
    EXPECT_FALSE(in_process_last_error_result_code().has_value());
  }
  EXPECT_THAT(document_done_job_id(), testing::Optional(kJobId));
  EXPECT_TRUE(destination_is_preview());
  EXPECT_EQ(error_dialog_shown_count(), 0u);
  EXPECT_EQ(print_job_destruction_count(), 1);
}
#endif  // BUILDFLAG(IS_MAC)

#endif  // BUILDFLAG(ENABLE_OOP_PRINTING)

#if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS)
class TestPrintViewManagerForContentAnalysis : public TestPrintViewManager {};

ContentAnalysisConfigurationVariation;

class ContentAnalysisPrintBrowserTestBase
    : public SystemAccessProcessPrintBrowserTestBase {};

class ContentAnalysisAfterPrintPreviewBrowserTest
    : public ContentAnalysisPrintBrowserTestBase,
      public testing::WithParamInterface<
          ContentAnalysisConfigurationVariation> {};

class ContentAnalysisScriptedPreviewlessPrintBrowserTestBase
    : public ContentAnalysisPrintBrowserTestBase,
      public testing::WithParamInterface<
          ContentAnalysisConfigurationVariation> {};

class ContentAnalysisScriptedPreviewlessPrintAfterDialogBrowserTest
    : public ContentAnalysisScriptedPreviewlessPrintBrowserTestBase {};

#if !BUILDFLAG(IS_CHROMEOS)

IN_PROC_BROWSER_TEST_P(ContentAnalysisAfterPrintPreviewBrowserTest,
                       PrintWithPreviewBeforeLoaded) {}

IN_PROC_BROWSER_TEST_P(ContentAnalysisAfterPrintPreviewBrowserTest,
                       SystemPrintFromPrintPreview) {}

#if BUILDFLAG(IS_MAC)
IN_PROC_BROWSER_TEST_P(ContentAnalysisAfterPrintPreviewBrowserTest,
                       OpenPdfInPreviewFromPrintPreview) {
  AddPrinter("printer_name");

  if (UseService() && !PrintAllowedOrNonBlockingPolicy()) {
    // This results in a stranded context left in the Print Backend service.
    // It will persist harmlessly until the service terminates after a short
    // period of no printing activity.
    SkipPersistentContextsCheckOnShutdown();
  }

  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);
  auto* print_view_manager = SetUpAndReturnPrintViewManagerForContentAnalysis(
      web_contents,
      enterprise_connectors::ContentAnalysisRequest::PRINT_PREVIEW_PRINT);

  if (PrintAllowedOrNonBlockingPolicy()) {
    if (UseService()) {
      // The expected events for this are:
      // 1.  Ask the user for settings.
      // 2.  A print job is started for actual printing.
      // 3.  The print compositor will complete generating the document.
      // 4.  Completes with document done.
      // 5.  Wait for the actual printing job to be destroyed, to ensure
      //     printing finished cleanly before completing the test.
      SetNumExpectedMessages(/*num=*/5);
    } else {
      // The expected events for this are:
      // 1.  Update print settings.
      // 2.  Wait for the actual printing job to be destroyed, to ensure
      //     printing finished cleanly before completing the test.
      SetNumExpectedMessages(/*num=*/2);
    }
  } else {
    print_view_manager->set_on_print_preview_done_closure(base::BindOnce(
        &ContentAnalysisAfterPrintPreviewBrowserTest::CheckForQuit,
        base::Unretained(this)));
    // The expected events for this are:
    // 1.  Print Preview is done.
    SetNumExpectedMessages(/*num=*/1);
  }
  OpenPdfInPreviewOnceReadyAndLoaded();

  EXPECT_THAT(print_view_manager->preview_allowed(), testing::Optional(true));

  EXPECT_EQ(print_job_destruction_count(),
            PrintAllowedOrNonBlockingPolicy() ? 1 : 0);
  EXPECT_EQ(scanning_responses_count(), 1);

  // Validate that `NewDocument()` is only called for actual printing, not as
  // part of content analysis, since that can needlessly prompt the user.
  // When printing OOP, an extra call for a new document will occur since it
  // gets called in both the browser process and in the Print Backend service.
  EXPECT_EQ(new_document_called_count(), GetExpectedNewDocumentCalledCount());
}
#endif  // BUILDFLAG(IS_MAC)

IN_PROC_BROWSER_TEST_P(
    ContentAnalysisScriptedPreviewlessPrintAfterDialogBrowserTest,
    PrintNow) {}

IN_PROC_BROWSER_TEST_P(
    ContentAnalysisScriptedPreviewlessPrintAfterDialogBrowserTest,
    DocumentExecPrint) {}

IN_PROC_BROWSER_TEST_P(
    ContentAnalysisScriptedPreviewlessPrintAfterDialogBrowserTest,
    WindowPrint) {}
#endif  // !BUILDFLAG(IS_CHROMEOS)

INSTANTIATE_TEST_SUITE_P();

#if BUILDFLAG(ENABLE_BASIC_PRINT_DIALOG)

INSTANTIATE_TEST_SUITE_P();

#endif  // BUILDFLAG(ENABLE_BASIC_PRINT_DIALOG)

#endif  // BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS)

}  // namespace printing