Merge mozilla-central to autoland on a CLOSED TREE
[gecko.git] / widget / windows / nsPrinterWin.cpp
blob75676cd6131dd988aecb7c6511d3d0718fa6480e
1 /* -*- Mode: C++; tab-width: 4; 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 #include "nsPrinterWin.h"
8 #include <algorithm>
9 #include <windows.h>
10 #include <winspool.h>
12 #include "mozilla/Array.h"
13 #include "mozilla/dom/Promise.h"
14 #include "nsPaper.h"
15 #include "nsPrintSettingsImpl.h"
16 #include "nsPrintSettingsWin.h"
17 #include "nsWindowsHelpers.h"
18 #include "PrintBackgroundTask.h"
19 #include "WinUtils.h"
21 using namespace mozilla;
22 using namespace mozilla::gfx;
23 using namespace mozilla::widget;
24 using mozilla::PrintSettingsInitializer;
25 using mozilla::dom::Promise;
27 static const double kPointsPerTenthMM = 72.0 / 254.0;
28 static const double kPointsPerInch = 72.0;
30 nsPrinterWin::nsPrinterWin(const CommonPaperInfoArray* aArray,
31 const nsAString& aName)
32 : nsPrinterBase(aArray),
33 mName(aName),
34 mDefaultDevmodeWStorage("nsPrinterWin::mDefaultDevmodeWStorage") {}
36 // static
37 already_AddRefed<nsPrinterWin> nsPrinterWin::Create(
38 const CommonPaperInfoArray* aArray, const nsAString& aName) {
39 return do_AddRef(new nsPrinterWin(aArray, aName));
42 template <class T>
43 static nsTArray<T> GetDeviceCapabilityArray(const LPWSTR aPrinterName,
44 WORD aCapabilityID,
45 mozilla::Mutex& aDriverMutex,
46 int& aCount) {
47 MOZ_ASSERT(aCount >= 0, "Possibly passed aCount from previous error case.");
49 nsTArray<T> caps;
51 // We only want to access printer drivers in the parent process.
52 if (!XRE_IsParentProcess()) {
53 return caps;
56 // Both the call to get the size and the call to actually populate the array
57 // are relatively expensive, so as sometimes the lengths of the arrays that we
58 // retrieve depend on each other we allow a count to be passed in to save the
59 // first call. As we allocate double the count anyway this should allay any
60 // safety worries.
61 if (!aCount) {
62 // Passing nullptr as the port here seems to work. Given that we would have
63 // to OpenPrinter with just the name anyway to get the port that makes
64 // sense. Also, the printer set-up seems to stop you from having two
65 // printers with the same name. Note: this (and the call below) are blocking
66 // calls, which could be slow.
67 MutexAutoLock autoLock(aDriverMutex);
68 aCount = ::DeviceCapabilitiesW(aPrinterName, nullptr, aCapabilityID,
69 nullptr, nullptr);
70 if (aCount <= 0) {
71 return caps;
75 // As DeviceCapabilitiesW doesn't take a size, there is a greater risk of the
76 // buffer being overflowed, so we over-allocate for safety.
77 caps.SetLength(aCount * 2);
78 MutexAutoLock autoLock(aDriverMutex);
79 int count =
80 ::DeviceCapabilitiesW(aPrinterName, nullptr, aCapabilityID,
81 reinterpret_cast<LPWSTR>(caps.Elements()), nullptr);
82 if (count <= 0) {
83 caps.Clear();
84 return caps;
87 // We know from bug 1673708 that sometimes the final array returned is smaller
88 // than the required array count. Assert here to see if this is reproduced on
89 // test servers.
90 MOZ_ASSERT(count == aCount, "Different array count returned than expected.");
92 // Note that TruncateLength will crash if count > caps.Length().
93 caps.TruncateLength(count);
94 return caps;
97 static void DevmodeToSettingsInitializer(
98 const nsString& aPrinterName, const DEVMODEW* aDevmode,
99 mozilla::Mutex& aDriverMutex,
100 PrintSettingsInitializer& aSettingsInitializer) {
101 aSettingsInitializer.mPrinter.Assign(aPrinterName);
103 HDC dc;
105 MutexAutoLock autoLock(aDriverMutex);
106 dc = ::CreateICW(nullptr, aPrinterName.get(), nullptr, aDevmode);
108 nsAutoHDC printerDc(dc);
109 MOZ_ASSERT(printerDc, "CreateICW failed");
110 if (!printerDc) {
111 return;
114 if (aDevmode->dmFields & DM_PAPERSIZE) {
115 aSettingsInitializer.mPaperInfo.mId.Truncate();
116 aSettingsInitializer.mPaperInfo.mId.AppendInt(aDevmode->dmPaperSize);
117 // If it is not a paper size we know about, the unit will remain unchanged.
118 nsPrintSettingsWin::PaperSizeUnitFromDmPaperSize(
119 aDevmode->dmPaperSize, aSettingsInitializer.mPaperSizeUnit);
122 int pixelsPerInchY = ::GetDeviceCaps(printerDc, LOGPIXELSY);
123 int physicalHeight = ::GetDeviceCaps(printerDc, PHYSICALHEIGHT);
124 double heightInInches = double(physicalHeight) / pixelsPerInchY;
125 int pixelsPerInchX = ::GetDeviceCaps(printerDc, LOGPIXELSX);
126 int physicalWidth = ::GetDeviceCaps(printerDc, PHYSICALWIDTH);
127 double widthInches = double(physicalWidth) / pixelsPerInchX;
128 if (aDevmode->dmFields & DM_ORIENTATION &&
129 aDevmode->dmOrientation == DMORIENT_LANDSCAPE) {
130 std::swap(widthInches, heightInInches);
132 aSettingsInitializer.mPaperInfo.mSize.SizeTo(widthInches * kPointsPerInch,
133 heightInInches * kPointsPerInch);
135 gfx::MarginDouble margin =
136 WinUtils::GetUnwriteableMarginsForDeviceInInches(printerDc);
137 aSettingsInitializer.mPaperInfo.mUnwriteableMargin = Some(MarginDouble{
138 margin.top * kPointsPerInch, margin.right * kPointsPerInch,
139 margin.bottom * kPointsPerInch, margin.left * kPointsPerInch});
141 // Using Y to match existing code for print scaling calculations.
142 aSettingsInitializer.mResolution = pixelsPerInchY;
144 if (aDevmode->dmFields & DM_COLOR) {
145 // See comment for PrintSettingsInitializer.mPrintInColor
146 aSettingsInitializer.mPrintInColor =
147 aDevmode->dmColor != DMCOLOR_MONOCHROME;
150 if (aDevmode->dmFields & DM_ORIENTATION) {
151 aSettingsInitializer.mSheetOrientation =
152 int32_t(aDevmode->dmOrientation == DMORIENT_PORTRAIT
153 ? nsPrintSettings::kPortraitOrientation
154 : nsPrintSettings::kLandscapeOrientation);
157 if (aDevmode->dmFields & DM_COPIES) {
158 aSettingsInitializer.mNumCopies = aDevmode->dmCopies;
161 if (aDevmode->dmFields & DM_DUPLEX) {
162 switch (aDevmode->dmDuplex) {
163 default:
164 MOZ_FALLTHROUGH_ASSERT("bad value for dmDuplex field");
165 case DMDUP_SIMPLEX:
166 aSettingsInitializer.mDuplex = nsPrintSettings::kDuplexNone;
167 break;
168 case DMDUP_VERTICAL:
169 aSettingsInitializer.mDuplex = nsPrintSettings::kDuplexFlipOnLongEdge;
170 break;
171 case DMDUP_HORIZONTAL:
172 aSettingsInitializer.mDuplex = nsPrintSettings::kDuplexFlipOnShortEdge;
173 break;
178 NS_IMETHODIMP
179 nsPrinterWin::GetName(nsAString& aName) {
180 aName.Assign(mName);
181 return NS_OK;
184 NS_IMETHODIMP
185 nsPrinterWin::GetSystemName(nsAString& aName) {
186 aName.Assign(mName);
187 return NS_OK;
190 namespace mozilla {
191 template <>
192 void ResolveOrReject(Promise& aPromise, nsPrinterWin& aPrinter,
193 const PrintSettingsInitializer& aResult) {
194 aPromise.MaybeResolve(
195 RefPtr<nsIPrintSettings>(CreatePlatformPrintSettings(aResult)));
197 } // namespace mozilla
199 NS_IMETHODIMP nsPrinterWin::CopyFromWithValidation(
200 nsIPrintSettings* aSettingsToCopyFrom, JSContext* aCx,
201 Promise** aResultPromise) {
202 MOZ_ASSERT(NS_IsMainThread());
203 MOZ_ASSERT(aResultPromise);
205 PrintSettingsInitializer settingsInitializer =
206 aSettingsToCopyFrom->GetSettingsInitializer();
207 return PrintBackgroundTaskPromise(
208 *this, aCx, aResultPromise, "CopyFromWithValidation"_ns,
209 &nsPrinterWin::GetValidatedSettings, settingsInitializer);
212 bool nsPrinterWin::SupportsDuplex() const {
213 MutexAutoLock autoLock(mDriverMutex);
214 return ::DeviceCapabilitiesW(mName.get(), nullptr, DC_DUPLEX, nullptr,
215 nullptr) == 1;
218 bool nsPrinterWin::SupportsColor() const {
219 MutexAutoLock autoLock(mDriverMutex);
220 return ::DeviceCapabilitiesW(mName.get(), nullptr, DC_COLORDEVICE, nullptr,
221 nullptr) == 1;
224 bool nsPrinterWin::SupportsMonochrome() const {
225 if (!SupportsColor()) {
226 return true;
229 nsHPRINTER hPrinter = nullptr;
230 if (NS_WARN_IF(!::OpenPrinterW(mName.get(), &hPrinter, nullptr))) {
231 return false;
233 nsAutoPrinter autoPrinter(hPrinter);
235 nsTArray<uint8_t> devmodeWStorage = CopyDefaultDevmodeW();
236 if (devmodeWStorage.IsEmpty()) {
237 return false;
240 auto* devmode = reinterpret_cast<DEVMODEW*>(devmodeWStorage.Elements());
242 devmode->dmFields |= DM_COLOR;
243 devmode->dmColor = DMCOLOR_MONOCHROME;
244 // Try to modify the devmode settings and see if the setting sticks.
246 // This has been the only reliable way to detect it that we've found.
247 MutexAutoLock autoLock(mDriverMutex);
248 LONG ret =
249 ::DocumentPropertiesW(nullptr, autoPrinter.get(), mName.get(), devmode,
250 devmode, DM_IN_BUFFER | DM_OUT_BUFFER);
251 if (ret != IDOK) {
252 return false;
254 return !(devmode->dmFields & DM_COLOR) ||
255 devmode->dmColor == DMCOLOR_MONOCHROME;
258 bool nsPrinterWin::SupportsCollation() const {
259 MutexAutoLock autoLock(mDriverMutex);
260 return ::DeviceCapabilitiesW(mName.get(), nullptr, DC_COLLATE, nullptr,
261 nullptr) == 1;
264 nsPrinterBase::PrinterInfo nsPrinterWin::CreatePrinterInfo() const {
265 return PrinterInfo{PaperList(), DefaultSettings()};
268 mozilla::gfx::MarginDouble nsPrinterWin::GetMarginsForPaper(
269 nsString aPaperId) const {
270 gfx::MarginDouble margin;
272 nsTArray<uint8_t> devmodeWStorage = CopyDefaultDevmodeW();
273 if (devmodeWStorage.IsEmpty()) {
274 return margin;
277 auto* devmode = reinterpret_cast<DEVMODEW*>(devmodeWStorage.Elements());
279 devmode->dmFields = DM_PAPERSIZE;
280 devmode->dmPaperSize = _wtoi((const wchar_t*)aPaperId.BeginReading());
281 HDC dc;
283 MutexAutoLock autoLock(mDriverMutex);
284 dc = ::CreateICW(nullptr, mName.get(), nullptr, devmode);
286 nsAutoHDC printerDc(dc);
287 MOZ_ASSERT(printerDc, "CreateICW failed");
288 if (!printerDc) {
289 return margin;
291 margin = WinUtils::GetUnwriteableMarginsForDeviceInInches(printerDc);
292 margin.top *= kPointsPerInch;
293 margin.right *= kPointsPerInch;
294 margin.bottom *= kPointsPerInch;
295 margin.left *= kPointsPerInch;
297 return margin;
300 nsTArray<uint8_t> nsPrinterWin::CopyDefaultDevmodeW() const {
301 nsTArray<uint8_t> devmodeStorageW;
303 auto devmodeStorageWLock = mDefaultDevmodeWStorage.Lock();
304 if (devmodeStorageWLock->IsEmpty()) {
305 nsHPRINTER hPrinter = nullptr;
306 // OpenPrinter could fail if, for example, the printer has been removed
307 // or otherwise become inaccessible since it was selected.
308 if (NS_WARN_IF(!::OpenPrinterW(mName.get(), &hPrinter, nullptr))) {
309 return devmodeStorageW;
311 nsAutoPrinter autoPrinter(hPrinter);
312 // Allocate devmode storage of the correct size.
313 MutexAutoLock autoLock(mDriverMutex);
314 LONG bytesNeeded = ::DocumentPropertiesW(nullptr, autoPrinter.get(),
315 mName.get(), nullptr, nullptr, 0);
316 // Note that we must cast the sizeof() to a signed type so that comparison
317 // with the signed, potentially-negative bytesNeeded will work!
318 MOZ_ASSERT(bytesNeeded >= LONG(sizeof(DEVMODEW)),
319 "DocumentPropertiesW failed to get valid size");
320 if (bytesNeeded < LONG(sizeof(DEVMODEW))) {
321 return devmodeStorageW;
324 // Allocate extra space in case of bad drivers that return a too-small
325 // result from DocumentProperties.
326 // (See https://bugzilla.mozilla.org/show_bug.cgi?id=1664530#c5)
327 if (!devmodeStorageWLock->SetLength(bytesNeeded * 2, fallible)) {
328 return devmodeStorageW;
331 memset(devmodeStorageWLock->Elements(), 0, devmodeStorageWLock->Length());
332 auto* devmode =
333 reinterpret_cast<DEVMODEW*>(devmodeStorageWLock->Elements());
334 LONG ret = ::DocumentPropertiesW(nullptr, autoPrinter.get(), mName.get(),
335 devmode, nullptr, DM_OUT_BUFFER);
336 MOZ_ASSERT(ret == IDOK, "DocumentPropertiesW failed");
337 // Make sure that the lengths in the DEVMODEW make sense.
338 if (ret != IDOK || devmode->dmSize != sizeof(DEVMODEW) ||
339 devmode->dmSize + devmode->dmDriverExtra >
340 devmodeStorageWLock->Length()) {
341 // Clear mDefaultDevmodeWStorage to make sure we try again next time.
342 devmodeStorageWLock->Clear();
343 return devmodeStorageW;
347 devmodeStorageW.Assign(devmodeStorageWLock.ref());
348 return devmodeStorageW;
351 nsTArray<mozilla::PaperInfo> nsPrinterWin::PaperList() const {
352 // Paper IDs are returned as WORDs.
353 int requiredArrayCount = 0;
354 auto paperIds = GetDeviceCapabilityArray<WORD>(
355 mName.get(), DC_PAPERS, mDriverMutex, requiredArrayCount);
356 if (!paperIds.Length()) {
357 return {};
360 // Paper names are returned in 64 long character buffers.
361 auto paperNames = GetDeviceCapabilityArray<Array<wchar_t, 64>>(
362 mName.get(), DC_PAPERNAMES, mDriverMutex, requiredArrayCount);
363 // Check that we have the same number of names as IDs.
364 if (paperNames.Length() != paperIds.Length()) {
365 return {};
368 // Paper sizes are returned as POINT structs with a tenth of a millimeter as
369 // the unit.
370 auto paperSizes = GetDeviceCapabilityArray<POINT>(
371 mName.get(), DC_PAPERSIZE, mDriverMutex, requiredArrayCount);
372 // Check that we have the same number of sizes as IDs.
373 if (paperSizes.Length() != paperIds.Length()) {
374 return {};
377 nsTArray<mozilla::PaperInfo> paperList;
378 paperList.SetCapacity(paperNames.Length());
379 for (size_t i = 0; i < paperNames.Length(); ++i) {
380 // Paper names are null terminated unless they are 64 characters long.
381 auto firstNull =
382 std::find(paperNames[i].cbegin(), paperNames[i].cend(), L'\0');
383 auto nameLength = firstNull - paperNames[i].cbegin();
384 double width = paperSizes[i].x * kPointsPerTenthMM;
385 double height = paperSizes[i].y * kPointsPerTenthMM;
387 // Skip if no name or invalid size.
388 if (!nameLength || width <= 0 || height <= 0) {
389 continue;
392 // Windows paper IDs are 16-bit integers; we stringify them to store in the
393 // PaperInfo.mId field.
394 nsString paperIdString;
395 paperIdString.AppendInt(paperIds[i]);
397 // We don't resolve the margins eagerly because they're really expensive (on
398 // the order of seconds for some drivers).
399 nsDependentSubstring name(paperNames[i].cbegin(), nameLength);
400 paperList.AppendElement(mozilla::PaperInfo(paperIdString, nsString(name),
401 {width, height}, Nothing()));
404 return paperList;
407 PrintSettingsInitializer nsPrinterWin::DefaultSettings() const {
408 nsTArray<uint8_t> devmodeWStorage = CopyDefaultDevmodeW();
409 if (devmodeWStorage.IsEmpty()) {
410 return {};
413 const auto* devmode =
414 reinterpret_cast<const DEVMODEW*>(devmodeWStorage.Elements());
416 PrintSettingsInitializer settingsInitializer;
417 DevmodeToSettingsInitializer(mName, devmode, mDriverMutex,
418 settingsInitializer);
419 settingsInitializer.mDevmodeWStorage = std::move(devmodeWStorage);
420 return settingsInitializer;
423 PrintSettingsInitializer nsPrinterWin::GetValidatedSettings(
424 PrintSettingsInitializer aSettingsToValidate) const {
425 // This function validates the settings by relying on the printer driver
426 // rejecting any invalid settings and resetting them to valid values.
428 // Create a copy of the default DEVMODE for this printer.
429 nsTArray<uint8_t> devmodeWStorage = CopyDefaultDevmodeW();
430 if (devmodeWStorage.IsEmpty()) {
431 return aSettingsToValidate;
434 nsHPRINTER hPrinter = nullptr;
435 if (NS_WARN_IF(!::OpenPrinterW(mName.get(), &hPrinter, nullptr))) {
436 return aSettingsToValidate;
438 nsAutoPrinter autoPrinter(hPrinter);
440 // Copy the settings from aSettingsToValidate into our DEVMODE.
441 DEVMODEW* devmode = reinterpret_cast<DEVMODEW*>(devmodeWStorage.Elements());
442 if (!aSettingsToValidate.mPaperInfo.mId.IsEmpty()) {
443 devmode->dmPaperSize = _wtoi(
444 (const wchar_t*)aSettingsToValidate.mPaperInfo.mId.BeginReading());
445 devmode->dmFields |= DM_PAPERSIZE;
446 } else {
447 devmode->dmPaperSize = 0;
448 devmode->dmFields &= ~DM_PAPERSIZE;
451 devmode->dmFields |= DM_COLOR;
452 devmode->dmColor =
453 aSettingsToValidate.mPrintInColor ? DMCOLOR_COLOR : DMCOLOR_MONOCHROME;
455 // Note: small page sizes can be required here for sticker, label and slide
456 // printers etc. see bug 1271900.
457 if (aSettingsToValidate.mPaperInfo.mSize.height > 0) {
458 devmode->dmPaperLength = std::round(
459 aSettingsToValidate.mPaperInfo.mSize.height / kPointsPerTenthMM);
460 devmode->dmFields |= DM_PAPERLENGTH;
461 } else {
462 devmode->dmPaperLength = 0;
463 devmode->dmFields &= ~DM_PAPERLENGTH;
466 if (aSettingsToValidate.mPaperInfo.mSize.width > 0) {
467 devmode->dmPaperWidth = std::round(
468 aSettingsToValidate.mPaperInfo.mSize.width / kPointsPerTenthMM);
469 devmode->dmFields |= DM_PAPERWIDTH;
470 } else {
471 devmode->dmPaperWidth = 0;
472 devmode->dmFields &= ~DM_PAPERWIDTH;
475 // Setup Orientation
476 devmode->dmOrientation = aSettingsToValidate.mSheetOrientation ==
477 nsPrintSettings::kPortraitOrientation
478 ? DMORIENT_PORTRAIT
479 : DMORIENT_LANDSCAPE;
480 devmode->dmFields |= DM_ORIENTATION;
482 // Setup Number of Copies
483 devmode->dmCopies = aSettingsToValidate.mNumCopies;
484 devmode->dmFields |= DM_COPIES;
486 // Setup Simplex/Duplex mode
487 devmode->dmFields |= DM_DUPLEX;
488 switch (aSettingsToValidate.mDuplex) {
489 case nsPrintSettings::kDuplexNone:
490 devmode->dmDuplex = DMDUP_SIMPLEX;
491 break;
492 case nsPrintSettings::kDuplexFlipOnLongEdge:
493 devmode->dmDuplex = DMDUP_VERTICAL;
494 break;
495 case nsPrintSettings::kDuplexFlipOnShortEdge:
496 devmode->dmDuplex = DMDUP_HORIZONTAL;
497 break;
498 default:
499 MOZ_ASSERT_UNREACHABLE("bad value for duplex option");
500 break;
503 // Apply the settings in the DEVMODE to the printer and retrieve the updated
504 // DEVMODE back into the same structure.
505 LONG ret;
507 MutexAutoLock autoLock(mDriverMutex);
508 ret = ::DocumentPropertiesW(nullptr, autoPrinter.get(), mName.get(),
509 devmode, devmode, DM_IN_BUFFER | DM_OUT_BUFFER);
511 if (ret != IDOK) {
512 return aSettingsToValidate;
515 // Copy the settings back from the DEVMODE into aSettingsToValidate and
516 // return.
517 DevmodeToSettingsInitializer(mName, devmode, mDriverMutex,
518 aSettingsToValidate);
519 aSettingsToValidate.mDevmodeWStorage = std::move(devmodeWStorage);
520 return aSettingsToValidate;