Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / widget / cocoa / nsDeviceContextSpecX.mm
blob9bb8449b705e09e206a16f0d55e9231911d79e33
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 "nsDeviceContextSpecX.h"
8 #import <Cocoa/Cocoa.h>
9 #include "mozilla/gfx/PrintPromise.h"
10 #include <CoreFoundation/CoreFoundation.h>
11 #include <unistd.h>
13 #ifdef MOZ_ENABLE_SKIA_PDF
14 #  include "mozilla/gfx/PrintTargetSkPDF.h"
15 #endif
16 #include "mozilla/gfx/PrintTargetCG.h"
17 #include "mozilla/Logging.h"
18 #include "mozilla/Preferences.h"
19 #include "mozilla/RefPtr.h"
20 #include "mozilla/Telemetry.h"
22 #include "AppleUtils.h"
23 #include "nsCocoaUtils.h"
24 #include "nsCRT.h"
25 #include "nsCUPSShim.h"
26 #include "nsDirectoryServiceDefs.h"
27 #include "nsILocalFileMac.h"
28 #include "nsIOutputStream.h"
29 #include "nsPaper.h"
30 #include "nsPrinterListCUPS.h"
31 #include "nsPrintSettingsX.h"
32 #include "nsQueryObject.h"
33 #include "prenv.h"
35 // This must be the last include:
36 #include "nsObjCExceptions.h"
38 using namespace mozilla;
39 using mozilla::gfx::IntSize;
40 using mozilla::gfx::PrintEndDocumentPromise;
41 using mozilla::gfx::PrintTarget;
42 using mozilla::gfx::PrintTargetCG;
43 #ifdef MOZ_ENABLE_SKIA_PDF
44 using mozilla::gfx::PrintTargetSkPDF;
45 #endif
47 //----------------------------------------------------------------------
48 // nsDeviceContentSpecX
50 nsDeviceContextSpecX::nsDeviceContextSpecX() = default;
52 nsDeviceContextSpecX::~nsDeviceContextSpecX() {
53   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
55   if (mPrintSession) {
56     ::PMRelease(mPrintSession);
57   }
58   if (mPageFormat) {
59     ::PMRelease(mPageFormat);
60   }
61   if (mPMPrintSettings) {
62     ::PMRelease(mPMPrintSettings);
63   }
65   NS_OBJC_END_TRY_IGNORE_BLOCK;
68 NS_IMPL_ISUPPORTS(nsDeviceContextSpecX, nsIDeviceContextSpec)
70 NS_IMETHODIMP nsDeviceContextSpecX::Init(nsIPrintSettings* aPS,
71                                          bool aIsPrintPreview) {
72   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
74   RefPtr<nsPrintSettingsX> settings(do_QueryObject(aPS));
75   if (!settings) {
76     return NS_ERROR_NO_INTERFACE;
77   }
78   // Note: unlike other platforms, we don't set our base class's mPrintSettings
79   // here since we don't need it currently (we do set mPMPrintSettings below).
81   NSPrintInfo* printInfo = settings->CreateOrCopyPrintInfo();
82   if (!printInfo) {
83     return NS_ERROR_FAILURE;
84   }
85   if (aPS->GetOutputDestination() ==
86       nsIPrintSettings::kOutputDestinationStream) {
87     aPS->GetOutputStream(getter_AddRefs(mOutputStream));
88     if (!mOutputStream) {
89       return NS_ERROR_FAILURE;
90     }
91   }
92   mPrintSession = static_cast<PMPrintSession>([printInfo PMPrintSession]);
93   mPageFormat = static_cast<PMPageFormat>([printInfo PMPageFormat]);
94   mPMPrintSettings = static_cast<PMPrintSettings>([printInfo PMPrintSettings]);
95   MOZ_ASSERT(mPrintSession && mPageFormat && mPMPrintSettings);
96   ::PMRetain(mPrintSession);
97   ::PMRetain(mPageFormat);
98   ::PMRetain(mPMPrintSettings);
99   [printInfo release];
101 #ifdef MOZ_ENABLE_SKIA_PDF
102   nsAutoString printViaPdf;
103   mozilla::Preferences::GetString("print.print_via_pdf_encoder", printViaPdf);
104   if (printViaPdf.EqualsLiteral("skia-pdf")) {
105     // Annoyingly, PMPrinterPrintWithFile does not pay attention to the
106     // kPMDestination* value set in the PMPrintSession; it always sends the PDF
107     // to the specified printer.  This means that if we create the PDF using
108     // SkPDF then we need to manually handle user actions like "Open PDF in
109     // Preview" and "Save as PDF...".
110     // TODO: Currently we do not support using SkPDF for kPMDestinationFax or
111     // kPMDestinationProcessPDF ("Add PDF to iBooks, etc.), and we only support
112     // it for kPMDestinationFile if the destination file is a PDF.
113     // XXX Could PMWorkflowSubmitPDFWithSettings/PMPrinterPrintWithProvider
114     // help?
115     OSStatus status = noErr;
116     PMDestinationType destination;
117     status = ::PMSessionGetDestinationType(mPrintSession, mPMPrintSettings,
118                                            &destination);
119     if (status == noErr) {
120       if (destination == kPMDestinationPrinter ||
121           destination == kPMDestinationPreview) {
122         mPrintViaSkPDF = true;
123       } else if (destination == kPMDestinationFile) {
124         AutoCFRelease<CFURLRef> destURL(nullptr);
125         status = ::PMSessionCopyDestinationLocation(
126             mPrintSession, mPMPrintSettings, destURL.receive());
127         if (status == noErr) {
128           AutoCFRelease<CFStringRef> destPathRef =
129               CFURLCopyFileSystemPath(destURL, kCFURLPOSIXPathStyle);
130           NSString* destPath = (NSString*)CFStringRef(destPathRef);
131           NSString* destPathExt = [destPath pathExtension];
132           if ([destPathExt isEqualToString:@"pdf"]) {
133             mPrintViaSkPDF = true;
134           }
135         }
136       }
137     }
138   }
139 #endif
141   int16_t outputFormat = aPS->GetOutputFormat();
143   if (outputFormat == nsIPrintSettings::kOutputFormatPDF) {
144     // We don't actually currently support/use kOutputFormatPDF on mac, but
145     // this is for completeness in case we add that (we probably need to in
146     // order to support adding links into saved PDFs, for example).
147     Telemetry::ScalarAdd(Telemetry::ScalarID::PRINTING_TARGET_TYPE,
148                          u"pdf_file"_ns, 1);
149   } else {
150     PMDestinationType destination;
151     OSStatus status = ::PMSessionGetDestinationType(
152         mPrintSession, mPMPrintSettings, &destination);
153     if (status == noErr && (destination == kPMDestinationFile ||
154                             destination == kPMDestinationPreview ||
155                             destination == kPMDestinationProcessPDF)) {
156       Telemetry::ScalarAdd(Telemetry::ScalarID::PRINTING_TARGET_TYPE,
157                            u"pdf_file"_ns, 1);
158     } else {
159       Telemetry::ScalarAdd(Telemetry::ScalarID::PRINTING_TARGET_TYPE,
160                            u"unknown"_ns, 1);
161     }
162   }
164   return NS_OK;
166   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
169 NS_IMETHODIMP nsDeviceContextSpecX::BeginDocument(
170     const nsAString& aTitle, const nsAString& aPrintToFileName,
171     int32_t aStartPage, int32_t aEndPage) {
172   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
174   return NS_OK;
176   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
179 RefPtr<PrintEndDocumentPromise> nsDeviceContextSpecX::EndDocument() {
180   return nsIDeviceContextSpec::EndDocumentPromiseFromResult(DoEndDocument(),
181                                                             __func__);
184 nsresult nsDeviceContextSpecX::DoEndDocument() {
185   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
187 #ifdef MOZ_ENABLE_SKIA_PDF
188   if (mPrintViaSkPDF) {
189     OSStatus status = noErr;
191     nsCOMPtr<nsILocalFileMac> tmpPDFFile = do_QueryInterface(mTempFile);
192     if (!tmpPDFFile) {
193       return NS_ERROR_FAILURE;
194     }
195     AutoCFRelease<CFURLRef> pdfURL(nullptr);
196     // Note that the caller is responsible to release pdfURL according to
197     // nsILocalFileMac.idl, even though we didn't follow the Core Foundation
198     // naming conventions here (the method should've been called CopyCFURL).
199     nsresult rv = tmpPDFFile->GetCFURL(pdfURL.receive());
200     NS_ENSURE_SUCCESS(rv, rv);
202     PMDestinationType destination;
203     status = ::PMSessionGetDestinationType(mPrintSession, mPMPrintSettings,
204                                            &destination);
206     switch (destination) {
207       case kPMDestinationPrinter: {
208         PMPrinter currentPrinter = NULL;
209         status = ::PMSessionGetCurrentPrinter(mPrintSession, &currentPrinter);
210         if (status != noErr) {
211           return NS_ERROR_FAILURE;
212         }
213         CFStringRef mimeType = CFSTR("application/pdf");
214         status = ::PMPrinterPrintWithFile(currentPrinter, mPMPrintSettings,
215                                           mPageFormat, mimeType, pdfURL);
216         break;
217       }
218       case kPMDestinationPreview: {
219         // XXXjwatt Or should we use CocoaFileUtils::RevealFileInFinder(pdfURL);
220         AutoCFRelease<CFStringRef> pdfPath =
221             CFURLCopyFileSystemPath(pdfURL, kCFURLPOSIXPathStyle);
222         NSString* path = (NSString*)CFStringRef(pdfPath);
223         NSWorkspace* ws = [NSWorkspace sharedWorkspace];
224         [ws openFile:path];
225         break;
226       }
227       case kPMDestinationFile: {
228         AutoCFRelease<CFURLRef> destURL(nullptr);
229         status = ::PMSessionCopyDestinationLocation(
230             mPrintSession, mPMPrintSettings, destURL.receive());
231         if (status == noErr) {
232           AutoCFRelease<CFStringRef> sourcePathRef =
233               CFURLCopyFileSystemPath(pdfURL, kCFURLPOSIXPathStyle);
234           NSString* sourcePath = (NSString*)CFStringRef(sourcePathRef);
235 #  ifdef DEBUG
236           AutoCFRelease<CFStringRef> destPathRef =
237               CFURLCopyFileSystemPath(destURL, kCFURLPOSIXPathStyle);
238           NSString* destPath = (NSString*)CFStringRef(destPathRef);
239           NSString* destPathExt = [destPath pathExtension];
240           MOZ_ASSERT([destPathExt isEqualToString:@"pdf"],
241                      "nsDeviceContextSpecX::Init only allows '.pdf' for now");
242           // We could use /usr/sbin/cupsfilter to convert the PDF to PS, but
243           // currently we don't.
244 #  endif
245           NSFileManager* fileManager = [NSFileManager defaultManager];
246           if ([fileManager fileExistsAtPath:sourcePath]) {
247             NSURL* src = static_cast<NSURL*>(CFURLRef(pdfURL));
248             NSURL* dest = static_cast<NSURL*>(CFURLRef(destURL));
249             bool ok = [fileManager
250                 replaceItemAtURL:dest
251                    withItemAtURL:src
252                   backupItemName:nil
253                          options:
254                              NSFileManagerItemReplacementUsingNewMetadataOnly
255                 resultingItemURL:nil
256                            error:nil];
257             if (!ok) {
258               return NS_ERROR_FAILURE;
259             }
260           }
261         }
262         break;
263       }
264       default:
265         MOZ_ASSERT_UNREACHABLE("nsDeviceContextSpecX::Init doesn't set "
266                                "mPrintViaSkPDF for other values");
267     }
269     return (status == noErr) ? NS_OK : NS_ERROR_FAILURE;
270   }
271 #endif
273   return NS_OK;
275   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
278 void nsDeviceContextSpecX::GetPaperRect(double* aTop, double* aLeft,
279                                         double* aBottom, double* aRight) {
280   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
282   PMRect paperRect;
283   ::PMGetAdjustedPaperRect(mPageFormat, &paperRect);
285   *aTop = paperRect.top;
286   *aLeft = paperRect.left;
287   *aBottom = paperRect.bottom;
288   *aRight = paperRect.right;
290   NS_OBJC_END_TRY_IGNORE_BLOCK;
293 already_AddRefed<PrintTarget> nsDeviceContextSpecX::MakePrintTarget() {
294   double top, left, bottom, right;
295   GetPaperRect(&top, &left, &bottom, &right);
296   const double width = right - left;
297   const double height = bottom - top;
298   IntSize size = IntSize::Ceil(width, height);
300 #ifdef MOZ_ENABLE_SKIA_PDF
301   if (mPrintViaSkPDF) {
302     // TODO: Add support for stream printing via SkPDF if we enable that again.
303     nsresult rv =
304         NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(mTempFile));
305     NS_ENSURE_SUCCESS(rv, nullptr);
306     nsAutoCString tempPath("tmp-printing.pdf");
307     mTempFile->AppendNative(tempPath);
308     rv = mTempFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
309     NS_ENSURE_SUCCESS(rv, nullptr);
310     mTempFile->GetNativePath(tempPath);
311     auto stream = MakeUnique<SkFILEWStream>(tempPath.get());
312     return PrintTargetSkPDF::CreateOrNull(std::move(stream), size);
313   }
314 #endif
316   return PrintTargetCG::CreateOrNull(mOutputStream, mPrintSession, mPageFormat,
317                                      mPMPrintSettings, size);