1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "RemotePrintJobParent.h"
8 
9 #include <fstream>
10 
11 #include "gfxContext.h"
12 #include "mozilla/Attributes.h"
13 #include "mozilla/Unused.h"
14 #include "nsAppDirectoryServiceDefs.h"
15 #include "nsComponentManagerUtils.h"
16 #include "nsDirectoryServiceUtils.h"
17 #include "nsDeviceContext.h"
18 #include "nsIDeviceContextSpec.h"
19 #include "nsIPrintSettings.h"
20 #include "nsIWebProgressListener.h"
21 #include "PrintTranslator.h"
22 #include "private/pprio.h"
23 #include "nsAnonymousTemporaryFile.h"
24 
25 namespace mozilla {
26 namespace layout {
27 
RemotePrintJobParent(nsIPrintSettings * aPrintSettings)28 RemotePrintJobParent::RemotePrintJobParent(nsIPrintSettings* aPrintSettings)
29     : mPrintSettings(aPrintSettings), mIsDoingPrinting(false) {
30   MOZ_COUNT_CTOR(RemotePrintJobParent);
31 }
32 
RecvInitializePrint(const nsString & aDocumentTitle,const nsString & aPrintToFile,const int32_t & aStartPage,const int32_t & aEndPage)33 mozilla::ipc::IPCResult RemotePrintJobParent::RecvInitializePrint(
34     const nsString& aDocumentTitle, const nsString& aPrintToFile,
35     const int32_t& aStartPage, const int32_t& aEndPage) {
36   nsresult rv =
37       InitializePrintDevice(aDocumentTitle, aPrintToFile, aStartPage, aEndPage);
38   if (NS_FAILED(rv)) {
39     Unused << SendPrintInitializationResult(rv, FileDescriptor());
40     Unused << Send__delete__(this);
41     return IPC_OK();
42   }
43 
44   mPrintTranslator.reset(new PrintTranslator(mPrintDeviceContext));
45   FileDescriptor fd;
46   rv = PrepareNextPageFD(&fd);
47   if (NS_FAILED(rv)) {
48     Unused << SendPrintInitializationResult(rv, FileDescriptor());
49     Unused << Send__delete__(this);
50     return IPC_OK();
51   }
52 
53   Unused << SendPrintInitializationResult(NS_OK, fd);
54   return IPC_OK();
55 }
56 
InitializePrintDevice(const nsString & aDocumentTitle,const nsString & aPrintToFile,const int32_t & aStartPage,const int32_t & aEndPage)57 nsresult RemotePrintJobParent::InitializePrintDevice(
58     const nsString& aDocumentTitle, const nsString& aPrintToFile,
59     const int32_t& aStartPage, const int32_t& aEndPage) {
60   nsresult rv;
61   nsCOMPtr<nsIDeviceContextSpec> deviceContextSpec =
62       do_CreateInstance("@mozilla.org/gfx/devicecontextspec;1", &rv);
63   if (NS_WARN_IF(NS_FAILED(rv))) {
64     return rv;
65   }
66 
67   rv = deviceContextSpec->Init(nullptr, mPrintSettings, false);
68   if (NS_WARN_IF(NS_FAILED(rv))) {
69     return rv;
70   }
71 
72   mPrintDeviceContext = new nsDeviceContext();
73   rv = mPrintDeviceContext->InitForPrinting(deviceContextSpec);
74   if (NS_WARN_IF(NS_FAILED(rv))) {
75     return rv;
76   }
77 
78   rv = mPrintDeviceContext->BeginDocument(aDocumentTitle, aPrintToFile,
79                                           aStartPage, aEndPage);
80   if (NS_FAILED(rv)) {
81     NS_WARNING_ASSERTION(rv == NS_ERROR_ABORT,
82                          "Failed to initialize print device");
83     return rv;
84   }
85 
86   if (!mPrintDeviceContext->IsSyncPagePrinting()) {
87     mPrintDeviceContext->RegisterPageDoneCallback(
88         [this](nsresult aResult) { PageDone(aResult); });
89   }
90 
91   mIsDoingPrinting = true;
92 
93   return NS_OK;
94 }
95 
PrepareNextPageFD(FileDescriptor * aFd)96 nsresult RemotePrintJobParent::PrepareNextPageFD(FileDescriptor* aFd) {
97   PRFileDesc* prFd = nullptr;
98   nsresult rv = NS_OpenAnonymousTemporaryFile(&prFd);
99   if (NS_FAILED(rv)) {
100     return rv;
101   }
102   *aFd = FileDescriptor(
103       FileDescriptor::PlatformHandleType(PR_FileDesc2NativeHandle(prFd)));
104   mCurrentPageStream.OpenFD(prFd);
105   return NS_OK;
106 }
107 
RecvProcessPage()108 mozilla::ipc::IPCResult RemotePrintJobParent::RecvProcessPage() {
109   if (!mCurrentPageStream.IsOpen()) {
110     Unused << SendAbortPrint(NS_ERROR_FAILURE);
111     return IPC_OK();
112   }
113   mCurrentPageStream.Seek(0, PR_SEEK_SET);
114   nsresult rv = PrintPage(mCurrentPageStream);
115   mCurrentPageStream.Close();
116 
117   if (mPrintDeviceContext->IsSyncPagePrinting()) {
118     PageDone(rv);
119   }
120 
121   return IPC_OK();
122 }
123 
PrintPage(PRFileDescStream & aRecording)124 nsresult RemotePrintJobParent::PrintPage(PRFileDescStream& aRecording) {
125   MOZ_ASSERT(mPrintDeviceContext);
126 
127   nsresult rv = mPrintDeviceContext->BeginPage();
128   if (NS_WARN_IF(NS_FAILED(rv))) {
129     return rv;
130   }
131   if (!mPrintTranslator->TranslateRecording(aRecording)) {
132     return NS_ERROR_FAILURE;
133   }
134 
135   rv = mPrintDeviceContext->EndPage();
136   if (NS_WARN_IF(NS_FAILED(rv))) {
137     return rv;
138   }
139 
140   return NS_OK;
141 }
142 
PageDone(nsresult aResult)143 void RemotePrintJobParent::PageDone(nsresult aResult) {
144   MOZ_ASSERT(mIsDoingPrinting);
145 
146   if (NS_FAILED(aResult)) {
147     Unused << SendAbortPrint(aResult);
148   } else {
149     FileDescriptor fd;
150     aResult = PrepareNextPageFD(&fd);
151     if (NS_FAILED(aResult)) {
152       Unused << SendAbortPrint(aResult);
153     }
154 
155     Unused << SendPageProcessed(fd);
156   }
157 }
158 
RecvFinalizePrint()159 mozilla::ipc::IPCResult RemotePrintJobParent::RecvFinalizePrint() {
160   // EndDocument is sometimes called in the child even when BeginDocument has
161   // not been called. See bug 1223332.
162   if (mPrintDeviceContext) {
163     DebugOnly<nsresult> rv = mPrintDeviceContext->EndDocument();
164 
165     // Too late to abort the child just log.
166     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EndDocument failed");
167 
168     // Since RecvFinalizePrint is called after all page printed, there should
169     // be no more page-done callbacks after that, in theory. Unregistering
170     // page-done callback is not must have, but we still do this for safety.
171     mPrintDeviceContext->UnregisterPageDoneCallback();
172   }
173 
174   mIsDoingPrinting = false;
175 
176   Unused << Send__delete__(this);
177   return IPC_OK();
178 }
179 
RecvAbortPrint(const nsresult & aRv)180 mozilla::ipc::IPCResult RemotePrintJobParent::RecvAbortPrint(
181     const nsresult& aRv) {
182   if (mPrintDeviceContext) {
183     Unused << mPrintDeviceContext->AbortDocument();
184     mPrintDeviceContext->UnregisterPageDoneCallback();
185   }
186 
187   mIsDoingPrinting = false;
188 
189   Unused << Send__delete__(this);
190   return IPC_OK();
191 }
192 
RecvStateChange(const long & aStateFlags,const nsresult & aStatus)193 mozilla::ipc::IPCResult RemotePrintJobParent::RecvStateChange(
194     const long& aStateFlags, const nsresult& aStatus) {
195   uint32_t numberOfListeners = mPrintProgressListeners.Length();
196   for (uint32_t i = 0; i < numberOfListeners; ++i) {
197     nsIWebProgressListener* listener = mPrintProgressListeners.SafeElementAt(i);
198     listener->OnStateChange(nullptr, nullptr, aStateFlags, aStatus);
199   }
200 
201   return IPC_OK();
202 }
203 
RecvProgressChange(const long & aCurSelfProgress,const long & aMaxSelfProgress,const long & aCurTotalProgress,const long & aMaxTotalProgress)204 mozilla::ipc::IPCResult RemotePrintJobParent::RecvProgressChange(
205     const long& aCurSelfProgress, const long& aMaxSelfProgress,
206     const long& aCurTotalProgress, const long& aMaxTotalProgress) {
207   uint32_t numberOfListeners = mPrintProgressListeners.Length();
208   for (uint32_t i = 0; i < numberOfListeners; ++i) {
209     nsIWebProgressListener* listener = mPrintProgressListeners.SafeElementAt(i);
210     listener->OnProgressChange(nullptr, nullptr, aCurSelfProgress,
211                                aMaxSelfProgress, aCurTotalProgress,
212                                aMaxTotalProgress);
213   }
214 
215   return IPC_OK();
216 }
217 
RecvStatusChange(const nsresult & aStatus)218 mozilla::ipc::IPCResult RemotePrintJobParent::RecvStatusChange(
219     const nsresult& aStatus) {
220   uint32_t numberOfListeners = mPrintProgressListeners.Length();
221   for (uint32_t i = 0; i < numberOfListeners; ++i) {
222     nsIWebProgressListener* listener = mPrintProgressListeners.SafeElementAt(i);
223     listener->OnStatusChange(nullptr, nullptr, aStatus, nullptr);
224   }
225 
226   return IPC_OK();
227 }
228 
RegisterListener(nsIWebProgressListener * aListener)229 void RemotePrintJobParent::RegisterListener(nsIWebProgressListener* aListener) {
230   MOZ_ASSERT(aListener);
231 
232   mPrintProgressListeners.AppendElement(aListener);
233 }
234 
GetPrintSettings()235 already_AddRefed<nsIPrintSettings> RemotePrintJobParent::GetPrintSettings() {
236   nsCOMPtr<nsIPrintSettings> printSettings = mPrintSettings;
237   return printSettings.forget();
238 }
239 
~RemotePrintJobParent()240 RemotePrintJobParent::~RemotePrintJobParent() {
241   MOZ_COUNT_DTOR(RemotePrintJobParent);
242 }
243 
ActorDestroy(ActorDestroyReason aWhy)244 void RemotePrintJobParent::ActorDestroy(ActorDestroyReason aWhy) {
245   if (mPrintDeviceContext) {
246     mPrintDeviceContext->UnregisterPageDoneCallback();
247   }
248 
249   mIsDoingPrinting = false;
250 
251   // If progress dialog is opened, notify closing it.
252   for (auto listener : mPrintProgressListeners) {
253     listener->OnStateChange(nullptr, nullptr,
254                             nsIWebProgressListener::STATE_STOP, NS_OK);
255   }
256 }
257 
258 }  // namespace layout
259 }  // namespace mozilla
260