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

#include "chrome/browser/ui/webui/print_preview/print_preview_handler.h"

#include <stddef.h>

#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "ash/constants/ash_features.h"
#include "base/check.h"
#include "base/check_deref.h"
#include "base/dcheck_is_on.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/i18n/number_formatting.h"
#include "base/json/json_reader.h"
#include "base/lazy_instance.h"
#include "base/time/time.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/app_mode/app_mode_utils.h"
#include "chrome/browser/bad_message.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/printing/background_printing_manager.h"
#include "chrome/browser/printing/prefs_util.h"
#include "chrome/browser/printing/print_error_dialog.h"
#include "chrome/browser/printing/print_job_manager.h"
#include "chrome/browser/printing/print_preview_dialog_controller.h"
#include "chrome/browser/printing/print_preview_sticky_settings.h"
#include "chrome/browser/printing/print_view_manager.h"
#include "chrome/browser/printing/printer_manager_dialog.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/webui/print_preview/pdf_printer_handler.h"
#include "chrome/browser/ui/webui/print_preview/policy_settings.h"
#include "chrome/browser/ui/webui/print_preview/print_preview_metrics.h"
#include "chrome/browser/ui/webui/print_preview/print_preview_ui.h"
#include "chrome/browser/ui/webui/print_preview/print_preview_utils.h"
#include "chrome/browser/ui/webui/print_preview/printer_handler.h"
#include "chrome/common/crash_keys.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/printing/printer_capabilities.h"
#include "chrome/common/webui_url_constants.h"
#include "components/cloud_devices/common/cloud_device_description.h"
#include "components/cloud_devices/common/printer_description.h"
#include "components/enterprise/buildflags/buildflags.h"
#include "components/prefs/pref_service.h"
#include "components/printing/common/cloud_print_cdd_conversion.h"
#include "components/url_formatter/url_formatter.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.h"
#include "net/base/url_util.h"
#include "printing/backend/print_backend.h"
#include "printing/backend/print_backend_consts.h"
#include "printing/backend/print_backend_utils.h"
#include "printing/backend/printing_restrictions.h"
#include "printing/buildflags/buildflags.h"
#include "printing/mojom/print.mojom.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "third_party/icu/source/i18n/unicode/ulocdata.h"
#include "ui/shell_dialogs/selected_file_info.h"

#if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS)
#include "chrome/browser/enterprise/data_protection/print_utils.h"
#if BUILDFLAG(IS_MAC)
#include "chrome/grit/generated_resources.h"
#include "ui/base/l10n/l10n_util.h"
#endif  // BUILDFLAG(IS_MAC)
#endif
#if BUILDFLAG(IS_CHROMEOS)
#include "ash/constants/ash_pref_names.h"
#include "ash/webui/settings/public/constants/routes.mojom.h"
#include "chrome/browser/ash/drive/drive_integration_service.h"
#include "chrome/browser/ash/drive/drive_integration_service_factory.h"
#include "chromeos/ash/experiences/settings_ui/settings_app_manager.h"
#include "components/session_manager/core/session.h"
#include "components/session_manager/core/session_manager.h"
#include "components/user_manager/user_manager.h"
#endif

#if DCHECK_IS_ON()
#include "base/debug/stack_trace.h"
#endif

using content::RenderFrameHost;
using content::WebContents;

namespace printing {

namespace {

mojom::PrinterType GetPrinterTypeForUserAction(UserActionBuckets user_action) {
  switch (user_action) {
    case UserActionBuckets::kPrintWithExtension:
      return mojom::PrinterType::kExtension;
    // On Chrome OS, printing to Google Drive needs to open the local file
    // picker so |kPrintToGoogleDriveCros| action should be handled by the
    // PDFPrinterHandler.
    case UserActionBuckets::kPrintToGoogleDriveCros:
    case UserActionBuckets::kPrintToPdf:
      return mojom::PrinterType::kPdf;
    case UserActionBuckets::kPrintToPrinter:
    case UserActionBuckets::kFallbackToAdvancedSettingsDialog:
    case UserActionBuckets::kOpenInMacPreview:
      return mojom::PrinterType::kLocal;
    default:
      NOTREACHED();
  }
}

// Dictionary Fields for Print Preview initial settings. Keep in sync with
// field names for print_preview.NativeInitialSettings in
// chrome/browser/resources/print_preview/native_layer.js
//
// Name of a dictionary field specifying whether to print automatically in
// kiosk mode. See http://crbug.com/40339435.
const char kIsInKioskAutoPrintMode[] = "isInKioskAutoPrintMode";
// Dictionary field to indicate whether Chrome is running in forced app (app
// kiosk) mode. It's not the same as desktop Chrome kiosk (the one above).
const char kIsInAppKioskMode[] = "isInAppKioskMode";
// Name of a dictionary field holding the UI locale.
const char kUiLocale[] = "uiLocale";
// Name of a dictionary field holding the thousands delimiter according to the
// locale.
const char kThousandsDelimiter[] = "thousandsDelimiter";
// Name of a dictionary field holding the decimal delimiter according to the
// locale.
const char kDecimalDelimiter[] = "decimalDelimiter";
// Name of a dictionary field holding the measurement system according to the
// locale.
const char kUnitType[] = "unitType";
// Name of a dictionary field holding the initiator title.
const char kDocumentTitle[] = "documentTitle";
// Name of a dictionary field holding the state of selection for document.
const char kDocumentHasSelection[] = "documentHasSelection";
// Name of a dictionary field holding saved print preview state
const char kAppState[] = "serializedAppStateStr";
// Name of a dictionary field holding the default destination selection rules.
const char kDefaultDestinationSelectionRules[] =
    "serializedDefaultDestinationSelectionRulesStr";
// Name of a dictionary field holding policy values for printing settings.
const char kPolicies[] = "policies";
// Name of a dictionary field holding policy allowed mode value for the setting.
const char kAllowedMode[] = "allowedMode";
// Name of a dictionary field holding policy default mode value for the setting.
const char kDefaultMode[] = "defaultMode";
// Name of a dictionary pref holding the policy value for the header/footer
// checkbox.
const char kHeaderFooter[] = "headerFooter";
// Name of a dictionary pref holding the policy value for the background
// graphics checkbox.
const char kCssBackground[] = "cssBackground";
// Name of a dictionary pref holding the policy value for the paper size
// setting.
const char kMediaSize[] = "mediaSize";
#if BUILDFLAG(IS_CHROMEOS)
// Name of a dictionary field holding policy value for the setting.
const char kValue[] = "value";
// Name of a dictionary pref holding the policy value for the sheets number.
const char kSheets[] = "sheets";
// Name of a dictionary pref holding the policy value for the color setting.
const char kColor[] = "color";
// Name of a dictionary pref holding the policy value for the duplex setting.
const char kDuplex[] = "duplex";
// Name of a dictionary pref holding the policy value for the pin setting.
const char kPin[] = "pin";
// Name of a dictionary field indicating whether the user's Drive directory is
// mounted.
const char kIsDriveMounted[] = "isDriveMounted";
#endif  // BUILDFLAG(IS_CHROMEOS)
// Name of a dictionary field indicating whether the 'Save to PDF' destination
// is disabled.
const char kPdfPrinterDisabled[] = "pdfPrinterDisabled";
// Name of a dictionary field indicating whether the destinations are managed by
// the PrinterTypeDenyList enterprise policy.
const char kDestinationsManaged[] = "destinationsManaged";
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
// Name of a dictionary pref holding the policy value for whether the
// "Print as image" option should be available to the user in the Print Preview
// for a PDF job.
const char kPrintPdfAsImageAvailability[] = "printPdfAsImageAvailability";
#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
// Name of dictionary pref holding policy value for whether the
// "Print as image" option should default to set in Print Preview for
// a PDF job.
const char kPrintPdfAsImage[] = "printPdfAsImage";

// Gets the print job settings dictionary from |json_str|. Assumes the Print
// Preview WebUI does not send over invalid data.
base::DictValue GetSettingsDictionary(const std::string& json_str) {
  std::optional<base::Value> settings =
      base::JSONReader::Read(json_str, base::JSON_PARSE_CHROMIUM_EXTENSIONS);
  base::DictValue dict = std::move(*settings).TakeDict();
  CHECK(!dict.empty());
  return dict;
}

UserActionBuckets DetermineUserAction(const base::DictValue& settings) {
#if BUILDFLAG(IS_MAC)
  if (settings.contains(kSettingOpenPDFInPreview)) {
    return UserActionBuckets::kOpenInMacPreview;
  }
#endif

#if BUILDFLAG(IS_CHROMEOS)
  if (settings.FindBool(kSettingPrintToGoogleDrive).value_or(false)) {
    return UserActionBuckets::kPrintToGoogleDriveCros;
  }
#endif

  mojom::PrinterType type = static_cast<mojom::PrinterType>(
      settings.FindInt(kSettingPrinterType).value());
  switch (type) {
    case mojom::PrinterType::kExtension:
      return UserActionBuckets::kPrintWithExtension;
    case mojom::PrinterType::kPdf:
      return UserActionBuckets::kPrintToPdf;
    case mojom::PrinterType::kLocal:
      break;
    default:
      NOTREACHED();
  }

  if (settings.FindBool(kSettingShowSystemDialog).value_or(false)) {
    return UserActionBuckets::kFallbackToAdvancedSettingsDialog;
  }
  return UserActionBuckets::kPrintToPrinter;
}

base::DictValue GetPolicies(const PrefService& prefs) {
  base::DictValue policies;

  base::DictValue header_footer_policy;
  if (prefs.HasPrefPath(prefs::kPrintHeaderFooter)) {
    if (prefs.IsManagedPreference(prefs::kPrintHeaderFooter)) {
      header_footer_policy.Set(kAllowedMode,
                               prefs.GetBoolean(prefs::kPrintHeaderFooter));
    } else {
      header_footer_policy.Set(kDefaultMode,
                               prefs.GetBoolean(prefs::kPrintHeaderFooter));
    }
  }
  if (!header_footer_policy.empty()) {
    policies.Set(kHeaderFooter, std::move(header_footer_policy));
  }

  base::DictValue background_graphics_policy;
  if (prefs.HasPrefPath(prefs::kPrintingAllowedBackgroundGraphicsModes)) {
    int value =
        prefs.GetInteger(prefs::kPrintingAllowedBackgroundGraphicsModes);
    if (value) {
      background_graphics_policy.Set(kAllowedMode, value);
    }
  }
  if (prefs.HasPrefPath(prefs::kPrintingBackgroundGraphicsDefault)) {
    int value = prefs.GetInteger(prefs::kPrintingBackgroundGraphicsDefault);
    if (value) {
      background_graphics_policy.Set(kDefaultMode, value);
    }
  }
  if (!background_graphics_policy.empty()) {
    policies.Set(kCssBackground, std::move(background_graphics_policy));
  }

  base::DictValue paper_size_policy;
  std::optional<gfx::Size> default_paper_size = ParsePaperSizeDefault(prefs);
  if (default_paper_size.has_value()) {
    base::DictValue default_paper_size_value;
    default_paper_size_value.Set(kPaperSizeWidth,
                                 default_paper_size.value().width());
    default_paper_size_value.Set(kPaperSizeHeight,
                                 default_paper_size.value().height());
    paper_size_policy.Set(kDefaultMode, std::move(default_paper_size_value));
  }
  if (!paper_size_policy.empty()) {
    policies.Set(kMediaSize, std::move(paper_size_policy));
  }

#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
  base::DictValue print_as_image_available_for_pdf_policy;
  if (prefs.HasPrefPath(prefs::kPrintPdfAsImageAvailability)) {
    print_as_image_available_for_pdf_policy.Set(
        kAllowedMode, prefs.GetBoolean(prefs::kPrintPdfAsImageAvailability));
  }
  if (!print_as_image_available_for_pdf_policy.empty()) {
    policies.Set(kPrintPdfAsImageAvailability,
                 std::move(print_as_image_available_for_pdf_policy));
  }
#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)

  base::DictValue print_as_image_for_pdf_default_policy;
  if (prefs.HasPrefPath(prefs::kPrintPdfAsImageDefault)) {
    print_as_image_for_pdf_default_policy.Set(
        kDefaultMode, prefs.GetBoolean(prefs::kPrintPdfAsImageDefault));
  }
  if (!print_as_image_for_pdf_default_policy.empty()) {
    policies.Set(kPrintPdfAsImage,
                 std::move(print_as_image_for_pdf_default_policy));
  }

#if BUILDFLAG(IS_CHROMEOS)
  if (prefs.HasPrefPath(ash::prefs::kPrintingMaxSheetsAllowed)) {
    int max_sheets = prefs.GetInteger(ash::prefs::kPrintingMaxSheetsAllowed);
    if (max_sheets >= 0) {
      base::DictValue sheets_policy;
      sheets_policy.Set(kValue, max_sheets);
      policies.Set(kSheets, std::move(sheets_policy));
    }
  }

  base::DictValue color_policy;
  if (prefs.HasPrefPath(ash::prefs::kPrintingAllowedColorModes)) {
    int value = prefs.GetInteger(ash::prefs::kPrintingAllowedColorModes);
    if (value) {
      color_policy.Set(kAllowedMode, value);
    }
  }
  if (prefs.HasPrefPath(ash::prefs::kPrintingColorDefault)) {
    int value = prefs.GetInteger(ash::prefs::kPrintingColorDefault);
    if (value) {
      color_policy.Set(kDefaultMode, value);
    }
  }
  if (!color_policy.empty()) {
    policies.Set(kColor, std::move(color_policy));
  }

  base::DictValue duplex_policy;
  if (prefs.HasPrefPath(ash::prefs::kPrintingAllowedDuplexModes)) {
    int value = prefs.GetInteger(ash::prefs::kPrintingAllowedDuplexModes);
    if (value) {
      duplex_policy.Set(kAllowedMode, value);
    }
  }
  if (prefs.HasPrefPath(ash::prefs::kPrintingDuplexDefault)) {
    int value = prefs.GetInteger(ash::prefs::kPrintingDuplexDefault);
    if (value) {
      duplex_policy.Set(kDefaultMode, value);
    }
  }
  if (!duplex_policy.empty()) {
    policies.Set(kDuplex, std::move(duplex_policy));
  }

  base::DictValue pin_policy;
  if (prefs.HasPrefPath(ash::prefs::kPrintingAllowedPinModes)) {
    int value = prefs.GetInteger(ash::prefs::kPrintingAllowedPinModes);
    if (value) {
      pin_policy.Set(kAllowedMode, value);
    }
  }
  if (prefs.HasPrefPath(ash::prefs::kPrintingPinDefault)) {
    int value = prefs.GetInteger(ash::prefs::kPrintingPinDefault);
    if (value) {
      pin_policy.Set(kDefaultMode, value);
    }
  }
  if (!pin_policy.empty()) {
    policies.Set(kPin, std::move(pin_policy));
  }
#endif  // BUILDFLAG(IS_CHROMEOS)

  return policies;
}

}  // namespace

PrintPreviewHandler::PrintPreviewHandler() {
  ReportUserActionHistogram(UserActionBuckets::kPreviewStarted);
}

PrintPreviewHandler::~PrintPreviewHandler() = default;

void PrintPreviewHandler::RegisterMessages() {
  web_ui()->RegisterMessageCallback(
      "getPrinters",
      base::BindRepeating(&PrintPreviewHandler::HandleGetPrinters,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "getPreview", base::BindRepeating(&PrintPreviewHandler::HandleGetPreview,
                                        base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "doPrint", base::BindRepeating(&PrintPreviewHandler::HandleDoPrint,
                                     base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "getPrinterCapabilities",
      base::BindRepeating(&PrintPreviewHandler::HandleGetPrinterCapabilities,
                          base::Unretained(this)));
#if BUILDFLAG(ENABLE_BASIC_PRINT_DIALOG)
  web_ui()->RegisterMessageCallback(
      "showSystemDialog",
      base::BindRepeating(&PrintPreviewHandler::HandleShowSystemDialog,
                          base::Unretained(this)));
#endif
  web_ui()->RegisterMessageCallback(
      "closePrintPreviewDialog",
      base::BindRepeating(&PrintPreviewHandler::HandleClosePreviewDialog,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "hidePreview",
      base::BindRepeating(&PrintPreviewHandler::HandleHidePreview,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "cancelPendingPrintRequest",
      base::BindRepeating(&PrintPreviewHandler::HandleCancelPendingPrintRequest,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "saveAppState",
      base::BindRepeating(&PrintPreviewHandler::HandleSaveAppState,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "getInitialSettings",
      base::BindRepeating(&PrintPreviewHandler::HandleGetInitialSettings,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "managePrinters",
      base::BindRepeating(&PrintPreviewHandler::HandleManagePrinters,
                          base::Unretained(this)));
}

void PrintPreviewHandler::OnJavascriptAllowed() {
  print_preview_ui()->SetPreviewUIId();
  ReadPrinterTypeDenyListFromPrefs();
}

void PrintPreviewHandler::OnJavascriptDisallowed() {
  // Normally the handler and print preview will be destroyed together, but
  // this is necessary for refresh or navigation from the chrome://print page.
  weak_factory_.InvalidateWeakPtrs();
  print_preview_ui()->ClearPreviewUIId();
  preview_callbacks_.clear();
  preview_failures_.clear();
  printer_type_deny_list_.clear();
}

WebContents* PrintPreviewHandler::preview_web_contents() {
  return web_ui()->GetWebContents();
}

PrefService* PrintPreviewHandler::GetPrefs() {
  auto* prefs = Profile::FromWebUI(web_ui())->GetPrefs();
  DCHECK(prefs);
  return prefs;
}

void PrintPreviewHandler::ReadPrinterTypeDenyListFromPrefs() {
  PrefService* prefs = GetPrefs();
  if (!prefs->HasPrefPath(prefs::kPrinterTypeDenyList)) {
    return;
  }

  const base::ListValue& deny_list_from_prefs =
      prefs->GetList(prefs::kPrinterTypeDenyList);

  std::vector<mojom::PrinterType> deny_list;
  deny_list.reserve(deny_list_from_prefs.size());
  for (const base::Value& deny_list_value : deny_list_from_prefs) {
    const std::string& deny_list_str = deny_list_value.GetString();
    mojom::PrinterType printer_type;
    if (deny_list_str == "extension") {
      printer_type = mojom::PrinterType::kExtension;
    } else if (deny_list_str == "pdf") {
      printer_type = mojom::PrinterType::kPdf;
    } else if (deny_list_str == "local") {
      printer_type = mojom::PrinterType::kLocal;
    } else {
      continue;
    }

    deny_list.push_back(printer_type);
  }
  OnPrinterTypeDenyListReady(deny_list);
}

void PrintPreviewHandler::OnPrinterTypeDenyListReady(
    const std::vector<mojom::PrinterType>& deny_list_types) {
  printer_type_deny_list_ = deny_list_types;
}

PrintPreviewUI* PrintPreviewHandler::print_preview_ui() {
  return web_ui()->GetController()->GetAs<PrintPreviewUI>();
}

bool PrintPreviewHandler::ShouldReceiveRendererMessage(int request_id) {
  if (!IsJavascriptAllowed()) {
    BadMessageReceived();
    return false;
  }

  if (!preview_callbacks_.contains(request_id)) {
    BadMessageReceived();
    return false;
  }

  return true;
}

std::string PrintPreviewHandler::GetCallbackId(int request_id) {
  std::string result;
  if (!IsJavascriptAllowed()) {
    BadMessageReceived();
    return result;
  }

  auto it = preview_callbacks_.find(request_id);
  if (it == preview_callbacks_.end()) {
    BadMessageReceived();
    return result;
  }
  result = it->second;
  preview_callbacks_.erase(it);
  return result;
}

void PrintPreviewHandler::HandleGetPrinters(const base::ListValue& args) {
  CHECK_GE(args.size(), 2u);
  const std::string& callback_id = args[0].GetString();
  CHECK(!callback_id.empty());
  int type = args[1].GetInt();
  mojom::PrinterType printer_type = static_cast<mojom::PrinterType>(type);

  // Immediately resolve the callback without fetching printers if the printer
  // type is on the deny list.
  if (printer_type_deny_list_.contains(printer_type)) {
    ResolveJavascriptCallback(base::Value(callback_id), base::Value());
    return;
  }

  // Make sure all in progress requests are canceled before new printer search
  // starts.
  PrinterHandler* handler = GetPrinterHandler(printer_type);
  handler->Reset();
  handler->StartGetPrinters(
      base::BindRepeating(&PrintPreviewHandler::OnAddedPrinters,
                          weak_factory_.GetWeakPtr(), printer_type),
      base::BindOnce(&PrintPreviewHandler::OnGetPrintersDone,
                     weak_factory_.GetWeakPtr(), callback_id, printer_type,
                     base::TimeTicks::Now()));
}

void PrintPreviewHandler::HandleGetPrinterCapabilities(
    const base::ListValue& args) {
  // Validate that we have a valid callback_id
  if (args.size() < 1 || !args[0].is_string() || args[0].GetString().empty()) {
    RejectJavascriptCallback(base::Value(""), base::Value());
    return;
  }
  // If we got here, we know that we have at least one string element.
  const std::string& callback_id = args[0].GetString();
  if (args.size() < 3) {
    RejectJavascriptCallback(base::Value(callback_id), base::Value());
    return;
  }
  const std::string* printer_name = args[1].GetIfString();
  std::optional<int> type = args[2].GetIfInt();
  if (!printer_name || printer_name->empty() || !type.has_value()) {
    RejectJavascriptCallback(base::Value(callback_id), base::Value());
    return;
  }
  mojom::PrinterType printer_type = static_cast<mojom::PrinterType>(*type);

  // Reject the callback if the printer type is on the deny list.
  if (printer_type_deny_list_.contains(printer_type)) {
    RejectJavascriptCallback(base::Value(callback_id), base::Value());
    return;
  }

  PrinterHandler* handler = GetPrinterHandler(printer_type);
  handler->StartGetCapability(
      *printer_name,
      base::BindOnce(&PrintPreviewHandler::SendPrinterCapabilities,
                     weak_factory_.GetWeakPtr(), callback_id));
}

void PrintPreviewHandler::HandleGetPreview(const base::ListValue& args) {
  DCHECK_EQ(2U, args.size());

  // All of the conditions below should be guaranteed by the print preview
  // javascript.
  const std::string& callback_id = args[0].GetString();
  CHECK(!callback_id.empty());
  const std::string& json_str = args[1].GetString();
  base::DictValue settings = GetSettingsDictionary(json_str);
  int request_id = settings.FindInt(kPreviewRequestID).value();
  CHECK_GT(request_id, -1);

  CHECK(!preview_callbacks_.contains(request_id));
  preview_callbacks_[request_id] = callback_id;
  print_preview_ui()->OnPrintPreviewRequest(request_id);
  // Add an additional key in order to identify |print_preview_ui| later on
  // when calling PrintPreviewUI::ShouldCancelRequest() on the IO thread.
  settings.Set(kPreviewUIID,
               print_preview_ui()->GetIDForPrintPreviewUI().ToString());

  WebContents* initiator = GetInitiator();
  auto* manager =
      initiator ? PrintViewManager::FromWebContents(initiator) : nullptr;
  RenderFrameHost* rfh = manager ? manager->print_preview_rfh() : nullptr;
  if (!rfh) {
    ReportUserActionHistogram(UserActionBuckets::kInitiatorClosed);
    print_preview_ui()->OnClosePrintPreviewDialog();
    return;
  }

  // Retrieve the page title and url and send it to the renderer process if
  // headers and footers are to be displayed.
  std::optional<bool> display_header_footer_opt =
      settings.FindBool(kSettingHeaderFooterEnabled);
  DCHECK(display_header_footer_opt);
  if (display_header_footer_opt.value_or(false)) {
    settings.Set(kSettingHeaderFooterTitle, initiator->GetTitle());

    GURL::Replacements url_sanitizer;
    url_sanitizer.ClearUsername();
    url_sanitizer.ClearPassword();
    const GURL& initiator_url = initiator->GetLastCommittedURL();
    settings.Set(kSettingHeaderFooterURL,
                 url_formatter::FormatUrl(
                     initiator_url.ReplaceComponents(url_sanitizer)));
  }

  VLOG(1) << "Print preview request start";

  if (!print_render_frame_.is_bound()) {
    rfh->GetRemoteAssociatedInterfaces()->GetInterface(&print_render_frame_);
  }

  if (!print_preview_ui()->IsBound()) {
    print_render_frame_->SetPrintPreviewUI(
        print_preview_ui()->BindPrintPreviewUI());
  }
  print_render_frame_->PrintPreview(settings.Clone());
  manager->AppendPrintPreviewSettings(settings.Clone(),
                                      rfh->GetProcess()->IsPdf());
  last_preview_settings_ = std::move(settings);
}

void PrintPreviewHandler::HandleDoPrint(const base::ListValue& args) {
  CHECK(args[0].is_string());
  const std::string& callback_id = args[0].GetString();
  CHECK(!callback_id.empty());
  CHECK(args[1].is_string());
  const std::string& json_str = args[1].GetString();

  base::DictValue settings = GetSettingsDictionary(json_str);
  const UserActionBuckets user_action = DetermineUserAction(settings);

  int page_count = settings.FindInt(kSettingPreviewPageCount).value_or(-1);
  if (page_count <= 0) {
    RejectJavascriptCallback(base::Value(callback_id),
                             base::Value("NO_PAGE_COUNT"));
    return;
  }

  scoped_refptr<base::RefCountedMemory> data =
      print_preview_ui()->GetPrintPreviewDataForIndex(
          COMPLETE_PREVIEW_DOCUMENT_INDEX);
  if (!data) {
    // Nothing to print, no preview available.
    RejectJavascriptCallback(base::Value(callback_id), base::Value("NO_DATA"));
    return;
  }
  DCHECK(data->size());

  // After validating `settings`, record metrics.
  std::optional<bool> maybe_is_pdf = IsPrintingPdf();
  CHECK(maybe_is_pdf.has_value());
  if (last_preview_settings_.has_value()) {
    ReportPrintSettingsStats(settings, last_preview_settings_.value(),
                             maybe_is_pdf.value());
  }
  {
    PrintDocumentTypeBuckets doc_type =
        maybe_is_pdf.value() ? PrintDocumentTypeBuckets::kPdfDocument
                             : PrintDocumentTypeBuckets::kHtmlDocument;
    ReportPrintDocumentTypeHistograms(doc_type);
  }
  ReportUserActionHistogram(user_action);

#if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS)
  std::string device_name = *settings.FindString(kSettingDeviceName);

  using enterprise_data_protection::PrintScanningContext;
  auto scan_context =
      settings.FindBool(kSettingShowSystemDialog).value_or(false)
          ? PrintScanningContext::kSystemPrintAfterPreview
          : PrintScanningContext::kNormalPrintAfterPreview;

#if BUILDFLAG(IS_MAC)
  if (settings.FindBool(kSettingOpenPDFInPreview).value_or(false)) {
    // This override only affects reporting of content analysis violations, and
    // the rest of the printing stack is expected to use the same device name
    // present in `settings` if content analysis allows printing.
    device_name =
        l10n_util::GetStringUTF8(IDS_PRINT_PREVIEW_OPEN_PDF_IN_PREVIEW_APP);
    scan_context = PrintScanningContext::kOpenPdfInPreview;
  }
#endif  // BUILDFLAG(IS_MAC)

  auto on_verdict =
      base::BindOnce(&PrintPreviewHandler::OnVerdictByEnterprisePolicy,
                     weak_factory_.GetWeakPtr(), user_action,
                     std::move(settings), data, callback_id);

  auto hide_preview = base::BindOnce(&PrintPreviewHandler::OnHidePreviewDialog,
                                     weak_factory_.GetWeakPtr());

  enterprise_data_protection::PrintIfAllowedByPolicy(
      data, GetInitiator(), std::move(device_name), scan_context,
      std::move(on_verdict), std::move(hide_preview));

#else
  FinishHandleDoPrint(user_action, std::move(settings), data, callback_id);
#endif  // BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS)
}

#if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS)
void PrintPreviewHandler::OnVerdictByEnterprisePolicy(
    UserActionBuckets user_action,
    base::DictValue settings,
    scoped_refptr<base::RefCountedMemory> data,
    const std::string& callback_id,
    bool allowed) {
  if (allowed) {
    FinishHandleDoPrint(user_action, std::move(settings), data, callback_id);
  } else {
    OnPrintResult(callback_id, base::Value("NOT_ALLOWED"));
  }
}

void PrintPreviewHandler::OnHidePreviewDialog() {
  print_preview_ui()->OnHidePreviewDialog();
}
#endif  // BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS)

void PrintPreviewHandler::FinishHandleDoPrint(
    UserActionBuckets user_action,
    base::DictValue settings,
    scoped_refptr<base::RefCountedMemory> data,
    const std::string& callback_id) {
  PrinterHandler* handler =
      GetPrinterHandler(GetPrinterTypeForUserAction(user_action));
  handler->StartPrint(print_preview_ui()->initiator_title(),
                      std::move(settings), data,
                      base::BindOnce(&PrintPreviewHandler::OnPrintResult,
                                     weak_factory_.GetWeakPtr(), callback_id));
}

void PrintPreviewHandler::HandleHidePreview(const base::ListValue& /*args*/) {
  print_preview_ui()->OnHidePreviewDialog();
}

void PrintPreviewHandler::HandleCancelPendingPrintRequest(
    const base::ListValue& /*args*/) {
  WebContents* initiator = GetInitiator();
  if (initiator) {
    ClearInitiatorDetails();
  }
  ShowPrintErrorDialogForGenericError();
}

void PrintPreviewHandler::HandleSaveAppState(const base::ListValue& args) {
  std::string data_to_save;
  PrintPreviewStickySettings* sticky_settings =
      PrintPreviewStickySettings::GetInstance();
  if (args[0].is_string()) {
    data_to_save = args[0].GetString();
  }
  if (!data_to_save.empty()) {
    sticky_settings->StoreAppState(data_to_save);
  }
  sticky_settings->SaveInPrefs(GetPrefs());
}

#if BUILDFLAG(ENABLE_BASIC_PRINT_DIALOG)
void PrintPreviewHandler::HandleShowSystemDialog(
    const base::ListValue& /*args*/) {
  ReportUserActionHistogram(
      UserActionBuckets::kFallbackToAdvancedSettingsDialog);

  WebContents* initiator = GetInitiator();
  if (!initiator) {
    return;
  }

  auto weak_this = weak_factory_.GetWeakPtr();
  auto* print_view_manager = PrintViewManager::FromWebContents(initiator);
  print_view_manager->PrintForSystemDialogNow(base::BindOnce(
      &PrintPreviewHandler::ClosePreviewDialog, weak_factory_.GetWeakPtr()));
  if (!weak_this) {
    return;
  }

  // Cancel the pending preview request if exists.
  print_preview_ui()->OnCancelPendingPreviewRequest();
}
#endif

void PrintPreviewHandler::HandleClosePreviewDialog(
    const base::ListValue& /*args*/) {
  ReportUserActionHistogram(UserActionBuckets::kCancel);
}

void PrintPreviewHandler::GetLocaleInformation(base::DictValue* settings) {
  // Getting the measurement system based on the locale.
  UErrorCode errorCode = U_ZERO_ERROR;
  const char* locale = g_browser_process->GetApplicationLocale().c_str();
  settings->Set(kUiLocale, std::string(locale));
  UMeasurementSystem system = ulocdata_getMeasurementSystem(locale, &errorCode);
  // On error, assume the units are SI.
  // Since the only measurement units print preview's WebUI cares about are
  // those for measuring distance, assume anything non-US is SI.
  if (errorCode > U_ZERO_ERROR || system != UMS_US) {
    system = UMS_SI;
  }

  // Getting the number formatting based on the locale and writing to
  // dictionary.
  std::u16string number_format = base::FormatDouble(123456.78, 2);
  size_t thousands_pos = number_format.find('3') + 1;
  std::u16string thousands_delimiter = number_format.substr(thousands_pos, 1);
  if (number_format[thousands_pos] == '4') {
    thousands_delimiter.clear();
  }
  size_t decimal_pos = number_format.find('6') + 1;
  DCHECK_NE(number_format[decimal_pos], '7');
  std::u16string decimal_delimiter = number_format.substr(decimal_pos, 1);
  settings->Set(kDecimalDelimiter, decimal_delimiter);
  settings->Set(kThousandsDelimiter, thousands_delimiter);
  settings->Set(kUnitType, system);
}

void PrintPreviewHandler::HandleGetInitialSettings(
    const base::ListValue& args) {
  CHECK(args[0].is_string());
  const std::string& callback_id = args[0].GetString();
  CHECK(!callback_id.empty());

  AllowJavascript();

  PrinterHandler* handler = GetPrinterHandler(mojom::PrinterType::kLocal);
  base::OnceCallback<void(base::DictValue, const std::string&)> cb =
      base::BindOnce(&PrintPreviewHandler::SendInitialSettings,
                     weak_factory_.GetWeakPtr(), callback_id);
  handler->GetDefaultPrinter(
      base::BindOnce(std::move(cb), GetPolicies(*GetPrefs())));
}

void PrintPreviewHandler::SendInitialSettings(
    const std::string& callback_id,
    base::DictValue policies,
    const std::string& default_printer) {
  bool is_pdf;
  mojom::RequestPrintPreviewParams default_placeholder_params;

  // `request_params` and `maybe_is_pdf` should both be non-null or both be
  // null.
  auto* dialog_controller = PrintPreviewDialogController::GetInstance();
  CHECK(dialog_controller);
  const mojom::RequestPrintPreviewParams* request_params =
      dialog_controller->GetRequestParams(preview_web_contents());
  std::optional<bool> maybe_is_pdf = IsPrintingPdf();
  if (request_params) {
    CHECK(maybe_is_pdf.has_value());
    is_pdf = maybe_is_pdf.value();
  } else {
    // This only happens with a direct navigation to chrome://print, which can
    // happen in some tests. Just use `default_placeholder_params` to set up the
    // test with some default values, so it does not crash.
    CHECK(!maybe_is_pdf.has_value());
    constexpr bool kIsPdf = false;
    is_pdf = kIsPdf;
    request_params = &default_placeholder_params;
  }

  base::DictValue initial_settings;
  initial_settings.Set(kDocumentTitle, print_preview_ui()->initiator_title());
  initial_settings.Set(kSettingPreviewModifiable, !is_pdf);
#if BUILDFLAG(IS_CHROMEOS)
  initial_settings.Set(kSettingPreviewIsFromArc, request_params->is_from_arc);
#endif
  initial_settings.Set(kSettingPrinterName, default_printer);
  initial_settings.Set(kDocumentHasSelection, request_params->has_selection);
  initial_settings.Set(kSettingShouldPrintSelectionOnly,
                       request_params->selection_only);
  PrefService* prefs = GetPrefs();
  PrintPreviewStickySettings* sticky_settings =
      PrintPreviewStickySettings::GetInstance();
  sticky_settings->RestoreFromPrefs(prefs);
  if (sticky_settings->printer_app_state()) {
    initial_settings.Set(kAppState, *sticky_settings->printer_app_state());
  } else {
    initial_settings.Set(kAppState, base::Value());
  }

  if (!policies.empty()) {
    initial_settings.Set(kPolicies, std::move(policies));
  }

  initial_settings.Set(kPdfPrinterDisabled, printer_type_deny_list_.contains(
                                                mojom::PrinterType::kPdf));

  const bool destinations_managed =
      !printer_type_deny_list_.empty() &&
      prefs->IsManagedPreference(prefs::kPrinterTypeDenyList);
  initial_settings.Set(kDestinationsManaged, destinations_managed);

  initial_settings.Set(kIsInKioskAutoPrintMode, SilentPrintingEnabled());
  initial_settings.Set(kIsInAppKioskMode, IsRunningInForcedAppMode());
  const std::string rules_str =
      prefs->GetString(prefs::kPrintPreviewDefaultDestinationSelectionRules);
  if (rules_str.empty()) {
    initial_settings.Set(kDefaultDestinationSelectionRules, base::Value());
  } else {
    initial_settings.Set(kDefaultDestinationSelectionRules, rules_str);
  }

  GetLocaleInformation(&initial_settings);

#if BUILDFLAG(IS_CHROMEOS)
  drive::DriveIntegrationService* drive_service =
      drive::DriveIntegrationServiceFactory::GetForProfile(
          Profile::FromWebUI(web_ui()));
  initial_settings.Set(kIsDriveMounted,
                       drive_service && drive_service->IsMounted());
#endif

  ResolveJavascriptCallback(base::Value(callback_id), initial_settings);
}

void PrintPreviewHandler::ClosePreviewDialog() {
  print_preview_ui()->OnClosePrintPreviewDialog();
}

void PrintPreviewHandler::SendPrinterCapabilities(
    const std::string& callback_id,
    base::DictValue settings_info) {
  // Check that |settings_info| is valid.
  base::DictValue* settings = settings_info.FindDict(kSettingCapabilities);
  if (settings) {
    FilterContinuousFeedMediaSizes(*settings);
    VLOG(1) << "Get printer capabilities finished";
    ResolveJavascriptCallback(base::Value(callback_id), settings_info);
    return;
  }

  VLOG(1) << "Get printer capabilities failed";
  RejectJavascriptCallback(base::Value(callback_id), base::Value());
}

WebContents* PrintPreviewHandler::GetInitiator() {
  auto* dialog_controller = PrintPreviewDialogController::GetInstance();
  CHECK(dialog_controller);
  return dialog_controller->GetInitiator(preview_web_contents());
}

std::optional<bool> PrintPreviewHandler::IsPrintingPdf() {
  auto* dialog_controller = PrintPreviewDialogController::GetInstance();
  CHECK(dialog_controller);
  return dialog_controller->IsPrintingPdf(preview_web_contents());
}

void PrintPreviewHandler::OnPrintPreviewReady(
    const base::UnguessableToken& preview_uid,
    int request_id) {
  std::string callback_id = GetCallbackId(request_id);
  if (callback_id.empty()) {
    return;
  }

  ResolveJavascriptCallback(base::Value(callback_id),
                            base::Value(preview_uid.ToString()));
}

void PrintPreviewHandler::OnPrintPreviewFailed(int request_id) {
  WebContents* initiator = GetInitiator();
  if (!initiator || initiator->IsBeingDestroyed()) {
    return;  // Drop notification if fired during destruction sequence.
  }

  std::string callback_id = GetCallbackId(request_id);
  if (callback_id.empty()) {
    return;
  }

  if (!reported_failed_preview_) {
    reported_failed_preview_ = true;
    ReportUserActionHistogram(UserActionBuckets::kPreviewFailed);
  }

  // Keep track of failures.
  bool inserted = preview_failures_.insert(request_id).second;
  DCHECK(inserted);
  RejectJavascriptCallback(base::Value(callback_id),
                           base::Value("PREVIEW_FAILED"));
}

void PrintPreviewHandler::OnInvalidPrinterSettings(int request_id) {
  std::string callback_id = GetCallbackId(request_id);
  if (callback_id.empty()) {
    return;
  }

  RejectJavascriptCallback(base::Value(callback_id),
                           base::Value("SETTINGS_INVALID"));
}

void PrintPreviewHandler::SendPrintPresetOptions(bool disable_scaling,
                                                 int copies,
                                                 mojom::DuplexMode duplex,
                                                 int request_id) {
  if (!ShouldReceiveRendererMessage(request_id)) {
    return;
  }

  FireWebUIListener("print-preset-options", base::Value(disable_scaling),
                    base::Value(copies), base::Value(static_cast<int>(duplex)));
}

void PrintPreviewHandler::SendPageCountReady(int page_count,
                                             int fit_to_page_scaling,
                                             int request_id) {
  if (!ShouldReceiveRendererMessage(request_id)) {
    return;
  }

  FireWebUIListener("page-count-ready", base::Value(page_count),
                    base::Value(request_id), base::Value(fit_to_page_scaling));
}

void PrintPreviewHandler::SendPageLayoutReady(
    base::DictValue layout,
    bool all_pages_have_custom_size,
    bool all_pages_have_custom_orientation,
    int request_id) {
  if (!ShouldReceiveRendererMessage(request_id)) {
    return;
  }

  FireWebUIListener("page-layout-ready", std::move(layout),
                    base::Value(all_pages_have_custom_size),
                    base::Value(all_pages_have_custom_orientation));
}

void PrintPreviewHandler::SendPagePreviewReady(
    int page_index,
    const base::UnguessableToken& preview_uid,
    int preview_request_id) {
  // With print compositing, by the time compositing finishes and this method
  // gets called, the print preview may have failed. Since the failure message
  // may have arrived first, check for this case and bail out instead of
  // thinking this may be a bad IPC message.
  if (preview_failures_.contains(preview_request_id)) {
    return;
  }

  if (!ShouldReceiveRendererMessage(preview_request_id)) {
    return;
  }

  FireWebUIListener("page-preview-ready", base::Value(page_index),
                    base::Value(preview_uid.ToString()),
                    base::Value(preview_request_id));
}

void PrintPreviewHandler::OnPrintPreviewCancelled(int request_id) {
  std::string callback_id = GetCallbackId(request_id);
  if (callback_id.empty()) {
    return;
  }

  RejectJavascriptCallback(base::Value(callback_id), base::Value("CANCELLED"));
}

void PrintPreviewHandler::OnPrintRequestCancelled() {
  base::ListValue empty;
  HandleCancelPendingPrintRequest(empty);
}

void PrintPreviewHandler::ClearInitiatorDetails() {
  WebContents* initiator = GetInitiator();
  if (!initiator) {
    return;
  }

  // We no longer require the initiator details. Remove those details associated
  // with the preview dialog to allow the initiator to create another preview
  // dialog.
  auto* dialog_controller = PrintPreviewDialogController::GetInstance();
  CHECK(dialog_controller);
  dialog_controller->EraseInitiatorInfo(preview_web_contents());
}

PrinterHandler* PrintPreviewHandler::GetPrinterHandler(
    mojom::PrinterType printer_type) {
  if (printer_type == mojom::PrinterType::kExtension) {
    if (!extension_printer_handler_) {
      extension_printer_handler_ = PrinterHandler::CreateForExtensionPrinters(
          Profile::FromWebUI(web_ui()));
    }
    return extension_printer_handler_.get();
  }
  if (printer_type == mojom::PrinterType::kPdf) {
    if (!pdf_printer_handler_) {
      pdf_printer_handler_ = PrinterHandler::CreateForPdfPrinter(
          Profile::FromWebUI(web_ui()), preview_web_contents(),
          PrintPreviewStickySettings::GetInstance());
    }
    return pdf_printer_handler_.get();
  }
  if (printer_type == mojom::PrinterType::kLocal) {
    if (!local_printer_handler_) {
      local_printer_handler_ = PrinterHandler::CreateForLocalPrinters(
          preview_web_contents(), Profile::FromWebUI(web_ui()));
    }
    return local_printer_handler_.get();
  }
  NOTREACHED();
}

PdfPrinterHandler* PrintPreviewHandler::GetPdfPrinterHandler() {
  return static_cast<PdfPrinterHandler*>(
      GetPrinterHandler(mojom::PrinterType::kPdf));
}

void PrintPreviewHandler::OnAddedPrinters(mojom::PrinterType printer_type,
                                          base::ListValue printers) {
  DCHECK(printer_type == mojom::PrinterType::kExtension ||
         printer_type == mojom::PrinterType::kLocal);
  // Save the count here, as `printers` gets moved below.
  const size_t printer_count = printers.size();
  DCHECK(printer_count);
  FireWebUIListener("printers-added",
                    base::Value(static_cast<int>(printer_type)), printers);

  if (printer_type == mojom::PrinterType::kLocal &&
      !has_logged_printers_count_) {
    ReportNumberOfPrinters(printer_count);
    has_logged_printers_count_ = true;
  }
}

void PrintPreviewHandler::OnGetPrintersDone(const std::string& callback_id,
                                            mojom::PrinterType printer_type,
                                            const base::TimeTicks& start_time) {
  RecordGetPrintersTimeHistogram(printer_type, start_time);
  ResolveJavascriptCallback(base::Value(callback_id), base::Value());
}

void PrintPreviewHandler::OnPrintResult(const std::string& callback_id,
                                        const base::Value& error) {
  if (error.is_none()) {
    ResolveJavascriptCallback(base::Value(callback_id), error);
  } else {
    RejectJavascriptCallback(base::Value(callback_id), error);
  }
  // Remove the preview dialog from the background printing manager if it is
  // being stored there. Since the PDF has been sent and the callback is
  // resolved or rejected, it is no longer needed and can be destroyed.
  BackgroundPrintingManager* background_printing_manager =
      g_browser_process->background_printing_manager();
  if (background_printing_manager->HasPrintPreviewDialog(
          preview_web_contents())) {
    background_printing_manager->OnPrintRequestCancelled(
        preview_web_contents());
  }
}

void PrintPreviewHandler::BadMessageReceived() {
  bad_message::ReceivedBadMessage(
      GetInitiator()->GetPrimaryMainFrame()->GetProcess(),
      bad_message::BadMessageReason::PPH_EXTRA_PREVIEW_MESSAGE);
#if DCHECK_IS_ON()
  // TODO(crbug.com/40870686): Remove this once the bug is fixed.
  base::debug::StackTrace().Print();
#endif
}

void PrintPreviewHandler::FileSelectedForTesting(const base::FilePath& path,
                                                 int index) {
  GetPdfPrinterHandler()->FileSelected(ui::SelectedFileInfo(path), index);
}

void PrintPreviewHandler::SetPdfSavedClosureForTesting(
    base::OnceClosure closure) {
  GetPdfPrinterHandler()->SetPdfSavedClosureForTesting(std::move(closure));
}

void PrintPreviewHandler::HandleManagePrinters(const base::ListValue& args) {
#if BUILDFLAG(IS_CHROMEOS)
  // TODO(crbug.com/447287122): Consider to use the active session, instead of
  // primary session, or pass the user context from callers.
  auto* session = session_manager::SessionManager::Get()->GetPrimarySession();
  CHECK(session);
  auto* user =
      user_manager::UserManager::Get()->FindUser(session->account_id());
  ash::SettingsAppManager::Get()->Open(
      CHECK_DEREF(user),
      {.sub_page = chromeos::settings::mojom::kPrintingDetailsSubpagePath});
#else
  printing::PrinterManagerDialog::ShowPrinterManagerDialog();
#endif
}

}  // namespace printing
