Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / widget / windows / nsPrintDialogUtil.cpp
blob43f56e9706cd546ccb97ed479f7bbbe37feaa10c
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 /* -------------------------------------------------------------------
7 To Build This:
9 You need to add this to the the makefile.win in mozilla/dom/base:
11 .\$(OBJDIR)\nsFlyOwnPrintDialog.obj \
14 And this to the makefile.win in mozilla/content/build:
16 WIN_LIBS= \
17 winspool.lib \
18 comctl32.lib \
19 comdlg32.lib
21 ---------------------------------------------------------------------- */
23 #include <windows.h>
24 #include <tchar.h>
26 #include <unknwn.h>
27 #include <commdlg.h>
29 #include "mozilla/BackgroundHangMonitor.h"
30 #include "mozilla/ScopeExit.h"
31 #include "mozilla/Span.h"
32 #include "nsString.h"
33 #include "nsReadableUtils.h"
34 #include "nsIPrintSettings.h"
35 #include "nsIPrintSettingsWin.h"
36 #include "nsIPrinterList.h"
37 #include "nsServiceManagerUtils.h"
39 #include "nsRect.h"
41 #include "nsCRT.h"
42 #include "prenv.h" /* for PR_GetEnv */
44 #include <windows.h>
45 #include <winspool.h>
47 // For Localization
49 // For NS_CopyUnicodeToNative
50 #include "nsNativeCharsetUtils.h"
52 // This is for extending the dialog
53 #include <dlgs.h>
55 #include "nsWindowsHelpers.h"
56 #include "WinUtils.h"
58 //-----------------------------------------------
59 // Global Data
60 //-----------------------------------------------
62 static HWND gParentWnd = nullptr;
64 //----------------------------------------------------------------------------------
65 // Returns a Global Moveable Memory Handle to a DevMode
66 // from the Printer by the name of aPrintName
68 // NOTE:
69 // This function assumes that aPrintName has already been converted from
70 // unicode
72 static nsReturnRef<nsHGLOBAL> CreateGlobalDevModeAndInit(
73 const nsString& aPrintName, nsIPrintSettings* aPS) {
74 nsHPRINTER hPrinter = nullptr;
75 // const cast kludge for silly Win32 api's
76 LPWSTR printName =
77 const_cast<wchar_t*>(static_cast<const wchar_t*>(aPrintName.get()));
78 BOOL status = ::OpenPrinterW(printName, &hPrinter, nullptr);
79 if (!status) {
80 return nsReturnRef<nsHGLOBAL>();
83 // Make sure hPrinter is closed on all paths
84 nsAutoPrinter autoPrinter(hPrinter);
86 // Get the buffer size
87 LONG needed = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, nullptr,
88 nullptr, 0);
89 if (needed < 0) {
90 return nsReturnRef<nsHGLOBAL>();
93 // Some drivers do not return the correct size for their DEVMODE, so we
94 // over-allocate to try and compensate.
95 // (See https://bugzilla.mozilla.org/show_bug.cgi?id=1664530#c5)
96 needed *= 2;
97 nsAutoDevMode newDevMode(
98 (LPDEVMODEW)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, needed));
99 if (!newDevMode) {
100 return nsReturnRef<nsHGLOBAL>();
103 nsHGLOBAL hDevMode = ::GlobalAlloc(GHND, needed);
104 nsAutoGlobalMem globalDevMode(hDevMode);
105 if (!hDevMode) {
106 return nsReturnRef<nsHGLOBAL>();
109 LONG ret = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, newDevMode,
110 nullptr, DM_OUT_BUFFER);
111 if (ret != IDOK) {
112 return nsReturnRef<nsHGLOBAL>();
115 // Lock memory and copy contents from DEVMODE (current printer)
116 // to Global Memory DEVMODE
117 LPDEVMODEW devMode = (DEVMODEW*)::GlobalLock(hDevMode);
118 if (!devMode) {
119 return nsReturnRef<nsHGLOBAL>();
122 memcpy(devMode, newDevMode.get(), needed);
123 // Initialize values from the PrintSettings
124 nsCOMPtr<nsIPrintSettingsWin> psWin = do_QueryInterface(aPS);
125 MOZ_ASSERT(psWin);
126 psWin->CopyToNative(devMode);
128 // Sets back the changes we made to the DevMode into the Printer Driver
129 ret = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, devMode, devMode,
130 DM_IN_BUFFER | DM_OUT_BUFFER);
131 if (ret != IDOK) {
132 ::GlobalUnlock(hDevMode);
133 return nsReturnRef<nsHGLOBAL>();
136 ::GlobalUnlock(hDevMode);
138 return globalDevMode.out();
141 //------------------------------------------------------------------
142 // helper
143 static void GetDefaultPrinterNameFromGlobalPrinters(nsAString& aPrinterName) {
144 aPrinterName.Truncate();
145 nsCOMPtr<nsIPrinterList> printerList =
146 do_GetService("@mozilla.org/gfx/printerlist;1");
147 if (printerList) {
148 printerList->GetSystemDefaultPrinterName(aPrinterName);
152 //------------------------------------------------------------------
153 // Displays the native Print Dialog
154 nsresult NativeShowPrintDialog(HWND aHWnd, bool aHaveSelection,
155 nsIPrintSettings* aPrintSettings) {
156 // NS_ENSURE_ARG_POINTER(aHWnd);
157 NS_ENSURE_ARG_POINTER(aPrintSettings);
159 // Get the Print Name to be used
160 nsString printerName;
161 aPrintSettings->GetPrinterName(printerName);
163 // If there is no name then use the default printer
164 if (printerName.IsEmpty()) {
165 GetDefaultPrinterNameFromGlobalPrinters(printerName);
166 } else {
167 HANDLE hPrinter = nullptr;
168 if (!::OpenPrinterW(const_cast<wchar_t*>(
169 static_cast<const wchar_t*>(printerName.get())),
170 &hPrinter, nullptr)) {
171 // If the last used printer is not found, we should use default printer.
172 GetDefaultPrinterNameFromGlobalPrinters(printerName);
173 } else {
174 ::ClosePrinter(hPrinter);
178 // Now create a DEVNAMES struct so the the dialog is initialized correctly.
180 uint32_t len = printerName.Length();
181 nsHGLOBAL hDevNames =
182 ::GlobalAlloc(GHND, sizeof(wchar_t) * (len + 1) + sizeof(DEVNAMES));
183 nsAutoGlobalMem autoDevNames(hDevNames);
184 if (!hDevNames) {
185 return NS_ERROR_OUT_OF_MEMORY;
188 DEVNAMES* pDevNames = (DEVNAMES*)::GlobalLock(hDevNames);
189 if (!pDevNames) {
190 return NS_ERROR_FAILURE;
192 pDevNames->wDriverOffset = sizeof(DEVNAMES) / sizeof(wchar_t);
193 pDevNames->wDeviceOffset = sizeof(DEVNAMES) / sizeof(wchar_t);
194 pDevNames->wOutputOffset = sizeof(DEVNAMES) / sizeof(wchar_t) + len;
195 pDevNames->wDefault = 0;
197 memcpy(pDevNames + 1, printerName.get(), (len + 1) * sizeof(wchar_t));
198 ::GlobalUnlock(hDevNames);
200 // Create a Moveable Memory Object that holds a new DevMode
201 // from the Printer Name
202 // The PRINTDLG.hDevMode requires that it be a moveable memory object
203 // NOTE: autoDevMode is automatically freed when any error occurred
204 nsAutoGlobalMem autoDevMode(
205 CreateGlobalDevModeAndInit(printerName, aPrintSettings));
207 // Prepare to Display the Print Dialog
208 // https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms646942(v=vs.85)
209 // https://docs.microsoft.com/en-us/windows/win32/api/commdlg/ns-commdlg-printdlgexw
210 PRINTDLGEXW prntdlg;
211 memset(&prntdlg, 0, sizeof(prntdlg));
213 prntdlg.lStructSize = sizeof(prntdlg);
214 prntdlg.hwndOwner = aHWnd;
215 prntdlg.hDevMode = autoDevMode.get();
216 prntdlg.hDevNames = hDevNames;
217 prntdlg.hDC = nullptr;
218 prntdlg.Flags = PD_ALLPAGES | PD_RETURNIC | PD_USEDEVMODECOPIESANDCOLLATE |
219 PD_COLLATE | PD_NOCURRENTPAGE;
221 // If there is a current selection then enable the "Selection" radio button
222 if (!aHaveSelection) {
223 prntdlg.Flags |= PD_NOSELECTION;
226 // 10 seems like a reasonable max number of ranges to support by default if
227 // the user doesn't choose a greater thing in the UI.
228 constexpr size_t kMinSupportedRanges = 10;
230 AutoTArray<PRINTPAGERANGE, kMinSupportedRanges> winPageRanges;
231 // Set up the page ranges.
233 AutoTArray<int32_t, kMinSupportedRanges * 2> pageRanges;
234 aPrintSettings->GetPageRanges(pageRanges);
235 // If there is a specified page range then enable the "Custom" radio button
236 if (!pageRanges.IsEmpty()) {
237 prntdlg.Flags |= PD_PAGENUMS;
240 const size_t specifiedRanges = pageRanges.Length() / 2;
241 const size_t maxRanges = std::max(kMinSupportedRanges, specifiedRanges);
243 prntdlg.nMaxPageRanges = maxRanges;
244 prntdlg.nPageRanges = specifiedRanges;
246 winPageRanges.SetCapacity(maxRanges);
247 for (size_t i = 0; i < pageRanges.Length(); i += 2) {
248 PRINTPAGERANGE* range = winPageRanges.AppendElement();
249 range->nFromPage = pageRanges[i];
250 range->nToPage = pageRanges[i + 1];
252 prntdlg.lpPageRanges = winPageRanges.Elements();
254 prntdlg.nMinPage = 1;
255 // TODO(emilio): Could probably get the right page number here from the
256 // new print UI.
257 prntdlg.nMaxPage = 0xFFFF;
260 // NOTE(emilio): This can always be 1 because we use the DEVMODE copies
261 // feature (see PD_USEDEVMODECOPIESANDCOLLATE).
262 prntdlg.nCopies = 1;
264 prntdlg.hInstance = nullptr;
265 prntdlg.lpPrintTemplateName = nullptr;
267 prntdlg.lpCallback = nullptr;
268 prntdlg.nPropertyPages = 0;
269 prntdlg.lphPropertyPages = nullptr;
271 prntdlg.nStartPage = START_PAGE_GENERAL;
272 prntdlg.dwResultAction = 0;
274 HRESULT result;
276 mozilla::widget::WinUtils::AutoSystemDpiAware dpiAwareness;
277 mozilla::BackgroundHangMonitor().NotifyWait();
278 result = ::PrintDlgExW(&prntdlg);
281 auto cancelOnExit = mozilla::MakeScopeExit([&] { ::SetFocus(aHWnd); });
283 if (NS_WARN_IF(!SUCCEEDED(result))) {
284 #ifdef DEBUG
285 printf_stderr("PrintDlgExW failed with %lx\n", result);
286 #endif
287 return NS_ERROR_FAILURE;
289 if (NS_WARN_IF(prntdlg.dwResultAction != PD_RESULT_PRINT)) {
290 return NS_ERROR_ABORT;
292 // check to make sure we don't have any nullptr pointers
293 NS_ENSURE_TRUE(prntdlg.hDevMode, NS_ERROR_ABORT);
294 NS_ENSURE_TRUE(prntdlg.hDevNames, NS_ERROR_ABORT);
295 // Lock the deviceNames and check for nullptr
296 DEVNAMES* devnames = (DEVNAMES*)::GlobalLock(prntdlg.hDevNames);
297 NS_ENSURE_TRUE(devnames, NS_ERROR_ABORT);
299 char16_t* device = &(((char16_t*)devnames)[devnames->wDeviceOffset]);
300 char16_t* driver = &(((char16_t*)devnames)[devnames->wDriverOffset]);
302 // Check to see if the "Print To File" control is checked
303 // then take the name from devNames and set it in the PrintSettings
305 // NOTE:
306 // As per Microsoft SDK documentation the returned value offset from
307 // devnames->wOutputOffset is either "FILE:" or nullptr
308 // if the "Print To File" checkbox is checked it MUST be "FILE:"
309 // We assert as an extra safety check.
310 if (prntdlg.Flags & PD_PRINTTOFILE) {
311 char16ptr_t fileName = &(((wchar_t*)devnames)[devnames->wOutputOffset]);
312 NS_ASSERTION(wcscmp(fileName, L"FILE:") == 0, "FileName must be `FILE:`");
313 aPrintSettings->SetOutputDestination(
314 nsIPrintSettings::kOutputDestinationFile);
315 aPrintSettings->SetToFileName(nsDependentString(fileName));
316 } else {
317 // clear "print to file" info
318 aPrintSettings->SetOutputDestination(
319 nsIPrintSettings::kOutputDestinationPrinter);
320 aPrintSettings->SetToFileName(u""_ns);
323 nsCOMPtr<nsIPrintSettingsWin> psWin(do_QueryInterface(aPrintSettings));
324 MOZ_RELEASE_ASSERT(psWin);
326 // Setup local Data members
327 psWin->SetDeviceName(nsDependentString(device));
328 psWin->SetDriverName(nsDependentString(driver));
330 // Fill the print options with the info from the dialog
331 aPrintSettings->SetPrinterName(nsDependentString(device));
332 aPrintSettings->SetPrintSelectionOnly(prntdlg.Flags & PD_SELECTION);
334 AutoTArray<int32_t, kMinSupportedRanges * 2> pageRanges;
335 if (prntdlg.Flags & PD_PAGENUMS) {
336 pageRanges.SetCapacity(prntdlg.nPageRanges * 2);
337 for (const auto& range :
338 mozilla::Span(prntdlg.lpPageRanges, prntdlg.nPageRanges)) {
339 pageRanges.AppendElement(range.nFromPage);
340 pageRanges.AppendElement(range.nToPage);
343 aPrintSettings->SetPageRanges(pageRanges);
345 // Unlock DeviceNames
346 ::GlobalUnlock(prntdlg.hDevNames);
348 // Transfer the settings from the native data to the PrintSettings
349 LPDEVMODEW devMode = (LPDEVMODEW)::GlobalLock(prntdlg.hDevMode);
350 if (!devMode || !prntdlg.hDC) {
351 return NS_ERROR_FAILURE;
353 psWin->SetDevMode(devMode); // copies DevMode
354 psWin->CopyFromNative(prntdlg.hDC, devMode);
355 ::GlobalUnlock(prntdlg.hDevMode);
356 ::DeleteDC(prntdlg.hDC);
358 cancelOnExit.release();
359 return NS_OK;