Bug 1758713 [wpt PR 33128] - Clarify the status of the CSS build system, a=testonly
[gecko.git] / widget / windows / nsPrintDialogUtil.cpp
blob6038aa73339161fb0b291f6d9c83ff39637f55c7
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 "plstr.h"
24 #include <windows.h>
25 #include <tchar.h>
27 #include <unknwn.h>
28 #include <commdlg.h>
30 #include "mozilla/BackgroundHangMonitor.h"
31 #include "mozilla/ScopeExit.h"
32 #include "mozilla/Span.h"
33 #include "nsString.h"
34 #include "nsReadableUtils.h"
35 #include "nsIPrintSettings.h"
36 #include "nsIPrintSettingsWin.h"
37 #include "nsIPrinterList.h"
38 #include "nsServiceManagerUtils.h"
40 #include "nsRect.h"
42 #include "nsCRT.h"
43 #include "prenv.h" /* for PR_GetEnv */
45 #include <windows.h>
46 #include <winspool.h>
48 // For Localization
50 // For NS_CopyUnicodeToNative
51 #include "nsNativeCharsetUtils.h"
53 // This is for extending the dialog
54 #include <dlgs.h>
56 #include "nsWindowsHelpers.h"
57 #include "WinUtils.h"
59 //-----------------------------------------------
60 // Global Data
61 //-----------------------------------------------
63 static HWND gParentWnd = nullptr;
65 //----------------------------------------------------------------------------------
66 // Returns a Global Moveable Memory Handle to a DevMode
67 // from the Printer by the name of aPrintName
69 // NOTE:
70 // This function assumes that aPrintName has already been converted from
71 // unicode
73 static nsReturnRef<nsHGLOBAL> CreateGlobalDevModeAndInit(
74 const nsString& aPrintName, nsIPrintSettings* aPS) {
75 nsHPRINTER hPrinter = nullptr;
76 // const cast kludge for silly Win32 api's
77 LPWSTR printName =
78 const_cast<wchar_t*>(static_cast<const wchar_t*>(aPrintName.get()));
79 BOOL status = ::OpenPrinterW(printName, &hPrinter, nullptr);
80 if (!status) {
81 return nsReturnRef<nsHGLOBAL>();
84 // Make sure hPrinter is closed on all paths
85 nsAutoPrinter autoPrinter(hPrinter);
87 // Get the buffer size
88 LONG needed = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, nullptr,
89 nullptr, 0);
90 if (needed < 0) {
91 return nsReturnRef<nsHGLOBAL>();
94 // Some drivers do not return the correct size for their DEVMODE, so we
95 // over-allocate to try and compensate.
96 // (See https://bugzilla.mozilla.org/show_bug.cgi?id=1664530#c5)
97 needed *= 2;
98 nsAutoDevMode newDevMode(
99 (LPDEVMODEW)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, needed));
100 if (!newDevMode) {
101 return nsReturnRef<nsHGLOBAL>();
104 nsHGLOBAL hDevMode = ::GlobalAlloc(GHND, needed);
105 nsAutoGlobalMem globalDevMode(hDevMode);
106 if (!hDevMode) {
107 return nsReturnRef<nsHGLOBAL>();
110 LONG ret = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, newDevMode,
111 nullptr, DM_OUT_BUFFER);
112 if (ret != IDOK) {
113 return nsReturnRef<nsHGLOBAL>();
116 // Lock memory and copy contents from DEVMODE (current printer)
117 // to Global Memory DEVMODE
118 LPDEVMODEW devMode = (DEVMODEW*)::GlobalLock(hDevMode);
119 if (!devMode) {
120 return nsReturnRef<nsHGLOBAL>();
123 memcpy(devMode, newDevMode.get(), needed);
124 // Initialize values from the PrintSettings
125 nsCOMPtr<nsIPrintSettingsWin> psWin = do_QueryInterface(aPS);
126 MOZ_ASSERT(psWin);
127 psWin->CopyToNative(devMode);
129 // Sets back the changes we made to the DevMode into the Printer Driver
130 ret = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, devMode, devMode,
131 DM_IN_BUFFER | DM_OUT_BUFFER);
132 if (ret != IDOK) {
133 ::GlobalUnlock(hDevMode);
134 return nsReturnRef<nsHGLOBAL>();
137 ::GlobalUnlock(hDevMode);
139 return globalDevMode.out();
142 //------------------------------------------------------------------
143 // helper
144 static void GetDefaultPrinterNameFromGlobalPrinters(nsAString& aPrinterName) {
145 aPrinterName.Truncate();
146 nsCOMPtr<nsIPrinterList> printerList =
147 do_GetService("@mozilla.org/gfx/printerlist;1");
148 if (printerList) {
149 printerList->GetSystemDefaultPrinterName(aPrinterName);
153 //------------------------------------------------------------------
154 // Displays the native Print Dialog
155 static nsresult ShowNativePrintDialog(HWND aHWnd,
156 nsIPrintSettings* aPrintSettings) {
157 // NS_ENSURE_ARG_POINTER(aHWnd);
158 NS_ENSURE_ARG_POINTER(aPrintSettings);
160 // Get the Print Name to be used
161 nsString printerName;
162 aPrintSettings->GetPrinterName(printerName);
164 // If there is no name then use the default printer
165 if (printerName.IsEmpty()) {
166 GetDefaultPrinterNameFromGlobalPrinters(printerName);
167 } else {
168 HANDLE hPrinter = nullptr;
169 if (!::OpenPrinterW(const_cast<wchar_t*>(
170 static_cast<const wchar_t*>(printerName.get())),
171 &hPrinter, nullptr)) {
172 // If the last used printer is not found, we should use default printer.
173 GetDefaultPrinterNameFromGlobalPrinters(printerName);
174 } else {
175 ::ClosePrinter(hPrinter);
179 // Now create a DEVNAMES struct so the the dialog is initialized correctly.
181 uint32_t len = printerName.Length();
182 nsHGLOBAL hDevNames =
183 ::GlobalAlloc(GHND, sizeof(wchar_t) * (len + 1) + sizeof(DEVNAMES));
184 nsAutoGlobalMem autoDevNames(hDevNames);
185 if (!hDevNames) {
186 return NS_ERROR_OUT_OF_MEMORY;
189 DEVNAMES* pDevNames = (DEVNAMES*)::GlobalLock(hDevNames);
190 if (!pDevNames) {
191 return NS_ERROR_FAILURE;
193 pDevNames->wDriverOffset = sizeof(DEVNAMES) / sizeof(wchar_t);
194 pDevNames->wDeviceOffset = sizeof(DEVNAMES) / sizeof(wchar_t);
195 pDevNames->wOutputOffset = sizeof(DEVNAMES) / sizeof(wchar_t) + len;
196 pDevNames->wDefault = 0;
198 memcpy(pDevNames + 1, printerName.get(), (len + 1) * sizeof(wchar_t));
199 ::GlobalUnlock(hDevNames);
201 // Create a Moveable Memory Object that holds a new DevMode
202 // from the Printer Name
203 // The PRINTDLG.hDevMode requires that it be a moveable memory object
204 // NOTE: autoDevMode is automatically freed when any error occurred
205 nsAutoGlobalMem autoDevMode(
206 CreateGlobalDevModeAndInit(printerName, aPrintSettings));
208 // Prepare to Display the Print Dialog
209 // https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms646942(v=vs.85)
210 // https://docs.microsoft.com/en-us/windows/win32/api/commdlg/ns-commdlg-printdlgexw
211 PRINTDLGEXW prntdlg;
212 memset(&prntdlg, 0, sizeof(prntdlg));
214 prntdlg.lStructSize = sizeof(prntdlg);
215 prntdlg.hwndOwner = aHWnd;
216 prntdlg.hDevMode = autoDevMode.get();
217 prntdlg.hDevNames = hDevNames;
218 prntdlg.hDC = nullptr;
219 prntdlg.Flags = PD_ALLPAGES | PD_RETURNIC | PD_USEDEVMODECOPIESANDCOLLATE |
220 PD_COLLATE | PD_NOCURRENTPAGE;
222 // If there is a current selection then enable the "Selection" radio button
223 if (!aPrintSettings->GetIsPrintSelectionRBEnabled()) {
224 prntdlg.Flags |= PD_NOSELECTION;
227 // 10 seems like a reasonable max number of ranges to support by default if
228 // the user doesn't choose a greater thing in the UI.
229 constexpr size_t kMinSupportedRanges = 10;
231 AutoTArray<PRINTPAGERANGE, kMinSupportedRanges> winPageRanges;
232 // Set up the page ranges.
234 AutoTArray<int32_t, kMinSupportedRanges * 2> pageRanges;
235 aPrintSettings->GetPageRanges(pageRanges);
236 // If there is a specified page range then enable the "Custom" radio button
237 if (!pageRanges.IsEmpty()) {
238 prntdlg.Flags |= PD_PAGENUMS;
241 const size_t specifiedRanges = pageRanges.Length() / 2;
242 const size_t maxRanges = std::max(kMinSupportedRanges, specifiedRanges);
244 prntdlg.nMaxPageRanges = maxRanges;
245 prntdlg.nPageRanges = specifiedRanges;
247 winPageRanges.SetCapacity(maxRanges);
248 for (size_t i = 0; i < pageRanges.Length(); i += 2) {
249 PRINTPAGERANGE* range = winPageRanges.AppendElement();
250 range->nFromPage = pageRanges[i];
251 range->nToPage = pageRanges[i + 1];
253 prntdlg.lpPageRanges = winPageRanges.Elements();
255 prntdlg.nMinPage = 1;
256 // TODO(emilio): Could probably get the right page number here from the
257 // new print UI.
258 prntdlg.nMaxPage = 0xFFFF;
261 // NOTE(emilio): This can always be 1 because we use the DEVMODE copies
262 // feature (see PD_USEDEVMODECOPIESANDCOLLATE).
263 prntdlg.nCopies = 1;
265 prntdlg.hInstance = nullptr;
266 prntdlg.lpPrintTemplateName = nullptr;
268 prntdlg.lpCallback = nullptr;
269 prntdlg.nPropertyPages = 0;
270 prntdlg.lphPropertyPages = nullptr;
272 prntdlg.nStartPage = START_PAGE_GENERAL;
273 prntdlg.dwResultAction = 0;
275 HRESULT result;
277 mozilla::widget::WinUtils::AutoSystemDpiAware dpiAwareness;
278 mozilla::BackgroundHangMonitor().NotifyWait();
279 result = ::PrintDlgExW(&prntdlg);
282 auto cancelOnExit = mozilla::MakeScopeExit([&] {
283 ::SetFocus(aHWnd);
284 aPrintSettings->SetIsCancelled(true);
287 if (NS_WARN_IF(!SUCCEEDED(result))) {
288 #ifdef DEBUG
289 printf_stderr("PrintDlgExW failed with %x\n", result);
290 #endif
291 return NS_ERROR_FAILURE;
293 if (NS_WARN_IF(prntdlg.dwResultAction != PD_RESULT_PRINT)) {
294 return NS_ERROR_ABORT;
296 // check to make sure we don't have any nullptr pointers
297 NS_ENSURE_TRUE(prntdlg.hDevMode, NS_ERROR_ABORT);
298 NS_ENSURE_TRUE(prntdlg.hDevNames, NS_ERROR_ABORT);
299 // Lock the deviceNames and check for nullptr
300 DEVNAMES* devnames = (DEVNAMES*)::GlobalLock(prntdlg.hDevNames);
301 NS_ENSURE_TRUE(devnames, NS_ERROR_ABORT);
303 char16_t* device = &(((char16_t*)devnames)[devnames->wDeviceOffset]);
304 char16_t* driver = &(((char16_t*)devnames)[devnames->wDriverOffset]);
306 // Check to see if the "Print To File" control is checked
307 // then take the name from devNames and set it in the PrintSettings
309 // NOTE:
310 // As per Microsoft SDK documentation the returned value offset from
311 // devnames->wOutputOffset is either "FILE:" or nullptr
312 // if the "Print To File" checkbox is checked it MUST be "FILE:"
313 // We assert as an extra safety check.
314 if (prntdlg.Flags & PD_PRINTTOFILE) {
315 char16ptr_t fileName = &(((wchar_t*)devnames)[devnames->wOutputOffset]);
316 NS_ASSERTION(wcscmp(fileName, L"FILE:") == 0, "FileName must be `FILE:`");
317 aPrintSettings->SetToFileName(nsDependentString(fileName));
318 aPrintSettings->SetPrintToFile(true);
319 } else {
320 // clear "print to file" info
321 aPrintSettings->SetPrintToFile(false);
322 aPrintSettings->SetToFileName(u""_ns);
325 nsCOMPtr<nsIPrintSettingsWin> psWin(do_QueryInterface(aPrintSettings));
326 MOZ_RELEASE_ASSERT(psWin);
328 // Setup local Data members
329 psWin->SetDeviceName(nsDependentString(device));
330 psWin->SetDriverName(nsDependentString(driver));
332 // Fill the print options with the info from the dialog
333 aPrintSettings->SetPrinterName(nsDependentString(device));
334 aPrintSettings->SetPrintSelectionOnly(prntdlg.Flags & PD_SELECTION);
336 AutoTArray<int32_t, kMinSupportedRanges * 2> pageRanges;
337 if (prntdlg.Flags & PD_PAGENUMS) {
338 pageRanges.SetCapacity(prntdlg.nPageRanges * 2);
339 for (const auto& range :
340 mozilla::Span(prntdlg.lpPageRanges, prntdlg.nPageRanges)) {
341 pageRanges.AppendElement(range.nFromPage);
342 pageRanges.AppendElement(range.nToPage);
345 aPrintSettings->SetPageRanges(pageRanges);
347 // Unlock DeviceNames
348 ::GlobalUnlock(prntdlg.hDevNames);
350 // Transfer the settings from the native data to the PrintSettings
351 LPDEVMODEW devMode = (LPDEVMODEW)::GlobalLock(prntdlg.hDevMode);
352 if (!devMode || !prntdlg.hDC) {
353 return NS_ERROR_FAILURE;
355 psWin->SetDevMode(devMode); // copies DevMode
356 psWin->CopyFromNative(prntdlg.hDC, devMode);
357 ::GlobalUnlock(prntdlg.hDevMode);
358 ::DeleteDC(prntdlg.hDC);
360 cancelOnExit.release();
361 return NS_OK;
364 //----------------------------------------------------------------------------------
365 //-- Show Print Dialog
366 //----------------------------------------------------------------------------------
367 nsresult NativeShowPrintDialog(HWND aHWnd, nsIPrintSettings* aPrintSettings) {
368 nsresult rv = ShowNativePrintDialog(aHWnd, aPrintSettings);
369 if (aHWnd) {
370 ::DestroyWindow(aHWnd);
373 return rv;