910e62b5创建于 1月15日历史提交
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "printing/test_printing_context.h"

#include <memory>
#include <utility>

#include "base/check.h"
#include "base/containers/flat_map.h"
#include "base/notimplemented.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "printing/backend/print_backend.h"
#include "printing/buildflags/buildflags.h"
#include "printing/mojom/print.mojom.h"
#include "printing/print_settings.h"
#include "printing/printing_context.h"
#include "printing/units.h"
#include "ui/gfx/geometry/size.h"

#if BUILDFLAG(ENABLE_OOP_PRINTING_NO_OOP_BASIC_PRINT_DIALOG)
#include "printing/printing_features.h"
#endif

#if BUILDFLAG(IS_WIN)
#include "printing/printed_page_win.h"
#endif

namespace printing {

namespace {

#if BUILDFLAG(IS_WIN)
// Metafile data that is generated by
// chrome/browser/printing/pdf_to_emf_converter.cc can be of different types,
// which are expected to match particular printer languages.
bool AreLanguageAndMetafileTypesCompatible(
    mojom::PrinterLanguageType printer_language_type,
    mojom::MetafileDataType metafile_data_type) {
  switch (printer_language_type) {
    case mojom::PrinterLanguageType::kTextOnly:
    case mojom::PrinterLanguageType::kPostscriptLevel2:
    case mojom::PrinterLanguageType::kPostscriptLevel3:
      return metafile_data_type == mojom::MetafileDataType::kPostScriptEmf;
    case mojom::PrinterLanguageType::kNone:
      return metafile_data_type == mojom::MetafileDataType::kEMF;
    case mojom::PrinterLanguageType::kXps:
      // TODO(crbug.com/40100562):  Update to use an XPS MetafileDataType once
      // it is available.
      return false;
  }
}
#endif  // BUILDFLAG(IS_WIN)

}  // namespace

TestPrintingContextDelegate::TestPrintingContextDelegate() = default;

TestPrintingContextDelegate::~TestPrintingContextDelegate() = default;

gfx::NativeView TestPrintingContextDelegate::GetParentView() {
  return gfx::NativeView();
}

std::string TestPrintingContextDelegate::GetAppLocale() {
  return std::string();
}

TestPrintingContext::TestPrintingContext(
    Delegate* delegate,
    OutOfProcessBehavior out_of_process_behavior)
    : PrintingContext(delegate, out_of_process_behavior) {}

TestPrintingContext::~TestPrintingContext() = default;

void TestPrintingContext::SetDeviceSettings(
    const std::string& device_name,
    std::unique_ptr<PrintSettings> settings) {
  device_settings_.emplace(device_name, std::move(settings));
}

void TestPrintingContext::SetNewDocumentJobId(int job_id) {
  new_document_job_id_ = job_id;
}

void TestPrintingContext::SetUserSettings(const PrintSettings& settings) {
  user_settings_ = settings;
}

void TestPrintingContext::AskUserForSettings(int max_pages,
                                             bool has_selection,
                                             bool is_scripted,
                                             PrintSettingsCallback callback) {
  std::move(callback).Run(
      AskUserForSettingsImpl(max_pages, has_selection, is_scripted));
}

mojom::ResultCode TestPrintingContext::AskUserForSettingsImpl(
    int max_pages,
    bool has_selection,
    bool is_scripted) {
  // Do not actually ask the user with a dialog, just pretend like user
  // made some kind of interaction.
  if (ask_user_for_settings_cancel_) {
    // Pretend the user hit the Cancel button.
    return mojom::ResultCode::kCanceled;
  }
  if (ask_user_for_settings_fails_) {
    // Pretend the system print dialog fails.
    return mojom::ResultCode::kFailed;
  }

  // Allow for test-specific user modifications.
  if (user_settings_.has_value()) {
    *settings_ = *user_settings_;
  } else {
    // Pretend the user selected the default printer and used the default
    // settings for it.
    scoped_refptr<PrintBackend> print_backend =
        PrintBackend::CreateInstance(/*locale=*/std::string());
    std::string printer_name;
    if (print_backend->GetDefaultPrinterName(printer_name) !=
        mojom::ResultCode::kSuccess) {
      return mojom::ResultCode::kFailed;
    }
    auto found = device_settings_.find(printer_name);
    if (found == device_settings_.end()) {
      return mojom::ResultCode::kFailed;
    }
    settings_ = std::make_unique<PrintSettings>(*found->second);
  }

  // Capture a snapshot, simluating changes made to platform device context.
  applied_settings_ = *settings_;

  return mojom::ResultCode::kSuccess;
}

mojom::ResultCode TestPrintingContext::UseDefaultSettings() {
  if (use_default_settings_fails_)
    return mojom::ResultCode::kFailed;

  scoped_refptr<PrintBackend> print_backend =
      PrintBackend::CreateInstance(/*locale=*/std::string());
  std::string printer_name;
  mojom::ResultCode result = print_backend->GetDefaultPrinterName(printer_name);
  if (result != mojom::ResultCode::kSuccess)
    return result;
  auto found = device_settings_.find(printer_name);
  if (found == device_settings_.end())
    return mojom::ResultCode::kFailed;
  settings_ = std::make_unique<PrintSettings>(*found->second);

  // Capture a snapshot, simluating changes made to platform device context.
  applied_settings_ = *settings_;

  return mojom::ResultCode::kSuccess;
}

gfx::Size TestPrintingContext::GetPdfPaperSizeDeviceUnits() {
  // Default to A4 paper size, which is an alternative to Letter size that is
  // often used as the fallback size for some platform-specific
  // implementations.
  return gfx::Size(kA4WidthInch * settings_->device_units_per_inch(),
                   kA4HeightInch * settings_->device_units_per_inch());
}

mojom::ResultCode TestPrintingContext::UpdatePrinterSettings(
    const PrinterSettings& printer_settings) {
  DCHECK(!in_print_job_);

  if (update_printer_settings_fails_) {
    return mojom::ResultCode::kFailed;
  }

  // The printer name is to be embedded in the printing context's existing
  // settings.
  const std::string device_name = base::UTF16ToUTF8(settings_->device_name());
  auto found = device_settings_.find(device_name);
  if (found == device_settings_.end()) {
    DLOG(ERROR) << "No such device found in test printing context: `"
                << device_name << "`";
    return mojom::ResultCode::kFailed;
  }

  // Perform some initialization, akin to various platform-specific actions in
  // `InitPrintSettings()`.
  DVLOG(1) << "Updating context settings for device `" << device_name << "`";
  std::unique_ptr<PrintSettings> existing_settings = std::move(settings_);
  settings_ = std::make_unique<PrintSettings>(*found->second);
  settings_->set_copies(existing_settings->copies());
  // Client-supplied settings take priority over default device settings; try to
  // transfer the non-empty ones.
  if (existing_settings->color() != mojom::ColorModel::kUnknownColorModel) {
    settings_->set_color(existing_settings->color());
  }
  settings_->set_collate(existing_settings->collate());
  if (!existing_settings->dpi_size().IsEmpty()) {
    settings_->set_dpi_xy(existing_settings->dpi_horizontal(),
                          existing_settings->dpi_vertical());
  }
  if (!existing_settings->title().empty()) {
    settings_->set_title(existing_settings->title());
  }
  if (!existing_settings->requested_media().IsDefault()) {
    settings_->set_requested_media(existing_settings->requested_media());
  }
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
  for (const auto& item : existing_settings->advanced_settings())
    settings_->advanced_settings().emplace(item.first, item.second.Clone());
#endif

#if BUILDFLAG(IS_WIN)
  if (printer_settings.show_system_dialog) {
    return AskUserForSettingsImpl(printer_settings.page_count,
                                  /*has_selection=*/false,
                                  /*is_scripted=*/false);
  }
#endif

#if BUILDFLAG(IS_MAC)
  destination_is_preview_ = printer_settings.external_preview;
#endif

  // Capture a snapshot, simluating changes made to platform device context.
  applied_settings_ = *settings_;

  return mojom::ResultCode::kSuccess;
}

mojom::ResultCode TestPrintingContext::NewDocument(
    const std::u16string& document_name) {
  DCHECK(!in_print_job_);

#if BUILDFLAG(ENABLE_OOP_PRINTING_NO_OOP_BASIC_PRINT_DIALOG)
  if (out_of_process_behavior() ==
          OutOfProcessBehavior::kEnabledPerformSystemCalls &&
      !settings_->system_print_dialog_data().empty()) {
    // Mimic the update when system print dialog settings are provided to
    // Print Backend service from the browser process.
    applied_settings_ = *settings_;
  }
#endif

  if (on_new_document_callback_) {
    on_new_document_callback_.Run(
#if BUILDFLAG(IS_MAC)
        destination_is_preview_,
#endif
        applied_settings_);
  }

  abort_printing_ = false;
  in_print_job_ = true;

#if BUILDFLAG(ENABLE_OOP_PRINTING)
  const bool make_system_calls = out_of_process_behavior() !=
                                 OutOfProcessBehavior::kEnabledSkipSystemCalls;
#else
  const bool make_system_calls = true;
#endif
  if (make_system_calls) {
    if (new_document_cancels_) {
      return mojom::ResultCode::kCanceled;
    }
    if (new_document_fails_)
      return mojom::ResultCode::kFailed;
    if (new_document_blocked_by_permissions_)
      return mojom::ResultCode::kAccessDenied;

    // A print job is now active, so potentially update `job_id`.
    if (new_document_job_id_.has_value()) {
      job_id_ = new_document_job_id_.value();
    }
  }

  // No-op.
  return mojom::ResultCode::kSuccess;
}

#if BUILDFLAG(IS_WIN)
mojom::ResultCode TestPrintingContext::RenderPage(const PrintedPage& page,
                                                  const PageSetup& page_setup) {
  if (abort_printing_)
    return mojom::ResultCode::kCanceled;
  DCHECK(in_print_job_);
  DVLOG(1) << "Render page " << page.page_number();

  if (render_page_blocked_by_permissions_)
    return mojom::ResultCode::kAccessDenied;

  if (render_page_fail_for_page_number_.has_value() &&
      *render_page_fail_for_page_number_ == page.page_number()) {
    return mojom::ResultCode::kFailed;
  }

#if BUILDFLAG(IS_WIN)
  // Examine the driver type for the printer, to make sure the provided
  // metafile is of the correct type.
  const std::string device_name =
      base::UTF16ToUTF8(applied_settings_.device_name());
  // TODO(crbug.com/327554077):  Update to expect having a valid `device_name`
  // once sandboxed OOPPD gets fixed for current API call flow.
  if (!device_name.empty()) {
    auto found = device_settings_.find(device_name);
    if (found == device_settings_.end()) {
      DLOG(ERROR) << "No such device found in test printing context: `"
                  << device_name << "`";
      return mojom::ResultCode::kFailed;
    }
    const PrintSettings* device_default_print_settings = found->second.get();
    if (!AreLanguageAndMetafileTypesCompatible(
            device_default_print_settings->printer_language_type(),
            page.metafile()->GetDataType())) {
      DVLOG(1) << "Incompatible metafile type for printer language type: "
               << device_default_print_settings->printer_language_type() << ", "
               << page.metafile()->GetDataType();
      return mojom::ResultCode::kFailed;
    }
  }
#endif  // BUILDFLAG(IS_WIN)

  // No-op.
  return mojom::ResultCode::kSuccess;
}
#endif  // BUILDFLAG(IS_WIN)

mojom::ResultCode TestPrintingContext::PrintDocument(
    const MetafilePlayer& metafile,
    const PrintSettings& settings,
    uint32_t num_pages) {
  if (abort_printing_)
    return mojom::ResultCode::kCanceled;
  DCHECK(in_print_job_);
  DVLOG(1) << "Print document";

  if (render_document_blocked_by_permissions_)
    return mojom::ResultCode::kAccessDenied;

  // No-op.
  return mojom::ResultCode::kSuccess;
}

mojom::ResultCode TestPrintingContext::DocumentDone() {
  DCHECK(in_print_job_);
  DVLOG(1) << "Document done";

  if (document_done_blocked_by_permissions_)
    return mojom::ResultCode::kAccessDenied;

  ResetSettings();
  return mojom::ResultCode::kSuccess;
}

void TestPrintingContext::Cancel() {
  abort_printing_ = true;
  in_print_job_ = false;
  DVLOG(1) << "Canceling print job";
}
void TestPrintingContext::ReleaseContext() {}

printing::NativeDrawingContext TestPrintingContext::context() const {
  // No native context for test.
  return nullptr;
}

#if BUILDFLAG(IS_WIN)
mojom::ResultCode TestPrintingContext::InitWithSettingsForTest(
    std::unique_ptr<PrintSettings> settings) {
  NOTIMPLEMENTED();
  return mojom::ResultCode::kFailed;
}
#endif  // BUILDFLAG(IS_WIN)

}  // namespace printing