RemotePrintJobParent.cpp (11297B)
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 "PrintTranslator.h" 12 #include "gfxContext.h" 13 #include "mozilla/ProfilerMarkers.h" 14 #include "nsAnonymousTemporaryFile.h" 15 #include "nsAppDirectoryServiceDefs.h" 16 #include "nsComponentManagerUtils.h" 17 #include "nsDeviceContext.h" 18 #include "nsDirectoryServiceUtils.h" 19 #include "nsIDeviceContextSpec.h" 20 #include "nsIPrintSettings.h" 21 #include "nsIWebProgressListener.h" 22 #include "private/pprio.h" 23 24 namespace mozilla::layout { 25 26 RemotePrintJobParent::RemotePrintJobParent(nsIPrintSettings* aPrintSettings) 27 : mPrintSettings(aPrintSettings), 28 mIsDoingPrinting(false), 29 mStatus(NS_ERROR_UNEXPECTED) { 30 MOZ_COUNT_CTOR(RemotePrintJobParent); 31 } 32 33 mozilla::ipc::IPCResult RemotePrintJobParent::RecvInitializePrint( 34 const nsAString& aDocumentTitle, const int32_t& aStartPage, 35 const int32_t& aEndPage) { 36 PROFILER_MARKER_TEXT("RemotePrintJobParent", LAYOUT_Printing, {}, 37 "RemotePrintJobParent::RecvInitializePrint"_ns); 38 39 nsresult rv = InitializePrintDevice(aDocumentTitle, aStartPage, aEndPage); 40 if (NS_FAILED(rv)) { 41 (void)SendPrintInitializationResult(rv, FileDescriptor()); 42 mStatus = rv; 43 (void)Send__delete__(this); 44 return IPC_OK(); 45 } 46 47 mPrintTranslator = MakeUnique<PrintTranslator>(mPrintDeviceContext); 48 FileDescriptor fd; 49 rv = PrepareNextPageFD(&fd); 50 if (NS_FAILED(rv)) { 51 (void)SendPrintInitializationResult(rv, FileDescriptor()); 52 mStatus = rv; 53 (void)Send__delete__(this); 54 return IPC_OK(); 55 } 56 57 (void)SendPrintInitializationResult(NS_OK, fd); 58 return IPC_OK(); 59 } 60 61 nsresult RemotePrintJobParent::InitializePrintDevice( 62 const nsAString& aDocumentTitle, const int32_t& aStartPage, 63 const int32_t& aEndPage) { 64 AUTO_PROFILER_MARKER_TEXT("RemotePrintJobParent", LAYOUT_Printing, {}, 65 "RemotePrintJobParent::InitializePrintDevice"_ns); 66 67 nsresult rv; 68 nsCOMPtr<nsIDeviceContextSpec> deviceContextSpec = 69 do_CreateInstance("@mozilla.org/gfx/devicecontextspec;1", &rv); 70 if (NS_WARN_IF(NS_FAILED(rv))) { 71 return rv; 72 } 73 74 rv = deviceContextSpec->Init(mPrintSettings, false); 75 if (NS_WARN_IF(NS_FAILED(rv))) { 76 return rv; 77 } 78 79 mPrintDeviceContext = new nsDeviceContext(); 80 rv = mPrintDeviceContext->InitForPrinting(deviceContextSpec); 81 if (NS_WARN_IF(NS_FAILED(rv))) { 82 return rv; 83 } 84 85 nsAutoString fileName; 86 mPrintSettings->GetToFileName(fileName); 87 88 rv = mPrintDeviceContext->BeginDocument(aDocumentTitle, fileName, aStartPage, 89 aEndPage); 90 if (NS_FAILED(rv)) { 91 NS_WARNING_ASSERTION(rv == NS_ERROR_ABORT, 92 "Failed to initialize print device"); 93 return rv; 94 } 95 96 mIsDoingPrinting = true; 97 98 return NS_OK; 99 } 100 101 nsresult RemotePrintJobParent::PrepareNextPageFD(FileDescriptor* aFd) { 102 AUTO_PROFILER_MARKER_TEXT("RemotePrintJobParent", LAYOUT_Printing, {}, 103 "RemotePrintJobParent::PrepareNextPageFD"_ns); 104 105 PRFileDesc* prFd = nullptr; 106 nsresult rv = NS_OpenAnonymousTemporaryFile(&prFd); 107 if (NS_FAILED(rv)) { 108 return rv; 109 } 110 *aFd = FileDescriptor( 111 FileDescriptor::PlatformHandleType(PR_FileDesc2NativeHandle(prFd))); 112 mCurrentPageStream.OpenFD(prFd); 113 return NS_OK; 114 } 115 116 mozilla::ipc::IPCResult RemotePrintJobParent::RecvProcessPage( 117 const int32_t& aWidthInPoints, const int32_t& aHeightInPoints, 118 nsTArray<uint64_t>&& aDeps) { 119 PROFILER_MARKER_TEXT("RemotePrintJobParent", LAYOUT_Printing, {}, 120 "RemotePrintJobParent::RecvProcessPage"_ns); 121 if (!mCurrentPageStream.IsOpen()) { 122 (void)SendAbortPrint(NS_ERROR_FAILURE); 123 return IPC_OK(); 124 } 125 mCurrentPageStream.Seek(0, PR_SEEK_SET); 126 127 gfx::IntSize pageSizeInPoints(aWidthInPoints, aHeightInPoints); 128 129 if (aDeps.IsEmpty()) { 130 FinishProcessingPage(pageSizeInPoints); 131 return IPC_OK(); 132 } 133 134 nsTHashSet<uint64_t> deps; 135 for (auto i : aDeps) { 136 deps.Insert(i); 137 } 138 139 gfx::CrossProcessPaint::Start(std::move(deps)) 140 ->Then( 141 GetCurrentSerialEventTarget(), __func__, 142 [self = RefPtr{this}, pageSizeInPoints]( 143 gfx::CrossProcessPaint::ResolvedFragmentMap&& aFragments) { 144 self->FinishProcessingPage(pageSizeInPoints, &aFragments); 145 }, 146 [self = RefPtr{this}, pageSizeInPoints](const nsresult& aRv) { 147 self->FinishProcessingPage(pageSizeInPoints); 148 }); 149 150 return IPC_OK(); 151 } 152 153 void RemotePrintJobParent::FinishProcessingPage( 154 const gfx::IntSize& aSizeInPoints, 155 gfx::CrossProcessPaint::ResolvedFragmentMap* aFragments) { 156 nsresult rv = PrintPage(aSizeInPoints, mCurrentPageStream, aFragments); 157 158 mCurrentPageStream.Close(); 159 160 PageDone(rv); 161 } 162 163 nsresult RemotePrintJobParent::PrintPage( 164 const gfx::IntSize& aSizeInPoints, PRFileDescStream& aRecording, 165 gfx::CrossProcessPaint::ResolvedFragmentMap* aFragments) { 166 MOZ_ASSERT(mPrintDeviceContext); 167 AUTO_PROFILER_MARKER_TEXT("RemotePrintJobParent", LAYOUT_Printing, {}, 168 "RemotePrintJobParent::PrintPage"_ns); 169 170 nsresult rv = mPrintDeviceContext->BeginPage(aSizeInPoints); 171 if (NS_WARN_IF(NS_FAILED(rv))) { 172 return rv; 173 } 174 if (aFragments) { 175 mPrintTranslator->SetDependentSurfaces(aFragments); 176 } 177 if (!mPrintTranslator->TranslateRecording(aRecording)) { 178 mPrintTranslator->SetDependentSurfaces(nullptr); 179 return NS_ERROR_FAILURE; 180 } 181 mPrintTranslator->SetDependentSurfaces(nullptr); 182 183 rv = mPrintDeviceContext->EndPage(); 184 if (NS_WARN_IF(NS_FAILED(rv))) { 185 return rv; 186 } 187 188 return NS_OK; 189 } 190 191 void RemotePrintJobParent::PageDone(nsresult aResult) { 192 MOZ_ASSERT(mIsDoingPrinting); 193 194 if (NS_FAILED(aResult)) { 195 (void)SendAbortPrint(aResult); 196 } else { 197 FileDescriptor fd; 198 aResult = PrepareNextPageFD(&fd); 199 if (NS_FAILED(aResult)) { 200 (void)SendAbortPrint(aResult); 201 } 202 203 (void)SendPageProcessed(fd); 204 } 205 } 206 207 static void NotifyStatusChange( 208 const nsCOMArray<nsIWebProgressListener>& aListeners, nsresult aStatus) { 209 uint32_t numberOfListeners = aListeners.Length(); 210 for (uint32_t i = 0; i < numberOfListeners; ++i) { 211 nsIWebProgressListener* listener = aListeners[static_cast<int32_t>(i)]; 212 listener->OnStatusChange(nullptr, nullptr, aStatus, nullptr); 213 } 214 } 215 216 static void NotifyStateChange( 217 const nsCOMArray<nsIWebProgressListener>& aListeners, long aStateFlags, 218 nsresult aStatus) { 219 uint32_t numberOfListeners = aListeners.Length(); 220 for (uint32_t i = 0; i < numberOfListeners; ++i) { 221 nsIWebProgressListener* listener = aListeners[static_cast<int32_t>(i)]; 222 listener->OnStateChange(nullptr, nullptr, aStateFlags, aStatus); 223 } 224 } 225 226 static void Cleanup(const nsCOMArray<nsIWebProgressListener>& aListeners, 227 RefPtr<nsDeviceContext>& aAbortContext, 228 const bool aPrintingInterrupted, const nsresult aResult) { 229 auto result = aResult; 230 if (MOZ_UNLIKELY(aPrintingInterrupted && NS_SUCCEEDED(result))) { 231 result = NS_ERROR_UNEXPECTED; 232 } 233 if (NS_FAILED(result)) { 234 NotifyStatusChange(aListeners, result); 235 } 236 if (aPrintingInterrupted && aAbortContext) { 237 // Abort any started print. 238 (void)aAbortContext->AbortDocument(); 239 } 240 // However the print went, let the listeners know that we're done. 241 NotifyStateChange(aListeners, 242 nsIWebProgressListener::STATE_STOP | 243 nsIWebProgressListener::STATE_IS_DOCUMENT, 244 result); 245 } 246 247 mozilla::ipc::IPCResult RemotePrintJobParent::RecvFinalizePrint() { 248 PROFILER_MARKER_TEXT("RemotePrintJobParent", LAYOUT_Printing, {}, 249 "RemotePrintJobParent::RecvFinalizePrint"_ns); 250 251 // EndDocument is sometimes called in the child even when BeginDocument has 252 // not been called. See bug 1223332. 253 if (mPrintDeviceContext) { 254 mPrintDeviceContext->EndDocument()->Then( 255 GetMainThreadSerialEventTarget(), __func__, 256 [listeners = std::move(mPrintProgressListeners)]( 257 const mozilla::gfx::PrintEndDocumentPromise::ResolveOrRejectValue& 258 aResult) { 259 // Printing isn't interrupted, so we don't need the device context 260 // here. 261 RefPtr<nsDeviceContext> empty; 262 if (aResult.IsResolve()) { 263 Cleanup(listeners, empty, /* aPrintingInterrupted = */ false, 264 NS_OK); 265 } else { 266 Cleanup(listeners, empty, /* aPrintingInterrupted = */ false, 267 aResult.RejectValue()); 268 } 269 }); 270 mStatus = NS_OK; 271 } 272 273 mIsDoingPrinting = false; 274 275 (void)Send__delete__(this); 276 return IPC_OK(); 277 } 278 279 mozilla::ipc::IPCResult RemotePrintJobParent::RecvAbortPrint( 280 const nsresult& aRv) { 281 PROFILER_MARKER_TEXT("RemotePrintJobParent", LAYOUT_Printing, {}, 282 "RemotePrintJobParent::RecvAbortPrint"_ns); 283 284 // Leave the cleanup to `ActorDestroy()`. 285 (void)Send__delete__(this); 286 return IPC_OK(); 287 } 288 289 mozilla::ipc::IPCResult RemotePrintJobParent::RecvProgressChange( 290 const long& aCurSelfProgress, const long& aMaxSelfProgress, 291 const long& aCurTotalProgress, const long& aMaxTotalProgress) { 292 PROFILER_MARKER_TEXT("RemotePrintJobParent", LAYOUT_Printing, {}, 293 "RemotePrintJobParent::RecvProgressChange"_ns); 294 // Our progress follows that of `RemotePrintJobChild` closely enough - forward 295 // it instead of keeping more state variables here. 296 for (auto* listener : mPrintProgressListeners) { 297 listener->OnProgressChange(nullptr, nullptr, aCurSelfProgress, 298 aMaxSelfProgress, aCurTotalProgress, 299 aMaxTotalProgress); 300 } 301 return IPC_OK(); 302 } 303 304 mozilla::ipc::IPCResult RemotePrintJobParent::RecvStatusChange( 305 const nsresult& aStatus) { 306 PROFILER_MARKER_TEXT("RemotePrintJobParent", LAYOUT_Printing, {}, 307 "RemotePrintJobParent::RecvProgressChange"_ns); 308 if (NS_FAILED(aStatus)) { 309 // Remember the failure status for cleanup to forward to listeners. 310 mStatus = aStatus; 311 } 312 313 return IPC_OK(); 314 } 315 316 void RemotePrintJobParent::RegisterListener(nsIWebProgressListener* aListener) { 317 MOZ_ASSERT(aListener); 318 319 // Our listener is a Promise created by CanonicalBrowsingContext::Print 320 mPrintProgressListeners.AppendElement(aListener); 321 } 322 323 already_AddRefed<nsIPrintSettings> RemotePrintJobParent::GetPrintSettings() { 324 nsCOMPtr<nsIPrintSettings> printSettings = mPrintSettings; 325 return printSettings.forget(); 326 } 327 328 RemotePrintJobParent::~RemotePrintJobParent() { 329 MOZ_COUNT_DTOR(RemotePrintJobParent); 330 } 331 332 void RemotePrintJobParent::ActorDestroy(ActorDestroyReason aWhy) { 333 if (MOZ_UNLIKELY(mIsDoingPrinting && NS_SUCCEEDED(mStatus))) { 334 mStatus = NS_ERROR_UNEXPECTED; 335 } 336 Cleanup(mPrintProgressListeners, mPrintDeviceContext, mIsDoingPrinting, 337 mStatus); 338 // At any rate, this actor is done and cleaned up. 339 mIsDoingPrinting = false; 340 } 341 342 } // namespace mozilla::layout