// Copyright 2020 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_chromeos.h"
#include <stddef.h>
#include <memory>
#include <string>
#include <utility>
#include "base/check.h"
#include "base/check_op.h"
#include "base/containers/to_value_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/values.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/printing/print_preview_dialog_controller.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/webui/print_preview/local_printer_handler_chromeos.h"
#include "chrome/browser/ui/webui/print_preview/print_preview_handler.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/printing/printer_capabilities.h"
#include "chrome/common/webui_url_constants.h"
#include "chromeos/crosapi/mojom/local_printer.mojom.h"
#include "chromeos/printing/printer_configuration.h"
#include "chromeos/printing/printing_constants.h"
#include "components/device_event_log/device_event_log.h"
#include "components/signin/public/identity_manager/scope_set.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "printing/mojom/print.mojom.h"
#include "url/gurl.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "chrome/browser/ash/crosapi/crosapi_ash.h"
#include "chrome/browser/ash/crosapi/crosapi_manager.h"
#include "chrome/browser/ash/crosapi/local_printer_ash.h"
#elif BUILDFLAG(IS_CHROMEOS_LACROS)
#include "chromeos/lacros/lacros_service.h"
#endif
namespace printing {
namespace {
base::Value::Dict PrintServersConfigMojomToValue(
crosapi::mojom::PrintServersConfigPtr config) {
base::Value::List ui_print_servers;
for (const auto& print_server : config->print_servers) {
base::Value::Dict ui_print_server;
ui_print_server.Set("id", print_server->id);
ui_print_server.Set("name", print_server->name);
ui_print_servers.Append(std::move(ui_print_server));
}
base::Value::Dict ui_print_servers_config;
ui_print_servers_config.Set("printServers", std::move(ui_print_servers));
ui_print_servers_config.Set(
"isSingleServerFetchingMode",
config->fetching_mode ==
ash::ServerPrintersFetchingMode::kSingleServerOnly);
return ui_print_servers_config;
}
base::Value::List ConvertPrintersToValues(
const std::vector<crosapi::mojom::LocalDestinationInfoPtr>& printers) {
return base::ToValueList(printers, [](const auto& printer) {
return LocalPrinterHandlerChromeos::PrinterToValue(*printer);
});
}
} // namespace
PrintPreviewHandlerChromeOS::PrintPreviewHandlerChromeOS() {
#if BUILDFLAG(IS_CHROMEOS_ASH)
DCHECK(crosapi::CrosapiManager::IsInitialized());
local_printer_ =
crosapi::CrosapiManager::Get()->crosapi_ash()->local_printer_ash();
#elif BUILDFLAG(IS_CHROMEOS_LACROS)
chromeos::LacrosService* service = chromeos::LacrosService::Get();
if (!service->IsAvailable<crosapi::mojom::LocalPrinter>()) {
PRINTER_LOG(DEBUG) << "Local printer not available";
return;
}
local_printer_ = service->GetRemote<crosapi::mojom::LocalPrinter>().get();
local_printer_version_ =
service->GetInterfaceVersion<crosapi::mojom::LocalPrinter>();
#endif
}
PrintPreviewHandlerChromeOS::~PrintPreviewHandlerChromeOS() = default;
void PrintPreviewHandlerChromeOS::RegisterMessages() {
web_ui()->RegisterMessageCallback(
"setupPrinter",
base::BindRepeating(&PrintPreviewHandlerChromeOS::HandlePrinterSetup,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"grantExtensionPrinterAccess",
base::BindRepeating(
&PrintPreviewHandlerChromeOS::HandleGrantExtensionPrinterAccess,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"getEulaUrl",
base::BindRepeating(&PrintPreviewHandlerChromeOS::HandleGetEulaUrl,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"requestPrinterStatus",
base::BindRepeating(
&PrintPreviewHandlerChromeOS::HandleRequestPrinterStatusUpdate,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"choosePrintServers",
base::BindRepeating(
&PrintPreviewHandlerChromeOS::HandleChoosePrintServers,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"getPrintServersConfig",
base::BindRepeating(
&PrintPreviewHandlerChromeOS::HandleGetPrintServersConfig,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"recordPrintAttemptOutcome",
base::BindRepeating(
&PrintPreviewHandlerChromeOS::HandleRecordPrintAttemptOutcome,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"getShowManagePrinters",
base::BindRepeating(
&PrintPreviewHandlerChromeOS::HandleGetShowManagePrinters,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"observeLocalPrinters",
base::BindRepeating(
&PrintPreviewHandlerChromeOS::HandleObserveLocalPrinters,
base::Unretained(this)));
}
void PrintPreviewHandlerChromeOS::OnJavascriptAllowed() {
receiver_.reset(); // Just in case this method is called multiple times.
if (!local_printer_) {
PRINTER_LOG(DEBUG) << "Local printer not available";
return;
}
local_printer_->AddPrintServerObserver(
receiver_.BindNewPipeAndPassRemoteWithVersion(), base::DoNothing());
}
void PrintPreviewHandlerChromeOS::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();
receiver_.reset();
}
void PrintPreviewHandlerChromeOS::HandleGrantExtensionPrinterAccess(
const base::Value::List& args) {
DCHECK(args[0].is_string());
DCHECK(args[1].is_string());
std::string callback_id = args[0].GetString();
std::string printer_id = args[1].GetString();
DCHECK(!callback_id.empty());
MaybeAllowJavascript();
PrinterHandler* handler = GetPrinterHandler(mojom::PrinterType::kExtension);
handler->StartGrantPrinterAccess(
printer_id,
base::BindOnce(&PrintPreviewHandlerChromeOS::OnGotExtensionPrinterInfo,
weak_factory_.GetWeakPtr(), callback_id));
}
// |args| is expected to contain a string with representing the callback id
// followed by a list of arguments the first of which should be the printer id.
void PrintPreviewHandlerChromeOS::HandlePrinterSetup(
const base::Value::List& args) {
std::string callback_id;
std::string printer_name;
MaybeAllowJavascript();
if (args[0].is_string() && args[1].is_string()) {
callback_id = args[0].GetString();
printer_name = args[1].GetString();
}
if (callback_id.empty() || printer_name.empty()) {
RejectJavascriptCallback(base::Value(callback_id),
base::Value(printer_name));
return;
}
PrinterHandler* handler = GetPrinterHandler(mojom::PrinterType::kLocal);
handler->StartGetCapability(
printer_name,
base::BindOnce(&PrintPreviewHandlerChromeOS::SendPrinterSetup,
weak_factory_.GetWeakPtr(), callback_id, printer_name));
}
void PrintPreviewHandlerChromeOS::HandleGetEulaUrl(
const base::Value::List& args) {
CHECK_EQ(2U, args.size());
MaybeAllowJavascript();
const std::string& callback_id = args[0].GetString();
const std::string& destination_id = args[1].GetString();
PrinterHandler* handler = GetPrinterHandler(mojom::PrinterType::kLocal);
handler->StartGetEulaUrl(
destination_id, base::BindOnce(&PrintPreviewHandlerChromeOS::SendEulaUrl,
weak_factory_.GetWeakPtr(), callback_id));
}
void PrintPreviewHandlerChromeOS::SendEulaUrl(const std::string& callback_id,
const std::string& eula_url) {
VLOG(1) << "Get PPD license finished";
ResolveJavascriptCallback(base::Value(callback_id), base::Value(eula_url));
}
// Resolves the callback with a PrinterSetupResponse object (defined in
// chrome/browser/resources/print_preview/native_layer_cros.js) or rejects it
// if `destination_info` does not contain a capabilities dictionary.
// `destination_info` is a CapabilitiesResponse object (defined in
// chrome/browser/resources/print_preview/native_layer.js).
void PrintPreviewHandlerChromeOS::SendPrinterSetup(
const std::string& callback_id,
const std::string& printer_name,
base::Value::Dict destination_info) {
base::Value::Dict* caps_value =
destination_info.FindDict(kSettingCapabilities);
if (!caps_value) {
VLOG(1) << "Printer setup failed";
RejectJavascriptCallback(base::Value(callback_id), base::Value());
return;
}
FilterContinuousFeedMediaSizes(*caps_value);
base::Value::Dict response;
response.Set("printerId", printer_name);
response.Set("capabilities", std::move(*caps_value));
base::Value::Dict* printer = destination_info.FindDict(kPrinter);
if (printer) {
base::Value::Dict* policies_value = printer->FindDict(kSettingPolicies);
if (policies_value)
response.Set("policies", std::move(*policies_value));
}
ResolveJavascriptCallback(base::Value(callback_id), response);
}
PrintPreviewHandler* PrintPreviewHandlerChromeOS::GetPrintPreviewHandler() {
PrintPreviewUI* ui = web_ui()->GetController()->GetAs<PrintPreviewUI>();
CHECK(ui);
return ui->handler();
}
PrinterHandler* PrintPreviewHandlerChromeOS::GetPrinterHandler(
mojom::PrinterType printer_type) {
return GetPrintPreviewHandler()->GetPrinterHandler(printer_type);
}
void PrintPreviewHandlerChromeOS::MaybeAllowJavascript() {
if (!IsJavascriptAllowed() &&
GetPrintPreviewHandler()->IsJavascriptAllowed()) {
AllowJavascript();
}
}
void PrintPreviewHandlerChromeOS::OnGotExtensionPrinterInfo(
const std::string& callback_id,
const base::Value::Dict& printer_info) {
if (printer_info.empty()) {
RejectJavascriptCallback(base::Value(callback_id), base::Value());
} else {
ResolveJavascriptCallback(base::Value(callback_id), printer_info);
}
}
void PrintPreviewHandlerChromeOS::HandleRequestPrinterStatusUpdate(
const base::Value::List& args) {
CHECK_EQ(2U, args.size());
const std::string& callback_id = args[0].GetString();
const std::string& printer_id = args[1].GetString();
MaybeAllowJavascript();
PrinterHandler* handler = GetPrinterHandler(mojom::PrinterType::kLocal);
handler->StartPrinterStatusRequest(
printer_id,
base::BindOnce(&PrintPreviewHandlerChromeOS::
HandleRequestPrinterStatusUpdateCompletion,
weak_factory_.GetWeakPtr(), base::Value(callback_id)));
}
void PrintPreviewHandlerChromeOS::HandleRequestPrinterStatusUpdateCompletion(
base::Value callback_id,
std::optional<base::Value::Dict> result) {
if (result)
ResolveJavascriptCallback(callback_id, *result);
else
ResolveJavascriptCallback(callback_id, base::Value());
}
void PrintPreviewHandlerChromeOS::HandleChoosePrintServers(
const base::Value::List& args) {
CHECK_EQ(1U, args.size());
const base::Value& val = args[0];
std::vector<std::string> print_server_ids;
for (const auto& id : val.GetList()) {
print_server_ids.push_back(id.GetString());
}
MaybeAllowJavascript();
FireWebUIListener("server-printers-loading", base::Value(true));
if (!local_printer_) {
PRINTER_LOG(DEBUG) << "Local printer not available";
return;
}
local_printer_->ChoosePrintServers(print_server_ids, base::DoNothing());
}
void PrintPreviewHandlerChromeOS::HandleGetPrintServersConfig(
const base::Value::List& args) {
CHECK(args[0].is_string());
std::string callback_id = args[0].GetString();
CHECK(!callback_id.empty());
MaybeAllowJavascript();
if (!local_printer_) {
PRINTER_LOG(DEBUG) << "Local printer not available";
ResolveJavascriptCallback(base::Value(callback_id), base::Value());
return;
}
local_printer_->GetPrintServersConfig(
base::BindOnce(PrintServersConfigMojomToValue)
.Then(base::BindOnce(
&PrintPreviewHandlerChromeOS::ResolveJavascriptCallback,
weak_factory_.GetWeakPtr(), base::Value(callback_id))));
}
void PrintPreviewHandlerChromeOS::HandleRecordPrintAttemptOutcome(
const base::Value::List& args) {
CHECK(args[0].is_int());
chromeos::PrintAttemptOutcome result =
static_cast<chromeos::PrintAttemptOutcome>(args[0].GetInt());
base::UmaHistogramEnumeration("PrintPreview.PrintAttemptOutcome", result);
}
void PrintPreviewHandlerChromeOS::OnPrintServersChanged(
crosapi::mojom::PrintServersConfigPtr ptr) {
MaybeAllowJavascript();
FireWebUIListener("print-servers-config-changed",
PrintServersConfigMojomToValue(std::move(ptr)));
}
void PrintPreviewHandlerChromeOS::OnServerPrintersChanged() {
MaybeAllowJavascript();
FireWebUIListener("server-printers-loading", base::Value(false));
}
content::WebContents* PrintPreviewHandlerChromeOS::GetInitiator() {
if (this->test_initiator_) {
return this->test_initiator_;
}
auto* dialog_controller = PrintPreviewDialogController::GetInstance();
CHECK(dialog_controller);
return dialog_controller->GetInitiator(web_ui()->GetWebContents());
}
void PrintPreviewHandlerChromeOS::HandleGetShowManagePrinters(
const base::Value::List& args) {
CHECK_EQ(1U, args.size());
CHECK(args[0].is_string());
// AllowJavascript needs to be called here instead of relying on
// `HandleGetInitialSettings` due to timing of calls.
if (!IsJavascriptAllowed()) {
AllowJavascript();
}
auto* initiator = this->GetInitiator();
if (initiator == nullptr) {
ResolveJavascriptCallback(args[0], base::Value(false));
return;
}
const bool domain_is_os_settings = initiator->GetLastCommittedURL().DomainIs(
chrome::kChromeUIOSSettingsHost);
ResolveJavascriptCallback(args[0], base::Value(!domain_is_os_settings));
}
void PrintPreviewHandlerChromeOS::HandleObserveLocalPrinters(
const base::Value::List& args) {
CHECK_EQ(1U, args.size());
CHECK(args[0].is_string());
const std::string& callback_id = args[0].GetString();
#if BUILDFLAG(IS_CHROMEOS_LACROS)
if (int{crosapi::mojom::LocalPrinter::MethodMinVersions::
kAddLocalPrintersObserverMinVersion} > local_printer_version_) {
PRINTER_LOG(DEBUG) << "Local printer version incompatible";
ResolveJavascriptCallback(callback_id, base::Value::List());
return;
}
#endif
if (!local_printer_) {
PRINTER_LOG(DEBUG) << "Local printer not available";
ResolveJavascriptCallback(callback_id, base::Value::List());
return;
}
// Each instance of Print Preview only needs to subscribe once.
if (local_printers_receiver_.is_bound()) {
ResolveJavascriptCallback(callback_id, base::Value::List());
return;
}
local_printer_->AddLocalPrintersObserver(
local_printers_receiver_.BindNewPipeAndPassRemoteWithVersion(),
base::BindOnce(&PrintPreviewHandlerChromeOS::OnHandleObserveLocalPrinters,
weak_factory_.GetWeakPtr(), callback_id));
}
void PrintPreviewHandlerChromeOS::OnHandleObserveLocalPrinters(
const std::string& callback_id,
std::vector<crosapi::mojom::LocalDestinationInfoPtr> printers) {
ResolveJavascriptCallback(callback_id, ConvertPrintersToValues(printers));
}
void PrintPreviewHandlerChromeOS::OnLocalPrintersUpdated(
std::vector<crosapi::mojom::LocalDestinationInfoPtr> printers) {
FireWebUIListener("local-printers-updated",
ConvertPrintersToValues(printers));
}
void PrintPreviewHandlerChromeOS::SetInitiatorForTesting(
content::WebContents* test_initiator) {
this->test_initiator_ = test_initiator;
}
} // namespace printing