diff --git a/BUILD.gn b/BUILD.gn index 1363a4296671..f6bacd6c8888 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -701,6 +701,8 @@ source_set("electron_lib") { sources += [ "shell/browser/printing/print_view_manager_electron.cc", "shell/browser/printing/print_view_manager_electron.h", + "shell/browser/printing/printing_utils.cc", + "shell/browser/printing/printing_utils.h", "shell/renderer/printing/print_render_frame_helper_delegate.cc", "shell/renderer/printing/print_render_frame_helper_delegate.h", ] diff --git a/shell/browser/api/electron_api_web_contents.cc b/shell/browser/api/electron_api_web_contents.cc index 01ed13bf0f4e..24b8bb8ab450 100644 --- a/shell/browser/api/electron_api_web_contents.cc +++ b/shell/browser/api/electron_api_web_contents.cc @@ -76,7 +76,6 @@ #include "mojo/public/cpp/system/platform_handle.h" #include "ppapi/buildflags/buildflags.h" #include "printing/buildflags/buildflags.h" -#include "printing/print_job_constants.h" #include "services/resource_coordinator/public/cpp/memory_instrumentation/memory_instrumentation.h" #include "services/service_manager/public/cpp/interface_provider.h" #include "shell/browser/api/electron_api_browser_window.h" @@ -173,10 +172,10 @@ #include "components/printing/browser/print_manager_utils.h" #include "components/printing/browser/print_to_pdf/pdf_print_result.h" #include "components/printing/browser/print_to_pdf/pdf_print_utils.h" -#include "printing/backend/print_backend.h" // nogncheck -#include "printing/mojom/print.mojom.h" // nogncheck +#include "printing/mojom/print.mojom.h" // nogncheck #include "printing/page_range.h" #include "shell/browser/printing/print_view_manager_electron.h" +#include "shell/browser/printing/printing_utils.h" #if BUILDFLAG(IS_WIN) #include "printing/backend/win_helper.h" @@ -530,96 +529,6 @@ std::optional GetCursorBlinkInterval() { return std::nullopt; } -#if BUILDFLAG(ENABLE_PRINTING) -// This will return false if no printer with the provided device_name can be -// found on the network. We need to check this because Chromium does not do -// sanity checking of device_name validity and so will crash on invalid names. -bool IsDeviceNameValid(const std::u16string& device_name) { -#if BUILDFLAG(IS_MAC) - base::apple::ScopedCFTypeRef new_printer_id( - base::SysUTF16ToCFStringRef(device_name)); - PMPrinter new_printer = PMPrinterCreateFromPrinterID(new_printer_id.get()); - bool printer_exists = new_printer != nullptr; - PMRelease(new_printer); - return printer_exists; -#else - scoped_refptr print_backend = - printing::PrintBackend::CreateInstance( - g_browser_process->GetApplicationLocale()); - return print_backend->IsValidPrinter(base::UTF16ToUTF8(device_name)); -#endif -} - -// This function returns a validated device name. -// If the user passed one to webContents.print(), we check that it's valid and -// return it or fail if the network doesn't recognize it. If the user didn't -// pass a device name, we first try to return the system default printer. If one -// isn't set, then pull all the printers and use the first one or fail if none -// exist. -std::pair GetDeviceNameToUse( - const std::u16string& device_name) { -#if BUILDFLAG(IS_WIN) - // Blocking is needed here because Windows printer drivers are oftentimes - // not thread-safe and have to be accessed on the UI thread. - ScopedAllowBlockingForElectron allow_blocking; -#endif - - if (!device_name.empty()) { - if (!IsDeviceNameValid(device_name)) - return std::make_pair("Invalid deviceName provided", std::u16string()); - return std::make_pair(std::string(), device_name); - } - - scoped_refptr print_backend = - printing::PrintBackend::CreateInstance( - g_browser_process->GetApplicationLocale()); - std::string printer_name; - printing::mojom::ResultCode code = - print_backend->GetDefaultPrinterName(printer_name); - - // We don't want to return if this fails since some devices won't have a - // default printer. - if (code != printing::mojom::ResultCode::kSuccess) - LOG(ERROR) << "Failed to get default printer name"; - - if (printer_name.empty()) { - printing::PrinterList printers; - if (print_backend->EnumeratePrinters(printers) != - printing::mojom::ResultCode::kSuccess) - return std::make_pair("Failed to enumerate printers", std::u16string()); - if (printers.empty()) - return std::make_pair("No printers available on the network", - std::u16string()); - - printer_name = printers.front().printer_name; - } - - return std::make_pair(std::string(), base::UTF8ToUTF16(printer_name)); -} - -// Copied from -// chrome/browser/ui/webui/print_preview/local_printer_handler_default.cc:L36-L54 -scoped_refptr CreatePrinterHandlerTaskRunner() { - // USER_VISIBLE because the result is displayed in the print preview dialog. -#if !BUILDFLAG(IS_WIN) - static constexpr base::TaskTraits kTraits = { - base::MayBlock(), base::TaskPriority::USER_VISIBLE}; -#endif - -#if defined(USE_CUPS) - // CUPS is thread safe. - return base::ThreadPool::CreateTaskRunner(kTraits); -#elif BUILDFLAG(IS_WIN) - // Windows drivers are likely not thread-safe and need to be accessed on the - // UI thread. - return content::GetUIThreadTaskRunner({base::TaskPriority::USER_VISIBLE}); -#else - // Be conservative on unsupported platforms. - return base::ThreadPool::CreateSingleThreadTaskRunner(kTraits); -#endif -} -#endif - struct UserDataLink : public base::SupportsUserData::Data { explicit UserDataLink(base::WeakPtr contents) : web_contents(contents) {} @@ -2972,6 +2881,12 @@ void WebContents::OnGetDeviceNameToUse( // If the user has passed a deviceName use it, otherwise use default printer. print_settings.Set(printing::kSettingDeviceName, info.second); + if (!print_settings.FindInt(printing::kSettingDpiHorizontal)) { + gfx::Size dpi = GetDefaultPrinterDPI(info.second); + print_settings.Set(printing::kSettingDpiHorizontal, dpi.width()); + print_settings.Set(printing::kSettingDpiVertical, dpi.height()); + } + auto* print_view_manager = PrintViewManagerElectron::FromWebContents(web_contents()); if (!print_view_manager) @@ -3133,7 +3048,6 @@ void WebContents::Print(gin::Arguments* args) { // Set custom dots per inch (dpi) gin_helper::Dictionary dpi_settings; - int dpi = 72; if (options.Get("dpi", &dpi_settings)) { int horizontal = 72; dpi_settings.Get("horizontal", &horizontal); @@ -3141,9 +3055,6 @@ void WebContents::Print(gin::Arguments* args) { int vertical = 72; dpi_settings.Get("vertical", &vertical); settings.Set(printing::kSettingDpiVertical, vertical); - } else { - settings.Set(printing::kSettingDpiHorizontal, dpi); - settings.Set(printing::kSettingDpiVertical, dpi); } print_task_runner_->PostTaskAndReplyWithResult( diff --git a/shell/browser/printing/printing_utils.cc b/shell/browser/printing/printing_utils.cc new file mode 100644 index 000000000000..5b0f934cd793 --- /dev/null +++ b/shell/browser/printing/printing_utils.cc @@ -0,0 +1,134 @@ +// Copyright (c) 2024 Microsoft, GmbH. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "shell/browser/printing/printing_utils.h" + +#include "base/apple/scoped_typeref.h" +#include "base/strings/sys_string_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/task/task_traits.h" +#include "base/task/thread_pool.h" +#include "chrome/browser/browser_process.h" +#include "content/public/browser/browser_thread.h" +#include "electron/buildflags/buildflags.h" +#include "printing/backend/print_backend.h" // nogncheck +#include "printing/units.h" +#include "shell/common/thread_restrictions.h" + +#if BUILDFLAG(IS_MAC) +#include +#endif + +#if BUILDFLAG(IS_LINUX) +#include +#endif + +#if BUILDFLAG(IS_WIN) +#include +#endif + +namespace electron { + +gfx::Size GetDefaultPrinterDPI(const std::u16string& device_name) { +#if BUILDFLAG(IS_MAC) + return gfx::Size(printing::kDefaultMacDpi, printing::kDefaultMacDpi); +#elif BUILDFLAG(IS_WIN) + HDC hdc = + CreateDC(L"WINSPOOL", base::as_wcstr(device_name), nullptr, nullptr); + if (hdc == nullptr) + return gfx::Size(printing::kDefaultPdfDpi, printing::kDefaultPdfDpi); + + int dpi_x = GetDeviceCaps(hdc, LOGPIXELSX); + int dpi_y = GetDeviceCaps(hdc, LOGPIXELSY); + DeleteDC(hdc); + + return gfx::Size(dpi_x, dpi_y); +#else + GtkPrintSettings* print_settings = gtk_print_settings_new(); + int dpi = gtk_print_settings_get_resolution(print_settings); + g_object_unref(print_settings); + return gfx::Size(dpi, dpi); +#endif +} + +bool IsDeviceNameValid(const std::u16string& device_name) { +#if BUILDFLAG(IS_MAC) + base::apple::ScopedCFTypeRef new_printer_id( + base::SysUTF16ToCFStringRef(device_name)); + PMPrinter new_printer = PMPrinterCreateFromPrinterID(new_printer_id.get()); + bool printer_exists = new_printer != nullptr; + PMRelease(new_printer); + return printer_exists; +#else + scoped_refptr print_backend = + printing::PrintBackend::CreateInstance( + g_browser_process->GetApplicationLocale()); + return print_backend->IsValidPrinter(base::UTF16ToUTF8(device_name)); +#endif +} + +std::pair GetDeviceNameToUse( + const std::u16string& device_name) { +#if BUILDFLAG(IS_WIN) + // Blocking is needed here because Windows printer drivers are oftentimes + // not thread-safe and have to be accessed on the UI thread. + ScopedAllowBlockingForElectron allow_blocking; +#endif + + if (!device_name.empty()) { + if (!IsDeviceNameValid(device_name)) + return std::make_pair("Invalid deviceName provided", std::u16string()); + return std::make_pair(std::string(), device_name); + } + + scoped_refptr print_backend = + printing::PrintBackend::CreateInstance( + g_browser_process->GetApplicationLocale()); + std::string printer_name; + printing::mojom::ResultCode code = + print_backend->GetDefaultPrinterName(printer_name); + + // We don't want to return if this fails since some devices won't have a + // default printer. + if (code != printing::mojom::ResultCode::kSuccess) + LOG(ERROR) << "Failed to get default printer name"; + + if (printer_name.empty()) { + printing::PrinterList printers; + if (print_backend->EnumeratePrinters(printers) != + printing::mojom::ResultCode::kSuccess) + return std::make_pair("Failed to enumerate printers", std::u16string()); + if (printers.empty()) + return std::make_pair("No printers available on the network", + std::u16string()); + + printer_name = printers.front().printer_name; + } + + return std::make_pair(std::string(), base::UTF8ToUTF16(printer_name)); +} + +// Copied from +// chrome/browser/ui/webui/print_preview/local_printer_handler_default.cc:L36-L54 +scoped_refptr CreatePrinterHandlerTaskRunner() { + // USER_VISIBLE because the result is displayed in the print preview dialog. +#if !BUILDFLAG(IS_WIN) + static constexpr base::TaskTraits kTraits = { + base::MayBlock(), base::TaskPriority::USER_VISIBLE}; +#endif + +#if defined(USE_CUPS) + // CUPS is thread safe. + return base::ThreadPool::CreateTaskRunner(kTraits); +#elif BUILDFLAG(IS_WIN) + // Windows drivers are likely not thread-safe and need to be accessed on the + // UI thread. + return content::GetUIThreadTaskRunner({base::TaskPriority::USER_VISIBLE}); +#else + // Be conservative on unsupported platforms. + return base::ThreadPool::CreateSingleThreadTaskRunner(kTraits); +#endif +} + +} // namespace electron diff --git a/shell/browser/printing/printing_utils.h b/shell/browser/printing/printing_utils.h new file mode 100644 index 000000000000..f18174f0876d --- /dev/null +++ b/shell/browser/printing/printing_utils.h @@ -0,0 +1,41 @@ +// Copyright (c) 2024 Microsoft, GmbH. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ELECTRON_SHELL_BROWSER_PRINTING_PRINTING_UTILS_H_ +#define ELECTRON_SHELL_BROWSER_PRINTING_PRINTING_UTILS_H_ + +#include + +#include "base/memory/scoped_refptr.h" +#include "base/task/task_runner.h" + +namespace gfx { +class Size; +} + +namespace electron { + +// This function returns the per-platform default printer's DPI. +gfx::Size GetDefaultPrinterDPI(const std::u16string& device_name); + +// This will return false if no printer with the provided device_name can be +// found on the network. We need to check this because Chromium does not do +// sanity checking of device_name validity and so will crash on invalid names. +bool IsDeviceNameValid(const std::u16string& device_name); + +// This function returns a validated device name. +// If the user passed one to webContents.print(), we check that it's valid and +// return it or fail if the network doesn't recognize it. If the user didn't +// pass a device name, we first try to return the system default printer. If one +// isn't set, then pull all the printers and use the first one or fail if none +// exist. +std::pair GetDeviceNameToUse( + const std::u16string& device_name); + +// This function creates a task runner for use with printing tasks. +scoped_refptr CreatePrinterHandlerTaskRunner(); + +} // namespace electron + +#endif // ELECTRON_SHELL_BROWSER_PRINTING_PRINTING_UTILS_H_