Backed out changeset b71c8c052463 (bug 1943846) for causing mass failures. CLOSED...
[gecko.git] / widget / windows / nsPrintDialogUtil.cpp
blob9d2033d61e39c3d5575e9e984e08974a76d802dd
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 = const_cast<wchar_t*>(aPrintName.getW());
77 BOOL status = ::OpenPrinterW(printName, &hPrinter, nullptr);
78 if (!status) {
79 return nsReturnRef<nsHGLOBAL>();
82 // Make sure hPrinter is closed on all paths
83 nsAutoPrinter autoPrinter(hPrinter);
85 // Get the buffer size
86 LONG needed = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, nullptr,
87 nullptr, 0);
88 if (needed < 0) {
89 return nsReturnRef<nsHGLOBAL>();
92 // Some drivers do not return the correct size for their DEVMODE, so we
93 // over-allocate to try and compensate.
94 // (See https://bugzilla.mozilla.org/show_bug.cgi?id=1664530#c5)
95 needed *= 2;
96 nsAutoDevMode newDevMode(
97 (LPDEVMODEW)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, needed));
98 if (!newDevMode) {
99 return nsReturnRef<nsHGLOBAL>();
102 nsHGLOBAL hDevMode = ::GlobalAlloc(GHND, needed);
103 nsAutoGlobalMem globalDevMode(hDevMode);
104 if (!hDevMode) {
105 return nsReturnRef<nsHGLOBAL>();
108 LONG ret = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, newDevMode,
109 nullptr, DM_OUT_BUFFER);
110 if (ret != IDOK) {
111 return nsReturnRef<nsHGLOBAL>();
114 // Lock memory and copy contents from DEVMODE (current printer)
115 // to Global Memory DEVMODE
116 LPDEVMODEW devMode = (DEVMODEW*)::GlobalLock(hDevMode);
117 if (!devMode) {
118 return nsReturnRef<nsHGLOBAL>();
121 memcpy(devMode, newDevMode.get(), needed);
122 // Initialize values from the PrintSettings
123 nsCOMPtr<nsIPrintSettingsWin> psWin = do_QueryInterface(aPS);
124 MOZ_ASSERT(psWin);
125 psWin->CopyToNative(devMode);
127 // Sets back the changes we made to the DevMode into the Printer Driver
128 ret = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, devMode, devMode,
129 DM_IN_BUFFER | DM_OUT_BUFFER);
130 if (ret != IDOK) {
131 ::GlobalUnlock(hDevMode);
132 return nsReturnRef<nsHGLOBAL>();
135 ::GlobalUnlock(hDevMode);
137 return globalDevMode.out();
140 //------------------------------------------------------------------
141 // helper
142 static void GetDefaultPrinterNameFromGlobalPrinters(nsAString& aPrinterName) {
143 aPrinterName.Truncate();
144 nsCOMPtr<nsIPrinterList> printerList =
145 do_GetService("@mozilla.org/gfx/printerlist;1");
146 if (printerList) {
147 printerList->GetSystemDefaultPrinterName(aPrinterName);
151 //------------------------------------------------------------------
152 // Displays the native Print Dialog
153 nsresult NativeShowPrintDialog(HWND aHWnd, bool aHaveSelection,
154 nsIPrintSettings* aPrintSettings) {
155 // NS_ENSURE_ARG_POINTER(aHWnd);
156 NS_ENSURE_ARG_POINTER(aPrintSettings);
158 // Get the Print Name to be used
159 nsString printerName;
160 aPrintSettings->GetPrinterName(printerName);
162 // If there is no name then use the default printer
163 if (printerName.IsEmpty()) {
164 GetDefaultPrinterNameFromGlobalPrinters(printerName);
165 } else {
166 HANDLE hPrinter = nullptr;
167 if (!::OpenPrinterW(const_cast<wchar_t*>(printerName.getW()), &hPrinter,
168 nullptr)) {
169 // If the last used printer is not found, we should use default printer.
170 GetDefaultPrinterNameFromGlobalPrinters(printerName);
171 } else {
172 ::ClosePrinter(hPrinter);
176 // Now create a DEVNAMES struct so the the dialog is initialized correctly.
178 uint32_t len = printerName.Length();
179 nsHGLOBAL hDevNames =
180 ::GlobalAlloc(GHND, sizeof(wchar_t) * (len + 1) + sizeof(DEVNAMES));
181 nsAutoGlobalMem autoDevNames(hDevNames);
182 if (!hDevNames) {
183 return NS_ERROR_OUT_OF_MEMORY;
186 DEVNAMES* pDevNames = (DEVNAMES*)::GlobalLock(hDevNames);
187 if (!pDevNames) {
188 return NS_ERROR_FAILURE;
190 pDevNames->wDriverOffset = sizeof(DEVNAMES) / sizeof(wchar_t);
191 pDevNames->wDeviceOffset = sizeof(DEVNAMES) / sizeof(wchar_t);
192 pDevNames->wOutputOffset = sizeof(DEVNAMES) / sizeof(wchar_t) + len;
193 pDevNames->wDefault = 0;
195 memcpy(pDevNames + 1, printerName.get(), (len + 1) * sizeof(wchar_t));
196 ::GlobalUnlock(hDevNames);
198 // Create a Moveable Memory Object that holds a new DevMode
199 // from the Printer Name
200 // The PRINTDLG.hDevMode requires that it be a moveable memory object
201 // NOTE: autoDevMode is automatically freed when any error occurred
202 nsAutoGlobalMem autoDevMode(
203 CreateGlobalDevModeAndInit(printerName, aPrintSettings));
205 // Prepare to Display the Print Dialog
206 // https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms646942(v=vs.85)
207 // https://docs.microsoft.com/en-us/windows/win32/api/commdlg/ns-commdlg-printdlgexw
208 PRINTDLGEXW prntdlg;
209 memset(&prntdlg, 0, sizeof(prntdlg));
211 prntdlg.lStructSize = sizeof(prntdlg);
212 prntdlg.hwndOwner = aHWnd;
213 prntdlg.hDevMode = autoDevMode.get();
214 prntdlg.hDevNames = hDevNames;
215 prntdlg.hDC = nullptr;
216 prntdlg.Flags = PD_ALLPAGES | PD_RETURNIC | PD_USEDEVMODECOPIESANDCOLLATE |
217 PD_COLLATE | PD_NOCURRENTPAGE;
219 // If there is a current selection then enable the "Selection" radio button
220 if (!aHaveSelection) {
221 prntdlg.Flags |= PD_NOSELECTION;
224 // 10 seems like a reasonable max number of ranges to support by default if
225 // the user doesn't choose a greater thing in the UI.
226 constexpr size_t kMinSupportedRanges = 10;
228 AutoTArray<PRINTPAGERANGE, kMinSupportedRanges> winPageRanges;
229 // Set up the page ranges.
231 AutoTArray<int32_t, kMinSupportedRanges * 2> pageRanges;
232 aPrintSettings->GetPageRanges(pageRanges);
233 // If there is a specified page range then enable the "Custom" radio button
234 if (!pageRanges.IsEmpty()) {
235 prntdlg.Flags |= PD_PAGENUMS;
238 const size_t specifiedRanges = pageRanges.Length() / 2;
239 const size_t maxRanges = std::max(kMinSupportedRanges, specifiedRanges);
241 prntdlg.nMaxPageRanges = maxRanges;
242 prntdlg.nPageRanges = specifiedRanges;
244 winPageRanges.SetCapacity(maxRanges);
245 for (size_t i = 0; i < pageRanges.Length(); i += 2) {
246 PRINTPAGERANGE* range = winPageRanges.AppendElement();
247 range->nFromPage = pageRanges[i];
248 range->nToPage = pageRanges[i + 1];
250 prntdlg.lpPageRanges = winPageRanges.Elements();
252 prntdlg.nMinPage = 1;
253 // TODO(emilio): Could probably get the right page number here from the
254 // new print UI.
255 prntdlg.nMaxPage = 0xFFFF;
258 // NOTE(emilio): This can always be 1 because we use the DEVMODE copies
259 // feature (see PD_USEDEVMODECOPIESANDCOLLATE).
260 prntdlg.nCopies = 1;
262 prntdlg.hInstance = nullptr;
263 prntdlg.lpPrintTemplateName = nullptr;
265 prntdlg.lpCallback = nullptr;
266 prntdlg.nPropertyPages = 0;
267 prntdlg.lphPropertyPages = nullptr;
269 prntdlg.nStartPage = START_PAGE_GENERAL;
270 prntdlg.dwResultAction = 0;
272 HRESULT result;
274 mozilla::widget::WinUtils::AutoSystemDpiAware dpiAwareness;
275 mozilla::BackgroundHangMonitor().NotifyWait();
276 result = ::PrintDlgExW(&prntdlg);
279 auto cancelOnExit = mozilla::MakeScopeExit([&] { ::SetFocus(aHWnd); });
281 if (NS_WARN_IF(!SUCCEEDED(result))) {
282 #ifdef DEBUG
283 printf_stderr("PrintDlgExW failed with %lx\n", result);
284 #endif
285 return NS_ERROR_FAILURE;
287 if (NS_WARN_IF(prntdlg.dwResultAction != PD_RESULT_PRINT)) {
288 return NS_ERROR_ABORT;
290 // check to make sure we don't have any nullptr pointers
291 NS_ENSURE_TRUE(prntdlg.hDevMode, NS_ERROR_ABORT);
292 NS_ENSURE_TRUE(prntdlg.hDevNames, NS_ERROR_ABORT);
293 // Lock the deviceNames and check for nullptr
294 DEVNAMES* devnames = (DEVNAMES*)::GlobalLock(prntdlg.hDevNames);
295 NS_ENSURE_TRUE(devnames, NS_ERROR_ABORT);
297 char16_t* device = &(((char16_t*)devnames)[devnames->wDeviceOffset]);
298 char16_t* driver = &(((char16_t*)devnames)[devnames->wDriverOffset]);
300 // Check to see if the "Print To File" control is checked
301 // then take the name from devNames and set it in the PrintSettings
303 // NOTE:
304 // As per Microsoft SDK documentation the returned value offset from
305 // devnames->wOutputOffset is either "FILE:" or nullptr
306 // if the "Print To File" checkbox is checked it MUST be "FILE:"
307 // We assert as an extra safety check.
308 if (prntdlg.Flags & PD_PRINTTOFILE) {
309 char16ptr_t fileName = &(((wchar_t*)devnames)[devnames->wOutputOffset]);
310 NS_ASSERTION(wcscmp(fileName, L"FILE:") == 0, "FileName must be `FILE:`");
311 aPrintSettings->SetOutputDestination(
312 nsIPrintSettings::kOutputDestinationFile);
313 aPrintSettings->SetToFileName(nsDependentString(fileName));
314 } else {
315 // clear "print to file" info
316 aPrintSettings->SetOutputDestination(
317 nsIPrintSettings::kOutputDestinationPrinter);
318 aPrintSettings->SetToFileName(u""_ns);
321 nsCOMPtr<nsIPrintSettingsWin> psWin(do_QueryInterface(aPrintSettings));
322 MOZ_RELEASE_ASSERT(psWin);
324 // Setup local Data members
325 psWin->SetDeviceName(nsDependentString(device));
326 psWin->SetDriverName(nsDependentString(driver));
328 // Fill the print options with the info from the dialog
329 aPrintSettings->SetPrinterName(nsDependentString(device));
330 aPrintSettings->SetPrintSelectionOnly(prntdlg.Flags & PD_SELECTION);
332 AutoTArray<int32_t, kMinSupportedRanges * 2> pageRanges;
333 if (prntdlg.Flags & PD_PAGENUMS) {
334 pageRanges.SetCapacity(prntdlg.nPageRanges * 2);
335 for (const auto& range :
336 mozilla::Span(prntdlg.lpPageRanges, prntdlg.nPageRanges)) {
337 pageRanges.AppendElement(range.nFromPage);
338 pageRanges.AppendElement(range.nToPage);
341 aPrintSettings->SetPageRanges(pageRanges);
343 // Unlock DeviceNames
344 ::GlobalUnlock(prntdlg.hDevNames);
346 // Transfer the settings from the native data to the PrintSettings
347 LPDEVMODEW devMode = (LPDEVMODEW)::GlobalLock(prntdlg.hDevMode);
348 if (!devMode || !prntdlg.hDC) {
349 return NS_ERROR_FAILURE;
351 psWin->SetDevMode(devMode); // copies DevMode
352 psWin->CopyFromNative(prntdlg.hDC, devMode);
353 ::GlobalUnlock(prntdlg.hDevMode);
354 ::DeleteDC(prntdlg.hDC);
356 cancelOnExit.release();
357 return NS_OK;