diff options
Diffstat (limited to '')
-rw-r--r-- | widget/windows/nsPrinterWin.cpp | 521 |
1 files changed, 521 insertions, 0 deletions
diff --git a/widget/windows/nsPrinterWin.cpp b/widget/windows/nsPrinterWin.cpp new file mode 100644 index 0000000000..75676cd613 --- /dev/null +++ b/widget/windows/nsPrinterWin.cpp @@ -0,0 +1,521 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsPrinterWin.h" + +#include <algorithm> +#include <windows.h> +#include <winspool.h> + +#include "mozilla/Array.h" +#include "mozilla/dom/Promise.h" +#include "nsPaper.h" +#include "nsPrintSettingsImpl.h" +#include "nsPrintSettingsWin.h" +#include "nsWindowsHelpers.h" +#include "PrintBackgroundTask.h" +#include "WinUtils.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::widget; +using mozilla::PrintSettingsInitializer; +using mozilla::dom::Promise; + +static const double kPointsPerTenthMM = 72.0 / 254.0; +static const double kPointsPerInch = 72.0; + +nsPrinterWin::nsPrinterWin(const CommonPaperInfoArray* aArray, + const nsAString& aName) + : nsPrinterBase(aArray), + mName(aName), + mDefaultDevmodeWStorage("nsPrinterWin::mDefaultDevmodeWStorage") {} + +// static +already_AddRefed<nsPrinterWin> nsPrinterWin::Create( + const CommonPaperInfoArray* aArray, const nsAString& aName) { + return do_AddRef(new nsPrinterWin(aArray, aName)); +} + +template <class T> +static nsTArray<T> GetDeviceCapabilityArray(const LPWSTR aPrinterName, + WORD aCapabilityID, + mozilla::Mutex& aDriverMutex, + int& aCount) { + MOZ_ASSERT(aCount >= 0, "Possibly passed aCount from previous error case."); + + nsTArray<T> caps; + + // We only want to access printer drivers in the parent process. + if (!XRE_IsParentProcess()) { + return caps; + } + + // Both the call to get the size and the call to actually populate the array + // are relatively expensive, so as sometimes the lengths of the arrays that we + // retrieve depend on each other we allow a count to be passed in to save the + // first call. As we allocate double the count anyway this should allay any + // safety worries. + if (!aCount) { + // Passing nullptr as the port here seems to work. Given that we would have + // to OpenPrinter with just the name anyway to get the port that makes + // sense. Also, the printer set-up seems to stop you from having two + // printers with the same name. Note: this (and the call below) are blocking + // calls, which could be slow. + MutexAutoLock autoLock(aDriverMutex); + aCount = ::DeviceCapabilitiesW(aPrinterName, nullptr, aCapabilityID, + nullptr, nullptr); + if (aCount <= 0) { + return caps; + } + } + + // As DeviceCapabilitiesW doesn't take a size, there is a greater risk of the + // buffer being overflowed, so we over-allocate for safety. + caps.SetLength(aCount * 2); + MutexAutoLock autoLock(aDriverMutex); + int count = + ::DeviceCapabilitiesW(aPrinterName, nullptr, aCapabilityID, + reinterpret_cast<LPWSTR>(caps.Elements()), nullptr); + if (count <= 0) { + caps.Clear(); + return caps; + } + + // We know from bug 1673708 that sometimes the final array returned is smaller + // than the required array count. Assert here to see if this is reproduced on + // test servers. + MOZ_ASSERT(count == aCount, "Different array count returned than expected."); + + // Note that TruncateLength will crash if count > caps.Length(). + caps.TruncateLength(count); + return caps; +} + +static void DevmodeToSettingsInitializer( + const nsString& aPrinterName, const DEVMODEW* aDevmode, + mozilla::Mutex& aDriverMutex, + PrintSettingsInitializer& aSettingsInitializer) { + aSettingsInitializer.mPrinter.Assign(aPrinterName); + + HDC dc; + { + MutexAutoLock autoLock(aDriverMutex); + dc = ::CreateICW(nullptr, aPrinterName.get(), nullptr, aDevmode); + } + nsAutoHDC printerDc(dc); + MOZ_ASSERT(printerDc, "CreateICW failed"); + if (!printerDc) { + return; + } + + if (aDevmode->dmFields & DM_PAPERSIZE) { + aSettingsInitializer.mPaperInfo.mId.Truncate(); + aSettingsInitializer.mPaperInfo.mId.AppendInt(aDevmode->dmPaperSize); + // If it is not a paper size we know about, the unit will remain unchanged. + nsPrintSettingsWin::PaperSizeUnitFromDmPaperSize( + aDevmode->dmPaperSize, aSettingsInitializer.mPaperSizeUnit); + } + + int pixelsPerInchY = ::GetDeviceCaps(printerDc, LOGPIXELSY); + int physicalHeight = ::GetDeviceCaps(printerDc, PHYSICALHEIGHT); + double heightInInches = double(physicalHeight) / pixelsPerInchY; + int pixelsPerInchX = ::GetDeviceCaps(printerDc, LOGPIXELSX); + int physicalWidth = ::GetDeviceCaps(printerDc, PHYSICALWIDTH); + double widthInches = double(physicalWidth) / pixelsPerInchX; + if (aDevmode->dmFields & DM_ORIENTATION && + aDevmode->dmOrientation == DMORIENT_LANDSCAPE) { + std::swap(widthInches, heightInInches); + } + aSettingsInitializer.mPaperInfo.mSize.SizeTo(widthInches * kPointsPerInch, + heightInInches * kPointsPerInch); + + gfx::MarginDouble margin = + WinUtils::GetUnwriteableMarginsForDeviceInInches(printerDc); + aSettingsInitializer.mPaperInfo.mUnwriteableMargin = Some(MarginDouble{ + margin.top * kPointsPerInch, margin.right * kPointsPerInch, + margin.bottom * kPointsPerInch, margin.left * kPointsPerInch}); + + // Using Y to match existing code for print scaling calculations. + aSettingsInitializer.mResolution = pixelsPerInchY; + + if (aDevmode->dmFields & DM_COLOR) { + // See comment for PrintSettingsInitializer.mPrintInColor + aSettingsInitializer.mPrintInColor = + aDevmode->dmColor != DMCOLOR_MONOCHROME; + } + + if (aDevmode->dmFields & DM_ORIENTATION) { + aSettingsInitializer.mSheetOrientation = + int32_t(aDevmode->dmOrientation == DMORIENT_PORTRAIT + ? nsPrintSettings::kPortraitOrientation + : nsPrintSettings::kLandscapeOrientation); + } + + if (aDevmode->dmFields & DM_COPIES) { + aSettingsInitializer.mNumCopies = aDevmode->dmCopies; + } + + if (aDevmode->dmFields & DM_DUPLEX) { + switch (aDevmode->dmDuplex) { + default: + MOZ_FALLTHROUGH_ASSERT("bad value for dmDuplex field"); + case DMDUP_SIMPLEX: + aSettingsInitializer.mDuplex = nsPrintSettings::kDuplexNone; + break; + case DMDUP_VERTICAL: + aSettingsInitializer.mDuplex = nsPrintSettings::kDuplexFlipOnLongEdge; + break; + case DMDUP_HORIZONTAL: + aSettingsInitializer.mDuplex = nsPrintSettings::kDuplexFlipOnShortEdge; + break; + } + } +} + +NS_IMETHODIMP +nsPrinterWin::GetName(nsAString& aName) { + aName.Assign(mName); + return NS_OK; +} + +NS_IMETHODIMP +nsPrinterWin::GetSystemName(nsAString& aName) { + aName.Assign(mName); + return NS_OK; +} + +namespace mozilla { +template <> +void ResolveOrReject(Promise& aPromise, nsPrinterWin& aPrinter, + const PrintSettingsInitializer& aResult) { + aPromise.MaybeResolve( + RefPtr<nsIPrintSettings>(CreatePlatformPrintSettings(aResult))); +} +} // namespace mozilla + +NS_IMETHODIMP nsPrinterWin::CopyFromWithValidation( + nsIPrintSettings* aSettingsToCopyFrom, JSContext* aCx, + Promise** aResultPromise) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aResultPromise); + + PrintSettingsInitializer settingsInitializer = + aSettingsToCopyFrom->GetSettingsInitializer(); + return PrintBackgroundTaskPromise( + *this, aCx, aResultPromise, "CopyFromWithValidation"_ns, + &nsPrinterWin::GetValidatedSettings, settingsInitializer); +} + +bool nsPrinterWin::SupportsDuplex() const { + MutexAutoLock autoLock(mDriverMutex); + return ::DeviceCapabilitiesW(mName.get(), nullptr, DC_DUPLEX, nullptr, + nullptr) == 1; +} + +bool nsPrinterWin::SupportsColor() const { + MutexAutoLock autoLock(mDriverMutex); + return ::DeviceCapabilitiesW(mName.get(), nullptr, DC_COLORDEVICE, nullptr, + nullptr) == 1; +} + +bool nsPrinterWin::SupportsMonochrome() const { + if (!SupportsColor()) { + return true; + } + + nsHPRINTER hPrinter = nullptr; + if (NS_WARN_IF(!::OpenPrinterW(mName.get(), &hPrinter, nullptr))) { + return false; + } + nsAutoPrinter autoPrinter(hPrinter); + + nsTArray<uint8_t> devmodeWStorage = CopyDefaultDevmodeW(); + if (devmodeWStorage.IsEmpty()) { + return false; + } + + auto* devmode = reinterpret_cast<DEVMODEW*>(devmodeWStorage.Elements()); + + devmode->dmFields |= DM_COLOR; + devmode->dmColor = DMCOLOR_MONOCHROME; + // Try to modify the devmode settings and see if the setting sticks. + // + // This has been the only reliable way to detect it that we've found. + MutexAutoLock autoLock(mDriverMutex); + LONG ret = + ::DocumentPropertiesW(nullptr, autoPrinter.get(), mName.get(), devmode, + devmode, DM_IN_BUFFER | DM_OUT_BUFFER); + if (ret != IDOK) { + return false; + } + return !(devmode->dmFields & DM_COLOR) || + devmode->dmColor == DMCOLOR_MONOCHROME; +} + +bool nsPrinterWin::SupportsCollation() const { + MutexAutoLock autoLock(mDriverMutex); + return ::DeviceCapabilitiesW(mName.get(), nullptr, DC_COLLATE, nullptr, + nullptr) == 1; +} + +nsPrinterBase::PrinterInfo nsPrinterWin::CreatePrinterInfo() const { + return PrinterInfo{PaperList(), DefaultSettings()}; +} + +mozilla::gfx::MarginDouble nsPrinterWin::GetMarginsForPaper( + nsString aPaperId) const { + gfx::MarginDouble margin; + + nsTArray<uint8_t> devmodeWStorage = CopyDefaultDevmodeW(); + if (devmodeWStorage.IsEmpty()) { + return margin; + } + + auto* devmode = reinterpret_cast<DEVMODEW*>(devmodeWStorage.Elements()); + + devmode->dmFields = DM_PAPERSIZE; + devmode->dmPaperSize = _wtoi((const wchar_t*)aPaperId.BeginReading()); + HDC dc; + { + MutexAutoLock autoLock(mDriverMutex); + dc = ::CreateICW(nullptr, mName.get(), nullptr, devmode); + } + nsAutoHDC printerDc(dc); + MOZ_ASSERT(printerDc, "CreateICW failed"); + if (!printerDc) { + return margin; + } + margin = WinUtils::GetUnwriteableMarginsForDeviceInInches(printerDc); + margin.top *= kPointsPerInch; + margin.right *= kPointsPerInch; + margin.bottom *= kPointsPerInch; + margin.left *= kPointsPerInch; + + return margin; +} + +nsTArray<uint8_t> nsPrinterWin::CopyDefaultDevmodeW() const { + nsTArray<uint8_t> devmodeStorageW; + + auto devmodeStorageWLock = mDefaultDevmodeWStorage.Lock(); + if (devmodeStorageWLock->IsEmpty()) { + nsHPRINTER hPrinter = nullptr; + // OpenPrinter could fail if, for example, the printer has been removed + // or otherwise become inaccessible since it was selected. + if (NS_WARN_IF(!::OpenPrinterW(mName.get(), &hPrinter, nullptr))) { + return devmodeStorageW; + } + nsAutoPrinter autoPrinter(hPrinter); + // Allocate devmode storage of the correct size. + MutexAutoLock autoLock(mDriverMutex); + LONG bytesNeeded = ::DocumentPropertiesW(nullptr, autoPrinter.get(), + mName.get(), nullptr, nullptr, 0); + // Note that we must cast the sizeof() to a signed type so that comparison + // with the signed, potentially-negative bytesNeeded will work! + MOZ_ASSERT(bytesNeeded >= LONG(sizeof(DEVMODEW)), + "DocumentPropertiesW failed to get valid size"); + if (bytesNeeded < LONG(sizeof(DEVMODEW))) { + return devmodeStorageW; + } + + // Allocate extra space in case of bad drivers that return a too-small + // result from DocumentProperties. + // (See https://bugzilla.mozilla.org/show_bug.cgi?id=1664530#c5) + if (!devmodeStorageWLock->SetLength(bytesNeeded * 2, fallible)) { + return devmodeStorageW; + } + + memset(devmodeStorageWLock->Elements(), 0, devmodeStorageWLock->Length()); + auto* devmode = + reinterpret_cast<DEVMODEW*>(devmodeStorageWLock->Elements()); + LONG ret = ::DocumentPropertiesW(nullptr, autoPrinter.get(), mName.get(), + devmode, nullptr, DM_OUT_BUFFER); + MOZ_ASSERT(ret == IDOK, "DocumentPropertiesW failed"); + // Make sure that the lengths in the DEVMODEW make sense. + if (ret != IDOK || devmode->dmSize != sizeof(DEVMODEW) || + devmode->dmSize + devmode->dmDriverExtra > + devmodeStorageWLock->Length()) { + // Clear mDefaultDevmodeWStorage to make sure we try again next time. + devmodeStorageWLock->Clear(); + return devmodeStorageW; + } + } + + devmodeStorageW.Assign(devmodeStorageWLock.ref()); + return devmodeStorageW; +} + +nsTArray<mozilla::PaperInfo> nsPrinterWin::PaperList() const { + // Paper IDs are returned as WORDs. + int requiredArrayCount = 0; + auto paperIds = GetDeviceCapabilityArray<WORD>( + mName.get(), DC_PAPERS, mDriverMutex, requiredArrayCount); + if (!paperIds.Length()) { + return {}; + } + + // Paper names are returned in 64 long character buffers. + auto paperNames = GetDeviceCapabilityArray<Array<wchar_t, 64>>( + mName.get(), DC_PAPERNAMES, mDriverMutex, requiredArrayCount); + // Check that we have the same number of names as IDs. + if (paperNames.Length() != paperIds.Length()) { + return {}; + } + + // Paper sizes are returned as POINT structs with a tenth of a millimeter as + // the unit. + auto paperSizes = GetDeviceCapabilityArray<POINT>( + mName.get(), DC_PAPERSIZE, mDriverMutex, requiredArrayCount); + // Check that we have the same number of sizes as IDs. + if (paperSizes.Length() != paperIds.Length()) { + return {}; + } + + nsTArray<mozilla::PaperInfo> paperList; + paperList.SetCapacity(paperNames.Length()); + for (size_t i = 0; i < paperNames.Length(); ++i) { + // Paper names are null terminated unless they are 64 characters long. + auto firstNull = + std::find(paperNames[i].cbegin(), paperNames[i].cend(), L'\0'); + auto nameLength = firstNull - paperNames[i].cbegin(); + double width = paperSizes[i].x * kPointsPerTenthMM; + double height = paperSizes[i].y * kPointsPerTenthMM; + + // Skip if no name or invalid size. + if (!nameLength || width <= 0 || height <= 0) { + continue; + } + + // Windows paper IDs are 16-bit integers; we stringify them to store in the + // PaperInfo.mId field. + nsString paperIdString; + paperIdString.AppendInt(paperIds[i]); + + // We don't resolve the margins eagerly because they're really expensive (on + // the order of seconds for some drivers). + nsDependentSubstring name(paperNames[i].cbegin(), nameLength); + paperList.AppendElement(mozilla::PaperInfo(paperIdString, nsString(name), + {width, height}, Nothing())); + } + + return paperList; +} + +PrintSettingsInitializer nsPrinterWin::DefaultSettings() const { + nsTArray<uint8_t> devmodeWStorage = CopyDefaultDevmodeW(); + if (devmodeWStorage.IsEmpty()) { + return {}; + } + + const auto* devmode = + reinterpret_cast<const DEVMODEW*>(devmodeWStorage.Elements()); + + PrintSettingsInitializer settingsInitializer; + DevmodeToSettingsInitializer(mName, devmode, mDriverMutex, + settingsInitializer); + settingsInitializer.mDevmodeWStorage = std::move(devmodeWStorage); + return settingsInitializer; +} + +PrintSettingsInitializer nsPrinterWin::GetValidatedSettings( + PrintSettingsInitializer aSettingsToValidate) const { + // This function validates the settings by relying on the printer driver + // rejecting any invalid settings and resetting them to valid values. + + // Create a copy of the default DEVMODE for this printer. + nsTArray<uint8_t> devmodeWStorage = CopyDefaultDevmodeW(); + if (devmodeWStorage.IsEmpty()) { + return aSettingsToValidate; + } + + nsHPRINTER hPrinter = nullptr; + if (NS_WARN_IF(!::OpenPrinterW(mName.get(), &hPrinter, nullptr))) { + return aSettingsToValidate; + } + nsAutoPrinter autoPrinter(hPrinter); + + // Copy the settings from aSettingsToValidate into our DEVMODE. + DEVMODEW* devmode = reinterpret_cast<DEVMODEW*>(devmodeWStorage.Elements()); + if (!aSettingsToValidate.mPaperInfo.mId.IsEmpty()) { + devmode->dmPaperSize = _wtoi( + (const wchar_t*)aSettingsToValidate.mPaperInfo.mId.BeginReading()); + devmode->dmFields |= DM_PAPERSIZE; + } else { + devmode->dmPaperSize = 0; + devmode->dmFields &= ~DM_PAPERSIZE; + } + + devmode->dmFields |= DM_COLOR; + devmode->dmColor = + aSettingsToValidate.mPrintInColor ? DMCOLOR_COLOR : DMCOLOR_MONOCHROME; + + // Note: small page sizes can be required here for sticker, label and slide + // printers etc. see bug 1271900. + if (aSettingsToValidate.mPaperInfo.mSize.height > 0) { + devmode->dmPaperLength = std::round( + aSettingsToValidate.mPaperInfo.mSize.height / kPointsPerTenthMM); + devmode->dmFields |= DM_PAPERLENGTH; + } else { + devmode->dmPaperLength = 0; + devmode->dmFields &= ~DM_PAPERLENGTH; + } + + if (aSettingsToValidate.mPaperInfo.mSize.width > 0) { + devmode->dmPaperWidth = std::round( + aSettingsToValidate.mPaperInfo.mSize.width / kPointsPerTenthMM); + devmode->dmFields |= DM_PAPERWIDTH; + } else { + devmode->dmPaperWidth = 0; + devmode->dmFields &= ~DM_PAPERWIDTH; + } + + // Setup Orientation + devmode->dmOrientation = aSettingsToValidate.mSheetOrientation == + nsPrintSettings::kPortraitOrientation + ? DMORIENT_PORTRAIT + : DMORIENT_LANDSCAPE; + devmode->dmFields |= DM_ORIENTATION; + + // Setup Number of Copies + devmode->dmCopies = aSettingsToValidate.mNumCopies; + devmode->dmFields |= DM_COPIES; + + // Setup Simplex/Duplex mode + devmode->dmFields |= DM_DUPLEX; + switch (aSettingsToValidate.mDuplex) { + case nsPrintSettings::kDuplexNone: + devmode->dmDuplex = DMDUP_SIMPLEX; + break; + case nsPrintSettings::kDuplexFlipOnLongEdge: + devmode->dmDuplex = DMDUP_VERTICAL; + break; + case nsPrintSettings::kDuplexFlipOnShortEdge: + devmode->dmDuplex = DMDUP_HORIZONTAL; + break; + default: + MOZ_ASSERT_UNREACHABLE("bad value for duplex option"); + break; + } + + // Apply the settings in the DEVMODE to the printer and retrieve the updated + // DEVMODE back into the same structure. + LONG ret; + { + MutexAutoLock autoLock(mDriverMutex); + ret = ::DocumentPropertiesW(nullptr, autoPrinter.get(), mName.get(), + devmode, devmode, DM_IN_BUFFER | DM_OUT_BUFFER); + } + if (ret != IDOK) { + return aSettingsToValidate; + } + + // Copy the settings back from the DEVMODE into aSettingsToValidate and + // return. + DevmodeToSettingsInitializer(mName, devmode, mDriverMutex, + aSettingsToValidate); + aSettingsToValidate.mDevmodeWStorage = std::move(devmodeWStorage); + return aSettingsToValidate; +} |