summaryrefslogtreecommitdiffstats
path: root/layout/printing/ipc/RemotePrintJobParent.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/printing/ipc/RemotePrintJobParent.cpp')
-rw-r--r--layout/printing/ipc/RemotePrintJobParent.cpp316
1 files changed, 316 insertions, 0 deletions
diff --git a/layout/printing/ipc/RemotePrintJobParent.cpp b/layout/printing/ipc/RemotePrintJobParent.cpp
new file mode 100644
index 0000000000..8b3d5d5bd9
--- /dev/null
+++ b/layout/printing/ipc/RemotePrintJobParent.cpp
@@ -0,0 +1,316 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "RemotePrintJobParent.h"
+
+#include <fstream>
+
+#include "gfxContext.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Unused.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsComponentManagerUtils.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDeviceContext.h"
+#include "nsIDeviceContextSpec.h"
+#include "nsIPrintSettings.h"
+#include "nsIWebProgressListener.h"
+#include "PrintTranslator.h"
+#include "private/pprio.h"
+#include "nsAnonymousTemporaryFile.h"
+
+namespace mozilla::layout {
+
+RemotePrintJobParent::RemotePrintJobParent(nsIPrintSettings* aPrintSettings)
+ : mPrintSettings(aPrintSettings),
+ mIsDoingPrinting(false),
+ mStatus(NS_ERROR_UNEXPECTED) {
+ MOZ_COUNT_CTOR(RemotePrintJobParent);
+}
+
+mozilla::ipc::IPCResult RemotePrintJobParent::RecvInitializePrint(
+ const nsAString& aDocumentTitle, const int32_t& aStartPage,
+ const int32_t& aEndPage) {
+ nsresult rv = InitializePrintDevice(aDocumentTitle, aStartPage, aEndPage);
+ if (NS_FAILED(rv)) {
+ Unused << SendPrintInitializationResult(rv, FileDescriptor());
+ mStatus = rv;
+ Unused << Send__delete__(this);
+ return IPC_OK();
+ }
+
+ mPrintTranslator.reset(new PrintTranslator(mPrintDeviceContext));
+ FileDescriptor fd;
+ rv = PrepareNextPageFD(&fd);
+ if (NS_FAILED(rv)) {
+ Unused << SendPrintInitializationResult(rv, FileDescriptor());
+ mStatus = rv;
+ Unused << Send__delete__(this);
+ return IPC_OK();
+ }
+
+ Unused << SendPrintInitializationResult(NS_OK, fd);
+ return IPC_OK();
+}
+
+nsresult RemotePrintJobParent::InitializePrintDevice(
+ const nsAString& aDocumentTitle, const int32_t& aStartPage,
+ const int32_t& aEndPage) {
+ nsresult rv;
+ nsCOMPtr<nsIDeviceContextSpec> deviceContextSpec =
+ do_CreateInstance("@mozilla.org/gfx/devicecontextspec;1", &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = deviceContextSpec->Init(mPrintSettings, false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mPrintDeviceContext = new nsDeviceContext();
+ rv = mPrintDeviceContext->InitForPrinting(deviceContextSpec);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoString fileName;
+ mPrintSettings->GetToFileName(fileName);
+
+ rv = mPrintDeviceContext->BeginDocument(aDocumentTitle, fileName, aStartPage,
+ aEndPage);
+ if (NS_FAILED(rv)) {
+ NS_WARNING_ASSERTION(rv == NS_ERROR_ABORT,
+ "Failed to initialize print device");
+ return rv;
+ }
+
+ mIsDoingPrinting = true;
+
+ return NS_OK;
+}
+
+nsresult RemotePrintJobParent::PrepareNextPageFD(FileDescriptor* aFd) {
+ PRFileDesc* prFd = nullptr;
+ nsresult rv = NS_OpenAnonymousTemporaryFile(&prFd);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ *aFd = FileDescriptor(
+ FileDescriptor::PlatformHandleType(PR_FileDesc2NativeHandle(prFd)));
+ mCurrentPageStream.OpenFD(prFd);
+ return NS_OK;
+}
+
+mozilla::ipc::IPCResult RemotePrintJobParent::RecvProcessPage(
+ nsTArray<uint64_t>&& aDeps) {
+ if (!mCurrentPageStream.IsOpen()) {
+ Unused << SendAbortPrint(NS_ERROR_FAILURE);
+ return IPC_OK();
+ }
+ mCurrentPageStream.Seek(0, PR_SEEK_SET);
+
+ if (aDeps.IsEmpty()) {
+ FinishProcessingPage();
+ return IPC_OK();
+ }
+
+ nsTHashSet<uint64_t> deps;
+ for (auto i : aDeps) {
+ deps.Insert(i);
+ }
+
+ gfx::CrossProcessPaint::Start(std::move(deps))
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self = RefPtr{this}](
+ gfx::CrossProcessPaint::ResolvedFragmentMap&& aFragments) {
+ self->FinishProcessingPage(&aFragments);
+ },
+ [self = RefPtr{this}](const nsresult& aRv) {
+ self->FinishProcessingPage();
+ });
+
+ return IPC_OK();
+}
+
+void RemotePrintJobParent::FinishProcessingPage(
+ gfx::CrossProcessPaint::ResolvedFragmentMap* aFragments) {
+ nsresult rv = PrintPage(mCurrentPageStream, aFragments);
+
+ mCurrentPageStream.Close();
+
+ PageDone(rv);
+}
+
+nsresult RemotePrintJobParent::PrintPage(
+ PRFileDescStream& aRecording,
+ gfx::CrossProcessPaint::ResolvedFragmentMap* aFragments) {
+ MOZ_ASSERT(mPrintDeviceContext);
+
+ nsresult rv = mPrintDeviceContext->BeginPage();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ if (aFragments) {
+ mPrintTranslator->SetDependentSurfaces(aFragments);
+ }
+ if (!mPrintTranslator->TranslateRecording(aRecording)) {
+ mPrintTranslator->SetDependentSurfaces(nullptr);
+ return NS_ERROR_FAILURE;
+ }
+ mPrintTranslator->SetDependentSurfaces(nullptr);
+
+ rv = mPrintDeviceContext->EndPage();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void RemotePrintJobParent::PageDone(nsresult aResult) {
+ MOZ_ASSERT(mIsDoingPrinting);
+
+ if (NS_FAILED(aResult)) {
+ Unused << SendAbortPrint(aResult);
+ } else {
+ FileDescriptor fd;
+ aResult = PrepareNextPageFD(&fd);
+ if (NS_FAILED(aResult)) {
+ Unused << SendAbortPrint(aResult);
+ }
+
+ Unused << SendPageProcessed(fd);
+ }
+}
+
+static void NotifyStatusChange(
+ const nsCOMArray<nsIWebProgressListener>& aListeners, nsresult aStatus) {
+ uint32_t numberOfListeners = aListeners.Length();
+ for (uint32_t i = 0; i < numberOfListeners; ++i) {
+ nsIWebProgressListener* listener = aListeners[static_cast<int32_t>(i)];
+ listener->OnStatusChange(nullptr, nullptr, aStatus, nullptr);
+ }
+}
+
+static void NotifyStateChange(
+ const nsCOMArray<nsIWebProgressListener>& aListeners, long aStateFlags,
+ nsresult aStatus) {
+ uint32_t numberOfListeners = aListeners.Length();
+ for (uint32_t i = 0; i < numberOfListeners; ++i) {
+ nsIWebProgressListener* listener = aListeners[static_cast<int32_t>(i)];
+ listener->OnStateChange(nullptr, nullptr, aStateFlags, aStatus);
+ }
+}
+
+static void Cleanup(const nsCOMArray<nsIWebProgressListener>& aListeners,
+ RefPtr<nsDeviceContext>& aAbortContext,
+ const bool aPrintingInterrupted, const nsresult aResult) {
+ auto result = aResult;
+ if (MOZ_UNLIKELY(aPrintingInterrupted && NS_SUCCEEDED(result))) {
+ result = NS_ERROR_UNEXPECTED;
+ }
+ if (NS_FAILED(result)) {
+ NotifyStatusChange(aListeners, result);
+ }
+ if (aPrintingInterrupted && aAbortContext) {
+ // Abort any started print.
+ Unused << aAbortContext->AbortDocument();
+ }
+ // However the print went, let the listeners know that we're done.
+ NotifyStateChange(aListeners,
+ nsIWebProgressListener::STATE_STOP |
+ nsIWebProgressListener::STATE_IS_DOCUMENT,
+ result);
+}
+
+mozilla::ipc::IPCResult RemotePrintJobParent::RecvFinalizePrint() {
+ // EndDocument is sometimes called in the child even when BeginDocument has
+ // not been called. See bug 1223332.
+ if (mPrintDeviceContext) {
+ mPrintDeviceContext->EndDocument()->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [listeners = std::move(mPrintProgressListeners)](
+ const mozilla::gfx::PrintEndDocumentPromise::ResolveOrRejectValue&
+ aResult) {
+ // Printing isn't interrupted, so we don't need the device context
+ // here.
+ RefPtr<nsDeviceContext> empty;
+ if (aResult.IsResolve()) {
+ Cleanup(listeners, empty, /* aPrintingInterrupted = */ false,
+ NS_OK);
+ } else {
+ Cleanup(listeners, empty, /* aPrintingInterrupted = */ false,
+ aResult.RejectValue());
+ }
+ });
+ mStatus = NS_OK;
+ }
+
+ mIsDoingPrinting = false;
+
+ Unused << Send__delete__(this);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult RemotePrintJobParent::RecvAbortPrint(
+ const nsresult& aRv) {
+ // Leave the cleanup to `ActorDestroy()`.
+ Unused << Send__delete__(this);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult RemotePrintJobParent::RecvProgressChange(
+ const long& aCurSelfProgress, const long& aMaxSelfProgress,
+ const long& aCurTotalProgress, const long& aMaxTotalProgress) {
+ // Our progress follows that of `RemotePrintJobChild` closely enough - forward
+ // it instead of keeping more state variables here.
+ for (auto* listener : mPrintProgressListeners) {
+ listener->OnProgressChange(nullptr, nullptr, aCurSelfProgress,
+ aMaxSelfProgress, aCurTotalProgress,
+ aMaxTotalProgress);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult RemotePrintJobParent::RecvStatusChange(
+ const nsresult& aStatus) {
+ if (NS_FAILED(aStatus)) {
+ // Remember the failure status for cleanup to forward to listeners.
+ mStatus = aStatus;
+ }
+
+ return IPC_OK();
+}
+
+void RemotePrintJobParent::RegisterListener(nsIWebProgressListener* aListener) {
+ MOZ_ASSERT(aListener);
+
+ // Our listener is a Promise created by CanonicalBrowsingContext::Print
+ mPrintProgressListeners.AppendElement(aListener);
+}
+
+already_AddRefed<nsIPrintSettings> RemotePrintJobParent::GetPrintSettings() {
+ nsCOMPtr<nsIPrintSettings> printSettings = mPrintSettings;
+ return printSettings.forget();
+}
+
+RemotePrintJobParent::~RemotePrintJobParent() {
+ MOZ_COUNT_DTOR(RemotePrintJobParent);
+}
+
+void RemotePrintJobParent::ActorDestroy(ActorDestroyReason aWhy) {
+ if (MOZ_UNLIKELY(mIsDoingPrinting && NS_SUCCEEDED(mStatus))) {
+ mStatus = NS_ERROR_UNEXPECTED;
+ }
+ Cleanup(mPrintProgressListeners, mPrintDeviceContext, mIsDoingPrinting,
+ mStatus);
+ // At any rate, this actor is done and cleaned up.
+ mIsDoingPrinting = false;
+}
+
+} // namespace mozilla::layout