summaryrefslogtreecommitdiffstats
path: root/layout/printing
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /layout/printing
parentInitial commit. (diff)
downloadfirefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz
firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'layout/printing')
-rw-r--r--layout/printing/DrawEventRecorder.cpp51
-rw-r--r--layout/printing/DrawEventRecorder.h167
-rw-r--r--layout/printing/PrintTranslator.cpp107
-rw-r--r--layout/printing/PrintTranslator.h182
-rw-r--r--layout/printing/crashtests/1662259.html7
-rw-r--r--layout/printing/crashtests/1671503.html12
-rw-r--r--layout/printing/crashtests/509839-1.html10
-rw-r--r--layout/printing/crashtests/509839-2.html8
-rw-r--r--layout/printing/crashtests/576878.xhtml9
-rw-r--r--layout/printing/crashtests/793844.html10
-rw-r--r--layout/printing/crashtests/crashtests.list6
-rw-r--r--layout/printing/ipc/PRemotePrintJob.ipdl60
-rw-r--r--layout/printing/ipc/RemotePrintJobChild.cpp167
-rw-r--r--layout/printing/ipc/RemotePrintJobChild.h65
-rw-r--r--layout/printing/ipc/RemotePrintJobParent.cpp295
-rw-r--r--layout/printing/ipc/RemotePrintJobParent.h102
-rw-r--r--layout/printing/moz.build40
-rw-r--r--layout/printing/nsIPrintProgress.idl43
-rw-r--r--layout/printing/nsIPrintProgressParams.idl14
-rw-r--r--layout/printing/nsPagePrintTimer.cpp203
-rw-r--r--layout/printing/nsPagePrintTimer.h92
-rw-r--r--layout/printing/nsPrintData.cpp110
-rw-r--r--layout/printing/nsPrintData.h82
-rw-r--r--layout/printing/nsPrintJob.cpp3059
-rw-r--r--layout/printing/nsPrintJob.h308
-rw-r--r--layout/printing/nsPrintObject.cpp136
-rw-r--r--layout/printing/nsPrintObject.h86
27 files changed, 5431 insertions, 0 deletions
diff --git a/layout/printing/DrawEventRecorder.cpp b/layout/printing/DrawEventRecorder.cpp
new file mode 100644
index 0000000000..69b9b48ae6
--- /dev/null
+++ b/layout/printing/DrawEventRecorder.cpp
@@ -0,0 +1,51 @@
+/* -*- 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 "DrawEventRecorder.h"
+
+namespace mozilla {
+namespace layout {
+
+void DrawEventRecorderPRFileDesc::RecordEvent(
+ const gfx::RecordedEvent& aEvent) {
+ aEvent.RecordToStream(mOutputStream);
+
+ Flush();
+}
+
+DrawEventRecorderPRFileDesc::~DrawEventRecorderPRFileDesc() {
+ if (IsOpen()) {
+ Close();
+ }
+}
+
+void DrawEventRecorderPRFileDesc::Flush() { mOutputStream.Flush(); }
+
+bool DrawEventRecorderPRFileDesc::IsOpen() { return mOutputStream.IsOpen(); }
+
+void DrawEventRecorderPRFileDesc::OpenFD(PRFileDesc* aFd) {
+ MOZ_DIAGNOSTIC_ASSERT(!IsOpen());
+
+ mOutputStream.OpenFD(aFd);
+ WriteHeader(mOutputStream);
+}
+
+void DrawEventRecorderPRFileDesc::Close() {
+ MOZ_DIAGNOSTIC_ASSERT(IsOpen());
+
+ mOutputStream.Close();
+}
+
+void DrawEventRecorderPRFileDesc::AddDependentSurface(uint64_t aDependencyId) {
+ mDependentSurfaces.AppendElement(aDependencyId);
+}
+
+nsTArray<uint64_t>&& DrawEventRecorderPRFileDesc::TakeDependentSurfaces() {
+ return std::move(mDependentSurfaces);
+}
+
+} // namespace layout
+} // namespace mozilla
diff --git a/layout/printing/DrawEventRecorder.h b/layout/printing/DrawEventRecorder.h
new file mode 100644
index 0000000000..79eaa65ef4
--- /dev/null
+++ b/layout/printing/DrawEventRecorder.h
@@ -0,0 +1,167 @@
+/* -*- 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/. */
+
+#ifndef mozilla_layout_printing_DrawEventRecorder_h
+#define mozilla_layout_printing_DrawEventRecorder_h
+
+#include <memory>
+
+#include "mozilla/gfx/DrawEventRecorder.h"
+#include "mozilla/gfx/RecordingTypes.h"
+#include "prio.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace layout {
+
+class PRFileDescStream final : public mozilla::gfx::EventStream {
+ // Most writes, as seen in the print IPC use case, are very small (<32 bytes),
+ // with a small number of very large (>40KB) writes. Writes larger than this
+ // value are not buffered.
+ static const size_t kBufferSize = 1024;
+
+ public:
+ PRFileDescStream()
+ : mFd(nullptr), mBuffer(nullptr), mBufferPos(0), mGood(true) {}
+ PRFileDescStream(const PRFileDescStream& other) = delete;
+ ~PRFileDescStream() { Close(); }
+
+ void OpenFD(PRFileDesc* aFd) {
+ MOZ_DIAGNOSTIC_ASSERT(!IsOpen());
+ mFd = aFd;
+ mGood = !!mFd;
+ mBuffer.reset(new uint8_t[kBufferSize]);
+ mBufferPos = 0;
+ }
+
+ void Close() {
+ // We need to be API compatible with std::ostream, and so we silently handle
+ // closes on a closed FD.
+ if (IsOpen()) {
+ Flush();
+ PR_Close(mFd);
+ mFd = nullptr;
+ mBuffer.reset();
+ mBufferPos = 0;
+ }
+ }
+
+ bool IsOpen() { return mFd != nullptr; }
+
+ void Flush() {
+ // See comment in Close().
+ if (IsOpen() && mBufferPos > 0) {
+ PRInt32 length =
+ PR_Write(mFd, static_cast<const void*>(mBuffer.get()), mBufferPos);
+ mGood = length >= 0 && static_cast<size_t>(length) == mBufferPos;
+ mBufferPos = 0;
+ }
+ }
+
+ void Seek(PRInt64 aOffset, PRSeekWhence aWhence) {
+ Flush();
+ PRInt64 pos = PR_Seek64(mFd, aOffset, aWhence);
+ mGood = pos != -1;
+ }
+
+ void write(const char* aData, size_t aSize) override {
+ if (!good()) {
+ return;
+ }
+
+ // See comment in Close().
+ if (IsOpen()) {
+ // If we're writing more data than could ever fit in our buffer, flush the
+ // buffer and write directly.
+ if (aSize > kBufferSize) {
+ Flush();
+ PRInt32 length = PR_Write(mFd, static_cast<const void*>(aData), aSize);
+ mGood = length >= 0 && static_cast<size_t>(length) == aSize;
+ // If our write could fit in our buffer, but doesn't because the buffer
+ // is partially full, write to the buffer, flush the buffer, and then
+ // write the rest of the data to the buffer.
+ } else if (aSize > AvailableBufferSpace()) {
+ size_t length = AvailableBufferSpace();
+ WriteToBuffer(aData, length);
+ Flush();
+
+ WriteToBuffer(aData + length, aSize - length);
+ // Write fits in the buffer.
+ } else {
+ WriteToBuffer(aData, aSize);
+ }
+ }
+ }
+
+ void read(char* aOut, size_t aSize) override {
+ if (!good()) {
+ return;
+ }
+
+ Flush();
+ PRInt32 res = PR_Read(mFd, static_cast<void*>(aOut), aSize);
+ mGood = res >= 0 && (static_cast<size_t>(res) == aSize);
+ }
+
+ bool good() final { return mGood; }
+
+ void SetIsBad() final { mGood = false; }
+
+ private:
+ size_t AvailableBufferSpace() { return kBufferSize - mBufferPos; }
+
+ void WriteToBuffer(const char* aData, size_t aSize) {
+ MOZ_ASSERT(aSize <= AvailableBufferSpace());
+ memcpy(mBuffer.get() + mBufferPos, aData, aSize);
+ mBufferPos += aSize;
+ }
+
+ PRFileDesc* mFd;
+ std::unique_ptr<uint8_t[]> mBuffer;
+ size_t mBufferPos;
+ bool mGood;
+};
+
+class DrawEventRecorderPRFileDesc final : public gfx::DrawEventRecorderPrivate {
+ public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawEventRecorderPRFileDesc, override)
+ explicit DrawEventRecorderPRFileDesc() = default;
+ ~DrawEventRecorderPRFileDesc();
+
+ void RecordEvent(const gfx::RecordedEvent& aEvent) override;
+
+ /**
+ * Returns whether a recording file is currently open.
+ */
+ bool IsOpen();
+
+ /**
+ * Opens the recorder with the provided PRFileDesc *.
+ */
+ void OpenFD(PRFileDesc* aFd);
+
+ /**
+ * Closes the file so that it can be processed. The recorder does NOT forget
+ * which objects it has recorded. This can be used with OpenNew, so that a
+ * recording can be processed in chunks. The file must be open.
+ */
+ void Close();
+
+ void AddDependentSurface(uint64_t aDependencyId) override;
+ nsTArray<uint64_t>&& TakeDependentSurfaces();
+
+ private:
+ void Flush() override;
+
+ PRFileDescStream mOutputStream;
+ nsTArray<uint64_t> mDependentSurfaces;
+};
+
+} // namespace layout
+
+} // namespace mozilla
+
+#endif /* mozilla_layout_printing_DrawEventRecorder_h */
diff --git a/layout/printing/PrintTranslator.cpp b/layout/printing/PrintTranslator.cpp
new file mode 100644
index 0000000000..86eddfaf38
--- /dev/null
+++ b/layout/printing/PrintTranslator.cpp
@@ -0,0 +1,107 @@
+/* -*- 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 "PrintTranslator.h"
+
+#include "gfxContext.h"
+#include "nsDeviceContext.h"
+#include "mozilla/gfx/RecordedEvent.h"
+#include "mozilla/gfx/RecordingTypes.h"
+#include "mozilla/UniquePtr.h"
+#include "InlineTranslator.h"
+
+using namespace mozilla::gfx;
+
+namespace mozilla {
+namespace layout {
+
+PrintTranslator::PrintTranslator(nsDeviceContext* aDeviceContext)
+ : mDeviceContext(aDeviceContext) {
+ RefPtr<gfxContext> context =
+ mDeviceContext->CreateReferenceRenderingContext();
+ mBaseDT = context->GetDrawTarget();
+}
+
+bool PrintTranslator::TranslateRecording(PRFileDescStream& aRecording) {
+ uint32_t magicInt;
+ ReadElement(aRecording, magicInt);
+ if (magicInt != mozilla::gfx::kMagicInt) {
+ return false;
+ }
+
+ uint16_t majorRevision;
+ ReadElement(aRecording, majorRevision);
+ if (majorRevision != kMajorRevision) {
+ return false;
+ }
+
+ uint16_t minorRevision;
+ ReadElement(aRecording, minorRevision);
+ if (minorRevision > kMinorRevision) {
+ return false;
+ }
+
+ int32_t eventType;
+ ReadElement(aRecording, eventType);
+ while (aRecording.good()) {
+ bool success = RecordedEvent::DoWithEventFromStream(
+ aRecording, static_cast<RecordedEvent::EventType>(eventType),
+ [&](RecordedEvent* recordedEvent) -> bool {
+ // Make sure that the whole event was read from the stream.
+ if (!aRecording.good()) {
+ return false;
+ }
+
+ return recordedEvent->PlayEvent(this);
+ });
+
+ if (!success) {
+ return false;
+ }
+
+ ReadElement(aRecording, eventType);
+ }
+
+ return true;
+}
+
+already_AddRefed<DrawTarget> PrintTranslator::CreateDrawTarget(
+ ReferencePtr aRefPtr, const gfx::IntSize& aSize,
+ gfx::SurfaceFormat aFormat) {
+ RefPtr<gfxContext> context = mDeviceContext->CreateRenderingContext();
+ if (!context) {
+ NS_WARNING("Failed to create rendering context for print.");
+ return nullptr;
+ }
+
+ RefPtr<DrawTarget> drawTarget = context->GetDrawTarget();
+ AddDrawTarget(aRefPtr, drawTarget);
+ return drawTarget.forget();
+}
+
+already_AddRefed<SourceSurface> PrintTranslator::LookupExternalSurface(
+ uint64_t aKey) {
+ RefPtr<RecordedDependentSurface> surface = mDependentSurfaces.Get(aKey);
+ if (!surface) {
+ return nullptr;
+ }
+
+ RefPtr<DrawTarget> newDT = GetReferenceDrawTarget()->CreateSimilarDrawTarget(
+ surface->mSize, SurfaceFormat::B8G8R8A8);
+
+ InlineTranslator translator(newDT, nullptr);
+ translator.SetDependentSurfaces(&mDependentSurfaces);
+ if (!translator.TranslateRecording((char*)surface->mRecording.mData,
+ surface->mRecording.mLen)) {
+ return nullptr;
+ }
+
+ RefPtr<SourceSurface> snapshot = newDT->Snapshot();
+ return snapshot.forget();
+}
+
+} // namespace layout
+} // namespace mozilla
diff --git a/layout/printing/PrintTranslator.h b/layout/printing/PrintTranslator.h
new file mode 100644
index 0000000000..eb911901bc
--- /dev/null
+++ b/layout/printing/PrintTranslator.h
@@ -0,0 +1,182 @@
+/* -*- 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/. */
+
+#ifndef mozilla_layout_PrintTranslator_h
+#define mozilla_layout_PrintTranslator_h
+
+#include <istream>
+
+#include "DrawEventRecorder.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Filters.h"
+#include "mozilla/gfx/RecordedEvent.h"
+#include "nsRefPtrHashtable.h"
+
+class nsDeviceContext;
+
+namespace mozilla {
+namespace layout {
+
+using gfx::DrawTarget;
+using gfx::FilterNode;
+using gfx::GradientStops;
+using gfx::NativeFontResource;
+using gfx::Path;
+using gfx::RecordedDependentSurface;
+using gfx::ReferencePtr;
+using gfx::ScaledFont;
+using gfx::SourceSurface;
+using gfx::Translator;
+using gfx::UnscaledFont;
+
+class PrintTranslator final : public Translator {
+ public:
+ explicit PrintTranslator(nsDeviceContext* aDeviceContext);
+
+ bool TranslateRecording(PRFileDescStream& aRecording);
+
+ void SetDependentSurfaces(
+ nsRefPtrHashtable<nsUint64HashKey, RecordedDependentSurface>&&
+ aDependentSurfaces) {
+ mDependentSurfaces = std::move(aDependentSurfaces);
+ }
+
+ DrawTarget* LookupDrawTarget(ReferencePtr aRefPtr) final {
+ DrawTarget* result = mDrawTargets.GetWeak(aRefPtr);
+ MOZ_ASSERT(result);
+ return result;
+ }
+
+ Path* LookupPath(ReferencePtr aRefPtr) final {
+ Path* result = mPaths.GetWeak(aRefPtr);
+ MOZ_ASSERT(result);
+ return result;
+ }
+
+ SourceSurface* LookupSourceSurface(ReferencePtr aRefPtr) final {
+ SourceSurface* result = mSourceSurfaces.GetWeak(aRefPtr);
+ MOZ_ASSERT(result);
+ return result;
+ }
+
+ FilterNode* LookupFilterNode(ReferencePtr aRefPtr) final {
+ FilterNode* result = mFilterNodes.GetWeak(aRefPtr);
+ MOZ_ASSERT(result);
+ return result;
+ }
+
+ GradientStops* LookupGradientStops(ReferencePtr aRefPtr) final {
+ GradientStops* result = mGradientStops.GetWeak(aRefPtr);
+ MOZ_ASSERT(result);
+ return result;
+ }
+
+ ScaledFont* LookupScaledFont(ReferencePtr aRefPtr) final {
+ ScaledFont* result = mScaledFonts.GetWeak(aRefPtr);
+ MOZ_ASSERT(result);
+ return result;
+ }
+
+ UnscaledFont* LookupUnscaledFont(ReferencePtr aRefPtr) final {
+ UnscaledFont* result = mUnscaledFonts.GetWeak(aRefPtr);
+ MOZ_ASSERT(result);
+ return result;
+ }
+
+ NativeFontResource* LookupNativeFontResource(uint64_t aKey) final {
+ NativeFontResource* result = mNativeFontResources.GetWeak(aKey);
+ MOZ_ASSERT(result);
+ return result;
+ }
+
+ already_AddRefed<SourceSurface> LookupExternalSurface(uint64_t aKey) final;
+
+ void AddDrawTarget(ReferencePtr aRefPtr, DrawTarget* aDT) final {
+ mDrawTargets.Put(aRefPtr, RefPtr{aDT});
+ }
+
+ void AddPath(ReferencePtr aRefPtr, Path* aPath) final {
+ mPaths.Put(aRefPtr, RefPtr{aPath});
+ }
+
+ void AddSourceSurface(ReferencePtr aRefPtr, SourceSurface* aSurface) final {
+ mSourceSurfaces.Put(aRefPtr, RefPtr{aSurface});
+ }
+
+ void AddFilterNode(ReferencePtr aRefPtr, FilterNode* aFilter) final {
+ mFilterNodes.Put(aRefPtr, RefPtr{aFilter});
+ }
+
+ void AddGradientStops(ReferencePtr aRefPtr, GradientStops* aStops) final {
+ mGradientStops.Put(aRefPtr, RefPtr{aStops});
+ }
+
+ void AddScaledFont(ReferencePtr aRefPtr, ScaledFont* aScaledFont) final {
+ mScaledFonts.Put(aRefPtr, RefPtr{aScaledFont});
+ }
+
+ void AddUnscaledFont(ReferencePtr aRefPtr,
+ UnscaledFont* aUnscaledFont) final {
+ mUnscaledFonts.Put(aRefPtr, RefPtr{aUnscaledFont});
+ }
+
+ void AddNativeFontResource(uint64_t aKey,
+ NativeFontResource* aScaledFontResouce) final {
+ mNativeFontResources.Put(aKey, RefPtr{aScaledFontResouce});
+ }
+
+ void RemoveDrawTarget(ReferencePtr aRefPtr) final {
+ mDrawTargets.Remove(aRefPtr);
+ }
+
+ void RemovePath(ReferencePtr aRefPtr) final { mPaths.Remove(aRefPtr); }
+
+ void RemoveSourceSurface(ReferencePtr aRefPtr) final {
+ mSourceSurfaces.Remove(aRefPtr);
+ }
+
+ void RemoveFilterNode(ReferencePtr aRefPtr) final {
+ mFilterNodes.Remove(aRefPtr);
+ }
+
+ void RemoveGradientStops(ReferencePtr aRefPtr) final {
+ mGradientStops.Remove(aRefPtr);
+ }
+
+ void RemoveScaledFont(ReferencePtr aRefPtr) final {
+ mScaledFonts.Remove(aRefPtr);
+ }
+
+ void RemoveUnscaledFont(ReferencePtr aRefPtr) final {
+ mUnscaledFonts.Remove(aRefPtr);
+ }
+
+ already_AddRefed<DrawTarget> CreateDrawTarget(
+ ReferencePtr aRefPtr, const gfx::IntSize& aSize,
+ gfx::SurfaceFormat aFormat) final;
+
+ mozilla::gfx::DrawTarget* GetReferenceDrawTarget() final { return mBaseDT; }
+
+ private:
+ RefPtr<nsDeviceContext> mDeviceContext;
+ RefPtr<DrawTarget> mBaseDT;
+
+ nsRefPtrHashtable<nsPtrHashKey<void>, DrawTarget> mDrawTargets;
+ nsRefPtrHashtable<nsPtrHashKey<void>, Path> mPaths;
+ nsRefPtrHashtable<nsPtrHashKey<void>, SourceSurface> mSourceSurfaces;
+ nsRefPtrHashtable<nsPtrHashKey<void>, FilterNode> mFilterNodes;
+ nsRefPtrHashtable<nsPtrHashKey<void>, GradientStops> mGradientStops;
+ nsRefPtrHashtable<nsPtrHashKey<void>, ScaledFont> mScaledFonts;
+ nsRefPtrHashtable<nsPtrHashKey<void>, UnscaledFont> mUnscaledFonts;
+ nsRefPtrHashtable<nsUint64HashKey, NativeFontResource> mNativeFontResources;
+ nsRefPtrHashtable<nsUint64HashKey, RecordedDependentSurface>
+ mDependentSurfaces;
+};
+
+} // namespace layout
+} // namespace mozilla
+
+#endif // mozilla_layout_PrintTranslator_h
diff --git a/layout/printing/crashtests/1662259.html b/layout/printing/crashtests/1662259.html
new file mode 100644
index 0000000000..0e5d338635
--- /dev/null
+++ b/layout/printing/crashtests/1662259.html
@@ -0,0 +1,7 @@
+<script>
+window.onload = () => {
+ SpecialPowers.wrap(window).printPreview()?.close()
+}
+</script>
+<body hidden>
+<iframe src='file:'>
diff --git a/layout/printing/crashtests/1671503.html b/layout/printing/crashtests/1671503.html
new file mode 100644
index 0000000000..68313e603f
--- /dev/null
+++ b/layout/printing/crashtests/1671503.html
@@ -0,0 +1,12 @@
+<html class="reftest-wait">
+<script>
+document.addEventListener("DOMContentLoaded", () => {
+ window.requestIdleCallback(() => {
+ SpecialPowers.wrap(window).printPreview()?.close()
+ SpecialPowers.wrap(window).printPreview()?.close()
+ document.documentElement.className = "";
+ })
+})
+</script>
+<iframe src='data:text/html,foo'></iframe>
+</html>
diff --git a/layout/printing/crashtests/509839-1.html b/layout/printing/crashtests/509839-1.html
new file mode 100644
index 0000000000..fefa9060bb
--- /dev/null
+++ b/layout/printing/crashtests/509839-1.html
@@ -0,0 +1,10 @@
+<html class="reftest-paged"><head>
+<title>Crash [@ nsViewManager::CreateView][@ nsCSSFrameConstructor::AddFrameConstructionItemsInternal] on closing print preview with -moz-transform, position: fixed table displays</title>
+</head><body>
+<div style="position: fixed;">
+<div style="display: table-header-group; -moz-transform: rotate(1deg);">
+<div style="position: relative;"></div>
+<div style="display: table-row;page-break-before: always;"></div>
+</div>
+</body>
+</html>
diff --git a/layout/printing/crashtests/509839-2.html b/layout/printing/crashtests/509839-2.html
new file mode 100644
index 0000000000..f04630246a
--- /dev/null
+++ b/layout/printing/crashtests/509839-2.html
@@ -0,0 +1,8 @@
+<html style="position: fixed; -moz-transform: scale(0.000001);" class="reftest-paged">
+<head>
+</head>
+<body>
+<div style="display: table-row;"></div>
+<div style="display: table-row; page-break-before: right;"></div>
+</body>
+</html>
diff --git a/layout/printing/crashtests/576878.xhtml b/layout/printing/crashtests/576878.xhtml
new file mode 100644
index 0000000000..428fed9307
--- /dev/null
+++ b/layout/printing/crashtests/576878.xhtml
@@ -0,0 +1,9 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:mathml="http://www.w3.org/1998/Math/MathML" class="reftest-paged">
+
+<mathml:mfenced style="position: fixed;">
+<span style="position: inherit; float: right;"><span style="position:fixed;">b</span></span>
+</mathml:mfenced>
+<div style="page-break-before: always; "/>
+
+m
+</html>
diff --git a/layout/printing/crashtests/793844.html b/layout/printing/crashtests/793844.html
new file mode 100644
index 0000000000..3165fc1794
--- /dev/null
+++ b/layout/printing/crashtests/793844.html
@@ -0,0 +1,10 @@
+<html class="reftest-paged">
+<head>
+<title>crash in nsContentList::nsContentList on print preview</title>
+</head><body>
+
+<iframe onload="window.location.reload()" src="data:text/html,">
+</iframe>
+
+
+</body></html>
diff --git a/layout/printing/crashtests/crashtests.list b/layout/printing/crashtests/crashtests.list
new file mode 100644
index 0000000000..958e095134
--- /dev/null
+++ b/layout/printing/crashtests/crashtests.list
@@ -0,0 +1,6 @@
+load 509839-1.html
+load 509839-2.html
+load 576878.xhtml
+asserts-if(Android,0-1) load 793844.html
+load 1662259.html
+skip-if(Android) load 1671503.html
diff --git a/layout/printing/ipc/PRemotePrintJob.ipdl b/layout/printing/ipc/PRemotePrintJob.ipdl
new file mode 100644
index 0000000000..bb1633e192
--- /dev/null
+++ b/layout/printing/ipc/PRemotePrintJob.ipdl
@@ -0,0 +1,60 @@
+/* -*- 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 protocol PPrinting;
+
+namespace mozilla {
+namespace layout {
+
+async refcounted protocol PRemotePrintJob
+{
+ manager PPrinting;
+
+both:
+ // Tell either side to abort printing and clean up.
+ async AbortPrint(nsresult aRv);
+
+parent:
+ // Initialize the real print device with the given information.
+ async InitializePrint(nsString aDocumentTitle, nsString aPrintToFile,
+ int32_t aStartPage, int32_t aEndPage);
+
+ // Translate the page recording writen into |fd| and play back the events to
+ // the real print device.
+ async ProcessPage(uint64_t[] deps);
+
+ // This informs the real print device that we've finished, so it can trigger
+ // the actual print.
+ async FinalizePrint();
+
+ // Report a state change to listeners in the parent process.
+ async StateChange(long aStateFlags,
+ nsresult aStatus);
+
+ // Report a progress change to listeners in the parent process.
+ async ProgressChange(long aCurSelfProgress,
+ long aMaxSelfProgress,
+ long aCurTotalProgress,
+ long aMaxTotalProgress);
+
+ // Report a status change to listeners in the parent process.
+ async StatusChange(nsresult aStatus);
+
+child:
+ // Inform the child that the print has been initialized in the parent or has
+ // failed with result aRv. Includes a file descriptor which the first page
+ // can be written to.
+ async PrintInitializationResult(nsresult aRv, FileDescriptor aFd);
+
+ // Inform the child that the latest page has been processed remotely. Includes
+ // a file descriptor which the next page can be written to.
+ async PageProcessed(FileDescriptor aFd);
+
+ async __delete__();
+};
+
+} // namespace layout
+} // namespace mozilla
diff --git a/layout/printing/ipc/RemotePrintJobChild.cpp b/layout/printing/ipc/RemotePrintJobChild.cpp
new file mode 100644
index 0000000000..a7b7f5cfaa
--- /dev/null
+++ b/layout/printing/ipc/RemotePrintJobChild.cpp
@@ -0,0 +1,167 @@
+/* -*- 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 "RemotePrintJobChild.h"
+
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/Unused.h"
+#include "nsPagePrintTimer.h"
+#include "nsPrintJob.h"
+#include "private/pprio.h"
+
+namespace mozilla {
+namespace layout {
+
+NS_IMPL_ISUPPORTS(RemotePrintJobChild, nsIWebProgressListener)
+
+RemotePrintJobChild::RemotePrintJobChild() = default;
+
+nsresult RemotePrintJobChild::InitializePrint(const nsString& aDocumentTitle,
+ const nsString& aPrintToFile,
+ const int32_t& aStartPage,
+ const int32_t& aEndPage) {
+ // Print initialization can sometimes display a dialog in the parent, so we
+ // need to spin a nested event loop until initialization completes.
+ Unused << SendInitializePrint(aDocumentTitle, aPrintToFile, aStartPage,
+ aEndPage);
+ mozilla::SpinEventLoopUntil([&]() { return mPrintInitialized; });
+
+ return mInitializationResult;
+}
+
+mozilla::ipc::IPCResult RemotePrintJobChild::RecvPrintInitializationResult(
+ const nsresult& aRv, const mozilla::ipc::FileDescriptor& aFd) {
+ mPrintInitialized = true;
+ mInitializationResult = aRv;
+ if (NS_SUCCEEDED(aRv)) {
+ SetNextPageFD(aFd);
+ }
+ return IPC_OK();
+}
+
+PRFileDesc* RemotePrintJobChild::GetNextPageFD() {
+ MOZ_ASSERT(mNextPageFD);
+ PRFileDesc* fd = mNextPageFD;
+ mNextPageFD = nullptr;
+ return fd;
+}
+
+void RemotePrintJobChild::SetNextPageFD(
+ const mozilla::ipc::FileDescriptor& aFd) {
+ auto handle = aFd.ClonePlatformHandle();
+ mNextPageFD = PR_ImportFile(PROsfd(handle.release()));
+}
+
+void RemotePrintJobChild::ProcessPage(nsTArray<uint64_t>&& aDeps) {
+ MOZ_ASSERT(mPagePrintTimer);
+
+ mPagePrintTimer->WaitForRemotePrint();
+ if (!mDestroyed) {
+ Unused << SendProcessPage(std::move(aDeps));
+ }
+}
+
+mozilla::ipc::IPCResult RemotePrintJobChild::RecvPageProcessed(
+ const mozilla::ipc::FileDescriptor& aFd) {
+ MOZ_ASSERT(mPagePrintTimer);
+ SetNextPageFD(aFd);
+
+ mPagePrintTimer->RemotePrintFinished();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult RemotePrintJobChild::RecvAbortPrint(
+ const nsresult& aRv) {
+ MOZ_ASSERT(mPrintJob);
+
+ mPrintJob->CleanupOnFailure(aRv, true);
+ return IPC_OK();
+}
+
+void RemotePrintJobChild::SetPagePrintTimer(nsPagePrintTimer* aPagePrintTimer) {
+ MOZ_ASSERT(aPagePrintTimer);
+
+ mPagePrintTimer = aPagePrintTimer;
+}
+
+void RemotePrintJobChild::SetPrintJob(nsPrintJob* aPrintJob) {
+ MOZ_ASSERT(aPrintJob);
+
+ mPrintJob = aPrintJob;
+}
+
+// nsIWebProgressListener
+
+NS_IMETHODIMP
+RemotePrintJobChild::OnStateChange(nsIWebProgress* aProgress,
+ nsIRequest* aRequest, uint32_t aStateFlags,
+ nsresult aStatus) {
+ if (!mDestroyed) {
+ Unused << SendStateChange(aStateFlags, aStatus);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RemotePrintJobChild::OnProgressChange(nsIWebProgress* aProgress,
+ nsIRequest* aRequest,
+ int32_t aCurSelfProgress,
+ int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress,
+ int32_t aMaxTotalProgress) {
+ if (!mDestroyed) {
+ Unused << SendProgressChange(aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RemotePrintJobChild::OnLocationChange(nsIWebProgress* aProgress,
+ nsIRequest* aRequest, nsIURI* aURI,
+ uint32_t aFlags) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RemotePrintJobChild::OnStatusChange(nsIWebProgress* aProgress,
+ nsIRequest* aRequest, nsresult aStatus,
+ const char16_t* aMessage) {
+ if (NS_SUCCEEDED(mInitializationResult) && !mDestroyed) {
+ Unused << SendStatusChange(aStatus);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RemotePrintJobChild::OnSecurityChange(nsIWebProgress* aProgress,
+ nsIRequest* aRequest, uint32_t aState) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RemotePrintJobChild::OnContentBlockingEvent(nsIWebProgress* aProgress,
+ nsIRequest* aRequest,
+ uint32_t aEvent) {
+ return NS_OK;
+}
+
+// End of nsIWebProgressListener
+
+RemotePrintJobChild::~RemotePrintJobChild() = default;
+
+void RemotePrintJobChild::ActorDestroy(ActorDestroyReason aWhy) {
+ mPagePrintTimer = nullptr;
+ mPrintJob = nullptr;
+
+ mDestroyed = true;
+}
+
+} // namespace layout
+} // namespace mozilla
diff --git a/layout/printing/ipc/RemotePrintJobChild.h b/layout/printing/ipc/RemotePrintJobChild.h
new file mode 100644
index 0000000000..7e046977b6
--- /dev/null
+++ b/layout/printing/ipc/RemotePrintJobChild.h
@@ -0,0 +1,65 @@
+/* -*- 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/. */
+
+#ifndef mozilla_layout_RemotePrintJobChild_h
+#define mozilla_layout_RemotePrintJobChild_h
+
+#include "mozilla/layout/PRemotePrintJobChild.h"
+
+#include "mozilla/RefPtr.h"
+#include "nsIWebProgressListener.h"
+
+class nsPagePrintTimer;
+class nsPrintJob;
+
+namespace mozilla {
+namespace layout {
+
+class RemotePrintJobChild final : public PRemotePrintJobChild,
+ public nsIWebProgressListener {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIWEBPROGRESSLISTENER
+
+ RemotePrintJobChild();
+
+ void ActorDestroy(ActorDestroyReason aWhy) final;
+
+ nsresult InitializePrint(const nsString& aDocumentTitle,
+ const nsString& aPrintToFile,
+ const int32_t& aStartPage, const int32_t& aEndPage);
+
+ mozilla::ipc::IPCResult RecvPrintInitializationResult(
+ const nsresult& aRv, const FileDescriptor& aFd) final;
+
+ void ProcessPage(nsTArray<uint64_t>&& aDeps);
+
+ mozilla::ipc::IPCResult RecvPageProcessed(const FileDescriptor& aFd) final;
+
+ mozilla::ipc::IPCResult RecvAbortPrint(const nsresult& aRv) final;
+
+ void SetPagePrintTimer(nsPagePrintTimer* aPagePrintTimer);
+
+ void SetPrintJob(nsPrintJob* aPrintJob);
+
+ PRFileDesc* GetNextPageFD();
+
+ private:
+ ~RemotePrintJobChild() final;
+ void SetNextPageFD(const mozilla::ipc::FileDescriptor& aFd);
+
+ bool mPrintInitialized = false;
+ bool mDestroyed = false;
+ nsresult mInitializationResult = NS_OK;
+ RefPtr<nsPagePrintTimer> mPagePrintTimer;
+ RefPtr<nsPrintJob> mPrintJob;
+ PRFileDesc* mNextPageFD = nullptr;
+};
+
+} // namespace layout
+} // namespace mozilla
+
+#endif // mozilla_layout_RemotePrintJobChild_h
diff --git a/layout/printing/ipc/RemotePrintJobParent.cpp b/layout/printing/ipc/RemotePrintJobParent.cpp
new file mode 100644
index 0000000000..4e9ca0ed0d
--- /dev/null
+++ b/layout/printing/ipc/RemotePrintJobParent.cpp
@@ -0,0 +1,295 @@
+/* -*- 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 {
+namespace layout {
+
+RemotePrintJobParent::RemotePrintJobParent(nsIPrintSettings* aPrintSettings)
+ : mPrintSettings(aPrintSettings), mIsDoingPrinting(false) {
+ MOZ_COUNT_CTOR(RemotePrintJobParent);
+}
+
+mozilla::ipc::IPCResult RemotePrintJobParent::RecvInitializePrint(
+ const nsString& aDocumentTitle, const nsString& aPrintToFile,
+ const int32_t& aStartPage, const int32_t& aEndPage) {
+ nsresult rv =
+ InitializePrintDevice(aDocumentTitle, aPrintToFile, aStartPage, aEndPage);
+ if (NS_FAILED(rv)) {
+ Unused << SendPrintInitializationResult(rv, FileDescriptor());
+ // Let any listeners know about the failure before we delete.
+ Unused << RecvStatusChange(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());
+ // Let any listeners know about the failure before we delete.
+ Unused << RecvStatusChange(rv);
+ Unused << Send__delete__(this);
+ return IPC_OK();
+ }
+
+ Unused << SendPrintInitializationResult(NS_OK, fd);
+ return IPC_OK();
+}
+
+nsresult RemotePrintJobParent::InitializePrintDevice(
+ const nsString& aDocumentTitle, const nsString& aPrintToFile,
+ 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(nullptr, 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;
+ }
+
+ rv = mPrintDeviceContext->BeginDocument(aDocumentTitle, aPrintToFile,
+ aStartPage, aEndPage);
+ if (NS_FAILED(rv)) {
+ NS_WARNING_ASSERTION(rv == NS_ERROR_ABORT,
+ "Failed to initialize print device");
+ return rv;
+ }
+
+ if (!mPrintDeviceContext->IsSyncPagePrinting()) {
+ mPrintDeviceContext->RegisterPageDoneCallback(
+ [self = RefPtr{this}](nsresult aResult) { self->PageDone(aResult); });
+ }
+
+ 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();
+ }
+
+ nsTHashtable<nsUint64HashKey> deps;
+ for (auto i : aDeps) {
+ deps.PutEntry(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();
+
+ if (mPrintDeviceContext->IsSyncPagePrinting()) {
+ 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(std::move(*aFragments));
+ }
+ if (!mPrintTranslator->TranslateRecording(aRecording)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ 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);
+ }
+}
+
+mozilla::ipc::IPCResult RemotePrintJobParent::RecvFinalizePrint() {
+ // EndDocument is sometimes called in the child even when BeginDocument has
+ // not been called. See bug 1223332.
+ if (mPrintDeviceContext) {
+ DebugOnly<nsresult> rv = mPrintDeviceContext->EndDocument();
+
+ // Too late to abort the child just log.
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EndDocument failed");
+
+ // Since RecvFinalizePrint is called after all page printed, there should
+ // be no more page-done callbacks after that, in theory. Unregistering
+ // page-done callback is not must have, but we still do this for safety.
+ mPrintDeviceContext->UnregisterPageDoneCallback();
+ }
+
+ mIsDoingPrinting = false;
+
+ Unused << Send__delete__(this);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult RemotePrintJobParent::RecvAbortPrint(
+ const nsresult& aRv) {
+ if (mPrintDeviceContext) {
+ Unused << mPrintDeviceContext->AbortDocument();
+ mPrintDeviceContext->UnregisterPageDoneCallback();
+ }
+
+ mIsDoingPrinting = false;
+
+ Unused << Send__delete__(this);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult RemotePrintJobParent::RecvStateChange(
+ const long& aStateFlags, const nsresult& aStatus) {
+ uint32_t numberOfListeners = mPrintProgressListeners.Length();
+ for (uint32_t i = 0; i < numberOfListeners; ++i) {
+ nsIWebProgressListener* listener = mPrintProgressListeners.SafeElementAt(i);
+ listener->OnStateChange(nullptr, nullptr, aStateFlags, aStatus);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult RemotePrintJobParent::RecvProgressChange(
+ const long& aCurSelfProgress, const long& aMaxSelfProgress,
+ const long& aCurTotalProgress, const long& aMaxTotalProgress) {
+ uint32_t numberOfListeners = mPrintProgressListeners.Length();
+ for (uint32_t i = 0; i < numberOfListeners; ++i) {
+ nsIWebProgressListener* listener = mPrintProgressListeners.SafeElementAt(i);
+ listener->OnProgressChange(nullptr, nullptr, aCurSelfProgress,
+ aMaxSelfProgress, aCurTotalProgress,
+ aMaxTotalProgress);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult RemotePrintJobParent::RecvStatusChange(
+ const nsresult& aStatus) {
+ uint32_t numberOfListeners = mPrintProgressListeners.Length();
+ for (uint32_t i = 0; i < numberOfListeners; ++i) {
+ nsIWebProgressListener* listener = mPrintProgressListeners.SafeElementAt(i);
+ listener->OnStatusChange(nullptr, nullptr, aStatus, nullptr);
+ }
+
+ return IPC_OK();
+}
+
+void RemotePrintJobParent::RegisterListener(nsIWebProgressListener* aListener) {
+ MOZ_ASSERT(aListener);
+
+ 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 (mPrintDeviceContext) {
+ mPrintDeviceContext->UnregisterPageDoneCallback();
+ }
+
+ mIsDoingPrinting = false;
+
+ // If progress dialog is opened, notify closing it.
+ for (auto listener : mPrintProgressListeners) {
+ listener->OnStateChange(nullptr, nullptr,
+ nsIWebProgressListener::STATE_STOP, NS_OK);
+ }
+}
+
+} // namespace layout
+} // namespace mozilla
diff --git a/layout/printing/ipc/RemotePrintJobParent.h b/layout/printing/ipc/RemotePrintJobParent.h
new file mode 100644
index 0000000000..df7b3dd51f
--- /dev/null
+++ b/layout/printing/ipc/RemotePrintJobParent.h
@@ -0,0 +1,102 @@
+/* -*- 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/. */
+
+#ifndef mozilla_layout_RemotePrintJobParent_h
+#define mozilla_layout_RemotePrintJobParent_h
+
+#include "mozilla/layout/PRemotePrintJobParent.h"
+#include "mozilla/layout/printing/DrawEventRecorder.h"
+
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/gfx/RecordedEvent.h"
+#include "mozilla/gfx/CrossProcessPaint.h"
+
+class nsDeviceContext;
+class nsIPrintSettings;
+class nsIWebProgressListener;
+
+namespace mozilla {
+namespace layout {
+
+class PrintTranslator;
+
+class RemotePrintJobParent final : public PRemotePrintJobParent {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(RemotePrintJobParent);
+
+ explicit RemotePrintJobParent(nsIPrintSettings* aPrintSettings);
+
+ void ActorDestroy(ActorDestroyReason aWhy) final;
+
+ mozilla::ipc::IPCResult RecvInitializePrint(const nsString& aDocumentTitle,
+ const nsString& aPrintToFile,
+ const int32_t& aStartPage,
+ const int32_t& aEndPage) final;
+
+ mozilla::ipc::IPCResult RecvProcessPage(nsTArray<uint64_t>&& aDeps) final;
+
+ mozilla::ipc::IPCResult RecvFinalizePrint() final;
+
+ mozilla::ipc::IPCResult RecvAbortPrint(const nsresult& aRv) final;
+
+ mozilla::ipc::IPCResult RecvStateChange(const long& aStateFlags,
+ const nsresult& aStatus) final;
+
+ mozilla::ipc::IPCResult RecvProgressChange(
+ const long& aCurSelfProgress, const long& aMaxSelfProgress,
+ const long& aCurTotalProgress, const long& aMaxTotalProgress) final;
+
+ mozilla::ipc::IPCResult RecvStatusChange(const nsresult& aStatus) final;
+
+ /**
+ * Register a progress listener to receive print progress updates.
+ *
+ * @param aListener the progress listener to register. Must not be null.
+ */
+ void RegisterListener(nsIWebProgressListener* aListener);
+
+ /**
+ * @return the print settings for this remote print job.
+ */
+ already_AddRefed<nsIPrintSettings> GetPrintSettings();
+
+ private:
+ ~RemotePrintJobParent() final;
+
+ nsresult InitializePrintDevice(const nsString& aDocumentTitle,
+ const nsString& aPrintToFile,
+ const int32_t& aStartPage,
+ const int32_t& aEndPage);
+
+ nsresult PrepareNextPageFD(FileDescriptor* aFd);
+
+ nsresult PrintPage(
+ PRFileDescStream& aRecording,
+ gfx::CrossProcessPaint::ResolvedFragmentMap* aFragments = nullptr);
+ void FinishProcessingPage(
+ gfx::CrossProcessPaint::ResolvedFragmentMap* aFragments = nullptr);
+
+ /**
+ * Called to notify our corresponding RemotePrintJobChild once we've
+ * finished printing a page.
+ */
+ void PageDone(nsresult aResult);
+
+ nsCOMPtr<nsIPrintSettings> mPrintSettings;
+ RefPtr<nsDeviceContext> mPrintDeviceContext;
+ UniquePtr<PrintTranslator> mPrintTranslator;
+ nsCOMArray<nsIWebProgressListener> mPrintProgressListeners;
+ PRFileDescStream mCurrentPageStream;
+ bool mIsDoingPrinting;
+};
+
+} // namespace layout
+} // namespace mozilla
+
+#endif // mozilla_layout_RemotePrintJobParent_h
diff --git a/layout/printing/moz.build b/layout/printing/moz.build
new file mode 100644
index 0000000000..c8d5be6d71
--- /dev/null
+++ b/layout/printing/moz.build
@@ -0,0 +1,40 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+XPIDL_SOURCES += [
+ "nsIPrintProgress.idl",
+ "nsIPrintProgressParams.idl",
+]
+
+EXPORTS.mozilla.layout += [
+ "ipc/RemotePrintJobChild.h",
+ "ipc/RemotePrintJobParent.h",
+]
+
+EXPORTS.mozilla.layout.printing += ["DrawEventRecorder.h"]
+
+XPIDL_MODULE = "layout_printing"
+
+UNIFIED_SOURCES += [
+ "DrawEventRecorder.cpp",
+ "ipc/RemotePrintJobChild.cpp",
+ "ipc/RemotePrintJobParent.cpp",
+ "nsPagePrintTimer.cpp",
+ "nsPrintData.cpp",
+ "nsPrintJob.cpp",
+ "nsPrintObject.cpp",
+ "PrintTranslator.cpp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+LOCAL_INCLUDES += [
+ "../base",
+ "/dom/base",
+ "/gfx/2d/",
+ "/netwerk/base/",
+]
diff --git a/layout/printing/nsIPrintProgress.idl b/layout/printing/nsIPrintProgress.idl
new file mode 100644
index 0000000000..09722133bb
--- /dev/null
+++ b/layout/printing/nsIPrintProgress.idl
@@ -0,0 +1,43 @@
+/* -*- 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 "nsIWebProgressListener.idl"
+
+interface mozIDOMWindowProxy;
+interface nsIObserver;
+interface nsIPrompt;
+
+[scriptable, uuid(05f4fb88-e568-4d35-b394-ce0aa3eea6fc)]
+interface nsIPrintProgress: nsIWebProgressListener {
+
+ /* Open the progress dialog
+ you can specify parameters through an xpcom object
+ */
+ void openProgressDialog(in mozIDOMWindowProxy parent,
+ in string dialogURL,
+ in nsISupports parameters,
+ in nsIObserver openDialogObserver,
+ out boolean notifyOnOpen);
+
+ /* Close the progress dialog */
+ void closeProgressDialog(in boolean forceClose);
+
+ /* Register a Web Progress Listener */
+ void registerListener(in nsIWebProgressListener listener);
+
+ /* Unregister a Web Progress Listener */
+ void unregisterListener(in nsIWebProgressListener listener);
+
+ /* This method is called after the dialog that shows the progress has been shown
+ */
+ void doneIniting();
+
+ /* Retrieve the prompter, needed to display modal dialog on top of progress dialog */
+ nsIPrompt getPrompter();
+
+ /* Indicated if the user asked to cancel the current process */
+ attribute boolean processCanceledByUser;
+};
+
+
diff --git a/layout/printing/nsIPrintProgressParams.idl b/layout/printing/nsIPrintProgressParams.idl
new file mode 100644
index 0000000000..fa93ea8521
--- /dev/null
+++ b/layout/printing/nsIPrintProgressParams.idl
@@ -0,0 +1,14 @@
+/* -*- 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 "nsISupports.idl"
+
+[scriptable, uuid(CA89B55B-6FAF-4051-9645-1C03EF5108F8)]
+interface nsIPrintProgressParams: nsISupports
+{
+ attribute AString docTitle;
+ attribute AString docURL;
+};
diff --git a/layout/printing/nsPagePrintTimer.cpp b/layout/printing/nsPagePrintTimer.cpp
new file mode 100644
index 0000000000..8949c3e5df
--- /dev/null
+++ b/layout/printing/nsPagePrintTimer.cpp
@@ -0,0 +1,203 @@
+/* -*- 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 "nsPagePrintTimer.h"
+
+#include "mozilla/dom/Document.h"
+#include "mozilla/Unused.h"
+#include "nsPrintJob.h"
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS_INHERITED(nsPagePrintTimer, mozilla::Runnable,
+ nsITimerCallback)
+
+nsPagePrintTimer::~nsPagePrintTimer() { Disconnect(); }
+
+void nsPagePrintTimer::Disconnect() {
+ mPrintJob = nullptr;
+ mPrintObj = nullptr;
+ if (mDocViewerPrint) {
+ // This matches the IncrementDestroyBlockedCount call in the constructor.
+ mDocViewerPrint->DecrementDestroyBlockedCount();
+ mDocViewerPrint = nullptr;
+ }
+}
+
+nsresult nsPagePrintTimer::StartTimer(bool aUseDelay) {
+ uint32_t delay = 0;
+ if (aUseDelay) {
+ if (mFiringCount < 10) {
+ // Longer delay for the few first pages.
+ delay = mDelay + ((10 - mFiringCount) * 100);
+ } else {
+ delay = mDelay;
+ }
+ }
+ return NS_NewTimerWithCallback(
+ getter_AddRefs(mTimer), this, delay, nsITimer::TYPE_ONE_SHOT,
+ mDocument->EventTargetFor(TaskCategory::Other));
+}
+
+nsresult nsPagePrintTimer::StartWatchDogTimer() {
+ if (mWatchDogTimer) {
+ mWatchDogTimer->Cancel();
+ }
+ // Instead of just doing one timer for a long period do multiple so we
+ // can check if the user cancelled the printing.
+ return NS_NewTimerWithCallback(
+ getter_AddRefs(mWatchDogTimer), this, WATCH_DOG_INTERVAL,
+ nsITimer::TYPE_ONE_SHOT, mDocument->EventTargetFor(TaskCategory::Other));
+}
+
+void nsPagePrintTimer::StopWatchDogTimer() {
+ if (mWatchDogTimer) {
+ mWatchDogTimer->Cancel();
+ mWatchDogTimer = nullptr;
+ }
+}
+
+// nsRunnable
+NS_IMETHODIMP
+nsPagePrintTimer::Run() {
+ bool initNewTimer = true;
+ // Check to see if we are done
+ // inRange will be true if a sheet is actually printed
+ bool inRange;
+ bool donePrinting;
+
+ // donePrinting will be true if it completed successfully or
+ // if the printing was cancelled
+ donePrinting = !mPrintJob || mPrintJob->PrintSheet(mPrintObj, inRange);
+ if (donePrinting) {
+ if (mWaitingForRemotePrint ||
+ // If we are not waiting for the remote printing, it is the time to
+ // end printing task by calling DonePrintingSheets.
+ (!mPrintJob || mPrintJob->DonePrintingSheets(mPrintObj, NS_OK))) {
+ initNewTimer = false;
+ mDone = true;
+ }
+ }
+
+ // Note that the Stop() destroys this after the print job finishes
+ // (The nsPrintJob stops holding a reference when DonePrintingSheets
+ // returns true.)
+ Stop();
+ if (initNewTimer) {
+ ++mFiringCount;
+ nsresult result = StartTimer(inRange);
+ if (NS_FAILED(result)) {
+ mDone = true; // had a failure.. we are finished..
+ if (mPrintJob) {
+ mPrintJob->SetIsPrinting(false);
+ }
+ }
+ }
+ return NS_OK;
+}
+
+// nsITimerCallback
+NS_IMETHODIMP
+nsPagePrintTimer::Notify(nsITimer* timer) {
+ // When finished there may be still pending notifications, which we can just
+ // ignore.
+ if (mDone) {
+ return NS_OK;
+ }
+
+ // There are four things that call Notify with different values for timer:
+ // 1) the delay between sheets (timer == mTimer)
+ // 2) canvasPrintState done (timer == null)
+ // 3) the watch dog timer (timer == mWatchDogTimer)
+ // 4) the waiting for remote print "timer" (timer == mWaitingForRemotePrint)
+ if (!timer) {
+ // Reset the counter since a mozPrintCallback has finished.
+ mWatchDogCount = 0;
+ } else if (timer == mTimer) {
+ // Reset the watchdog timer before the start of every sheet.
+ mWatchDogCount = 0;
+ mTimer = nullptr;
+ } else if (timer == mWaitingForRemotePrint) {
+ mWaitingForRemotePrint = nullptr;
+
+ // If we are still waiting for the sheet delay timer, don't let the
+ // notification from the remote print job trigger the next sheet.
+ if (mTimer) {
+ return NS_OK;
+ }
+ } else if (timer == mWatchDogTimer) {
+ mWatchDogCount++;
+ if (mWatchDogCount > WATCH_DOG_MAX_COUNT) {
+ Fail();
+ return NS_OK;
+ }
+ }
+
+ bool donePrePrint = true;
+ // Don't start to pre-print if we're waiting on the parent still.
+ if (mPrintJob && !mWaitingForRemotePrint) {
+ donePrePrint = mPrintJob->PrePrintSheet();
+ }
+
+ if (donePrePrint && !mWaitingForRemotePrint) {
+ StopWatchDogTimer();
+ // Pass nullptr here since name already was set in constructor.
+ mDocument->Dispatch(TaskCategory::Other, do_AddRef(this));
+ } else {
+ // Start the watch dog if we're waiting for preprint to ensure that if any
+ // mozPrintCallbacks take to long we error out.
+ StartWatchDogTimer();
+ }
+
+ return NS_OK;
+}
+
+void nsPagePrintTimer::WaitForRemotePrint() {
+ mWaitingForRemotePrint = NS_NewTimer();
+ if (!mWaitingForRemotePrint) {
+ NS_WARNING("Failed to wait for remote print, we might time-out.");
+ }
+}
+
+void nsPagePrintTimer::RemotePrintFinished() {
+ if (!mWaitingForRemotePrint) {
+ return;
+ }
+
+ // now clean up print or print the next webshell
+ if (mDone && mPrintJob) {
+ mDone = mPrintJob->DonePrintingSheets(mPrintObj, NS_OK);
+ }
+
+ mWaitingForRemotePrint->SetTarget(
+ mDocument->EventTargetFor(mozilla::TaskCategory::Other));
+ mozilla::Unused << mWaitingForRemotePrint->InitWithCallback(
+ this, 0, nsITimer::TYPE_ONE_SHOT);
+}
+
+nsresult nsPagePrintTimer::Start(nsPrintObject* aPO) {
+ mPrintObj = aPO;
+ mDone = false;
+ return StartTimer(false);
+}
+
+void nsPagePrintTimer::Stop() {
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+ StopWatchDogTimer();
+}
+
+void nsPagePrintTimer::Fail() {
+ NS_WARNING("nsPagePrintTimer::Fail called");
+
+ mDone = true;
+ Stop();
+ if (mPrintJob) {
+ mPrintJob->CleanupOnFailure(NS_OK, false);
+ }
+}
diff --git a/layout/printing/nsPagePrintTimer.h b/layout/printing/nsPagePrintTimer.h
new file mode 100644
index 0000000000..f761fc36e9
--- /dev/null
+++ b/layout/printing/nsPagePrintTimer.h
@@ -0,0 +1,92 @@
+/* -*- 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/. */
+#ifndef nsPagePrintTimer_h___
+#define nsPagePrintTimer_h___
+
+// Timer Includes
+#include "nsITimer.h"
+
+#include "nsIDocumentViewerPrint.h"
+#include "nsPrintObject.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/OwningNonNull.h"
+#include "nsThreadUtils.h"
+
+class nsPrintJob;
+
+//---------------------------------------------------
+//-- Page Timer Class
+//---------------------------------------------------
+// Strictly speaking, this actually manages the timing of printing *sheets*
+// (instances of "PrintedSheetFrame"), each of which may encompass multiple
+// pages (nsPageFrames) of the document. The use of "Page" in the class name
+// here is for historical / colloquial purposes.
+class nsPagePrintTimer final : public mozilla::Runnable,
+ public nsITimerCallback {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ nsPagePrintTimer(nsPrintJob* aPrintJob,
+ nsIDocumentViewerPrint* aDocViewerPrint,
+ mozilla::dom::Document* aDocument, uint32_t aDelay)
+ : Runnable("nsPagePrintTimer"),
+ mPrintJob(aPrintJob),
+ mDocViewerPrint(aDocViewerPrint),
+ mDocument(aDocument),
+ mDelay(aDelay),
+ mFiringCount(0),
+ mPrintObj(nullptr),
+ mWatchDogCount(0),
+ mDone(false) {
+ MOZ_ASSERT(aDocViewerPrint && aDocument);
+ mDocViewerPrint->IncrementDestroyBlockedCount();
+ }
+
+ NS_DECL_NSITIMERCALLBACK
+
+ nsresult Start(nsPrintObject* aPO);
+
+ NS_IMETHOD Run() override;
+
+ void Stop();
+
+ void WaitForRemotePrint();
+ void RemotePrintFinished();
+
+ void Disconnect();
+
+ private:
+ ~nsPagePrintTimer();
+
+ nsresult StartTimer(bool aUseDelay);
+ nsresult StartWatchDogTimer();
+ void StopWatchDogTimer();
+ void Fail();
+
+ nsPrintJob* mPrintJob;
+ nsCOMPtr<nsIDocumentViewerPrint> mDocViewerPrint;
+ RefPtr<mozilla::dom::Document> mDocument;
+ nsCOMPtr<nsITimer> mTimer;
+ nsCOMPtr<nsITimer> mWatchDogTimer;
+ nsCOMPtr<nsITimer> mWaitingForRemotePrint;
+ uint32_t mDelay;
+ uint32_t mFiringCount;
+ nsPrintObject* mPrintObj;
+ uint32_t mWatchDogCount;
+ bool mDone;
+
+ static const uint32_t WATCH_DOG_INTERVAL = 1000;
+ static const uint32_t WATCH_DOG_MAX_COUNT =
+#ifdef DEBUG
+ // Debug builds are very slow (on Mac at least) and can need extra time
+ 30
+#else
+ 10
+#endif
+ ;
+};
+
+#endif /* nsPagePrintTimer_h___ */
diff --git a/layout/printing/nsPrintData.cpp b/layout/printing/nsPrintData.cpp
new file mode 100644
index 0000000000..70b0df2b4d
--- /dev/null
+++ b/layout/printing/nsPrintData.cpp
@@ -0,0 +1,110 @@
+/* -*- 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 "nsPrintData.h"
+
+#include "nsIPrintProgressParams.h"
+#include "nsIStringBundle.h"
+#include "nsIWidget.h"
+#include "nsPrintObject.h"
+#include "nsIWebProgressListener.h"
+#include "mozilla/Services.h"
+
+//-----------------------------------------------------
+// PR LOGGING
+#include "mozilla/Logging.h"
+
+extern mozilla::LazyLogModule gPrintingLog;
+
+#define PR_PL(_p1) MOZ_LOG(gPrintingLog, mozilla::LogLevel::Debug, _p1);
+
+//---------------------------------------------------
+//-- nsPrintData Class Impl
+//---------------------------------------------------
+nsPrintData::nsPrintData(ePrintDataType aType)
+ : mType(aType),
+ mPrintDocList(0),
+ mIsParentAFrameSet(false),
+ mOnStartSent(false),
+ mIsAborted(false),
+ mPreparingForPrint(false),
+ mShrinkToFit(false),
+ mNumPrintablePages(0),
+ mShrinkRatio(1.0) {}
+
+nsPrintData::~nsPrintData() {
+ // Only Send an OnEndPrinting if we have started printing
+ if (mOnStartSent && mType != eIsPrintPreview) {
+ OnEndPrinting();
+ }
+
+ if (mPrintDC) {
+ PR_PL(("****************** End Document ************************\n"));
+ PR_PL(("\n"));
+ bool isCancelled = false;
+ mPrintSettings->GetIsCancelled(&isCancelled);
+
+ nsresult rv = NS_OK;
+ if (mType == eIsPrinting && mPrintDC->IsCurrentlyPrintingDocument()) {
+ if (!isCancelled && !mIsAborted) {
+ rv = mPrintDC->EndDocument();
+ } else {
+ rv = mPrintDC->AbortDocument();
+ }
+ if (NS_FAILED(rv)) {
+ // XXX nsPrintData::ShowPrintErrorDialog(rv);
+ }
+ }
+ }
+}
+
+void nsPrintData::OnStartPrinting() {
+ if (!mOnStartSent) {
+ DoOnProgressChange(0, 0, true,
+ nsIWebProgressListener::STATE_START |
+ nsIWebProgressListener::STATE_IS_DOCUMENT |
+ nsIWebProgressListener::STATE_IS_NETWORK);
+ mOnStartSent = true;
+ }
+}
+
+void nsPrintData::OnEndPrinting() {
+ DoOnProgressChange(100, 100, true,
+ nsIWebProgressListener::STATE_STOP |
+ nsIWebProgressListener::STATE_IS_DOCUMENT);
+ DoOnProgressChange(100, 100, true,
+ nsIWebProgressListener::STATE_STOP |
+ nsIWebProgressListener::STATE_IS_NETWORK);
+}
+
+void nsPrintData::DoOnProgressChange(int32_t aProgress, int32_t aMaxProgress,
+ bool aDoStartStop, int32_t aFlag) {
+ size_t numberOfListeners = mPrintProgressListeners.Length();
+ for (size_t i = 0; i < numberOfListeners; ++i) {
+ nsCOMPtr<nsIWebProgressListener> listener =
+ mPrintProgressListeners.SafeElementAt(i);
+ if (NS_WARN_IF(!listener)) {
+ continue;
+ }
+ listener->OnProgressChange(nullptr, nullptr, aProgress, aMaxProgress,
+ aProgress, aMaxProgress);
+ if (aDoStartStop) {
+ listener->OnStateChange(nullptr, nullptr, aFlag, NS_OK);
+ }
+ }
+}
+
+void nsPrintData::DoOnStatusChange(nsresult aStatus) {
+ size_t numberOfListeners = mPrintProgressListeners.Length();
+ for (size_t i = 0; i < numberOfListeners; ++i) {
+ nsCOMPtr<nsIWebProgressListener> listener =
+ mPrintProgressListeners.SafeElementAt(i);
+ if (NS_WARN_IF(!listener)) {
+ continue;
+ }
+ listener->OnStatusChange(nullptr, nullptr, aStatus, nullptr);
+ }
+}
diff --git a/layout/printing/nsPrintData.h b/layout/printing/nsPrintData.h
new file mode 100644
index 0000000000..26d4083604
--- /dev/null
+++ b/layout/printing/nsPrintData.h
@@ -0,0 +1,82 @@
+/* -*- 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/. */
+
+#ifndef nsPrintData_h___
+#define nsPrintData_h___
+
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+
+// Interfaces
+#include "nsDeviceContext.h"
+#include "nsIPrintSettings.h"
+#include "nsISupportsImpl.h"
+#include "nsTArray.h"
+#include "nsCOMArray.h"
+
+class nsPrintObject;
+class nsIPrintProgressParams;
+class nsIWebProgressListener;
+
+//------------------------------------------------------------------------
+// nsPrintData Class
+//
+// mPreparingForPrint - indicates that we have started Printing but
+// have not gone to the timer to start printing the pages. It gets turned
+// off right before we go to the timer.
+//------------------------------------------------------------------------
+class nsPrintData {
+ public:
+ typedef enum { eIsPrinting, eIsPrintPreview } ePrintDataType;
+
+ explicit nsPrintData(ePrintDataType aType);
+
+ NS_INLINE_DECL_REFCOUNTING(nsPrintData)
+
+ // Listener Helper Methods
+ void OnEndPrinting();
+ void OnStartPrinting();
+ void DoOnProgressChange(int32_t aProgress, int32_t aMaxProgress,
+ bool aDoStartStop, int32_t aFlag);
+
+ void DoOnStatusChange(nsresult aStatus);
+
+ ePrintDataType mType; // the type of data this is (Printing or Print Preview)
+ RefPtr<nsDeviceContext> mPrintDC;
+
+ mozilla::UniquePtr<nsPrintObject> mPrintObject;
+
+ nsCOMArray<nsIWebProgressListener> mPrintProgressListeners;
+ nsCOMPtr<nsIPrintProgressParams> mPrintProgressParams;
+
+ // If there is a focused iframe, mSelectionRoot is set to its nsPrintObject.
+ // Otherwise, if there is a selection, it is set to the root nsPrintObject.
+ // Otherwise, it is unset.
+ nsPrintObject* mSelectionRoot = nullptr;
+
+ // Array of non-owning pointers to all the nsPrintObjects owned by this
+ // nsPrintData. This includes this->mPrintObject, as well as all of its
+ // mKids (and their mKids, etc.)
+ nsTArray<nsPrintObject*> mPrintDocList;
+
+ bool mIsParentAFrameSet;
+ bool mOnStartSent;
+ bool mIsAborted; // tells us the document is being aborted
+ bool mPreparingForPrint; // see comments above
+ bool mShrinkToFit;
+ int32_t mNumPrintablePages;
+ float mShrinkRatio;
+
+ nsCOMPtr<nsIPrintSettings> mPrintSettings;
+
+ private:
+ nsPrintData() = delete;
+ nsPrintData& operator=(const nsPrintData& aOther) = delete;
+
+ ~nsPrintData(); // non-virtual
+};
+
+#endif /* nsPrintData_h___ */
diff --git a/layout/printing/nsPrintJob.cpp b/layout/printing/nsPrintJob.cpp
new file mode 100644
index 0000000000..fe4e7cb1b8
--- /dev/null
+++ b/layout/printing/nsPrintJob.cpp
@@ -0,0 +1,3059 @@
+/* -*- 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 "nsPrintJob.h"
+
+#include "nsDebug.h"
+#include "nsDocShell.h"
+#include "nsReadableUtils.h"
+#include "nsCRT.h"
+#include "nsQueryObject.h"
+
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/ComputedStyleInlines.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/PBrowser.h"
+#include "mozilla/dom/Selection.h"
+#include "mozilla/dom/CustomEvent.h"
+#include "mozilla/dom/HTMLCanvasElement.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/StaticPrefs_print.h"
+#include "mozilla/Telemetry.h"
+#include "nsIBrowserChild.h"
+#include "nsIOService.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIStringBundle.h"
+#include "nsPIDOMWindow.h"
+#include "nsPrintData.h"
+#include "nsPrintObject.h"
+#include "nsIDocShell.h"
+#include "nsIURI.h"
+#include "nsITextToSubURI.h"
+#include "nsError.h"
+
+#include "nsView.h"
+#include <algorithm>
+
+// Print Options
+#include "nsIPrintSettings.h"
+#include "nsIPrintSettingsService.h"
+#include "nsIPrintSession.h"
+#include "nsGfxCIID.h"
+#include "nsGkAtoms.h"
+#include "nsXPCOM.h"
+
+static const char sPrintSettingsServiceContractID[] =
+ "@mozilla.org/gfx/printsettings-service;1";
+
+#include "nsThreadUtils.h"
+
+// Printing
+#include "nsIWebBrowserPrint.h"
+
+// Print Preview
+
+// Print Progress
+#include "nsIObserver.h"
+
+// Print error dialog
+
+// Printing Prompts
+#include "nsIPrintingPromptService.h"
+static const char kPrintingPromptService[] =
+ "@mozilla.org/embedcomp/printingprompt-service;1";
+
+// Printing Timer
+#include "nsPagePrintTimer.h"
+
+// FrameSet
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.h"
+
+// Focus
+
+// Misc
+#include "gfxContext.h"
+#include "mozilla/gfx/DrawEventRecorder.h"
+#include "mozilla/layout/RemotePrintJobChild.h"
+#include "nsISupportsUtils.h"
+#include "nsIScriptContext.h"
+#include "nsIDocumentObserver.h"
+#include "nsContentCID.h"
+#include "nsLayoutCID.h"
+#include "nsContentUtils.h"
+#include "nsLayoutUtils.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PresShell.h"
+#include "Text.h"
+
+#include "nsWidgetsCID.h"
+#include "nsIDeviceContextSpec.h"
+#include "nsDeviceContextSpecProxy.h"
+#include "nsViewManager.h"
+
+#include "nsPageSequenceFrame.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIWebBrowserChrome.h"
+#include "nsFrameManager.h"
+#include "mozilla/ReflowInput.h"
+#include "nsIContentViewer.h"
+#include "nsIDocumentViewerPrint.h"
+
+#include "nsFocusManager.h"
+#include "nsRange.h"
+#include "mozilla/Components.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLFrameElement.h"
+#include "nsContentList.h"
+#include "xpcpublic.h"
+#include "nsVariant.h"
+#include "mozilla/ServoStyleSet.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+//-----------------------------------------------------
+// PR LOGGING
+#include "mozilla/Logging.h"
+
+#ifdef DEBUG
+// PR_LOGGING is force to always be on (even in release builds)
+// but we only want some of it on,
+//#define EXTENDED_DEBUG_PRINTING
+#endif
+
+// this log level turns on the dumping of each document's layout info
+#define DUMP_LAYOUT_LEVEL (static_cast<mozilla::LogLevel>(9))
+
+#ifndef PR_PL
+static mozilla::LazyLogModule gPrintingLog("printing");
+
+# define PR_PL(_p1) MOZ_LOG(gPrintingLog, mozilla::LogLevel::Debug, _p1);
+#endif
+
+#ifdef EXTENDED_DEBUG_PRINTING
+static uint32_t gDumpFileNameCnt = 0;
+static uint32_t gDumpLOFileNameCnt = 0;
+#endif
+
+#define PRT_YESNO(_p) ((_p) ? "YES" : "NO")
+static const char* gFrameTypesStr[] = {"eDoc", "eFrame", "eIFrame",
+ "eFrameSet"};
+
+// This processes the selection on aOrigDoc and creates an inverted selection on
+// aDoc, which it then deletes. If the start or end of the inverted selection
+// ranges occur in text nodes then an ellipsis is added.
+static nsresult DeleteNonSelectedNodes(Document& aDoc);
+
+#ifdef EXTENDED_DEBUG_PRINTING
+// Forward Declarations
+static void DumpPrintObjectsListStart(const char* aStr,
+ const nsTArray<nsPrintObject*>& aDocList);
+static void DumpPrintObjectsTree(nsPrintObject* aPO, int aLevel = 0,
+ FILE* aFD = nullptr);
+static void DumpPrintObjectsTreeLayout(const UniquePtr<nsPrintObject>& aPO,
+ nsDeviceContext* aDC, int aLevel = 0,
+ FILE* aFD = nullptr);
+
+# define DUMP_DOC_LIST(_title) \
+ DumpPrintObjectsListStart((_title), mPrt->mPrintDocList);
+# define DUMP_DOC_TREE DumpPrintObjectsTree(mPrt->mPrintObject.get());
+# define DUMP_DOC_TREELAYOUT \
+ DumpPrintObjectsTreeLayout(mPrt->mPrintObject, mPrt->mPrintDC);
+#else
+# define DUMP_DOC_LIST(_title)
+# define DUMP_DOC_TREE
+# define DUMP_DOC_TREELAYOUT
+#endif
+
+// -------------------------------------------------------
+// Helpers
+// -------------------------------------------------------
+
+static bool HasFramesetChild(nsIContent* aContent) {
+ if (!aContent) {
+ return false;
+ }
+
+ // do a breadth search across all siblings
+ for (nsIContent* child = aContent->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ if (child->IsHTMLElement(nsGkAtoms::frameset)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool IsParentAFrameSet(nsIDocShell* aParent) {
+ // See if the incoming doc is the root document
+ if (!aParent) return false;
+
+ // When it is the top level document we need to check
+ // to see if it contains a frameset. If it does, then
+ // we only want to print the doc's children and not the document itself
+ // For anything else we always print all the children and the document
+ // for example, if the doc contains an IFRAME we eant to print the child
+ // document (the IFRAME) and then the rest of the document.
+ //
+ // XXX we really need to search the frame tree, and not the content
+ // but there is no way to distinguish between IFRAMEs and FRAMEs
+ // with the GetFrameType call.
+ // Bug 53459 has been files so we can eventually distinguish
+ // between IFRAME frames and FRAME frames
+ bool isFrameSet = false;
+ // only check to see if there is a frameset if there is
+ // NO parent doc for this doc. meaning this parent is the root doc
+ nsCOMPtr<Document> doc = aParent->GetDocument();
+ if (doc) {
+ nsIContent* rootElement = doc->GetRootElement();
+ if (rootElement) {
+ isFrameSet = HasFramesetChild(rootElement);
+ }
+ }
+ return isFrameSet;
+}
+
+/**
+ * Build a tree of nsPrintObjects under aPO. It also appends a (depth first)
+ * flat list of all the nsPrintObjects created to aPrintData->mPrintDocList. If
+ * one of the nsPrintObject's document is the focused document, then the print
+ * object is set as aPrintData->mSelectionRoot.
+ * @param aParentPO The parent nsPrintObject to populate, must not be null.
+ * @param aFocusedDoc Document from the window that had focus when print was
+ * initiated.
+ * @param aPrintData nsPrintData for the current print, must not be null.
+ */
+static void BuildNestedPrintObjects(const UniquePtr<nsPrintObject>& aParentPO,
+ const RefPtr<Document>& aFocusedDoc,
+ RefPtr<nsPrintData>& aPrintData) {
+ MOZ_ASSERT(aParentPO);
+ MOZ_ASSERT(aPrintData);
+
+ // If aParentPO is for an iframe and its original document is focusedDoc then
+ // always set as the selection root.
+ if (aParentPO->mFrameType == eIFrame &&
+ aParentPO->mDocument->GetOriginalDocument() == aFocusedDoc) {
+ aPrintData->mSelectionRoot = aParentPO.get();
+ } else if (!aPrintData->mSelectionRoot && aParentPO->HasSelection()) {
+ // If there is no focused iframe but there is a selection in one or more
+ // frames then we want to set the root nsPrintObject as the focus root so
+ // that later EnablePrintingSelectionOnly can search for and enable all
+ // nsPrintObjects containing selections.
+ aPrintData->mSelectionRoot = aPrintData->mPrintObject.get();
+ }
+
+ for (auto& bc : aParentPO->mDocShell->GetBrowsingContext()->Children()) {
+ nsCOMPtr<nsIDocShell> docShell = bc->GetDocShell();
+ if (!docShell) {
+ continue;
+ }
+
+ RefPtr<Document> doc = docShell->GetDocument();
+ MOZ_DIAGNOSTIC_ASSERT(doc);
+ // We might find non-static documents here if the fission remoteness change
+ // hasn't happened / finished yet. In that case, just skip them, the same
+ // way we do for remote frames above.
+ MOZ_DIAGNOSTIC_ASSERT(doc->IsStaticDocument() || doc->IsInitialDocument());
+ if (!doc || !doc->IsStaticDocument()) {
+ continue;
+ }
+
+ auto childPO = MakeUnique<nsPrintObject>();
+ nsresult rv = childPO->InitAsNestedObject(docShell, doc, aParentPO.get());
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT_UNREACHABLE("Init failed?");
+ }
+
+ aPrintData->mPrintDocList.AppendElement(childPO.get());
+ BuildNestedPrintObjects(childPO, aFocusedDoc, aPrintData);
+ aParentPO->mKids.AppendElement(std::move(childPO));
+ }
+}
+
+/**
+ * On platforms that support it, sets the printer name stored in the
+ * nsIPrintSettings to the last-used printer if a printer name is not already
+ * set.
+ * XXXjwatt: Why is this necessary? Can't the code that reads the printer
+ * name later "just" use the last-used printer if a name isn't specified? Then
+ * we wouldn't have this inconsistency between platforms and processes.
+ */
+static nsresult EnsureSettingsHasPrinterNameSet(
+ nsIPrintSettings* aPrintSettings) {
+#if defined(XP_MACOSX) || defined(ANDROID)
+ // Mac doesn't support retrieving a printer list.
+ return NS_OK;
+#else
+ NS_ENSURE_ARG_POINTER(aPrintSettings);
+
+ // See if aPrintSettings already has a printer
+ nsString printerName;
+ nsresult rv = aPrintSettings->GetPrinterName(printerName);
+ if (NS_SUCCEEDED(rv) && !printerName.IsEmpty()) {
+ return NS_OK;
+ }
+
+ // aPrintSettings doesn't have a printer set.
+ // Try to fetch the name of the last-used printer.
+ nsCOMPtr<nsIPrintSettingsService> printSettingsService =
+ do_GetService(sPrintSettingsServiceContractID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = printSettingsService->GetLastUsedPrinterName(printerName);
+ if (NS_SUCCEEDED(rv) && !printerName.IsEmpty()) {
+ rv = aPrintSettings->SetPrinterName(printerName);
+ }
+ return rv;
+#endif
+}
+
+static nsresult GetDefaultPrintSettings(nsIPrintSettings** aSettings) {
+ *aSettings = nullptr;
+
+ nsresult rv = NS_ERROR_FAILURE;
+ nsCOMPtr<nsIPrintSettingsService> printSettingsService =
+ do_GetService(sPrintSettingsServiceContractID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return printSettingsService->GetDefaultPrintSettingsForPrinting(aSettings);
+}
+
+//-------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsPrintJob, nsIWebProgressListener, nsISupportsWeakReference,
+ nsIObserver)
+
+//-------------------------------------------------------
+nsPrintJob::nsPrintJob() = default;
+
+nsPrintJob::~nsPrintJob() {
+ Destroy(); // for insurance
+ DisconnectPagePrintTimer();
+}
+
+bool nsPrintJob::CheckBeforeDestroy() const {
+ return mPrt && mPrt->mPreparingForPrint;
+}
+
+PresShell* nsPrintJob::GetPrintPreviewPresShell() {
+ return mPrtPreview->mPrintObject->mPresShell;
+}
+
+//-------------------------------------------------------
+void nsPrintJob::Destroy() {
+ if (mIsDestroying) {
+ return;
+ }
+ mIsDestroying = true;
+
+ mPrt = nullptr;
+
+#ifdef NS_PRINT_PREVIEW
+ mPrtPreview = nullptr;
+#endif
+ mDocViewerPrint = nullptr;
+}
+
+//-------------------------------------------------------
+void nsPrintJob::DestroyPrintingData() { mPrt = nullptr; }
+
+//---------------------------------------------------------------------------------
+//-- Section: Methods needed by the DocViewer
+//---------------------------------------------------------------------------------
+
+//--------------------------------------------------------
+nsresult nsPrintJob::Initialize(nsIDocumentViewerPrint* aDocViewerPrint,
+ nsIDocShell* aDocShell, Document* aOriginalDoc,
+ float aScreenDPI) {
+ NS_ENSURE_ARG_POINTER(aDocViewerPrint);
+ NS_ENSURE_ARG_POINTER(aDocShell);
+ NS_ENSURE_ARG_POINTER(aOriginalDoc);
+
+ mDocViewerPrint = aDocViewerPrint;
+ mDocShell = do_GetWeakReference(aDocShell);
+ mScreenDPI = aScreenDPI;
+
+ // Anything state that we need from aOriginalDoc must be fetched and stored
+ // here, since the document that the user selected to print may mutate
+ // across consecutive PrintPreview() calls.
+
+ Element* root = aOriginalDoc->GetRootElement();
+ mDisallowSelectionPrint =
+ root &&
+ root->HasAttr(kNameSpaceID_None, nsGkAtoms::mozdisallowselectionprint);
+
+ if (nsPIDOMWindowOuter* window = aOriginalDoc->GetWindow()) {
+ if (nsCOMPtr<nsIWebBrowserChrome> wbc = window->GetWebBrowserChrome()) {
+ // We only get this in order to skip opening the progress dialog when
+ // the window is modal. Once the platform code stops opening the
+ // progress dialog (bug 1558907), we can get rid of this.
+ wbc->IsWindowModal(&mIsForModalWindow);
+ }
+ }
+
+ return NS_OK;
+}
+
+//-------------------------------------------------------
+nsresult nsPrintJob::Cancel() {
+ if (mPrt && mPrt->mPrintSettings) {
+ return mPrt->mPrintSettings->SetIsCancelled(true);
+ }
+ return NS_ERROR_FAILURE;
+}
+
+//-----------------------------------------------------------------
+std::tuple<nsPageSequenceFrame*, int32_t>
+nsPrintJob::GetSeqFrameAndCountSheets() const {
+ nsPrintData* printData = mPrtPreview ? mPrtPreview : mPrt;
+ if (NS_WARN_IF(!printData)) {
+ return {nullptr, 0};
+ }
+
+ const nsPrintObject* po = printData->mPrintObject.get();
+ if (NS_WARN_IF(!po)) {
+ return {nullptr, 0};
+ }
+
+ // This is sometimes incorrectly called before the pres shell has been created
+ // (bug 1141756). MOZ_DIAGNOSTIC_ASSERT so we'll still see the crash in
+ // Nightly/Aurora in case the other patch fixes this.
+ if (!po->mPresShell) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ false, "GetSeqFrameAndCountSheets needs a non-null pres shell");
+ return {nullptr, 0};
+ }
+
+ nsPageSequenceFrame* seqFrame = po->mPresShell->GetPageSequenceFrame();
+ if (!seqFrame) {
+ return {nullptr, 0};
+ }
+
+ // count the total number of sheets
+ return {seqFrame, seqFrame->PrincipalChildList().GetLength()};
+}
+//---------------------------------------------------------------------------------
+//-- Done: Methods needed by the DocViewer
+//---------------------------------------------------------------------------------
+
+//---------------------------------------------------------------------------------
+//-- Section: nsIWebBrowserPrint
+//---------------------------------------------------------------------------------
+
+// Foward decl for Debug Helper Functions
+#ifdef EXTENDED_DEBUG_PRINTING
+# ifdef XP_WIN
+static int RemoveFilesInDir(const char* aDir);
+# endif
+static void GetDocTitleAndURL(const UniquePtr<nsPrintObject>& aPO,
+ nsACString& aDocStr, nsACString& aURLStr);
+static void DumpPrintObjectsTree(nsPrintObject* aPO, int aLevel, FILE* aFD);
+static void DumpPrintObjectsList(const nsTArray<nsPrintObject*>& aDocList);
+static void RootFrameList(nsPresContext* aPresContext, FILE* out,
+ const char* aPrefix);
+static void DumpViews(nsIDocShell* aDocShell, FILE* out);
+static void DumpLayoutData(const char* aTitleStr, const char* aURLStr,
+ nsPresContext* aPresContext, nsDeviceContext* aDC,
+ nsIFrame* aRootFrame, nsIDocShell* aDocShell,
+ FILE* aFD);
+#endif
+
+//--------------------------------------------------------------------------------
+
+nsresult nsPrintJob::CommonPrint(bool aIsPrintPreview,
+ nsIPrintSettings* aPrintSettings,
+ nsIWebProgressListener* aWebProgressListener,
+ Document* aSourceDoc) {
+ // Callers must hold a strong reference to |this| to ensure that we stay
+ // alive for the duration of this method, because our main owning reference
+ // (on nsDocumentViewer) might be cleared during this function (if we cause
+ // script to run and it cancels the print operation).
+
+ nsresult rv = DoCommonPrint(aIsPrintPreview, aPrintSettings,
+ aWebProgressListener, aSourceDoc);
+ if (NS_FAILED(rv)) {
+ if (aIsPrintPreview) {
+ mIsCreatingPrintPreview = false;
+ SetIsPrintPreview(false);
+ } else {
+ SetIsPrinting(false);
+ }
+ if (mProgressDialogIsShown) CloseProgressDialog(aWebProgressListener);
+ if (rv != NS_ERROR_ABORT && rv != NS_ERROR_OUT_OF_MEMORY) {
+ FirePrintingErrorEvent(rv);
+ }
+ mPrt = nullptr;
+ }
+
+ return rv;
+}
+
+nsresult nsPrintJob::DoCommonPrint(bool aIsPrintPreview,
+ nsIPrintSettings* aPrintSettings,
+ nsIWebProgressListener* aWebProgressListener,
+ Document* aDoc) {
+ MOZ_ASSERT(aDoc->IsStaticDocument());
+
+ nsresult rv;
+
+ // Grab the new instance with local variable to guarantee that it won't be
+ // deleted during this method.
+ // Note: Methods we call early below rely on mPrt being set.
+ mPrt = new nsPrintData(aIsPrintPreview ? nsPrintData::eIsPrintPreview
+ : nsPrintData::eIsPrinting);
+ RefPtr<nsPrintData> printData = mPrt;
+
+ if (aIsPrintPreview) {
+ // The WebProgressListener can be QI'ed to nsIPrintingPromptService
+ // then that means the progress dialog is already being shown.
+ nsCOMPtr<nsIPrintingPromptService> pps(
+ do_QueryInterface(aWebProgressListener));
+ mProgressDialogIsShown = pps != nullptr;
+
+ mIsCreatingPrintPreview = true;
+
+ // Our new print preview nsPrintData is stored in mPtr until we move it
+ // to mPrtPreview once we've finish creating the print preview. We must
+ // clear mPtrPreview so that code will use mPtr until that happens.
+ mPrtPreview = nullptr;
+
+ SetIsPrintPreview(true);
+ } else {
+ mProgressDialogIsShown = false;
+
+ SetIsPrinting(true);
+ }
+
+ if (aWebProgressListener) {
+ printData->mPrintProgressListeners.AppendObject(aWebProgressListener);
+ }
+
+ // Get the document from the currently focused window.
+ RefPtr<Document> focusedDoc = FindFocusedDocument(aDoc);
+
+ // Get the docshell for this documentviewer
+ nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mDocShell, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ {
+ nsAutoScriptBlocker scriptBlocker;
+ printData->mPrintObject = MakeUnique<nsPrintObject>();
+ rv = printData->mPrintObject->InitAsRootObject(docShell, aDoc,
+ mIsCreatingPrintPreview);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ printData->mPrintDocList.AppendElement(printData->mPrintObject.get());
+
+ printData->mIsParentAFrameSet = IsParentAFrameSet(docShell);
+ printData->mPrintObject->mFrameType =
+ printData->mIsParentAFrameSet ? eFrameSet : eDoc;
+
+ BuildNestedPrintObjects(printData->mPrintObject, focusedDoc, printData);
+ }
+
+ // The nsAutoScriptBlocker above will now have been destroyed, which may
+ // cause our print/print-preview operation to finish. In this case, we
+ // should immediately return an error code so that the root caller knows
+ // it shouldn't continue to do anything with this instance.
+ if (mIsDestroying) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // XXX This isn't really correct...
+ if (!printData->mPrintObject->mDocument ||
+ !printData->mPrintObject->mDocument->GetRootElement())
+ return NS_ERROR_GFX_PRINTER_STARTDOC;
+
+ // if they don't pass in a PrintSettings, then get the Global PS
+ printData->mPrintSettings = aPrintSettings;
+ if (!printData->mPrintSettings) {
+ MOZ_TRY(GetDefaultPrintSettings(getter_AddRefs(printData->mPrintSettings)));
+ }
+
+ MOZ_TRY(EnsureSettingsHasPrinterNameSet(printData->mPrintSettings));
+
+ printData->mPrintSettings->SetIsCancelled(false);
+ printData->mPrintSettings->GetShrinkToFit(&printData->mShrinkToFit);
+
+ // Create a print session and let the print settings know about it.
+ // Don't overwrite an existing print session.
+ // The print settings hold an nsWeakPtr to the session so it does not
+ // need to be cleared from the settings at the end of the job.
+ // XXX What lifetime does the printSession need to have?
+ nsCOMPtr<nsIPrintSession> printSession;
+ bool remotePrintJobListening = false;
+ if (!mIsCreatingPrintPreview) {
+ rv = printData->mPrintSettings->GetPrintSession(
+ getter_AddRefs(printSession));
+ if (NS_FAILED(rv) || !printSession) {
+ printSession = do_CreateInstance("@mozilla.org/gfx/printsession;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ printData->mPrintSettings->SetPrintSession(printSession);
+ } else {
+ RefPtr<layout::RemotePrintJobChild> remotePrintJob =
+ printSession->GetRemotePrintJob();
+ if (remotePrintJob) {
+ // If we have a RemotePrintJob add it to the print progress listeners,
+ // so it can forward to the parent.
+ printData->mPrintProgressListeners.AppendElement(remotePrintJob);
+ remotePrintJobListening = true;
+ }
+ }
+ }
+
+ // Now determine how to set up the Frame print UI
+ printData->mPrintSettings->SetIsPrintSelectionRBEnabled(
+ !mDisallowSelectionPrint && printData->mSelectionRoot);
+
+ bool printingViaParent =
+ XRE_IsContentProcess() && Preferences::GetBool("print.print_via_parent");
+ nsCOMPtr<nsIDeviceContextSpec> devspec;
+ if (printingViaParent) {
+ devspec = new nsDeviceContextSpecProxy();
+ } else {
+ devspec = do_CreateInstance("@mozilla.org/gfx/devicecontextspec;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ bool printSilently = false;
+ printData->mPrintSettings->GetPrintSilent(&printSilently);
+ if (StaticPrefs::print_always_print_silent()) {
+ printSilently = true;
+ }
+
+ if (mIsDoingPrinting && printSilently) {
+ Telemetry::ScalarAdd(Telemetry::ScalarID::PRINTING_SILENT_PRINT, 1);
+ }
+
+ // If printing via parent we still call ShowPrintDialog even for print preview
+ // because we use that to retrieve the print settings from the printer.
+ // The dialog is not shown, but this means we don't need to access the printer
+ // driver from the child, which causes sandboxing issues.
+ if (!mIsCreatingPrintPreview || printingViaParent) {
+ // The new print UI does not need to enter ShowPrintDialog below to spin
+ // the event loop and fetch real printer settings from the parent process,
+ // since it always passes complete print settings. (In fact, trying to
+ // fetch them from the parent can cause crashes.) Here we check for that
+ // case so that we can avoid calling ShowPrintDialog below. To err on the
+ // safe side, we exclude the old UI.
+ //
+ // TODO: We should MOZ_DIAGNOSTIC_ASSERT that GetIsInitializedFromPrinter
+ // returns true.
+ bool settingsAreComplete = false;
+ if (StaticPrefs::print_tab_modal_enabled()) {
+ printData->mPrintSettings->GetIsInitializedFromPrinter(
+ &settingsAreComplete);
+ }
+
+ // Ask dialog to be Print Shown via the Plugable Printing Dialog Service
+ // This service is for the Print Dialog and the Print Progress Dialog
+ // If printing silently or you can't get the service continue on
+ // If printing via the parent then we need to confirm that the pref is set
+ // and get a remote print job, but the parent won't display a prompt.
+ if (!settingsAreComplete && (!printSilently || printingViaParent)) {
+ nsCOMPtr<nsIPrintingPromptService> printPromptService(
+ do_GetService(kPrintingPromptService));
+ if (printPromptService) {
+ nsPIDOMWindowOuter* domWin = nullptr;
+ // We leave domWin as nullptr to indicate a call for print preview.
+ if (!mIsCreatingPrintPreview) {
+ domWin = aDoc->GetOriginalDocument()->GetWindow();
+ NS_ENSURE_TRUE(domWin, NS_ERROR_FAILURE);
+
+ if (!printSilently) {
+ if (mCreatedForPrintPreview) {
+ Telemetry::ScalarAdd(
+ Telemetry::ScalarID::PRINTING_DIALOG_OPENED_VIA_PREVIEW, 1);
+ } else {
+ Telemetry::ScalarAdd(
+ Telemetry::ScalarID::PRINTING_DIALOG_OPENED_WITHOUT_PREVIEW,
+ 1);
+ }
+ }
+ }
+
+ // Platforms not implementing a given dialog for the service may
+ // return NS_ERROR_NOT_IMPLEMENTED or an error code.
+ //
+ // NS_ERROR_NOT_IMPLEMENTED indicates they want default behavior
+ // Any other error code means we must bail out
+ //
+ rv = printPromptService->ShowPrintDialog(domWin,
+ printData->mPrintSettings);
+
+ if (!mIsCreatingPrintPreview) {
+ if (rv == NS_ERROR_ABORT) {
+ // When printing silently we can't get here since the user doesn't
+ // have the opportunity to cancel printing.
+ if (mCreatedForPrintPreview) {
+ Telemetry::ScalarAdd(
+ Telemetry::ScalarID::PRINTING_DIALOG_VIA_PREVIEW_CANCELLED,
+ 1);
+ } else {
+ Telemetry::ScalarAdd(
+ Telemetry::ScalarID::
+ PRINTING_DIALOG_WITHOUT_PREVIEW_CANCELLED,
+ 1);
+ }
+ }
+ }
+
+ //
+ // ShowPrintDialog triggers an event loop which means we can't assume
+ // that the state of this->{anything} matches the state we've checked
+ // above. Including that a given {thing} is non null.
+ if (NS_WARN_IF(mPrt != printData)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ // since we got the dialog and it worked then make sure we
+ // are telling GFX we want to print silent
+ printSilently = true;
+
+ if (printData->mPrintSettings && !mIsCreatingPrintPreview) {
+ // The user might have changed shrink-to-fit in the print dialog, so
+ // update our copy of its state
+ printData->mPrintSettings->GetShrinkToFit(&printData->mShrinkToFit);
+
+ // If we haven't already added the RemotePrintJob as a listener,
+ // add it now if there is one.
+ if (!remotePrintJobListening) {
+ RefPtr<layout::RemotePrintJobChild> remotePrintJob =
+ printSession->GetRemotePrintJob();
+ if (remotePrintJob) {
+ printData->mPrintProgressListeners.AppendElement(
+ remotePrintJob);
+ remotePrintJobListening = true;
+ }
+ }
+ }
+ } else if (rv == NS_ERROR_NOT_IMPLEMENTED) {
+ // This means the Dialog service was there,
+ // but they choose not to implement this dialog and
+ // are looking for default behavior from the toolkit
+ rv = NS_OK;
+ }
+ } else {
+ // No dialog service available
+ rv = NS_ERROR_NOT_IMPLEMENTED;
+ }
+ } else if (printSilently && !printingViaParent) {
+ // The condition above is only so contorted in order to enter this block
+ // under the exact same circumstances as we used to, in order to
+ // minimize risk for this change which may be getting late Beta uplift.
+ // Frankly calling SetupSilentPrinting should not be necessary any more
+ // since nsDeviceContextSpecGTK::EndDocument does what we need using a
+ // Runnable instead of spinning an event loop in a risk place like here.
+ // Additionally we should never need to do this when setting up print
+ // preview, we would only need it for printing.
+
+ // Call any code that requires a run of the event loop.
+ rv = printData->mPrintSettings->SetupSilentPrinting();
+ }
+ // Check explicitly for abort because it's expected
+ if (rv == NS_ERROR_ABORT) return rv;
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ MOZ_TRY(devspec->Init(nullptr, printData->mPrintSettings,
+ mIsCreatingPrintPreview));
+
+ printData->mPrintDC = new nsDeviceContext();
+ MOZ_TRY(printData->mPrintDC->InitForPrinting(devspec));
+
+ if (XRE_IsParentProcess() && !printData->mPrintDC->IsSyncPagePrinting()) {
+ RefPtr<nsPrintJob> self(this);
+ printData->mPrintDC->RegisterPageDoneCallback(
+ [self](nsresult aResult) { self->PageDone(aResult); });
+ }
+
+ if (!mozilla::StaticPrefs::print_tab_modal_enabled() &&
+ mIsCreatingPrintPreview) {
+ // In legacy print-preview mode, override any UI that wants to PrintPreview
+ // any selection or page range. The legacy print-preview intends to view
+ // every page in PrintPreview each time.
+ printData->mPrintSettings->SetPageRanges({});
+ }
+
+ MOZ_TRY(EnablePOsForPrinting());
+
+ if (mIsCreatingPrintPreview) {
+ bool notifyOnInit = false;
+ ShowPrintProgress(false, notifyOnInit, aDoc);
+
+ if (!notifyOnInit) {
+ rv = InitPrintDocConstruction(false);
+ } else {
+ rv = NS_OK;
+ }
+ } else {
+ bool doNotify;
+ ShowPrintProgress(true, doNotify, aDoc);
+ if (!doNotify) {
+ // Print listener setup...
+ printData->OnStartPrinting();
+
+ rv = InitPrintDocConstruction(false);
+ }
+ }
+
+ return NS_OK;
+}
+
+//---------------------------------------------------------------------------------
+nsresult nsPrintJob::Print(Document* aSourceDoc,
+ nsIPrintSettings* aPrintSettings,
+ nsIWebProgressListener* aWebProgressListener) {
+ // If we have a print preview document, use that instead of the original
+ // mDocument. That way animated images etc. get printed using the same state
+ // as in print preview.
+ RefPtr<Document> doc = mPrtPreview && mPrtPreview->mPrintObject
+ ? mPrtPreview->mPrintObject->mDocument.get()
+ : aSourceDoc;
+
+ nsresult rv = CommonPrint(false, aPrintSettings, aWebProgressListener, doc);
+
+ if (!aPrintSettings) {
+ // This is temporary until after bug 1602410 lands.
+ return rv;
+ }
+
+ // Save the print settings if the user picked them.
+ // We should probably do this immediately after the user confirms their
+ // selection (that is, move this to nsPrintingPromptService::ShowPrintDialog,
+ // just after the nsIPrintDialogService::Show call returns).
+ bool printSilently;
+ aPrintSettings->GetPrintSilent(&printSilently);
+ if (!printSilently) { // user picked settings
+ bool saveOnCancel;
+ aPrintSettings->GetSaveOnCancel(&saveOnCancel);
+ if ((rv != NS_ERROR_ABORT || saveOnCancel) &&
+ Preferences::GetBool("print.save_print_settings", false)) {
+ nsCOMPtr<nsIPrintSettingsService> printSettingsService =
+ do_GetService("@mozilla.org/gfx/printsettings-service;1");
+ printSettingsService->SavePrintSettingsToPrefs(
+ aPrintSettings, true, nsIPrintSettings::kInitSaveAll);
+ printSettingsService->SavePrintSettingsToPrefs(
+ aPrintSettings, false, nsIPrintSettings::kInitSavePrinterName);
+ }
+ }
+
+ return rv;
+}
+
+nsresult nsPrintJob::PrintPreview(Document* aSourceDoc,
+ nsIPrintSettings* aPrintSettings,
+ nsIWebProgressListener* aWebProgressListener,
+ PrintPreviewResolver&& aCallback) {
+ // Take ownership of aCallback, otherwise a function further up the call
+ // stack will call it to signal failure (by passing zero).
+ mPrintPreviewCallback = std::move(aCallback);
+
+ nsresult rv =
+ CommonPrint(true, aPrintSettings, aWebProgressListener, aSourceDoc);
+ if (NS_FAILED(rv)) {
+ if (mPrintPreviewCallback) {
+ mPrintPreviewCallback(
+ PrintPreviewResultInfo(0, 0, false, false, false)); // signal error
+ mPrintPreviewCallback = nullptr;
+ }
+ }
+ return rv;
+}
+
+int32_t nsPrintJob::GetRawNumPages() const {
+ auto [seqFrame, numSheets] = GetSeqFrameAndCountSheets();
+ Unused << numSheets;
+ return seqFrame ? seqFrame->GetRawNumPages() : 0;
+}
+
+bool nsPrintJob::GetIsEmpty() const {
+ auto [seqFrame, numSheets] = GetSeqFrameAndCountSheets();
+ if (!seqFrame) {
+ return true;
+ }
+ if (numSheets > 1) {
+ return false;
+ }
+ return !seqFrame->GetPagesInFirstSheet();
+}
+
+int32_t nsPrintJob::GetPrintPreviewNumSheets() const {
+ auto [seqFrame, numSheets] = GetSeqFrameAndCountSheets();
+ Unused << seqFrame;
+ return numSheets;
+}
+
+already_AddRefed<nsIPrintSettings> nsPrintJob::GetCurrentPrintSettings() {
+ if (mPrt) {
+ return do_AddRef(mPrt->mPrintSettings);
+ }
+ if (mPrtPreview) {
+ return do_AddRef(mPrtPreview->mPrintSettings);
+ }
+ return nullptr;
+}
+
+//-----------------------------------------------------------------
+//-- Section: Pre-Reflow Methods
+//-----------------------------------------------------------------
+
+//----------------------------------------------------------------------
+// Set up to use the "pluggable" Print Progress Dialog
+void nsPrintJob::ShowPrintProgress(bool aIsForPrinting, bool& aDoNotify,
+ Document* aDoc) {
+ // default to not notifying, that if something here goes wrong
+ // or we aren't going to show the progress dialog we can straight into
+ // reflowing the doc for printing.
+ aDoNotify = false;
+
+ // Guarantee that mPrt and the objects it owns won't be deleted. If this
+ // method shows a progress dialog and spins the event loop. So, mPrt may be
+ // cleared or recreated.
+ RefPtr<nsPrintData> printData = mPrt;
+
+ bool showProgresssDialog =
+ !mProgressDialogIsShown && StaticPrefs::print_show_print_progress();
+
+ // Turning off the showing of Print Progress in Prefs overrides
+ // whether the calling PS desire to have it on or off, so only check PS if
+ // prefs says it's ok to be on.
+ if (showProgresssDialog) {
+ printData->mPrintSettings->GetShowPrintProgress(&showProgresssDialog);
+ }
+
+ // Now open the service to get the progress dialog
+ // If we don't get a service, that's ok, then just don't show progress
+ if (showProgresssDialog) {
+ nsCOMPtr<nsIPrintingPromptService> printPromptService(
+ do_GetService(kPrintingPromptService));
+ if (printPromptService) {
+ if (mIsForModalWindow) {
+ // Showing a print progress dialog when printing a modal window
+ // isn't supported. See bug 301560.
+ return;
+ }
+
+ nsPIDOMWindowOuter* domWin = aDoc->GetOriginalDocument()->GetWindow();
+ if (!domWin) return;
+
+ nsCOMPtr<nsIWebProgressListener> printProgressListener;
+
+ nsresult rv = printPromptService->ShowPrintProgressDialog(
+ domWin, printData->mPrintSettings, this, aIsForPrinting,
+ getter_AddRefs(printProgressListener),
+ getter_AddRefs(printData->mPrintProgressParams), &aDoNotify);
+ if (NS_SUCCEEDED(rv)) {
+ if (printProgressListener) {
+ printData->mPrintProgressListeners.AppendObject(
+ printProgressListener);
+ }
+
+ if (printData->mPrintProgressParams) {
+ SetURLAndTitleOnProgressParams(printData->mPrintObject,
+ printData->mPrintProgressParams);
+ }
+ }
+ }
+ }
+}
+
+// static
+void nsPrintJob::GetDisplayTitleAndURL(Document& aDoc,
+ nsIPrintSettings* aSettings,
+ DocTitleDefault aTitleDefault,
+ nsAString& aTitle, nsAString& aURLStr) {
+ aTitle.Truncate();
+ aURLStr.Truncate();
+
+ if (aSettings) {
+ aSettings->GetTitle(aTitle);
+ aSettings->GetDocURL(aURLStr);
+ }
+
+ if (aTitle.IsEmpty()) {
+ aDoc.GetTitle(aTitle);
+ if (aTitle.IsEmpty()) {
+ if (!aURLStr.IsEmpty() &&
+ aTitleDefault == DocTitleDefault::eDocURLElseFallback) {
+ aTitle = aURLStr;
+ } else {
+ nsCOMPtr<nsIStringBundle> brandBundle;
+ nsCOMPtr<nsIStringBundleService> svc =
+ mozilla::services::GetStringBundleService();
+ if (svc) {
+ svc->CreateBundle("chrome://branding/locale/brand.properties",
+ getter_AddRefs(brandBundle));
+ if (brandBundle) {
+ brandBundle->GetStringFromName("brandShortName", aTitle);
+ }
+ }
+ if (aTitle.IsEmpty()) {
+ aTitle.AssignLiteral(u"Mozilla Document");
+ }
+ }
+ }
+ }
+
+ if (aURLStr.IsEmpty()) {
+ nsIURI* url = aDoc.GetDocumentURI();
+ if (!url) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> exposableURI = net::nsIOService::CreateExposableURI(url);
+ nsAutoCString urlCStr;
+ nsresult rv = exposableURI->GetSpec(urlCStr);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ nsCOMPtr<nsITextToSubURI> textToSubURI =
+ do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ textToSubURI->UnEscapeURIForUI(urlCStr, aURLStr);
+ }
+}
+
+//---------------------------------------------------------------------
+nsresult nsPrintJob::DocumentReadyForPrinting() {
+ // Send the document to the printer...
+ nsresult rv = SetupToPrintContent();
+ if (NS_FAILED(rv)) {
+ // The print job was canceled or there was a problem
+ // So remove all other documents from the print list
+ DonePrintingSheets(nullptr, rv);
+ }
+ return rv;
+}
+
+/** ---------------------------------------------------
+ * Cleans up when an error occurred
+ */
+nsresult nsPrintJob::CleanupOnFailure(nsresult aResult, bool aIsPrinting) {
+ PR_PL(("**** Failed %s - rv 0x%" PRIX32,
+ aIsPrinting ? "Printing" : "Print Preview",
+ static_cast<uint32_t>(aResult)));
+
+ /* cleanup... */
+ if (mPagePrintTimer) {
+ mPagePrintTimer->Stop();
+ DisconnectPagePrintTimer();
+ }
+
+ if (aIsPrinting) {
+ SetIsPrinting(false);
+ } else {
+ SetIsPrintPreview(false);
+ mIsCreatingPrintPreview = false;
+ }
+
+ /* cleanup done, let's fire-up an error dialog to notify the user
+ * what went wrong...
+ *
+ * When rv == NS_ERROR_ABORT, it means we want out of the
+ * print job without displaying any error messages
+ */
+ if (aResult != NS_ERROR_ABORT) {
+ FirePrintingErrorEvent(aResult);
+ }
+
+ FirePrintCompletionEvent();
+
+ return aResult;
+}
+
+//---------------------------------------------------------------------
+void nsPrintJob::FirePrintingErrorEvent(nsresult aPrintError) {
+ if (mPrintPreviewCallback) {
+ mPrintPreviewCallback(
+ PrintPreviewResultInfo(0, 0, false, false, false)); // signal error
+ mPrintPreviewCallback = nullptr;
+ }
+
+ nsCOMPtr<nsIContentViewer> cv = do_QueryInterface(mDocViewerPrint);
+ if (NS_WARN_IF(!cv)) {
+ return;
+ }
+
+ nsCOMPtr<Document> doc = cv->GetDocument();
+ RefPtr<CustomEvent> event = NS_NewDOMCustomEvent(doc, nullptr, nullptr);
+
+ MOZ_ASSERT(event);
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(event->GetParentObject())) {
+ return;
+ }
+ JSContext* cx = jsapi.cx();
+
+ JS::Rooted<JS::Value> detail(
+ cx, JS::NumberValue(static_cast<double>(aPrintError)));
+ event->InitCustomEvent(cx, u"PrintingError"_ns, false, false, detail);
+ event->SetTrusted(true);
+
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(doc, event);
+ asyncDispatcher->mOnlyChromeDispatch = ChromeOnlyDispatch::eYes;
+ asyncDispatcher->RunDOMEventWhenSafe();
+
+ // Inform any progress listeners of the Error.
+ if (mPrt) {
+ // Note that nsPrintData::DoOnStatusChange() will call some listeners.
+ // So, mPrt can be cleared or recreated.
+ RefPtr<nsPrintData> printData = mPrt;
+ printData->DoOnStatusChange(aPrintError);
+ }
+}
+
+//-----------------------------------------------------------------
+//-- Section: Reflow Methods
+//-----------------------------------------------------------------
+
+nsresult nsPrintJob::ReconstructAndReflow(bool doSetPixelScale) {
+ if (NS_WARN_IF(!mPrt)) {
+ return NS_ERROR_FAILURE;
+ }
+
+#if defined(XP_WIN) && defined(EXTENDED_DEBUG_PRINTING)
+ // We need to clear all the output files here
+ // because they will be re-created with second reflow of the docs
+ if (MOZ_LOG_TEST(gPrintingLog, DUMP_LAYOUT_LEVEL)) {
+ RemoveFilesInDir(".\\");
+ gDumpFileNameCnt = 0;
+ gDumpLOFileNameCnt = 0;
+ }
+#endif
+
+ // In this loop, it's conceivable that one of our helpers might clear mPrt,
+ // while we're using it & its members! So we capture it in an owning local
+ // reference & use that instead of using mPrt directly.
+ RefPtr<nsPrintData> printData = mPrt;
+ for (uint32_t i = 0; i < printData->mPrintDocList.Length(); ++i) {
+ nsPrintObject* po = printData->mPrintDocList.ElementAt(i);
+ NS_ASSERTION(po, "nsPrintObject can't be null!");
+
+ if (!po->PrintingIsEnabled() || po->mInvisible) {
+ continue;
+ }
+
+ // When the print object has been marked as "print the document" (i.e,
+ // po->PrintingIsEnabled() is true), mPresContext and mPresShell should be
+ // non-nullptr (i.e., should've been created for the print) since they
+ // are necessary to print the document.
+ MOZ_ASSERT(po->mPresContext && po->mPresShell,
+ "mPresContext and mPresShell shouldn't be nullptr when the "
+ "print object "
+ "has been marked as \"print the document\"");
+
+ UpdateZoomRatio(po, doSetPixelScale);
+
+ po->mPresContext->SetPageScale(po->mZoomRatio);
+
+ // Calculate scale factor from printer to screen
+ float printDPI = float(AppUnitsPerCSSInch()) /
+ float(printData->mPrintDC->AppUnitsPerDevPixel());
+ po->mPresContext->SetPrintPreviewScale(mScreenDPI / printDPI);
+
+ RefPtr<PresShell> presShell(po->mPresShell);
+ if (NS_WARN_IF(presShell->IsDestroying())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ presShell->ReconstructFrames();
+
+ // If the printing was canceled or restarted with different data,
+ // let's stop doing this printing.
+ if (NS_WARN_IF(mPrt != printData)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // For all views except the first one, setup the root view.
+ // ??? Can there be multiple po for the top-level-document?
+ bool documentIsTopLevel = true;
+ if (i != 0) {
+ nsSize adjSize;
+ bool doReturn;
+ nsresult rv = SetRootView(po, doReturn, documentIsTopLevel, adjSize);
+
+ MOZ_ASSERT(!documentIsTopLevel, "How could this happen?");
+
+ if (NS_FAILED(rv) || doReturn) {
+ return rv;
+ }
+ }
+
+ presShell->FlushPendingNotifications(FlushType::Layout);
+
+ if (NS_WARN_IF(presShell->IsDestroying())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // If the printing was canceled or restarted with different data,
+ // let's stop doing this printing.
+ if (NS_WARN_IF(mPrt != printData)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = UpdateSelectionAndShrinkPrintObject(po, documentIsTopLevel);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+//-------------------------------------------------------
+nsresult nsPrintJob::SetupToPrintContent() {
+ // This method may be called while DoCommonPrint() initializes the instance
+ // when its script blocker goes out of scope. In such case, this cannot do
+ // its job as expected because some objects in mPrt have not been initialized
+ // yet but they are necessary.
+ // Note: it shouldn't be possible for mPrt->mPrintObject to be null; we check
+ // it for good measure (after we check its owner) before we start
+ // dereferencing it below.
+ if (NS_WARN_IF(!mPrt) || NS_WARN_IF(!mPrt->mPrintObject)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // If this is creating print preview, mPrt->mPrintObject->mPresContext and
+ // mPrt->mPrintObject->mPresShell need to be non-nullptr because this cannot
+ // initialize page sequence frame without them at end of this method since
+ // page sequence frame has already been destroyed or not been created yet.
+ if (mIsCreatingPrintPreview &&
+ (NS_WARN_IF(!mPrt->mPrintObject->mPresContext) ||
+ NS_WARN_IF(!mPrt->mPrintObject->mPresShell))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // If this is printing some documents (not print-previewing the documents),
+ // mPrt->mPrintObject->mPresContext and mPrt->mPrintObject->mPresShell can be
+ // nullptr only when mPrt->mPrintObject->PrintingIsEnabled() is false. E.g.,
+ // if the document has a <frameset> element and it's printing only content in
+ // a <frame> element or all <frame> elements separately.
+ MOZ_ASSERT(
+ (!mIsCreatingPrintPreview && !mPrt->mPrintObject->PrintingIsEnabled()) ||
+ (mPrt->mPrintObject->mPresContext && mPrt->mPrintObject->mPresShell),
+ "mPresContext and mPresShell shouldn't be nullptr when printing the "
+ "document or creating print-preview");
+
+ bool didReconstruction = false;
+
+ // This method works with mPrt->mPrintObject. So, we need to guarantee that
+ // it won't be deleted in this method. We achieve this by holding a strong
+ // local reference to mPrt, which in turn keeps mPrintObject alive.
+ RefPtr<nsPrintData> printData = mPrt;
+
+ // If some new content got loaded since the initial reflow rebuild
+ // everything.
+ if (mDidLoadDataForPrinting) {
+ nsresult rv = ReconstructAndReflow(DoSetPixelScale());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ // If the printing was canceled or restarted with different data,
+ // let's stop doing this printing.
+ if (NS_WARN_IF(mPrt != printData)) {
+ return NS_ERROR_FAILURE;
+ }
+ didReconstruction = true;
+ }
+
+ // Here is where we figure out if extra reflow for shrinking the content
+ // is required.
+ // But skip this step if we are in PrintPreview
+ bool ppIsShrinkToFit = mPrtPreview && mPrtPreview->mShrinkToFit;
+ if (printData->mShrinkToFit && !ppIsShrinkToFit) {
+ // Now look for the PO that has the smallest percent for shrink to fit
+ if (printData->mPrintDocList.Length() > 1 &&
+ printData->mPrintObject->mFrameType == eFrameSet) {
+ nsPrintObject* smallestPO = FindSmallestSTF();
+ NS_ASSERTION(smallestPO, "There must always be an XMost PO!");
+ if (smallestPO) {
+ // Calc the shrinkage based on the entire content area
+ printData->mShrinkRatio = smallestPO->mShrinkRatio;
+ }
+ } else {
+ // Single document so use the Shrink as calculated for the PO
+ printData->mShrinkRatio = printData->mPrintObject->mShrinkRatio;
+ }
+
+ if (printData->mShrinkRatio < 0.998f) {
+ nsresult rv = ReconstructAndReflow(true);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ // If the printing was canceled or restarted with different data,
+ // let's stop doing this printing.
+ if (NS_WARN_IF(mPrt != printData)) {
+ return NS_ERROR_FAILURE;
+ }
+ didReconstruction = true;
+ }
+
+ if (MOZ_LOG_TEST(gPrintingLog, LogLevel::Debug)) {
+ float calcRatio = 0.0f;
+ if (printData->mPrintDocList.Length() > 1 &&
+ printData->mPrintObject->mFrameType == eFrameSet) {
+ nsPrintObject* smallestPO = FindSmallestSTF();
+ NS_ASSERTION(smallestPO, "There must always be an XMost PO!");
+ if (smallestPO) {
+ // Calc the shrinkage based on the entire content area
+ calcRatio = smallestPO->mShrinkRatio;
+ }
+ } else {
+ // Single document so use the Shrink as calculated for the PO
+ calcRatio = printData->mPrintObject->mShrinkRatio;
+ }
+ PR_PL(
+ ("*******************************************************************"
+ "*******\n"));
+ PR_PL(("STF Ratio is: %8.5f Effective Ratio: %8.5f Diff: %8.5f\n",
+ printData->mShrinkRatio, calcRatio,
+ printData->mShrinkRatio - calcRatio));
+ PR_PL(
+ ("*******************************************************************"
+ "*******\n"));
+ }
+ }
+
+ // If the frames got reconstructed and reflowed the number of pages might
+ // has changed.
+ if (didReconstruction) {
+ FirePrintPreviewUpdateEvent();
+ // If the printing was canceled or restarted with different data,
+ // let's stop doing this printing.
+ if (NS_WARN_IF(mPrt != printData)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ DUMP_DOC_LIST(("\nAfter Reflow------------------------------------------"));
+ PR_PL(("\n"));
+ PR_PL(("-------------------------------------------------------\n"));
+ PR_PL(("\n"));
+
+ CalcNumPrintablePages(printData->mNumPrintablePages);
+
+ PR_PL(("--- Printing %d pages\n", printData->mNumPrintablePages));
+ DUMP_DOC_TREELAYOUT;
+
+ // Print listener setup...
+ printData->OnStartPrinting();
+
+ // If the printing was canceled or restarted with different data,
+ // let's stop doing this printing.
+ if (NS_WARN_IF(mPrt != printData)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoString fileNameStr;
+ // check to see if we are printing to a file
+ bool isPrintToFile = false;
+ printData->mPrintSettings->GetPrintToFile(&isPrintToFile);
+ if (isPrintToFile) {
+ // On some platforms The BeginDocument needs to know the name of the file.
+ printData->mPrintSettings->GetToFileName(fileNameStr);
+ }
+
+ nsAutoString docTitleStr;
+ nsAutoString docURLStr;
+ GetDisplayTitleAndURL(
+ *printData->mPrintObject->mDocument, printData->mPrintSettings,
+ DocTitleDefault::eDocURLElseFallback, docTitleStr, docURLStr);
+
+ int32_t startPage = 1;
+ int32_t endPage = printData->mNumPrintablePages;
+
+ nsTArray<int32_t> ranges;
+ printData->mPrintSettings->GetPageRanges(ranges);
+ for (size_t i = 0; i < ranges.Length(); i += 2) {
+ startPage = std::max(1, std::min(startPage, ranges[i]));
+ endPage = std::min(printData->mNumPrintablePages,
+ std::max(endPage, ranges[i + 1]));
+ }
+
+ nsresult rv = NS_OK;
+ // BeginDocument may pass back a FAILURE code
+ // i.e. On Windows, if you are printing to a file and hit "Cancel"
+ // to the "File Name" dialog, this comes back as an error
+ // Don't start printing when regression test are executed
+ if (mIsDoingPrinting) {
+ rv = printData->mPrintDC->BeginDocument(docTitleStr, fileNameStr, startPage,
+ endPage);
+ }
+
+ if (mIsCreatingPrintPreview) {
+ // Copy docTitleStr and docURLStr to the pageSequenceFrame, to be displayed
+ // in the header
+ nsPageSequenceFrame* seqFrame =
+ printData->mPrintObject->mPresShell->GetPageSequenceFrame();
+ if (seqFrame) {
+ seqFrame->StartPrint(printData->mPrintObject->mPresContext,
+ printData->mPrintSettings, docTitleStr, docURLStr);
+ }
+ }
+
+ PR_PL(("****************** Begin Document ************************\n"));
+
+ if (NS_FAILED(rv)) {
+ NS_WARNING_ASSERTION(rv == NS_ERROR_ABORT,
+ "Failed to begin document for printing");
+ return rv;
+ }
+
+ // This will print the docshell document
+ // when it completes asynchronously in the DonePrintingSheets method
+ // it will check to see if there are more docshells to be printed and
+ // then PrintDocContent will be called again.
+
+ if (mIsDoingPrinting) {
+ PrintDocContent(printData->mPrintObject, rv); // ignore return value
+ }
+
+ return rv;
+}
+
+//-------------------------------------------------------
+// Recursively reflow each sub-doc and then calc
+// all the frame locations of the sub-docs
+nsresult nsPrintJob::ReflowDocList(const UniquePtr<nsPrintObject>& aPO,
+ bool aSetPixelScale) {
+ NS_ENSURE_ARG_POINTER(aPO);
+
+ // Check to see if the subdocument's element has been hidden by the parent
+ // document
+ if (aPO->mParent && aPO->mParent->mPresShell) {
+ nsIFrame* frame =
+ aPO->mContent ? aPO->mContent->GetPrimaryFrame() : nullptr;
+ if (!frame || !frame->StyleVisibility()->IsVisible()) {
+ aPO->EnablePrinting(false);
+ aPO->mInvisible = true;
+ return NS_OK;
+ }
+ }
+
+ UpdateZoomRatio(aPO.get(), aSetPixelScale);
+
+ // Reflow the PO
+ MOZ_TRY(ReflowPrintObject(aPO));
+
+ for (const UniquePtr<nsPrintObject>& kid : aPO->mKids) {
+ MOZ_TRY(ReflowDocList(kid, aSetPixelScale));
+ }
+ return NS_OK;
+}
+
+void nsPrintJob::FirePrintPreviewUpdateEvent() {
+ // Dispatch the event only while in PrintPreview. When printing, there is no
+ // listener bound to this event and therefore no need to dispatch it.
+ if (mCreatedForPrintPreview && !mIsDoingPrinting) {
+ nsCOMPtr<nsIContentViewer> cv = do_QueryInterface(mDocViewerPrint);
+ (new AsyncEventDispatcher(cv->GetDocument(), u"printPreviewUpdate"_ns,
+ CanBubble::eYes, ChromeOnlyDispatch::eYes))
+ ->RunDOMEventWhenSafe();
+ }
+}
+
+nsresult nsPrintJob::InitPrintDocConstruction(bool aHandleError) {
+ // Guarantee that mPrt->mPrintObject won't be deleted. It's owned by mPrt.
+ // So, we should grab it with local variable.
+ RefPtr<nsPrintData> printData = mPrt;
+
+ if (NS_WARN_IF(!printData)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Attach progressListener to catch network requests.
+ mDidLoadDataForPrinting = false;
+
+ {
+ AutoRestore<bool> restore{mDoingInitialReflow};
+ mDoingInitialReflow = true;
+
+ nsCOMPtr<nsIWebProgress> webProgress =
+ do_QueryInterface(printData->mPrintObject->mDocShell);
+ webProgress->AddProgressListener(static_cast<nsIWebProgressListener*>(this),
+ nsIWebProgress::NOTIFY_STATE_REQUEST);
+
+ MOZ_TRY(ReflowDocList(printData->mPrintObject, DoSetPixelScale()));
+
+ FirePrintPreviewUpdateEvent();
+ }
+
+ MaybeResumePrintAfterResourcesLoaded(aHandleError);
+ return NS_OK;
+}
+
+bool nsPrintJob::ShouldResumePrint() const {
+ if (mDoingInitialReflow) {
+ return false;
+ }
+ Document* doc = mPrt->mPrintObject->mDocument;
+ MOZ_ASSERT(doc);
+ NS_ENSURE_TRUE(doc, true);
+ nsCOMPtr<nsILoadGroup> lg = doc->GetDocumentLoadGroup();
+ NS_ENSURE_TRUE(lg, true);
+ bool pending = false;
+ nsresult rv = lg->IsPending(&pending);
+ NS_ENSURE_SUCCESS(rv, true);
+ return !pending;
+}
+
+nsresult nsPrintJob::MaybeResumePrintAfterResourcesLoaded(
+ bool aCleanupOnError) {
+ if (!ShouldResumePrint()) {
+ mDidLoadDataForPrinting = true;
+ return NS_OK;
+ }
+ // If Destroy() has already been called, mPtr is nullptr. Then, the instance
+ // needs to do nothing anymore in this method.
+ // Note: it shouldn't be possible for mPrt->mPrintObject to be null; we
+ // just check it for good measure, as we check its owner.
+ // Note: it shouldn't be possible for mPrt->mPrintObject->mDocShell to be
+ // null; we just check it for good measure, as we check its owner.
+ if (!mPrt || NS_WARN_IF(!mPrt->mPrintObject) ||
+ NS_WARN_IF(!mPrt->mPrintObject->mDocShell)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIWebProgress> webProgress =
+ do_QueryInterface(mPrt->mPrintObject->mDocShell);
+
+ webProgress->RemoveProgressListener(
+ static_cast<nsIWebProgressListener*>(this));
+
+ nsresult rv;
+ if (mIsDoingPrinting) {
+ rv = DocumentReadyForPrinting();
+ } else {
+ rv = FinishPrintPreview();
+ }
+
+ /* cleaup on failure + notify user */
+ if (aCleanupOnError && NS_FAILED(rv)) {
+ NS_WARNING_ASSERTION(rv == NS_ERROR_ABORT,
+ "nsPrintJob::ResumePrintAfterResourcesLoaded failed");
+ CleanupOnFailure(rv, !mIsDoingPrinting);
+ }
+
+ return rv;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIWebProgressListener
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
+nsPrintJob::OnStateChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ uint32_t aStateFlags, nsresult aStatus) {
+ if (aStateFlags & STATE_STOP) {
+ // If all resources are loaded, then finish and reflow.
+ MaybeResumePrintAfterResourcesLoaded(/* aCleanupOnError */ true);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintJob::OnProgressChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ int32_t aCurSelfProgress, int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress,
+ int32_t aMaxTotalProgress) {
+ MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintJob::OnLocationChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ nsIURI* aLocation, uint32_t aFlags) {
+ MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintJob::OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ nsresult aStatus, const char16_t* aMessage) {
+ MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintJob::OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ uint32_t aState) {
+ MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintJob::OnContentBlockingEvent(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, uint32_t aEvent) {
+ MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+//-------------------------------------------------------
+
+void nsPrintJob::UpdateZoomRatio(nsPrintObject* aPO, bool aSetPixelScale) {
+ // Here is where we set the shrinkage value into the DC
+ // and this is what actually makes it shrink
+ if (aSetPixelScale && aPO->mFrameType != eIFrame) {
+ // Round down
+ aPO->mZoomRatio = mPrt->mPrintSettings->GetPrintSelectionOnly()
+ ? aPO->mShrinkRatio - 0.005f
+ : mPrt->mShrinkRatio - 0.005f;
+ } else if (!mPrt->mShrinkToFit) {
+ double scaling;
+ mPrt->mPrintSettings->GetScaling(&scaling);
+ aPO->mZoomRatio = float(scaling);
+ }
+}
+
+nsresult nsPrintJob::UpdateSelectionAndShrinkPrintObject(
+ nsPrintObject* aPO, bool aDocumentIsTopLevel) {
+ PresShell* displayPresShell = aPO->mDocShell->GetPresShell();
+ // Transfer Selection Ranges to the new Print PresShell
+ RefPtr<Selection> selection, selectionPS;
+ // It's okay if there is no display shell, just skip copying the selection
+ if (displayPresShell) {
+ selection = displayPresShell->GetCurrentSelection(SelectionType::eNormal);
+ }
+ selectionPS = aPO->mPresShell->GetCurrentSelection(SelectionType::eNormal);
+
+ // Reset all existing selection ranges that might have been added by calling
+ // this function before.
+ if (selectionPS) {
+ selectionPS->RemoveAllRanges(IgnoreErrors());
+ }
+ if (selection && selectionPS) {
+ int32_t cnt = selection->RangeCount();
+ int32_t inx;
+ for (inx = 0; inx < cnt; ++inx) {
+ const RefPtr<nsRange> range{selection->GetRangeAt(inx)};
+ selectionPS->AddRangeAndSelectFramesAndNotifyListeners(*range,
+ IgnoreErrors());
+ }
+ }
+
+ // If we are trying to shrink the contents to fit on the page
+ // we must first locate the "pageContent" frame
+ // Then we walk the frame tree and look for the "xmost" frame
+ // this is the frame where the right-hand side of the frame extends
+ // the furthest
+ if (mPrt->mShrinkToFit && aDocumentIsTopLevel) {
+ nsPageSequenceFrame* pageSeqFrame = aPO->mPresShell->GetPageSequenceFrame();
+ NS_ENSURE_STATE(pageSeqFrame);
+ aPO->mShrinkRatio = pageSeqFrame->GetSTFPercent();
+ // Limit the shrink-to-fit scaling for some text-ish type of documents.
+ nsAutoString contentType;
+ aPO->mPresShell->GetDocument()->GetContentType(contentType);
+ if (contentType.EqualsLiteral("application/xhtml+xml") ||
+ StringBeginsWith(contentType, u"text/"_ns)) {
+ int32_t limitPercent =
+ Preferences::GetInt("print.shrink-to-fit.scale-limit-percent", 20);
+ limitPercent = std::max(0, limitPercent);
+ limitPercent = std::min(100, limitPercent);
+ float minShrinkRatio = float(limitPercent) / 100;
+ aPO->mShrinkRatio = std::max(aPO->mShrinkRatio, minShrinkRatio);
+ }
+ }
+ return NS_OK;
+}
+
+bool nsPrintJob::DoSetPixelScale() {
+ // This is an Optimization
+ // If we are in PP then we already know all the shrinkage information
+ // so just transfer it to the PrintData and we will skip the extra shrinkage
+ // reflow
+ //
+ // doSetPixelScale tells Reflow whether to set the shrinkage value into the DC
+ // The first time we do not want to do this, the second time through we do
+ bool doSetPixelScale = false;
+ bool ppIsShrinkToFit = mPrtPreview && mPrtPreview->mShrinkToFit;
+ if (ppIsShrinkToFit) {
+ mPrt->mShrinkRatio = mPrtPreview->mShrinkRatio;
+ doSetPixelScale = true;
+ }
+ return doSetPixelScale;
+}
+
+nsView* nsPrintJob::GetParentViewForRoot() {
+ if (mIsCreatingPrintPreview) {
+ if (nsCOMPtr<nsIContentViewer> cv = do_QueryInterface(mDocViewerPrint)) {
+ return cv->FindContainerView();
+ }
+ }
+ return nullptr;
+}
+
+nsresult nsPrintJob::SetRootView(nsPrintObject* aPO, bool& doReturn,
+ bool& documentIsTopLevel, nsSize& adjSize) {
+ bool canCreateScrollbars = true;
+
+ nsView* rootView;
+ nsView* parentView = nullptr;
+
+ doReturn = false;
+
+ if (aPO->mParent && aPO->mParent->PrintingIsEnabled()) {
+ nsIFrame* frame =
+ aPO->mContent ? aPO->mContent->GetPrimaryFrame() : nullptr;
+ // Without a frame, this document can't be displayed; therefore, there is no
+ // point to reflowing it
+ if (!frame) {
+ aPO->EnablePrinting(false);
+ doReturn = true;
+ return NS_OK;
+ }
+
+ // XXX If printing supported printing document hierarchies with non-constant
+ // zoom this would be wrong as we use the same mPrt->mPrintDC for all
+ // subdocuments.
+ adjSize = frame->GetContentRect().Size();
+ documentIsTopLevel = false;
+ // presshell exists because parent is printable
+
+ // the top nsPrintObject's widget will always have scrollbars
+ if (frame && frame->IsSubDocumentFrame()) {
+ nsView* view = frame->GetView();
+ NS_ENSURE_TRUE(view, NS_ERROR_FAILURE);
+ view = view->GetFirstChild();
+ NS_ENSURE_TRUE(view, NS_ERROR_FAILURE);
+ parentView = view;
+ canCreateScrollbars = false;
+ }
+ } else {
+ nscoord pageWidth, pageHeight;
+ mPrt->mPrintDC->GetDeviceSurfaceDimensions(pageWidth, pageHeight);
+ adjSize = nsSize(pageWidth, pageHeight);
+ documentIsTopLevel = true;
+ parentView = GetParentViewForRoot();
+ }
+
+ if (aPO->mViewManager->GetRootView()) {
+ // Reuse the root view that is already on the root frame.
+ rootView = aPO->mViewManager->GetRootView();
+ // Remove it from its existing parent if necessary
+ aPO->mViewManager->RemoveChild(rootView);
+ rootView->SetParent(parentView);
+ } else {
+ // Create a child window of the parent that is our "root view/window"
+ nsRect tbounds = nsRect(nsPoint(0, 0), adjSize);
+ rootView = aPO->mViewManager->CreateView(tbounds, parentView);
+ NS_ENSURE_TRUE(rootView, NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ if (mIsCreatingPrintPreview && documentIsTopLevel) {
+ aPO->mPresContext->SetPaginatedScrolling(canCreateScrollbars);
+ }
+
+ // Setup hierarchical relationship in view manager
+ aPO->mViewManager->SetRootView(rootView);
+
+ return NS_OK;
+}
+
+// Reflow a nsPrintObject
+nsresult nsPrintJob::ReflowPrintObject(const UniquePtr<nsPrintObject>& aPO) {
+ NS_ENSURE_STATE(aPO);
+
+ if (!aPO->PrintingIsEnabled()) {
+ return NS_OK;
+ }
+
+ NS_ASSERTION(!aPO->mPresContext, "Recreating prescontext");
+
+ // Guarantee that mPrt and the objects it owns won't be deleted in this method
+ // because it might be cleared if other modules called from here may fire
+ // events, notifying observers and/or listeners.
+ RefPtr<nsPrintData> printData = mPrt;
+
+ // create the PresContext
+ nsPresContext::nsPresContextType type =
+ mIsCreatingPrintPreview ? nsPresContext::eContext_PrintPreview
+ : nsPresContext::eContext_Print;
+ const bool shouldBeRoot =
+ (!aPO->mParent || !aPO->mParent->PrintingIsEnabled()) &&
+ !GetParentViewForRoot();
+ aPO->mPresContext = shouldBeRoot ? new nsRootPresContext(aPO->mDocument, type)
+ : new nsPresContext(aPO->mDocument, type);
+ aPO->mPresContext->SetPrintSettings(printData->mPrintSettings);
+
+ // init it with the DC
+ MOZ_TRY(aPO->mPresContext->Init(printData->mPrintDC));
+
+ aPO->mViewManager = new nsViewManager();
+
+ MOZ_TRY(aPO->mViewManager->Init(printData->mPrintDC));
+
+ aPO->mPresShell =
+ aPO->mDocument->CreatePresShell(aPO->mPresContext, aPO->mViewManager);
+ if (!aPO->mPresShell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // If we're printing selection then remove the nonselected nodes from our
+ // cloned document.
+ if (printData->mPrintSettings->GetPrintSelectionOnly()) {
+ // If we fail to remove the nodes then we should fail to print, because if
+ // the user was trying to print a small selection from a large document,
+ // sending the whole document to a real printer would be very frustrating.
+ MOZ_TRY(DeleteNonSelectedNodes(*aPO->mDocument));
+ }
+
+ bool doReturn = false;
+ bool documentIsTopLevel = false;
+ nsSize adjSize;
+
+ nsresult rv = SetRootView(aPO.get(), doReturn, documentIsTopLevel, adjSize);
+
+ if (NS_FAILED(rv) || doReturn) {
+ return rv;
+ }
+
+ PR_PL(("In DV::ReflowPrintObject PO: %p pS: %p (%9s) Setting w,h to %d,%d\n",
+ aPO.get(), aPO->mPresShell.get(), gFrameTypesStr[aPO->mFrameType],
+ adjSize.width, adjSize.height));
+
+ aPO->mPresShell->BeginObservingDocument();
+
+ // Here, we inform nsPresContext of the page size. Note that 'adjSize' is
+ // *usually* the page size, but we need to check. Strictly speaking, adjSize
+ // is the *device output size*, which is really the dimensions of a "sheet"
+ // rather than a "page" (an important distinction in an N-pages-per-sheet
+ // scenario). For some pages-per-sheet values, the pages are orthogonal to
+ // the sheet; we adjust for that here by swapping the width with the height.
+ nsSize pageSize = adjSize;
+ if (printData->mPrintSettings->HasOrthogonalSheetsAndPages()) {
+ std::swap(pageSize.width, pageSize.height);
+ }
+
+ aPO->mPresContext->SetPageSize(pageSize);
+
+ int32_t p2a = aPO->mPresContext->DeviceContext()->AppUnitsPerDevPixel();
+ if (documentIsTopLevel && mIsCreatingPrintPreview) {
+ if (nsCOMPtr<nsIContentViewer> cv = do_QueryInterface(mDocViewerPrint)) {
+ // If we're print-previewing and the top level document, use the bounds
+ // from our doc viewer. Page bounds is not what we want.
+ nsIntRect bounds;
+ cv->GetBounds(bounds);
+ adjSize = nsSize(bounds.width * p2a, bounds.height * p2a);
+ }
+ }
+ aPO->mPresContext->SetVisibleArea(nsRect(nsPoint(), adjSize));
+ aPO->mPresContext->SetIsRootPaginatedDocument(documentIsTopLevel);
+ aPO->mPresContext->SetPageScale(aPO->mZoomRatio);
+ // Calculate scale factor from printer to screen
+ float printDPI = float(AppUnitsPerCSSInch()) / float(p2a);
+ aPO->mPresContext->SetPrintPreviewScale(mScreenDPI / printDPI);
+
+ if (mIsCreatingPrintPreview && documentIsTopLevel) {
+ mDocViewerPrint->SetPrintPreviewPresentation(
+ aPO->mViewManager, aPO->mPresContext, aPO->mPresShell.get());
+ }
+
+ MOZ_TRY(aPO->mPresShell->Initialize());
+ NS_ASSERTION(aPO->mPresShell, "Presshell should still be here");
+
+ // Process the reflow event Initialize posted
+ RefPtr<PresShell> presShell = aPO->mPresShell;
+ presShell->FlushPendingNotifications(FlushType::Layout);
+
+ MOZ_TRY(UpdateSelectionAndShrinkPrintObject(aPO.get(), documentIsTopLevel));
+
+#ifdef EXTENDED_DEBUG_PRINTING
+ if (MOZ_LOG_TEST(gPrintingLog, DUMP_LAYOUT_LEVEL)) {
+ nsAutoCString docStr;
+ nsAutoCString urlStr;
+ GetDocTitleAndURL(aPO, docStr, urlStr);
+ char filename[256];
+ sprintf(filename, "print_dump_%d.txt", gDumpFileNameCnt++);
+ // Dump all the frames and view to a a file
+ FILE* fd = fopen(filename, "w");
+ if (fd) {
+ nsIFrame* theRootFrame = aPO->mPresShell->GetRootFrame();
+ fprintf(fd, "Title: %s\n", docStr.get());
+ fprintf(fd, "URL: %s\n", urlStr.get());
+ fprintf(fd, "--------------- Frames ----------------\n");
+ // RefPtr<gfxContext> renderingContext =
+ // printData->mPrintDocDC->CreateRenderingContext();
+ RootFrameList(aPO->mPresContext, fd, 0);
+ // DumpFrames(fd, aPO->mPresContext, renderingContext, theRootFrame, 0);
+ fprintf(fd, "---------------------------------------\n\n");
+ fprintf(fd, "--------------- Views From Root Frame----------------\n");
+ nsView* v = theRootFrame->GetView();
+ if (v) {
+ v->List(fd);
+ } else {
+ printf("View is null!\n");
+ }
+ if (aPO->mDocShell) {
+ fprintf(fd, "--------------- All Views ----------------\n");
+ DumpViews(aPO->mDocShell, fd);
+ fprintf(fd, "---------------------------------------\n\n");
+ }
+ fclose(fd);
+ }
+ }
+#endif
+
+ return NS_OK;
+}
+
+//-------------------------------------------------------
+// Figure out how many documents and how many total pages we are printing
+void nsPrintJob::CalcNumPrintablePages(int32_t& aNumPages) {
+ aNumPages = 0;
+ // Count the number of printable documents
+ // and printable pages
+ for (uint32_t i = 0; i < mPrt->mPrintDocList.Length(); i++) {
+ nsPrintObject* po = mPrt->mPrintDocList.ElementAt(i);
+ NS_ASSERTION(po, "nsPrintObject can't be null!");
+ // Note: The po->mPresContext null-check below is necessary, because it's
+ // possible po->mPresContext might never have been set. (e.g., if
+ // PrintingIsEnabled() returns false, ReflowPrintObject bails before setting
+ // mPresContext)
+ if (po->mPresContext && po->mPresContext->IsRootPaginatedDocument()) {
+ nsPageSequenceFrame* seqFrame = po->mPresShell->GetPageSequenceFrame();
+ if (seqFrame) {
+ aNumPages += seqFrame->PrincipalChildList().GetLength();
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------
+//-- Done: Reflow Methods
+//-----------------------------------------------------------------
+
+//-----------------------------------------------------------------
+//-- Section: Printing Methods
+//-----------------------------------------------------------------
+
+//-------------------------------------------------------
+// Called for each DocShell that needs to be printed
+bool nsPrintJob::PrintDocContent(const UniquePtr<nsPrintObject>& aPO,
+ nsresult& aStatus) {
+ NS_ASSERTION(aPO, "Pointer is null!");
+ aStatus = NS_OK;
+
+ if (!aPO->mHasBeenPrinted && aPO->PrintingIsEnabled()) {
+ aStatus = DoPrint(aPO);
+ return true;
+ }
+
+ // If |aPO->mHasBeenPrinted| is true,
+ // the kids frames are already processed in |PrintPage|.
+ // XXX This should be removed. Since bug 1552785 it has no longer been
+ // possible for us to have to print multiple subdocuments consecutively.
+ if (!aPO->mHasBeenPrinted && !aPO->mInvisible) {
+ for (const UniquePtr<nsPrintObject>& po : aPO->mKids) {
+ bool printed = PrintDocContent(po, aStatus);
+ if (printed || NS_FAILED(aStatus)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+// A helper struct to aid with DeleteNonSelectedNodes.
+struct MOZ_STACK_CLASS SelectionRangeState {
+ explicit SelectionRangeState(RefPtr<Selection> aSelection)
+ : mSelection(std::move(aSelection)) {
+ MOZ_ASSERT(mSelection);
+ MOZ_ASSERT(!mSelection->RangeCount());
+ }
+
+ // Selects all the nodes that are _not_ included in a given set of ranges.
+ MOZ_CAN_RUN_SCRIPT void SelectComplementOf(Span<const RefPtr<nsRange>>);
+ // Removes the selected ranges from the document.
+ MOZ_CAN_RUN_SCRIPT void RemoveSelectionFromDocument();
+
+ private:
+ struct Position {
+ nsINode* mNode;
+ uint32_t mOffset;
+ };
+
+ MOZ_CAN_RUN_SCRIPT void SelectRange(nsRange*);
+ MOZ_CAN_RUN_SCRIPT void SelectNodesExcept(const Position& aStart,
+ const Position& aEnd);
+ MOZ_CAN_RUN_SCRIPT void SelectNodesExceptInSubtree(const Position& aStart,
+ const Position& aEnd);
+
+ // A map from subtree root (document or shadow root) to the start position of
+ // the non-selected content (so far).
+ nsDataHashtable<nsPtrHashKey<nsINode>, Position> mPositions;
+
+ // The selection we're adding the ranges to.
+ const RefPtr<Selection> mSelection;
+};
+
+void SelectionRangeState::SelectComplementOf(
+ Span<const RefPtr<nsRange>> aRanges) {
+ for (const auto& range : aRanges) {
+ auto start = Position{range->GetStartContainer(), range->StartOffset()};
+ auto end = Position{range->GetEndContainer(), range->EndOffset()};
+ SelectNodesExcept(start, end);
+ }
+}
+
+void SelectionRangeState::SelectRange(nsRange* aRange) {
+ if (aRange && !aRange->Collapsed()) {
+ mSelection->AddRangeAndSelectFramesAndNotifyListeners(*aRange,
+ IgnoreErrors());
+ }
+}
+
+void SelectionRangeState::SelectNodesExcept(const Position& aStart,
+ const Position& aEnd) {
+ SelectNodesExceptInSubtree(aStart, aEnd);
+ if (auto* shadow = ShadowRoot::FromNode(aStart.mNode->SubtreeRoot())) {
+ auto* host = shadow->Host();
+ SelectNodesExcept(Position{host, 0}, Position{host, host->GetChildCount()});
+ } else {
+ MOZ_ASSERT(aStart.mNode->IsInUncomposedDoc());
+ }
+}
+
+void SelectionRangeState::SelectNodesExceptInSubtree(const Position& aStart,
+ const Position& aEnd) {
+ static constexpr auto kEllipsis = u"\x2026"_ns;
+
+ nsINode* root = aStart.mNode->SubtreeRoot();
+ auto& start = mPositions.LookupForAdd(root).OrInsert([&] {
+ return Position{root, 0};
+ });
+
+ bool ellipsizedStart = false;
+ if (auto* text = Text::FromNode(aStart.mNode)) {
+ if (start.mNode != text && aStart.mOffset &&
+ aStart.mOffset < text->Length()) {
+ text->InsertData(aStart.mOffset, kEllipsis, IgnoreErrors());
+ ellipsizedStart = true;
+ }
+ }
+
+ RefPtr<nsRange> range = nsRange::Create(
+ start.mNode, start.mOffset, aStart.mNode, aStart.mOffset, IgnoreErrors());
+ SelectRange(range);
+
+ start = aEnd;
+
+ // If we added an ellipsis at the start and the end position was relative to
+ // the same node account for it here.
+ if (ellipsizedStart && aStart.mNode == aEnd.mNode) {
+ start.mOffset += kEllipsis.Length();
+ }
+
+ // If the end is mid text then add an ellipsis.
+ if (auto* text = Text::FromNode(start.mNode)) {
+ if (start.mOffset && start.mOffset < text->Length()) {
+ text->InsertData(start.mOffset, kEllipsis, IgnoreErrors());
+ start.mOffset += kEllipsis.Length();
+ }
+ }
+}
+
+void SelectionRangeState::RemoveSelectionFromDocument() {
+ for (auto& entry : mPositions) {
+ const Position& pos = entry.GetData();
+ nsINode* root = entry.GetKey();
+ RefPtr<nsRange> range = nsRange::Create(
+ pos.mNode, pos.mOffset, root, root->GetChildCount(), IgnoreErrors());
+ SelectRange(range);
+ }
+ mSelection->DeleteFromDocument(IgnoreErrors());
+}
+
+/**
+ * Builds the complement set of ranges and adds those to the selection.
+ * Deletes all of the nodes contained in the complement set of ranges
+ * leaving behind only nodes that were originally selected.
+ * Adds ellipses to a selected node's text if text is truncated by a range.
+ * This is used to implement the "Print Selection Only" user interface option.
+ */
+MOZ_CAN_RUN_SCRIPT_BOUNDARY static nsresult DeleteNonSelectedNodes(
+ Document& aDoc) {
+ MOZ_ASSERT(aDoc.IsStaticDocument());
+ const auto* printRanges = static_cast<nsTArray<RefPtr<nsRange>>*>(
+ aDoc.GetProperty(nsGkAtoms::printselectionranges));
+ if (!printRanges) {
+ return NS_OK;
+ }
+
+ PresShell* presShell = aDoc.GetPresShell();
+ NS_ENSURE_STATE(presShell);
+ RefPtr<Selection> selection =
+ presShell->GetCurrentSelection(SelectionType::eNormal);
+ NS_ENSURE_STATE(selection);
+
+ SelectionRangeState state(std::move(selection));
+ state.SelectComplementOf(*printRanges);
+ state.RemoveSelectionFromDocument();
+ return NS_OK;
+}
+
+//-------------------------------------------------------
+nsresult nsPrintJob::DoPrint(const UniquePtr<nsPrintObject>& aPO) {
+ PR_PL(("\n"));
+ PR_PL(("**************************** %s ****************************\n",
+ gFrameTypesStr[aPO->mFrameType]));
+ PR_PL(("****** In DV::DoPrint PO: %p \n", aPO.get()));
+
+ PresShell* poPresShell = aPO->mPresShell;
+ nsPresContext* poPresContext = aPO->mPresContext;
+
+ NS_ASSERTION(poPresContext, "PrintObject has not been reflowed");
+ NS_ASSERTION(poPresContext->Type() != nsPresContext::eContext_PrintPreview,
+ "How did this context end up here?");
+
+ // Guarantee that mPrt and the objects it owns won't be deleted in this method
+ // because it might be cleared if other modules called from here may fire
+ // events, notifying observers and/or listeners.
+ RefPtr<nsPrintData> printData = mPrt;
+
+ if (printData->mPrintProgressParams) {
+ SetURLAndTitleOnProgressParams(aPO, printData->mPrintProgressParams);
+ }
+
+ {
+ // Ask the page sequence frame to print all the pages
+ nsPageSequenceFrame* seqFrame = poPresShell->GetPageSequenceFrame();
+ MOZ_ASSERT(seqFrame, "no page sequence frame");
+
+ // We are done preparing for printing, so we can turn this off
+ printData->mPreparingForPrint = false;
+
+#ifdef EXTENDED_DEBUG_PRINTING
+ nsIFrame* rootFrame = poPresShell->GetRootFrame();
+ if (aPO->PrintingIsEnabled()) {
+ nsAutoCString docStr;
+ nsAutoCString urlStr;
+ GetDocTitleAndURL(aPO, docStr, urlStr);
+ DumpLayoutData(docStr.get(), urlStr.get(), poPresContext,
+ printData->mPrintDC, rootFrame, aPO->mDocShell, nullptr);
+ }
+#endif
+
+ if (!printData->mPrintSettings) {
+ // not sure what to do here!
+ SetIsPrinting(false);
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoString docTitleStr;
+ nsAutoString docURLStr;
+ GetDisplayTitleAndURL(*aPO->mDocument, mPrt->mPrintSettings,
+ DocTitleDefault::eFallback, docTitleStr, docURLStr);
+
+ if (!seqFrame) {
+ SetIsPrinting(false);
+ return NS_ERROR_FAILURE;
+ }
+
+ // For telemetry, get paper size being used; convert the dimensions to
+ // points and ensure they reflect portrait orientation.
+ nsIPrintSettings* settings = printData->mPrintSettings;
+ double paperWidth, paperHeight;
+ settings->GetPaperWidth(&paperWidth);
+ settings->GetPaperHeight(&paperHeight);
+ int16_t sizeUnit;
+ settings->GetPaperSizeUnit(&sizeUnit);
+ switch (sizeUnit) {
+ case nsIPrintSettings::kPaperSizeInches:
+ paperWidth *= 72.0;
+ paperHeight *= 72.0;
+ break;
+ case nsIPrintSettings::kPaperSizeMillimeters:
+ paperWidth *= 72.0 / 25.4;
+ paperHeight *= 72.0 / 25.4;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("unknown paper size unit");
+ break;
+ }
+ if (paperWidth > paperHeight) {
+ std::swap(paperWidth, paperHeight);
+ }
+ // Use the paper size to build a Telemetry Scalar key.
+ nsString key;
+ key.AppendInt(int32_t(NS_round(paperWidth)));
+ key.Append(u"x");
+ key.AppendInt(int32_t(NS_round(paperHeight)));
+ Telemetry::ScalarAdd(Telemetry::ScalarID::PRINTING_PAPER_SIZE, key, 1);
+
+ mPageSeqFrame = seqFrame;
+ seqFrame->StartPrint(poPresContext, settings, docTitleStr, docURLStr);
+
+ // Schedule Page to Print
+ PR_PL(("Scheduling Print of PO: %p (%s) \n", aPO.get(),
+ gFrameTypesStr[aPO->mFrameType]));
+ StartPagePrintTimer(aPO);
+ }
+
+ return NS_OK;
+}
+
+//---------------------------------------------------------------------
+void nsPrintJob::SetURLAndTitleOnProgressParams(
+ const UniquePtr<nsPrintObject>& aPO, nsIPrintProgressParams* aParams) {
+ NS_ASSERTION(aPO, "Must have valid nsPrintObject");
+ NS_ASSERTION(aParams, "Must have valid nsIPrintProgressParams");
+
+ if (!aPO || !aPO->mDocShell || !aParams) {
+ return;
+ }
+ const uint32_t kTitleLength = 64;
+
+ nsAutoString docTitleStr;
+ nsAutoString docURLStr;
+ GetDisplayTitleAndURL(*aPO->mDocument, mPrt->mPrintSettings,
+ DocTitleDefault::eDocURLElseFallback, docTitleStr,
+ docURLStr);
+
+ // Make sure the Titles & URLS don't get too long for the progress dialog
+ EllipseLongString(docTitleStr, kTitleLength, false);
+ EllipseLongString(docURLStr, kTitleLength, true);
+
+ aParams->SetDocTitle(docTitleStr);
+ aParams->SetDocURL(docURLStr);
+}
+
+//---------------------------------------------------------------------
+void nsPrintJob::EllipseLongString(nsAString& aStr, const uint32_t aLen,
+ bool aDoFront) {
+ // Make sure the URLS don't get too long for the progress dialog
+ if (aLen >= 3 && aStr.Length() > aLen) {
+ if (aDoFront) {
+ nsAutoString newStr;
+ newStr.AppendLiteral("...");
+ newStr += Substring(aStr, aStr.Length() - (aLen - 3), aLen - 3);
+ aStr = newStr;
+ } else {
+ aStr.SetLength(aLen - 3);
+ aStr.AppendLiteral("...");
+ }
+ }
+}
+
+//-------------------------------------------------------
+bool nsPrintJob::PrePrintSheet() {
+ NS_ASSERTION(mPageSeqFrame.IsAlive(), "mPageSeqFrame is not alive!");
+ NS_ASSERTION(mPrt, "mPrt is null!");
+
+ // Although these should NEVER be nullptr
+ // This is added insurance, to make sure we don't crash in optimized builds
+ if (!mPrt || !mPageSeqFrame.IsAlive()) {
+ return true; // means we are done preparing the sheet.
+ }
+
+ // Guarantee that mPrt won't be deleted during a call of
+ // FirePrintingErrorEvent().
+ RefPtr<nsPrintData> printData = mPrt;
+
+ // Check setting to see if someone request it be cancelled
+ bool isCancelled = false;
+ printData->mPrintSettings->GetIsCancelled(&isCancelled);
+ if (isCancelled) return true;
+
+ // Ask mPageSeqFrame if the sheet is ready to be printed.
+ // If the sheet doesn't get printed at all, the |done| will be |true|.
+ bool done = false;
+ nsPageSequenceFrame* pageSeqFrame = do_QueryFrame(mPageSeqFrame.GetFrame());
+ nsresult rv = pageSeqFrame->PrePrintNextSheet(mPagePrintTimer, &done);
+ if (NS_FAILED(rv)) {
+ // ??? ::PrintSheet doesn't set |printData->mIsAborted = true| if
+ // rv != NS_ERROR_ABORT, but I don't really understand why this should be
+ // the right thing to do? Shouldn't |printData->mIsAborted| set to true
+ // all the time if something went wrong?
+ if (rv != NS_ERROR_ABORT) {
+ FirePrintingErrorEvent(rv);
+ printData->mIsAborted = true;
+ }
+ done = true;
+ }
+ return done;
+}
+
+bool nsPrintJob::PrintSheet(nsPrintObject* aPO, bool& aInRange) {
+ NS_ASSERTION(aPO, "aPO is null!");
+ NS_ASSERTION(mPageSeqFrame.IsAlive(), "mPageSeqFrame is not alive!");
+ NS_ASSERTION(mPrt, "mPrt is null!");
+
+ // XXXdholbert Nowadays, this function doesn't need to concern itself with
+ // page ranges -- page-range handling is now handled when we reflow our
+ // PrintedSheetFrames, and all PrintedSheetFrames are "in-range" and should
+ // be printed. So this outparam is unconditionally true. Bug 1669815 is filed
+ // on removing it entirely.
+ aInRange = true;
+
+ // Although these should NEVER be nullptr
+ // This is added insurance, to make sure we don't crash in optimized builds
+ if (!mPrt || !aPO || !mPageSeqFrame.IsAlive()) {
+ FirePrintingErrorEvent(NS_ERROR_FAILURE);
+ return true; // means we are done printing
+ }
+
+ // Guarantee that mPrt won't be deleted during a call of
+ // nsPrintData::DoOnProgressChange() which runs some listeners,
+ // which may clear (& might otherwise destroy).
+ RefPtr<nsPrintData> printData = mPrt;
+
+ PR_PL(("-----------------------------------\n"));
+ PR_PL(("------ In DV::PrintSheet PO: %p (%s)\n", aPO,
+ gFrameTypesStr[aPO->mFrameType]));
+
+ // Check setting to see if someone request it be cancelled
+ bool isCancelled = false;
+ printData->mPrintSettings->GetIsCancelled(&isCancelled);
+ if (isCancelled || printData->mIsAborted) {
+ return true;
+ }
+
+ nsPageSequenceFrame* pageSeqFrame = do_QueryFrame(mPageSeqFrame.GetFrame());
+ const uint32_t sheetIdx = pageSeqFrame->GetCurrentSheetIdx();
+ const uint32_t numSheets = pageSeqFrame->PrincipalChildList().GetLength();
+
+ PR_PL(("****** Printing sheet index %d of %d sheets(s)\n", sheetIdx,
+ numSheets));
+
+ MOZ_ASSERT(numSheets > 0, "print operations must have at least 1 sheet");
+ MOZ_ASSERT(sheetIdx < numSheets,
+ "sheetIdx shouldn't be allowed to go out of bounds");
+ printData->DoOnProgressChange(sheetIdx, numSheets, false, 0);
+ if (NS_WARN_IF(mPrt != printData)) {
+ // If current printing is canceled or new print is started, let's return
+ // true to notify the caller of current printing is done.
+ return true;
+ }
+
+ if (XRE_IsParentProcess() && !printData->mPrintDC->IsSyncPagePrinting()) {
+ mPagePrintTimer->WaitForRemotePrint();
+ }
+
+ // Print the sheet
+ // if a print job was cancelled externally, an EndPage or BeginPage may
+ // fail and the failure is passed back here.
+ // Returning true means we are done printing.
+ //
+ // When rv == NS_ERROR_ABORT, it means we want out of the
+ // print job without displaying any error messages
+ nsresult rv = pageSeqFrame->PrintNextSheet();
+ if (NS_FAILED(rv)) {
+ if (rv != NS_ERROR_ABORT) {
+ FirePrintingErrorEvent(rv);
+ printData->mIsAborted = true;
+ }
+ return true;
+ }
+
+ pageSeqFrame->DoPageEnd();
+
+ // If we just printed the final sheet (the one with index "numSheets-1"),
+ // then we're done!
+ return (sheetIdx == numSheets - 1);
+}
+
+void nsPrintJob::PageDone(nsresult aResult) {
+ MOZ_ASSERT(mIsDoingPrinting);
+
+ // mPagePrintTimer might be released during RemotePrintFinished, keep a
+ // reference here to make sure it lives long enough.
+ RefPtr<nsPagePrintTimer> timer = mPagePrintTimer;
+ timer->RemotePrintFinished();
+}
+
+//-----------------------------------------------------------------
+//-- Done: Printing Methods
+//-----------------------------------------------------------------
+
+//-----------------------------------------------------------------
+//-- Section: Misc Support Methods
+//-----------------------------------------------------------------
+
+//---------------------------------------------------------------------
+void nsPrintJob::SetIsPrinting(bool aIsPrinting) {
+ mIsDoingPrinting = aIsPrinting;
+ if (aIsPrinting) {
+ mHasEverPrinted = true;
+ }
+ if (mPrt && aIsPrinting) {
+ mPrt->mPreparingForPrint = true;
+ }
+}
+
+//---------------------------------------------------------------------
+void nsPrintJob::SetIsPrintPreview(bool aIsPrintPreview) {
+ mCreatedForPrintPreview = aIsPrintPreview;
+
+ if (mDocViewerPrint) {
+ mDocViewerPrint->SetIsPrintPreview(aIsPrintPreview);
+ }
+}
+
+Document* nsPrintJob::FindFocusedDocument(Document* aDoc) const {
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ NS_ENSURE_TRUE(fm, nullptr);
+
+ nsPIDOMWindowOuter* window = aDoc->GetOriginalDocument()->GetWindow();
+ NS_ENSURE_TRUE(window, nullptr);
+
+ nsCOMPtr<nsPIDOMWindowOuter> rootWindow = window->GetPrivateRoot();
+ NS_ENSURE_TRUE(rootWindow, nullptr);
+
+ nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+ nsFocusManager::GetFocusedDescendant(rootWindow,
+ nsFocusManager::eIncludeAllDescendants,
+ getter_AddRefs(focusedWindow));
+ NS_ENSURE_TRUE(focusedWindow, nullptr);
+
+ if (IsWindowsInOurSubTree(focusedWindow)) {
+ return focusedWindow->GetDoc();
+ }
+
+ return nullptr;
+}
+
+//---------------------------------------------------------------------
+bool nsPrintJob::IsWindowsInOurSubTree(nsPIDOMWindowOuter* window) const {
+ if (window) {
+ nsCOMPtr<nsIDocShell> ourDocShell(do_QueryReferent(mDocShell));
+ if (ourDocShell) {
+ BrowsingContext* ourBC = ourDocShell->GetBrowsingContext();
+ BrowsingContext* bc = window->GetBrowsingContext();
+ while (bc) {
+ if (bc == ourBC) {
+ return true;
+ }
+ bc = bc->GetParent();
+ }
+ }
+ }
+ return false;
+}
+
+//-------------------------------------------------------
+bool nsPrintJob::DonePrintingSheets(nsPrintObject* aPO, nsresult aResult) {
+ // NS_ASSERTION(aPO, "Pointer is null!");
+ PR_PL(("****** In DV::DonePrintingSheets PO: %p (%s)\n", aPO,
+ aPO ? gFrameTypesStr[aPO->mFrameType] : ""));
+
+ // If there is a pageSeqFrame, make sure there are no more printCanvas active
+ // that might call |Notify| on the pagePrintTimer after things are cleaned up
+ // and printing was marked as being done.
+ if (mPageSeqFrame.IsAlive()) {
+ nsPageSequenceFrame* pageSeqFrame = do_QueryFrame(mPageSeqFrame.GetFrame());
+ pageSeqFrame->ResetPrintCanvasList();
+ }
+
+ // Guarantee that mPrt and mPrt->mPrintObject won't be deleted during a
+ // call of PrintDocContent() and FirePrintCompletionEvent().
+ RefPtr<nsPrintData> printData = mPrt;
+
+ if (aPO && !printData->mIsAborted) {
+ aPO->mHasBeenPrinted = true;
+ nsresult rv;
+ bool didPrint = PrintDocContent(printData->mPrintObject, rv);
+ if (NS_SUCCEEDED(rv) && didPrint) {
+ PR_PL(
+ ("****** In DV::DonePrintingSheets PO: %p (%s) didPrint:%s (Not Done "
+ "Printing)\n",
+ aPO, gFrameTypesStr[aPO->mFrameType], PRT_YESNO(didPrint)));
+ return false;
+ }
+ }
+
+ if (XRE_IsParentProcess() && printData->mPrintDC &&
+ !printData->mPrintDC->IsSyncPagePrinting()) {
+ printData->mPrintDC->UnregisterPageDoneCallback();
+ }
+
+ if (NS_SUCCEEDED(aResult)) {
+ FirePrintCompletionEvent();
+ // XXX mPrt may be cleared or replaced with new instance here.
+ // However, the following methods will clean up with new mPrt or will
+ // do nothing due to no proper nsPrintData instance.
+ }
+
+ SetIsPrinting(false);
+
+ // Release reference to mPagePrintTimer; the timer object destroys itself
+ // after this returns true
+ DisconnectPagePrintTimer();
+
+ return true;
+}
+
+//-------------------------------------------------------
+nsresult nsPrintJob::EnablePOsForPrinting() {
+ // Guarantee that mPrt and the objects it owns won't be deleted.
+ RefPtr<nsPrintData> printData = mPrt;
+
+ // NOTE: All POs have been "turned off" for printing
+ // this is where we decided which POs get printed.
+
+ if (!printData || !printData->mPrintSettings) {
+ return NS_ERROR_FAILURE;
+ }
+
+ PR_PL(("\n"));
+ PR_PL(("********* nsPrintJob::EnablePOsForPrinting *********\n"));
+
+ if (!printData->mPrintSettings->GetPrintSelectionOnly()) {
+ printData->mPrintObject->EnablePrinting(true);
+ return NS_OK;
+ }
+
+ // This means we are either printing a selected iframe or
+ // we are printing the current selection.
+ NS_ENSURE_STATE(!mDisallowSelectionPrint && printData->mSelectionRoot);
+
+ // If mSelectionRoot is a selected iframe without a selection, then just
+ // enable normally from that point.
+ if (printData->mSelectionRoot->mFrameType == eIFrame &&
+ !printData->mSelectionRoot->HasSelection()) {
+ printData->mSelectionRoot->EnablePrinting(true);
+ } else {
+ // Otherwise, only enable nsPrintObjects that have a selection.
+ printData->mSelectionRoot->EnablePrintingSelectionOnly();
+ }
+ return NS_OK;
+}
+
+//-------------------------------------------------------
+// Return the nsPrintObject with that is XMost (The widest frameset frame) AND
+// contains the XMost (widest) layout frame
+nsPrintObject* nsPrintJob::FindSmallestSTF() {
+ float smallestRatio = 1.0f;
+ nsPrintObject* smallestPO = nullptr;
+
+ for (uint32_t i = 0; i < mPrt->mPrintDocList.Length(); i++) {
+ nsPrintObject* po = mPrt->mPrintDocList.ElementAt(i);
+ NS_ASSERTION(po, "nsPrintObject can't be null!");
+ if (po->mFrameType != eFrameSet && po->mFrameType != eIFrame) {
+ if (po->mShrinkRatio < smallestRatio) {
+ smallestRatio = po->mShrinkRatio;
+ smallestPO = po;
+ }
+ }
+ }
+
+#ifdef EXTENDED_DEBUG_PRINTING
+ if (smallestPO)
+ printf("*PO: %p Type: %d %10.3f\n", smallestPO, smallestPO->mFrameType,
+ smallestPO->mShrinkRatio);
+#endif
+ return smallestPO;
+}
+
+//-----------------------------------------------------------------
+//-- Done: Misc Support Methods
+//-----------------------------------------------------------------
+
+//-----------------------------------------------------------------
+//-- Section: Finishing up or Cleaning up
+//-----------------------------------------------------------------
+
+//-----------------------------------------------------------------
+void nsPrintJob::CloseProgressDialog(
+ nsIWebProgressListener* aWebProgressListener) {
+ if (aWebProgressListener) {
+ aWebProgressListener->OnStateChange(
+ nullptr, nullptr,
+ nsIWebProgressListener::STATE_STOP |
+ nsIWebProgressListener::STATE_IS_DOCUMENT,
+ NS_OK);
+ }
+}
+
+//-----------------------------------------------------------------
+nsresult nsPrintJob::FinishPrintPreview() {
+ nsresult rv = NS_OK;
+
+#ifdef NS_PRINT_PREVIEW
+
+ // If mPrt is null we've already finished with print preview. If mPrt is not
+ // null but mIsCreatingPrintPreview is false FinishPrintPreview must have
+ // already failed due to DocumentReadyForPrinting failing.
+ if (!mPrt || !mIsCreatingPrintPreview) {
+ return rv;
+ }
+
+ rv = DocumentReadyForPrinting();
+
+ // Note that this method may be called while the instance is being
+ // initialized. Some methods which initialize the instance (e.g.,
+ // DoCommonPrint) may need to stop initializing and return error if
+ // this is called. Therefore it's important to set mIsCreatingPrintPreview
+ // state to false here. If you need to stop setting that here, you need to
+ // keep them being able to check whether the owner stopped using this
+ // instance.
+ mIsCreatingPrintPreview = false;
+
+ // mPrt may be cleared during a call of nsPrintData::OnEndPrinting()
+ // because that method invokes some arbitrary listeners.
+ RefPtr<nsPrintData> printData = mPrt;
+ if (NS_FAILED(rv)) {
+ /* cleanup done, let's fire-up an error dialog to notify the user
+ * what went wrong...
+ */
+ printData->OnEndPrinting();
+
+ return rv;
+ }
+
+ if (mPrintPreviewCallback) {
+ const bool hasSelection =
+ !mDisallowSelectionPrint && printData->mSelectionRoot;
+ mPrintPreviewCallback(PrintPreviewResultInfo(
+ GetPrintPreviewNumSheets(), GetRawNumPages(), GetIsEmpty(),
+ hasSelection, hasSelection && printData->mPrintObject->HasSelection()));
+ mPrintPreviewCallback = nullptr;
+ }
+
+ // At this point we are done preparing everything
+ // before it is to be created
+
+ printData->OnEndPrinting();
+ // XXX If mPrt becomes nullptr or different instance here, what should we
+ // do?
+
+ // PrintPreview was built using the mPrt (code reuse)
+ // then we assign it over
+ mPrtPreview = std::move(mPrt);
+
+#endif // NS_PRINT_PREVIEW
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------
+//-- Done: Finishing up or Cleaning up
+//-----------------------------------------------------------------
+
+/*=============== Timer Related Code ======================*/
+nsresult nsPrintJob::StartPagePrintTimer(const UniquePtr<nsPrintObject>& aPO) {
+ if (!mPagePrintTimer) {
+ // Get the delay time in between the printing of each page
+ // this gives the user more time to press cancel
+ int32_t printPageDelay = 50;
+ mPrt->mPrintSettings->GetPrintPageDelay(&printPageDelay);
+
+ nsCOMPtr<nsIContentViewer> cv = do_QueryInterface(mDocViewerPrint);
+ NS_ENSURE_TRUE(cv, NS_ERROR_FAILURE);
+ nsCOMPtr<Document> doc = cv->GetDocument();
+ NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+
+ mPagePrintTimer =
+ new nsPagePrintTimer(this, mDocViewerPrint, doc, printPageDelay);
+
+ nsCOMPtr<nsIPrintSession> printSession;
+ nsresult rv =
+ mPrt->mPrintSettings->GetPrintSession(getter_AddRefs(printSession));
+ if (NS_SUCCEEDED(rv) && printSession) {
+ RefPtr<layout::RemotePrintJobChild> remotePrintJob =
+ printSession->GetRemotePrintJob();
+ if (remotePrintJob) {
+ remotePrintJob->SetPagePrintTimer(mPagePrintTimer);
+ remotePrintJob->SetPrintJob(this);
+ }
+ }
+ }
+
+ return mPagePrintTimer->Start(aPO.get());
+}
+
+/*=============== nsIObserver Interface ======================*/
+MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP nsPrintJob::Observe(
+ nsISupports* aSubject, const char* aTopic, const char16_t* aData) {
+ // We expect to be called by nsIPrintingPromptService after we were passed to
+ // it by via the nsIPrintingPromptService::ShowPrintProgressDialog call in
+ // ShowPrintProgress. Once it has opened the progress dialog it calls this
+ // method, passing null as the topic.
+
+ if (aTopic) {
+ return NS_OK;
+ }
+
+ nsresult rv = InitPrintDocConstruction(true);
+ if (!mIsDoingPrinting && mPrtPreview) {
+ RefPtr<nsPrintData> printDataOfPrintPreview = mPrtPreview;
+ printDataOfPrintPreview->OnEndPrinting();
+ }
+
+ return rv;
+}
+
+//---------------------------------------------------------------
+//-- PLEvent Notification
+//---------------------------------------------------------------
+class nsPrintCompletionEvent : public Runnable {
+ public:
+ explicit nsPrintCompletionEvent(nsIDocumentViewerPrint* docViewerPrint)
+ : mozilla::Runnable("nsPrintCompletionEvent"),
+ mDocViewerPrint(docViewerPrint) {
+ NS_ASSERTION(mDocViewerPrint, "mDocViewerPrint is null.");
+ }
+
+ NS_IMETHOD Run() override {
+ if (mDocViewerPrint) mDocViewerPrint->OnDonePrinting();
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<nsIDocumentViewerPrint> mDocViewerPrint;
+};
+
+//-----------------------------------------------------------
+void nsPrintJob::FirePrintCompletionEvent() {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIRunnable> event = new nsPrintCompletionEvent(mDocViewerPrint);
+ nsCOMPtr<nsIContentViewer> cv = do_QueryInterface(mDocViewerPrint);
+ NS_ENSURE_TRUE_VOID(cv);
+ nsCOMPtr<Document> doc = cv->GetDocument();
+ NS_ENSURE_TRUE_VOID(doc);
+
+ NS_ENSURE_SUCCESS_VOID(doc->Dispatch(TaskCategory::Other, event.forget()));
+}
+
+void nsPrintJob::DisconnectPagePrintTimer() {
+ if (mPagePrintTimer) {
+ mPagePrintTimer->Disconnect();
+ mPagePrintTimer = nullptr;
+ }
+}
+
+//---------------------------------------------------------------
+//---------------------------------------------------------------
+//-- Debug helper routines
+//---------------------------------------------------------------
+//---------------------------------------------------------------
+#if defined(XP_WIN) && defined(EXTENDED_DEBUG_PRINTING)
+# include "windows.h"
+# include "process.h"
+# include "direct.h"
+
+# define MY_FINDFIRST(a, b) FindFirstFile(a, b)
+# define MY_FINDNEXT(a, b) FindNextFile(a, b)
+# define ISDIR(a) (a.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+# define MY_FINDCLOSE(a) FindClose(a)
+# define MY_FILENAME(a) a.cFileName
+# define MY_FILESIZE(a) (a.nFileSizeHigh * MAXDWORD) + a.nFileSizeLow
+
+int RemoveFilesInDir(const char* aDir) {
+ WIN32_FIND_DATA data_ptr;
+ HANDLE find_handle;
+
+ char path[MAX_PATH];
+
+ strcpy(path, aDir);
+
+ // Append slash to the end of the directory names if not there
+ if (path[strlen(path) - 1] != '\\') strcat(path, "\\");
+
+ char findPath[MAX_PATH];
+ strcpy(findPath, path);
+ strcat(findPath, "*.*");
+
+ find_handle = MY_FINDFIRST(findPath, &data_ptr);
+
+ if (find_handle != INVALID_HANDLE_VALUE) {
+ do {
+ if (ISDIR(data_ptr) && (stricmp(MY_FILENAME(data_ptr), ".")) &&
+ (stricmp(MY_FILENAME(data_ptr), ".."))) {
+ // skip
+ } else if (!ISDIR(data_ptr)) {
+ if (!strncmp(MY_FILENAME(data_ptr), "print_dump", 10)) {
+ char fileName[MAX_PATH];
+ strcpy(fileName, aDir);
+ strcat(fileName, "\\");
+ strcat(fileName, MY_FILENAME(data_ptr));
+ printf("Removing %s\n", fileName);
+ remove(fileName);
+ }
+ }
+ } while (MY_FINDNEXT(find_handle, &data_ptr));
+ MY_FINDCLOSE(find_handle);
+ }
+ return TRUE;
+}
+#endif
+
+#ifdef EXTENDED_DEBUG_PRINTING
+
+/** ---------------------------------------------------
+ * Dumps Frames for Printing
+ */
+static void RootFrameList(nsPresContext* aPresContext, FILE* out,
+ const char* aPrefix) {
+ if (!aPresContext || !out) return;
+
+ if (PresShell* presShell = aPresContext->GetPresShell()) {
+ nsIFrame* frame = presShell->GetRootFrame();
+ if (frame) {
+ frame->List(out, aPrefix);
+ }
+ }
+}
+
+/** ---------------------------------------------------
+ * Dumps Frames for Printing
+ */
+static void DumpFrames(FILE* out, nsPresContext* aPresContext,
+ gfxContext* aRendContext, nsIFrame* aFrame,
+ int32_t aLevel) {
+ NS_ASSERTION(out, "Pointer is null!");
+ NS_ASSERTION(aPresContext, "Pointer is null!");
+ NS_ASSERTION(aRendContext, "Pointer is null!");
+ NS_ASSERTION(aFrame, "Pointer is null!");
+
+ nsIFrame* child = aFrame->PrincipalChildList().FirstChild();
+ while (child != nullptr) {
+ for (int32_t i = 0; i < aLevel; i++) {
+ fprintf(out, " ");
+ }
+ nsAutoString tmp;
+ child->GetFrameName(tmp);
+ fputs(NS_LossyConvertUTF16toASCII(tmp).get(), out);
+ bool isSelected;
+ if (child->IsVisibleForPainting()) {
+ fprintf(out, " %p %s", child, isSelected ? "VIS" : "UVS");
+ nsRect rect = child->GetRect();
+ fprintf(out, "[%d,%d,%d,%d] ", rect.x, rect.y, rect.width, rect.height);
+ fprintf(out, "v: %p ", (void*)child->GetView());
+ fprintf(out, "\n");
+ DumpFrames(out, aPresContext, aRendContext, child, aLevel + 1);
+ child = child->GetNextSibling();
+ }
+ }
+}
+
+/** ---------------------------------------------------
+ * Dumps the Views from the DocShell
+ */
+static void DumpViews(nsIDocShell* aDocShell, FILE* out) {
+ NS_ASSERTION(aDocShell, "Pointer is null!");
+ NS_ASSERTION(out, "Pointer is null!");
+
+ if (nullptr != aDocShell) {
+ fprintf(out, "docshell=%p \n", aDocShell);
+ if (PresShell* presShell = aDocShell->GetPresShell()) {
+ nsViewManager* vm = presShell->GetViewManager();
+ if (vm) {
+ nsView* root = vm->GetRootView();
+ if (root) {
+ root->List(out);
+ }
+ }
+ } else {
+ fputs("null pres shell\n", out);
+ }
+
+ // dump the views of the sub documents
+ int32_t i, n;
+ BrowsingContext* bc = nsDocShell::Cast(aDocShell)->GetBrowsingContext();
+ for (auto& child : bc->Children()) {
+ if (auto childDS = child->GetDocShell()) {
+ DumpViews(childAsShell, out);
+ }
+ }
+ }
+}
+
+/** ---------------------------------------------------
+ * Dumps the Views and Frames
+ */
+void DumpLayoutData(const char* aTitleStr, const char* aURLStr,
+ nsPresContext* aPresContext, nsDeviceContext* aDC,
+ nsIFrame* aRootFrame, nsIDocShell* aDocShell,
+ FILE* aFD = nullptr) {
+ if (!MOZ_LOG_TEST(gPrintingLog, DUMP_LAYOUT_LEVEL)) {
+ return;
+ }
+
+ if (aPresContext == nullptr || aDC == nullptr) {
+ return;
+ }
+
+# ifdef NS_PRINT_PREVIEW
+ if (aPresContext->Type() == nsPresContext::eContext_PrintPreview) {
+ return;
+ }
+# endif
+
+ NS_ASSERTION(aRootFrame, "Pointer is null!");
+ NS_ASSERTION(aDocShell, "Pointer is null!");
+
+ // Dump all the frames and view to a a file
+ char filename[256];
+ sprintf(filename, "print_dump_layout_%d.txt", gDumpLOFileNameCnt++);
+ FILE* fd = aFD ? aFD : fopen(filename, "w");
+ if (fd) {
+ fprintf(fd, "Title: %s\n", aTitleStr ? aTitleStr : "");
+ fprintf(fd, "URL: %s\n", aURLStr ? aURLStr : "");
+ fprintf(fd, "--------------- Frames ----------------\n");
+ fprintf(fd, "--------------- Frames ----------------\n");
+ // RefPtr<gfxContext> renderingContext =
+ // aDC->CreateRenderingContext();
+ RootFrameList(aPresContext, fd, "");
+ // DumpFrames(fd, aPresContext, renderingContext, aRootFrame, 0);
+ fprintf(fd, "---------------------------------------\n\n");
+ fprintf(fd, "--------------- Views From Root Frame----------------\n");
+ nsView* v = aRootFrame->GetView();
+ if (v) {
+ v->List(fd);
+ } else {
+ printf("View is null!\n");
+ }
+ if (aDocShell) {
+ fprintf(fd, "--------------- All Views ----------------\n");
+ DumpViews(aDocShell, fd);
+ fprintf(fd, "---------------------------------------\n\n");
+ }
+ if (aFD == nullptr) {
+ fclose(fd);
+ }
+ }
+}
+
+//-------------------------------------------------------------
+static void DumpPrintObjectsList(const nsTArray<nsPrintObject*>& aDocList) {
+ if (!MOZ_LOG_TEST(gPrintingLog, DUMP_LAYOUT_LEVEL)) {
+ return;
+ }
+
+ const char types[][3] = {"DC", "FR", "IF", "FS"};
+ PR_PL(("Doc List\n***************************************************\n"));
+ PR_PL(
+ ("T P A H PO DocShell Seq Page Root Page# "
+ "Rect\n"));
+ for (nsPrintObject* po : aDocList) {
+ NS_ASSERTION(po, "nsPrintObject can't be null!");
+ nsIFrame* rootFrame = nullptr;
+ if (po->mPresShell) {
+ rootFrame = po->mPresShell->GetRootFrame();
+ while (rootFrame != nullptr) {
+ nsPageSequenceFrame* sqf = do_QueryFrame(rootFrame);
+ if (sqf) {
+ break;
+ }
+ rootFrame = rootFrame->PrincipalChildList().FirstChild();
+ }
+ }
+
+ PR_PL(("%s %d %d %p %p %p\n", types[po->mFrameType],
+ po->PrintingIsEnabled(), po->mHasBeenPrinted, po,
+ po->mDocShell.get(), rootFrame));
+ }
+}
+
+//-------------------------------------------------------------
+static void DumpPrintObjectsTree(nsPrintObject* aPO, int aLevel, FILE* aFD) {
+ if (!MOZ_LOG_TEST(gPrintingLog, DUMP_LAYOUT_LEVEL)) {
+ return;
+ }
+
+ NS_ASSERTION(aPO, "Pointer is null!");
+
+ FILE* fd = aFD ? aFD : stdout;
+ const char types[][3] = {"DC", "FR", "IF", "FS"};
+ if (aLevel == 0) {
+ fprintf(fd,
+ "DocTree\n***************************************************\n");
+ fprintf(fd, "T PO DocShell Seq Page Page# Rect\n");
+ }
+ for (const auto& po : aPO->mKids) {
+ NS_ASSERTION(po, "nsPrintObject can't be null!");
+ for (int32_t k = 0; k < aLevel; k++) fprintf(fd, " ");
+ fprintf(fd, "%s %p %p\n", types[po->mFrameType], po.get(),
+ po->mDocShell.get());
+ }
+}
+
+//-------------------------------------------------------------
+static void GetDocTitleAndURL(const UniquePtr<nsPrintObject>& aPO,
+ nsACString& aDocStr, nsACString& aURLStr) {
+ nsAutoString docTitleStr;
+ nsAutoString docURLStr;
+ GetDocumentTitleAndURL(aPO->mDocument, docTitleStr, docURLStr);
+ CopyUTF16toUTF8(docTitleStr, aDocStr);
+ CopyUTF16toUTF8(docURLStr, aURLStr);
+}
+
+//-------------------------------------------------------------
+static void DumpPrintObjectsTreeLayout(const UniquePtr<nsPrintObject>& aPO,
+ nsDeviceContext* aDC, int aLevel,
+ FILE* aFD) {
+ if (!MOZ_LOG_TEST(gPrintingLog, DUMP_LAYOUT_LEVEL)) {
+ return;
+ }
+
+ NS_ASSERTION(aPO, "Pointer is null!");
+ NS_ASSERTION(aDC, "Pointer is null!");
+
+ const char types[][3] = {"DC", "FR", "IF", "FS"};
+ FILE* fd = nullptr;
+ if (aLevel == 0) {
+ fd = fopen("tree_layout.txt", "w");
+ fprintf(fd,
+ "DocTree\n***************************************************\n");
+ fprintf(fd, "***************************************************\n");
+ fprintf(fd, "T PO DocShell Seq Page Page# Rect\n");
+ } else {
+ fd = aFD;
+ }
+ if (fd) {
+ nsIFrame* rootFrame = nullptr;
+ if (aPO->mPresShell) {
+ rootFrame = aPO->mPresShell->GetRootFrame();
+ }
+ for (int32_t k = 0; k < aLevel; k++) fprintf(fd, " ");
+ fprintf(fd, "%s %p %p\n", types[aPO->mFrameType], aPO.get(),
+ aPO->mDocShell.get());
+ if (aPO->PrintingIsEnabled()) {
+ nsAutoCString docStr;
+ nsAutoCString urlStr;
+ GetDocTitleAndURL(aPO, docStr, urlStr);
+ DumpLayoutData(docStr.get(), urlStr.get(), aPO->mPresContext, aDC,
+ rootFrame, aPO->mDocShell, fd);
+ }
+ fprintf(fd, "<***************************************************>\n");
+
+ for (const auto& po : aPO->mKids) {
+ NS_ASSERTION(po, "nsPrintObject can't be null!");
+ DumpPrintObjectsTreeLayout(po, aDC, aLevel + 1, fd);
+ }
+ }
+ if (aLevel == 0 && fd) {
+ fclose(fd);
+ }
+}
+
+//-------------------------------------------------------------
+static void DumpPrintObjectsListStart(
+ const char* aStr, const nsTArray<nsPrintObject*>& aDocList) {
+ if (!MOZ_LOG_TEST(gPrintingLog, DUMP_LAYOUT_LEVEL)) {
+ return;
+ }
+
+ NS_ASSERTION(aStr, "Pointer is null!");
+
+ PR_PL(("%s\n", aStr));
+ DumpPrintObjectsList(aDocList);
+}
+
+#endif
+
+//---------------------------------------------------------------
+//---------------------------------------------------------------
+//-- End of debug helper routines
+//---------------------------------------------------------------
diff --git a/layout/printing/nsPrintJob.h b/layout/printing/nsPrintJob.h
new file mode 100644
index 0000000000..3eb6f5e57f
--- /dev/null
+++ b/layout/printing/nsPrintJob.h
@@ -0,0 +1,308 @@
+/* -*- 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/. */
+#ifndef nsPrintJob_h
+#define nsPrintJob_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+
+#include "nsCOMPtr.h"
+
+#include "nsHashKeys.h"
+#include "nsIFrame.h" // For WeakFrame
+#include "nsSize.h"
+#include "nsTHashtable.h"
+#include "nsWeakReference.h"
+
+// Interfaces
+#include "nsIObserver.h"
+#include "nsIWebProgress.h"
+#include "nsIWebProgressListener.h"
+
+// Classes
+class nsIFrame;
+class nsIPrintProgressParams;
+class nsIPrintSettings;
+class nsPrintData;
+class nsPagePrintTimer;
+class nsIDocShell;
+class nsIDocumentViewerPrint;
+class nsIFrame;
+class nsPrintObject;
+class nsIDocShell;
+class nsPageSequenceFrame;
+class nsPIDOMWindowOuter;
+class nsView;
+
+namespace mozilla {
+class PresShell;
+namespace dom {
+class Document;
+class PrintPreviewResultInfo;
+} // namespace dom
+} // namespace mozilla
+
+/**
+ * A print job may be instantiated either for printing to an actual physical
+ * printer, or for creating a print preview.
+ */
+class nsPrintJob final : public nsIObserver,
+ public nsIWebProgressListener,
+ public nsSupportsWeakReference {
+ using Document = mozilla::dom::Document;
+ using PrintPreviewResolver =
+ std::function<void(const mozilla::dom::PrintPreviewResultInfo&)>;
+
+ public:
+ static void CloseProgressDialog(nsIWebProgressListener* aWebProgressListener);
+
+ nsPrintJob();
+
+ // nsISupports interface...
+ NS_DECL_ISUPPORTS
+
+ // nsIObserver
+ NS_DECL_NSIOBSERVER
+
+ NS_DECL_NSIWEBPROGRESSLISTENER
+
+ /**
+ * Initialize for printing, or for creating a print preview document.
+ *
+ * aDocViewerPrint owns us.
+ *
+ * When called in preparation for printing, aOriginalDoc is aDocViewerPrint's
+ * document. The document/viewer may be for a sub-document (an iframe).
+ *
+ * When called in preparation for print preview, aOriginalDoc belongs to a
+ * different docViewer, in a different docShell, in a different TabGroup.
+ * In this case our aDocViewerPrint is the docViewer for the about:blank
+ * document in a new tab that the Firefox frontend code has created in
+ * preparation for PrintPreview to generate a print preview document in it.
+ *
+ * NOTE: In the case we're called for print preview, aOriginalDoc actually
+ * may not be the original document that the user selected to print. It
+ * is not the actual original document in the case when the user chooses to
+ * display a simplified version of a print preview document. In that
+ * instance the Firefox frontend code creates a second print preview tab,
+ * with a new docViewer and nsPrintJob, and passes the previous print preview
+ * document as aOriginalDoc (it doesn't want to pass the actual original
+ * document since it may have mutated)!
+ */
+ nsresult Initialize(nsIDocumentViewerPrint* aDocViewerPrint,
+ nsIDocShell* aDocShell, Document* aOriginalDoc,
+ float aScreenDPI);
+
+ // Our nsIWebBrowserPrint implementation (nsDocumentViewer) defers to the
+ // following methods.
+
+ /**
+ * May be called immediately after initialization, or after one or more
+ * PrintPreview calls.
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult
+ Print(Document* aSourceDoc, nsIPrintSettings* aPrintSettings,
+ nsIWebProgressListener* aWebProgressListener);
+
+ /**
+ * Generates a new print preview document and replaces our docViewer's
+ * document with it. (Note that this breaks the normal invariant that a
+ * Document and its nsDocumentViewer have an unchanging 1:1 relationship.)
+ *
+ * This may be called multiple times on the same instance in order to
+ * recreate the print preview document to take account of settings that the
+ * user has changed in the print preview interface. In this case aSourceDoc
+ * is actually our docViewer's current document!
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult
+ PrintPreview(Document* aSourceDoc, nsIPrintSettings* aPrintSettings,
+ nsIWebProgressListener* aWebProgressListener,
+ PrintPreviewResolver&& aCallback);
+
+ bool IsDoingPrint() const { return mIsDoingPrinting; }
+ bool CreatedForPrintPreview() const { return mCreatedForPrintPreview; }
+ bool HasEverPrinted() const { return mHasEverPrinted; }
+ /// If the returned value is not greater than zero, an error occurred.
+ int32_t GetRawNumPages() const;
+ // Returns whether the preview is empty due to page range exclusion.
+ bool GetIsEmpty() const;
+
+ // Returns the total number of PrintedSheetFrames (i.e. faces of a sheet of
+ // paper) for this print job. (This may be less than the raw number of pages,
+ // due to pages having been skipped in a page range or combined into a single
+ // sheet via pages-per-sheet.)
+ int32_t GetPrintPreviewNumSheets() const;
+ already_AddRefed<nsIPrintSettings> GetCurrentPrintSettings();
+
+ // The setters here also update the DocViewer
+ void SetIsPrinting(bool aIsPrinting);
+ bool GetIsPrinting() const { return mIsDoingPrinting; }
+ void SetIsPrintPreview(bool aIsPrintPreview);
+ bool GetIsCreatingPrintPreview() const { return mIsCreatingPrintPreview; }
+
+ std::tuple<nsPageSequenceFrame*, int32_t> GetSeqFrameAndCountSheets() const;
+
+ bool PrePrintSheet();
+ bool PrintSheet(nsPrintObject* aPOect, bool& aInRange);
+ bool DonePrintingSheets(nsPrintObject* aPO, nsresult aResult);
+
+ nsresult CleanupOnFailure(nsresult aResult, bool aIsPrinting);
+ // If FinishPrintPreview() fails, caller may need to reset the state of the
+ // object, for example by calling CleanupOnFailure().
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult FinishPrintPreview();
+ void FirePrintingErrorEvent(nsresult aPrintError);
+
+ bool CheckBeforeDestroy() const;
+ mozilla::PresShell* GetPrintPreviewPresShell();
+ nsresult Cancel();
+ void Destroy();
+ void DestroyPrintingData();
+
+ private:
+ nsPrintJob& operator=(const nsPrintJob& aOther) = delete;
+
+ ~nsPrintJob();
+
+ MOZ_CAN_RUN_SCRIPT nsresult DocumentReadyForPrinting();
+ MOZ_CAN_RUN_SCRIPT nsresult SetupToPrintContent();
+ nsresult EnablePOsForPrinting();
+ nsPrintObject* FindSmallestSTF();
+
+ bool PrintDocContent(const mozilla::UniquePtr<nsPrintObject>& aPO,
+ nsresult& aStatus);
+ nsresult DoPrint(const mozilla::UniquePtr<nsPrintObject>& aPO);
+
+ nsresult ReflowDocList(const mozilla::UniquePtr<nsPrintObject>& aPO,
+ bool aSetPixelScale);
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ nsresult ReflowPrintObject(const mozilla::UniquePtr<nsPrintObject>& aPO);
+
+ void CalcNumPrintablePages(int32_t& aNumPages);
+ void ShowPrintProgress(bool aIsForPrinting, bool& aDoNotify, Document* aDoc);
+ void SetURLAndTitleOnProgressParams(
+ const mozilla::UniquePtr<nsPrintObject>& aPO,
+ nsIPrintProgressParams* aParams);
+ void EllipseLongString(nsAString& aStr, const uint32_t aLen, bool aDoFront);
+
+ nsresult StartPagePrintTimer(const mozilla::UniquePtr<nsPrintObject>& aPO);
+
+ bool IsWindowsInOurSubTree(nsPIDOMWindowOuter* aDOMWindow) const;
+
+ /**
+ * @return The document from the focused windows for a document viewer.
+ *
+ * FIXME: This is somewhat unsound, this looks at the original document, which
+ * could've mutated after print was initiated.
+ */
+ Document* FindFocusedDocument(Document* aDoc) const;
+
+ /// Customizes the behaviour of GetDisplayTitleAndURL.
+ enum class DocTitleDefault : uint32_t { eDocURLElseFallback, eFallback };
+
+ /**
+ * Gets the title and URL of the document for display in save-to-PDF dialogs,
+ * print spooler lists and page headers/footers. This will get the title/URL
+ * from the PrintSettings, if set, otherwise it will get them from the
+ * document.
+ *
+ * For the title specifically, if a value is not provided by the settings
+ * object or the document then, if eDocURLElseFallback is passed, the document
+ * URL will be returned as the title if it's non-empty (which should always be
+ * the case). Otherwise a non-empty fallback title will be returned.
+ */
+ static void GetDisplayTitleAndURL(Document& aDoc, nsIPrintSettings* aSettings,
+ DocTitleDefault aTitleDefault,
+ nsAString& aTitle, nsAString& aURLStr);
+
+ MOZ_CAN_RUN_SCRIPT nsresult CommonPrint(
+ bool aIsPrintPreview, nsIPrintSettings* aPrintSettings,
+ nsIWebProgressListener* aWebProgressListener, Document* aSourceDoc);
+
+ MOZ_CAN_RUN_SCRIPT nsresult DoCommonPrint(
+ bool aIsPrintPreview, nsIPrintSettings* aPrintSettings,
+ nsIWebProgressListener* aWebProgressListener, Document* aSourceDoc);
+
+ void FirePrintCompletionEvent();
+
+ void DisconnectPagePrintTimer();
+
+ /**
+ * This method is called to resume printing after all outstanding resources
+ * referenced by the static clone have finished loading. (It is possibly
+ * called synchronously if there are no resources to load.) While a static
+ * clone will generally just be able to reference the (already loaded)
+ * resources that the original document references, the static clone may
+ * reference additional resources that have not previously been loaded
+ * (if it has a 'print' style sheet, for example).
+ */
+ MOZ_CAN_RUN_SCRIPT nsresult
+ MaybeResumePrintAfterResourcesLoaded(bool aCleanupOnError);
+
+ bool ShouldResumePrint() const;
+
+ nsresult SetRootView(nsPrintObject* aPO, bool& aDoReturn,
+ bool& aDocumentIsTopLevel, nsSize& aAdjSize);
+ nsView* GetParentViewForRoot();
+ bool DoSetPixelScale();
+ void UpdateZoomRatio(nsPrintObject* aPO, bool aSetPixelScale);
+ MOZ_CAN_RUN_SCRIPT nsresult ReconstructAndReflow(bool aDoSetPixelScale);
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult UpdateSelectionAndShrinkPrintObject(
+ nsPrintObject* aPO, bool aDocumentIsTopLevel);
+ MOZ_CAN_RUN_SCRIPT nsresult InitPrintDocConstruction(bool aHandleError);
+ void FirePrintPreviewUpdateEvent();
+
+ void PageDone(nsresult aResult);
+
+ // The document that we were originally created for in order to print it or
+ // create a print preview of it. This may belong to mDocViewerPrint or may
+ // belong to a different docViewer in a different docShell. In reality, this
+ // also may not be the original document that the user selected to print (see
+ // the comment documenting Initialize() above).
+ RefPtr<Document> mOriginalDoc;
+
+ // The docViewer that owns us, and its docShell.
+ nsCOMPtr<nsIDocumentViewerPrint> mDocViewerPrint;
+ nsWeakPtr mDocShell;
+
+ WeakFrame mPageSeqFrame;
+
+ // We are the primary owner of our nsPrintData member vars. These vars
+ // are refcounted so that functions (e.g. nsPrintData methods) can create
+ // temporary owning references when they need to fire a callback that
+ // could conceivably destroy this nsPrintJob owner object and all its
+ // member-data.
+ RefPtr<nsPrintData> mPrt;
+
+ // The nsPrintData for our last print preview (replaced every time the
+ // user changes settings in the print preview window).
+ // Note: Our new print preview nsPrintData is stored in mPtr until we move it
+ // to mPrtPreview once we've finish creating the print preview.
+ RefPtr<nsPrintData> mPrtPreview;
+
+ RefPtr<nsPagePrintTimer> mPagePrintTimer;
+
+ // If the code that initiates a print preview passes a PrintPreviewResolver
+ // (a std::function) to be notified of the final sheet/page counts (once
+ // we've sufficiently laid out the document to know what those are), that
+ // callback is stored here.
+ PrintPreviewResolver mPrintPreviewCallback;
+
+ float mScreenDPI = 115.0f;
+
+ bool mCreatedForPrintPreview = false;
+ bool mIsCreatingPrintPreview = false;
+ bool mIsDoingPrinting = false;
+ bool mHasEverPrinted = false;
+ bool mProgressDialogIsShown = false;
+ bool mDidLoadDataForPrinting = false;
+ bool mDoingInitialReflow = false;
+ bool mIsDestroying = false;
+ bool mDisallowSelectionPrint = false;
+ bool mIsForModalWindow = false;
+};
+
+#endif // nsPrintJob_h
diff --git a/layout/printing/nsPrintObject.cpp b/layout/printing/nsPrintObject.cpp
new file mode 100644
index 0000000000..ebbde60120
--- /dev/null
+++ b/layout/printing/nsPrintObject.cpp
@@ -0,0 +1,136 @@
+/* -*- 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 "nsPrintObject.h"
+
+#include "nsIContentViewer.h"
+#include "nsContentUtils.h" // for nsAutoScriptBlocker
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsPIDOMWindow.h"
+#include "nsPresContext.h"
+#include "nsGkAtoms.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIBaseWindow.h"
+#include "nsDocShell.h"
+
+#include "mozilla/PresShell.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+
+using namespace mozilla;
+using mozilla::dom::BrowsingContext;
+using mozilla::dom::Document;
+using mozilla::dom::Element;
+using mozilla::dom::Selection;
+
+//---------------------------------------------------
+//-- nsPrintObject Class Impl
+//---------------------------------------------------
+nsPrintObject::nsPrintObject()
+ : mContent(nullptr),
+ mFrameType(eFrame),
+ mParent(nullptr),
+ mHasBeenPrinted(false),
+ mInvisible(false),
+ mDidCreateDocShell(false),
+ mShrinkRatio(1.0),
+ mZoomRatio(1.0) {
+ MOZ_COUNT_CTOR(nsPrintObject);
+}
+
+nsPrintObject::~nsPrintObject() {
+ MOZ_COUNT_DTOR(nsPrintObject);
+
+ DestroyPresentation();
+ if (mDidCreateDocShell && mDocShell) {
+ RefPtr<BrowsingContext> bc(mDocShell->GetBrowsingContext());
+ nsDocShell::Cast(mDocShell)->Destroy();
+ bc->Detach();
+ }
+ mDocShell = nullptr;
+ mTreeOwner = nullptr; // mTreeOwner must be released after mDocShell;
+}
+
+//------------------------------------------------------------------
+
+nsresult nsPrintObject::InitAsRootObject(nsIDocShell* aDocShell, Document* aDoc,
+ bool aForPrintPreview) {
+ NS_ENSURE_STATE(aDocShell);
+ NS_ENSURE_STATE(aDoc);
+
+ MOZ_ASSERT(aDoc->IsStaticDocument());
+
+ mDocShell = aDocShell;
+ mDocument = aDoc;
+
+ // Ensure the document has no presentation.
+ DestroyPresentation();
+
+ return NS_OK;
+}
+
+nsresult nsPrintObject::InitAsNestedObject(nsIDocShell* aDocShell,
+ Document* aDoc,
+ nsPrintObject* aParent) {
+ NS_ENSURE_STATE(aDocShell);
+ NS_ENSURE_STATE(aDoc);
+
+ mParent = aParent;
+ mDocShell = aDocShell;
+ mDocument = aDoc;
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = aDoc->GetWindow();
+ mContent = window->GetFrameElementInternal();
+
+ // "frame" elements not in a frameset context should be treated
+ // as iframes
+ if (mContent->IsHTMLElement(nsGkAtoms::frame) &&
+ mParent->mFrameType == eFrameSet) {
+ mFrameType = eFrame;
+ } else {
+ // Assume something iframe-like, i.e. iframe, object, or embed
+ mFrameType = eIFrame;
+ }
+ return NS_OK;
+}
+
+//------------------------------------------------------------------
+// Resets PO by destroying the presentation
+void nsPrintObject::DestroyPresentation() {
+ if (mDocument) {
+ if (RefPtr<PresShell> ps = mDocument->GetPresShell()) {
+ MOZ_DIAGNOSTIC_ASSERT(!mPresShell || ps == mPresShell);
+ mPresShell = nullptr;
+ nsAutoScriptBlocker scriptBlocker;
+ ps->EndObservingDocument();
+ ps->Destroy();
+ }
+ }
+ mPresShell = nullptr;
+ mPresContext = nullptr;
+ mViewManager = nullptr;
+}
+
+void nsPrintObject::EnablePrinting(bool aEnable) {
+ mPrintingIsEnabled = aEnable;
+
+ for (const UniquePtr<nsPrintObject>& kid : mKids) {
+ kid->EnablePrinting(aEnable);
+ }
+}
+
+bool nsPrintObject::HasSelection() const {
+ return mDocument && mDocument->GetProperty(nsGkAtoms::printselectionranges);
+}
+
+void nsPrintObject::EnablePrintingSelectionOnly() {
+ mPrintingIsEnabled = HasSelection();
+
+ for (const UniquePtr<nsPrintObject>& kid : mKids) {
+ kid->EnablePrintingSelectionOnly();
+ }
+}
diff --git a/layout/printing/nsPrintObject.h b/layout/printing/nsPrintObject.h
new file mode 100644
index 0000000000..781f077ae6
--- /dev/null
+++ b/layout/printing/nsPrintObject.h
@@ -0,0 +1,86 @@
+/* -*- 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/. */
+#ifndef nsPrintObject_h___
+#define nsPrintObject_h___
+
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+
+// Interfaces
+#include "nsCOMPtr.h"
+#include "nsViewManager.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeOwner.h"
+
+class nsIContent;
+class nsPresContext;
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+// nsPrintObject Document Type
+enum PrintObjectType { eDoc = 0, eFrame = 1, eIFrame = 2, eFrameSet = 3 };
+
+//---------------------------------------------------
+//-- nsPrintObject Class
+//---------------------------------------------------
+class nsPrintObject {
+ public:
+ nsPrintObject();
+ ~nsPrintObject(); // non-virtual
+
+ nsresult InitAsRootObject(nsIDocShell* aDocShell,
+ mozilla::dom::Document* aDoc,
+ bool aForPrintPreview);
+ nsresult InitAsNestedObject(nsIDocShell* aDocShell,
+ mozilla::dom::Document* aDoc,
+ nsPrintObject* aParent);
+
+ void DestroyPresentation();
+
+ /**
+ * Recursively sets all the PO items to be printed
+ * from the given item down into the tree
+ */
+ void EnablePrinting(bool aEnable);
+
+ /**
+ * Recursively sets all the PO items to be printed if they have a selection.
+ */
+ void EnablePrintingSelectionOnly();
+
+ bool PrintingIsEnabled() const { return mPrintingIsEnabled; }
+
+ bool HasSelection() const;
+
+ // Data Members
+ nsCOMPtr<nsIDocShell> mDocShell;
+ nsCOMPtr<nsIDocShellTreeOwner> mTreeOwner;
+ RefPtr<mozilla::dom::Document> mDocument;
+
+ RefPtr<nsPresContext> mPresContext;
+ RefPtr<mozilla::PresShell> mPresShell;
+ RefPtr<nsViewManager> mViewManager;
+
+ nsCOMPtr<nsIContent> mContent;
+ PrintObjectType mFrameType;
+
+ nsTArray<mozilla::UniquePtr<nsPrintObject>> mKids;
+ nsPrintObject* mParent; // This is a non-owning pointer.
+ bool mHasBeenPrinted;
+ bool mInvisible; // Indicates PO is set to not visible by CSS
+ bool mDidCreateDocShell;
+ float mShrinkRatio;
+ float mZoomRatio;
+
+ private:
+ nsPrintObject& operator=(const nsPrintObject& aOther) = delete;
+
+ bool mPrintingIsEnabled = false;
+};
+
+#endif /* nsPrintObject_h___ */